В прошлой статье я описал настройку DOS-эмулятора DOSbox , привел примеры для операционной системы TICS , компилируемые под MS-DOS с помощью Borland Turbo C++ 3.1. Запуская примеры в пошаговом режиме и анализируя используемую оперативную память я пришел к выводу, что полноценная работа TICS возможна только на микроконтроллерах с объемом ОЗУ выше десяти килобайт.

Большинство AVR микроконтроллеров остаются недоступны для ОСРВ TICS. Имеет смысл перенести систему лишь на старшие модели (например Atmega1284 ). Более интересной может оказаться адаптация TICS для использования на микроконтроллерах с ядром Cortex-M3.

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

Как известно, структура программы для микроконтроллера содержит бесконечный цикл внутри функции main(), в теле которого поочередно вызываются различные функции.

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

Если создать массив указателей на вызываемые функции, то исходный код сократится до одной единственной функции start_tasks(), вызываемой внутри тела основной функции main().

Внутри функции start_tasks() выполняется бесконечный цикл, в теле которого происходит последовательный вызов задач через указатель на функцию задачи и передача параметров задаче через указатель на аргументы функции. Признаком окончания очереди задач task_queue является нулевой указатель на очередную задачу в очереди.

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

Не случайно внутри структуры task_s тип указателя на аргументы функции объявлен как пустой (void *). Это позволит передать в задачу( а также из нее ) аргументы любого типа, в том числе указатели на различные структуры данных.

Постановкой задач в очередь призвана заниматься функция make_task, которая инициализирует очередной блок задачи, присваивает указатель на функцию и аргументы задачи. В конце очереди задач вместо указателя на функцию в блок задачи записывается нулевой ( NULL ) указатель. Если у какой-либо задачи нет передаваемых параметров, то вместо указателя на аргументы записывается пустой указатель ( NULL ).

Функция make_task возвращает порядковый номер задачи в очереди. Задачи, поставленные в очередь первыми будут выполняться раньше всех последующих задач.

При заполнении очереди задач функция make_task возвращает код ошибки, постановка новой задачи в очередь при этом не происходит. Для увеличения размера очереди задач необходимо увеличить значение MAX_TASKS.

Если назначаемая задача является предпоследней в очереди, то на место следующей ( последней ) задачи в очереди записывается нулевой указатель. Макросы DI(di_opt) и EI(di_opt) предназначены соответственно для запрещения и разрешения прерываний в зависимости от опции di_opt, которая может принимать одно из двух возможных значений — TRUE или FALSE.

Очередь задач в моем примере объявлена как массив статических переменных, поэтому значения всех указателей заполняются нулями. В этом случае нет надобности записывать нулевой указатель в последний элемент очереди (поскольку он и так будет нулевым). Однако если очередь задач будет объявлена как массив глобальных указателей, то без инициализации нулем предпоследнего элемента очереди не обойтись. Кроме того, очередь задач является статической, то есть создается она в начале программы и размер очереди неизменен до завершения программы, поскольку определяется значением макроса MAX_TASKS. Пользователь не обязательно должен заполнить всю очередь , часть ее может оставаться пустой. В этом случае последний элемент очереди должен быть нулевым. Это обеспечивается при инициализации планировщика с помощью функции init_RTK().

На момент вызова функции init_RTK() все задачи должны быть инициализированы, то есть попросту говоря все вызовы make_task() должны быть произведены до выполнения инициализации с помощью init_RTK() .
В переменной task_counter храниться следующий за последней назначенной задачей номер.

Теперь настало время поговорить о таймерах. В качестве системного таймера наш простейший планировщик для микроконтроллеров AVR использует первый 16-разрядный таймер TIMER1 в режиме таймера-счетчика с обработкой прерывания по переполнению.

Таймер настраивается таким образом,что переполнение его значения происходит каждые 5 мсек времени. Внутри обработчика прерывания от таймера выполняется увеличение значения глобальной переменной Tics, относительно которой производятся отсчеты временных интервалов.

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

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

Чтобы стало понятней , рассмотрим применение этих макросов на примере.

Программа зациклиться до тех пор, пока значение Tics не достигнет ранее установленного относительно переменной Tics значения, то есть через 100 циклов по 5 мсек после запуска программного таймера.

Как видите организация таймеров предельно проста. Но при такой структуре возникает два тонких момента. Во-первых обратите внимание на макрос timeout(t). В нем выполняется сравнение «больше или равно». Это связано с тем, что между операциями анализа временного интервала может пройти больший промежуток времени, нежели 5 мсек( один тик ). Поэтому значение Tics может «проскочить» равенство установленному таймеру.

Во — вторых, проблема возникает на краю диапазона значения переменной счетчика тиков( тип uint64_t ). При переполнении значения счетчика значение Tics может оказаться больше установленного значения таймера,что вызовет немедленное срабатывание.

Бесперебойную работу программного таймера способен обеспечить следующий вариант макроса timeout(t).

Но в этом случае, как было отмечено ранее, момент равенства можно «проскочить», если между сравнениями пройдет временной промежуток выше 5 мсек. Для стабильной работы программы с различным временем «перебора» очереди задач можно установить коэффициент деления, который увеличит тик таймера.

Архив с примером проекта для Eclipse можно скачать как обычно с моего репозитория. Называется он State_machine_AVR.zip и представляет из себя простейший автомат состояний, выполняющий мерцание светодиодом.

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

Итак , рассмотрим концептуальный уровень работы нашей программы конечного автомата.

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

Задача конечного автомата представляет собой функцию без возвращаемого значения и с значением текущего состояния автомата в качестве аргумента функции. Внутри этой функции размещается конструкция switch с возможными состояниями в качестве анализируемых значений.

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

Задача led_flash_task выполняет мерцание светодиодом с частотой 1 Гц.

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

Как видите, ничего сложного в планировании выполнения задач нет. Конечно, мы рассмотрели один из самых простых планировщиков задач. Однако и в сложных операционных системах реального времени принципы планирования задач остаются неизменны на протяжении многих лет.

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

Viewed 44009 times by 5274 viewers

Last modified: 06/02/2020

Author

Comments

Ссылка на оригинальную документацию:

http://www.concentric.net/~tics/ticsds.htm
http://www.concentric.net/~tics/ticsinfo.htm

Comments are closed.