В те недале­кие вре­мена, ког­да мы были молоды­ми и здо­ровы­ми, все ламеры сидели на Windows 98, а тру‑хакеры пили пиво и дико нап­рягались, уста­нав­ливая на свои машины седь­мую слак­варь. Пос­тавил слак­варь — стал муж­чиной. Сегод­ня уста­новить линукс на свою машину может каж­дая блон­динка, поэто­му нам, хакерам, при­ходит­ся искать для себя новые испы­тания. Как нас­чет уста­нов­ки опе­раци­онной сис­темы реаль­ного вре­мени scmRTOS на Arduino? 🙂
 

Немного теории

По­хоже, что с логичес­кой точ­ки зре­ния без сак­рамен­таль­ного воп­роса «что же такое опе­раци­онная сис­тема?» нам не обой­тись, как бы баналь­но этот воп­рос ни зву­чал. По сути, ОС — это некий набор прог­рамм, который поз­воля­ет дру­гим прог­раммис­там не думать о железе, не замора­чивать­ся над раз­делени­ем ресур­сов физичес­кой сис­темы и обес­печени­ем мно­гоза­дач­ности. Более умно и под­робно это опи­сано в Википе­дии.

Те­перь кос­немся тер­мина опе­раци­онная сис­тема реаль­ного вре­мени. Если крат­ко, то ось такого типа гаран­тиру­ет реак­цию прог­раммы на «внеш­ний раз­дра­житель» (аппа­рат­ное или прог­рам­мное пре­рыва­ние) не более, чем через ого­ворен­ное вре­мя. Чуть коп­нув, мож­но выяс­нить, что сущес­тву­ют понятия «жес­тко­го реал­тай­ма» и «мяг­кого реал­тай­ма», но нас­толь­ко глу­боко в воп­рос вда­вать­ся не буду, на эту тему в интерне­тах дос­таточ­но матери­ала. Отме­чу толь­ко, что, выс­тавив в нас­толь­ной опе­раци­онной сис­теме при­ори­тет для про­цес­са «мак­сималь­ный», ты все рав­но не получишь «истинно­го» реал­тай­ма.

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

Но отвле­чем­ся от катас­троф. Про­ще говоря, ОС реаль­ного вре­мени обес­печит тво­ему коду переда­чу управле­ния за извес­тное вре­мя (от величи­ны это­го вре­мени зачас­тую зависит и выбор самой ОС) при воз­никно­вении какого‑либо события.

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

 

Многопоточность

И как же все это дол­жно работать, ведь ядро у мик­рокон­трол­лера Arduino одно, а про­цес­сов, которые надо выпол­нить «одновре­мен­но», мно­го? Для решения этой задачи при­дума­ли пла­ниров­щик, в его обя­зан­ности вхо­дят сле­дующие дей­ствия:

  • ре­шать, какой код выпол­нять в текущий момент;
  • при перек­лючении про­цес­сов отве­чать за сох­ранение сте­ков текуще­го про­цес­са и вос­ста­нов­ление сте­ков перек­люча­емо­го про­цес­са.

Ре­шение о перек­лючении меж­ду про­цес­сами при­нима­ется на осно­ве таких фак­торов, как исте­чение кван­та опе­раци­онной сис­темы (при орга­низа­ции рав­нопри­ори­тет­ного пла­ниро­вания или round robin), появ­ление необ­ходимос­ти выпол­нить код более высокоп­риори­тет­ного про­цес­са, передать управле­ние про­цес­су, который ожи­дал аппа­рат­ное событие (и это событие нас­тупило), при­нуди­тель­ный вызов перек­лючения про­цес­сов. На самом деле фак­торов боль­ше, я перечис­лил толь­ко основные.

