В данной серии статей будут приведены примеры использования основных features (таких как semaphore, mutex, event и msg) различных RTOS и вложены проекты для быстрого начала работы с ними.
Список используемых RTOS:
NOTE:
- mutex – используют для доступа к разделяемому ресурсу несколькими задачами. Когда одна задача захватила mutex и использует разделяемый ресурс, вторая не может захватить mutex и использовать разделяемый ресурс. Вторая задача будет находиться в ожидании освобождения mutex первой задачей. И как только mutex будет освобожден, вторая задача сможет его захватить и использовать разделяемый ресурс.
- msg — используют для обмена данными между задачами. Note: Глобальные переменные не используют т.к. несколько задач одновременно могут получить доступ к ним, что может привести к ошибке. Либо доступ к этим глобальным переменным нужно выносить в критическую секцию, в которой планирование задач запрещено.
- event – используют для того чтобы сообщить задаче, что произошло какое-то событие. Note: использование похоже на семафоры в режиме оповещения (сигнала) о том, что произошло событие.
- semaphore – существует два варианта использования семафора:
Первый: Семафор для одновременного доступа к разделяемом ресурсу более чем 1 задачей (для 1 задачи обычно используют mutex).
При каждом занимание ресурса задачей значение семафора будет уменьшаться, пока не достигнет 0. При нулевом значение семафора разделяемый ресурс больше не может быть занят новой задачей, пока существующая задача его не оставит. При оставлении разделяемого ресурса задачей, значение семафора увеличивается на 1, что приводит к возможности другой задаче занять разделяемый ресурс.
Второй: семафор сигналит о том, что что-то произошло – например, прерывание. Например, из прерывание, сигналить задаче-обработчику этого прерывания о том, что надо запуститься.
Note: Проблема сигналов из двоичного семафора – если у вас двоичный семафор, то если произошло сразу нескольких быстрых прерываний (быстрых настолько, что их задача-обработчик, которой мы хотим просигналить, не успела вызваться), то двоичный семафор просигналит только один раз. И если нам точно нужно знать, сколько прерываний произошло и для всех их вызвать обработчик, то возникнет ошибка. Поэтому, для этого нужно использовать счетные (не бинарные) семафоры с глубиной счета, на столько большой на сколько вы рассчитываете быстро будут идти прерывания, которые вы хотите обрабатывать в задаче-обработчика.
Во всех приложенных проектах-примерах RTOS делает одно и тоже (скачать пример можно в конце статьи):
- Задача 1 и задача 2 тестируют mutex. Они имеют одинаковый приоритет и пытаются одновременно захватить mutex(использовать разделяемый ресурс — строчку дисплея). Первая захватившая mutex задача выводит на дисплей свое имя и уходит в сон на 2 или 3 секунды (в зависимости 1 или 2 задача). В это время планировщик переключается на вторую задачу (т.к. первая с этим же приоритетом заснула) — вторая задача пытается захватить mutex, но он уже захвачен первой. Тогда, вторая задача блокируется до момента освобождения mutex. Mutex освободится когда первая задача проснется и освободит его (mutex) — тогда вторая задача захватывает mutex и так-же выводит на дисплей свое имя и уходит в сон. Далее все повторяется по такому же сценарию. Таким образом, мы можем протестировать mutex (более подробно — см. код в примере).
- Задачи 3, 4 и 5 тестируют semaphore (для доступа к разделяемому ресурсу). Они имеют одинаковый приоритет и пытаются одновременно захватывать семафоры (использовать разделяемый ресурс — 2 строчки дисплея — для 1 строчки дисплея достаточно mutex). Принцип работы этих задач такой-же как и mutex задач 1 и 2, описных выше (более подробно — см. код в примере).
- Задачи 6, 7 и 8 тестируют event. Задачи 6 и 7 отправляют event, а задача 8 принимает его и в зависимости от принятого event выводит различный текст на дисплей. Задача 6 и 7 имеют одинаковый приоритет, а задача 8 имеет повышенный приоритет — для того, чтобы по принятию event сразу проснуться и выполнить необходимое действие. По выполнению этого действия задача 8 опять уходит в режим блокировки до момента отправки следующего event от задач 6 или 7. Задача 6 отправляет event по нажатию кнопки на плате STM32F429i-Disco. А задача 7 отправляет event каждые 8 секунд.
- Задачи 9 и 10 тестируют msg. Для этого задача 9 каждую секунду просыпается, инкрементирует переменную (считает секунды) и отправляет данную переменную через msg задачи 10. Задача 10 принимает данный msg и выводит принятое значение на дисплей (на дисплее считается время в секундах).
TNKernel
TNKernel можно назвать минималистической RTOS с очень простым кодом ядра. В ней как раз есть только semaphore, mutex,event и msg – других features в ней нету. Зато реализация этих features очень проста (в отличие от других RTOS) и каждый может изучить код и разобраться как и что работает.
Ссылка на сайт разработчика: http://tnkernel.com/. Документация онлайн: http://www.pic24.ru/doku.php/tnkernel/ref/intro .
Мы рассмотрим только API для работы с данной RTOS на примере проекта для STM32F429i-disco.
Для начала надо настроить прерывание, по которому будет вызван system tick данной RTOS и создать для него обработчик (у нас system tick RTOS будет равен 1 мс, и вызываться через прерывание от SysTick таймера):
tn_arm_disable_interrupts();
SystemCoreClockUpdate();
SysTick_Config(SystemCoreClock/1000);
В сам обработчик надо вставить:
void SysTick_Handler(void)
{
tn_tick_int_processing();
//-- !!! For the Cortex CPU, this function always MUST be a last func in
//-- any user's interrupt handler
tn_int_exit();
}
Задачи
Задачи в TNKernel должны быть созданы в функции tn_app_init, которая после вызывается из ядра RTOS. Задача создается таким образом (добавлен макрос выравнивания для IAR):
// стек задачи
#if defined (__ICCARM__) // IAR ARM
#pragma data_alignment=8
#endif
align_attr_start unsigned int task_1_stack[TASK_1_STK_SIZE] align_attr_end;
TN_TCB task_1;
void task_1_func(void * par);
void tn_app_init(void)
{
task_1.id_task = 0;
tn_task_create(&task_1, //-- task TCB
task_1_func, //-- task function
TASK_1_PRIORITY, //-- task priority
&(task_1_stack //-- task stack first addr in memory
[TASK_1_STK_SIZE-1]),
TASK_1_STK_SIZE, //-- task stack size (in int,not bytes)
NULL, //-- task function parameter
TN_TASK_START_ON_CREATION); //-- Creation option
...
...
}
void task_1_func(void * par)
{
while(1)
{
// code
}
}
В main, RTOS запускается вызовом функции tn_start_system.
int main(void)
{
tn_arm_disable_interrupts();
SystemCoreClockUpdate();
SysTick_Config(SystemCoreClock/1000);
...
...
tn_start_system();
while(1);
}
Отправка задачи в сон:
tn_task_sleep(1000); // заснуть на 1000 тиков RTOS
Работа с mutex
Создание:
TN_MUTEX mutex_1;
mutex_1.id_mutex = 0;
tn_mutex_create(&mutex_1,
TN_MUTEX_ATTR_INHERIT, // наследование приоритета
0); // при наследование приоритета - приоритет не важен
Использование:
tn_mutex_lock(&mutex_1, TN_WAIT_INFINITE); // захватываем mutex
// code
// делаем что либо с разделяемым ресурсом
tn_mutex_unlock(&mutex_1); // освобождаем mutex
Работа с семафорами
Создание:
TN_SEM sem_1;
sem_1.id_sem = 0;
tn_sem_create(&sem_1,
2, //-- Start value
2); //-- Max value
Max value – максимальное значение семафора.
Если вы используете семафор для одновременного доступа к разделяемом ресурсу, то Max value указывает какое число задач могут иметь одновременный доступ к разделяемому ресурсу.
Если вы используете семафор для оповещения что произошло некое событие, то Max value показывает глубину очереди сигналов.Max value == 1 — двоичный семафор. Выбирайте Max value на столько большим на сколько вы рассчитываете быстро будут идти прерывания, которые вы хотите обрабатывать в задаче-обработчика.
Start value – начальное значение семафора. Если вы используете семафор для одновременного доступа к разделяемом ресурсу более чем 1 задачей, то данное значение должно быть равно Max_value.
Если вы используете семафоры для того, чтобы сигналить о чем-либо, то Start value должно быть равно 0.
Использование:
tn_sem_acquire(&sem_1, TN_WAIT_INFINITE); // захватываем ресурс семафором (или ждем события)
// code
// делаем что либо с разделяемым ресурсом
tn_sem_signal(&sem_1); // освобождаем ресурс семафором (или сигналим о событии)
Работа с msg
В TNKernel, msg передаются как указатели на соответствующие сообщения.
Создание:
#define QUEUE_1_SIZE 4
void *queue_1_mem[QUEUE_1_SIZE];
TN_DQUE queue_1;
queue_1.id_dque = 0;
tn_queue_create(&queue_1, //-- Ptr to already existing TN_DQUE
&queue_1_mem[0], //-- Ptr to already existing array of void*
QUEUE_1_SIZE); //-- Capacity of data queue(num entries)
Использование:
Передача:
char data;
char tx_data;
tx_data = data;
tn_queue_send(&queue_1, (void*)&tx_data, TN_WAIT_INFINITE);
Прием:
char *rx_data;
char data;
tn_queue_receive(&queue_1, (void **)&rx_data, TN_WAIT_INFINITE);
data = *rx_data;
Работа с event
Создание:
TN_EVENT event_1;
event_1.id_event = 0;
tn_event_create(&event_1,
TN_EVENT_ATTR_SINGLE | TN_EVENT_ATTR_CLR, // Eventflag attribute
0); // Initial value of the eventflag bit pattern
Использование:
Отправка:
// отправляем флаг - 0b 0000 0001
tn_event_set(&event_1, 1);
Прием:
tn_event_wait(&event_1,
0x01 + 0x02, // маска eventов
TN_EVENT_WCOND_OR, // хоть 1 event
&f_event, // сработавший event
TN_WAIT_INFINITE); // бесконечное ожидание
Пример для скачивания — stm32f429_tnekernel_iar_example. Пример сделан в IAR 7, проект заархивирован 7zip .