前言

之前辞职找工作的时候发现,很多公司希望招聘蓝牙技术方面的人才,所以干脆丢开LWIP静下心来学习蓝牙技术。原本以为一两星期能基本学会的,谁知道所选的蓝牙芯片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           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_TIMERS==2, OP_QUEUES_SIZE==4则它们的内存映射如下图所示。

节点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级。

蓝牙芯片NRF51822入门学习1:时间管理的更多相关文章

  1. Linux内核入门到放弃-时间管理-《深入Linux内核架构》笔记

    低分辨率定时器的实现 定时器激活与进程统计 IA-32将timer_interrupt注册为中断处理程序,而AMD64使用的是timer_event_interrupt.这两个函数都通过调用所谓的全局 ...

  2. dubbo入门学习 六 admin管理界面

    1. 本质就是一个web项目 2. 获取注册中心内Provider注册的信息.用页面呈现出来. 3. 实现步骤 3.1 把dubbo-admin-2.5.3.war上传到服务器tomcat中. 3.2 ...

  3. 工作8年对技术学习过程的一些 总结 与 感悟 为什么有时迷茫、无奈 学习编程语言的最高境界最重要的是编程思想 T 字发展 学技术忌讳”什么都会“ 每天进步一点等式图 时间管理矩阵

    工作这些年对技术学习过程的一些 总结 与 感悟(一) 引言 工作了8年,一路走来总有些感触时不时的浮现在脑海中.写下来留个痕迹,也顺便给大家一点参考.希望能给初学者一点帮助. 入门 进入计算机行业,起 ...

  4. (笔记)Linux内核学习(八)之定时器和时间管理

    一 内核中的时间观念 内核在硬件的帮助下计算和管理时间.硬件为内核提供一个系统定时器用以计算流逝的时间.系 统定时器以某种频率自行触发,产生时钟中断,进入内核时钟中断处理程序中进行处理. 墙上时间和系 ...

  5. Hadoop入门学习笔记---part3

    2015年元旦,好好学习,天天向上.良好的开端是成功的一半,任何学习都不能中断,只有坚持才会出结果.继续学习Hadoop.冰冻三尺,非一日之寒! 经过Hadoop的伪分布集群环境的搭建,基本对Hado ...

  6. opengl入门学习

    OpenGL入门学习 说起编程作图,大概还有很多人想起TC的#include <graphics.h>吧? 但是各位是否想过,那些画面绚丽的PC游戏是如何编写出来的?就靠TC那可怜的640 ...

  7. git入门学习(一):github for windows上传本地项目到github

    Git是目前最先进的分布式版本控制系统,作为一个程序员,我们需要掌握其用法.Github发布了Github for Windows 则大大降低了学习成本和使用难度,他甚至比SVN都简单. 一.首先在g ...

  8. OpenGL入门学习(转)

    OpenGL入门学习 http://www.cppblog.com/doing5552/archive/2009/01/08/71532.html 说起编程作图,大概还有很多人想起TC的#includ ...

  9. OpenCV入门学习笔记

    OpenCV入门学习笔记 参照OpenCV中文论坛相关文档(http://www.opencv.org.cn/) 一.简介 OpenCV(Open Source Computer Vision),开源 ...

随机推荐

  1. VC的话有必要认真听,但却不用急着照办

    本文来自著名风险投资人 Fred Wilson 的博客 AVC,他在 2016 年 8 月 23 日的这篇文章<Understanding VCs>里用简单的语言揭秘了 VC(风险投资人) ...

  2. logstash tomcat catalina.out 告警

    <pre name="code" class="html">[elk@dr-mysql01 tomcat]$ cat logstash_tomcat ...

  3. HDU5044---Tree 树链剖分

    大致题意:add1 u v   u到v路径上所有点的权值加上k,add2  u 到v路径上所有边的权值加上k 最后输出所有点的权值,边的权值..树链剖分预处理然后来个线性O(n)的操作.刚开始用线段树 ...

  4. Struts分页

    1.分页的bean类PaginationSupport.java  2.写好后直接在action里面调用,计算当前页显示的数据  3.写一个公用的jsp页面,直接在需要分页的页面include就可以了 ...

  5. java 实例化是调用了子类重写方法

    java 实例化时调用了抽象方法或者class里面某个方法,如果子类有重写改方法,实际运行的是子类重写方法 package auto.test; //抽象父类 public abstract clas ...

  6. (转)iOS Wow体验 - 第三章 - 用户体验的差异化策略

    本文是<iOS Wow Factor:Apps and UX Design Techniques for iPhone and iPad>第三章译文精选,其余章节将陆续放出.上一篇:Wow ...

  7. linux高级技巧:heartbeat+lvs(一)

    1.heartbeat一个简短的引论:        Heartbeat 项目是 Linux-HA project的一个组成部分,它实现了一个高可用集群系统.心跳服务和集群通信是高可用集群的两个关键组 ...

  8. 黑马程序员——Java高级应用(一)

    ------<a href="http://www.itheima.com" target="blank">Java培训.Android培训.iOS ...

  9. (转).net开发者对android第二周的学习体会

    这一周相对没有春节时这么闲了,白天也比较多的工作要做,每天晚上又要被我三岁的女儿折腾到十点, 实在没有多少时间学习.在前一周的基础上,这周我试着自己练习写了一个个人管理的android的程序,主要实现 ...

  10. jquery easyui-datagrid 如何清空数据

      //清空原有数据 方法1: var item = $('#filegrid').datagrid('getRows'); if (item) { ; i >= ; i--) { var in ...