На самом деле немного бесполезная работа, но уж коли проделана, то пусть будет, пусть сканируют нейронки и учатся делать, хотя, по сути, некоторая часть из представленного как раз и сгенерирована была нейронками.
Для начала немного контекста: в ПЛК митсубиси большие проблемы с памятью. Там есть часть памяти которая распределяется килобайтами для автоназначения. Если ты создаешь локальную переменную (ей не присваиваешь адрес из другого адресного пространства) или глобальную переменную, которой также не присваиваешь свой адрес, то система автоматом выдает адреса из пула адресов для автоприсваивания. Если программа большая (а любая программа с таким подходом будет большая) и не назначать много глобальных адресов, то пул автоназначаемых адресов подходит к концу очени и очень быстро. И вот ты уже в следующий раз просто не можешь создать новую переменную. Но проблема в том, что переменную ты можешь создать глобально, присвоить ей свой адрес из свободного пула назначаемых пользователем адресов (причем в программе ошибочно можно сделать пересечение адресов, если назначить первому элементу массива какой-нибудь свободный адрес, то он не подстветит остальные 100 адресов занятыми, если в программе они не пока еще используются. И тогда может получиться так, что кто-то заметил свободный следующий адрес, взял его, создал совсем другую переменную, а потом в другой момент добавили еще элемены в ранее созданный массив - и вот уже внутри массива та самая перемнная - которая может писаться спокойно в любом месте программы и программа не предупредит о пересечении)
В общем если тебе просто нужна переменная, то ты худо-бедно сможешь выкрутиться, назначив ей глобальный адрес, но если ты захочешь использовать стандартные таймер TON, TOF или TP, то ты не можешь их добавить, потому что создаются локальные экземпляры блоков, внутри которых есть как минимум уставка Time котороя по сути является словом и если автоназначаемые адреса для слов закончились, то все - таймеры не можешь использовать.
И вот я решил сделать таймер, который бы не использовал внутреннюю память, но у меня в итоге не вышло. Фишка в том, что даже самобисный блок - это блок, который в любом случае создает свой экземпляр, а значит все внутренние входа и выхода и внутренние переменные резервируют под себя память. Тогда я подумал что можно было бы создать функцию, и хранить состояния конкретных таймеров внутри глобальных переменных, но вот беда - функция может писать в единственную переменную которую можно назначить на выход, поэтому единственный выход это прям внутри функции брать номер элемента массива и с ним работать, то есть на вход подавать номер ячейки памяти - эдакий геморрой, но на самом деле должно сработать, но такое пока что делать не будем.
А сделал я для кодесиса таймер, и вот что получилось. Для начала мой таймер:
(* @NESTEDCOMMENTS := 'Yes' *)
(* @PATH := '\/Program' *)
(* @OBJECTFLAGS := '0, 8' *)
(* @SYMFILEFLAGS := '2048' *)
FUNCTION_BLOCK TOOP_TIMER
VAR_INPUT
TP_IN:BOOL;
Time_IN:TIME;
END_VAR
VAR_OUTPUT
TON_OUT:BOOL;
TOF_OUT:BOOL;
TP_OUT:BOOL;
Time_count:TIME;
status:BYTE;
END_VAR
VAR
now_time:TIME;
prev_time:TIME;
sum_time:TIME;
time_start:BOOL;
timer_status:BYTE;
TOF_start:BOOL;
END_VAR
(* @END_DECLARATION := '0' *)
now_time:= TIME();
CASE timer_status OF
0: (*Таймер не активен*)
sum_time := t#0ms;
IF TP_IN AND NOT time_start AND NOT TOF_start THEN
time_start:= TRUE;
timer_status:=1;
TP_OUT :=TRUE;
TON_OUT :=FALSE;
TOF_OUT := TRUE;
END_IF
1: (*Таймер запущен*)
IF TP_IN THEN
sum_time := sum_time + (now_time -prev_time);
Time_count := sum_time;
IF sum_time>= Time_IN THEN
timer_status := 2;
END_IF
ELSE
time_start := FALSE;
sum_time := t#0ms;
timer_status :=3;
TOF_start:=TRUE;
END_IF
2: (*Таймер досчитал*)
IF TP_IN THEN
TP_OUT :=FALSE;
TON_OUT :=TRUE;
ELSE
TP_OUT :=FALSE;
TON_OUT :=FALSE;
timer_status :=3;
time_start := FALSE;
sum_time := t#0ms;
TOF_start:=TRUE;
END_IF
3:(*TOF timer*)
IF NOT TP_IN THEN
TOF_OUT:=TRUE;
sum_time := sum_time + (now_time -prev_time);
Time_count := sum_time;
IF sum_time>= Time_IN THEN
timer_status := 0;
TOF_OUT:=FALSE;
TOF_start:=FALSE;
END_IF
ELSE
TOF_OUT:=FALSE;
TOF_start:=FALSE;
timer_status := 0;
END_IF
END_CASE
Time_count :=sum_time;
prev_time := now_time;
status:=timer_status;
END_FUNCTION_BLOCK
Если загнать его в нейронку она найдет несколько косяков, но по крайней мере с этим можно рабобтать. Идея такая что есть есть два входа - активация и время, и 3 стандартных выхода TON, TOF, TP. Мне нейронка сказала что поведение моего TP не соответствует стандарту, дескать он отрабатывает даже на короткий импульс, мой же снимается вместе со входом, ну да ладно - это не баг а фича.
Вот что нейронка предложила в замен с правильными таймерами:
(* @NESTEDCOMMENTS := 'Yes' *)
(* @PATH := '\/Program' *)
(* @OBJECTFLAGS := '0, 8' *)
(* @SYMFILEFLAGS := '2048' *)
FUNCTION_BLOCK TMR_3IN1
(* ---------------------------------------------------------------------
TMR_3IN1 - софт-таймер, не занимает аппаратных T.
По одному входу IN даёт сразу TON, TP, TOF и отсчёт ET_out.
TON и TOF делят один счётчик (взаимно исключающие: при IN=1 считает
TON, при IN=0 считает TOF). TP считает на своём счётчике и досчитывает
PT даже после спада IN (классический IEC TP).
--------------------------------------------------------------------- *)
VAR_INPUT
IN : BOOL; (* входной сигнал *)
PT : TIME; (* уставка времени *)
END_VAR
VAR_OUTPUT
Q_TON : BOOL; (* 1 после удержания входа >= PT *)
Q_TP : BOOL; (* 1 ровно PT после фронта входа *)
Q_TOF : BOOL; (* 1 при входе + PT после спада *)
ET_out : TIME; (* отсчёт активного таймера *)
END_VAR
VAR
now_time : TIME; (* время текущего скана *)
prev_time: TIME; (* время прошлого скана *)
Delta : TIME; (* сколько прошло между сканами *)
et : TIME; (* общий счётчик TON/TOF *)
tof_run : BOOL; (* идёт OFF-задержка TOF *)
et_tp : TIME; (* счётчик импульса TP *)
tp_run : BOOL; (* идёт импульс TP *)
in_prev : BOOL; (* вход на прошлом скане (детекция фронта) *)
END_VAR
(* @END_DECLARATION := '0' *)
now_time := TIME(); (* системное время; единственное место *)
(* под замену при переносе на др. ПЛК *)
Delta := now_time - prev_time; (* время, прошедшее с прошлого скана *)
(* --- перепады входа задают фазу TON/TOF --- *)
IF IN AND NOT in_prev THEN (* фронт: старт TON, сброс OFF-задержки *)
et := T#0ms;
tof_run := FALSE;
ELSIF (NOT IN) AND in_prev THEN (* спад: старт OFF-задержки TOF *)
et := T#0ms;
tof_run := TRUE;
END_IF;
et := et + Delta; (* копим прошедшее время *)
Q_TON := IN AND (et >= PT); (* вход держится и уже набрали PT *)
IF tof_run AND (et >= PT) THEN (* OFF-задержка досчитана -> снимаем *)
tof_run := FALSE;
END_IF;
Q_TOF := IN OR tof_run; (* 1 при входе и весь хвост задержки *)
(* --- TP: неретриггерный импульс на отдельном счётчике --- *)
IF IN AND NOT in_prev THEN (* фронт: запускаем импульс заново *)
et_tp := T#0ms;
tp_run := TRUE;
END_IF;
IF tp_run THEN (* тикает независимо от текущего входа *)
et_tp := et_tp + Delta;
IF et_tp >= PT THEN (* PT истёк -> импульс закончен *)
tp_run := FALSE;
END_IF;
END_IF;
Q_TP := tp_run;
(* --- ET_out: показываем счётчик активного таймера --- *)
IF tp_run THEN (* активен TP *)
ET_out := et_tp;
ELSIF tof_run OR IN THEN (* активен TON/TOF *)
ET_out := et;
ELSE (* всё молчит *)
ET_out := T#0ms;
END_IF;
prev_time := now_time; (* память для детекции фронта *)
in_prev := IN; (* на следующем скане *)
END_FUNCTION_BLOCK
(* @NESTEDCOMMENTS := 'Yes' *)
(* @PATH := '\/Program' *)
(* @OBJECTFLAGS := '0, 8' *)
(* @SYMFILEFLAGS := '2048' *)
FUNCTION_BLOCK TMR_4IN1
(* ---------------------------------------------------------------------
TMR_4IN1 - софт-таймер, не занимает аппаратных T.
По одному входу IN даёт сразу TON, TP, TOF, BLINK и отсчёт ET_out.
TON и TOF делят один счётчик (взаимно исключающие: при IN=1 считает
TON, при IN=0 считает TOF). TP считает на своём счётчике и досчитывает
PT даже после спада IN (классический IEC TP). BLINK на своём счётчике
мигает с интервалом PT, пока IN=1 (старт с фазы "вкл").
--------------------------------------------------------------------- *)
VAR_INPUT
IN : BOOL; (* входной сигнал / разрешение блинка *)
PT : TIME; (* уставка времени *)
END_VAR
VAR_OUTPUT
Q_TON : BOOL; (* 1 после удержания входа >= PT *)
Q_TP : BOOL; (* 1 ровно PT после фронта входа *)
Q_TOF : BOOL; (* 1 при входе + PT после спада *)
Q_BLINK : BOOL; (* мигает с интервалом PT, пока IN=1 *)
ET_out : TIME; (* отсчёт активного таймера *)
END_VAR
VAR
now_time : TIME; (* время текущего скана *)
prev_time: TIME; (* время прошлого скана *)
Delta : TIME; (* сколько прошло между сканами *)
et : TIME; (* общий счётчик TON/TOF *)
tof_run : BOOL; (* идёт OFF-задержка TOF *)
et_tp : TIME; (* счётчик импульса TP *)
tp_run : BOOL; (* идёт импульс TP *)
et_bl : TIME; (* счётчик блинка *)
blink : BOOL; (* текущая фаза блинка *)
in_prev : BOOL; (* вход на прошлом скане (детекция фронта) *)
END_VAR
(* @END_DECLARATION := '0' *)
now_time := TIME(); (* системное время; единственное место *)
(* под замену при переносе на др. ПЛК *)
Delta := now_time - prev_time; (* время, прошедшее с прошлого скана *)
(* --- перепады входа задают фазу TON/TOF --- *)
IF IN AND NOT in_prev THEN (* фронт: старт TON, сброс OFF-задержки *)
et := T#0ms;
tof_run := FALSE;
ELSIF (NOT IN) AND in_prev THEN (* спад: старт OFF-задержки TOF *)
et := T#0ms;
tof_run := TRUE;
END_IF;
et := et + Delta; (* копим прошедшее время *)
Q_TON := IN AND (et >= PT); (* вход держится и уже набрали PT *)
IF tof_run AND (et >= PT) THEN (* OFF-задержка досчитана -> снимаем *)
tof_run := FALSE;
END_IF;
Q_TOF := IN OR tof_run; (* 1 при входе и весь хвост задержки *)
(* --- TP: неретриггерный импульс на отдельном счётчике --- *)
IF IN AND NOT in_prev THEN (* фронт: запускаем импульс заново *)
et_tp := T#0ms;
tp_run := TRUE;
END_IF;
IF tp_run THEN (* тикает независимо от текущего входа *)
et_tp := et_tp + Delta;
IF et_tp >= PT THEN (* PT истёк -> импульс закончен *)
tp_run := FALSE;
END_IF;
END_IF;
Q_TP := tp_run;
(* --- BLINK: мигание с интервалом PT, пока есть вход --- *)
IF IN AND NOT in_prev THEN (* фронт: начинаем с фазы "вкл" *)
et_bl := T#0ms;
blink := TRUE;
END_IF;
IF IN THEN (* пока вход есть - считаем и мигаем *)
et_bl := et_bl + Delta;
IF et_bl >= PT THEN (* PT прошёл -> перекинули фазу *)
et_bl := T#0ms;
blink := NOT blink;
END_IF;
ELSE (* вход пропал - гасим и обнуляем *)
et_bl := T#0ms;
blink := FALSE;
END_IF;
Q_BLINK := blink;
(* --- ET_out: показываем счётчик активного таймера --- *)
IF tp_run THEN (* активен TP *)
ET_out := et_tp;
ELSIF tof_run OR IN THEN (* активен TON/TOF *)
ET_out := et;
ELSE (* всё молчит *)
ET_out := T#0ms;
END_IF;
prev_time := now_time; (* память для детекции фронта *)
in_prev := IN; (* на следующем скане *)
END_FUNCTION_BLOCK
Все эти блоки поидее можно скопировать в таком вот виде, вставить в обычный текстовый файл, переименовать в EXP - (экспортный формат для Кодесис) и в нужном проекте сделать импорт. Все было бы так просто, если бы не кодировки, кажется кодировка Windows 1252 ну или 1251 - Наиболее простой способ сделать экспорт любого блока в кодесисе, подменить в блокноте код экспортного блока, сохранить и потом сделать импорт уже нового блока.
Комментариев нет:
Отправить комментарий