scmRTOS для AVR. Первая программа

29/09/2010 - 18:12
  В предыдущей статье по scmRTOS мы создали в IARe некую заготовку. Теперь на ее основе мы напишем полноценную программу для микроконтроллера AVR. Программа будет простая — два светодиода моргающих с разной частотой. Однако  даже для ее реализации придется напрячь извилины и разобраться с основными моментами использования операционной системы.

Процессы  

   Каждый процесс в scmRTOS представляет собой отдельный класс, который описан с использованием шаблонов C++. Для создания процесса нужно определить его тип, объявить объект этого типа и написать функцию процесса Exec().

   Создание процесса выглядит следующим образом:

// Определяем тип процесса
typedef OS::process<OS::pr0, 40, 10> TProc1;

//Объявляем объект
TProc1 Proc1;

//функция процесса, выполняющая полезную работу
OS_PROCESS void TProc1::Exec()
{
      //объявления
      //инициализация данных процесса

    for(;;)
    {
        //полезный код
    }
}

  При определении типа процесса нужно задать его приоритет, размер стека данных, размер стека возвратов и имя.


   Приоритеты процессов определяют порядок их запуска планировщиком. Например, если в системе несколько процессов находятся в состоянии готовности и возникло событие, по которому должна произойти передача управления, то при прочих равных условиях планировщик передаст управление процессу с более высоким приоритетом.
   Приоритеты задаются с помощью перечислимого типа и могут принимать значения pr0, pr1, pr2 и так далее. Порядок старшинства приоритетов настраивается в конфигурационном файле scmRTOS_config.h.

//0  соответствует варианту, когда pr0 наиболее приоритетный процесс
//1 соответствует варианту, когда pr0 наименее приоритетный процесс

#define  scmRTOS_PRIORITY_ORDER             0

   Приоритеты процессов должны идти подряд и не должны повторяться. Приоритеты процессов задаются на этапе объявления типа процесса и не могут быть изменены по ходу программы.

   Процессы в scmRTOS используют два стека. В стеке данных - сохраняется контекст процесса, а в стеке возвратов - адреса возвратов из подпрограмм и прерываний.
   Размер стека данных определяется количеством используемых регистров (регистры общего назначения R0-R30 + регистр SREG + регистры указатели стека SPL и SPH) и количеством переменных процесса (так написано в документации, хотя для меня этот момент не понятен). Также стек данных будет использоваться при вызове  из процесса других функций - для передачи функциям аргументов или для сохранения содержимого зарезервированных регистров, в случае нехватки рабочих.
   С темой расчета размера стека данных я пока не разобрался.
   Размер стека возвратов определяется количеством вложенности вызовов подпрограмм. Размер стека возвратов в 2*N байт — соответствует N уровням вложенности подпрограмм.

  У каждого процесса есть исполняемая функция Exec(). В ней размещается пользовательский код. Он должен быть написан таким образом, чтобы выход из функции Exec() был невозможен. В общем случае в этой функции содержится бесконечный цикл, перед которым размещены объявления переменных  и инициализационный код.
 
  Операционная система scmRTOS позволяет объявить до 31 процесса. Количество объявленных процессов должно быть указано в конфигурационном файле scmRTOS_CONFIG.h.

//количество пользовательских процессов
#define  scmRTOS_PROCESS_COUNT              2
 
   Помимо процессов определенных пользователем, в операционной системе существует системный процесс - IdleProcess  (его не нужно создавать, он прописан в коде ОС). У него всегда самый низкий приоритет, не зависимо от заданного порядка старшинства приоритетов.
   В конфигурационном файле scmRTOS_config.h для системного процесса есть несколько настроек.

//размер стека данных и стека возвратов для системного процесса
#define scmRTOS_IDLE_PROCESS_DATA_STACK_SIZE         70
#define scmRTOS_IDLE_PROCESS_RETURN_STACK_SIZE     10

//0 – откл./1 – вкл. в системном процессе вызов функции IdleProcessUserHook()
//если указанная функция включена, то она должна быть определена пользователем

#define  scmRTOS_IDLE_HOOK_ENABLE           0

функция IdleProcessUserHook() определяется так:

void OS::IdleProcessUserHook()
{
  //какой-нибудь пользовательский код
}

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

process<prIDLE,
        scmRTOS_IDLE_PROCESS_DATA_STACK_SIZE,
        scmRTOS_IDLE_PROCESS_RETURN_STACK_SIZE> IdleProcess;

OS_PROCESS void process<prIDLE, 70, 10>::Exec()
{

    for(;;)
    {
        #if scmRTOS_IDLE_HOOK_ENABLE == 1
        IdleProcessUserHook();
        #endif
    }
}