Из ска­зан­ного может сло­жить­ся впе­чат­ление, что про­цес­сы всег­да будут работать по нис­ходящим при­ори­тетам (сна­чала завер­шает­ся более высокоп­риори­тет­ный про­цесс, переда­ется управле­ние менее при­ори­тет­ному и так до Idle). Но это впе­чат­ление не сов­сем вер­но. Зачас­тую про­цес­сы прив­лека­ют перифе­рию, от которой тре­бует­ся дож­дать­ся отве­та, и тог­да про­цесс «впа­дает в спяч­ку» (если код написан акку­рат­но), а пла­ниров­щик переда­ет управле­ние менее при­ори­тет­ному про­цес­су. Как толь­ко будет получен ответ от перифе­рии, обра­бот­чик пре­рыва­ний перифе­рии дол­жен взвес­ти соот­ветс­тву­ющий флаг (семафор, нап­ример), по которо­му и будет раз­бужен уснувший про­цесс.

 

Пациента на стол

Прин­цип работы всех ОС реаль­ного вре­мени прак­тичес­ки оди­наков (не зря они объ­еди­нены наз­вани­ем ;)), но есть и раз­личия: раз­ная реали­зация пла­ниров­щиков, раз­ное обес­печение меж­про­цес­сно­го вза­имо­дей­ствия, реали­зация тай­меров, набор плю­шек в виде под­дер­жки из короб­ки перифе­рий­ного обо­рудо­вания и фай­ловых сис­тем и про­чее. Что­бы разоб­рать­ся в самих прин­ципах работы ОС реаль­ного вре­мени, надо оста­новить­ся на чем‑то одном. Выбор пал на scmRTOS, написан­ной на С++ (если до это­го ты писал толь­ко на си, плю­сов не бой­ся — их тут нем­ного :)).

warning

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

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

В про­екте scmRTOS уже есть порт для нашего мик­рокон­трол­лера (точ­нее, для все­го семей­ства AVR, у которых ресур­сов хва­тает на запуск этой ОС), чем мы и вос­поль­зуем­ся. Но, конеч­но же, мы покопа­емся во внут­реннос­тях, так как цель дан­ного матери­ала — не прос­то запус­тить ОС, а разоб­рать­ся, как это все работа­ет.

В качес­тве базово­го про­екта я возь­му про­ект из матери­ала прош­лой статьи. Напом­ню, что в этом про­екте реали­зова­на сиг­нализа­ция вскры­тия холодиль­ника (монито­ринг раз­мыкания кон­так­тно­го дат­чика) — мор­гание све­тоди­ода с задава­емым из кон­соли интерва­лом. В рам­ках дан­ной статьи мы все­го лишь прик­рутим ОС к это­му про­екту и добавим одну малень­кую при­моч­ку — при воз­никно­вении события тебе в кон­соль будет выдавать­ся сооб­щение об этом. Фун­кци­ональ­но, конеч­но, ничего осо­бо не поменя­ется, но на чем‑то тре­ниро­вать­ся надо.

Переключение контекстов

Под кон­тек­стом про­цес­са под­разуме­вают стек воз­вра­тов, прог­рам­мный стек, зна­чения регис­тров, то есть все то, что тре­бует­ся прог­рамме для выпол­нения. При перек­лючении кон­тек­стов про­цес­сов про­исхо­дит сох­ранение сос­тояния про­цес­сора для выпол­няемо­го в текущий момент про­цес­са и вос­созда­ние сос­тояния про­цес­сора для нового. Фак­тичес­ки про­цесс нико­им обра­зом не может заподоз­рить, выпол­няет­ся он в мно­гоза­дач­ной сре­де или же в одно­задач­ной, так как управле­ние у него забира­ется бес­церемон­но, а при воз­вра­те управле­ния окру­жающая сре­да ока­зыва­ется вос­ста­нов­лена. Единс­твен­ное, о чем необ­ходимо заботить­ся про­цес­сам, — это раз­деление ресур­сов меж­ду ними, ведь, ког­да про­цесс начина­ет поль­зовать­ся раз­деля­емым ресур­сом, управле­ние у него может быть отоб­рано и переда­но дру­гому про­цес­су, который неожи­дан­но может захотеть пополь­зовать­ся тем же ресур­сом. Как при­мер, раз­деля­емы­ми ресур­сами явля­ется перифе­рия про­цес­сора, если какой‑то про­цесс захотел пооб­щать­ся с чем‑то по шине SPI, то рекомен­дует­ся исполь­зовать объ­екты син­хро­низа­ции для бло­киров­ки дос­тупа к шине дру­гим про­цес­сам.

 

