蓝牙芯片NRF51822入门学习:时间管理
前言
如果你跟我一样是一个蓝牙新手,并且还没有买nrf51822的开发板的话,推荐先学cc2541,如果已经买了开发板,那就看我的《蓝牙芯片NRF51822入门学习》系列文章吧,祝你尽早出坑。
本文面对的是:已经掌握nrf51822基本外设,并且阅读了TI官方视频,对蓝牙有了初步了解,但对怎么进一步学习nrf51822没有头绪的孩子。
本文的相关工具、代码和文章更新网盘链接:http://pan.baidu.com/s/1bn5y9gr 密码:ijxf
网盘内有以下两个子文件夹:
\相关软件 当中nrf51_sdk_v6_0_0_43681.zip是我们要用到的V6.0版本的SDK
\文章更新 内部有每篇文章的PDF、代码、资料、工具。
\BLE视频 TI对蓝牙基本属性的讲解
开发工具
5.1版MDK,nRFgo studio
固件版本
nrf51_sdk_v6_0_0_43681.msi,s110_nrf51822_7.0.0_softdevice.hex
相关硬件
JTAG,兼容pca10001的nrf51822开发板。
第一节:常用接口描述
开篇教程中,我们来学习nrf51822的Libraries中时间管理模块,它的源代码和头文件分别为app_timer.c/app_timer.h
这是Nordic为我们提供的虚拟定时器,这个定时器不同于硬件上的TIMER,而是基于RTC1实现的一种虚拟定时器,其将定时功能作为了一个资源进行管理,所以会有初始化、创建等过程。
PS1:
nrf51822的SDK采用封装思想,需要暴露给用户的信息都在相关模块的头文件中;为了提醒用户不去看具体实现细节,我们可以发现相关的API,比如app_timer_create()的源码部分都是没有接口描述信息的,相关使用方法需要看app_timer.h或者翻阅SDK目录下的Documentation\index.html。还有一些隐藏细节的封装技巧,感兴趣的孩童可以看本篇第三节。PS2:快速查找定位文件,可以安装一个软件:everything,输入文件名就可以瞬间查找到想要的文件,然后再文件上右键选择“open
path”就可以打开文件所在的文件夹了。
1、参数宏APP_TIMER_INIT()
这个宏用于初始化app_timer模块,这是一个参数宏,接口定义如下:
APP_TIMER_INIT(PRESCALER, MAX_TIMERS, OP_QUEUES_SIZE, USE_SCHEDULER)
其中PRESCALE 分频比例,填入0的话,每秒就产生32768次tick,定时最大长度为0xFFFFFF次tick,也就是说500多秒定时。
- PS3:与ucos提供的时基tick不同,本SDK的主要在定时到达的时候进入RTC中断,而不是每个TICK都进入。因此就算每秒就产生32768次tick,也不会拖慢系统性能。
MAX_TIMERS 必须大于等于工程中创建的timer数量。
OP_QUEUES_SIZE 操作队列的大小,具体意思看第三节。如果不作死,选择等于MAX_TIMERS就行了。
USE_SCHEDULER 是否使用任务调度器,当前不使用
2、参数宏APP_TIMER_TICKS()
这个宏用于计算特定毫秒数相当于多少个tick。接口定义如下:
APP_TIMER_TICKS(MS, PRESCALER)
其中MS是单位为毫秒的定时时间,PRESCALER是分频比例。
3、函数app_timer_create()
用于创建一个timer,并获取生成timer的控制句柄。接口定义如下:
uint32_t app_timer_create(app_timer_id_t * p_timer_id,app_timer_mode_t mode,app_timer_timeout_handler_t timeout_handler)
p_timer_id 读取到创建的timer的句柄
mode timer的类型,其中
APP_TIMER_MODE_SINGLE_SHOT是单次执行APP_TIMER_MODE_REPEATED是循环执行timeout_handler 被注册到内核的回调函数,当timer超时后就会执行。
4、函数app_timer_start()
设置一个timer的定时间隔和上下文参数,并启动这个timer。接口定义如下:
uint32_t app_timer_start(app_timer_id_t timer_id, uint32_t timeout_ticks, void * p_context)
timer_id_t app_timer_create()里创建的timer句柄
timeout_ticks 定时的tick数量,一般用APP_TIMER_TICKS()计算。
p_context 传递给超时回调函数的参数,不能指向局部的自动变量。
5、app_timer_stop()
停止一个timer的运行。接口定义如下:
uint32_t app_timer_stop(app_timer_id_t timer_id)
timer_id app_timer_create()里创建的timer句柄
第二节:流水灯例子
讨论延时最简单的例子就是流水灯了,代码位于网盘目录下的\文章更新\蓝牙芯片NRF51822入门学习1:时间管理.rar\ Timer_Blinky.rar,在SDK所在目录\nrf51822下新建文件夹Test,将Timer_Blinky.rar解压到文件夹里。
打开Timer_Blinky\MDK\ timer_blink.uvproj
实验代码位于main.c内
首先定义几个初始化用到的宏
#define APP_TIMER_PRESCALER 0 #define APP_TIMER_MAX_TIMERS 1 #define APP_TIMER_OP_QUEUE_SIZE 1
在TimerInit()中启用LFCLK并初始化timer(官方例程ble_app_uart没有开启LFCLLK的动作,也许S110的底层会自动开启LFCLK时钟)
NRF_CLOCK->EVENTS_LFCLKSTARTED = 0;NRF_CLOCK->TASKS_LFCLKSTART = 1;while(NRF_CLOCK->EVENTS_LFCLKSTARTED == 0){//donoting}NRF_CLOCK->EVENTS_LFCLKSTARTED = 0;APP_TIMER_INIT(APP_TIMER_PRESCALER, APP_TIMER_MAX_TIMERS, APP_TIMER_OP_QUEUE_SIZE, false);
然后在BlinkInit()内创建一个流水灯所用到的timer,相关代码是
uint32_t ERR_CODE = app_timer_create(&BlinkHandle, APP_TIMER_MODE_SINGLE_SHOT, BlinkTimeoutHandler);//创建定时器,并读取句
柄号,单次执行
APP_ERROR_CHECK(ERR_CODE);
其中BlinkHandle是用于保存句柄号的全局变量。
BlinkTimeoutHandler是为流水灯编写的超时回调函数,在这个函数内判断当前LED灯是否点亮,是否需要操作下一个LED灯,并通过app_timer_start()设置下一轮的定时时间。
最后通过BlinkStart()来启动流水灯的timer。
void BlinkStart(bool IsForward){static bool ForwardFlag;ForwardFlag = IsForward;……app_timer_start(BlinkHandle, LED_ON_TIME, (void*)&ForwardFlag);}
最后mian()的执行顺序如下:
int main(){TimerInit();BlinkInit();BlinkStart(true);while(1){// BlinkStart(false);// nrf_delay_ms(4000);// BlinkStart(true);// nrf_delay_ms(4000);}}
下载程序到开发板后,可以观察到LED灯依次被点亮。
第三节:app_timer具体实现的源码阅读提示
本节送给喜(te)欢(bie)专(dan)研(teng)的孩子,本节只对app_time模块做个粗略的概述,当对模块有了宏观的理解后,再读源代码就能事半功倍。
- PS4:推荐使用source insight阅读源码,高亮标记(shift+F8)功能非常有用,本教程均提供了source
insight的工程文件夹。
section1:
我们打开app_timer.h
#define APP_TIMER_NODE_SIZE 40 #define APP_TIMER_USER_OP_SIZE 24 #define APP_TIMER_USER_SIZE 8 #define APP_TIMER_INT_LEVELS 3
这里APP_TIMER_NODE_SIZE、APP_TIMER_USER_OP_SIZE、APP_TIMER_USER_SIZE分别声明的是timer_node_t、timer_user_op_t、timer_user_t三个结构体的大小,用于参数宏APP_TIMER_INIT()内部分配数据空间。这样的话就无需将这三个结构体的内部细节暴露给用户了。而在源码中使用静态断言STATIC_ASSERT()来判断这三个宏的大小是否正确。
APP_TIMER_INT_LEVELS指的是SDK定义的三个中断等级,APP_IRQ_PRIORITY_LOW(低优先级3)、APP_IRQ_PRIORITY_HIGH(高优先级1)、NRF_APP_PRIORITY_THREAD(用户优先级!=[1或者3])。
section2:
app_timer模块主要占用内存的分配方式,这些内存的分配在APP_TIMER_INIT()中实现,分别为timer节点(类型timer_node_t)、操作队列对象(类型timer_user_t)和操作队列存储空间(类型timer_user_op_t)分配空间。
假定APP_TIMER_INIT()的参数MAX_TIMERS2, OP_QUEUES_SIZE4则它们的内存映射如下图所示。
节点nodex就是我们创建的timer的对象,而由userx同对应的opx组成的队列则是保存相关操作(比如start或者stop操作)的地方。初始化函数同时会给p_buffer、mp_users这两个全局变量初始化,让它们分别指向首个timer节点和首个操作队列对象。这样后面就能用
p_buffer[xx]的方式找到对应的对象了。
section3:延时链表
timer节点的类型为timer_node_t,其内部有一个next属性,这是用来实现延时链表的。当相应的timer被start后,它的node就会被加入到延时链表中
如上图所示,全局变量m_timer_id_head是延时链表的表头,当表头指向TIMER_NULL时候证明延时链表为空。每个node都有一个ticks_to_expire属性,作用如下:
如上图所示,nodeA的ticks_to_expir指示了nodeA与上次超时(m_ticks_latest)之间的时间差;nodeB的ticks_to_expir则指示了与nodeA之间的时间差。
RTC1_Counter是app_timer模块选用的底层RTC的计数器,当它自增到nodeA标记的值后,就会触发一次RTC中断,在中断中执行nodeA内部所注册的超时回调函数并更新m_ticks_latest的值后,触发进入SWI0中断中;在SWI0中断内将nodeA从链表中删掉,并根据m_ticks_latest和nodeB的ticks_to_expire计算出新的NRF_RTC1->CC[0]值,用于定时时间到达后触发新的RTC中断。
假设用户用过app_timer_start()指示需要将一个timer对应nodeE加入到链表中,则会通过记录下来请求插入时候RTC1_Counter的值(记录于ticks_at_start)和定时间隔(记录于ticks_first_interval),计算插入到链表的那个位置。
而因为已经执行而被删掉的node,如果节点模式为APP_TIMER_MODE_REPEATED,也会重新插回到链表中。
section4:操作队列
在app_timer中有3个op队列,对应 APP_IRQ_PRIORITY_LOW、APP_IRQ_PRIORITY_HIGH、NRF_APP_PRIORITY_THREAD三个优先等级。
当用户调用app_timer_start()或者app_timer_stop()时候,相关的一些对延时链表的操作并不是立即执行的。而是通过FIFO操作写入到当前环境对应的op队列中(在user_id_get()里通过SCB->ICSR寄存获取当前运行环境,可以参考《STM32F10X-programming-manual.pdf》第134页的描述),然后触发软中断SWI0,在软中断中读取3个队列中的操作数据,对延时链表内对应的节点进行操作。
section5:相关中断
app_timer分别使用了两个中断:RTC1和SWI0,其中SWI0是软中断,用户可以分别通过NVIC_SetPendingIRQ(RTC1_IRQn)和NVIC_SetPendingIRQ(SWI0_IRQn)直接触发。
其中RTC1_IRQHandler()调用timer_timeouts_check()处理超时的情况,而SWI0_IRQHandler()调用timer_list_handler()对链表进行处理。当在timer_timeouts_check()内部需要对链表进行修改时候,就会触发SWI0来处理链表问题,反之亦然。
因为timer_timeouts_check()和timer_list_handler()都对链表进行操作,因此要求他们的中断优先等级必须一致,app_timer中默认都是APP_IRQ_PRIORITY_LOW级。
有明白的可以加群:805601459(备注CSDN)进行交流。本文仅限于内部交流,禁止商用!
蓝牙芯片NRF51822入门学习:时间管理相关推荐
- 学习时间管理,让程序猿一生过得更加自由
最近跟随老师去学习易效能时间管理公开课,虽然只是个公开课,但是也收获满满的,改善了我的拖延症,做事效率提高了很多,时间的分配比以前也科学多了-可见易效能时间管理课程叶武滨老师的功力非凡啊! 下面分享一 ...
- libevent源码学习-----时间管理
libevent监听的event有以下几种 文件描述符/套接字,没有设定超时时长 信号 文件描述符/套接字,设定超时时长 对于时间,libevent内部的时间管理是通过最小堆实现的,原因如下 既然某些 ...
- 跟sheldon学习时间管理
(本文转自温泉老师的博客|学生的时间管理与自我提高|CandyApe|,话说温泉老师是我们来自豆瓣的一位好友买家,从他这篇文章充分印证了古人"淫者见淫,智者见智"的理论-- 平日最 ...
- 学习时间管理,就要我们吃青蛙!这是什么梗?
时间管理,就要我们吃掉一只又大又丑的青蛙?我选择狗带... 说到吃青蛙,大家或许会觉得恶心,所幸,这里的吃青蛙并不是指我们生活中的青蛙.当然假如你有特殊爱好的,我也不能阻止. 返回正题,本文中所说的& ...
- 提高生活、学习、工作效率的方法——时间管理Vs个人管理
首先,我想对于大家来说,时间管理这个词应该并不陌生.不过,在开会之前,又有几个知道呢?反正我当时并不知道.这就需要反思了.不过这里,先不做反思,先说说时间管理. 初次接触到这个词,我想的是:为什么要管 ...
- 时间管理 |《小强升职记》学习笔记 | Part1
盲目,是组织他们快速成长的关键因素. 盲目的原因: 在这个人生的关键时期,我们被迫完成角色的转变. 我们还来不及构建自己的职业规划和人生目标. 我们不具备平衡工作和生活的能力. 我们没有养成良好的习惯 ...
- 第三次自考总结——时间管理和学习知识
上周日,参加了第三次的自考,对于时间管理和知识的学习又有了更多的认识.总结一下. 回顾 第一次参加自考的时候,自己没有按照老师要求的那样,没有做好时间管理,没有做好计划,没有看三遍书再去做题.完全是考 ...
- 时间管理专题_理论篇
本文属非商业用途,文中借鉴他人处均已注明出处,若侵犯原作权益,请联系删除,谢谢 简介 总感觉时间不够用,一方面确实没闲着,另一方面又找不到有那方面的巨大收获.之前接触过时间管理,但是不曾认真的总结过. ...
- 工作中如何时间管理?
忙忙碌碌一天下来工作效率大大折扣,时间都不知道花到哪里了.相信很多人都存在这个困惑,做一件事时想到另外一件事或者被其他事情打乱节奏,如果你也存在这种情况建议精读这篇文章,一定会对你有所帮助. 导读 工 ...
最新文章
- [二叉树建树] 后序遍历与中序遍历建立二叉树
- MS SQL SERVER中备份所有数据库
- ROS-Rtabmap:linux shell运行多个shell(运行多个程序)
- uva 1625——Color Length
- python 字典组成的列表_python - 合并多列表组成一个字典
- linux网卡顺序问题,linux网卡绑定及网卡顺序变更测试.docx
- Java——包装器类
- cardBattle游戏启动场景设计
- jmeter安全证书_使用Jmeter进行https接口测试时,如何导入证书
- 常用的网络上的 webservice 地址
- 桌面计算机恢复出厂设置,电脑桌面恢复出厂设置
- RACK为TCP BBR提供动力源
- springMVC 用超链接做国际化
- 今日杂感-20220322
- 现实中的项目范围变更
- 培训html源码,前端培训——html源码笔记
- 2021-CVPR-图像修复论文导读《TransFill: Reference-guided Image Inpainting by Merging Multiple Color and ~~~》
- Autoware介绍
- LoadLibrary failed with error 1114:动态链接库(DLL)初始化例程失败 解决方法
- 单片机百科知识点---杂七杂八系列