Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

FreeRTOS Курниц

.pdf
Скачиваний:
281
Добавлен:
25.03.2015
Размер:
4.26 Mб
Скачать

104

компоненты

микроконтроллеры

 

 

 

 

Рис. 6. Результат выполнения учебной программы № 3

Рис. 7. Последовательность выполнения задач в учебной программе № 3

 

образом место в очереди. Как только в оче-

Использование очередей

 

что несколько задач не будут одновре-

реди появилось свободное место, планиров-

для передачи больших объемов данных

 

менно обращаться к памяти, на которую

щик выведет из состояния блокировки ту за-

Если размер одного элемента очереди

 

ссылается указатель. В идеальном случае

дачу из числа «ожидавших», которая дольше

достаточно велик, то предпочтительно ис-

 

только задача-передатчик должна иметь

остальных пребывала блокированной. В на-

пользовать очередь для хранения не самих

 

доступ к памяти, пока указатель на эту

шем случае это задача-передатчик 2 (момент

элементов, а для хранения указателей на эле-

 

память находится в очереди. Когда же

времени (7)). Так как приоритет у нее выше,

менты (например, на массивы или на струк-

 

указатель прочитан из очереди, только

она вытеснит задачу-приемник и запишет

туры).

 

задача-приемник должна иметь возмож-

следующий элемент в очередь. После чего

Преимущества такого подхода:

 

ность доступа к памяти.

 

она вызовет планировщик API-функцией

Экономия памяти. Память при созда-

Память, на которую ссылается указатель,

taskYIELD(). Однако готовых к выполнению

нии очереди выделяется под все элемен-

 

должна существовать. Это требование ак-

задач с более высоким или равным приори-

ты очереди, даже если очередь пуста.

 

туально, если указатель ссылается на дина-

тетом на этот момент нет, поэтому пере-

Использование небольших по объему за-

 

мически выделенную память. Только одна

ключения контекста не произойдет, и задача-

нимаемой памяти указателей вместо объ-

 

задача должна быть ответственна за осво-

передатчик 2 продолжит выполняться. Она

емных структур или массивов позволяет

 

бождение динамически выделенной памя-

попытается записать в очередь еще один

достичь существенной экономии памяти.

 

ти. Задачи не должны обращаться к памя-

элемент, но очередь заполнена, и задача-

Меньшее время записи элемента в очередь

 

ти, если та уже была освобождена.

 

передатчик 2 перейдет в блокированное со-

и чтения его из очереди. При записи/чте-

Нельзя использовать указатель на пере-

стояние (момент времени (8)).

нии элемента из очереди происходит его

 

менные, расположенные в стеке задачи,

 

Снова сложилась ситуация, когда все вы-

побайтовое копирование. Копирование

 

то есть указатель на локальные перемен-

сокоприоритетные задачи-передатчики бло-

указателя выполняется быстрее копирова-

 

ные задачи. Данные, на которые ссылается

кированы, поэтому управление получит

ния объемных структур данных.

 

указатель, будут неверными после очеред-

низкоприоритетная задача-приемник (8).

Тем не менее использование указателей

 

ного переключения контекста.

 

Однако на этот раз после появления свобод-

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

Выводы

 

ного места в очереди разблокируется задача-

с некоторыми трудностями, преодоление

 

передатчик 1, так как теперь ее время пребы-

которых ложится на плечи программиста.

 

 

 

вания в блокированном состоянии превыша-

Для достижения корректной работы про-

 

В этой части статьи был подробно описан

ет время задачи-передатчика 2, и т.д.

граммы должны быть выполнены следую-

механизм очередей как средства межзадач-

 

Следует отметить, что в ранее приведен-

щие условия:

ного взаимодействия. Показаны основные

ном примере, когда задачи-передатчики

У памяти, адресуемой указателем, в каж-

способы организации такого взаимодей-

имеют более высокий приоритет, чем задача-

дый момент времени должна быть одна

ствия. Однако существуют еще несколько

приемник, в очереди в любой момент вре-

четко определенная задача-хозяин, ко-

API-функций для работы с очередями, кото-

мени не может быть более одного свободного

торая может обращаться к этой памя-

рые используются только для отладки ядра

места.

ти. То есть необходимо гарантировать,

FreeRTOS. О них будет рассказано в дальней-

 

 

 

ших публикациях, посвященных возможно-

 

 

 

стям отладки и трассировки. В следующей же

 

 

 

публикации внимание будет сконцентриро-

 

 

 

вано на особенностях обработки прерываний

 

 

 

микроконтроллера в среде FreeRTOS.

 

 

 

Литература

 

 

 

 

1.

Курниц А. FreeRTOS — операционная система

 

 

 

 

для микроконтроллеров // Компоненты и тех-

 

 

 

 

нологии. 2011. № 2–5.

 

 

 

 

2.

Barry R. Using the FreeRTOS real time kernel.

 

 

 

 

A Practical Guide. 2009.

 

 

 

 

3.

www.freertos.org

 

 

 

 

4.

http://ru.wikipedia.org/wiki/Очередь_(програм-

 

 

 

 

мирование)

 

 

 

 

 

КОМПОНЕНТЫ И ТЕХНОЛОГИИ • № 6 '2011

микроконтроллеры 23

