scmRTOS

курс молодого бойца

В этой статье будет сделана попытка описать начало работы с scmRTOS.

Во-первых, особую благодарность хочется выразить разработчику scmRTOS - Гарри Журову, автору - "двигателю" ARM-порта Сергею Борщу, ну и конечно, всем участникам обсуждения темы scmRTOS на различных форумах.

А зачем эта статья нужна?

Причин несколько....

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

Во-вторых, выложенные (по-сути, подаренные... земной поклон разработчикам!!!!) исходники - это лишь база для написания программ. А вот примера написания готовой программы на базе этих "крок" я не втречал (не считая примера в приложении С.1.). Вопросов же возникает масса...

В-третьих, scmRTOS напсана на С++, что опять не совсем "просто и понятно".

Ну и четвертое, главное для меня: "написал - лучше понял и на дольше запомнил".

Будет рассматриваться порт для SAM7. Все описанное будет проверяться на AT91SAM7S256.

---

Итак, для понимания даже простых (для упомянутых выше гуру - банальных) вещей, о которых будет вестись рассуждение, необходимы некоторые знания, а именно:

Во-первых, необходимо понимать как программировать оперируя объектами. Для этого нужно "почитать" "Объектно ориентированное программирование" Гради Буча. На мой взгляд, наиболее просто и понятно о сложном. Главное же , необходимо понять разницу между процедурами и объектами. То есть, при программировании (проектировании программы), например "Мигающий светодиод" необходимо писать не "мигание диодом", а "мигатель диодом, который может мигать". Это базовый принцип в понимании объектного подхода к программированию!!!!

Во-вторых, необходимо знание синтаксиса и основных "понятий" С++.

Ну и конечно, необходимо изучить scmRTOS.

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

Если у Вас возникло желание дополнить, указать на ошибки и т.п., то милости прошу - все "адреса" на Главной.

 

---

Понимая, что призыв "необходимо изучить scmRTOS" так и останется "призывом", остановлюсь в двух словах на описании scmRTOS...

---

Что же такое RTOS (Real-Time Operating System - Операционная система реального времени)? "...Итак, в контексте текущего рассмотрения, операционная система – совокупность программного обеспечения (ПО), дающего возможность разбить поток выполнения программы на несколько независимых, асинхронных по отношению друг к другу процессов и организовать взаимодействие между ними..." - Г.Журов, scmRTOS.

А для чего это нужно? Представьте, что Ваша программа должна выполнять следующие действия: считать число покупателей, вошедших в магазин; суммировать выручку, обобщая данные с каждой кассы; при срабатывании пожарной сигнализации, включать средства пожаротушения (конечно, в реальной жизни, установки жизнеобеспечения, в том числе и средства пожаротушения, должны включаться "аппаратно", без принятия решения "кокой-то" ОС); при наступлении темноты включать освещение... Ну и достаточно.. :-)

Можно это написать без RTOS? Конечно! Но при этом, как быть если в момент поступления данных от кассы, программа подсчитавает число покупателей? А если срабатывает пож.сигн.? Да еще и сумерки наступили... Вопросы конечно "решаемые" (путем распределения приоритетов прерываний, постоянного циклического опроса датчиков (процессов) и т.д.), но вот только, в сравнии с RTOS, такая программа и "читается" хуже, да и переносимость и изменение программы, при таком подходе, превращается в "пытку" - личный опыт .. :-)

А есть ли ситуации когда RTOS применять не разумно? Да!

"..Следует помнить, что применение ОС накладывает некоторые ограничения на применения процессора. Например, если нужно при возникновении прерывания максимально быстро на него среагировать, чтобы внутри обработчика прерывания, к примеру, «дернуть» ножкой МК, то ОС тут только помеха. Причина кроется в том, что переключение контекстов, а также работа средств межпроцессного взаимодействия выполняются в критических секциях, которые могут длиться десятки и сотни тактов процессора, и во время них прерывания заблокированы. Т.е. решение задач вроде формирования временных диаграмм при использовании ОСРВ становится крайне затруднительным (если не невозможным)..." - Г.Журов, scmRTOS.

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

Еще одно замечание... Все хоть чуть "серьезные" микроконроллеры (ARM, DSP... при цене <300руб) имеют "спец.средства" (программное прерывание, интервальный таймер и т.д.) для работы под управлением RTOS.

---

Теперь, собственно про scmRTOS...

"...Операционная система реального времени для однокристальных микроконтроллеров scmRTOS (Single-Chip Microcontroller Real-Time Operating System) использует приоритетное вытесняющее планирование процессов..." - Г.Журов, scmRTOS. Т.е. "..наиболее «быстрое» в смысле реакции на события..".

scmRTOS написана на С++. Плохо это или хорошо? Конечно хорошо! Люди с самого начала своей жизни "мыслят", оперируют объектами, а не их свойствами. Представьте, что вам принесли новый мобильный ТЛФ. Каким будет первый вопрос? - "А что он может?" И другая ситуация... Нужно просмотреть видеофайл. Начинается выбор.. На чем? - Утюг? - Не умеет.. - Монитор PC? Но без PC монитор бесполезен... DVD проигрыватель? Но ему нужет TV. Другими словами, нужен прибор (объект), который мог бы (имел свойство) "обработать" и "показать". Так и ОП (объектное программирование). Программируется не процедура , например вывод "отладочного" байта в PC, а Отладчик. способный вывести байт. И вот тут "простого" С не достаточно. Дальнейшее описание "выгоды" ОП программирования опускаю... Прочтите Г.Буча... (Вот разъяснение Г.Журова по-поводу стилей программирования: "Если используются процедуры, то стиль - процедурный. Если используются объекты сами по себе - то объектный. Если используются иерархии классов с виртуальными функциями, переопределяемыми в потомках, то стиль - ООП.")

