Chinaunix首页 | 论坛 | 博客
  • 博客访问: 8348
  • 博文数量: 5
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 10
  • 用 户 组: 普通用户
  • 注册时间: 2020-03-08 16:22
文章分类
文章存档

2020年(5)

我的朋友
最近访客

分类: 嵌入式

2020-03-08 23:55:14

原文地址:BLE-nordic协议栈timeslot机制 作者:ifndef

Nordic将其协议栈命名为softDevice,协议栈运行启动后肯定需要访问MCU上的一些外设资源,比如Radio,Timer0等。协议栈启动后,softDevice对这些资源的访问是独占的,即softDevice启动后,Radio,Timer0等外设资源是不允许应用再直接去访问的,不然会破坏协议栈的运行状态。

 

但是从BLE协议本身来说,BLE的工作是基于时间间隔的,即周期性的工作一会,睡眠一会,这也是低功耗的本质原因。所以BLE在睡眠的时候,协议栈虽然还是占据 MCURadio,Timer0等资源,但是其实softDevice并没有使用它们,但是即使没有使用应用程序还是不可以直接访问这些外设资源的。

 

不过有时候需求需要跑BLE的同时还要可以做一些2.4G的通信,那么正常情况下是不能实现的。不过softDevice针对这些需要访问和softDevice冲突的资源应用还是提供了一种解决办法---TimeSlot,即时间间隙。其根本就是在softDevice运行过程中没有使用那么独占资源的时候,提供一种访问接口,让应用程序可以在这段间隙临时使用这些资源。并在协议栈需要使用前及时释放这些资源避免影响协议栈的运行状态。

 

综上,timeSlot提供了一种访问接口,允许协议栈和应用可以同时访问一些协议栈会独占的关键资源。当然这里的同时指的是并发,而不是并行。即宏观上应用和协议栈在同时访问 Radio,Timer0这些本该协议栈独占的资源。而微观上实际还是串行访问的。

 

 

TimeSlot的使用首先需要请求请求一段会话,再这段会话期间可以请求多次 timeslot

即通过sd_radio_session_open()函数开始启动一段会话,在会话启动以后可以通过sd_radio_request()函数来获取时间间隙并在这段间隙中来操作Radio,Timer0那些本来应用程序不能直接访问的资源。在这段会话中,可以多次请求间隙。当不在需要请求间隙来操作这些特殊的资源后,就可以通过sd_radio_session_close()来结束整个会话。

 

会话的概念很简单,通俗的说就是在会话打开的这段时间内可以多次执行请求间隙的操作。会话的打开和关系没有什么需要特别注意的。仅仅就是打开和关闭操作而已。

需要注意的主要是timeSlot,即请求间隙的一些特性。

 

timeSlot请求类型

         earliest possible:第一次请求timeSlot的时候应该使用这种类型。因为请求的timeSlot可能和其他的协议栈访问冲突,所以并不是一请求就能满足,所以earliest possible,协议栈底层的调度模块会根据整体情况尽快安排请求的间隙。

         Normal:之后的请求可以使用这个类型,以该类型请求timeSlot,请求的timeSlot开始的时间是距离上一个timeSlot开始时间的时间。

 

如下图所示


timeSlot
请求优先级

         可以以高或者正常优先级来请求timeSlot,高优先级更能获取到timeSlot,以正常优先级获取更容易因为请求的timeslot和协议栈的其他高优先活动冲突导致不能请求到。

 

timeSlot请求的长度

         请求timeSlot,本质就是请求一段时间间隙,所以这个时间间隙有一个长度,一次timeSlot的长度限制为100us-100ms之间。不过在一次timeSlot结束的时候可以通过执行延长操作来延长这个timeSlot

 

timeSlot运行中高频时钟的设置

         应用程序可以设置 请求一个timeSlot时配置在这段 timeSlot时间间隙内的高频时钟源。比如 如果你需要在 timeSlot间隙中使用Radio,因为Radio需要使用外部高频晶振,那么在请求这个timeSlot的时候就可以设置 其高频时钟源为 外部晶振。这样协议栈就会自动在 请求的这段timeSlot开始前自动 将高频时钟源设置成外部晶振,并在 timeSlot结束时关闭。