Начинаем операцию

Ска­чивай исходни­ки ОС с SourceForge. В ком­плек­те идут нес­коль­ко при­меров, сами исходни­ки опе­раци­онки и порт для AVR.

info

Вы­ход из фун­кции‑про­цес­са зап­рещен в scmRTOS, про­цесс соз­дает­ся при стар­те мик­рокон­трол­лера. Но сущес­тву­ют опе­раци­онные сис­темы, в которых про­цес­сы могут порож­дать­ся и завер­шать­ся динами­чес­ки.

info

Яд­ро Linux име­ет реал­тай­мовую вер­сию для нас­толь­ных сис­тем — RTLinux, это «надс­трой­ка» над ядром Linux, которая поз­воля­ет при­менить реал­тайм в ресур­соем­ких задачах, нап­ример для обра­бот­ки ауди­опо­тока.

info

В статье рас­смот­рены основные, на взгляд авто­ра, момен­ты работы с ОС. Матери­ал явля­ется озна­коми­тель­ным, сжа­тым и субъ­ективным. Для более глу­боко­го понима­ния рекомен­дую обра­тить­ся к опи­санию scmRTOS.

Для начала работы надо соз­дать кон­фигура­цион­ный файл, файл пор­та и файл рас­ширений. Я недол­го думая ско­пиро­вал их из при­меров, под­кру­тив толь­ко файл кон­фигура­ции scmRTOS_CONFIG.h и отклю­чив вызовы со сто­роны ядра ненуж­ных мне хуков, сле­дующим обра­зом:

#define scmRTOS_SYSTEM_TICKS_ENABLE 0
#define scmRTOS_SYSTIMER_HOOK_ENABLE 0
// Выбор схемы переключения контекстов
#define scmRTOS_CONTEXT_SWITCH_SCHEME 1
// Использовать хук переключения контекстов
#define scmRTOS_CONTEXT_SWITCH_USER_HOOK_ENABLE 1

От­мечу отдель­но scmRTOS_CONTEXT_SWITCH_SCHEME и scmRTOS_CONTEXT_SWITCH_USER_HOOK_ENABLE. Сущес­тву­ет два спо­соба перек­лючения кон­тек­ста про­цес­сов: пря­мое и асин­хрон­ное перек­лючение с помощью прог­рам­мно­го пре­рыва­ния. Я решил пой­ти путем асин­хрон­ного перек­лючения (хотя в нашем слу­чае это неп­ринци­пиаль­но). Если ты вни­матель­но смот­рел даташит на мик­рокон­трол­лер ATmega2560 (а если не смот­рел, то я под­ска­жу), то заметил, что ATmega не име­ет прог­рам­мных пре­рыва­ний. В таком слу­чае авто­ры опе­раци­онки рекомен­дуют исполь­зовать какое‑нибудь низ­копри­ори­тет­ное аппа­рат­ное пре­рыва­ние и зас­тавлять мик­рокон­трол­лер вызывать его обра­бот­чик. В пор­те для AVR выб­рано пре­рыва­ние кон­трол­лера самоп­рограм­мирова­ния SPM. Собс­твен­но, для «сти­мули­рова­ния» пре­рыва­ния и необ­ходимо, что­бы опе­раци­онная сис­тема вызыва­ла поль­зователь­ский хук перек­лючения кон­тек­стов, так как это уже плат­формо­зави­симая часть.

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

TIMER0_CS_REG = (1 << CS01) | (1 << CS00); // clk/64
UNLOCK_SYSTEM_TIMER();

Для того что­бы соз­давать мень­ше каши и писать в «тер­минах» опе­раци­онной сис­темы, я исполь­зовал мак­росы для тай­мера, опре­делен­ные в scmRTOS_TARGET_CFG.h.

Под завер­шение добавим в фун­кцию main вызов перехо­да в опе­раци­онную сис­тему и выкинем из этой фун­кции все лиш­нее.

int main(void)
{
// Настраиваем железо
init_hw();
printf_P(PSTR("\r\nStarted...\r\n"));
OS::run();
}
 