Ну и про "лицензию"... В "scmRTOS What's New Version 3.05" первым пунктом значится: "GPL license changed to MIT". Т.е. пользуйся "как хочешь", но теперь, не забывай указывать авторство... Еще раз спасибо разработчикам!!! :-)

---

Что же скрывается за понятием "порт"? Это вариант scmRTOS для того или иного МК (ARM, AVR, MPS430...). Будет рассматриваться порт для ARM от Atmel (SAM7) для компилятора IAR v.430.

---

Почему версия IAR 4.30? Ведь уже есть 5.11?... Как мне кажется, из-за принципа "лучшее - враг хорошего". Да, 5.11 "новее", "прямее", НО ассемблерные инструкции претерпели изменения. Процедуры инициализации "впрямую" не совместимы. Можно "переделать" под 5.11? Конечно, но, позвольте спросить, а с выходом 5.12 или 6.01 опять "переделывать" ?! ....... А тут, "старенькое", надежное, с известными "проблемами"... Нужно ли городить огород с обновлением?

---

Что такое "шаблоны"? Попытайтесь на С написать функцию вида: Func(n,m){n = m;} .. "Да легко!". Согласен... а если тип n и m не известен? может быть char а может float .. Как тогда? Для С - две разные функции, а для С++ - шаблон (каркас) (если рассматривать только функции, то для реализации можно выбрать механизм полиморфизма через виртуальные функции) . Но шаблоном может быть не только функция, но и класс целиком!!! Следовательно, правильно написав один раз шаблон, можно быть уверенным в "безошибочности" его экземпляров.

------

Что такое "Программное Прерывание"? Это самое обычное прерывание МК, инициализация которого производится путем установки программой бита в регистре "прерываний". Как привило, Программное Прерывание имеет самый низкий приоритет в структуре МК. Там же где специального Программного Прерывания нет, используется одно из существующих (самое "ненужное"). Для AVR, например, используется прерывание от бутлоадера и т.д. (см.scmRTOS ).

---

Естественно, что при работе OC необходима "смена" процессов. Очевидно, что при передаче управления от одного процесса к другому необходимо сохранить состояние (регистры и т.д.) текущего прооцесса ("сохранение контекста"). Для этого можно или непосредственно вызывать функцию "сохранения/перехода", или инициализировать "программное прерывание". В коде обработки которого производится "сохранение/перход". Второй способ "лучше" первого.. Почему? Читайте scmRTOS гл.3.1.2 - 3.1.3 !!!! :-)

---

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

- EventFlag - флаг события;

- Message - сообщение. Если грубо и примитивно, то флаг события + сообщение (объект произвольного типа)

- Channel - кольцевой буфер, в который процессы могут безопасно, с точки зрения целостности передачи данных, записывать и считывать данные при межпроцессном взаимодействии. Тип конкретного буфера задается на этапе объявления ("инстанцирования шаблона" у автора).

---

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

- работает с дисплеем;

- управляет внешним устройством (включает реле гирлянды лампочек на елке)

- опрашивает PC клавиатуру;

- опрашивает валкодер (управляющий режимом работы , а не датчик положения);

- принимает данные от ИК-датчика.

Естественно, что для "отладки" должен быть реализован "отладочный канал".

Ну и достаточно....

---

Начало работы.

Начнем с рассмотрения первого примера - EventFlag.

Пропускаю описание низкоуровневой инициализации, как мне кажеться все достаточно понятно. Из "особенностей" следует указать, что кварц должен быть на 18,432 МГц, а к PIO0 должен быть подключен светодиод.

Итак, имеем три процесса.

Первый процесс "спит" два тика и ждет флага события "ef" :

Sleep(2);

ef.Wait();

Второй ждет флага события Timer_Ovf, который сигналится (устанавливается) в прерывании переполнения от таймера и зажигает светодиод ("0" на "вых"):

Timer_Ovf.Wait();

AT91C_BASE_PIOA->PIO_CODR = (1 << 0);

Третий спит и устанавливает флаг "ef", которого ждет первый процесс :

Sleep(1);

ef.Signal();

Следует сказать, что "гасится" светодиод при переполнении таймера

AT91C_BASE_PIOA->PIO_SODR = (1 << 0);

Timer_Ovf.SignalISR();

 

Если запустить откомпилированную программу, то светодиод, подключенный к PIO0 будет гореть "постоянно". В чем дело? Во-первых, он не "горит", а очень быстро мигает, точнее выключается на очень малое время, и это незаметно для глаза. Судите сами. Таймер "досчитал" до "конца", выключил диод, тут же установил флаг переполнения, по которому второй процесс включил светодиод. И гореть он будет до момента окончания счета (переполнения). / не буду считать скважность, но - TEST_TIMER_RATE = 3500 Гц, т.е. ~285мкС, а такт процессора около 20нС. Даже если на "перключение" тратится 100 тактов, то ВЫКЛ=2мкС, а ВКЛ=~283мкС/.

