разработка и программирование электронных устройств

Операционная система реального времени (ОСРВ) TICS

Начну издалека. Все помнят знаменитую компьютерную игру Warcraft 2. Непонятно как, но разработчики как-то умудрились выжать из скромных аппаратных ресурсов ( у меня эта игра отлично работала на 486-ом процессоре с тактовой частотой в 33МГц ) шикарную для того времени графику. Игра стартовала из-под MS-DOS и занимала на жестком диске меньше 50 Мб дискового пространства. Почему нынешние компьютерные игры не могут похвастаться такой оптимальностью использования доступных ресурсов? Да потому что излишество этих-самых ресурсов расслабило программистов, борьба за лишний килобайт памяти не ведется ( если вообще присутствует такое понятие ). Асы второго Warcraft-а ушли на пенсию , поливают цветы и садят деревья у себя на дачах.

А мораль сей басни такова, что не все старое является устаревшим и не все новое обязательно лучше давно позабытого старого.

В принципах построения встраиваемых операционных систем реального времени за последние 20 лет кардинальных изменений не произошло.

Просматривая исходные тексты операционных систем для микроконтроллеров, часто бросаются в глаза пробелы в знаниях разработчиков этого фундаментального кода. Тяжелый для понимания стиль исходного текста очень часто заставляет плюнуть и писать все самому, пусть не оптимально, зато понятно.

Разработанная в 90-х годах прошлого века ОСРВ TICS проверена временем и стабильной работой внутри бортового оборудования в различных стратегически важных отраслях. Система написана на языке программирования С и хорошо документирована ( естественно документация на английском языке ).

В настоящее время исходные тексты TICS распространяются на условиях лицензии GPL. Исходный код ОСРВ TICS вызывает восхищение своей простотой и логичностью, в общем — ничего лишнего.

Система в исходном виде рассчитана на компиляцию с помощью компилятора Borland Turbo C/C++ в операционной системе MS-DOS. TICS ориентирована на использование внутри процессоров архитектуры Intel x86. Так исторически сложилось, что ранние версии встраиваемых систем работали под управлением MS-DOS на процессорах Intel.

Хотя в документации TICS написано, что перенос системы на другие архитектуры микропроцессоров/микроконтроллеров не составит большого труда, до сих пор никто не предоставил в общее использование ( к этому обязывает лицензия GPL ) версии для микроконтроллеров.

Чтобы посмотреть на TICS в работе необходимо установить компилятор Borland Turbo C++ 3.1, рассчитаный на работу в MS-DOS. Компилятор является устаревшим и уже давно не используется для разработки программ ( в мои студенческие годы его использовали для обучения студентов программированию на языке С ).

Операционная система MS-DOS давно отошла в мир иной историю. Иногда становится проблематично запустить DOS- программу на новых компьютерах с 64-х разрядной версией операционной системы.

Для совместимости программ и просто ностальгии созданы эмуляторы DOS. Одним из таких является dosbox, который существует в версиях под Windows и Linux.

Установочный пакет DOSbox под Windows находится в моем репозитории. Нормально устанавливается и работает как под 32-х , так и под 64-х -разрядную Windows.

Далее я распишу как установить и настроить dosbox в Fedora Linux.

Итак, сначала необходимо загрузить программу из сети интернет и установить ее. Для этого выполним привычную в Fedora Linux команду с правами root :

# yum install dosbox

После установки избавимся от супер-прав и запустим dosbox из командной строки Linux :

1
2
3
# exit
$ cd
$ dosbox

В результате выполнения последней команды запуститься новое окно dosbox. В окне терминала Linux в одной из строк будет запись, подобная следующей :

Writing it to /home/Petruha/.dosbox/dosbox-0.74.conf

Это конфигурационный файл программы dosbox. В нем мы подправим записи настроек так, чтобы эмулятор запускался в полноэкранном режиме. Также установим приемлемую скорость работы CPU. Открываем и правим конфигурационный файл с помощью команд :

$ vi ~/.dosbox/dosbox-0.74.conf

В режиме редактирования vim запишем в конфигурационный файл такие параметры :

1
2
3
4
fullscreen = true
fullresolution = 640x480 ( тут устанавливается наиболее приемлемое для вас значение )
[cpu]
cycles = 20000 ( для современного процессора )

Сохраняем вновь установленные настройки и запускаем dosbox из основного меню. Но перед этим необходимо создать каталог для монтирования файловой системы dosbox.

Создаем в своем рабочем каталоге каталог с названием «с» и копируем туда скачанный с моего репозитория архив, содержащий компилятор Turbo C++ 3.1 , исходники TICS.