Процессы

Каж­дый про­цесс — это неболь­шая прог­рамма, которая выпол­няет­ся в бес­конеч­ном цик­ле, выход из фун­кции‑про­цес­са зап­рещен. По боль­шей час­ти все про­цес­сы обыч­но «спят» или выпол­няют код монито­рин­га внеш­ней перифе­рии (а может, даже и внеш­него обо­рудо­вания). Спя­щие про­цес­сы ожи­дают какого‑то события, для того что­бы его обра­ботать и уснуть до сле­дующе­го. Такими событи­ями могут быть сооб­щения от дру­гих про­цес­сов, сиг­налы из пре­рыва­ний об изме­нении сос­тояния перифе­рии.

В scmRTOS про­цес­сы соз­дают­ся как экзем­пля­ры клас­сов на осно­ве базово­го шаб­лонно­го клас­са OS::process<TPriority pr, size_t stack_size>. pr — это при­ори­тет про­цес­са, в зависи­мос­ти от нас­тро­ек ОС при­ори­теты рас­пре­деля­ются от млад­шего к более при­ори­тет­ному или наобо­рот. scmRTOS не поз­воля­ет соз­давать нес­коль­ко рав­нопри­ори­тет­ных про­цес­сов. stack_size зада­ет раз­мер сте­ка, который будет зарезер­вирован для про­цес­са, зна­чение не под­лежит какой‑то матема­тичес­кой оцен­ке и чаще все­го зада­ется «на глаз», а потом эмпи­ричес­ки под­гоня­ется под реаль­ность.

В нашем про­екте я сде­лал два про­цес­са:

  • про­цесс кон­соли команд;
  • про­цесс сиг­нализа­ции.

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

Для кра­соты объ­явлю typedef для каж­дого клас­са‑про­цес­са:

// Объявляем процесс обработки команд консоли
typedef OS::process<OS::pr1, 500> TCommandTask;
TCommandTask CommandTask;// экземпляр процесса консоли
// Объявляем процесс обработки сигнализации
typedef OS::process<OS::pr0, 500> TAlarmTask;
TAlarmTask AlarmTask; // экземпляр процесса сигнализации

Те­перь в прос­транс­тве имен namespace OS необ­ходимо опи­сать тело фун­кций‑про­цес­сов, при­веду код для кон­соли:

namespace OS
{
template<> OS_PROCESS void TCommandTask::exec()
{
for(;;)
{
process_command();
}
}//TCommandTask::exec()
}// namespace OS

Как видишь, ничего слож­ного, прос­то мы в бес­конеч­ном цик­ле кру­тим вызов обра­бот­чика команд.

 

Межпроцессное взаимодействие

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

warning

Обя­затель­но объ­яви экзем­пляр клас­са‑про­цес­са, ина­че опе­раци­онная сис­тема нач­нет вес­ти себя неадек­ватно, но отло­вить это будет край­не тяжело.

Код обра­бот­ки изме­нения сос­тояния дат­чика находит­ся в пре­рыва­нии, и необ­ходимо каким‑то обра­зом сооб­щить потоку, что про­изош­ло событие. Есть нес­коль­ко вари­антов: исполь­зовать фла­ги‑события (Event), «поч­товый ящик» MessageBox или меж­про­цес­сный канал channel. Я рекомен­дую исполь­зовать channel, так как он поз­воля­ет «кеширо­вать» изме­нения сос­тояния, в то вре­мя как про­цесс занима­ется обра­бот­кой «вытащен­ного» из канала события.

Для объ­екта, который мы будем переда­вать через канал, я объ­явил прос­тень­кий класс

struct TAlarmMessage {
enum TAlarmSrc
{
DI_ALARAM, // Сигнализация об изменении дискретного входа
AI_ALARM // Сигнализация об изменении аналогового датчика (резерв для примера)
}src;
uint8_t state;
};

Те­перь объ­являй экзем­пляр канала

OS::channel<TAlarmMessage, ALARM_MSG_BOX_CAPACITY> AlarmMessageBox;

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