Ошибка при описании функции Exec()! Заменил заданные значения размера стеков на именованные константы, и все откомпилировалось.

OS_PROCESS void process<prIDLE,
                                         scmRTOS_IDLE_PROCESS_DATA_STACK_SIZE,
                                        scmRTOS_IDLE_PROCESS_RETURN_STACK_SIZE >::Exec()
{
   ……
}

Посмотрел код в scmRTOS 3.10 – там этот баг устранили.


Планировщик

   Передача управления от одного процесса к другому может происходить в случаях когда:

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

   Собственно сама процедура передачи управления реализована в scmRTOS двумя способами:

- прямая передача управления путем вызова из планировщика функции переключателя контекста
- передача управления путем активации программного прерывания, в котором и происходит переключение контекста

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

   Способ передачи управления задается в конфигурационном файле scmRTOS_config.h.

//0 -  прямая передача управления,
//1 — передача управления с помощью программного прерывания

#define  scmRTOS_CONTEXT_SWITCH_SCHEME      0


Системный таймер

   Системный таймер служит для формирования временных интервалов, необходимых при работе процессов.
   Для реализации системного таймера в AVRках используется аппаратный таймер Т0. Перед запуском операционной системы этот таймер нужно инициализировать. Согласно документации на scmRTOS величину системного тика нужно установить в районе 1-10 мс.
    В конфигурационном файле scmRTOS_config.h для системного таймера есть несколько настроек.

//0 — выкл./1 — вкл. использование счетчика тиков системного таймера
#define  scmRTOS_SYSTEM_TICKS_ENABLE        1

//0 — запрещает/1 — разрешает  вложенные прерывания
//в обработчике прерывания от системного таймера

#define  scmRTOS_SYSTIMER_NEST_INTS_ENABLE  0

//0 выкл./1 вкл. в обработчике прерывания системного таймера
//вызов функции SystemTimerUserHook()
//в этом случае указанная функция
//должна быть определена в пользовательском коде   
 
#define  scmRTOS_SYSTIMER_HOOK_ENABLE       1

Функция SystemTimerUserHook() определяется так:

void OS::SystemTimerUserHook()
{
    //перезапуск таймера Т0 или какой-нибудь другой код

}

Первая программа на scmRTOS для AVR

  Описал вкратце ключевые моменты. Давайте собственно перейдем к нашей программе. Открываем проект. Открываем файл main.cpp. Нам нужно наполнить его полезным содержимым.

  В начале main.cpp подключаем заголовочные файлы.

#include <ioavr.h>
#include <scmRTOS.h> 

  Определяем типы процессов. У нас будет два процесса, каждый из которых будет управлять своим светодиодом. Размеры стеков процессов я задал импирически.

typedef OS::process<OS::pr0, 40, 10> TProc1;
typedef OS::process<OS::pr1, 40, 10> TProc2;

   Объявляем объекты процессов.

TProc1 Proc1;
TProc2 Proc2;

  Описываем функцию main. В ней находится инициализация порта B, инициализация системного таймера  T0 и функция запуска ОС.

#define LED1 0
#define LED2 1
#define LED_DDR DDRB
#define LED_PORT PORTB

int main()
{
    //настройка порта
    LED_DDR = 0xff;
    LED_PORT = (1<<LED1)|(1<<LED2);

    // инициализация системного таймера - период 1 мс
    TCNT0 = 0xf0;
    TCCR0 = 0x05;                  
    TIMSK |=  (1 << TOIE0);   
   
    //запуск операционной системы
    OS::Run();
}

   Описываем процессы.

OS_PROCESS void TProc1::Exec()
{
    for(;;)
    {
         LED_PORT ^= (1<<LED1);
         Sleep(500);  
    }
}

OS_PROCESS void TProc2::Exec()
{
    for(;;)
    {
         LED_PORT ^= (1<<LED2);
         Sleep(120);
    }
}

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

   Описываем функцию перезапуска системного таймера.

//функция перезапуска системного таймера
//она вызывается в прерывании

void OS::SystemTimerUserHook()
{
    TCNT0 = 0xf0;
}

   Настраиваем конфигурационный файл scmRTOS_CONFIG.h

//количество пользовательских процессов
#define  scmRTOS_PROCESS_COUNT              2

//запретить вложенные прерывания в обработчике прерывания системного таймера
#define  scmRTOS_SYSTIMER_NEST_INTS_ENABLE  0

//включить использование счетчика тиков системного таймера                 
#define  scmRTOS_SYSTEM_TICKS_ENABLE        1

//включить в обработчике прерывания системного таймера
// вызов функции SystemTimerUserHook()

#define  scmRTOS_SYSTIMER_HOOK_ENABLE       1

//выключить в системном процессе вызво функции IdleProcessUserHook()
#define  scmRTOS_IDLE_HOOK_ENABLE           0