Эмулятор можно запустить из меню «Приложения->Системные->DOSBox».Появится приглашение командной строки :

Z:\  _

Диск Z — виртуальный, он создается в оперативной памяти. Для того, чтобы иметь доступ к привычному в DOS жесткому диску C:\ , необходимо выполнить его монтирование :

Z:\ mount c ~/c

В ответ должно появится сообщение, похожее на следующее :

Drive C is mounted as local directory /home/Petruha/c/

Перейдем на диск «C:\» и запустим Volkov Commander :

1
2
Z:\ C:
C:\ vc

Следующим нашим шагом будет проверка возможностей эмулятора dosbox. Для этого запустите в VC программу C:\GAMES\Warcraft2\War2.exe( не забудьте подключить колонки или наушники ).

Наигравшись в старый добрый Warcraft 2 , я решил запустить парочку примеров для TICS. Вот что из этого получилось.

Общие моменты относительно TICS.

Как любая -другая операционная система реального времени TICS оперирует таким понятием как задача. Задача в TICS — это функция языка программирования С , которая не возвращает значения и не принимает параметров ( речь идет об основном типе задач в TICS )и содержит внутри бесконечный цикл.

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

Каждая такая задача имеет в своем распоряжении стек размером 1кБ ( этот размер можно менять ). При выгрузке задачи из памяти в следствии готовности более приоритетной задачи все ее параметры и значения внутренних регистром процессора( контекст задачи ) сохраняются в стеке и при возобновлении работы опять загружаются в память.

1 Кб памяти для каждой задачи может оказаться слишком «жирным» куском для микроконтроллеров с объемом ОЗУ в несколько килобайт. Поэтому существует другой тип задач в TICS — кооперативные задачи.

Кооперативные задачи используют один общий стек размером в 1Кб , при этом количество кооперативных задач может достигать сотни.

Кооперативная задача в TICS — функция языка С, которая не возвращает значения , а в качестве параметра принимает указатель на сообщение. Внутри кооперативной задачи находится обработчик сообщений , который в зависимости от значения принятого сообщения выполняет определенное действие и отдает управление ( происходит возврат из функции ).

Для программной обработки различных аппаратных модулей микроконтроллера кооперативная задача является наиболее подходящей.

Существует еще один тип задач — задача с разделением рабочего времени. Каждой такой задаче отводится временной интервал работы , после которого управление принудительно отдается следующей в списке задаче. Приоритет для этого типа задач не назначается. Применение данного типа задач совместно с более приоритетными задачами может привести к тому, что первые никогда не будут запущены.

Пример первый. Приоритеты задач, межпроцессное взаимодействие.

Итак, в первом примере мы создадим четыре задачи, каждая из которых будет выводить внутри бесконечного цикла на экран свое название и запускать паузу в 1 секунду. Результатом работы программы будет одновременный вывод четырех названий задач на экран каждую секунду. При нажатии любой клавиши программа завершается( за это отвечает еще одна задача kbMon).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#include "tics.h"
#include "ticsmain.h"
#include "ticsext.h"
#include "conio.h"
 
#define LEN_MSG_SPACE 120
#define LEN_TCB_SPACE 40
#define LEN_STACK_SPACE (256 * LEN_TCB_SPACE)
 
typeMsg MsgSpace[LEN_MSG_SPACE];
typeTcb TcbSpace[LEN_TCB_SPACE];
unsigned char StackSpace[LEN_STACK_SPACE];
static 	typeTcb * tcbA, * tcbB, * tcbC, * tcbD, * kbmon_tcb;
void kbMon(void)
{
	while (TRUE)
       {
		if (kbhit()) {
			exitTics();
			exit(0);
		}
		pause(100);
	}
}
void taskA(void)
{
	while(TRUE)
	{
	       printf(" task A ");
	       pause(1000);
	}
}
void taskB(void)
{
    while(TRUE)
    {
           printf(" task B ");
           pause(1000);
    }
}
void taskC(void)
{
    while(TRUE)
    {
           printf(" task C ");
           pause(1000);
    }
}
void taskD(void)
{
    while(TRUE)
    {
           printf(" task D ");
           pause(1000);
    }
}
void main(void)
{
	typeTics * tics;
	tics = makeTics(MsgSpace, sizeof(MsgSpace), StackSpace, sizeof(StackSpace),
	TcbSpace, sizeof(TcbSpace));
	startTics(tics);
	tcbA = makeTask(taskA,0);
	startTask(tcbA);
	tcbB = makeTask(taskB,0);
	startTask(tcbB);
	tcbC = makeTask(taskC, 0 );
	startTask(tcbC);
	tcbD = makeTask(taskD, 0);
	startTask(tcbD);
	kbmon_tcb = makeTask(kbMon,0);
	startTask(kbmon_tcb);
	suspend();
}