当然也可以不设置这个请求参数,依然后timeSlot中打开外部晶振时钟源,那么应用程序也要自己保证在timeSlot结束时 主动关闭外部时钟源。



timeSlot请求发起后就会接收到系统上抛上来的一些相关事件。

如下图

NRF_EVT_RADIO_SESSION_IDLE表示会话处于空闲,即当前没有请求的的timeSlot需要处理。

 

NRF_EVT_RADIO_SESSION_CLOSED:调用sd_radio_session_close()关闭会话时会收到这个事件。

 

NRF_EVT_RADIO_BLOCKEDNRF_EVT_RADIO_CANCELED:如果请求的timeSlot和协议栈的运行产生冲突,则本次请求会被阻塞或者取消,收到这两个事件时可以重新执行请求操作。

NRF_EVT_RADIO_SIGNAL_CALLBACK_INVALID_RETURN:信号处理函数返回的值无效。

 

NRF_EVT_RADIO_SIGNAL_CALLBACK_INVALID_RETURN这个事件涉及到的信号处理函数是调用sd_radio_session_open()打开会话时注册的,信号处理函数处理timeSlot相关的一些信号,该处理函数主要处理关于timeSlot相关的重要信号,并且在被调用时是处于系统最高优先级下。

主要有如下信号:



NRF_RADIO_CALLBACK_SIGNAL_TYPE_START表示请求的TimeSlot开始了。

 

NRF_RADIO_CALLBACK_SIGNAL_TYPE_RADIO:如果再请求的TimeSlot的时间内有操作Radio产生了一些中断,则会产生这个信号。

 

NRF_RADIO_CALLBACK_SIGNAL_TYPE_TIMER0Timer0中断,当请求一个timeSlot的时候设置了这个timeSlot的长度slot_length,所以当收到NRF_RADIO_CALLBACK_SIGNAL_TYPE_START信号后,并经过slot_length时间则这个timeSlot就结束了,如果应用程序在timeSlot时间段内使用了一些特殊的资源比如,则应该在timeSlot结束前释放这些资源,并且不应该再访问,除非重新请求了一个timeSlot,总之不能在请求timeSlot之外访问这些资源。所以在timeSlot启动后应用程序应该启动一个定时器保证在slot_length时间结束之前可以释放资源。这里就是使用Timer0,在timeSlot被安排运行前,协议栈会主动重置Timer01MHZ运行,并从0开始重新启动,所以在之后收到timeSlot启动信号NRF_RADIO_CALLBACK_SIGNAL_TYPE_START时,可以设置Timer0的超时时间比slot_length短一点,这样在Timer0定时到期后会就会收到这个NRF_RADIO_CALLBACK_SIGNAL_TYPE_TIMER0信号,然后最一些收尾工作结束这次timeSlot。当然也可以在结束这个timeSlot的时候同时请求下一个timeSlot.

 

NRF_RADIO_CALLBACK_SIGNAL_TYPE_EXTEND_SUCCEEDED:表示上次执行的timeSlot延长请求成功。每个timeSlot的长度限制在100us--100ms之间,不过在timeSlot要结束的时候可以通过执行延长请求来延长这个timeslot.

NRF_RADIO_CALLBACK_SIGNAL_TYPE_EXTEND_FAILED上次之执行的timeSlot延长请求失败,一般是和协议栈运行冲突导致。

 

调用sd_radio_session_open(radio_signal_callback) 注册的信号处理函数 需要处理上面介绍的这些信号需要注意的是 当该处理函数 radio_signal_callback被底层协议栈调用的时候是运行在最高优先级上的,所以不能在该 信号处理函数中调用 SVC机制实现的 sd_开头的协议栈api函数,因为SVC 中断需要立刻响应,不能被阻塞

 该信号处理函数除了处理上面的信号外还需要返回一个适当的返回值给协议栈。可以返回的值如下。



NRF_RADIO_SIGNAL_CALLBACK_ACTION_NONE
:返回该值表示不需要做什么动作。

 

NRF_RADIO_SIGNAL_CALLBACK_ACTION_END:该返回值表示本次timeSlot工作结束了,之后协议栈就可以安排别的工作了。

 

