分类: 嵌入式
2016-03-21 08:53:23
该讲介绍sdk中 app_scheduler的使用。
看名字容易理解成是一个调度模块。不过这个模块的作用并不是用来调度程序的。其内部实现就是一个简单的队列,你将 一些数据和其对应的处理函数 放入队列。
然后在另一个地方会 取出这些数据并调用对应的处理函数 。
这个模块的核心作用就是用来将 中断上下文中的 处理过程转移的main上下文中。 以避免中断处理过程太长导致可能丢失随后的中断信号。
比如一个 定时亮灯的程序。 再定时到期后 先亮第一个灯,同时关闭第二个灯。等到定时器下一次到期时 再亮第二个灯同时关闭第一个灯。 这样一直循环
通常的做法就是 定义一个flag 变量, 然后在中断处理程序中 根据flag来判断 亮那个灯和灭哪个灯。
但是如果 这里不是亮灯的处理。而是 更耗时的处理。那么就需要将 这个“亮灯灭灯”移到 main上下文中,然后同样根据flag来处理 亮那个灯。
如果现在不只一个中断事件,有很多个并且每个中断事件的处理函数都比较耗时。那么都需要移到main上下文中, 就会导致很多全局的flag。也不利于程序的维护。
Aapp_scheduler模块的就是为了解决这种问题。
1 首先通过app_sched_init 函数 初始化Aapp_scheduler模块,程序中通常使用宏
APP_SCHED_INIT(EVENT_SIZE, QUEUE_SIZE) 来初始化,该宏会自动创建队列需要的BUFF, 自己需要填的就是 event_size和queue_size。
2 在需要做一些耗时操作的中断处理函数中,通过 app_sched_event_put 函数 将 数据以及对应的处理函数 放入队列。
3 最后在main 中的for循环里执行app_sched_execute该函数会自动 提取 之前放入队列的 数据及其对应的处理函数 然后执行。
这里说一下APP_SCHED_INIT 的两个参数。
Event_size为可能放到队列中的 事件数据 的最大值。 比如现在有多个中断的事件处理函数中 都使用Aapp_scheduler这个, 上面说的定时点灯问题,放入队列中的事件数据 只要一个字节就可以了,通过该字节来判断是点哪个灯。 如过还存在一个 按键中断的处理函数也 会将 事件数据和其对应的处理程序 放入队列,而这个事件数据是 10字节的数组。
那么这个event_size 就填10 ,可以理解成 就是每次放入队列时可能会放入的数据最大值。
QUEUE_SIZE 就是队列的大小。这个根据自己程序中有多少地方可能会使用 app_sched_event_put
来 将数据和对应处理函数放入队列 而合理设置就可以了。
举个例子。加入程序中 只有两个中断处理的地方 会执行 这个put函数。并且中断的频率不高,那么就可以将 这个QUEUE_SIZE设置成2 。但是如果中断发生频率很高,就要根据具体情况设置大一点。 比如频率太高。而放入队列中的执行函数又比较耗时,可能你在回到main函数中去执行提取队列中要执行的事件的时候。中断又发生了 3次,那么QUEUE_SIZE 却只有2,显然不能保存所有 需要放到队列中的 要稍后处理的过程。 所以该值需要自己根据使用情况考虑。
下面在 官方 ble_uart的例子上 来实现 上面说的 定时周期性的轮流亮两个灯中的一个。
我们假设 这个 轮流亮灯就是个耗时操作,使用app_scheduler模块将其执行过程从中断处理 转移到main上下文中
使用的是keil5.14+sdk9.0例子在
XX:\Keil_v5\ARM\Pack\NordicSemiconductor\nRF_Examples\9.0.0\ble_peripheral\ble_app_uart 目录下。
该例子没有包含 app_scheduler模块的文件,所以需要手动加上
定时功能,我们使用 app_timer(详见其教程)
修改main.c 中app_timer可以创建的最大数。 加1
首先实现 轮流亮灯 以及 定时到期后的处理函数。定时处理函数中没有直接 执行 “耗时的轮流亮灯” 而是将需要亮的灯的编号(0或1) 以及亮灯的处理函数 都放入scheduler队列中。
struct led_id{
uint8_t id;
}led_event;
//定时处理函数知识 调用入队操作,将实际的处理函数放入队列以待其退出中断处理程序
//后再在main中执行
void led_timer_handler(void *p_contex){
led_event.id ^= 1; //轮流改变要亮的灯(0或1)
app_sched_event_put(&led_event, sizeof(led_event), open_led);//数据和处理函数放入scheduler队列
}
//实际的处理轮流亮灯的操作
void open_led(void *p_event_data, uint16_t event_size){
struct led_id *data = (struct led_id *)p_event_data;
if(data->id == 0){
nrf_gpio_pin_clear(LED0);
nrf_gpio_pin_set(LED1);
}else if(data->id == 1){
nrf_gpio_pin_clear(LED1);
nrf_gpio_pin_set(LED0);
}
}
然后实现以下 LED引脚的初始化,以及定时器的 初始化 和开启
#define LED0 23
#define LED1 24
app_timer_id_t led1_timer;
app_timer_id_t led2_timer;
void led_timer_init(void){
led_event.id = 0;
nrf_gpio_cfg_output(LED0);
nrf_gpio_cfg_output(LED1);
//创建定时器
app_timer_create(&led1_timer,APP_TIMER_MODE_REPEATED,led_timer_handler;
}
//启动定时器
void led_timer_start(void){
app_timer_start(led1_timer,
APP_TIMER_TICKS(1000, APP_TIMER_PRESCALER),
NULL);
}
最后在main.c中添加app_scheduler的初始化,led_timer的初始化和启动函数.以及在main函数的loop 的睡眠前放入 scheduler的 执行函数app_sched_execute();
int main(void)
{
uint32_t err_code;
bool erase_bonds;
uint8_t start_string[] = START_STRING;
// Initialize.
APP_TIMER_INIT(APP_TIMER_PRESCALER, APP_TIMER_MAX_TIMERS, APP_TIMER_OP_QUEUE_SIZE, false);
APP_SCHED_INIT(sizeof(led_event),1); //这里测试就一个定时器里用了app_scheduler模块,所以event_seize就填定时器处理函数中需要放入队列的 事件数据大小 就可以了, 队列大小也设置为1就可以了
led_timer_init();
uart_init();
buttons_leds_init(&erase_bonds);
ble_stack_init();
gap_params_init();
services_init();
advertising_init();
conn_params_init();
led_timer_start();
err_code = ble_advertising_start(BLE_ADV_MODE_FAST);
APP_ERROR_CHECK(err_code);
// Enter main loop.
for (;;)
{
app_sched_execute();
power_manage();
}
}
程序的执行流程如下:
初始化完成 进入 main 的for循环中 第一次执行app_sched_execute();
不过此时队列中并没有要处理的 执行事件。 所以直接power_manage()进入睡眠。 当定时器的定时到期后设备被唤醒 首先 执行 定时处理函数led_timer_handler。 然后函数退出中断处理函数,回到main中,于是又执行app_sched_execute();此时队列中有 定时处理函数中放入的 要执行的事件。于是提取执行open_led。实现轮流点灯。