使用寄存器点亮LED小灯
我使用的是野火的STM32F103VET6指南者开发板学习
2.1 GPIO
2.1.1 GPIO介绍
GPIO 是通用输入输出端口的简称,简单来说就是STM32 可控制的引脚。
STM32 芯片的 GPIO 引脚与外部设备连接起来,从而实现与外部通讯、控制以及数据采集的功能。
STM32 芯片的 GPIO 被分成很多组,每组有 16 个引脚,如型号为 STM32F103VET6 型号(指南者) 的芯片有 GPIOA、 GPIOB、 GPIOC 至 GPIOE 共 5 组 GPIO,芯片一共 100 个引脚,其中GPIO 就占了一大部分,所有的 GPIO 引脚都有基本的输入输出功能。
最基本的输出功能是由 STM32 控制引脚输出高、低电平,实现控制。
如:把 GPIO引脚接入到 LED 灯,那就可以控制 LED 灯的亮灭,引脚接入到继电器或三极管,那就可以通过继电器或三极管控制外部大功率电路的通断。
最基本的输入功能是检测外部输入电平.
如:把 GPIO 引脚连接到按键,通过电平高低区分按键是否被按下。
2.1.2 GPIO功能描述
GPIO端口的每个位可以由软件分别配置成多种模式:
- 输入浮空
- 输入上拉
- 输入下拉
- 模拟输入
- 开漏输出
- 推挽式输出
- 推挽式复用功能
- 开漏复用功能
每个I/O端口位可以自由编程,然而I/0端口寄存器必须按32位字被访问(不允许半字或字节访问)。 GPIOx_BSRR和GPIOx_BRR寄存器允许对任何GPIO寄存器的读/更改的独立访问;这样,在读和更改访问之间产生IRQ时不会发生危险。
I/O端口位的基本结构:
2.2 使用寄存器点亮LED小灯
2.2.1 硬件连接
这是一个 RGB 灯,里面由红蓝绿三个小灯构成, 使用 PWM 控制时可以混合成 256 不同的颜色。
图中从 3 个 LED 灯的阳极引出连接到 3.3V 电源,阴极各经过 1 个限流电阻引入至STM32 的 3 个 GPIO 引脚中,所以我们只要控制这三个引脚输出高低电平,即可控制其所连接 LED 灯的亮灭。
我们的目标是把 GPIO 的引脚设置成推挽输出模式并且默认下拉,输出低电平,这样就能让 LED 灯亮起来了。
2.2.2 启动文件
名为“ startup_stm32f10x_hd.s”的文件,它里边使用汇编语言写好了基本程序,当STM32 芯片上电启动的时候,首先会执行这里的汇编程序,从而建立起 C 语言的运行环境,所以我们把这个文件称为 启动文件 。
该文件使用的汇编指令是 Cortex-M3 内核支持的指令,可参考《Cortex-M3 权威指南》 中指令集章节
关于启动文件了解到如下内容即可:
1、我们需要在外部定义一个 SystemInit函数设置 STM32 的时钟;
2、STM32 上电后,会执行 SystemInit 函数;
3、最后执行我们 C 语言中的 main 函数。
2.2.3 stm32f10x.h 文件
连接 LED 灯的 GPIO 引脚,是要通过读写寄存器来控制的
如何控制寄存器?
寄存器就是给一个已经分配好地址的特殊的内存空间取的一个别名, 这个特殊的内存空间可以通过指针来操作。
在编程之前我们要先实现寄存器映射,有关寄存器映射的代码都统一写在 stm32f10x.h 文件中
stm32f10x.h文件代码如下:
/*本文件用于添加寄存器地址及结构体定义*/
/*片上外设基地址 */
#define PERIPH_BASE ((unsigned int)0x40000000)
/*APB2 总线基地址 */
#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)
/* AHB总线基地址 */
#define AHBPERIPH_BASE (PERIPH_BASE + 0x20000)
/*GPIOB外设基地址*/
#define GPIOB_BASE (APB2PERIPH_BASE + 0x0C00)
/* GPIOB寄存器地址,强制转换成指针 */
#define GPIOB_CRL *(unsigned int*)(GPIOB_BASE+0x00)
#define GPIOB_CRH *(unsigned int*)(GPIOB_BASE+0x04)
#define GPIOB_IDR *(unsigned int*)(GPIOB_BASE+0x08)
#define GPIOB_ODR *(unsigned int*)(GPIOB_BASE+0x0C)
#define GPIOB_BSRR *(unsigned int*)(GPIOB_BASE+0x10)
#define GPIOB_BRR *(unsigned int*)(GPIOB_BASE+0x14)
#define GPIOB_LCKR *(unsigned int*)(GPIOB_BASE+0x18)
/*RCC外设基地址*/
#define RCC_BASE (AHBPERIPH_BASE + 0x1000)
/*RCC的AHB1时钟使能寄存器地址,强制转换成指针*/
#define RCC_APB2ENR *(unsigned int*)(RCC_BASE+0x18)
1、为方便使用,寄存器的地址值都直接强制转换成了指针。
2、代码的最后两段是 RCC 外设寄存器的地址定义, RCC 外设是用来设置时钟的,以后我们会详细分析,本实验中只要了解到 使用 GPIO 外设必须开启它的时钟 即可。
2.2.4 main函数
① SystemInit()函数
如果只写main函数,而不写Systemlnit函数,编译时会发生错误:
“Error: L6218E: Undefined symbol SystemInit (referred from startup_stm32f10x.o)”
从分析启动文件时我们知道, Reset_Handler 调用了该函数用来初始化 SMT32 系统时钟,为了简单起见,我们在 main 文件里面定义一个SystemInit 空函数,什么也不做,为的是骗过编译器,把这个错误去掉。
添加代码如下:
// 函数为空,目的是为了骗过编译器不报错
void SystemInit(void)
{
}
关于配置系统时钟我们在后面再写。当我们不配置系统时时, STM32 会把 HSI 当作系统时钟, HSI=8M,由芯片内部的振荡器提供。
还有一个方法就是在启动文件中把有关SystemInit 的代码注释掉也可以。
②GPIO设置
首先我们把连接到 LED 灯的 GPIO 引脚 PB0 配置成 输出模式,即配置 GPIO 的端口配置低寄存器 CRL。
CRL 中包含 0-7 号引脚,每个引脚占用 4 个寄存器位。
1、MODE 位用来配置输出的速度,
2、CNF 位用来配置各种输入输出模式。
在这里我们把 PB0 配置为通用推挽输出,输出的速度为 10M。
代码如下:
//清空控制PB0的端口位
GPIOB_CRL &= ~( 0x0F<< (4*0));
// 配置PB0为通用推挽输出,速度为10M
GPIOB_CRL |= (1<<4*0);
注意:
1、在代码中,我们先把控制 PB0 的端口位清 0,然后再向它赋值“0001 b”,从而使GPIOB0 引脚设置成输出模式,速度为 10M。
2、代码中使用了“&=~”、“|=”这种操作方法是为了避免影响到寄存器中的其它位。
③控制输出低电平
在输出模式时,对端口位设置/清除寄存器 BSRR 寄存器、端口位清除寄存器 BRR 和ODR 寄存器写入参数即可控制引脚的电平状态,其中操作 BSRR 和 BRR 最终影响的都是ODR 寄存器,然后再通过ODR 寄存器的输出来控制 GPIO。
为了一步到位,在这里直接操作 ODR 寄存器来控制 GPIO 的电平。
// PB0 输出 低电平
GPIOB_ODR &= ~(1<<0);
④设置外设时钟
由于 STM32 的 外设很多,为了降低功耗,每个外设都对应着一个时钟,在芯片刚上电的时候这些时钟都是被关闭的,如果想要外设工作,必须把相应的时钟打开。
STM32 的所有外设的时钟由一个专门的外设来管理,叫 RCC(reset and clockcontrol)
所有的 GPIO 都挂载到 APB2 总线上,具体的时钟由 APB2 外设时钟使能寄存器(RCC_APB2ENR)来控制
代码如下:
// 开启GPIOB 端口时钟
RCC_APB2ENR |= (1<<3);
⑤完整代码
main.c:
/*工程模板(寄存器版本)*/
#include "stm32f10x.h"
/*主函数*/
int main(void)
{
// 开启GPIOB 端口时钟
RCC_APB2ENR |= (1<<3);
//清空控制PB0的端口位
GPIOB_CRL &= ~( 0x0F<< (4*0));
// 配置PB0为通用推挽输出,速度为10M
GPIOB_CRL |= (1<<4*0);
// PB0 输出 低电平
GPIOB_ODR &= ~(1<<0);
while(1);
}
// 函数为空,目的是为了骗过编译器不报错
void SystemInit(void)
{
}