NRF_RADIO_SIGNAL_CALLBACK_ACTION_REQUEST_AND_END:该返回值表示本次timeSlot工作结束了,并且请求了下一个timeSlot.

 

NRF_RADIO_SIGNAL_CALLBACK_ACTION_EXTEND:返回该返回值表示请求不结束本次timeSlot而是延长本次的timeSLot

 

综上,timeSlot的使用主要步骤如下:

 

使用 sd_radio_session_open(radio_signal_callback) 打开一个会话,并且注册一个信号处理函数 radio_signal_callback 

在会话没有关闭之前,可以通过 调用sd_radio_request() 函数请求一个timeSlot timeSlot开始后会产生各种 事件和信号,事件和信号需要单独处理,事件的处理需要自己在 sdk中的 系统事件处理函数中处理, 而信号则由 sd_radio_request()  这个函数处理。

 

当不需要再使用timeSlot后,通过调用sd_radio_session_close() 来结束打开的会话


下面是一个
参考 Nordic官网论坛上的测试用例,主要实现 通过申请timeSlot来实现 led灯的翻转。

开发环境:

         SDK版本:SDK13

         硬件:    nrf52840官方开发板PCA10056

         实验例子:在ble_peripheral下的ble_app_uart的例子基础上来实现。

 

首先创建一个timeSlot.c文件实现 timeSlot相关的操作。


