点亮LED
概述
该应用笔记主要以SDK_12.2.0为基础,先简要说明GPIO相关寄存器,在讲解如何通过控制GPIO点亮LED。
寄存器简要说明
nrf51822总共有32个引脚(PIN0~PIN31),每个引脚都可以通过寄存器PIN_CNF[n](n=0..31)来配置以下功能:
- 方向(也就是设置引脚是输入还是输出)
- 驱动器配置
- 使能上拉电阻和下拉电阻
- 电平检测
- 输入缓冲器连接/断开
- 模拟输入(仅特定引脚有模拟输入)
GPIO外设的基址为0x50000000,相应寄存器的偏移地址如下:
比如我们要配置PIN18引脚,则访问的地址为:0x50000000+0x748 = 0x50000748 。
PIN_CNF[n]寄存器
- A:引脚方向,0为输入,1为输出
- B:连接或断开输入缓冲区,0为连接输入缓冲区,1为断开输入缓冲区
- C:配置上下拉电阻,0为不使能,1为使能下拉电阻,3为使能上拉电阻
- D:驱动器配置
- 0:标准'0',标准'1'
- 1:高驱动'0',标准'1'
- 2:标准'0',高驱动'1'
- 3:高驱动'0',高'驱动'1'
- 4:断开'0',标准'1'
- 5:断开'0',高驱动'1'
- 6:标准'0', 断开'1'
- 7:高驱动'0',断开'1'
- E:引脚电平检测,0为不使能,2为检测高电平,3为检测低电平
PIN_CNF[n]作为GPIO部分最重要的寄存器,其实并不复杂。这里以配置为输出,并驱动LED为例:
- A设置为1,将引脚配置为输出引脚
- B设置为1,断开输入缓冲区
- C设置为0,不使能上下拉电阻
- D设置为0,驱动器配置为标准'0',标准'1'
- E设置为0,不使能引脚电平检测
如果我们要把PIN18配置为输出,可以用以下代码:
*(uint32_t*)0x50000748 = 0x00000003;
这是一行比较奇怪的代码,我们在项目中正常是不会这样写的(你这样写,可能项目主管会找你麻烦),这里提一下只是为了说明寄存器配置的本质就是向寄存器对应的地址写入相应的值。
上面已经提到了PIN_CNF[18]的偏移地址是0x50000748,而配置为输出只要把第0,1位设置为1,其它设置为0即可。
后面会提到使用SDK提供的函数进行初始化,这里其实只要了解即可。
OUT寄存器
- 读写相应引脚的驱动电平(0为低电平,1为高电平)
OUTSET寄存器
- 读OUTSET寄存器相当于读OUT寄存器
- 向相应位写入1,则相应引脚置为高电平
- 写0则无效
- 如果需要将某个引脚置为高电平,一般有两种方式:
- 方式1.先读取OUT寄存器,在改变某个位,之后在写入到OUT寄存器
- 方式2.直接设置OUTSET寄存器对应位
由于IO状态的更改是不可预测的,在方式1中,先读取OUT寄存器,在改变某个位的过程中,有些引脚的状态可能已经发生了变化。这时在将旧值写入OUT寄存器相当于脏写,这在大部分应用中都是不允许的。所以用户在将某个位设置为高电平时应该直接操作OUTSET寄存器(原子操作)。
OUTCLR寄存器
- 读OUTCLR寄存器相当于读OUT寄存器
- 向相应位写入1,则相应引脚置为低电平
- 写0则无效
- 类比于OUTSET寄存器,如果用户需要将某个引脚设置为低电平,则应该设置OUTCLR寄存器对应位
IN寄存器
- 读取相应引脚的输入状态
DIR寄存器
- 读写相应引脚的输入输出状态(0为输入,1为输出)
DIRSET寄存器
- 读DIRSET寄存器相当于读DIR寄存器
- 向相应位写入1,则相应引脚置为输出状态
- 写0则无效
DIRCLR寄存器
- 读DIRCLR寄存器相当于读DIR寄存器
- 向相应位写入1,则相应引脚置为输入状态
- 写0则无效
为了加深对操作GPIO寄存器的理解,提供了以下寄存器控制代码,主要功能是点亮5个LED
//软件延时 void nrf_delay() { int i,j; for (i = 0; i < 10000; i++) for (j = 0; j < 100; j++); } //函数入口 int main(void) { *(unsigned int*)0x50000748 = 0x00000003;//设置PIN18为输出 *(unsigned int*)0x5000074C = 0x00000003;//设置PIN19为输出 *(unsigned int*)0x50000750 = 0x00000003;//设置PIN20为输出 *(unsigned int*)0x50000754 = 0x00000003;//设置PIN21为输出 *(unsigned int*)0x50000758 = 0x00000003;//设置PIN22为输出 while (1) { *(unsigned int*)0x50000508 = 0x00540000;//将PIN18、PIN20和PIN22设置为高电平 *(unsigned int*)0x5000050C = 0x00280000;//将PIN19和PIN21设置为低电平 nrf_delay(); *(unsigned int*)0x50000508 = 0x00280000;//将PIN19和PIN21设置为高电平 *(unsigned int*)0x5000050C = 0x00540000;//将PIN18、PIN20和PIN22设置为低电平 nrf_delay(); } }
以上代码不需要包含任何头文件,虽然这段程序代码量少,但可读性和可维护性都很差,在实际项目中,是不会用的。
铺垫完了,现在我们在看看SDK提供给我们的例程是怎么样的。
测试步骤
在测试前,我们先了解一下GPIO的相关硬件原理图,由下图可知我们的开发板一共有5个USER LED,从PIN18~PIN22,高电平LED亮。
- 下载SDK包并解压,打开工程:...\examples\peripheral\blinky\pca10028\blank\arm4\blinky_pca10028.uvproj
注:注意路径不能包含中文字符,推荐解压到根目录下,因为如果目录太深,会导致程序无法正常编译。
- 编译程序,打开pca10028.h文件,修改以下代码:
#define LEDS_NUMBER 5 #define LED_START 18 #define LED_0 18 #define LED_1 19 #define LED_2 20 #define LED_3 21 #define LED_4 22 #define LED_STOP 22 #define LEDS_ACTIVE_STATE 1 //默认是高电平,LED亮 #define LEDS_LIST { LED_0, LED_1, LED_2, LED_3, LED_4 } #define LEDS_INV_MASK LEDS_MASK
- 编译下载,正常可以看到5个LED开始按流水灯方式点亮。
简要说明
打开main.c文件,我们可以看到main函数里代码并不多,其中:
bsp_board_leds_init函数主要是把相关引脚配置为输出模式,并关闭LED
void bsp_board_leds_init(void) { uint32_t i; for(i = 0; i < LEDS_NUMBER; ++i) { nrf_gpio_cfg_output(m_board_led_list[i]); //循环配置 LEDS_NUMBER 个LED为输出模式 } bsp_board_leds_off();//LED关闭 }
bsp_board_led_invert函数主要是翻转LED的状态
void bsp_board_led_invert(uint32_t led_idx) { ASSERT(led_idx < LEDS_NUMBER); nrf_gpio_pin_toggle(m_board_led_list[led_idx]);//翻转LED的状态 }
选择你要查看到函数,点击右键->Go To Definition Of "XXX" (快捷键F12),可以看到SDK经过层层封装,最后实际上也就是类似我们上面提供的寄存器控制代码,这里也就不赘述了。