Теперь обсудим как это все работает. Поскольку мы не назначаем задачам приоритеты, то по-умолчанию каждой из задач будет присвоен приоритет DEF_PRI, тоесть все они получат одинаковый приоритет. На экране монитора названия задач будут появляться в порядке их запуска, поскольку приоритеты одинаковы, то планировщик не будет выполнять никаких перестановок в созданной им очереди задач.

Первой запуститься задача taskA , она выведет свое название на экран и запустит функцию паузы pause(). Во время выжидания паузы выполниться переключение на следующую готовую к запуску задачу taskB. Так будет происходить с каждой из задач (поскольку они одинаковые ) до выполнения последней задачи taskD. Когда taskD запустит паузу готовых к выполнению задач в списке уже не будет и планировщик будет ждать в течении интервала паузы до готовности первой задачи ( и всех последующих ). Далее процесс повторяется циклически до нажатия на любую клавишу( для выхода ).

Если удалить паузу с тела одной из задач, то произойдет зацикливании программы внутри бесконечного цикла этой задачи, поскольку планировщик больше не сможет отобрать у нее управление для выполнения других действий.

Для удобства анализа результатов работы программы можно перенаправить вывод програмы с экрана в файл. Для этого создадим файл hello.bat , в который запишем :

hello > hello.txt

После очередной компиляции нашего примера будем запускать файл hello.bat .

Преднамеренно изменим приоритет одной из задач, – повысим его. В ОСРВ TICS большему приоритету задачи соответствует меньшее числовое значение. Все значения приоритетов назначаются относительно значения DEF_PRI. Тоесть, если мы хотим повысить приоритет одной из задач относительно остальных, то нам необходимо присвоить ей приоритет DEF_PRI – 1 .

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void main(void)
{
	typeTics * tics;
	tics = makeTics(MsgSpace, sizeof(MsgSpace), StackSpace, sizeof(StackSpace),
	TcbSpace, sizeof(TcbSpace));
	startTics(tics);
	tcbA = makeTask(taskA,0);
	startTask(tcbA);
	tcbB = makeTask(taskB,0);
	startTask(tcbB);
	tcbC = makeTask(taskC, 0 );
	startTask(tcbC);
	tcbD = makeTask(taskD, 0);
        tcbD->pri = DEF_PRI - 1;	
        startTask(tcbD);
	kbmon_tcb = makeTask(kbMon,0);
	startTask(kbmon_tcb);
	suspend();
}

Теперь название задачи с повышенным приоритетом выводится на экран раньше остальных.

Следующим примером мы «прощупаем» межпроцессное взаимодействие(между задачами )
с помощью сообщений. Сообщение в TICS — это структура данных, которая помимо самого сообщения как флага для выполнения каких-либо действий может содержать прикрепленные данные числового целого типа int , long и указатель на блок данных void *. Сообщениям присваиваются номера в диапазоне от 0 до 32767.

Итак , изменяем пример программы таким образом, что задача taskC после отображения своего названия ожидает прихода сообщения HELLO при помощи функции waitMsg(). После его прихода сообщение удаляется из очереди с помощью freeMsg. HELLO создается и отправляется задачей taskD с помощью makeMsg() и sendMsg(). При создании сообщения указывается его номер и указатель на блок управления задачей, которой это сообщение отправляется.

1
2
3
4
5
6
7
8
9
10
11
#define HELLO   1000
void taskC(void)
 {
	typeMsg * msg;
	while(TRUE)
	{
	      printf(" task C ");
	      msg = waitMsg(HELLO);
	      freeMsg(msg);
	}
 }

Получив сообщение , задача taskC удаляет его из очереди и продолжает свое выполнение.
Запускаем программу через hello.bat, ждем несколько секунд и нажимаем на любую клавишу для выхода из программы. Теперь в текстовом файле hello.txt можно наблюдать результат ее работы.

Первый раз последовательность запуска задач соответсвует порядку их старта. После первого прогона внутри задачи taskC запускается цикл ожидания сообщения HELLO. Как только задача taskD отправит сообщение HELLO, это сообщение будет записано в очередь сообщений для taskC и следующей запущенной задачей будет taskC.

1
2
3
4
5
6
7
8
9
void taskD(void)
 {
	while(TRUE)
	{
		printf(" task D ");
	       	sendMsg(makeMsg(tcbC, HELLO));
	       	pause(1000);
	}
 }

Для понимания исходного кода TICS , необходимо иметь представление об структурах языка С и указателях на структуры, поскольку на этих понятиях построена вся структура данных TICS.