TAlarmMessage msg;
// Тут мы уснем до получения аварийного сообщения
AlarmMessageBox.pop(msg);
// Получено сообщение, обработаем его
if (msg.state == 1)
printf_P(PSTR("\r\nAlarm: raised\r\n"));
else
printf_P(PSTR("\r\nAlarm: failed\r\n"));

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

Для отправ­ки сооб­щения об изме­нении сос­тояния дат­чика в код обра­бот­ки это­го самого изме­нения добавь сле­дующее:

TAlarmMessage msg;
msg.src = TAlarmMessage::DI_ALARAM;
msg.state = 1; // 0 для перехода в режим «норма»
AlarmMessageBox.push(msg);
ch_blink_mode(wm_alarm); // wm_normal для перехода в режим «норма»

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

 

Безопасность превыше всего

Как ты можешь заметить, наша прог­рамма из двух про­цес­сов сра­зу пыта­ется записать дан­ные в UART. Это пло­хо. А может, и не так, как кажет­ся на пер­вый взгляд. В прош­лый раз мы сде­лали прос­тень­кие FIFO-буферы для отправ­ки и при­ема дан­ных. Проб­лема зак­люча­ется в сле­дующих момен­тах:

  • в то вре­мя, пока один про­цесс пишет дан­ные в FIFO, вто­рой про­цесс может начать делать то же самое, и сооб­щение от одно­го потока будет «разор­вано» сооб­щени­ем дру­гого потока, а то и вов­се испорче­но;
  • в то вре­мя, пока про­цесс чита­ет дан­ные из FIFO, может воз­никнуть пре­рыва­ние от UART, которое кла­дет дан­ные в FIFO на при­ем;
  • два потока попыта­ются про­читать дан­ные из FIFO на при­ем;
  • в то вре­мя, пока про­цесс чита­ет дан­ные из FIFO, может воз­никнуть пре­рыва­ние от UART, которое захочет положить дан­ные в FIFO на при­ем.

Во всех перечис­ленных слу­чаях воз­ника­ют проб­лемы сов­мес­тно­го дос­тупа к внут­ренним перемен­ным FIFO. Для раз­решения этих кон­фликтных ситу­аций я орга­низо­вал канал channal с эле­мен­тами uint8_t. Допол­нитель­ные меры, которые приш­лось при­менить, — это про­вер­ка на пус­той канал перед чте­нием из него дан­ных на отправ­ку в UART и запись дан­ных в канал при при­еме из UART.

// Объявляем FIFO буферы для считывания и отправки данных в UART
#define TXFIFO_SIZE 128
uint8_t tx_buf[TXFIFO_SIZE];
static OS::TChannel uart_tx_fifo = OS::TChannel(tx_buf, TXFIFO_SIZE);
// Аналогично объявляется буфер на прием
ISR( USART0_RX_vect )
{
OS::TISRW tisrw;
unsigned char rxbyte = UDR0;
// Необходима проверка на заполненность входящего буфера, так как поведение самого класса в этой ситуации нас немного не устраивает
if (uart_rx_fifo.get_count() < RXFIFO_SIZE)
uart_rx_fifo.push(rxbyte);
}

Еще одна неп­рият­ность — это ког­да про­цесс оста­нав­лива­ется пре­рыва­нием и при работе обра­бот­чика пре­рыва­ния воз­никнет необ­ходимость переп­ланиро­вать задачи (нап­ример, получе­ны дан­ные из UART, которые ожи­дают­ся через объ­ект син­хро­низа­ции более высокоп­риори­тет­ным про­цес­сом, чем тот, что сей­час прер­ван). В этой ситу­ации объ­ект син­хро­низа­ции дает сиг­нал ОС о перек­лючении кон­тек­ста и надо как‑то объ­яснить товари­щу пла­ниров­щику, что переп­ланиро­вание сто­ит отло­жить до луч­ших вре­мен, так как в дан­ный момент идет выпол­нение кон­тек­ста пре­рыва­ния, а не про­цес­са. В scmRTOS дела­ется это дос­таточ­но прос­то, необ­ходимо объ­явить экзем­пляр клас­са‑врап­пера пре­рыва­ний OS::TISRW tisrw;.

