nordic timeslot实现radio和ble一起工作
ble和radio一起工作
常见的做法是关闭softdevice后,进行插播,但是通过定时器观察,重新初始化需要200多秒,增加了很大的功耗,不适合。
old = app_timer_cnt_get();
sd_softdevice_enable
new1 = app_timer_cnt_get();
diff = new1 - old; //8678*1000/32768=264ms
解决方法-timeslot机制
参考网址
https://devzone.nordicsemi.com/f/nordic-q-a/9957/timeslot-and-radio-receiver 实现radip
http://www.magic-chip.com/news/46-cn.html 中文版本,翻译的不错。
目前已在nrf52832、nrf51822、nrf51802上实现在不关闭协议栈的情况下一起工作,并且在hrs,template,beacon官方历程中实现,用的是SDK12.3,12.2。
timeslot有5个事件和n个信号,如下所示
事件调用:
static void sys_evt_dispatch(uint32_t sys_evt)
{
nrf_evt_signal_handler(sys_evt);
}
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_RADIO_CALLBACK_SIGNAL_TYPE_START //收到这个信号之后,你可以操作radio和timer等被softdevice占用的外设。
NRF_RADIO_CALLBACK_SIGNAL_TYPE_TIMER0 //定时器中断来临,会收到这个信号
NRF_RADIO_CALLBACK_SIGNAL_TYPE_RADIO //射频中断,会收到这个信号
NRF_RADIO_CALLBACK_SIGNAL_TYPE_EXTEND_SUCCEEDED //扩展时间申请成功,会收到这个信号
NRF_RADIO_CALLBACK_SIGNAL_TYPE_EXTEND_FAILED //扩展时间申请失败,会收到这个信号
1、图中第一次申请了10ms的可用时间,下次申请时间为100ms,申请了5ms的可用时间。
代码重要参数解析:
1、需要告诉softdevice申请多长时间,REQUST_TIME 5000 申请了5ms的可用时间。
2、申请周期,距离上一次申请的时间,REQUST_DISTANCE 2000000 申请周期2s。
3、第一次请求类型为earliest,后面的请求为normal。
4、使用了定时器,作用:在申请的5ms的可用时间到之前,提前释放资源。时间用完后,无需配置射频信息,softdevice自动恢复radio的蓝牙默认操作。
5、NRF_RADIO_HFCLK_CFG_XTAL_GUARANTEED,自动开启外部晶振,因为射频发送需要开启外部高频时钟。
相关代码
timeslot.c
#include <stdint.h>
#include <stdbool.h>
#include "nrf.h"
#include "app_error.h"
#include "nrf_gpio.h"
#include "softdevice_handler.h"
#include "boards.h"
#include "nrf_nvic.h"
#include "timeslot.h"
#include "nrf_log.h"
#include "nrf_log_ctrl.h"
#include "template.h"
#include "tedacp_init.h"
// 定义一个timeSlot的变量,该变量用来设置 请求的timeSlot的特性。
static nrf_radio_request_t m_timeslot_request;
// 请求的timeSlot的时间间隙长度
static uint32_t m_slot_length;
// 信号处理函数的返回值。
static nrf_radio_signal_callback_return_param_t signal_callback_return_param;
#define REQUST_TIME 5000
#define REQUST_DISTANCE 2000000
#define TS_LEN_US (5000UL) /**< Length of timeslot to be requested. */
#define TX_LEN_EXTENSION_US (5000UL)
#define TS_SAFETY_MARGIN_US (700UL)
#define TS_EXTEND_MARGIN_US (2000UL)
#define NRF_RADIO_REQUST_PRITITY NRF_RADIO_PRIORITY_NORMAL
void RADIO_IRQHandler(void);
//请求一个earliest possible 类型的timeSlot,第一次请求timeSLot的时候总是以该类型发起
// timeSlot时间长度为 15000 us
// timeSlot的请求优先级为正常优先级
// 这里设置了在timeSlot运行过程中会打开外部高频晶振时钟源,不过其实不是必须的。
// timeout_us表示请求timeSlot发出后,系统接收这个请求的最大延迟。
// timeSLot的时间长度为15000us
uint32_t request_next_event_earliest(void)
{
m_slot_length = REQUST_TIME;
m_timeslot_request.request_type = NRF_RADIO_REQ_TYPE_EARLIEST;
m_timeslot_request.params.earliest.hfclk = NRF_RADIO_HFCLK_CFG_XTAL_GUARANTEED;
m_timeslot_request.params.earliest.priority = NRF_RADIO_REQUST_PRITITY;
m_timeslot_request.params.earliest.length_us = m_slot_length;
m_timeslot_request.params.earliest.timeout_us = 1000000;
return sd_radio_request(&m_timeslot_request);
}
// 配置 earliest possible的类型的 timeSlot
void configure_next_event_earliest(void)
{
m_slot_length = REQUST_TIME;
m_timeslot_request.request_type = NRF_RADIO_REQ_TYPE_EARLIEST;
m_timeslot_request.params.earliest.hfclk = NRF_RADIO_HFCLK_CFG_XTAL_GUARANTEED;
m_timeslot_request.params.earliest.priority = NRF_RADIO_REQUST_PRITITY;
m_timeslot_request.params.earliest.length_us = m_slot_length;
m_timeslot_request.params.earliest.timeout_us = 1000000;
}
// 配置normal类型的timeSlot
void configure_next_event_normal(void)
{
m_slot_length = REQUST_TIME;
m_timeslot_request.request_type = NRF_RADIO_REQ_TYPE_NORMAL;
m_timeslot_request.params.normal.hfclk = NRF_RADIO_HFCLK_CFG_XTAL_GUARANTEED;
m_timeslot_request.params.normal.priority = NRF_RADIO_REQUST_PRITITY;
// norma类型的timeSlot 的开始时间为距离上一个 timeSlot的开始时间 distance_us后
m_timeslot_request.params.normal.distance_us = REQUST_DISTANCE;
m_timeslot_request.params.normal.length_us = m_slot_length;
}
// timeSlot 会话相关的 一些事件的处理
// 这里的主要处理是,在收到 IDLE事件,即申请的这段会话中没有 timeSlot需要处理后会// 产生这个事件,那么就可以 通过sd_radio_session_close 函数关闭这个 会话了
// 另外请求的timeSlot可能因为和协议栈运行产生冲突,那么就可以被阻塞或去掉,所以
// NRF_EVT_RADIO_BLOCKED 和 NRF_EVT_RADIO_CANCELED 事件的处理都是重新发起请求
void nrf_evt_signal_handler(uint32_t evt_id)
{
uint32_t err_code;
switch (evt_id)
{
case NRF_EVT_RADIO_SIGNAL_CALLBACK_INVALID_RETURN:
NRF_LOG_INFO("NRF_EVT_RADIO_SIGNAL_CALLBACK_INVALID_RETURN\r\n");
break;
case NRF_EVT_RADIO_SESSION_IDLE:
NRF_LOG_INFO("NRF_EVT_RADIO_SESSION_IDLE\r\n");
sd_radio_session_close();
break;
case NRF_EVT_RADIO_SESSION_CLOSED:
NRF_LOG_INFO("NRF_EVT_RADIO_SESSION_CLOSED\r\n");
break;
case NRF_EVT_RADIO_BLOCKED:
NRF_LOG_INFO("NRF_EVT_RADIO_BLOCKED\r\n");
//注意这里没有break,所以 这两个事件的处理都是重新发起请求
case NRF_EVT_RADIO_CANCELED:
NRF_LOG_INFO("NRF_EVT_RADIO_CANCELED\r\n");
err_code = request_next_event_earliest();
APP_ERROR_CHECK(err_code);
break;
default:
break;
}
}
// timeSlot相关的信号处理函数。
// 当请求的timeSlot 被安排开始后,就会收到 START信号,在这个信号处理里面实现
// 实现翻转 LED灯同时设置 Timer0的超时时间,time0在timeSLot开始后会被自动重置为// 1MH运行从0 计数,所以这里设置time0的超时时间比 timeSlot的时间长度短1000us,//这样在timeSLot
// 结束之前就可以收到 NRF_RADIO_CALLBACK_SIGNAL_TYPE_TIMER0信号,然后在这信号处
// 理里做一些收尾工作,在这里我们实现的是请求下一个timeSlot
// NRF_RADIO_CALLBACK_SIGNAL_TYPE_RADIO信号不需要处理,因为我们没有在timeSLot
// 中使用Radio,所以不会有这个信号。
nrf_radio_signal_callback_return_param_t * radio_callback(uint8_t signal_type)
{
static uint8_t start_count = 0;
static uint8_t timer0_count = 0;
switch(signal_type)
{
case NRF_RADIO_CALLBACK_SIGNAL_TYPE_START:
signal_callback_return_param.params.request.p_next = NULL;
signal_callback_return_param.callback_action = NRF_RADIO_SIGNAL_CALLBACK_ACTION_NONE;
#if 1
NRF_TIMER0->TASKS_STOP = 1;
NRF_TIMER0->TASKS_CLEAR = 1;
NRF_TIMER0->MODE = (TIMER_MODE_MODE_Timer << TIMER_MODE_MODE_Pos);
NRF_TIMER0->EVENTS_COMPARE[0] = 0;
NRF_TIMER0->INTENSET = TIMER_INTENSET_COMPARE0_Msk ;
NRF_TIMER0->CC[0] = m_slot_length - 100;
NRF_TIMER0->BITMODE = (TIMER_BITMODE_BITMODE_24Bit << TIMER_BITMODE_BITMODE_Pos);
NRF_TIMER0->TASKS_START = 1;
//非常重要,reset all status in the radio peripheral
NRF_RADIO->POWER = (RADIO_POWER_POWER_Enabled << RADIO_POWER_POWER_Pos);
#else
NRF_TIMER0->INTENSET = TIMER_INTENSET_COMPARE0_Msk;
NRF_TIMER0->CC[0] = m_slot_length - 100;
#endif
NVIC_EnableIRQ(TIMER0_IRQn);
radio_send();//实现射频寄存器配置,射频发送,等待发送完成,因此消耗的时间要小于申请的时间,否则会出错
nrf_gpio_pin_toggle(24); //52dk 20 51dk-24
NRF_LOG_INFO("NRF_RADIO_CALLBACK_SIGNAL_TYPE_START\r\n");
break;
case NRF_RADIO_CALLBACK_SIGNAL_TYPE_RADIO://未开启中断不会进入
NRF_LOG_INFO("NRF_RADIO_CALLBACK_SIGNAL_TYPE_RADIO\r\n");
RADIO_IRQHandler();
signal_callback_return_param.params.request.p_next = NULL;
signal_callback_return_param.callback_action = NRF_RADIO_SIGNAL_CALLBACK_ACTION_NONE;
break;
case NRF_RADIO_CALLBACK_SIGNAL_TYPE_TIMER0:
configure_next_event_normal();
signal_callback_return_param.params.request.p_next = &m_timeslot_request;
signal_callback_return_param.callback_action = NRF_RADIO_SIGNAL_CALLBACK_ACTION_REQUEST_AND_END;
NRF_LOG_INFO("NRF_RADIO_CALLBACK_SIGNAL_TYPE_TIMER0\r\n");
NRF_TIMER0->TASKS_STOP = 1;
NRF_TIMER0->TASKS_CLEAR = 1;
NRF_TIMER0->EVENTS_COMPARE[0] = 0;
break;
case NRF_RADIO_CALLBACK_SIGNAL_TYPE_EXTEND_SUCCEEDED:
NRF_LOG_INFO("NRF_RADIO_CALLBACK_SIGNAL_TYPE_EXTEND_SUCCEEDED\r\n");
break;
case NRF_RADIO_CALLBACK_SIGNAL_TYPE_EXTEND_FAILED:
NRF_LOG_INFO("NRF_RADIO_CALLBACK_SIGNAL_TYPE_EXTEND_FAILED\r\n");
configure_next_event_earliest();
signal_callback_return_param.params.request.p_next = &m_timeslot_request;
signal_callback_return_param.callback_action = NRF_RADIO_SIGNAL_CALLBACK_ACTION_REQUEST_AND_END;
break;
default:
//No implementation needed
break;
}
return (&signal_callback_return_param);
}
// timeSlot初始化函数。
uint32_t timeslot_sd_init(void)
{
uint32_t err_code;
err_code = sd_radio_session_open(radio_callback);
if (err_code != NRF_SUCCESS)
{
return err_code;
}
err_code = request_next_event_earliest();
if (err_code != NRF_SUCCESS)
{
(void)sd_radio_session_close();
return err_code;
}
nrf_gpio_cfg_output(24);
return NRF_SUCCESS;
}
timeslot.h
#ifndef TIMESLOT_H__
#define TIMESLOT_H__
#include <stdint.h>
#include "nrf.h"
/**@brief Radio event handler
*/
void RADIO_timeslot_IRQHandler(void);
/**@brief Request next timeslot event in earliest configuration
*/
uint32_t request_next_event_earliest(void);
/**@brief Configure next timeslot event in earliest configuration
*/
void configure_next_event_earliest(void);
/**@brief Configure next timeslot event in normal configuration
*/
void configure_next_event_normal(void);
/**@brief Timeslot signal handler
*/
void nrf_evt_signal_handler(uint32_t evt_id);
/**@brief Timeslot event handler
*/
nrf_radio_signal_callback_return_param_t * radio_callback(uint8_t signal_type);
/**@brief Function for initializing the timeslot API.
*/
uint32_t timeslot_sd_init(void);
#endif
main.c
timeslot_sd_init()
static void sys_evt_dispatch(uint32_t sys_evt)
{
nrf_evt_signal_handler(sys_evt);
}
Q&A
1、为什么开启了射频中断,但是收不到信号。
注意:当运行到radio_callback函数时用的中断优先级最高,因此无法被低优先级的中断抢断,解决方法如下:
使用最低优先级的中断
#define TIMESLOT_BEGIN_IRQn LPCOMP_IRQn
#define TIMESLOT_BEGIN_IRQHandler LPCOMP_IRQHandler
#define TIMESLOT_BEGIN_IRQPriority 1
在收到NRF_RADIO_CALLBACK_SIGNAL_TYPE_START信号后,启动低优先级的中断
NVIC_SetPendingIRQ(TIMESLOT_BEGIN_IRQn);
//在这里实现发送,那么可以产生射频中断完成信号
void TIMESLOT_BEGIN_IRQHandler(void)
{
radio_send();
}
初始化
NVIC_ClearPendingIRQ(TIMESLOT_BEGIN_IRQn);
NVIC_SetPriority(TIMESLOT_BEGIN_IRQn, 1);
NVIC_EnableIRQ(TIMESLOT_BEGIN_IRQn);
2、在某些自己的工程上,出现重启或者死机的情况。
建议不要开启日志打印,NRF_LOG_INFO