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