Как заставить диод мигать с частотой, заметной глазу? Изменить скважность, увеличив время выключенного состояния диода. Например:

 

Timer_Ovf.Wait();

Sleep(500);

AT91C_BASE_PIOA->PIO_CODR = (1 << 0);

 

То есть дождался 2-й процесс переполнения таймера, читай "выкл.диод", и заснул на 500 тиков системного таймера, а уж потом "вкл.диод". В итоге диод будет "слабо подмигивать" :-)

---

Внепроцессные объекты

В общем-то, можно переходить к "планированию" процессов, но, как мне кажется, сначала нужно выделить "внепроцессные" объекты. Ну например, блок отладки (DBGU). Он необходим только на этапе отладки, ни в какой процесс его помещать не нужно. То же самое можно сказать и об "интерфейсах связи" - TWI, SPI, UART и т.д. Они могут понадобиться любому из процессов, при этом в отдельный процесс их выделять нет смысла. (Не всегда! см. ниже 15.01.09)

Поэтому создаем в отдельных файлах ("единицах компиляции") объекты MyDBGU и MyTWI. Функции (а если придерживаться терминологии ОП, то свойства объектов) объявляем с идентификатором static ("имеется ввиду тит связывания,а не класс памяти" (с) Г.Журов), для того чтобы можно было использовать "везде и всюду" . Подключаем созданные файлы к проекту. Объявляем глобальные объекты:

// Определение моих объектов
STAS::TMyDBGU mDBGU(115200);
STAS::TMyTWI mTWI(400000);

, вставляем в программу строку

Timer_Ovf.Wait();

Sleep(500);

#if DEBUG
mDBGU.MyDbguPrintk("Начало...\n\r");
#endif

AT91C_BASE_PIOA->PIO_CODR = (1 << 0);

и наслаждаемся результатом в окне терминала на PC.