点击(此处)折叠或打开

  1. //需要使用的头文件
  2. #include <stdint.h>
  3. #include <stdbool.h>
  4. #include "nrf.h"
  5. #include "app_error.h"
  6. #include "nrf_gpio.h"
  7. #include "softdevice_handler.h"
  8. #include "boards.h"
  9. #include "nrf_nvic.h"

  10. // 定义一个timeSlot的变量,该变量用来设置 请求的timeSlot的特性。
  11. static nrf_radio_request_t m_timeslot_request;
  12. // 请求的timeSlot的时间间隙长度
  13. static uint32_t m_slot_length;
  14. // 信号处理函数的返回值。
  15. static nrf_radio_signal_callback_return_param_t signal_callback_return_param;


  16. //请求一个earliest possible 类型的timeSlot,第一次请求timeSLot的时候总是以该类型发起
  17. // timeSlot时间长度为 15000 us
  18. // timeSlot的请求优先级为正常优先级
  19. // 这里设置了在timeSlot运行过程中会打开外部高频晶振时钟源,不过其实不是必须的。
  20. // timeout_us表示请求timeSlot发出后,系统接收这个请求的最大延迟。
  21. // timeSLot的时间长度为15000us
  22. uint32_t request_next_event_earliest(void)
  23. {
  24.     m_slot_length = 15000;
  25.     m_timeslot_request.request_type = NRF_RADIO_REQ_TYPE_EARLIEST;
  26.     m_timeslot_request.params.earliest.hfclk = NRF_RADIO_HFCLK_CFG_XTAL_GUARANTEED;
  27.     m_timeslot_request.params.earliest.priority = NRF_RADIO_PRIORITY_NORMAL;
  28.     m_timeslot_request.params.earliest.length_us = m_slot_length;
  29.     m_timeslot_request.params.earliest.timeout_us = 1000000;
  30.     return sd_radio_request(&m_timeslot_request);
  31. }

  32. // 配置 earliest possible的类型的 timeSlot
  33. void configure_next_event_earliest(void)
  34. {
  35.     m_slot_length = 15000;
  36.     m_timeslot_request.request_type = NRF_RADIO_REQ_TYPE_EARLIEST;
  37.     m_timeslot_request.params.earliest.hfclk = NRF_RADIO_HFCLK_CFG_XTAL_GUARANTEED;
  38.     m_timeslot_request.params.earliest.priority = NRF_RADIO_PRIORITY_NORMAL;
  39.     m_timeslot_request.params.earliest.length_us = m_slot_length;
  40.     m_timeslot_request.params.earliest.timeout_us = 1000000;
  41. }

  42. // 配置normal类型的timeSlot
  43. void configure_next_event_normal(void)
  44. {
  45.     m_slot_length = 15000;
  46.     m_timeslot_request.request_type = NRF_RADIO_REQ_TYPE_NORMAL;
  47.     m_timeslot_request.params.normal.hfclk = NRF_RADIO_HFCLK_CFG_XTAL_GUARANTEED;
  48. m_timeslot_request.params.normal.priority = NRF_RADIO_PRIORITY_HIGH;
  49. // norma类型的timeSlot 的开始时间为距离上一个 timeSlot的开始时间 distance_us后
  50.     m_timeslot_request.params.normal.distance_us = 100000;
  51.     m_timeslot_request.params.normal.length_us = m_slot_length;
  52. }


  53. // timeSlot 会话相关的 一些事件的处理
  54. // 这里的主要处理是,在收到 IDLE事件,即申请的这段会话中没有 timeSlot需要处理后会// 产生这个事件,那么就可以 通过sd_radio_session_close 函数关闭这个 会话了
  55. // 另外请求的timeSlot可能因为和协议栈运行产生冲突,那么就可以被阻塞或去掉,所以
  56. // NRF_EVT_RADIO_BLOCKED 和 NRF_EVT_RADIO_CANCELED 事件的处理都是重新发起请求
  57. void nrf_evt_signal_handler(uint32_t evt_id)
  58. {
  59.     uint32_t err_code;
  60.     switch (evt_id)
  61.     {
  62.         case NRF_EVT_RADIO_SIGNAL_CALLBACK_INVALID_RETURN:
  63.             printf("NRF_EVT_RADIO_SIGNAL_CALLBACK_INVALID_RETURN\r\n");
  64.             break;
  65.         case NRF_EVT_RADIO_SESSION_IDLE:
  66.             printf("NRF_EVT_RADIO_SESSION_IDLE\r\n");
  67.             sd_radio_session_close();
  68.             break;
  69.         case NRF_EVT_RADIO_SESSION_CLOSED:
  70.             printf("NRF_EVT_RADIO_SESSION_CLOSED\r\n");
  71.             break;
  72.         case NRF_EVT_RADIO_BLOCKED:
  73.             printf("NRF_EVT_RADIO_BLOCKED\r\n");
  74.             //注意这里没有break,所以 这两个事件的处理都是重新发起请求
  75.         case NRF_EVT_RADIO_CANCELED:
  76.             printf("NRF_EVT_RADIO_CANCELED\r\n");
  77.             err_code = request_next_event_earliest();
  78.             APP_ERROR_CHECK(err_code);
  79.             break;
  80.         default:
  81.             break;
  82.     }
  83. }


  84. // timeSlot相关的信号处理函数。
  85. // 当请求的timeSlot 被安排开始后,就会收到 START信号,在这个信号处理里面实现
  86. // 实现翻转 LED灯同时设置 Timer0的超时时间,time0在timeSLot开始后会被自动重置为// 1MH运行从0 计数,所以这里设置time0的超时时间比 timeSlot的时间长度短1000us,//这样在timeSLot
  87. // 结束之前就可以收到 NRF_RADIO_CALLBACK_SIGNAL_TYPE_TIMER0信号,然后在这信号处
  88. // 理里做一些收尾工作,在这里我们实现的是请求下一个timeSlot

  89. // NRF_RADIO_CALLBACK_SIGNAL_TYPE_RADIO信号不需要处理,因为我们没有在timeSLot
  90. // 中使用Radio,所以不会有这个信号。

  91. nrf_radio_signal_callback_return_param_t * radio_callback(uint8_t signal_type)
  92. {
  93.     static uint8_t start_count = 0;
  94.     static uint8_t timer0_count = 0;

  95.     switch(signal_type)
  96.     {
  97.         case NRF_RADIO_CALLBACK_SIGNAL_TYPE_START:
  98.             signal_callback_return_param.params.request.p_next = NULL;
  99. signal_callback_return_param.callback_action    = NRF_RADIO_SIGNAL_CALLBACK_ACTION_NONE;

  100.             NRF_TIMER0->INTENSET = TIMER_INTENSET_COMPARE0_Msk;
  101.             NRF_TIMER0->CC[0] = m_slot_length - 1000;
  102.             NVIC_EnableIRQ(TIMER0_IRQn);

  103.             nrf_gpio_pin_toggle(16);
  104.             //避免打印太快
  105.             if(start_count++ >10 ){            
  106.                 printf("NRF_RADIO_CALLBACK_SIGNAL_TYPE_START\r\n");
  107.                 start_count = 0;
  108.             }
  109.             break;

  110.         case NRF_RADIO_CALLBACK_SIGNAL_TYPE_RADIO:
  111.             signal_callback_return_param.params.request.p_next = NULL;
  112.             signal_callback_return_param.callback_action = NRF_RADIO_SIGNAL_CALLBACK_ACTION_NONE;
  113.             break;

  114.         case NRF_RADIO_CALLBACK_SIGNAL_TYPE_TIMER0:
  115.             configure_next_event_normal();
  116.             signal_callback_return_param.params.request.p_next = &m_timeslot_request;
  117.             signal_callback_return_param.callback_action    = NRF_RADIO_SIGNAL_CALLBACK_ACTION_REQUEST_AND_END;
  118.             if(timer0_count++ >10 ){            
  119.                 printf("NRF_RADIO_CALLBACK_SIGNAL_TYPE_TIMER0\r\n");
  120.                 timer0_count = 0;
  121.             }
  122.             break;
  123.         case NRF_RADIO_CALLBACK_SIGNAL_TYPE_EXTEND_SUCCEEDED:
  124.             printf("NRF_RADIO_CALLBACK_SIGNAL_TYPE_EXTEND_SUCCEEDED\r\n");
  125.             break;
  126.         case NRF_RADIO_CALLBACK_SIGNAL_TYPE_EXTEND_FAILED:
  127.             printf("NRF_RADIO_CALLBACK_SIGNAL_TYPE_EXTEND_FAILED\r\n");
  128.             configure_next_event_earliest();
  129.             signal_callback_return_param.params.request.p_next = &m_timeslot_request;
  130.     signal_callback_return_param.callback_action    = NRF_RADIO_SIGNAL_CALLBACK_ACTION_REQUEST_AND_END;
  131.             break;
  132.         default:
  133.             //No implementation needed
  134.             break;
  135.     }
  136.     return (&signal_callback_return_param);
  137. }

  138. // timeSlot初始化函数。
  139. uint32_t timeslot_sd_init(void)
  140. {
  141.     uint32_t err_code;

  142.     err_code = sd_radio_session_open(radio_callback);
  143.     if (err_code != NRF_SUCCESS)
  144.     {
  145.         return err_code;
  146.     }

  147.     err_code = request_next_event_earliest();
  148.     if (err_code != NRF_SUCCESS)
  149.     {
  150.         (void)sd_radio_session_close();
  151.         return err_code;
  152.     }
  153.     nrf_gpio_cfg_output(16);
  154.     return NRF_SUCCESS;
  155. }


之后在main 函数中添加 timeSlot的相关操作。

首先如果使用的开发板上没有uart的硬件流控,那么关闭代码中的uart流控




然后在main函数中添加timeSlot的初始化函数



timeSlot
相关的信号处理函数radio_callback 在调用 会话打开函数sd_radio_session_open(radio_callback);时注册的,那么当发生这些信号时协议栈会自动调用 注册的处理函数radio_callback

 

不过事件处理函数nrf_evt_signal_handler却并不是注册的。而是需要主动添加到系统事件处理函数中,不过 ble_app_uart的例子中并没有系统事件处理函数,所以需要自己实现。

首先在main.c文件中 static void ble_stack_init(void) 函数中添加将系统处理函数sys_evt_dispatch注册到系统的代码。

static void ble_stack_init(void)

{

         ………………

         ………………

    err_code = softdevice_ble_evt_handler_set(ble_evt_dispatch);

    APP_ERROR_CHECK(err_code);

        

         err_code = softdevice_sys_evt_handler_set(sys_evt_dispatch);

    APP_ERROR_CHECK(err_code);

}

Sys_evt_dispatch的实现很简单,就是调用 timeSlot的事件处理函数nrf_evt_signal_handler

main.c文件中添加。

static void sys_evt_dispatch(uint32_t sys_evt)

{

         nrf_evt_signal_handler(sys_evt);

}

 

编译烧写代码到开发板上后,就可以看到设置的LED等在闪烁了,即使ble连接后 led闪烁也会继续正常工作。




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