Продолжение. Начало в № 2`2011 FreeRTOS —

операционная система

для микроконтроллеров

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

kurnits@stim.by микроконтроллеров.

Введение

3)Как обработчики прерываний связаны

на прерывание обработчик прерывания вы-

 

с остальным кодом и как организовать

полняет только первичные действия, напри-

Шестая часть статьи посвящена взаимо-

программу, чтобы обеспечить наибы-

мер считывает данные. Затем львиную долю

действию прерываний с остальной частью

стрейшую обработку асинхронных собы-

обработки берет на себя задача-обработчик

программы и поможет читателям ответить

тий внешнего мира?

прерывания. Такая организация обработки

на следующие вопросы:

FreeRTOS не предъявляет никаких требо-

прерываний называется отложенной обра-

1)Какие API-функции и макросы можно ис-

ваний к организации обработки событий,

боткой. При этом обработчик прерывания

пользовать внутри обработчиков преры-

однако предоставляет удобные возможности

выполняет только самые «экстренные» дей-

ваний?

для такой организации.

ствия, а основная обработка «откладывает-

2)Как реализовать отложенную обработку

Прерывание (interrupt) — это событие

ся», пока ее не выполнит задача-обработчик

прерываний?

(сигнал), заставляющее микроконтроллер

прерывания.

3)Как создавать и использовать двоичные

изменить текущий порядок исполнения ко-

 

и счетные семафоры?

манд. При этом выполнение текущей после-

Двоичные семафоры

4)Как использовать очереди для передачи

довательности команд приостанавливается,

Двоичные семафоры предназначены для

информации в обработчик прерывания

и управление передается обработчику пре-

эффективной синхронизации выполне-

и из него?

рывания — подпрограмме, которую можно

ния задачи с возникновением прерывания.

5)Каковы особенности обработки вложен-

представитьфункциейязыкаСи.Обработчик

Они позволяют переводить задачу из со-

ных прерываний во FreeRTOS?

прерывания реагирует на событие и обслу-

стояния блокировки в состояние готовности

События и прерывания

живает его, после чего возвращает управле-

к выполнению каждый раз, когда происходит

ние в прерванный код [6]. Прерывания ини-

прерывание. Это дает возможность перене-

 

циируются периферией микроконтроллера,

сти бóльшую часть кода, отвечающего за об-

Встраиваемые микроконтроллерные си-

например прерывание от таймера/счетчика

работку внешнего события, из обработчика

стемы функционируют, отвечая действиями

или изменение логического уровня на выво-

прерывания в тело задачи, выполнение кото-

на события внешнего мира. Например, полу-

де микроконтроллера.

рой синхронизировано с соответствующим

чение Ethernet-пакета (событие) требует обра-

Следует заметить, что во FreeRTOS все

прерыванием. Внутри обработчика прерыва-

ботки в задаче, которая реализует TCP/IP-стек

API-функции и макросы, имена которых

ния останется лишь небольшой, быстро вы-

(действие). Обычно встраиваемые системы

заканчиваются на FromISR или FROM_ISR,

полняющийся фрагмент кода. Говорят, что

обслуживают события, которые приходят

предназначены для использования в обра-

обработка прерывания отложена и непосред-

от множества источников, причем каждое со-

ботчиках прерываний и должны вызываться

ственно выполняется задачей-обработчиком.

бытие имеет свое требование по времени ре-

только внутри них.

Если прерывание происходит при возник-

акции системы и расходам времени на его об-

Отложенная обработка

новении особенно критичного к времени ре-

работку. При разработке встраиваемой

акции внешнего события, то имеет смысл на-

микроконтроллерной системы необходимо

прерываний

значить задаче-обработчику достаточно вы-

подобрать свою стратегию реализации обслу-

 

сокий приоритет, чтобы при возникновении

живания событий внешнего мира. При этом

При проектировании встраиваемой

прерывания она вытесняла другие задачи

перед разработчиком возникает ряд вопросов:

микроконтроллерной системы на осно-

в системе. Это произойдет, когда завершит

1)Каким образом события будут регистриро-

ве FreeRTOS необходимо учесть, насколько

свое выполнение обработчик прерывания.

ваться? Обычно применяют прерывания,

долго продолжается процесс обработки пре-

Выполнение задачи-обработчика начинается

однако возможен и опрос состояния вы-

рывания. В самом простом случае, когда при

сразу же после окончания выполнения об-

водов микроконтроллера.

обработке прерывания повторные прерыва-

работчика прерывания. Создается впечатле-

2)В случае использования прерываний необ-

ния запрещены, временные задержки в об-

ние, что весь код, отвечающий за обработку

ходимо решить, какую часть программного

работчике прерываний могут существенно

внешнего события, реализован внутри об-

кода, реализующего обработку события, по-

ухудшить время реакции системы на собы-

работчика прерывания (рис. 1).

местить внутри обработчика прерывания,

тия. Тогда для выполнения продолжитель-

На рис. 1 видно, что прерывание преры-

а какую — вне обработчика. Обычно стара-

ных действий по обработке прерывания

вает выполнение одной задачи и возвращает

ются сократить размер обработчика преры-

вводится так называемый «отложенный» ре-

управление другой. В момент времени (1)

вания настолько, насколько это возможно.

жим их выполнения [5]. В процессе реакции

выполняется прикладная задача, когда про-

КОМПОНЕНТЫ И ТЕХНОЛОГИИ • № 7 '2011

www.kit e.ru

24

микроконтроллеры

 

 

 

 

 

фор, но никогда не отдает его обратно. Такой

 

 

 

сценарий еще раз подчеркивает сходство ра-

 

 

 

боты двоичного семафора с очередью. Стоит

 

 

 

отметить, что одна из частых причин ошибок

 

 

 

в программе, связанных с семафорами, заклю-

 

 

 

чается в том, что в других сценариях задача

 

 

 

после захвата семафора должна его отдать.

 

 

 

Работа с двоичными семафорами

 

Рис. 1. Отложенная обработка прерывания с использованием двоичного семафора

Во FreeRTOS механизм семафоров основан

 

на механизме очередей. По большому счету

 

 

 

 

 

 

API-функции для работы с семафорами пред-

исходит прерывание при возникновении

На рис. 2 показано, как обработчик пре-

ставляют собой макросы — «обертки» других

какого-то внешнего события. В момент вре-

рывания отдает семафор, вне зависимости

API-функций для работы с очередями. Здесь

мени (2) управление получает обработчик

от того, был ли он захвачен до этого. Задача-

и далее для простоты будем называть их API-

прерывания, который, используя механизм

обработчик в свою очередь захватывает сема-

функциями для работы с семафорами.

двоичного семафора, выводит из блоки-

 

 

рованного состояния задачу-обработчик

а

 

прерывания. Так как приоритет задачи-

 

 

обработчика выше приоритета прикладной

 

 

задачи, то задача-обработчик вытесняет при-

 

 

кладную задачу, которая остается в состоя-

 

 

нии готовности к выполнению (3). В момент

 

 

времени (4) задача-обработчик блокируется,

 

 

ожидая возникновения следующего преры-

 

 

вания, и управление снова получает низко-

 

 

приоритетная прикладная задача.

б

 

 

В теории многопоточного программиро-

 

 

 

 

вания [1] двоичный семафор определен как

 

 

переменная, доступ к которой может быть

 

 

осуществлен только с помощью двух атомар-

 

 

ных функций (то есть тех, которые не могут

 

 

быть прерваны планировщиком):

 

 

1)wait() или P() — означает захват семафора,

 

 

 

если он свободен, и ожидание, если занят.

 

 

 

В примере выше функцию wait() реализует

в

 

 

задача-обработчик прерывания.

 

 

 

 

2)signal() или V() — означает выдачу семафо-

 

 

 

ра, то есть после того как одна задача выда-

 

 

 

ет семафор, другая задача, которая ожидает

 

 

 

возможности его захвата, может его захва-

 

 

 

тить. В примере выше функцию signal()

 

 

 

реализует обработчик прерывания.

 

 

 

Легко заметить, что операция выдачи

 

 

двоичного семафора напоминает операцию

г

 

помещения элемента в очередь, а операция

 

 

 

захвата семафора — чтения элемента из оче-

 

 

реди. Если установить размер очереди рав-

 

 

ным одному элементу, то очередь превра-

 

 

щается в двоичный семафор. Наличие эле-

 

 

мента в очереди означает, что одна (а может,

 

 

и несколько) задача произвела(и) выдачу

 

 

семафора, и теперь другая задача может его

 

 

захватить. Пустая же очередь означает ситуа-

д

 

цию, когда семафор уже был захвачен, и за-

 

 

 

дача, которая «хочет» его захватить, вынуж-

 

 

дена ожидать (находясь в блокированном со-

 

 

стоянии), пока другая задача или обработчик

 

 

прерывания произведут выдачу семафора.

 

 

 

В именах API-функций FreeRTOS для ра-

 

 

боты с семафорами используются термины

 

 

Take — эквивалентен функции wait(), то есть

 

 

захват двоичного семафора, и Give — экви-

 

 

валентен функции signal(), то есть означает

Рис. 2. Синхронизация прерывания и задачи-обработчика с помощью двоичного семафора

выдачу семафора.

 

 

 

 

 

КОМПОНЕНТЫ И ТЕХНОЛОГИИ • № 7 '2011

микроконтроллеры 25

Все API-функции работы с семафора-

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

ми сосредоточены в заголовочном файле

 

 

 

 

 

 

 

 

 

 

 

 

/Source/Include/semphr.h, поэтому следует

 

 

 

 

 

 

 

 

 

а

 

 

 

 

 

 

 

 

 

 

 

 

 

убедиться, что этот файл находится в списке

 

 

 

 

 

 

 

 

 

 

 

 

включенных (#include) в проект.

 

 

 

 

 

 

 

 

 

 

 

 

Доступ ко всем семафорам во FreeRTOS

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

(а не только к двоичным) осуществляется

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

с помощью дескриптора (идентификато-

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

ра) — переменной типа xSemaphoreHandle.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Создание двоичного семафора

 

 

 

 

 

 

 

 

 

б

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Семафор должен быть явно создан перед

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

первым его использованием. API-функция

 

 

 

 

 

 

 

 

 

 

 

 

vSemaphoreCreateBinary() служит для созда-

 

 

 

 

 

 

 

 

 

 

 

 

ния двоичного семафора.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

void vSemaphoreCreateBinary( xSemaphoreHandle xSemaphore );

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Рис. 3. Результат вызова xSemaphoreGiveFromISR():

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

а) без принудительного переключения контекста; б) с принудительным переключением контекста

 

Единственным аргументом является де-

 

 

 

 

 

 

 

 

 

 

 

 

скриптор семафора, в него будет возвращен

 

 

 

 

 

 

 

 

 

 

 

 

дескриптор в случае успешного создания

portMAX_DELAY приведет к тому, что вы-

1. xSemaphore — дескриптор семафора, ко-

семафора. Если семафор не создан по при-

хода из блокированного состояния по ис-

торый должен быть в явном виде создан

чине отсутствия памяти, вернется значение

течении времени тайм-аута не произойдет.

до первого использования.

NULL. Так как vSemaphoreCreateBinary()

Задача будет сколь угодно долго «ожидать»

2. pxHigherPriorityTaskWoken — значение

представляет собой макрос, то аргумент

возможности захватить семафор, пока та-

по адресу pxHigherPriorityTaskWoken

xSemaphore следует передавать напрямую,

кая возможность не появится. Для этого

устанавливает сама API-функция

то есть нельзя использовать указатель на де-

макроопределение INCLUDE_vTaskSuspend

xSemaphoreGiveFromISR() в зависимости

скриптор и операцию переадресации.

в файле FreeRTOSConfig.h должно быть

от того, разблокирована ли более высоко-

 

 

равно «1».

приоритетная задача в результате выдачи

Захват семафора

• Возвращаемое значение — возможны два

семафора. Подробнее об этом будет сказа-

О с у щ е с т в л я е т с я A P I - ф у н к ц и е й

варианта:

но далее.

xSemaphoreTake() и может вызываться толь-

pdPASS — свидетельствует об успешном

3. Возвращаемое значение — возможны два

ко из задач. В классической терминологии

захвате семафора. Если определено вре-

варианта:

[1] соответствует функции P() или wait().

мя тайм-аута (параметр xTicksToWait

pdPASS —вызовxSemaphoreGiveFromISR()

Чтобы задача смогла захватить семафор, он

не равен 0), то возврат значения pdPASS

был успешным, семафор отдан.

должен быть отдан другой задачей или об-

говорит о том, что семафор стал досту-

pdFAIL — означает, что семафор в мо-

работчиком прерывания. Все типы семафо-

пен до истечения времени тайм-аута

мент вызова xSemaphoreGiveFromISR()

ров за исключением рекурсивных (о них —

и был успешно захвачен.

уже был доступен, то есть ранее отдан

в следующей публикации) могут быть

pdFALSE — означает, что семафор недо-

другой задачей или прерыванием.

захвачены с помощью xSemaphoreTake().

ступен (никто его не отдал). Если опре-

Если после выдачи семафора в теле обра-

API-функцию xSemaphoreTake() нельзя вы-

делено время тайм-аута (параметр

ботчика прерывания была разблокирована

зывать из обработчиков прерываний.

xTicksToWait не равен 0 или portMAX_

более высокоприоритетная задача, чем та, что

Прототип:

DELAY), то возврат значения pdFALSE

была прервана обработчиком прерывания,

 

 

говорит о том, что время тайм-аута ис-

то API-функция xSemaphoreGiveFromISR()

portBASE_TYPE xSemaphoreTake( xSemaphoreHandle xSemaphore,

 

текло, а семафор так и не стал доступен.

установит *pxHigherPriorityTaskWoken рав-

portTickType xTicksToWait );

 

 

 

 

 

ным pdTRUE. В противном случае значение

 

 

 

 

 

 

 

 

Выдача семафора

*pxHigherPriorityTaskWoken останется без

Назначение параметров и возвращаемое

из обработчика прерывания

изменений.

значение:

Все типы семафоров во FreeRTOS, исклю-

Значение *pxHigherPriorityTaskWoken

xSemaphore — дескриптор семафора.

чая рекурсивные, могут быть выданы из тела

необходимо отслеживать для того, чтобы

Должен быть получен с помощью API-

обработчика прерывания при помощи API-

«вручную»выполнитьпереключениеконтекста

функции создания семафора.

функции xSemaphoreGiveFromISR().

задачи в конце обработчика прерывания, если

xTicksToWait — максимальное количество

API-функция xSemaphoreGiveFromISR()

в результате выдачи семафора была разблоки-

квантов времени, в течение которого за-

представляет собой специальную версию

рована более высокоприоритетная задача. Если

дача может пребывать в блокированном

API-функции xSemaphoreGive(), которая

этого не сделать, то после выполнения обра-

состоянии, если семафор невозможно

предназначена для вызова из тела обработчи-

ботчика прерывания выполнение продолжит

захватить (семафор недоступен). Для

ка прерывания.

та задача, выполнение которой были прервано

представления времени в миллисекундах

П р о т о т и п A P I - ф у н к ц и и

этим прерыванием (рис. 3). Ничего «страшно-

следует использовать макроопределение

xSemaphoreGiveFromISR():

го» в этом случае не произойдет: текущая за-

portTICK_RATE_MS [2, КиТ № 4]). Задание

 

 

 

 

дача будет выполняться до истечения текущего

xTicksToWait равным 0 приведет к тому,

portBASE_TYPE xSemaphoreGiveFromISR( xSemaphoreHandle

 

кванта времени, после чего планировщик вы-

что задача не перейдет в блокированное

xSemaphore, portBASE_TYPE *pxHigherPriorityTaskWoken );

 

полнит переключение контекста (которое он

 

 

 

 

состояние, если семафор недоступен,

 

 

 

 

выполняет каждый системный квант), и управ-

а продолжит свое выполнение сразу же.

Назначение параметров и возвращаемое

ление получит более высокоприоритетная за-

Установка xTicksToWait равным константе

значение:

дача (рис. 3а). Единственное, что пострадает, —

КОМПОНЕНТЫ И ТЕХНОЛОГИИ • № 7 '2011

www.kit e.ru

26

микроконтроллеры

 

 

 

Рис. 4. Результаты выполнения учебной программы № 1

Рис. 5. Последовательность выполнения задач в учебной программе № 1

это время реакции системы на прерывание, ко-

/*-----------------------------------------------------------*/

В демонстрационных целях использовано

торое может составлять до одного системного

/* Задача-обработчик */

не аппаратное, а программное прерывание

static void vHandlerTask(void *pvParameters) {

кванта: величина dT на рис. 3.

MS-DOS, которое «вручную» вызывается

/*Какибольшинствозадач,реализованакакбесконечныйцикл*/

 

Далее в учебной программе № 1 будет

for (;;) {

из служебной периодической задачи каждые

 

/* Реализовано ожидание события с помощью двоичного

приведен пример использования значения

500 мс. Заметьте, что сообщение на дисплей

семафора. Семафор после создания становится

*pxHigherPriorityTaskWoken для принуди-

доступен (так, как будто его кто-то отдал).

выводится как до генерации прерывания, так

Поэтому сразу после запуска планировщика задача

тельного переключения контекста.

и после него, что позволяет проследить по-

захватит его. Второй раз сделать это ей не удастся,

 

В случае использования API-функции

и она будет ожидать, находясь в блокированном

следовательность выполнения задач (рис. 4).

 

состоянии, пока семафор не отдаст обработчик

xSemaphoreGive() переключение контекста

Следует обратить внимание на использо-

прерывания. Время ожидания задано равным

происходит автоматически, и нет необходи-

бесконечности, поэтому нет необходимости проверять

вание параметра xHigherPriorityTaskWoken

возвращаемое функцией xSemaphoreTake() значение. */

мости в его принудительном переключении.

в API-функции xSemaphoreGiveFromISR().

xSemaphoreTake(xBinarySemaphore, portMAX_DELAY);

 

Рассмотрим учебную программу № 1, в ко-

/* Если программа “дошла” до этого места, значит,

До вызова функции ему присваивается значе-

 

семафор был успешно захвачен.

торой продемонстрировано использование

ние pdFALSE, а после вызова — проверяется

Обработка события, связанного с семафором.

двоичного семафора для синхронизации

В нашем случае – индикация на дисплей. */

на равенство pdTRUE. Таким образом отсле-

puts(“Handler task - Processing event.\r\n”);

прерывания и задачи-обработчика этого пре-

живается необходимость принудительного

}

рывания:

}

переключения контекста. В данной учебной

 

 

#include <stdlib.h>

/*-----------------------------------------------------------*/

программе такая необходимость возникает

 

/* Точка входа. С функции main() начнется выполнение

каждый раз, так как в системе постоянно на-

 

#include <stdio.h>

 

программы. */

ходится более высокоприоритетная задача-

 

#include <string.h>

int main(void) {

 

#include <dos.h>

/* Перед использованием семафор необходимо создать. */

обработчик, которая ожидает возможности

 

#include “FreeRTOS.h”

 

vSemaphoreCreateBinary(xBinarySemaphore);

захватить семафор.

 

#include “task.h”

 

/* Связать прерывание MS-DOS с обработчиком прерывания

 

#include “semphr.h”

vExampleInterruptHandler(). */

Для принудительного переключения кон-

 

#include “portasm.h”

 

_dos_setvect(0x82, vExampleInterruptHandler);

текста служит API-макрос portSWITCH_

 

 

 

/* Двоичный семафор – глобальная переменная */

/* Если семафор успешно создан */

 

if (xBinarySemaphore != NULL) {

CONTEXT(). Однако для других платформ

 

xSemaphoreHandle xBinarySemaphore;

 

/* Создать задачу-обработчик, которая будет

имя макроса будет иным, например, для ми-

 

 

 

/*-----------------------------------------------------------*/

синхронизирована с прерыванием.

 

Приоритет задачи-обработчика выше,

кроконтроллеров AVR это будет taskYIELD(),

 

/* Периодическая задача */

 

чем у периодической задачи. */

 

static void vPeriodicTask(void *pvParameters) {

для ARM7 — portYIELD_FROM_ISR(). Узнать

 

xTaskCreate(vHandlerTask, “Handler”, 1000, NULL, 3, NULL);

 

for (;;) {

точное имя макроса можно из демонстраци-

 

/* Создать периодическую задачу, которая будет

 

* Эта задача используется только с целью генерации

 

прерывания каждые 500 мс */

генерировать прерывание с некоторым интервалом.

онного проекта для конкретной платформы.

 

Ее приоритет – ниже, чем у задачи-обработчика. */

 

vTaskDelay(500 / portTICK_RATE_MS);

Переключение между задачами в учебной

 

xTaskCreate(vPeriodicTask, “Periodic”, 1000, NULL, 1, NULL);

 

/* Сгенерировать прерывание.

 

Вывести сообщение до этого и после. */

/* Запуск планировщика. */

программе № 1 приведено на рис. 5.

 

vTaskStartScheduler();

 

puts(“Periodic task - About to generate an interrupt.\r\n”);

Бóльшую часть времени ни одна задача

 

}

 

__asm {int 0x82} /* Сгенерировать прерывание MS-DOS */

 

puts(“Periodic task - Interrupt generated.\r\n\r\n\r\n”);

/* При нормальном выполнении программа до этого места

не выполняется (бездействие), но каждые 0,5 с

 

“не дойдет” */

 

}

управление получает периодическая задача

 

}

for (;;)

 

 

;

(1). Она выводит первое сообщение на экран

 

/*-----------------------------------------------------------*/

}

 

и принудительно вызывает прерывание, об-

 

/* Обработчик прерывания */

 

 

static void __interrupt __far vExampleInterruptHandler( void )

 

 

 

{

 

 

 

static portBASE_TYPE xHigherPriorityTaskWoken;

 

 

 

xHigherPriorityTaskWoken = pdFALSE;

 

 

 

/* Отдать семафор задаче-обработчику */

 

 

 

x S e m a p h o r e G i ve F r o m I S R ( x B i n a r y S e m a p h o r e ,

 

 

 

&xHigherPriorityTaskWoken );

 

 

 

if( xHigherPriorityTaskWoken == pdTRUE )

 

 

 

{

 

 

 

/* Это разблокирует задачу-обработчик. При этом

 

 

 

приоритет задачи-обработчика выше приоритета

 

 

 

выполняющейся в данный момент периодической

 

 

 

задачи. Поэтому переключаем контекст

 

 

 

принудительно – так мы добьемся того, что после

 

 

 

выполнения обработчика прерывания управление

 

 

 

получит задача-обработчик.*/

 

 

 

/* Макрос, выполняющий переключение контекста.

 

 

 

* На других платформах имя макроса может быть другое! */

 

 

 

portSWITCH_CONTEXT();

 

 

 

}

Рис. 6. Результаты выполнения учебной программы № 1 при отсутствии принудительного переключения контекста

 

}

 

 

 

 

 

 

КОМПОНЕНТЫ И ТЕХНОЛОГИИ • № 7 '2011

 

 

микроконтроллеры

27

 

 

второе свое сообщение на дисплей и блокиру-

а

 

ется на время 0,5 с. Система снова переходит

 

 

в состояние бездействия.

 

 

 

Если не выполнять принудительного пе-

 

 

реключения контекста, то есть исключить

 

 

из программы строку:

 

 

 

portSWITCH_CONTEXT();

 

б

 

то можно наблюдать описанный ранее эф-

 

 

фект (рис. 6).

 

 

 

В этом случае можно видеть, что сообще-

 

 

ния, выводимые низкоприоритетной пе-

 

 

риодической задачей, следуют друг за дру-

 

 

гом, то есть высокоприоритетная задача-

 

 

обработчик не получает управления сразу

 

 

после того, как обработчик прерывания от-

 

 

дает семафор.

 

в

 

Подведя итоги, можно представить такую

 

 

последовательность действий при отложен-

 

 

ной обработке прерываний с помощью дво-

 

 

ичного семафора:

 

 

 

• Происходит событие внешнего мира,

 

 

вследствие него — прерывание микро-

 

 

контроллера.

 

 

 

• Выполняется обработчик прерывания,

 

 

который отдает семафор и разблокирует

г

 

таким образом задачу — обработчик пре-

 

 

рывания.

 

 

 

• Задача-обработчик начинает выполняться,

 

 

как только завершит выполнение обработ-

 

 

чик прерывания. Первое, что она делает, —

 

 

захватывает семафор.

 

 

 

• Задача-обработчик обслуживает событие,

 

 

связанное с прерыванием, после чего пыта-

 

 

ется снова захватить семафор и переходит

д

 

в блокированное состояние, пока семафор

 

снова не станет доступен.

 

 

 

 

 

 

Счетные семафоры

 

 

 

Организация обработки прерываний с по-

 

 

мощью двоичных семафоров — отличное

 

 

решение, если частота возникновения одно-

 

 

го и того же прерывания не превышает неко-

е

 

торый порог. Если это же самое прерывание

 

возникнет до того, как задача-обработчик за-

 

 

 

 

вершит его обработку, то задача-обработчик

 

 

не перейдет в блокированное состояние по за-

 

 

вершении обработки предыдущего прерыва-

 

 

ния, а сразу же займется обслуживанием сле-

 

 

дующего. Предыдущее прерывание окажется

 

 

потерянным. Этот сценарий показан на рис. 7.

 

 

 

Таким образом, с использованием двоич-

 

 

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

Рис. 7. «Потеря» прерывания при обработке с помощью двоичного семафора

щих друг за другом событий может быть об-

служено максимум одно событие.

 

 

 

 

 

 

Решить проблему обслуживания серии

работчик которого начинает выполняться

переключению контекста задача-обработчик

быстро следующих друг за другом событий

сразу же (2). Обработчик прерывания отда-

получает управление (3). Задача-обработчик

можно используя счетные семафоры.

 

ет семафор, поэтому разблокируется задача-

выводит свое сообщение на дисплей и пы-

В отличие от двоичных семафоров со-

обработчик, которая ожидала возможности

тается снова захватить семафор, который

стояние счетного семафора определяется

захватить этот семафор. Приоритет у задачи-

уже недоступен, поэтому она блокируется.

не значениями отдан/захвачен, а представ-

обработчика выше, чем у периодической за-

Управление снова получает низкоприоритет-

ляет собой целое неотрицательное число —

дачи, поэтому благодаря принудительному

ная периодическая задача (4). Она выводит

значение счетного семафора. И если двоич-

КОМПОНЕНТЫ И ТЕХНОЛОГИИ • № 7 '2011

www.kit e.ru

28

микроконтроллеры

 

ный семафор — это, по сути, очередь дли-

 

ной в 1 элемент, то счетный семафор можно

а

представить очередью в несколько элемен-

 

тов. Причем текущее значение семафора

 

представляет собой длину очереди, то есть

 

количество элементов, которые в данный

 

момент находятся в очереди. Значение эле-

 

ментов, хранящихся в очереди, когда она ис-

 

пользуется как счетный (или двоичный) се-

 

мафор, не важно, а важно само наличие или

 

отсутствие элемента.

б

 

Существует два основных применения

 

счетных семафоров:

 

1. Подсчет событий. В этом случае обработчик

 

 

прерывания будет отдавать семафор, то есть

 

 

увеличивать его значение на единицу, ког-

 

 

да происходит событие. Задача-обработчик

 

 

будет захватывать семафор (уменьшать его

 

 

значение на единицу) каждый раз при обра-

 

 

ботке события. Текущее значение семафора

в

 

будет представлять собой разность между

 

 

количеством событий, которые произошли,

 

 

и количеством событий, которые обрабо-

 

 

таны. Такой способ организации взаимо-

 

 

действия показан на рис. 8. При создании

 

 

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

 

 

событий следует задавать начальное его зна-

 

 

чение, равное нулю.

 

2. Управление доступом к ресурсам. В этом

г

 

случае значение счетного семафора пред-

 

 

ставляет собой количество доступных ре-

 

 

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

 

 

задача должна сначала получить (захва-

 

 

тить) семафор — это уменьшит значение

 

 

семафора на единицу. Когда значение се-

 

 

мафора станет равным нулю, это означает,

 

 

что доступных ресурсов нет. Когда задача

 

 

завершает работу с данным ресурсом, она

д

 

отдает семафор — увеличивает его значе-

 

 

 

ние на единицу. При создании счетного се-

 

 

мафора для управления ресурсами следует

 

 

задавать начальное его значение равным

 

 

количеству свободных ресурсов. В даль-

 

 

нейших публикациях будет более подроб-

 

 

но освещена тема управления ресурсами

 

 

во FreeRTOS.

 

Работа со счетными семафорами

е

 

Создание счетного семафора

 

 

Как и другие объекты ядра, счетный сема-

 

фор должен быть явно создан перед первым

 

его использованием:

 

 

xSemaphoreHandle xSemaphoreCreateCounting( unsigned portBASE_

 

 

TYPE uxMaxCount,

 

 

unsigned portBASE_TYPE uxInitialCount );

 

 

 

Рис. 8. Подсчет событий с помощью счетного семафора

Назначение параметров и возвращаемое

 

 

значение:

ступных ресурсов, если семафор использу-

чать, что ни одного события еще не про-

1. uxMaxCount — задает максимально воз-

ется для управления ресурсами.

изошло. Если семафор используется для

можное значение семафора. Если проводить

2. uxInitialCount — задает значение сема-

управления доступом к ресурсам, то сле-

аналогию с очередями, то он эквивалентен

фора, которое он принимает сразу после

дует установить uxInitialCount равным

размеру очереди. Определяет максимальное

создания. Если семафор используется для

максимальному значению — параметру

количество событий, которые может обра-

подсчета событий, следует установить

uxMaxCount. Это будет означать, что все

ботать семафор, или общее количество до-

uxInitialCount равным 0, что будет озна-

ресурсы свободны.

КОМПОНЕНТЫ И ТЕХНОЛОГИИ • № 7 '2011

микроконтроллеры 29

Рис. 9. Обработка быстро следующих событий

3.Возвращаемое значение — равно NULL, если семафор не создан по причине отсутствия требуемого объема свободной памяти. Ненулевое значение означает успешное создание счетного семафора. Это значение необходимо сохранить в переменной типа xSemaphoreHandle для обращения

к семафору в дальнейшем.

API-функции выдачи (инкремента, увеличения на единицу) и захвата (декремента, уменьшения на единицу) счетного семафора ничем не отличаются от таковых для двоичных семафоров: xSemaphoreTake() — захват семафора; xSemaphoreGive(), xSemaphoreGiveFromISR() — выдача семафора, соответственно, из задачи и из обработчика прерывания.

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

/*-----------------------------------------------------------*/ /* Обработчик прерывания */

static void __interrupt __far vExampleInterruptHandler( void )

{

static portBASE_TYPE xHigherPriorityTaskWoken; xHigherPriorityTaskWoken = pdFALSE;

/* Отдать семафор задаче-обработчику несколько раз. Таким образом симулируется быстро следующая группа событий, с которыми связано прерывание. Первая выдача разблокирует задачу-обработчик. Последующие будут “запомнены” счетным семафором и обработаны позже. “Потери” событий не происходит. */

x S e m a p h o r e G i ve F r o m I S R ( x B i n a r y S e m a p h o r e , &xHigherPriorityTaskWoken );

x S e m a p h o r e G i ve F r o m I S R ( x B i n a r y S e m a p h o r e , &xHigherPriorityTaskWoken );

x S e m a p h o r e G i ve F r o m I S R ( x B i n a r y S e m a p h o r e , &xHigherPriorityTaskWoken );

if( xHigherPriorityTaskWoken == pdTRUE )

{

/* Макрос, выполняющий переключение контекста.

* На других платформах имя макроса может быть другое! */ portSWITCH_CONTEXT();

}

}

API-функцию создания двоичного семафора в главной функции main():

/* Перед использованием семафор необходимо создать. */ vSemaphoreCreateBinary(xBinarySemaphore);

следует заменить функцией создания счетного семафора:

/* Перед использованием счетный семафор необходимо создать. Семафор сможет обработать максимум 10 событий. Начальное

значение = 0. */

xBinarySemaphore = xSemaphoreCreateCounting( 10, 0 );

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

Судя по результатам работы (рис. 9), все три события были обработаны задачейобработчиком. Если же изменить тип используемого в программе семафора на двоичный, то результат выполнения программы не будет отличаться от приведенного на рис. 4. Это будет свидетельствовать о том, что двоичный семафор в отличие от счетного не может зафиксировать более одного события.

Использование очередей в обработчиках прерываний

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

Ранее [2, КиТ № 6] мы говорили об APIфункциях для работы с очередями: xQueueSendToFront(), xQueueSendToBack()

иxQueueReceive(). Использование их внутри тела обработчика прерывания приведет к краху программы. Для этого существуют версии этих функций, предназначенные для вызова из обработчиков прерываний: xQueueSendToFrontFromISR(), x Q u e u e S e n d T o B a c k F r o m I S R ( )

иxQueueReceiveFromISR(), причем вызов их из тела задачи запрещен. APIфункция xQueueSendFromISR() является полным эквивалентом функции xQueueSendToBackFromISR().

Функции xQueueSendToFrontFromISR(), xQueueSendToBackFromISR() служат для записи данных в очередь и отличаются лишь тем,

что первая помещает элемент в начало очереди, а вторая — в конец. В остальном их поведение идентично.

Рассмотрим их прототипы:

portBASE_TYPE xQueueSendToFrontFromISR( xQueueHandle x Q u e u e , vo i d * p v I t e m To Q u e u e p o r t B A S E _ T Y P E *pxHigherPriorityTaskWoken );

portBASE_TYPE xQueueSendToBackFromISR( xQueueHandle x Q u e u e , vo i d * p v I t e m To Q u e u e p o r t B A S E _ T Y P E *pxHigherPriorityTaskWoken );

Аргументы и возвращаемое значение:

1.xQueue — дескриптор очереди, в которую будет записан элемент. Дескриптор очереди может быть получен при ее создании API-функцией xQueueCreate().

2.pvItemToQueue — указатель на элемент, который будет записан в очередь. Размер элемента зафиксирован при создании очереди, так что для побайтового копирования элемента достаточно иметь указатель на него.

3.pxHigherPriorityTaskWoken — значение *pxHigherPriorityTaskWoken устанавливается равным pdTRUE, если существует задача, которая «хочет» прочитать данные из очереди, и приоритет у нее выше, чем у задачи, выполнение которой прервало прерывание. Если таковой задачи нет, то значение *pxHigherPriorityTaskWoken остается неизменным. Проанализировав значение *pxHigherPriorityTaskWoken после выполнения xQueueSendToFrontFromISR() или xQueueSendToBackFromISR(), можно сделать вывод о необходимости принудительного переключения контекста в конце обработчика прерывания. В этом случае управление сразу перейдет разблокированной высокоприоритетной задаче.

4.Возвращаемое значение — может принимать 2 значения:

pdPASS — означает, что данные успешно записаны в очередь.

errQUEUE_FULL — означает, что данные не записаны в очередь, так как очередь

заполнена.

API-функция xQueueReceiveFromISR()

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

Ее прототип:

portBASE_TYPE xQueueReceiveFromISR( xQueueHandle pxQueue,

void *pvBuffer, portBASE_TYPE *pxTaskWoken

);

Аргументы и возвращаемое значение:

1.xQueue — дескриптор очереди, из которой будет считан элемент. Дескриптор очереди может быть получен при ее создании APIфункцией xQueueCreate().

2.pvBuffer — указатель на область памяти, в которую будет скопирован элемент из очереди. Объем памяти, на которую ссылается указатель, должен быть не меньше размера одного элемента очереди.

КОМПОНЕНТЫ И ТЕХНОЛОГИИ • № 7 '2011

www.kit e.ru

30 микроконтроллеры

Рис. 10. Обмен данными между задачами и прерыванием в учебной программе № 2

3.pxTaskWoken — значение *pxTaskWoken

устанавливается равным pdTRUE, если существует задача, которая «хочет» записать данные в очередь, и приоритет у нее выше, чем у задачи, выполнение которой прервало прерывание. Если таковой задачи нет, то значение *pxTaskWoken остается неизменным. Проанализировав значение *pxTaskWoken

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

4.Возвращаемое значение — может принимать 2 значения:

–  pdTRUE — означает, что данные успешно прочитаны из очереди.

pdFALSE — означает, что данные не прочитаны, так как очередь пуста.

Следует обратить внимание, что в отличие отверсийAPI-функцийдляработысочередя- ми, предназначенными для вызова из тела задачи, описанные выше API-функции не имеют параметра portTickType xTicksToWait, который задает время ожидания задачи в блокированном состоянии. Что и понятно, так как обработчик прерывания — это не задача, и он не может переходить в блокированное состояние. Поэтому если чтение/запись из/в очередь невозможно выполнить внутри обработчика прерывания, то соответствующая API-функция вернет управление сразу же.

Эффективное использование очередей

Бóльшая часть демонстрационных проектов из дистрибутива FreeRTOS содержит пример работы с очередями, в котором очередь используется для передачи каждого отдельного символа, полученного от универсального асинхронного приемопередатчика (UART), где символ записывается в очередь внутри обработчика прерывания, а считывается из нее в теле задачи.

Передача сообщения побайтно при помощи очереди — это очень неэффективный метод обмена информацией (особенно на высоких скоростяхпередачи)иприводитсявдемонстрационных проектах лишь для наглядности.

Гораздо эффективнее использовать один из следующих подходов:

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

2.Интерпретировать сообщение внутри обработчика прерывания, а очередь использовать для передачи интерпретированной команды(какпоказанонарис.5,КиТ№6`2011, стр. 102). Такой подход допускается, если интерпретация не содержит сложных алгоритмов и занимает немного процессорного

