Command disabled: backlink
 
Available Languages?:

FAQ по ОСРВ OSA

Можно ли вызывать сервисы ожидания из функций, вызываемых задачами?

Ответ: нет.

Этот вопрос, пожалуй, задают чаще остальных. Такой подход, действительно, выглядит очень заманчивым, когда, например, в задачах часто вызывается сервис 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.

Примечание. В принципе, такой подход допускается, если у программиста есть уверенность в том, что в один момент времени только одна задача производит вызов такой функции. Однако, здесь надо быть крайне осторожным и хорошо понимать, что он делает. Поэтому, если Вы не уверены, что уследите за вызовами при дальнейшем росте программы, - не применяйте такой прием!

(Вернуться к списку 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));

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

(Вернуться к списку 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_CreateP:

    LCD_Out(UserMenu[i].strMenu);        // Выводим на экран название функции
    OS_Task_CreateP(0, UserMenu[i].Func); // Создаем задачу с высшим приоритетом

(Вернуться к списку 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().

(Вернуться к списку 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

Такое, хоть и редко, но бывает нужно.

У короткого сообщения есть две особенности, которые нужно учитывать при проектировании программы:

  1. это сообщение может иметь только перечислимый тип (нужно учитывать при замене типа); т.е. этот тип нельзя переопределить на структуру, поскольку запись значения в короткое сообщение и чтение из него выполняется оператором присваивания ( = );
  2. через него нельзя передавать нулевое значение. Этим приходится платить за использование такого эффективного инструмента. Нулевое значение рассматривается системой как отсутствие сообщения.

(Вернуться к списку 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. Поэтому всегда будет происходить одна и та же последовательность:

  1. Task1 устанавливает семафор bs;
  2. Task2 и Task3 становятся готовыми к выполнению;
  3. после работы Task1 планировщик начинает поиск задачи, начиная со следующей после последней выполненной, т.е. начиная с Task2;
  4. планировщик рассматривает Task2, видит, что она готова; лучший приоритет пока не выбран, поэтому лучшим на данный момент планировщик будет считать 1 (приоритет задачи Task2). Планировщик ее запоминает как кандидата на запуск;
  5. планировщик рассматривает Task3, видит, что она готова, но ее приоритет не лучше, чем текущий лучший, поэтому он эту задачу пропускает;
  6. планировщик рассматривает Task1, видит, что она не готова к выполнению (она в задержке);
  7. управление передается задаче Task2;
  8. Task2 сбрасывает семафор (сервис OS_Bsem_Wait делает это автоматически), отрабатывает какие-то свои действия и возвращается в планировщик;
  9. планировщик начинает просмотр списка задач, начиная с Task3, но уже видит, что семафор сброшен, поэтому Task3 опять ставится в ожидание;
  10. следующей управление получит 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);

В общем, способы решения есть, а какой применять, - на усмотрение программиста. Главное - надо помнить об этой особенности планировщика.

(Вернуться к списку 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();

(Вернуться к списку 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;
}

(Вернуться к списку FAQ)


Почему виснет OS_Delay?

"Почему после вызова OS_Delay() задача подвисает, значение таймеров не изменяется, хотя в osacfg.h определена константа OS_ENABLE_TTIMERS?". Как ни странно, вопрос довольно частный.

Ответ: счет таймеров выполняет сервис OS_Timer, который должен периодически вызываться.

 
osa/articles/rtos_usage_3.txt · Последние изменения: 23.02.2010 23:09 От osa_chief
 
Creative Commons License Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki