30
КОМПОНЕНТЫ И ТЕХНОЛОГИИ • № 7 '2011
микроконтроллеры
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
#include
#include
#include
#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. Очередь
Достарыңызбен бөлісу: |