времени.

Рассмотрим учебную программу № 2, в которой продемонстрировано применение API-функций xQueueSendToBackFromISR() и xQueueReceiveFromISR() внутри обработчика прерываний. В программе реализована задача — генератор чисел, которая отвечает за генерацию последовательности целых чисел. Целые числа по 5 штук помещаются в очередь № 1, после чего происходит программное прерывание (для простоты оно генерируется из тела задачи — генератора чисел). Внутри обработчика прерывания происходит чтение числа из очереди № 1 с помощью APIфункции xQueueReceiveFromISR(). Далее это число преобразуется в указатель на строку, который помещается в очередь № 2 с помощью API-функции xQueueSendToBackFromISR(). Задача-принтер считывает указатели из очереди № 2 и выводит соответствующие им строки на экран (рис. 10).

Текст учебной программы № 2:

#include <stdlib.h> #include <stdio.h> #include <string.h> #include <dos.h> #include “FreeRTOS.h” #include “task.h” #include “queue.h” #include “portasm.h”

/* Дескрипторы очередей – глобальные переменные */ xQueueHandle xIntegerQueue;

xQueueHandle xStringQueue;

/*-----------------------------------------------------------

*/

/* Периодическая задача — генератор чисел */

 

static void vIntegerGenerator(void *pvParameters) {

 

portTickType xLastExecutionTime; unsigned portLONG ulValueToSend = 0; int i;

/* Переменная xLastExecutionTime нуждается в инициализации текущим значением счетчика квантов.

Это единственный случай, когда ее значение задается явно. В дальнейшем ее значение будет автоматически модифицироваться API-функцией vTaskDelayUntil(). */

xLastExecutionTime = xTaskGetTickCount(); for (;;) {

/* Это периодическая задача. Период выполнения – 200 мс. */ vTaskDelayUntil(&xLastExecutionTime,200/portTICK_RATE_MS); /* Отправить в очередь № 1 5 чисел от 0 до 4. Числа будут

считаны из очереди в обработчике прерывания. Обработчик прерывания всегда опустошает очередь, поэтому запись 5 элементов будет всегда возможна – в переходе в блокированное состояние нет необходимости */

for (i = 0; i < 5; i++) {

xQueueSendToBack(xIntegerQueue, &ulValueToSend, 0); ulValueToSend++;

}

/* Принудительно вызвать прерывание. Отобразить сообщение до его вызова и после. */

puts(“Generator task - About to generate an interrupt.”); __asm {int 0x82} /* Эта инструкция сгенерирует прерывание. */ puts(“Generator task - Interrupt generated.\r\n”);

 

}

}

 