(подразумевается, что порт DBGU ARM подключен к СОМ порту РС, а в программе есть определение: #define DEBUG 1)

Следует отметить, что для использования приведенных выше объектов DBGU и TWI никакого наследования или др. механизмов не требуется. Static достаточно.

---

"Настоящий" процесс

Итак, программа уже умеет выводить данные отладки в PC, общаться с внешними устройствами по шине TWI (I2C). То есть, как бы создана "платформа" для программы на базе scmRTOS. Идем дальше...

Предположим, что дисплей, используемый в системе, использует шину TWI. Драйвер дисплея написан. В драйвере "спокойно" используем функции объектов MyDBGU и MyTWI:

....

WorkStr[2] = HV_MULT_FACT_3;
TMyTWI::TWI_WriteByte(....);
TMyDBGU::MyDelayuSec( 1000 );
WorkStr[0] = MAIN_PAGE;

....

/Фукнция TMyDBGU::MyDelayuSec( 1000 ); "пересчитывает" uSec в "тики" процессора. Использование для "задержки" Sleep() ведет к значительному увеличению расхода стека. Исправьте, если ошибаюсь./

А вот драйвер наследуем в платформонезависимый объект/класс Disp:

....

namespace STAS
{
class TDisp : public TPCF8535
{
public:
TDisp(char CONTRAST) : TPCF8535(CONTRAST){};

....

 

Предположим, что работой с дисплеем будет "заниматься" процесс №2. Объявляем объект класса TDisp. Возможно будет необходимо увеличить размер стека для этого процесса.

....

typedef OS::process<OS::pr0, 200> TProc1;
typedef OS::process<OS::pr1, 400> TProc2;
typedef OS::process<OS::pr2, 200> TProc3;

....

OS_PROCESS void TProc2::Exec()
{
STAS::TDisp mDISP(0x43);
mDISP.DispPrint("LCD готов к работе...");
for(;;)
{

Запускаем программу и экран дисплея "оживает"... :-)

На данном этапе уже имеем работающую RTOS и "полезный" процесс вывода данных на дисплей...

---

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

Начать необходимо с изучения приложения С.1 scmRTOS . Там описана реализация примерно такого примера...

14.01.09г.

К большой радости, должен сказать, что эта статья не осталась незамеченной. Авторы и "продвигатели scmRTOS в массы" откликнулись. Нашли ошибки (ошибки сотояли в нерабочих ссылках и "английской" орфографии - былы сразу же исправлены). Естественно, помогли рекомендациями и советами... Нельзя сказать, что статья будет ими (Гуру) проверяться, но, наверняка, если появятся грубые ошибки, то они не остануться незамеченными... Со своей же стороны, прошу обо всех замеченных ошибках/неточностях сообщать, пожалуйста...

Разграничение доступа к общим ресурсам.

Не следует забывать о необходимости разграничения доступа к общему ресурсу (в данном случае это интерфейс вывода отладочной инфы и работа по шине TWI). /мое внимание на это обратил, и посоветовал воспользоваться Семафорами, С.Борщ. Спасибо :-) /

Вообще, вопрос разделения доступа к общему ресурсу далеко не праздный. И если общий ресурс это "... некоторый статический массив с глобальной областью видимости.." ( см. стр.60 scmRTOS), то применение "..семафора взаимоисключения.. ..хорошо подходит..". А вот если общим ресурсом является "шина" обмена данными с внешними устройствами, то применение именно Mutex не так однозначно. Здесь (форум electronix.ru) как раз такой вопрос рассматривается. И, как мне кажется, "..очередь запросов..", когда "..каждая задача кидает.." в очередь адрес внешн.устройства и указатель на данные, более универсальна. Да и такая "очередь" является ни чем иным как средством межроцессного обмена channel (см. гл.5.5 scmRTOS). Только следует помнить, что эти рассуждения применимы для интерфейса связи, выделенного в отдельный процесс.

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

Следовательно:

...
OS::TEventFlag Timer_Ovf;
OS::TMutex MutexTWI;
...

//-----------------------------------------------------------------
// Работа с дисплеем
//-----------------------------------------------------------------
OS_PROCESS void TProc2::Exec()
{....
MutexTWI.Lock();
mDISP.DispPrint("LCD готов к работе...");
MutexTWI.Unlock();
for(;;)
{
...
// неросредственно вывод на дисплей
MutexTWI.Lock();
... // функции, работающие с шиной
MutexTWI.Unlock();
....
}

/безусловно, код может быть другим, как и использование LockSoftly() и IsLocked() вместо/вместе с Lock()./

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

18.01.09г.

Планирование процессов.

Итак, предположим, что на МК (читай RTOS) должны быть возложены следующие задачи:

- опрос термодатчика (шина 3-wire);
- работа/опрос внутреннего АЦП;
- опрос валкодера;
- опрос PC клавиатуры;
- опрос IrDA приемника;
- работа (связь) с РС по "шине" UART и/или USB;
- управление внешними устройствами по шине I2C, MicroWire, SPI, 1-Wire;
- отображение различной информации на системном ЖКИ.

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

Главной и самой трудной, на мой взгляд, задачей является правильное планирование/назначение процессов. Чем больше процессов, тем более сложной становится работа системы. Связано это как со сложностью "понимания"/представления программистом работы системы "в целом", так и с чисто "механическими" трудностями (переключением между процессами, выделением памяти для стека/контекста каждого процесса). Поэтому, как я понимаю, если отдельный процесс "МОЖНО" не создавать, то его "НУЖНО" не создавать...

Далее...
А с какой же стороны подойти к вопросу планирования процессов?
Напрашивается два подхода.

Первый - выбрать однотипные задачи и попробовать их объединить в процесс. Например, есть несколько задач опроса разный устройств. Наверное можно создать процесс Опрос, и, при входе в него, последовательно опрашивать все необходимые устройства. Будет работать? Да, но только пока нет необходимости опрашивать устройства, например, в разное время и/или при выполнении некоторых условий. Все... на этом нормальная работа (программирование) заканчивается: куча кода, да и сама суть RTOS остается на "обочине". Кстати, это не что иное, как пример ПРОЦЕДУРНОГО программирования... :-)

Второй способ - разделить/разбить всю систему на отдельные объекты, попытаться сгруппировать эти объкты, а затем назначить каждому объекту/группе отдельный процесс... Тогда и управлять процессами просто, и все средства RTOS "под рукой"... :-) А это, в свою очередь ОП... :-)

Планирование процессов будем проводить по второму варианту.....
Сначала попытаемся определить объекты:
- Термодатчик;
- АЦП;
- Валкодер;
- PC_клавиатура;
- IrDA_приемник;
- Интерфейс_РС;
- ВнешнееУстройство;
- LCD.

Что же...Приступим.
Начнем с ВнешнихУстройств. Вообще, ВнешнихУстройств может быть несколько, при этом их назначение может отличаться коренным образом. Рассмотрим пример. Имеются: Фотодатчик, для определения темного времени суток, (кстати, "наш" теромодатчик есть ни что иное, как ВнешнееУстройство :-) ), исполнительное устройво (ИУ) для включения уличного фонаря, ИУ для включения ламп в подъезде /на 1-ом этаже, где нет окон и всегда темно/, "таблетка"/система домофона.
Можно ли для каждого объекта (4 шт) назначить свой прооцесс? Конечно... Но вот только нужно ли? Будет ли работать ИУ уличного фонаря без Фотодатчика? Нет конечно, состояние ИУ_фонаря напрямую зависит от состояния Фотодатчика! Тогда зачем для ИУ_фонаря отдельный процесс? Не за чем! То есть для Фотодатчика и ИУ_фонаря назначаем один, а не два процесса.

