- PVSM.RU - https://www.pvsm.ru -
Автомат заварки - это карусель, на каждой позиции которой выполняется определенная операция, затем поворот и все повторяется.
Работой автомата управляет компьютер и несколько микроконтроллеров. Все это можно представить как взаимодействующие параллельные процессы.
Как же программно смоделировать множество параллельно работающих процессов?
Хочу поделиться опытом разработки системы управления подобным автоматом.
Компьютерная часть системы управления автоматом заварки, это Borland C++. В разработке программ ADuC 842 использовал компилятор С среды Keil uVision. В нем нет параллельных потоков Thread Builder C++. Что было сделано, читаем далее.
Термин Процесс (Process) буду использовать как синоним потока. Еще один термин - Интерпретатор (Shell), циклически повторяющаяся конструкция, реализованная различными способами. Это может быть программа обработки прерывания таймера или функция в основной программе.
Я представил процесс в виде таблицы. Каждая строка, это один шаг, содержание строки - программный код. За один проход по таблице выполняется одна строка, буду называть её текущая. Текущая строка может выполняться несколько раз несколькими циклами Интерпретатора.
Интерпретатор состоит из таблиц Процессов. Алгоритм Интерпретатора - это циклический проход по таблицам процессов и выполнение текущей строки таблицы каждого активного процесса. Процесс может запустить другой процесс, проверить ход его выполнения, остановить.
Реализовать такую идею на базе макропроцессора С оказалось несложно. В результате я получил быстро реализуемую, хорошо читаемую и достаточно легко модифицируемую систему. Макроязык описания процессов приведен в Таблице №1.
Таблица №1 Макроязык описания процессов.
|
#define PROCESS_TABLE(PRCS_NBR) xdata unsigned char PRCS_[PRCS_NBR]; xdata unsigned char PRCP_[PRCS_NBR]; xdata long PRCZ_[PRCS_NBR]; data unsigned char PRCI_ |
Таблица процессов: массив номеров текущих строк, для приостановки процесса, задержки процессов, служебная переменная Например, остановить все процессы:[1] [1] PRCS_NBR - количество процессов #define STOP_PROCESS_ALL(PRCS_NBR) for(PRCI_=1;PRCI_<PRCS_NBR;++PRCI_) STOP_PROCESS(PRCI_) |
|
#define PROCESS_TABLE_EXTERN extern xdata unsigned int PRCS_[]; extern xdata unsigned int PRCP_[]; extern xdata long PRCZ_[]; extern data PRCI_ |
Таблица процессов (ссылка) |
|
#define PROCESS(PNAME) switch(PRCS[PNAME]) |
Процесс PNAME - индекс в массивах PROCESS_TABLE |
|
#define STEP(SNBR) case(SNBR): |
Шаг процесса номер SNBR |
|
#define END_STEP break; |
Конец шага |
|
#define GOTO_STEP(PNAME,SNBR) PRCS[PNAME]=SNBR |
Перейти к шагу SNBR процесса PNAME |
|
#define GOTO_STEP_NEXT(PNAME) ++PRCS_[PNAME] |
Перейти к след шагу процесса PNAME |
|
#define GOTO_STEP_BEGIN(PNAME) PRCS_[PNAME]=(PRCS_[PNAME]!=0)?1:0
|
Перейти к 1-му шагу процесса PNAME (защита от перезапуска остановленного извне процесса) |
|
#define SET_SLEEP(PNAME,MS) PRCZ_[PNAME]=(MS/INIT_T_PROCESS)
|
Задать задержку в мс INIT_T_PROCESS – интервал прерываний таймера |
|
#define SET_SLEEP_TIC(PNAME,TIC) PRCZ_[PNAME]=(TIC) |
Задать задержку в количестве прерываний таймера |
|
#define SLEEP_THEN(PNAME) if(PRCZ_[PNAME]>0) —PRCZ_[PNAME];else |
Выполнить задержку, затем ...
|
|
#define END_SLEEP(PNAME) (—PRCZ_[PNAME]<=0) |
Задержка завершена? Можно использовать в конструкциях if(END_SLEEP(PNAME)) (у меня не прижилось)
|
|
#define START_PROCESS(PNAME) PRCS_[PNAME]=1 |
Запустить процесс PNAME |
|
#define CONTINUE_PROCESS(PNAME) PRCS_[PNAME]=PRCP_[PNAME] |
Продолжить процесс PNAME |
|
#define START_PROCESS_STEP(PNAME,NSTEP) PRCS_[PNAME]=NSTEP |
Запустить процесс PNAME с шага NSTEP |
|
#define STOP_PROCESS(PNAME) PRCP_[PNAME]=PRCS_[PNAME]=0 |
Остановить процесс PNAME
|
|
#define PAUSE_PROCESS(PNAME) PRCP_[PNAME]=PRCS_[PNAME],PRCS_[PNAME]=0 |
Приостановить процесс PNAME |
|
#define STOP_PROCESS_ALL(PRCS_NBR) for(PRCI_=1;PRCI_<PRCS_NBR;++PRCI_)STOP_PROCESS(PRCI_)
|
Остановить все процессы (кроме процесса с номером 0)
|
|
#define BEGIN_PROCESS(PNAME) (PRCS_[PNAME]>0) |
Процесс PNAME запущен?
|
|
#define END_PROCESS(PNAME) (PRCS_[PNAME]==0) |
Процесс PNAME завершен?
|
|
#define PROCESS_POINT(POINT_NAME) xdata unsigned int POINT_NAME |
Указатель на процесс, это переменная, содержащая номер процесса. Увеличивает гибкость манипулирования процессами, например: START_PROCESS(POINT_NAME) |
|
#define PROCESS_POINT_EXTERN(POINT_NAME) extern xdata unsigned int POINT_NAME |
Внешняя ссылка на переменную указатель |
[1] [2] Я использую для инициализации таблицы процессов в самом начале.
|
NN шага |
Содержание шага |
|
1 |
Обнулить счетчик срабатывания датчика наличия стекла. ПЦ[1] [1] поворота барабана выдвинуть. ПЦ открытия зажимов стекла выдвинуть. Перейти к шагу N2. |
|
2 |
Задержка 400 мс[2] [3]. Перейти к шагу N3. |
|
3 |
ПЦ горизонтальной подачи стекла выдвинуть. Перейти к шагу N4. |
|
4 |
Анализ датчика наличия стекла. Задержка 300 мс. Перейти к шагу N5. |
|
5 |
ЕСЛИ ПЦ горизонтальной подачи стекла не вышел, ТО СТОП[3] [4], ИНАЧЕ Перейти к шагу N6. |
|
6 |
ПЦ вертикальной подачи стекла выдвинуть. Перейти к шагу N7. |
|
7 |
Задержка 300 мс. Перейти к шагу N8. |
|
8 |
ПЦ поворота барабана задвинуть. ПЦ открытия зажимов стекла задвинуть. Перейти к шагу N9. |
|
9 |
Задержка 300 мс. Перейти к шагу N10. |
|
10 |
ПЦ вертикальной подачи стекла задвинуть. Перейти к шагу N11. |
|
11 |
Задержка 400 мс. Перейти к шагу N12. |
|
12 |
ПЦ горизонтальной подачи стекла задвинуть. Перейти к шагу N13. |
|
13 |
Задержка 300 мс. Перейти к шагу N14. |
|
14 |
СТОП |
[1] [2] Пневмоцилиндр
[2] [5] Время срабатывания механики в миллисекундах
[3] [6] Останов процесса
Таблица №2. Процесс загрузки стекла PozZagrStekla (макроязык)
|
#define PozZagrStekla 12 |
Загрузка стекла, процесс номер 12 |
|
PROCESS (PozZagrStekla) { |
Процесс управления в позиции загрузки стекла |
|
STEP(1) NetStekCnt=0; PCDozStek=1; PCZagimStek=1; PutMCP(GPIOA, MDBa, MDB); SET_SLEEP(PozZagrStekla,400); GOTO_STEP_NEXT(PozZagrStekla); END_STEP |
Счетчик срабатывания. датчика наличия стекла ПЦ поворота барабана выдвинуть ПЦ открытия зажимов стекла выдвинуть |
|
STEP(2) SLEEP_THEN(PozZagrStekla) GOTO_STEP_NEXT(PozZagrStekla); END_STEP |
задержка |
|
STEP(3) PCPodStekGor = 1; SET_SLEEP(PozZagrStekla,300); GOTO_STEP_NEXT(PozZagrStekla); END_STEP |
ПЦ горизонтальной подачи стекла выдвинуть |
|
STEP(4) if(NetStek == 0) ++NetStekCnt; else if(NetStekCnt<2) NetStekCnt=0; SLEEP_THEN(PozZagrStekla) GOTO_STEP_NEXT(PozZagrStekla); END_STEP |
Анализ датчика наличия стекла (НЕТ - два ноля подряд) задержка |
|
STEP(5) PUT_MURT_CMD(PutB, DAT_STOP_GET, ret); SLEEP(2); GET_MURT(GetB, ret); DatMURT=GetB[1]; GOTO_STEP_NEXT(PozZagrStekla); END_STEP |
Запрос состояния датчиков у другого микроконтроллера (не поясняю детально, чтобы не отвлекаться от сути дела) |
|
STEP(6) if(DatPodStekGor==0) STOP_PROCESS(PozZagrStekla); else GOTO_STEP_NEXT(PozZagrStekla); END_STEP |
Если механизм подачи стекла горизонтальный не вышел, Стоп |
|
STEP(7) PCPodStekVer=1; PutMCP(GPIOA,MDBa,MDB); SET_SLEEP(PozZagrStekla,300); GOTO_STEP_NEXT(PozZagrStekla); |
ПЦ вертикальной подачи стекла выдвинуть |
|
STEP(8) SLEEP_THEN(PozZagrStekla) GOTO_STEP_NEXT(PozZagrStekla); END_STEP |
задержка |
|
STEP(9) PCDozStek=0; PCZagimStek=0; PutMCP(GPIOA, MDBa, MDB); SET_SLEEP(PozZagrStekla,300); GOTO_STEP_NEXT(PozZagrStekla); END_STEP |
ПЦ поворота барабана задвинуть ПЦ открытия зажимов стекла задвинуть |
|
STEP(10) SLEEP_THEN(PozZagrStekla) GOTO_STEP_NEXT(PozZagrStekla); END_STEP |
задержка |
|
STEP(11) PCPodStekVer=0; PutMCP(GPIOA, MDBa, MDB); SET_SLEEP(PozZagrStekla,400); GOTO_STEP_NEXT(PozZagrStekla); END_STEP |
ПЦ вертикальной подачи стекла задвинуть |
|
STEP(12) SLEEP_THEN(PozZagrStekla) GOTO_STEP_NEXT(PozZagrStekla); END_STEP |
задержка |
|
STEP(13) PCPodStekGor=0; PutMCP(GPIOA, MDBa, MDB); SET_SLEEP(PozZagrStekla,300); GOTO_STEP_NEXT(PozZagrStekla); END_STEP |
ПЦ горизонтальной подачи стекла задвинуть |
|
STEP(14) SLEEP_THEN(PozZagrStekla) STOP_PROCESS(PozZagrStekla); END_STEP |
задержка стоп |
|
} |
|
Таблица №3. Процесс выгрузки герконов VygrGer
|
#define VygrGer 24 |
Выгрузка герконов, процесс номер 24 |
|
PROCESS (VygrGer) { |
Процесс управления в позиции выгрузки герконов |
|
STEP(1) PCVygrGer = 1; PutMCP(GPIOB, MDBb, MDB); SET_SLEEP(VygrGer,1000); GOTO_STEP_NEXT(VygrGer); END_STEP |
ПЦ открытия зажимов стекла выдвинуть |
|
STEP(2) SLEEP_THEN(VygrGer) GOTO_STEP_NEXT(VygrGer); END_STEP |
задержка
|
|
STEP(3) PCVygrGer = 0; PutMCP(GPIOB, MDBb, MDB); STOP_PROCESS(VygrGer); END_STEP |
ПЦ открытия зажимов стекла задвинуть
стоп |
|
} |
|
Разработка языка описания процессов (таблица №1) шла параллельно с разработкой самой системы управления каруселью, а средства макропроцессора С избавили от необходимости разрабатывать какой-либо интерпретатор.
Писать и корректировать такие тексты оказалось довольно просто.
Интересно, что тело основной программы микроконтроллера, выполняющего команды компьютера, тоже можно представить в виде процесса, каждая строка которого выполняет команду. Привожу пример такого процесса ARBITR .
Таблица №4. Главный процесс ARBITR (фрагмент)
|
#define SHELL_PROCESS_1 while(1) |
Интерпретатор процессов №1 |
|
#define ARBITR 0 |
Главный процесс ARBITR номер 0 |
|
PROCESS_TABLE(PRCS_N); |
Таблица процессов |
|
xdata[1] [1] unsigned char PutB[BUF_LEN]; xdata unsigned char GetB[BUF_LEN]; |
Буферы ввода-вывода |
|
void main(void) { |
|
|
SHELL_PROCESS_1 { |
Интерпретатор номер 1 (основная программа) |
|
if (CONNPC==CLOSE) {CONNECT_PC;} GET_PC(GetB,ret); START_PROCESS_STEP(ARBITR,(ret==RET_OK)?GetB[1]:NULL_CMD); |
Установить соединение с PC
Прием от PC (запуск процесса ARBITR с шага GetB[1] или NULL_CMD |
|
PROCESS(ARBITR) { |
|
|
STEP(NULL_CMD) END_STEP |
Нет команды |
|
STEP(CONN_CLOSE) CONNPC=CLOSE; PUT_PC_CMD(PutB, RET_OK); END_STEP |
Закрыть соединение с PC |
|
STEP(START_WORK) START_PROCESS(WorkKar); PUT_PC_CMD(PutB, RET_OK); END_STEP |
Запуск рабочего цикла карусели (процесс WorkKar) Ответить компьютеру |
|
STEP(STOP_WORK) PUT_MURT_CMD(PutB, ENABLE_OFF, ret); STOP_PROCESS_ALL; PUT_PC_CMD(PutB, RET_OK); END_STEP |
Остановить карусель (другой микроконтроллер) Остановить все процессы Ответить компьютеру |
|
} |
|
|
} |
|
|
} |
|
[1] [2] внешняя оперативная память ADuC
В качестве приложений привожу фрагменты реальных модулей, составляющих прошивку микроконтроллера, условно называемого ARBITR. Он является управляющим посредником между компьютером и остальными контроллерами.








Автор: sansan57
Источник [7]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/mikrokontrollery/440988
Ссылки в тексте:
[1] [1]: #_ftn1
[2] [1]: #_ftnref1
[3] [2]: #_ftn2
[4] [3]: #_ftn3
[5] [2]: #_ftnref2
[6] [3]: #_ftnref3
[7] Источник: https://habr.com/ru/articles/982814/?utm_source=habrahabr&utm_medium=rss&utm_campaign=982814
Нажмите здесь для печати.