/*

-----------------------------------------------------------*/

/* Обработчик прерывания */

static void __interrupt __far vExampleInterruptHandler( void )

{

static portBASE_TYPE xHigherPriorityTaskWoken; static unsigned long ulReceivedNumber;

/* Массив строк определен как static, значит, память для его размещения выделяется как для глобальной переменной (он хранится не в стеке). */

static const char *pcStrings[] =

{

“String 0”, “String 1”, “String 2”, “String 3”

};

/* Аргумент API-функции xQueueReceiveFromISR(), который устанавливается в pdTRUE, если операция с очередью разблокирует более высокоприоритетную задачу.

Перед вызовом xQueueReceiveFromISR() должен принудительно устанавливаться в pdFALSE */

xHigherPriorityTaskWoken = pdFALSE;

/* Считывать из очереди числа, пока та не станет пустой. */ while( xQueueReceiveFromISR( xIntegerQueue,

&ulReceivedNumber,

&xHigherPriorityTaskWoken ) != errQUEUE_EMPTY )

{

/* Обнулить в числе все биты, кроме последних двух. Таким образом, полученное число будет принимать значения от 0 до 3. Использовать полученное число как индекс в массиве строк. Получить таким образом указатель на строку, который передать в очередь № 2 */

ulReceivedNumber &= 0x03; xQueueSendToBackFromISR( xStringQueue,

&pcStrings[ ulReceivedNumber ], &xHigherPriorityTaskWoken );

}

/* Проверить, не разблокировалась ли более высокоприоритетная задача при записи в очередь. Если да, то выполнить принудительное переключение контекста. */

if( xHigherPriorityTaskWoken == pdTRUE )

{

 

/* Макрос , выполняющий переключение контекста.

 

На других платформах имя макроса может быть другое! */

 

portSWITCH _ CONTEXT();

 

}

}

 