Очереди сообщений представляют собой двухсвязные списки. Двойной связанный список — это последовательность С- структур, в каждой из которых хранится указатель на следующую и предыдущую структуру в этом списке( для первой структуры в списке указатель на предыдущую структуру равен NULL, для последней — указатель на следующую структуру равен NULL ). Таким образом можно легко перемещаться по всему списку от начала до конца и наоборот.

Пример второй. Использование кооперативных задач и таймеров в TICS.

Рассмотрим пример программы, которая выполняет псевдо-мерцание светодиодом ( выводит на экран соответственно LED ON и LED OFF) с частотой 1 Гц.
Для реализации этого простого примера достаточно создать всего одну задачу.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#include "tics.h"
#include "ticsmain.h"
#include "ticsext.h"
#include "conio.h"
#define LEN_MSG_SPACE 120
#define LEN_TCB_SPACE 40
#define LEN_STACK_SPACE (256 * LEN_TCB_SPACE)
static 	typeTcb * ledflash_tcb, * kbmon_tcb;
typeMsg MsgSpace[LEN_MSG_SPACE];
typeTcb TcbSpace[LEN_TCB_SPACE];
unsigned char StackSpace[LEN_STACK_SPACE];
static unsigned int state;
void kbMon(void)
{
	while (TRUE) {
		if (kbhit()) {
			exitTics();
			exit(0);
		}
		pause(100);
	}
}
void ledFlash(void)
{
	typeMsg * timer, * msg;
	timer = makeTimer(1000L);
	timer->flags |= PERIODIC;
	startTimer(timer);
	while(TRUE)
	{
	     msg = waitMsg(ANY_MSG);
	     if(msg->msgNum == TIMEOUT) {
		if(state) {
			printf("\nLED OFF");
			state = FALSE;
		}
		else {
			printf("\nLED ON");
			state = TRUE;
		}
	     }
	     freeMsg(msg);
	}
}
void ledInit(void)
{
	ledflash_tcb = makeTask(ledFlash,0);
	startTask(ledflash_tcb);
}
void main(void)
{
	typeTics * tics;
	tics = makeTics(MsgSpace, sizeof(MsgSpace), StackSpace, sizeof(StackSpace),
	TcbSpace, sizeof(TcbSpace));
	startTics(tics);
	ledInit();
	kbmon_tcb = makeTask(kbMon,0);
	startTask(kbmon_tcb);
	suspend();
}

Функция инициализации ledInit() создает и запускает на выполнение задачу ledFlash().

Временной интервал для обеспечения требуемой задержки будет формироваться с помощью периодического таймера. TICS поддерживает всего три типа таймеров: пауза, одноразовый и периодический таймер.

Как нетрудно догадаться из названия периодические таймеры выполняются постоянно с определенным периодом, а одноразовые — отсчитывают заданный интервал времени и прекращают свою работу.

После истечения заданного временного интервала по-умолчанию таймер отправляет сообщение TIMEOUT. Перед запуском таймера можно изменить сообщение, которое он будет отправлять при срабатывании.

Таймеры в TICS являются дифференциальными , то есть все временные интервалы таймеров сортируются по-возрастанию и отсчет последующих интервалов производится относительно предыдущих.

При запуске задачи ledFlash() запускается периодический таймер, который каждую секунду будет посылать сигнал TIMEOUT запустившей его задаче. Далее происходит вход в бесконечный цикл, внутри которого выполняется ожидание любого сообщения( ANY_MSG).

По приходу сообщения выполняется сравнение его номера с TIMEOUT и выполнение необходимых действий в случае равенства.

После обработки сообщения освобождается память, выделенная для него в очереди сообщений.

В зависимости от текущего состояния статической переменной state, которая может принимать только два значения TRUE или FALSE, псевдо-светодиод будет либо загораться , либо тухнуть. Так будет продолжаться до нажатия любой клавиши( выхода из программы ).

Для второго варианта реализации мерцания светодиода потребуется создать сервер и клиент сообщений. Задача mainTask() в начале запускает периодический таймер с интервалом срабатывания 1 секунда и входит в бесконечный цикл. В цикле задачи выполняется ожидание сообщения TIMEOUT от таймера, после получения которого сообщение удаляется из очереди и выполняется анализ состояния псевдо-светодиода.

В зависимости от текущего его состояния сервер сообщений mainTask() отправляет обработчику сообщений ledFlash() соответствующее сообщение для включения или выключения светодиода. ledFlash() создается как кооперативная задача.

Для того,чтобы создать кооперативную задачу в TICS достаточно установить бит COOP в регисте флагов блока управления задачей:

