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

Так выглядит замыкающий геркон - контакт детали и стеклянная колба
Так выглядит замыкающий геркон - контакт детали и стеклянная колба
Автомат заварки замыкающих герконов (Standex Electronics Japan  Company Video)
Автомат заварки замыкающих герконов (Standex Electronics Japan  Company Video)

Автомат заварки - это карусель, на каждой позиции которой выполняется определенная операция, затем поворот и все повторяется.

Работой автомата управляет компьютер и несколько микроконтроллеров. Все это можно представить как взаимодействующие параллельные процессы.

Как же программно смоделировать множество параллельно работающих процессов?

Хочу поделиться опытом разработки системы управления подобным автоматом.

К сути дела

Компьютерная часть системы управления автоматом заварки, это 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);
END_STEP

ПЦ вертикальной подачи стекла выдвинуть

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. Он является управляющим посредником между компьютером и остальными контроллерами.