/*

-----------------------------------------------------------*/

/* Задача-принтер. */

static void vStringPrinter(void *pvParameters) {

 

char *pcString;

 

/* Бесконечный цикл */

 

for (;;) {

 

/* Прочитать очередной указатель на строку из очереди № 2.

 

Находится в блокированном состоянии сколь угодно долго,

 

пока очередь № 2 пуста . */

 

xQueueReceive(xStringQueue, &pcString, portMAX_DELAY);

 

/* Вывести строку , на которую ссылается указатель на дисплей. */

 

puts(pcString);

 

}

}

 

/*-----------------------------------------------------------

*/

/* Точка входа. С функции main() начнется выполнение программы. */

int main(void) {

/* Как и другие объекты ядра, очереди необходимо создать до первого их использования. Очередь xIntegerQueue будет хранить переменные типа unsigned long. Очередь

КОМПОНЕНТЫ И ТЕХНОЛОГИИ • № 7 '2011

микроконтроллеры 31

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

ния еще не завершился, а возникает высоко-

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

приоритетноепрерывание,ипроцессорначи-

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

нает выполнять его программу-обработчик.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Большинство портов FreeRTOS допу-

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

скает вложение прерываний. Эти порты

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

требуют задания одного или двух конфи-

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

гурационных макроопределений в файле

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

FreeRTOSConfig.h:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

1. configKERNEL_INTERRUPT_PRIORITY —

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

задает приоритет прерывания, исполь-

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

зуемого для отсчета системных квантов

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

FreeRTOS. Если порт не использует ма-

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

кроопределение configMAX_SYSCALL_

Рис. 11. Результаты выполнения учебной программы № 2

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

INTERRUPT_PRIORITY, то для обеспече-

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

ния вложенности прерываний все прерыва-

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

ния, в обработчиках которых встречаются

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

API-функции FreeRTOS, должны иметь

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

этот же приоритет.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

2. conf igMAX_SYSCALL_INTERRUPT_

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

PRIORITY — задает наибольший приоритет

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

прерывания, из обработчика которого мож-

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

но вызывать API-функции FreeRTOS (чтобы

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

прерывания могли быть вложенными).

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Получить модель вложенности прерыва-

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

ний без каких-либо ограничений можно задав

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

значение configMAX_SYSCALL_INTERRUPT_

Рис. 12. Последовательность выполнения задач и прерываний в учебной программе № 2

 

PRIORITY выше, чем configKERNEL_

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

INTERRUPT_PRIORITY.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Рассмотрим пример. Пусть некий микро-

xStringQueue будет хранить переменные типа char* –

 

редь № 2 разблокирует высокоприоритетную

контроллер имеет 7 возможных приоритетов

указатели на нуль-терминальные строки.

 

 

задачу-принтер (3). Задача-принтер считыва-

прерываний. Значение приоритета 7 соответ-

Обе очереди создаются размером 10 элементов.

 

Реальная программа должна проверять значения xIntegerQueue,

 

ет указатели на строки из очереди № 2, пока

ствует самому высокоприоритетному пре-

xStringQueue, чтобы убедиться, что очереди успешно созданы. */

 

 

они там есть, и выводит соответствующие

рыванию, 1 — самому низкоприоритетному.

xIntegerQueue = xQueueCreate(10, sizeof(unsigned long));

 

xStringQueue = xQueueCreate(10, sizeof(char *));

 

строки на экран. Как только очередь № 2 опу-

Зададим значение configMAX_SYSCALL_

/* Связать прерывание MS-DOS с обработчиком прерывания

 

 

стошится, задача-принтер переходит в бло-

INTERRUPT_PRIORITY = 3, а значение

vExampleInterruptHandler(). */

 

_dos_setvect(0x82, vExampleInterruptHandler);

 

кированное состояние (4). Управление снова

configKERNEL_INTERRUPT_PRIORITY = 1

/* Создать задачу — генератор чисел с приоритетом 1. */

 

xTaskCreate(vIntegerGenerator, “IntGen”, 1000, NULL, 1, NULL);

 

получает низкоприоритетная задача — ге-

(рис. 13).

/* Создать задачу-принтер с приоритетом 2. */

 

нератор чисел, которая также блокируется

Прерывания с приоритетом 1–3 не будут

xTaskCreate(vStringPrinter, “String”, 1000, NULL, 2, NULL);

 

/* Запуск планировщика. */

 

на время ~200 мс, так что система снова пере-

выполняться, пока ядро или задача выпол-

vTaskStartScheduler();

 

ходит в состояние бездействия (5).

няют код, находящийся в критической сек-

/* При нормальном выполнении программа до этого места

 

“не дойдет” */

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

ции, но могут при этом использовать API-

for (;;)

 

Вложенность прерываний

 

функции. На время реакции на такие преры-

;

 

 

 

 

 

 

 

 

 

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

вания будет оказывать влияние активность

 

 

 

 

 

 

 

 

 

 

Во многих архитектурах микроконтролле-

ядра FreeRTOS.

Заметьте, что для эффективного распреде-

ров прерывания имеют приоритеты, которые

На прерывания с приоритетом 4 и выше

ления ресурсов памяти данных (как и реко-

могут быть жестко заданы, но может суще-

не влияют критические секции, так что ниче-

мендовалось в [2, КиТ № 6]) очередь № 2 хра-

ствовать возможность и конфигурировать

го, что делает ядро в данный момент, не поме-

нит не сами строки, а лишь указатели на стро-

уровни приоритетов прерываний.

шает выполнению обработчика такого пре-

ки, которые содержатся в отдельном массиве.

Важно различать приоритет задач и прио-

рывания. Обычно те прерывания, которые

Такое решение вполне допустимо, так как со-

ритет прерываний. Приоритеты прерываний

имеют самые строгие временны′е требования

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

аппаратно фиксированы в архитектуре микро-

(например, управление током в обмотках

По результатам выполнения (рис. 11) вид-

контроллера (или определены при его кон-

двигателя), должны иметь приоритет выше,

но, что в результате возникновения прерыва-

фигурации), а приоритеты задач — это про-

чем configMAX_SYSCALL_INTERRUPT_

ния была разблокирована высокоприоритет-

граммная абстракция на уровне ядра FreeRTOS.

PRIORITY, чтобы гарантировать, что ядро

ная задача-принтер, после чего управление

Приоритет прерываний задает преимущество

не внесет дрожание (jitter) во время реакции

снова возвращается низкоприоритетной за-

на выполнение того или иного обработчика

на прерывание.

даче — генератору чисел (рис. 12).

прерыванияпривозникновениисразунесколь-

И наконец, прерывания, которые не вы-

Задача-бездействие выполняется бóльшую

ких прерываний. Задачи не выполняются

зывают никаких API-функций, могут иметь

часть времени. Каждые 200 мс она вытесняет-

во время выполнения обработчика прерыва-

любой из возможных приоритетов.

ся задачей — генератором чисел (1). Задача —

ния, поэтому приоритет задач не имеет ника-

Критическая секция в FreeRTOS — это уча-

генератор чисел записывает в очередь № 1

кого отношения к приоритету прерываний.

сток кода, во время выполнения которого за-

пять целых чисел, после чего принудитель-

Под вложенностью прерываний понима-

прещены прерывания процессора и, соответ-

но вызывает прерывание (2). Обработчик

ется корректная работа FreeRTOS при одно-

ственно, не происходит переключение кон-

прерывания считывает числа из очереди

временном возникновении сразу нескольких

текста каждый квант времени [7]. Подробнее

№ 1 и записывает в очередь № 2 указатели

прерываний с разными приоритетами, когда

о критических секциях — в следующей пу-

на соответствующие строки. Запись в оче-

обработчик низкоприоритетного прерыва-

бликации.

КОМПОНЕНТЫ И ТЕХНОЛОГИИ • № 7 '2011

www.kit e.ru

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]