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/tutorials/b/software-development-kit/posts/setting-up-the-timeslot-api 实现led的翻转

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);
}

nordic timeslot实现radio和ble一起工作

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 //扩展时间申请失败,会收到这个信号

 

 

nordic timeslot实现radio和ble一起工作

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