===== 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~~