分类: 嵌入式
2018-05-25 17:56:25
Nordic将其协议栈命名为softDevice,协议栈运行启动后肯定需要访问MCU上的一些外设资源,比如Radio,Timer0等。协议栈启动后,softDevice对这些资源的访问是独占的,即softDevice启动后,Radio,Timer0等外设资源是不允许应用再直接去访问的,不然会破坏协议栈的运行状态。
但是从BLE协议本身来说,BLE的工作是基于时间间隔的,即周期性的工作一会,睡眠一会,这也是低功耗的本质原因。所以BLE在睡眠的时候,协议栈虽然还是”占据”了 MCU的Radio,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_BLOCKED和NRF_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_TIMER0:Timer0中断,当请求一个timeSlot的时候设置了这个timeSlot的长度slot_length,所以当收到NRF_RADIO_CALLBACK_SIGNAL_TYPE_START信号后,并经过slot_length时间则这个timeSlot就结束了,如果应用程序在timeSlot时间段内使用了一些特殊的资源比如,则应该在timeSlot结束前释放这些资源,并且不应该再访问,除非重新请求了一个timeSlot,总之不能在请求timeSlot之外访问这些资源。所以在timeSlot启动后应用程序应该启动一个定时器保证在slot_length时间结束之前可以释放资源。这里就是使用Timer0,在timeSlot被安排运行前,协议栈会主动重置Timer0以1MHZ运行,并从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相关的操作。
点击(此处)折叠或打开
之后在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闪烁也会继续正常工作。