Короткое введение


Автомат заварки - это карусель, на каждой позиции которой выполняется определенная операция, затем поворот и все повторяется.
Работой автомата управляет компьютер и несколько микроконтроллеров. Все это можно представить как взаимодействующие параллельные процессы.
Как же программно смоделировать множество параллельно работающих процессов?
Хочу поделиться опытом разработки системы управления подобным автоматом.
К сути дела
Компьютерная часть системы управления автоматом заварки, это 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] 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] Я использую для инициализации таблицы процессов в самом начале.
Вот примеры описания процессов загрузки стекла и выгрузки герконов.
Процесс загрузки стекла (описание)
NN шага | Содержание шага |
1 | Обнулить счетчик срабатывания датчика наличия стекла. ПЦ[1] поворота барабана выдвинуть. ПЦ открытия зажимов стекла выдвинуть. Перейти к шагу N2. |
2 | Задержка 400 мс[2]. Перейти к шагу N3. |
3 | ПЦ горизонтальной подачи стекла выдвинуть. Перейти к шагу N4. |
4 | Анализ датчика наличия стекла. Задержка 300 мс. Перейти к шагу N5. |
5 | ЕСЛИ ПЦ горизонтальной подачи стекла не вышел, ТО СТОП[3], ИНАЧЕ Перейти к шагу N6. |
6 | ПЦ вертикальной подачи стекла выдвинуть. Перейти к шагу N7. |
7 | Задержка 300 мс. Перейти к шагу N8. |
8 | ПЦ поворота барабана задвинуть. ПЦ открытия зажимов стекла задвинуть. Перейти к шагу N9. |
9 | Задержка 300 мс. Перейти к шагу N10. |
10 | ПЦ вертикальной подачи стекла задвинуть. Перейти к шагу N11. |
11 | Задержка 400 мс. Перейти к шагу N12. |
12 | ПЦ горизонтальной подачи стекла задвинуть. Перейти к шагу N13. |
13 | Задержка 300 мс. Перейти к шагу N14. |
14 | СТОП |
[1] Пневмоцилиндр
[2] Время срабатывания механики в миллисекундах
[3] Останов процесса
Таблица №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] 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] внешняя оперативная память ADuC
В качестве приложений привожу фрагменты реальных модулей, составляющих прошивку микроконтроллера, условно называемого ARBITR. Он является управляющим посредником между компьютером и остальными контроллерами.








