===== FAQ по ОСРВ OSA =====
Здесь я собрал ответы на самые распространенные вопросы по OSA.
[[osa:articles:rtos_usage#Можно ли вызывать сервисы ожидания из функций, вызываемых задачами?|Можно ли вызывать сервисы ожидания из функций, вызываемых задачами?]]\\
[[osa:articles:rtos_usage#Ожидание события в цикле|Ожидание события в цикле]]\\
[[osa:articles:rtos_usage#Можно ли создать задачу по указателю на функцию?|Можно ли создать задачу по указателю на функцию?]]\\
[[osa:articles:rtos_usage#Что будет, если отсылать/принимать сообщения из неинициализированной очереди?|Что будет, если отсылать/принимать сообщения из неинициализированной очереди?]]\\
[[osa:articles:rtos_usage#Изменение типов сообщений|Изменение типов сообщений]]\\
[[osa:articles:rtos_usage#Если две задачи ожидают одного и того же события|Если две задачи ожидают одного и того же события]]\\
[[osa:articles:rtos_usage#Модификация тела сообщения до того, как оно будет принято|Модификация тела сообщения до того, как оно будет принято]]\\
[[osa:articles:rtos_usage#Использование таймеров вне задач|Использование таймеров вне задач]]\\
[[osa:articles:rtos_usage#Почему виснет OS_Delay?|Почему виснет OS_Delay?]]
----
==== Можно ли вызывать сервисы ожидания из функций, вызываемых задачами? ====
**Ответ: нет.**
Этот вопрос, пожалуй, задают чаще остальных. Такой подход, действительно, выглядит очень заманчивым, когда, например, в задачах часто вызывается сервис OS_Delay с одним и тем же параметром:
// В этом примере просто напрашивается вынос OS_Delay(10) в отдельную функцию.
// Сам вызов сервиса занимает около 10 слов ROM, и его, конечно, хочется
// заменить одним вызовом.
...
OS_Delay(10);
...
if (...) OS_Delay(10);
...
do {
...
OS_Delay(10);
...
} while (...);
...
или когда есть несколько задач, ожидающих одно и то же событие:
...
// Ждем, когда освободится доступ в EEPROM
OS_Bsem_Wait(BS_EEPROM_FREE);
...
К сожалению в ОСРВ OSA такое недопустимо. Дело в том, что при таком подходе произойдет путаница с адресами возврата в стеке. Рассмотрим на примере:
//-----------------------------------------------
void Delay10 (void)
{
OS_Delay(10);
}
//-----------------------------------------------
void Bsem_Wait (void)
{
OS_Bsem_Wait(BS_BINSEM);
}
//-----------------------------------------------
void TaskA (void)
{
for (;;)
{
Delay10();
/*...*/
Delay10();
/*...*/
Delay10();
/*...*/
}
}
//-----------------------------------------------
void TaskB (void)
{
for (;;)
{
Bsem_Wait();
/*...*/
Bsem_Wait();
/*...*/
Bsem_Wait();
/*...*/
}
}
//-----------------------------------------------
Итак, у нас есть две задачи: TaskA и TaskB. Обе вызывают разные функции, в каждой из которых есть сервис, содержащий код возврата в планировщик (в перечне сервисов все такие сервисы в примечании отмечены буквой "T"). Для простоты рассмотрим работу этого примера на PIC16. //(Примечание: планировщик в этих контроллерах передает управление задачам присвоением адреса задачи паре регистров PCLATH:PCL, а задачи возвращают управление планировщику, совершая переход на него по GOTO. Таким образом экономится стек, т.к. ни задачи, ни планировщик не вызываются через CALL)//. При выполнении программы возможна такая последовательность:
^ . ^ Действие ^ Передача управления ^ Стек ^
| 1 | Планировщик запускает задачу **TaskA** | PCLATH:PCL=TaskA | - |
| 2 | Задача **TaskA** вызывает функцию **Delay10()**| CALL Delay10. | **ret_addr_A**\\ - |
| 3 | Функция **Delay10** вызывает системный сервис **OS_Delay**, который, инициализировав задержку, передает управление планировщику | GOTO sched | **ret_addr_A**\\ - |
| 4 | Планировщик запускает задачу **TaskB** | PCLATH:PCL=TaskB | **ret_addr_A**\\ - |
| 5 | Задача **TaskB** вызывает функцию **Bsem_Wait()** | CALL Bsem_Wait | **ret_addr_B**\\ **ret_addr_A**\\ - |
| 6 | Функция **Bsem_Wait** вызывает системный сервис **OS_Bsem_Wait**, который передает управление планировщику | GOTO sched | **ret_addr_B**\\ **ret_addr_A**\\ - |
| 7 | Планировщик крутится вхолостую, пока идет задержка, запущенная в задаче **TaskA** и пока не установлен семафор, которого ожидает задача **TaskB** | |**ret_addr_B**\\ **ret_addr_A**\\ - |
| 8 | Задержка закончилась, плнировщик передает управление задаче **TaskA** в то же место, откуда был возврат в планировщик, а именно - в середину функиции **Delay10** | PCLATH:PCL=Delay10 |**ret_addr_B**\\ **ret_addr_A**\\ - |
| 9 | И теперь - кульминация: функция делает возврат, при котором из стека берется последний положенный туда адрес, а именно - **ret_addr_B** | RETURN | **ret_addr_A**\\ - |
Как видно, после 9-ой операции мы из функции **Delay10** вернемся в функцию-задачу **TaskB**, хотя должны были вернуться в **TaskA**.
//**Примечание.** В принципе, такой подход допускается, если у программиста есть уверенность в том, что в один момент времени только одна задача производит вызов такой функции. Однако, здесь надо быть крайне осторожным и хорошо понимать, что он делает. Поэтому, если Вы не уверены, что уследите за вызовами при дальнейшем росте программы, - не применяйте такой прием!//
([[osa:articles:rtos_usage#FAQ по ОСРВ OSA|Вернуться к списку FAQ]])
----
==== Ожидание события в цикле ====
Некоторые из присланных мне программ содержали однотипную ошибку, которую я бы хотел здесь обрисовать. Иногда бывает так, что в ходе ожидания какого-либо события требуется выполнять какое-то действие. Поэтому код этого ожидания некоторые писали без использования сервисов OS_xxx_Wait, заменяя их циклом do {...} while. Рассмотрим отвлеченный пример: пока ожидаем установки какого-то двоичного семафора, нам нужно сравнивать напряжения на двух входах АЦП и, в зависимости от результата сравнения, зажигать либо красный либо зеленый светодиод.
Код такого ожидания выглядел так:
do
{
if (ADC_Read(0) > ADC_Read(1)) // Сравниваем напряжения на двух аналоговых входах
{
GREEN_LED = 1;
RED_LED = 0;
} else {
GREEN_LED = 0;
RED_LED = 1;
}
OS_Yield(); // Возврат в планировщик
} while (!OS_Bsem_Check(BS_START));
Ошибка такого подхода заключается в том, что задача, крутясь в таком цикле, является **всегда готовой** к выполнению. И если в программе есть задачи с более низким приоритетом, то они не смогут получить управление до тех пор, пока эта задача не дождется семафора. А если ожидаемый ей семафор должна установить как раз задача с более низким приоритетом, то программа просто зависнет в вечном ожидании.
Как быть в таких случаях? Здесь есть несколько вариантов решения этой коллизии.
== 1. Вынести выполняемый при ожидании код в отдельную низкоприоритетную задачу ==
С точки зрения концепции ОСРВ такой способ самый правильный. В данном конкретном примере сравнение напряжений на входах АЦП и ожидание семафора - функционально разные действия и нет никакого смысла выполнять их одновременно.
OST_TASK_POINTER tp;
/******************************************************************************/
// Отдельная задача для работы с АЦП и светодиодами
/******************************************************************************/
void Task_ADC_Leds (void)
{
tp = OS_GetCurTask();
for (;;)
{
/*...*/
/* Здесь сравниваем напряжения */
/*...*/
OS_Yield(); // Возврат в планировщик
}
}
/******************************************************************************/
// Наша задача
/******************************************************************************/
void Task (void)
{
for (;;)
{
/*...*/
// Перед ожиданием создаем задачу сравнения напряжений
OS_Task_Create(7, Task_ADC_Leds);
// Ждем наш семафор
OS_Bsem_Wait(BS_START);
// Удаляем задачу сравнения напряжений
OS_Task_Delete(tp);
/*...*/
}
}
Но при своей правильности этот подход не всегда оправдан, т.к. требует наличие свободного дескриптора на момент создания новой задачи, дополнительной глобальной переменной и времени на создание/удаление задачи.
== 2. Понизить приоритет на время ожидания ==
Понизив приоритет до минимального, мы исключаем, что какая-либо задача окажется блокированной. С точки зрения ресурсов контроллера я бы назвал такой подход оптимальным.
static char prio;
/*...*/
prio = OS_Task_GetPriority(this_task); // Запоминаем текущий приоритет задачи
OS_Task_SetPriority(this_task, 7); // Понижаем приоритет до минимального
do {
/*...*/
/* Здесь сравниваем напряжения */
/*...*/
OS_Yield();
} while (!OS_Bsem_Check(BS_START));
OS_Task_SetPriority(this_task, prio); // После цикла восстанавливаем сохраненный
// приоритет
//Примечание: рекомендуется понижать приоритет не до самого низкого (7-го), а до предпоследнего (6-го), т.к.низший приоритет удобно использовать для задачи SLEEP'а.//
== 3. Вставить в цикл небольшую задержку ==
Заменив OS_Yield() на OS_Delay(1), мы гарантировано на время одного системного тика ставим задачу в режим ожидания (время задержки можно увеличить, если одного тика мало):
do {
/*...*/
/* Здесь сравниваем напряжения */
/*...*/
OS_Delay(1);
} while (!OS_Bsem_Check(BS_START));
Недостатком такого способа будет увеличение периода сравнения напряжений. Возможно, в данном примере это не страшно, но в другом случае, если операции внутри цикла критичны ко времени, это может отрицательно сказаться на логике работы устройства.
([[osa:articles:rtos_usage#FAQ по ОСРВ OSA|Вернуться к списку FAQ]])
----
==== Можно ли создать задачу по указателю на функцию? ====
**Ответ: да**
В OSA есть два недокументированных сервиса, которые позволяют это сделать. Я их не стал описывать в общей документации, чтобы не вносить путаницы в логику работы задач. Тем не менее, такой способ создания задач может оказаться очень удобным в пользовательских приложениях, где адрес функции-задачи привязан, например, к пункту меню. Предположим, у нас определен тип структуры, содержащей название пункта меню и адрес задачи, которую нужно будет выполнять по этому пункту:
typedef struct
{
const char* strMenu;
void (*Func)(void);
} TMenuItem;
Далее в программе определен массив этих структур:
const TMenuItem UserMenu[] = {
{"Load", Task_Load},
{"Save", Task_Save},
{"View", Task_View}
{"Edit", Task_Edit}
};
Все эти задачи описываются как обычно, например:
void Task_Load (void)
{
for (;;)
{
/*...*/
}
}
Далее - одна тонкость. Чтобы компилятор правильно строил дерево вызовов подпрограмм, ему нужно указать, что функции, которые мы перечислили в массиве, являются задачами. Для этого в main() нужно для каждой такой функции вызвать сервис OS_Task_Reserve:
void main (void)
{
/*...*/
OS_Task_Reserve(Task_Load);
OS_Task_Reserve(Task_Save);
OS_Task_Reserve(Task_View);
OS_Task_Reserve(Task_Edit);
/*...*/
}
После этого компилятор будет знать, что эти функции косвенно вызываются из main, и правильно распределит локальные переменные этих функций.
Теперь в произвольном месте программы можно создавать задачи по указателю на функцию, пользуясь специальным сервисом OS_Task_Create**P**:
LCD_Out(UserMenu[i].strMenu); // Выводим на экран название функции
OS_Task_CreateP(0, UserMenu[i].Func); // Создаем задачу с высшим приоритетом
([[osa:articles:rtos_usage#FAQ по ОСРВ OSA|Вернуться к списку FAQ]])
----
==== Что будет, если отсылать/принимать сообщения из неинициализированной очереди? ====
**Ответ: ничего хорошего**
Это относится не только к неинициализированной очереди, но и к неинициализированным: счетным семафорам, коротким сообщениям, указателям на сообщения. Неизвестно, что содержат в себе эти переменные на момент обращения к ним. Поэтому нужно всегда следить за тем, чтобы эти объекты ОС инициализировались **до** первого обращения к ним.
Рекомендую проанализировать работу следующего примера:
OST_QUEUE q;
OST_MSG smsg;
OST_MSG rmsg;
void Task1 (void)
{
OS_Queue_Create(q); // Создаем очередь (инициализируем)
for (;;)
{
OS_Queue_Send(q, smsg); // Отсылаем сообщение в очередь
}
}
void Task2 (void)
{
for (;;)
{
OS_Queue_Wait(q, rmsg); // Ожидаем сообщение из очереди
}
}
void main (void)
{
OS_Init();
OS_Task_Create(1, Task1);
OS_Task_Create(0, Task2);
OS_Run();
}
Обратите внимание на расстановку приоритетов: приоритет задачи Task2 выше, чем Task1. Это означает, что первой выполнится именно задача Task2, т.е. та, которая ожидает сообщение из очереди, а задача, инициализирующая очередь, запустится второй (если OS_Queue_Wait не вызовет сбой программы). При всей очевидности выхода из ситуации (а именно - правильной расстановке приоритетов), допустить такую ошибку очень просто. Для неприоритетного режима - вообще неизвестно, какая задача запустится первой. Поэтому очереди (и все остальные объекты, требующие инициализации) следует инициализировать так, чтобы на момент обращения к ним они гарантировано были инициализированны. Тривиальный способ - создавать их в функции main() до вызова сервиса OS_Run().
([[osa:articles:rtos_usage#FAQ по ОСРВ OSA|Вернуться к списку FAQ]])
----
==== Изменение типов сообщений ====
OSA имеет два вида сообщений: указатель на сообщение и короткое однобайтовое сообщение. Различаются они тем, что с помощью первого можно передавать любой объем информации, т.к. фактически передается только указатель на нее, а с помощью второго - только одно значение (по умолчанию это значения от 1 до 255). Учитывая архитектурные особенности PIC-контроллеров, программистам оставлена возможность изменять типы этих сообщений.
Сначала поговорим о типе **указателя на сообщение**. По умолчанию указатель на сообщение имеет тип **void* **, т.е. указатель на область RAM-памяти. Учитывая, что ядро PIC-контроллера построены по гарвардской архитектуре (раздельные шины адреса для памяти данных и программы), указатели на данные в ОЗУ и указатели на константы, хранящиеся в программной памяти, - это разные вещи. Поэтому ОСРВ OSA предоставляет программисту возможность на этапе написания программы выбрать тип указателей на сообщения. Этот тип нужно указать в файл конфигурации osacfg.h:
#define OS_MSG_TYPE const char *
в этом примере мы заменяем тип указателя на сообщение так, что сможем в программе обмениваться строковыми константами.
OST_MSG_CB msg_cb; // Дескриптор сообщения
const char * MenuStrings[] = {"Load", "Save", "Save as...", "Exit"};
void Task_Menu (void)
{
for (;;)
{
for (i = 0; i < 4; i++)
OS_Msg_Send(msg_cb, MenuString[i]);
/*...*/
}
}
//Примечание: в программе может быть применен только один тип для указателей на сообщения, и он не может меняться в ходе выполнения программы. (Исключение составляют указатели в HT-PICC18, когда указан ключ компиляции -CP24.)//
Теперь об одной ошибке, вернее, о некоторой некорректности использования возможности замены типа указателя на сообщения. Несколько раз в программах видел такое:
typedef struct {
char* name;
int age;
int weight;
} TMyStruct;
#define OS_MSG_TYPE TMyStruct *
Т.е. программист создает некую свою структуру и тип сообщения заменяет указателем на нее. Ошибки здесь, конечно, нет. Но концептуально такой подход довольно спорный. За год программист может написать одну программу, а может и 10, и 20. Каждая программа может быть индивидуальна, и данные, которыми будут обмениваться задачи, - тоже. Если в каждой программе подменять тип указателя на сообщение каким-то специфичным указателем вместо void*, то возникнет некоторая неразбериха, да и проблемы с переносом модулей. Если предполагается работать с указателями на эти структуры, расположенные в RAM-области памяти, то лучше оставить тип void*. Это же замечание касается указателей на структуры, расположенные в ROM-области памяти - их лучше определять как **const void* **.
Теперь два слова о изменении типа **короткого сообщения**. Для чего оно вообще сделано? Две причины: экономия RAM и повышение скорости. Довольно часто между программами нужно обмениваться незначительными объемами информации: "нажата кнопка 5", "переключиться в режим 3", "зажечь светодиод 12". Преимущества перед указателями на сообщения:
* если бы мы использовали для передачи такой информации указатели на сообщения, нам бы пришлось тратить 2 байта ОЗУ (3 - для PIC18, 4 для PIC24 и dsPIC) на дескриптор сообщения и 1 байт на тело сообщения, куда будет указывать дескриптор, итого - 3 байта. При использовании короткого сообщения нам понадобится всего 1 байт.
* при приеме указателя на сообщение программе потребуется сперва сформировать указатель в FSR, а только потом уже получить доступ к информации. При приеме же короткого сообщения мы получаем информацию напрямую.
* для указателей на сообщения нельзя менять содержимое отправленных данных, пока задача-приемник не получила сообщение. При использовании короткого сообщения это ограничение снимается, т.к. все сообщение помещается в дескриптор.
По умолчанию короткое сообщение имеет тип **unsigned char**. Этот тип может быть заменен на любой перечислимый тип (int, long, float, bit) заданием константы в файле osacfg.h:
#define OS_SMSG_TYPE unsigned long
Такое, хоть и редко, но бывает нужно.
У короткого сообщения есть две особенности, которые нужно учитывать при проектировании программы:
- это сообщение может иметь только перечислимый тип (нужно учитывать при замене типа); т.е. этот тип нельзя переопределить на структуру, поскольку запись значения в короткое сообщение и чтение из него выполняется оператором присваивания ( = );
- через него нельзя передавать нулевое значение. Этим приходится платить за использование такого эффективного инструмента. Нулевое значение рассматривается системой как отсутствие сообщения.
([[osa:articles:rtos_usage#FAQ по ОСРВ OSA|Вернуться к списку FAQ]])
----
==== Если две задачи ожидают одного и того же события. ====
При написании программы с использованием ОСРВ OSA нужно учитывать одну особенность ее планировщика. При поиске лучшей готовой задачи для выполнения планировщик в цикле пробегается по всем дескрипторам, проверяет готовность задач и сравнивает их приоритеты. Управление получит готовая задача с высшим приоритетом. Если есть несколько задач с одинаковым приоритетом, то управление получит та, которая была рассмотрена планировщиком раньше. Дескрипторы задач хранятся в массиве, который планировщиком рассматривается как кольцевой. Каждый раз он начинает поиск готовой задачи со следующей после последней выполненной. Например, у нас 5 задач. Последней задачей выполнялась 3-я. Тогда порядок проверки задач при следующей работе планировщика будет таким: 4, 5, 1, 2, 3.
Проблема в том, что если две задачи ждут одного и того же события, например, семафора, который устанавливается третьей задачей, то одна из задач никогда не получит управление. Рассмотрим пример:
/******************************************************************************/
// Задача, устанавливающая семафор
/******************************************************************************/
void Task1 (void)
{
for (;;) {
OS_Bsem_Set(bsem);
OS_Delay(10);
}
}
/******************************************************************************/
// Первая задача, ожидающая семафор
/******************************************************************************/
void Task2 (void)
{
for (;;) {
OS_Bsem_Wait(bsem);
/*...*/
}
}
/******************************************************************************/
// Вторая задача, ожидающая семафор
/******************************************************************************/
void Task3 (void)
{
for (;;) {
OS_Bsem_Wait(bsem);
/*...*/
}
}
/******************************************************************************/
//
/******************************************************************************/
void main (void)
{
OS_Init();
OS_Task_Create(1, Task1); // Все задачи с равными приоритетами
OS_Task_Create(1, Task2);
OS_Task_Create(1, Task3);
OS_Run();
}
Последовательность просмотра задач планировщиком будет такова: Task1, Task2, Task3, Task1, Task2, Task3, ... . Проблема в том, что Task2 и Task3 не получают управления до тех пор, пока не будет установлен двоичный семафор, а устанавливается он только в Task1. Поэтому всегда будет происходить одна и та же последовательность:
- **Task1** устанавливает семафор **bs**;
- **Task2** и **Task3** становятся готовыми к выполнению;
- после работы **Task1** планировщик начинает поиск задачи, начиная со следующей после последней выполненной, т.е. начиная с **Task2**;
- планировщик рассматривает **Task2**, видит, что она готова; лучший приоритет пока не выбран, поэтому лучшим на данный момент планировщик будет считать 1 (приоритет задачи Task2). Планировщик ее запоминает как кандидата на запуск;
- планировщик рассматривает **Task3**, видит, что она готова, но ее приоритет не лучше, чем текущий лучший, поэтому он эту задачу пропускает;
- планировщик рассматривает **Task1**, видит, что она не готова к выполнению (она в задержке);
- управление передается задаче **Task2**;
- **Task2** сбрасывает семафор (сервис OS_Bsem_Wait делает это автоматически), отрабатывает какие-то свои действия и возвращается в планировщик;
- планировщик начинает просмотр списка задач, начиная с **Task3**, но уже видит, что семафор сброшен, поэтому **Task3** опять ставится в ожидание;
- следующей управление получит **Task1** по истечении времени задержки, а потом все начнется сначала.
Как тут быть? Единого способа решения нет. Можно ретранслировать семафор дальше, т.е. дождавшись его в Task2, сразу же установить его, чтобы и Task3 смогла получить управление. Но это не лучший вариант, т.к. неизвестно, сколько задач в цепочке, и на какой нужно останавливать установку семафора. Можно использовать счетный семафор, но опять же нужно знать, сколько задач его ожидают, чтобы установить в нем правильное число. На мой взгляд, самым удобным в таком случае будет использование флагов. В задаче-отправителе устанавливать все биты флага:
/*...*/
OS_Flag_Set_1(flag, 0xFF);
/*...*/
а в задачах-приемниках ожидать только своего.
/* В задаче Task2 */
OS_Flag_Wait(flag, 0x01);
OS_Flag_Set_0(flag, 0x01);
/* В задаче Task3 */
OS_Flag_Wait(flag, 0x02);
OS_Flag_Set_0(flag, 0x02);
В общем, способы решения есть, а какой применять, - на усмотрение программиста. **Главное - надо помнить об этой особенности планировщика.**
([[osa:articles:rtos_usage#FAQ по ОСРВ OSA|Вернуться к списку FAQ]])
----
==== Модификация тела сообщения до того, как оно будет принято ====
В одной присланной мне программе была допущена такая ошибка: задача формировала тело сообщения, отправляла другой задаче указатель на него, а потом сразу же модифицировала для отправки следующего. Выглядело это так:
OST_MSG_CB msg_cb;
void Task (void)
{
static char buf[3];
for (;;) {
// Формируем первое сообщение
buf[0] = '1';
buf[1] = '2';
buf[2] = '3';
OS_Msg_Send(msg_cb, buf);
OS_Yield();
// Формируем второе сообщение
buf[0] = '5';
buf[1] = '6';
buf[2] = '7';
OS_Msg_Send(msg_cb, buf);
OS_Yield();
/*...*/
}
}
Не смотря на то, что после отправки сообщения выполняется OS_Yield, чтобы дать возможность адресату получить сообщение, оно может и не доставиться с первого раза (по любой причине: задача-приемник чем-то занята, или имеет низкий приоритет, или, наконец, находится в режиме паузы). Сервис OS_Msg_Send устроен так, что он не сможет отправить сообщение до тех пор, пока предыдущее не получено адресатом. Тем не менее, следует помнить, что этот сервис следит только за дескриптором сообщения, а не за областью памяти, где фактически расположено тело.
Что произойдет в нашем примере, если после первого OS_Yield задача-адресат не успеет принять отправленное ей сообщение? Фактически ей отправляется только адрес области памяти, где располагается массив buf, в котором на момент отправки лежат значения '1', '2' и '3'. Итак, после выполнения OS_Yield сообщение не было принято. Планировщик возвращает управление задаче отправителю, и она продолжает свое выполнение с того места, откуда вышла, т.е. со следующей строки после OS_Yield. Здесь у нас происходит замена элементов буфера buf на '5', '6' и '7'. После этого задача Task пытается отправить очередное сообщение, но так как предыдущее еще не получено адресатом, то эта задача становится в ожидание, когда дескриптор освободится.
По прошествии какого-то времени адресат получит сообщение и освободит дескриптор, но в теле сообщения будут уже новые значения (не "123", а "567"). Задача отправитель же, дождавшись освобождения дескриптора, отсылает следующее сообщение; фактически - это тот же указатель на тот же участок памяти. И задача-адресат второй раз подряд получит "567".
Как этого избежать? Всего-навсего перед формированием нового сообщения в том же буфере нужно проверять, было ли предыдущее сообщение доставлено.
// Формируем первое сообщение
buf[0] = '1';
buf[1] = '2';
buf[2] = '3';
OS_Msg_Send(msg_cb, buf);
OS_Cond_Wait(!OS_Msg_Check(msg_cb)); // Ставим задачу в режим ожидания до тех пор,
// пока сообщение не будет принято
// Формируем второе сообщение
buf[0] = '5';
buf[1] = '6';
buf[2] = '7';
OS_Msg_Send(msg_cb, buf);
OS_Yield();
([[osa:articles:rtos_usage#FAQ по ОСРВ OSA|Вернуться к списку FAQ]])
----
==== Использование таймеров вне задач ====
Как статические, так и динамические таймеры могут использоваться в любом месте программы (за исключением сервисов Wait и Delay). Это позволяет:
**1. В любом месте программы ожидать какого-то условия с выходом по таймауту.**
char MyFunc1 (void)
{
/*...*/
OS_Stimer_Run(0, 10); // Запускаем статический таймер 0
// на отсчет 10 системных тиков
while (!RB0 && !OS_Stimer_Check(0)) // Ожидаем установки RB0
continue;
/*...*/
}
**2. Формировать задержку внутри фоновых функций (не задач).**
void MyFunc2 (void)
{
/*...*/
OS_Stimer_Run(0, 10); // Запускаем статический таймер 0
// на отсчет 10 системных тиков
while (!OS_Stimer_Check(0)) continue; // Ожидаем конца счета
/*...*/
}
**3. Выделять квант времени для работы какой-либо функции.**
char buffer[10];
void Task (void)
{
static OST_DTIMER dt;
OS_Dtimer_Create(dt); // Инициализируем таймер
for (;;)
{
OS_Dtimer_Run(dt, 50); // Выделяем для функции Receive квант
// времени в 50 тиков
if (Receive(&dt) == 1)
{
/*...*/
}
/*...*/
}
}
char Receive (OST_DTIMER *dt)
{
static bit b;
do
{
b = GetBit(); // Принимаем бит данных
ShiftBit(b); // Вдвигаем его в буфер
if (CheckSumOK()) return 1; // Проверяем контрольную сумму
} while (!OS_Dtimer_Check(*dt)); // Висим в цикле, пока таймер не досчитает
// Попали сюда, значит ничего не приняли за отведенное время
return 0;
}
([[osa:articles:rtos_usage#FAQ по ОСРВ OSA|Вернуться к списку FAQ]])
~~UP~~
----
==== Почему виснет OS_Delay? ====
"Почему после вызова OS_Delay() задача подвисает, значение таймеров не изменяется, хотя в osacfg.h определена константа OS_ENABLE_TTIMERS?". Как ни странно, вопрос довольно частный.
**Ответ: счет таймеров выполняет сервис OS_Timer, который должен периодически вызываться.**
~~UP~~