Сложнее обстоит дело с ИУ_подъезда... Свет в поъезде, возле входной двери должен зажигаться: а)в результате разных событий/условий (стемнело или человек вошел в поъезд - домофон сработал); б)зажигаться на разное время (постоянно ночью и на ~30 сек при срабатывании домофона) /справедливости ради следует сказать, что рассматриваемый пример не до конца соответствует реальности.. :-) ситуация когда человек ВЫходит из подъезда не рассмотрена. Да и, как правило, свет в таких местах горит постоянно. Давайте примем, что в подъезде стоит ИФК датчик движения, который так же может подавать "сигнал" ИУ_поъезда при появлении человека... Но, для простоты, его (ИФК дат.) работу мы рассматривать не будем. Кстати... Отвлекусь от темы... Известно, что для спирали лампы накаливания, как впрочем, и для других приборов, наиболее вреден переходной процесс. То есть процесс вкл/выкл. Однако, лампа, горящая постоянно, имеет ПОЛЕЗНУЮ наработку (время работы до перегорания), меньше нежели та, которая включается и выкл. по мере необходимости. В квартире, в длином коридоре освещение состоит из лампы возле входной двери и лампы, включаемой ИФК датчиком на ~7сек. Лампа возле входной двери включается и "горит" до тех пор, пока кому-нибудь не придет в голову, что "килловатики-то бегут", т.е. примерно на 2-4 часа в сутки. Так вот лампы, работающие в "импульсном" режиме (~180...200 циклов срабатывания), служат около года, а та, которую "включили и забыли" не более 3-х месяцев!.../ То есть объединить объект ИУ_подъезда с другими в одном процессе сложно. Для Домофона, в силу его "обособленности" отдельный процесс так же необходим. Таким образом, для четырех объектов достаточно спранировать три процесса.

Вернемся к нашей задаче...

Какие из объектов зависят друг от друга, а какие являются обособленными?...

Конечно "свой" процесс нужен для дисплея (процесс LCDProc).

Правильно ли будет объединить в один процесс устройства ввода (валкодер, клавиатуру, IrDA)? Скорее НЕТ, чем ДА. Конечно эти обекты "служат" для одной и той же цели. В результате их работы формируются похожие команды, но вот алгоритмы их опроса, правила обработки получаемой информации сильно разнятся. Поэтому запланируем для каждого из них свой процесс (процессы ValcoderProc, PCKeyboardProc, IrDAReceiverProc).

Работу с внешним устройством определям в отдельный процесс (процесс OutDeviceProc).
Для опроса термодатчика выделяем процесс TemperProc.
Ну и для работы с АЦП планируем процесс ADCProc.

Таким образом, не удалось /мне, во всяком случае/ объединить несколько объектов в один процесс. Я думаю, что "плохо" это или "хорошо" станет понятно при написании программы :-). Посмотрим...

Итого... Общее количество процессов - 8, что меньше, максимально возможных в scmRTOS, 31. Каждый из назначенных процессов "самостоятелен", то есть не зависит от работы другого процесса. Любой из процессов может быть изъят/добавлен в систему, что не повлияет на функционирование системы в целом...

Дополнение!
Как только количество процессов определено, необходимо "..сконфигурировать систему.." /сообщить ситеме сколько будет процессов/. Для этого необходимо в файле "..\scr\scmRTOS_config.h" указать значение конфигурационного макроса scmRTOS_PROCESS_COUNT (см.стр.29 scmRTOS), для нашего случая:

...
//--------------------------------------------------------------
//
// Specify scmRTOS Process Count. Must be less then 32
//
//
#define scmRTOS_PROCESS_COUNT 8
...


На самом деле, в системе будет назначено 9 процессов. "Девятый" - системный процесс IdleProcess планируется автоматически. Он имеет низший приоритет. И имеено необходимостью наличия в ситеме этого процесса вызвано ограничение числа пользовательских процессов (31 при 32 "физически" возможных).

Теперь самое время заняться установкой приоритета для каждого из процессов..

19.01.09г.

Установка приоритетов процессов

Для чего нужны и почему важны приоритеты?
Установка приоритетов позволяет пренебречь одним процессом в пользу другого, выделить главный, самый важный процесс. Установить очередность выполнения процессов/действий. Важно ли это? Конечно! Приведу фривольный, но очень точный и наглядный пример распределения приоритетов процессов (пример, кстати, широко известный, как анекдот :-) ): 1.Достал, сходил (по малой нужде), забыл стряхнуть; 2. Достал, стряхнул, забыл сходить; 3.Сходил, стряхнул, забыл достать.
Пример в пояснении не нуждается. Да и добавить нечего..

Как в scmRTOS назначить приоритеты процессам?
В описании scmRTOS приоритеты рассмотрены в гл.4.1.4. А для того, чтобы назначить нужный приоритет процессу нужно указать "индекс в таблице указателей на процессы... ...при регистрации процесса".

...
//---------------------------------------------------------------
//
// Process types
//
typedef OS::process<OS::pr0, 200> TProc1;
typedef OS::process<OS::pr1, 400> TProc2;
typedef OS::process<OS::pr2, 200> TProc3;;;)
...


, где pr0, pr1, pr2 - приоритеты процессов. pr0 - наивысшый приоритет.

То есть если необходимо переназначить высший приоритет, например, процессу TProc2, то достаточно изменить:

...
//---------------------------------------------------------------
//
// Process types
//
typedef OS::process<OS::pr1, 200> TProc1;
typedef OS::process<OS::pr0, 400> TProc2;
typedef OS::process<OS::pr2, 200> TProc3;;;)
...


Следует помнить, что: "...приоритеты всех процессов должны идти подряд, «дырок» не допускается, например, если в системе 4 процесса, то приоритеты процессов должны иметь значения pr0, pr1, pr2, pr3. Не допускаются также одинаковые значения приоритетов, т.е. каждый процесс должен иметь уникальное значение приоритета..." (стр.28 scmRTOS)