tcb->flags |= COOP;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#include "tics.h"
#include "ticsmain.h"
#include "ticsext.h"
#include "conio.h"
#define LEN_MSG_SPACE 120
#define LEN_TCB_SPACE 40
#define LEN_STACK_SPACE (256 * LEN_TCB_SPACE)
#define LED_ON_MSG		1000
#define LED_OFF_MSG		1001
static 	typeTcb * ledflash_tcb, * kbmon_tcb, * mainTask_tcb;
typeMsg MsgSpace[LEN_MSG_SPACE];
typeTcb TcbSpace[LEN_TCB_SPACE];
unsigned char StackSpace[LEN_STACK_SPACE];
static unsigned int state;
void kbMon(void)
{
	while (TRUE) {
		if (kbhit()) {
			exitTics();
			exit(0);
		}
		pause(100);
	}
}
void ledFlash(typeMsg * msg)
{
	switch(msg->msgNum)
	{
		case LED_ON_MSG:
			printf("\nLED ON");
			return;
		case LED_OFF_MSG:
			printf("\nLED OFF");
			return;
		default:
			return;
	}
}
void ledInit(void)
{
	mainTask_tcb = makeTask(mainTask,0);
	startTask(mainTask_tcb);
	ledflash_tcb = makeTask(ledFlash,0);
	ledflash_tcb->flags |= COOP;
	startTask(ledflash_tcb);
}
void mainTask(void)
{
	typeMsg * timer, * msg;
	timer = makeTimer(1000L);
	timer->flags |= PERIODIC;
	startTimer(timer);
	while(TRUE)
	{
	msg = waitMsg(TIMEOUT);
	freeMsg(msg);
	if(state) {
		msg = makeMsg(ledflash_tcb,LED_OFF_MSG);
		sendMsg(msg);
		state = FALSE;
	}
	else {
		msg = makeMsg(ledflash_tcb,LED_ON_MSG);
		sendMsg(msg);
		state = TRUE;
	}
	}
}
void main(void)
{
	typeTics * tics;
	tics = makeTics(MsgSpace, sizeof(MsgSpace), StackSpace, sizeof(StackSpace),
	TcbSpace, sizeof(TcbSpace));
	startTics(tics);
	ledInit();
	kbmon_tcb = makeTask(kbMon,0);
	startTask(kbmon_tcb);
	suspend();
}

Такое решение может показаться избыточным для простой задачи мерцания светодиодом. Так оно и есть. Но представьте себе ситуацию, когда в системе необходимо одновременно выполнять сканирование клавиатуры, управление индикаторами и т.д.. Все эти задачи носят периодический характер. Для каждой из них можно создать кооперативную задачу — обработчик сообщений . Инициировать периодический запуск задачи возможно с помощью приема сообщения от сервера, внутри которого запущен один или несколько периодических таймеров.

Имея в своем арсенале операционную системему TICS, программист получает мощный инструмент для создания любой структуры сложного проекта. В простых программах, как вы сами могли убедиться из предыдущего примера, применение ОСРВ может оказаться нерациональным.

Портирование TICS на другие архитектуры.

Безусловно, нас с вами больше всего интересует применение ОСРВ для программирования микроконтроллеров. Эта статья не будет иметь прикладной ценности , если TICS так и останется системой под MS-DOS.

Портирование TICS на архитектуры микроконтроллеров, по словам авторов, должно занять минимум времени и усилий. Вся аппаратно-зависимая часть системы сосредоточена в одном файле target.c( название файла может быть любым ), который необходимо полностью переписать для новой архитектуры, сохраняя зависимости от основной части TICS.

Для переноса на различные архитектуры микроконтроллеров необходимо в первую очередь хорошо знать архитектуру целевого микроконтроллера, а во-вторых — хорошо понимать принципы функционирования TICS и способы взаимодействия различных его частей.

Вторую задачу невозможно осилить, не имея совершенно никакого опыта программирования под ОСРВ TICS.

Данная статья задумывалась как начальный этап подготовки к освоению TICS и портированию его на различные архитектуры микроконтроллеров.

Продолжение будет…

5 Comments to Операционная система реального времени (ОСРВ) TICS

  1. Volldemar's Gravatar Volldemar
    12 мая 2011 at 10:36 | Permalink

    В экземплах TICSa, которые идут в «образе» диска С, ссылки в пректах ТС на диск Д, как это исправить?

  2. Volldemar's Gravatar Volldemar
    12 мая 2011 at 17:52 | Permalink

    C:\Users\-=user=-\AppData\Local\DOSBox

Leave a Reply

You must be logged in to post a comment.