Chinaunix首页 | 论坛 | 博客

fx

  • 博客访问: 1346908
  • 博文数量: 115
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 3964
  • 用 户 组: 普通用户
  • 注册时间: 2013-05-02 14:36
文章分类
文章存档

2022年(2)

2019年(2)

2018年(10)

2017年(1)

2016年(50)

2015年(12)

2014年(9)

2013年(29)

分类: 嵌入式

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。实现轮流点灯。



阅读(4969) | 评论(1) | 转发(1) |
给主人留下些什么吧!~~

yunjie1672017-10-16 16:21:05

这个类似linux里的中断顶半部和底半部处理机制一样,建议open_led写成open_led_cb会更易理解一些~