Порядок приоритетов "по возрастанию" (pr0 - высший) может быть изменен на "по убыванию" (pr0 - низший), но "не всегда и не везде" (С). В частности, для порта ARM7 такое изменение не возможно. О чем Сергей Борщ - автор порта, написал в файле "..\ARM7\OS_Target.h": "The ascending order is used, because of a little bit better performance. Descending order is not implemented in ARM7 port." То есть, используется порядок "по возрастанию" из-за несколько лучшей эффективности. Порядок "по убыванию" для порта ARM7 не предусмотрен.

Что же, можно возвращаться к нашей задаче... :-)
Итак: процессы спланированы, порядок назначения приоритетов рассмотрен...

Начнем с очевидного.
В приложении С.1 scmRTOS Гарри Журовым - создателем scmRTOS причины назначению процессу работы с дисплеем низшего приоритеа изложены достаточно подробно. Но, позволю себе дополнить.. Думаю, не вызовет возражений утверждение, что самой медленной в системе "человек-машина" является реакция человека. А любого вида индикация состояния (информация на дисплее, мигающая лампочка, "ревущая" сирена) призвана оповестить человека, для принятия им обдуманного решения. (та же система пожаротушения автоматически немедленно включает средства пожаротушения, а уже потом извещает людей о возникновении пожара, необходимости эвакуации и т.д.).
Другими словами, вывод информации на дисплей должен производится тогда, когда другой работы у системы не осталось.

Далее, увы, все не так очевидно..
Какому процессу отдать наивысший приоритет?
На мой взгляд, основным аргументом при опредении Главного процесса служит общее назначение системы. Если, например, создается система терморегуляции в морозильной установке, то главным должен быть процесс работы с датчиком температуры-компрессором. Если создается система управлением ориентации телескопа по заданному азимуту, то главный процесс ... бла-бла-бла .... :-)
А как же быть в нашем случае? Что в рассматриваемой задаче наиболее критично ко времени отклика?
Обработка устройств ввода (клавиатура и т.п.) - процессы медленные, так как "завязаны" на человека. Управление внешним устройством, в данной задаче, так же не требует НЕМЕДЛЕННОГО исполнения (допустим, что внешнее устройство - это блок управления вентиляционным отверстием, форточкой в квартире). В системе используется АЦП, и если он (АЦП) используется для реализации индикатора уровня звукового сигнала, или для вывода графика изменения температуры, то особая "срочность" не нужна. А вот если использовать АЦП для реализации запоминающего осциллографа /пусть "плохонького", но запоминающего/, то скорость реакции для этого процесса очень важна.
Другими словами, назначение наивысшего приоритета тому или иному процессу зависит от решаемых системой задач, и от квалифицированности программиста.

Рассуждая аналогично назначаем приоритеты:
- АЦП (ADCProc - pr0 );
- Интерфейс_РС (PCProc - pr1 );
- ВнешнееУстройство (OutDeviceProc - pr2 );
- Валкодер (ValcoderProc - pr3);
- PC_клавиатура (PCKeyboardProc - pr4 );
- IrDA_приемник (IrDAReceiverProc - pr5);
- Термодатчик (TemperProc - pr6);
- LCD ( LCDProc - pr7).


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

21.01.09г.

Средства межпроцессного взаимодействия.

Средства взаимодействия процессов друг с другом описаны Гарри Журовым в п.2.2.3 scmRTOS /для чего нужны и какие применяются в scmRTOS/ в в гл.5 /каждое из средсв описано подробно, назначение, варианты применения/.

Попробую порассуждать над возможным использованием каждого из средств. Проще говоря, то, что написан в гл5 scmRTOS попробую выразить бытовым языком.
- OS::TMutex (семафоры взаимоисключения). Если есть некий общий ресурс: массив данных или какнал/шина ввода/вывода, к которому могут получить доступ несколько процессов, то для "защиты" от совместного доступа применяется Mutex. Если в системе несколько ресурсов, которые необходимо защищать, то для каждого нужно использовать свой Mutex.
- OS:TEventFlag (флаги событий). Необходимы, как написано у автора, для "...синхронизации между процессами. Т.е. например, один из процессов для выполнения своей работы должен дождаться события. При этом он может поступать разными способами: можно просто в цикле опрашивать..." некий флаг, а можно перевести процесс "...в состояние ожидания события.." (спячку) "..и, как только событие произойдет..." процесс будет разбужен ядром, и ему будет передано управление.
- OS::message (сообщения). Фактически то же самое, что и EventLfag, только к флагу "прикреплено" сообщение (объект произвольного типа - переменная, массив, указатель, и т.д.)
- OS::сhannel (канал для передачи информации между процессами). Если очень просто, то это кольцевой буфер. Например, процесс АЦП помещает значения в такой буфер, а процесс Дисплей, когда до него очередь доходит, считывает эти значения и выводит их на экран. При этом этот "обмен" безопасен, и АЦП записывает в "конец" буфера, а дисплей считывает их с "начала". /Если появились вопросы, типа: а если буфер переполнен? а можно записать в "начало", а не в "конец"? - все в scmRTOS. Читайте! :-)/ Но самое главное, на мой взгяд, это возможность использования OS::channel для создания/построения очереди сообщений. Ну например, несколько процессов должны выводить данные на дисплей. Создаем Какнал, в который все, кто должен, пишут, а Дисплей читает и выводит. /следует отметить, что у такой организации есть одно немаловажное "ограничение" - данные, записываемые в Канал разными процессами, должны иметь одну и ту же форму (тип, размер и т.д.) А можно создать очередь сообщений с произвольными типами данных для каждого процесса? Можно! Гарри Журов все написал и разжевал. :-) (см прил. С.1 scmRTOS)/