//прямая передача управления
#define  scmRTOS_CONTEXT_SWITCH_SCHEME      0

//порядок приоритетов — pr0 имеет наивысший приоритет
#define  scmRTOS_PRIORITY_ORDER             0

Остальные настройки, не указанные здесь, трогать не нужно.

Компилируем проект и запускаем в железе или Proteus`e.


Файлы

Документация на scmRTOS v2
Дистрибутив scmRTOS v3.05
Проект для IARa - первая программа на scmRTOS
Проект для Proteus`a

Comments   

# Guest 2010-09-30 03:43
Статья очень интересная, грамотная... Спасибо...
Только вот я разочарован в ОС'ах (((Мне казалось, что на нее больше можно возложить(
# Pashgan 2010-09-30 14:49
Ты после моей статьи разочаровался в ОСах? А ты доки на scmRTOS читал? Операционная система реального времени позволяет разбивать приложение на несколько независимых процессов, предоставляет набор сервисов (системный таймер, семафоры, очереди..) и, самое главное, гарантирует заданное время реакции на поступившее событие. Разве это мало?
# Guest 2010-10-01 03:28
Да... "заданное" время реакции - это безусловно круто, только вот задано оно не тобой:((((
Я к тому, что ОСРВ на АВРах себя оправдывает лишь в приложениях, ориентированных на работу с пользователем.. . К примеру, если мне надо каким-нибудь двигателем трехфазным ШИМом управлять, я не выберу ОС я сделаю на прерываниях... и буду прав
P.S. Я все-таки зануда)))))
# Pashgan 2010-10-01 17:44
RTOS - это инструмент и использовать его нужно по назначению. RTOS предоставляет удобства, но за них естественно приходится платить. Не знаю на сколько оправдывает себя применение операционки на AVRах, разбираюсь с ней чисто из спортивного интереса.
# Alegro 2010-10-09 20:43
В ряде случаев применение ОC значительно упрощает управляющий алгоритм программы. Особенно, когда нужно управлять несколькими параллельными процессами. Вы правильно сказали:"RTOS это инструмент и использовать его нужно по назначению". Вернее сказать - когда в этом есть необходимость.
# Guest 2010-09-30 13:28
Да, спасибо за столь содержательную статью! Я сразу во всем разобрался! 8)
# Phil 2010-10-16 18:19
Пример с 2мя LEDами не может показать, хороша или нет ОС. Все трудности выползают с реальными проектами. Отладка довольно трудная, при сложном проекте если где-то скобку потерял, компилятор (у меня GCC) выдает дурацкие ошибки, поиск которых занимает время. Плюсы scmRTOS: простота при применении. Минус: отсутствие драйверов, как в например beRTOS. Там еще и собственная IDE есть. Даже не верится, что итальянцы сей продукт родили. Но это так, к scm не относится.
И еще раз: scmRTOS система простая, в которой есть все, что ОСРВ требует: семафоры, события, очереди. Простота и подкупает. Разобраться, как писать для неё, можно за пару часов. Не в пример freeRTOS.
Лично "перешёл" на ОС т.к. требовалось параллельное асинхронное обращение к устройству, висящему на SPI, из разных процессов.
# fisherman 2011-11-14 08:39
К сожалению, пример со светодиодами малоинформативе н. А как работать с прерываниями, используя RTOS? К примеру, внешнее прерывание INT0. Конкретно - обрабатывать сигналы от ИК-пульта дистанционного управления. Не могли бы осветить такую тему?
# Sanya 2011-11-26 07:49
Уважаемый Pashgan, вы вводите людей в заблуждение, говоря, что scmRTOS использует 2 стека. Вообщето 2 или 1 - это компиляторозави симо. Например IAR EWAVR использует 2 стека - стек данных(DSTACK) и стек возвратов(RSTAC K), а WinAVR - 1 общий стек
# 789 2011-12-21 15:30
Имелось в виду по-видимомуто, что у прерываний и задач есть свои стеки (в IAR он систоит из SP и Y). То есть при переходе/уходе на/из прерывание стек переключается на нужный. Отсюда экономия ОЗУ.
# Pashgan 2012-09-20 21:50
Quote:
Уважаемый Pashgan, вы вводите людей в заблуждение, говоря, что scmRTOS использует 2 стека
Не совсем верно выразился, правильнее будет так, как написано в доках:
"Таким образом, при использовании компиляторов от IAR Systems име-
ется два стека: один стек для данных, второй – для адресов возвратов.
scmRTOS учитывает этот момент и поддерживает по два стека для ка-
ждого процесса, причем размер каждого стека можно задать индивидуально,
исходя из требований ресурсоемкости прикладной задачи."

У вас недостаточно прав для комментирования.