nRF5x SDK: что такое планировщик (sheduler) Печать
Добавил(а) microsin   

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

Если вы хотите отложить некоторую не очень срочную обработку прерывания, у которого более высокий приоритет, "на потом", т. е. на программное прерывание или прерывание с более низким приоритетом (или даже на основной поток вычислений вне контекста прерываний), то просто поместите запрос в очередь, вызвав функцию:

uint32_t app_sched_event_put(void *                    p_event_data,
                             uint16_t                  event_size,
                             app_sched_event_handler_t handler);

Этот вызов принимает 3 аргумента - указатель на данные события (p_event_data), их размер (event_size) и дескриптор обработчика события (handler). Этот обработчик события выполнится в контексте низкого приоритета выполнения, или в контексте main, если будет обнаружено, что нечто было поставлено в очередь - путем запуска app_sched_execute из бесконечного цикла main. Когда эта функция обнаружит что-нибудь в очереди, она вызовет handler, который был предоставлен в 3 аргументе. Этот handler распакует первые 2 аргумента и выполнит то, что необходимо (выполнится код пользователя). Таким образом, в данных p_event_data может быть все что угодно - событие, другой обработчик, указатель на данные, указатель на функцию - все что может потребовать выполнения дальнейших действий. Таким способом мы передали выполнение алгоритма из контекста выполнения с высоким приоритетом в контекст выполнения с низким приоритетом, т. е. в любой другой контекст, где в бесконечном цикле крутятся вызовы app_sched_execute.

Модули APP_TIMERS и SOFTDEVICE также используют эту концепцию для передачи своих выполняемых действий в контекст main. Это делается с помощью создания третьего аргумента, простой распаковки данных и вызова обработчиков с этими распакованными данными.

Последнее, но не менее важное - если вы хотите получить оба события BLE-стека и SYS в планировщике, то просто передайте softdevice_evt_schedule в второй аргумент в softdevice_handler_init, и это будет сделано - оба события будут поставлены в очередь для выполнения в основном контексте.

Вот алгоритм действий, как можно сделать свои функции put и get (передачу данных и действий из одного контекста выполнения в другой):

1. Инициализируйте планировщик (scheduler) с буфером достаточно объема для хранения в нем необходимого количества событий, которые будут проталкиваться туда из разных потоков (или контекстов обработчиков прерываний ISR).

2. Создайте структуру, которая может содержать информацию, необходимую для передачи в пост-обработку. Например:

typedef struct
{
   type1 data1;
   type2 data2;
   type3 * pointer1;
   type4 * callback_function (type1 data1, type2 data2, type3 * pointer1);
} some_struct_t;

3. Создайте функцию, которая будет создавать данные и проталкивать их в очередь планировщика (scheduler queue).

static inline uint32_t app_module_evt_schedule(type4 *callback_handler,
                                               type1 d1,
                                               type2 d2,
                                               type3 *p1)
{
   some_struct_t push_event;
 
   push_event.callback_function = callback_handler;
   push_event.data1 = d1;
   push_event.data2 = d2;
   push_event.pointer1 = p1;
 
   return app_sched_event_put((void *)&push_event,
                              sizeof(push_event),
                              pull_event_function);
}

Сейчас Вы сделали функцию, которая сворачивает Ваши данные и их обработчик в одну структуру, запрашивает планировщик для проталкивания её в очередь. Также Вы дали ему достаточно продвинутую функцию, чтобы она смогла обработать эту структуру и вызвать callback.

4. Создайте pull_event_function. Она будет брать данные из очереди и запускать их обработчик (callback-функцию).

static inline void pull_event_function(void * p_event_data, uint16_t event_size)
{
   some_struct_t *p_some_struct = (some_struct_t *)p_event_data;
 
   ASSERT(event_size == sizeof(some_struct_t));
   p_some_struct->callback_function(p_some_struct->data1,
                                    p_some_struct->data2,
                                    p_some_struct->pointer1);
}

Теперь все готово для проталкивания Вашего события в очередь планировщика, и чтобы выбрать и обработать это событие и его данные. Осталось только поместить вызов app_module_evt_schedule() в одном контексте (с высоким приоритетом выполнения), и вызов app_sched_execute() в другом контексте (с более низким приоритетом выполнения). Вуаля! Вы перетащили выполняемые действия из высокоприоритетного контекста в низкоприоритетный.