Теперь можно переходить к конкретному выбору..

30.01.09г.

Сложно придумать что-то новое. Да и зачем? Правильный выбор уже сделан автором scmRTOS, поэтому просто подробно рассмотрим Приложение С.1 scmRTOS...

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


В первой половине абзаца излагается суть одного из принципов взаимодействия, а во второй половине методы преодоления недостатков такого способа средствами RTOS. Кстати, такой способ является основным при НЕ RTOS программировании (процедурном в том числе): как только поступает внешний сигнал, инициирующий разрешенное прерывание, то выполнение основной программы прекращается, а управление передается обработчику прерывания. И хорошо, если разработчик/программист, внял советам "...делать обработчики прерывания "короткими", по возможности, не использовать вызовы невстраиваемых функций.." и т.д. А если обработчик прерывания написан "неверно", то вероятность возникновения одновременного обращения к ресурсу в такой программе резко возрастает. /другими словами, обработчик прерывания может "портить" данные, с которыми работала основная программа"..., а если предположить, что в момент обработки одного прерывания, возникает другое, более приоритетное, то ситуация еще более усугубляется../ При этом следует помнить, что в RTOS вообще и scmRTOS в частности предусмотрены специальные средства разрешения подобных ситуаций (мутексы, обязательное сохранение контекста текущего процесса ...). /конечно, для НЕ RTOS программы, в основной ее части можно просто анализировать состояние флага прерывания, без его (прерывания) разрешения, но это уже не будет обработкой "...непосредственно в момент поступления сигнала..."/. В общем, выполнение всех манипуляций, связанных с низкоприоритетным процессом, в момент поступления внешнего сигнала - плохая идея.


01.02.09г.

...
Или посылать сообщения через очередь просто в виде данных-сигнатур и производить анализ на приемном конце. Но и тут придется городить код анализа на принимающем конце. Это, в свою очередь, повлечет необходимость обеспечить все классы-инициаторы сообщений соответствующим интерфейсом. В любом случае код разрастется, реализация «размажется».
...


Опять "все знакомо" :-) Именно так, лично я, и делал. Нужно "управлять" чем-то - извольте. Посылается некая константа (сигнатура данных /не путать с сигнатурой фукции и т.д./), например "5", а на приемном конце производится "расшифровка" и выполнение того или иного действия. И все бы ничего, но между функцией "Включить" и "Вывести строку" есть, мягко говоря, разница. Приходится анализ поступившей сигнатуры выделять в отдельный блок-процедуру, а выполнение требуемых дейтствий в другой. При этом на передающем конце должен быть такой же способ ("интерфейс") формирования команд. Гарри Е.Журов, в общении именно по этому пункту, обратил внимание еще и на сложность дополнения/изменения такого рода интерфейса. Так что - "...можно, но не нужно..."(с). Безусловно, есть ситуации когда такой подход (передача сигнатур данных) является единственно возможным. Такая ситуация возникает, на мой взгляд, когда необходимо связать два устройства, одно из которых должно ассинхронно управлять работой другого. Например пульт ДУ и TV, "брелок" и автосигнализация, комьютер и внешний контроллер и т.д.

...
Реализация через классы-сообщения с виртуальными функциями ничем не уступает вышеприведенным способам ни по эффективности, ни по замыслу. Здесь работу с ЖК дисплеем можно вынести в относительно низкоприоритетный процесс, чтобы он не мешал более высокоприоритетным, в которых просто при необходимости ставить сообщения в очередь и заниматься дальше своей более приоритетной, чем загрузка и управление ЖКД, работой. Т.е. по сути здесь имеет место постановка в очередь действий, которые не требуют немедленного выполнения. Одновременно решается проблема с атомарностью доступа к ЖК дисплею.
...



На первый взгляд, все понятно... Но, придерживаясь идеи статьи - "просто о сложном", попробую объяснить что же такое полиморфизм, виртуальная функция и абстрактный класс. Это необходимо для понимания дальнейшего материала. Следует понимать, что в любом самоучителе С++ нижеизложенные сведения будут представлены более полно и точно!
02.02.09г.
Итак...
Полиморфизм... Это один из "трех китов" С++ (инкапсуляция и наследование - два других). Полиморфизм (polymorphism) от греческого polymorphos (polys - многочисленный, morphe - форма).Определение Джесса Либерти: "В С++ полиморфизмом называется способность иметь несколько реализаций с тем же самым названием (именем)..." или "...один интерфейс, множество методов." - Г.Шилдт. Следует отметить, что опредений полиморфизма достаточно много не всегда они тождественны. В С++ рассматривается полиморфизм нескольких видов. А у автора С++ Бьярна Стауструпа я определения полиморфизма ("Полиморфизма - это ...") не нашел вообще, однако в своей книге "The C++ Programming Language Third Edition - 1997", Chapter 12, p.312 он написал "... Getting ‘‘the right’’ behavior from Employee’s functions independently of exactly what kind of Employee is actually used is called polymorphism...". Ниже приведено определение виртуальной функции Г.Шилдта, который, по-сути, повторяет это определение.

Виртуальная функция... Это как раз то "название (имя)" для которого можно создать "несколько реализаций", или тот самый "интерфейс", который может иметь "множество методов". / хочу заметить, что в русском языке слово метод является синонимом слова способ, а в языке С++ метод - это конкретная реализация виртуальной функции. Не нужно эти понятия смешивать./ Приведу определение/описание виртуальной функции Г.Шилдта: "Виртуальная функция является членом класса. Она объявляется внутри базового класса и переопределяется в производном классе... Виртуальная функция может вызываться так же, как и любая другая функция-член. Однако наиболее интересен вызов виртуальной функции через указатель, благодаря чему поддерживается динамический полиморфизм... При этом определение конкретной версии виртуальной функции имеет место не в процессе компиляции, а в процессе выполнения программы."

Абстрактный класс... Вернемся к рассмотрению виртуальной функции. При ее описании не было сказано о том, что виртуальная функция не имеет выходного, после компиляции, программного кода. Компилятор использует ее как "имя", интерфейс, программный код формируется для методов, реализаций этой функции. Не даром же называется такая функция "виртуальной" :-) Теперь классы... В вольной интерпретации Б.Страуструпа, абстрактным называется класс, для которого не могут существовать обекты ("...Some classes.. ..represent abstract concepts for which objects cannot exist..."). Снова цитата из Г.Шилдта: "...Если класс содержит хотя бы одну чистую виртуальную функцию, то о нем говорят как об абстрактном классе. Поскольку в абстрактном классе содержится, по крайней мере, одна функция, у которой отсутствует тело функции, технически такой класс неполон, и ни одного объекта этого класса создать нельзя. Таким образом, абстрактные классы могут быть только наследуемыми..."

Последнее в этой части... Мое глубокое убеждение, что изучать С++ по какой-то одной, даже очень "хорошей" книге нельзя. Естественно, что оправной точкой, первоисточником являются работы автора С++ - Бьярна Страуструпа (Bjarne Stroustrup). Но вот только его работ в русском переводе, легких для понимая, я не нашел. Поэтому для себя я выработал формулу: Г.Шилдт,"Самоучитель С++" + B.Stroustrup,"The C++ Programming Language" + "все остальное". Состав и авторы могут быть любыми, важно чтобы они дополняли друг друга, и в итоге складывалась цельная картина. И еще один момент... Бывают ситуации в жизни, когда сложное возможно объяснить просто (" - Зюйд-зюйд-вест. - Да ты палцем покажи!"(с)), а бывают когда предполагается, что собеседник, читатель и т.д. обладает базовыми понятиями, и их употребление в объяснении не вызывает вопросов... Язык С++ - это как раз такая, не простая "ситуация". И до тех пор пока словосочетания и фразы типа: "... объектная парадигма программирования..." или "...указатель базового класса можно использовать в качестве указателя на объект производного класса.." будут заставлять задумываться серьезного результата ждать сложно. Повторить созданное кем-то можно, а вот понять "почему?" и "как?" и сделать что-то свое - очень трудно..

Теперь можно перейти к рассмотрению программного кода приложения С.1...

04.02.09г.
Собственно, анализ программного кода не представляет сложностей. Тем не менее...:
- сначала необходимо создать сами действия, указатели на которые можно будет поставить в очередь. /строки 1-22 приложения/. Следует понимать, что показанные в этих строках функции должны быть ранее объявлены в программе. При этом они должны объявляться с квалификатором static. В примере аргументы функции загрузки символов имеют квалификатор cosnt, что означает способ размещения этих агрументов в памяти /см.стр.100,1-й абзац scmRTOS/;
- затем нужно создать название/имя для сообщения. Для этого создается абстрактный класс с витруальной функцией, которая и является тем самым "именем" с несколькими реализациями /строки 23-28/;
- далее создаются необходимые реализации сообщений /строки 29-73/. Именем у которых будет имя, объявленное в строках 23-28, а содежранием будут функции, показанные в строках 1-22;
- теперь необходимо определить тип и размер самой очереди сообщений /строки 74-83/;
- ну и, в заключении, нужно добавить реализацию постановки сообщений в очередь в процессах /строки 84-129/.

...

При кажущейся второстепенности организации работы с дисплеем - и приоритет у процесса почти самый низкий, и на работу остальных процессов влияния не оказывает, на самом деле, правильный интерфейс пользователя (это и дисплей, и расположение органов управления и т.д.) имеет, пожалуй, наиважнейшее значение. От того насколько правильно для восприятия будет размещена информация на дисплее, насколько полными и точными будут меню пользователя и т.д., зависит, в конечном итоге, насколько востребованным будет созданный прибор/устройтсво. Если прибор работает правильно, то пользователю безразлично ЧТО находится внутри, важно КАК это выглядит и насколько УДОБНО этим пользоваться. И большое спасибо Гарри Журову за то, что он в качестве примера использования scmRTOS выбрал именно работу с дисплеем...

(C) STAS633

05.12.08г.-04.02.09г.

Назад

 



Hosted by uCoz