Коп­нув в исходни­ки ОС, ты уви­дишь, что при соз­дании экзем­пля­ра клас­са‑врап­пера ядру сооб­щает­ся, что сей­час выпол­няет­ся код пре­рыва­ния, а при вызове пла­ниров­щика про­веря­ется, не в пре­рыва­нии ли мы сей­час. Если выпол­няет­ся код обра­бот­чика пре­рыва­ния, то перек­лючение кон­тек­ста не про­изой­дет (либо будет отправ­лен зап­рос на прог­рам­мное пре­рыва­ние, отве­чающее за перек­лючение кон­тек­стов, которое будет обра­бота­но пос­ле всех аппа­рат­ных пре­рыва­ний).

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

void foo()
{
// много кода
...
{// операторные скобки, ограничивающие критическую секцию
TCritSect cs;
// небезопасный код работы с общими ресурсами
...
}// закрывающая скобка критической секции
// еще много-много кода
...
}

info

Раз­работ­чики scmRTOS не рекомен­дуют исполь­зовать фун­кции push/pop для channel в пре­рыва­ниях, так как код этих фун­кций перег­ружен бло­киров­кой пре­рыва­ний и вызовом пла­ниров­щика.

danger

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

 

К пуску готов

Итак, теперь код готов к тому, что­бы его про­шить в мик­рокон­трол­лер и пос­мотреть, что же получи­лось. Про­шивай мик­рокон­трол­лер, откры­вай тер­минал­ку и для пущего эффекта наж­ми на кноп­ку RESET на пла­те. Как и в прош­лый раз, в тер­минал выводит­ся сооб­щение о стар­те и приг­лашение кон­соли:

Started...
Arduino>

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

Alarm: raised

Ес­ли пос­ле это­го зам­кнуть дат­чик, то в тер­минале сра­зу же появит­ся уве­дом­ление:

Alarm: failed

 

Заключение

На этом я закан­чиваю очень и очень крат­кое зна­комс­тво с опе­раци­онны­ми сис­темами реаль­ного вре­мени на мик­рокон­трол­лерах. scmRTOS дос­таточ­но малень­кая и мощ­ная опе­раци­онная сис­тема для такого малыша, как ATmega2560. В ней нет под­дер­жки фай­ловых сис­тем, работы с перифе­рией и еще вся­ких вкус­ностей, но в рам­ках ее при­мене­ния на мик­рокон­трол­лерах с очень огра­ничен­ными ресур­сами это разум­но воз­ложено на пле­чи раз­работ­чика — поль­зовате­ля ОС. Опе­раци­онные сис­темы дают огромный прос­тор для более кра­сиво­го, понят­ного и фун­кци­ональ­ного кода. Наде­юсь, что дан­ный матери­ал подог­рел твой инте­рес к прог­рамми­рова­нию мик­рокон­трол­леров. Желез­ный при­вет, RESET :).

Рис. 1. Экран терминала при размыкании/замыкании контактного датчика
Рис. 1. Экран тер­минала при раз­мыкании/замыка­нии кон­так­тно­го дат­чика

Программистам, работающим в Windows

Во­лею судеб матери­ал для этой статьи я готовил в ОС Windows и узнал в свя­зи с этим мно­го, хм, инте­рес­ного. Во‑пер­вых, драй­веры для Arduino при­дет­ся ска­чивать либо вмес­те с Arduino IDE (в виде ZIP-архи­ва и отту­да выдер­гивать драй­веры) либо искать на прос­торах интерне­та. Я ска­чал Arduino IDE. Вто­рой сюр­приз меня под­жидал при попыт­ке про­шить мик­рокон­трол­лер с помощью AVRDUDE. Он упор­но не хотел про­шивать­ся, но я заметил, что при перезаг­рузке иног­да он «цеп­ляет­ся». Выяс­нилось, что AVRDUDE не очень‑то хочет работать с аппа­рат­ным управле­нием потоком пор­та, если ему не ука­зать явно тип прог­рамма­тора через параметр -cwiring вмес­то -c STK500.

Оставить мнение