NCS初探--基于nRF5340的blinky

开发环境:SEGGER Embedded Studio V5.32a

NCS版本:v1.5.1

开发板:PCA10095

项目路径:

 开发板路径:

项目装载:

注意上图的 nRF Connect SDK ReleasenRF Connect Toolchain Version 也可以选择:

但是前提是在SES中配置好:

注意: 路径按照自己安装的NCS选择。

 编译完成:左边是build,右边是debug

 

 烧入现象:LED1周期性闪烁。

代码解析:

1.宏定义:

LED0_NODE:

 由以上两个宏定义可得 LED0_NODE 等价于 DT_N_ALIAS_led0 

DT_NODE_HAS_STATUS:

 这个用来判断设备是否OK的宏比较复杂,此处使用#if,可以看出其实就是判断此宏定义是为0还是为1。我们深入里面查看,首先替换掉第一层得到:

替换掉第二层:

替换掉第三层:

第四层替换很巧妙:

首先这里我们需要拼接 _XXXX config_macro 但是注意,此时的config_macro 实际内容是:

LED0_NODE_STATUS_okay

替换掉上个宏定义得到的内容实际是:

DT_N_ALIAS_led0_STATUS_okay

这个宏是一个由脚本自动根据设备树文件生成的宏,它在 devicetree_unfixed.h 中被定义:

 所以,如果在设备树中,led0的状态为okay,则此宏自动被定义且为1!所以此处替换结果为:

注意最重要的一点,下面有 _XXXX1 的定义:

这个逗号极为重要。 我一开始忽略了逗号就导致代码看不明白。

这里分为两种情况:

1. DT_N_S_leds_S_led_0_STATUS_okay 宏没有被定义,则此处替换为:

Z_IS_ENABLED2(_XXXX)

2.DT_N_S_leds_S_led_0_STATUS_okay 宏被定义了,且为1,则此处替换为:

Z_IS_ENABLED2(_XXXX1)

进一步被替换为:

Z_IS_ENABLED2(_YYYY,)

替换第5层和第六层:

1.如果上一步结果是 Z_IS_ENABLED2(_XXXX) ,则替换结果为:

很明显最后结果为0。

2.如果上一步结果是 Z_IS_ENABLED2(_XXXX1) ,则替换结果为:

很明显结果为1。其实就是因为上面那个逗号。所以追溯到最后,此宏会判断在其他.h里是否定义了某个宏,来确定返回值是否为1。

LED0:

详细过程不再赘述,替换结果为:DT_N_S_soc_S_peripheral_50000000_S_gpio_842500_P_label

即 "GPIO_0"

PIN:

详细过程不再赘述,替换结果为:

DT_N_S_leds_S_led_0_P_gpios_IDX_0_VAL_pin

即 28

FLAGS:

详细过程不再赘述,替换结果为:

DT_N_S_leds_S_led_0_P_gpios_IDX_0_VAL_flags

即 1

至此,所有的宏定义都已经被分析完。

2.主体代码

 可以看到主要代码分为3步:

1.通过LED0获取设备相关信息

2.根据设备信息中的引脚号去初始化相关引脚

3.在while循环里闪烁led

device_get_binding()

 可以看到这个函数使用inline关键字修饰,代表其为内联函数,也就是在实代码编译过程中,函数名会被替换为函数体内容,所以C语言中内联函数一般被定义在.h文件中。好处就是用空间换时间。

compiler_barrier()为编译器内存屏障,防止编译器优化指令执行顺序,在驱动中较为常见,因为驱动设备一般来说需要先初始化才能使用,但是有时候编译器优化会导致指令乱序执行。

另外一个函数根据名称获取设备的函数,这个过程被认为是绑定。根据上一节宏定义,我们已经知道这个函数的入参其实就是 LED0 也就是字符串 "GPIO_0" ,此函数通过两个循环,去轮询__device_start 到 __device_end 之间的所有dev,然后比对每个dev的名字指针,或名字字符串是否相同去判断是否是名字为入参的dev,且此设备必须为 ready 状态。

ready状态怎么判断?

在zephyr中,把所有设备按顺序排列后,每个设备用1bit来表示状态。比如我把所有设备状态存在地址为0x00000000的内存里,我们知道在32位单片机里,物理地址是unsigned long 类型,所以每32个设备我们存在一个地址里面,然后累加存下面32个设备状态。从代码里面印证我们的分析:

这里是一个简单的计算,比如我们找的设备排在第35个,首先我们35/32 = 1余3,所以 sys_test_bit()的两个参数分别为:__device_init_status_start + 1 和 3 ,也就是第35个设备的状态储存在,以32个设备为一组划分,第二个组里面bit3。注意bit是从0开始计算,也就是第四个bit。

而关于 __device_start 和 __device_end 的定义如下:

 所以此处可以看作是一个结构体数组,首元素指针为 __device_start ,最后一个元素指针为 __device_end,通过结构体指针累加,去调用每个结构体元素的name与入参比对。

 这时候会有一个疑问,__device_start到__device_end里的数据是在什么时候被存进去的呢?ready状态又是什么时候被置位的呢?

起始和之前写过的RT-Thread那篇文章里提到的自动初始化一样,这部分设备信息,在编译时就已经确定。在gpio_nrfx.c中有:

 

此宏较为复杂,具体不再分析,其中内容与设备树生成的.h有关,基本流程是读取设备树内信息,把信息以结构体的形式填充在相应的段内。

需要注意的是在设备注册的时候,操作设备的一系列api就随函数指针传入设备信息内了:

右边的所有函数,最终还是调用了Nordic的底层库。 

其实具体实现不明白也没关系,只需要会按照规则修改设备树文件,并会使用设备树宏就可以了,设备树相关宏都以 DT_ 开头。

 gpio_pin_configure()

 此函数使用简单,注意这里配置使用了 | FLAGS,是为了在默认配置的基础上去修改。

可以配置的选项包括输入输出配置,上下拉配置,中断配置等,具体配置可以在函数内查看:

熟悉GPIO的应该从名字就可以判断每个标志所代表配置含义。(在查看这部分代码时,经常会触发一个同时打开多个同名文件导致软件无响应bug,事实证明,一个新的东西总是伴随着新的bug)

gpio_pin_set()

关于引脚电平设置,在这里最终调用的函数为:

 这里的port即dev,而dev的api则是在之前介绍的文件gpio_nrfx.c中设备初始化传入的api函数指针。最终调用的还是Nordic的底层库:

最后,不得不说zephyr是一个很复杂轻量级系统。 面对市面上像ucos,freertos,rt-thread,zephyr等让人眼花缭乱的轻量级os,如何选择一个合适的os很重要。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值