一、前言背景
为了精确控制led周期闪烁,通过引入定时器中断来处理。看了数据手册和例程,发现Nordic有提供了两种截然不同的定时器应用方案
二、定时器归类
TIMER: 定时器外设,可以理解为硬件定时器(类似STM32的通用定时器),运行在高频时钟源上(HFCLK),两种工作模式,定时与计数(捕捉与比较);Nordic52810总共有三路定时器外设,TIMER0
TIMER1
TIMER1
- 优点: 功能丰富,可配置高中断优先级,精确高
- 缺点: 功耗较高
app_timer:定时模块,可以理解为软件定时器(软件模拟的定时器),基于低频时钟(32kHZ)RTC1,配合协议栈使用,当定时时间溢出时(timeout),会通过蓝牙协议栈回调预先配置好的函数接口
- 优点:功耗极低(uA),可配置多个定时器中断(理论上RAM充裕的话,可以一直往队列里面插入)
- 缺点:精度较低,最小计时精度为1ms,由于是软定时器,会被其他高优先级任务抢占,多多少少会被影响计时精度
三、接口讲解
考虑到功耗至上的原则,选择app_timer中断来控制LED灯光闪烁(这个精度也不需要太高)
协议栈运行前需要初始化
app_timer
模块
ret_code_t app_timer_init(void)
{
// Stop RTC to prevent any running timers from expiring (in case of reinitialization)
rtc1_stop();//先停止rtc1
// Initialize operation queue
m_op_queue.first = 0;
m_op_queue.last = 0;
m_op_queue.size = APP_TIMER_CONFIG_OP_QUEUE_SIZE+1;//最大支持的队列大小,可根据实际应用需要调整宏
mp_timer_id_head = NULL;
m_ticks_elapsed_q_read_ind = 0;
m_ticks_elapsed_q_write_ind = 0;
#if APP_TIMER_WITH_PROFILER
m_max_user_op_queue_utilization = 0;
#endif
NVIC_ClearPendingIRQ(SWI_IRQn);//通过swi异常中断后来触发定时回调
NVIC_SetPriority(SWI_IRQn, SWI_IRQ_PRI);
NVIC_EnableIRQ(SWI_IRQn);
rtc1_init(APP_TIMER_CONFIG_RTC_FREQUENCY);
m_ticks_latest = rtc1_counter_get();
return NRF_SUCCESS;
}
初始化软定时器模块后,需要进行定时器的创建,通过接口
app_timer_create
ret_code_t app_timer_create(app_timer_id_t const * p_timer_id,
app_timer_mode_t mode,
app_timer_timeout_handler_t timeout_handler)
{
// Check state and parameters
VERIFY_MODULE_INITIALIZED();
if (timeout_handler == NULL)//定时中断回调的函数,为空返回无效参数
{
return NRF_ERROR_INVALID_PARAM;
}
if (p_timer_id == NULL)//句柄,为空返回无效参数
{
return NRF_ERROR_INVALID_PARAM;
}
if (((timer_node_t*)*p_timer_id)->is_running)
{
return NRF_ERROR_INVALID_STATE;
}
timer_node_t * p_node = (timer_node_t *)*p_timer_id;//保存到队列中
p_node->is_running = false;
p_node->mode = mode;
p_node->p_timeout_handler = timeout_handler;
return NRF_SUCCESS;
}
软定时器创建完成后,可通过模块启动接口
app_timer_start
使能定时,也可以通过app_timer_stop
停止定时
四、代码实例
通过配置1秒重复触发回调进行led灯的翻转闪烁
APP_TIMER_DEF(led_timer_id); //定义句柄
//定时器触发回调
static void led_callback(void * p_context)
{
bsp_LedToggle(BSP_LED0);//翻转led
}
int main(void)
{
app_timer_init();//初始化app_timer模块
// Initialize.
ble_stack_init();//初始化协议栈
bsp_InitLED();//初始化led
app_timer_create(&led_timer_id,APP_TIMER_MODE_REPEATED,led_callback);//注册句柄,重复触发,回调函数
app_timer_start(led_timer_id,APP_TIMER_TICKS(1000), NULL); //1秒触发一次
while(1);
}
五、备注
- 未初始化app_timer的情况下进行创建定时器将会导致非法访问空指针,导致系统异常复位
- 未创建定时器任务,调用开启、停止接口也会导致访问空指针,导致系统异常复位
- 重复调用开启、停止接口,将会出现产生不可预知的结果