![](/user_photo/2706_HbeT2.jpg)
FreeRTOS Курниц
.pdf![](/html/2706/313/html_xzvV9fFCcg.49zV/htmlconvd-h4YvAf31x1.jpg)
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 |
![](/html/2706/313/html_xzvV9fFCcg.49zV/htmlconvd-h4YvAf32x1.jpg)
микроконтроллеры 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 |
![](/html/2706/313/html_xzvV9fFCcg.49zV/htmlconvd-h4YvAf33x1.jpg)
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 |
![](/html/2706/313/html_xzvV9fFCcg.49zV/htmlconvd-h4YvAf34x1.jpg)
микроконтроллеры 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 |
![](/html/2706/313/html_xzvV9fFCcg.49zV/htmlconvd-h4YvAf35x1.jpg)
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 |
![](/html/2706/313/html_xzvV9fFCcg.49zV/htmlconvd-h4YvAf36x1.jpg)
|
|
микроконтроллеры |
27 |
|
|
второе свое сообщение на дисплей и блокиру- |
|
а |
|
ется на время 0,5 с. Система снова переходит |
|
|
|
в состояние бездействия. |
|
|
|
Если не выполнять принудительного пе- |
|
|
|
реключения контекста, то есть исключить |
|
|
|
из программы строку: |
|
|
|
portSWITCH_CONTEXT(); |
|
б |
|
то можно наблюдать описанный ранее эф- |
|
|
|
фект (рис. 6). |
|
|
|
В этом случае можно видеть, что сообще- |
|
|
|
ния, выводимые низкоприоритетной пе- |
|
|
|
риодической задачей, следуют друг за дру- |
|
|
|
гом, то есть высокоприоритетная задача- |
|
|
|
обработчик не получает управления сразу |
|
|
|
после того, как обработчик прерывания от- |
|
|
|
дает семафор. |
|
в |
|
Подведя итоги, можно представить такую |
|
|
|
последовательность действий при отложен- |
|
|
|
ной обработке прерываний с помощью дво- |
|
|
|
ичного семафора: |
|
|
|
• Происходит событие внешнего мира, |
|
|
|
вследствие него — прерывание микро- |
|
|
|
контроллера. |
|
|
|
• Выполняется обработчик прерывания, |
|
|
|
который отдает семафор и разблокирует |
|
г |
|
таким образом задачу — обработчик пре- |
|
|
|
рывания. |
|
|
|
• Задача-обработчик начинает выполняться, |
|
|
|
как только завершит выполнение обработ- |
|
|
|
чик прерывания. Первое, что она делает, — |
|
|
|
захватывает семафор. |
|
|
|
• Задача-обработчик обслуживает событие, |
|
|
|
связанное с прерыванием, после чего пыта- |
|
|
|
ется снова захватить семафор и переходит |
|
д |
|
в блокированное состояние, пока семафор |
|
|
снова не станет доступен. |
|
|
|
|
|
|
|
|
Счетные семафоры |
|
|
|
Организация обработки прерываний с по- |
|
|
|
мощью двоичных семафоров — отличное |
|
|
|
решение, если частота возникновения одно- |
|
|
|
го и того же прерывания не превышает неко- |
|
е |
|
торый порог. Если это же самое прерывание |
|
|
возникнет до того, как задача-обработчик за- |
||
|
|
||
|
|
вершит его обработку, то задача-обработчик |
|
|
|
не перейдет в блокированное состояние по за- |
|
|
|
вершении обработки предыдущего прерыва- |
|
|
|
ния, а сразу же займется обслуживанием сле- |
|
|
|
дующего. Предыдущее прерывание окажется |
|
|
|
потерянным. Этот сценарий показан на рис. 7. |
|
|
|
Таким образом, с использованием двоич- |
|
|
|
ных семафоров из цепочки быстро следую- |
|
Рис. 7. «Потеря» прерывания при обработке с помощью двоичного семафора |
щих друг за другом событий может быть об- |
||
служено максимум одно событие. |
|
||
|
|
|
|
|
|
Решить проблему обслуживания серии |
|
работчик которого начинает выполняться |
переключению контекста задача-обработчик |
быстро следующих друг за другом событий |
|
сразу же (2). Обработчик прерывания отда- |
получает управление (3). Задача-обработчик |
можно используя счетные семафоры. |
|
ет семафор, поэтому разблокируется задача- |
выводит свое сообщение на дисплей и пы- |
В отличие от двоичных семафоров со- |
|
обработчик, которая ожидала возможности |
тается снова захватить семафор, который |
стояние счетного семафора определяется |
|
захватить этот семафор. Приоритет у задачи- |
уже недоступен, поэтому она блокируется. |
не значениями отдан/захвачен, а представ- |
|
обработчика выше, чем у периодической за- |
Управление снова получает низкоприоритет- |
ляет собой целое неотрицательное число — |
|
дачи, поэтому благодаря принудительному |
ная периодическая задача (4). Она выводит |
значение счетного семафора. И если двоич- |
КОМПОНЕНТЫ И ТЕХНОЛОГИИ • № 7 '2011 |
www.kit e.ru |
![](/html/2706/313/html_xzvV9fFCcg.49zV/htmlconvd-h4YvAf37x1.jpg)
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
![](/html/2706/313/html_xzvV9fFCcg.49zV/htmlconvd-h4YvAf38x1.jpg)
микроконтроллеры 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 |
![](/html/2706/313/html_xzvV9fFCcg.49zV/htmlconvd-h4YvAf39x1.jpg)
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
![](/html/2706/313/html_xzvV9fFCcg.49zV/htmlconvd-h4YvAf40x1.jpg)
микроконтроллеры 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 |