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


Автомат заварки - это карусель, на каждой позиции которой выполняется определенная операция, затем поворот и все повторяется.
Работой автомата управляет компьютер и несколько микроконтроллеров. Все это можно представить как взаимодействующие параллельные процессы.
Как же программно смоделировать множество параллельно работающих процессов?
Хочу поделиться опытом разработки системы управления подобным автоматом.
К сути дела
Компьютерная часть системы управления автоматом заварки, это 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. Он является управляющим посредником между компьютером и остальными контроллерами.








Комментарии (8)

hardegor
04.01.2026 19:17Похоже это недописанная часть курсовой студента. А где содержательная часть отчёта? Наверное стоило еще графы переходов построить и траекторию взаимодействия с компьютером. И фотографий сделать побольше, а то непонятна как там всё происходит...

sansan57 Автор
04.01.2026 19:17Мне казалось, и так перегрузил информацией. Писал для профессионалов. разработчиков, думал, может кому пригодится. Цель была описание разработанного языка на примерах фрагментов системы управления автоматом.

Siemargl
04.01.2026 19:17Ужасно. И с точки зрения программирования и с т.з аппаратной.
На что только не пойдут, чтобы не купить готовый Плк за 500-1000$ =)

sansan57 Автор
04.01.2026 19:17Микроконтроллер плюс расширители портов - это тот же ПЛК. От программирования, пусть средствами этого ПЛК, вам все равно не уйти. Чем сложнее будут конструкторские и схемотехнические решения, тем больше придется потрудиться программисту.

Nostromo11
04.01.2026 19:17Borland C++, 8051-based MCU, uVision4, переизобретение RTOS, скриншоты исходников - это какая-то необъяснимая тяга к некрофилии?
eugene_brad
Одна фраза -"через прерывания" - и всего остального в этой статье не надо
sansan57 Автор
Зачем языки высокого уровня, когда есть ассемблер?