Планировщик (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() в другом контексте (с более низким приоритетом выполнения). Вуаля! Вы перетащили выполняемые действия из высокоприоритетного контекста в низкоприоритетный.
|