文章内容
:以 STM32 最小系统核心板(STM32F103C8T6)+面板板+3只红绿蓝 LED 搭建电路,使用 GPIOB、GPIOC、GPIOD 这3个端口控制 LED 灯,轮流闪烁,间隔时长1秒。
开发环境
:keil 5
目录
1 初识寄存器
我们知道,存储器本身没有地址,给存储器分配地址的过程叫存储器映射,那什么叫寄存器映射?寄存器到底是什么?
1.1 寄存器
- 在存储器 Block2 这块区域,设计的是片上外设,它们以四个字节为一个单元,共 32bit,每一个单元对应不同的功能。
- 我们可以根据每个单元功能的不同,以功能为名给这个内存单元取一个别名,这个别名就是我们经常说的寄存器
- 这个给已经分配好地址的有特定功能的内存单元取别名的过程就叫寄存器映射。
1.2 STM32 的外设地址映射
1.2.1 总线基地址
-
从存储器映射那张图的 Block2 可以看到,分为4大块,每块都有一个起始地址,这个起始地址就是基地址。
-
然后到下一块起始地址的时候就会和前一块地址出现偏差,这个差值就是偏移量,即相对基地址的偏移量。
总线基地址如下图所示:
总线名称 | 总线基地址 | 相对外设基地址的偏移 |
---|---|---|
APB1 | 0x4000 0000 | 0x0 |
APB2 | 0x4001 0000 | 0x0001 0000 |
AHB | 0x4001 8000 | 0x0001 8000 |
从上图可以看到 APB1 总线基地址是 0x4000 0000,相对外设基地址的偏移量是0,所以此总线也是外设 Block2 的基地址。
1.2.2 外设基地址
- 每条总线上都会挂接着很多的外设,这些外设也会有自己的地址范围,XXX外设的首个地址即最低地址就是XXX外设的基地址,也称作XXX边界地址。
- 有关STM32F10xxx 外设的具体边界地址可以参考《STM32中文参考手册》P28页第23节, 里面有详细的介绍。这里我们就以GPIO外设来讲解外设基地址,其他的外设也是同样分析。
GPIO外设基地址如下图所示:
外设名称 | 外设基地址 | 相对 APB2 总线的地址偏移 |
---|---|---|
GPIOA | 0x4001 0800 | 0x0000 0800 |
GPIOB | 0x4001 0C00 | 0x0000 0C00 |
GPIOC | 0x4001 1000 | 0x0000 1000 |
GPIOD | 0x4001 1400 | 0x0000 1400 |
GPIOE | 0x4001 1800 | 0x0000 1800 |
GPIOF | 0x4001 1C00 | 0x0000 1C00 |
GPIOG | 0x4001 2000 | 0x0000 2000 |
1.2.3 外设寄存器地址
- XXX外设的寄存器就分布在其对应的外设地址范围内。
- 这里我们以GPIO外设为例,GPIO是通用输入输出端口的简称,可以通过软件来控制其输入和输出。GPIO有很多个寄存器,每一个都有特定的功能。每个寄存器为32bit,占四个字节,这些寄存器都是按顺序依次排列在外设的基地址上。寄存器的位置都以相对该外设基地址的偏移地址来描述。
这里我们以GPIOC端口为例,来说明GPIO都有哪些寄存器,如下表所示:
寄存器名称 | 寄存器地址 | 相对 GPIOB 基址的偏移 |
---|---|---|
GPIOB_CRL | 0x4001 0C00 | 0x00 |
GPIOB_CRH | 0x4001 0C04 | 0x04 |
GPIOB_IDR | 0x4001 0C08 | 0x08 |
GPIOB_ODR | 0x4001 0C0C | 0x0C |
GPIOH_BSRR | 0x4001 0C10 | 0x10 |
GPIOH_BRR | 0x4001 0C14 | 0x14 |
GPIOH_LCKR | 0x4001 0C18 | 0x18 |
2 使用寄存器点灯
2.1 工程文件创建
2.1.1 新建工程文件
在 keil 里创建新工程文件可以参考笔者的另一条博文:基于 MDK 创建汇编语言的STM32工程并分析HEX文件
① 打开 keil ,先点击菜单栏的Project
,接着点击New uVision Project
创建新工程;
② 在弹出的窗口选择工程路径,输入工程名(可随意命名,最好全英文,这里命名为 LED ),并保存;
③ 在新窗口中选择 CPU 型号,这里选择STM32F103C8
芯片。
④ 在 run-time environment
窗口中,选择下图所示:
2.1.2 添加文件
在工程文件里添加 main.c 文件
接下来我们考虑如何使用寄存器进行代码编写。
2.2 配置 GPIO 端口
2.2.1 初识 GPIO
- GPIO 是通用输入输出端口的简称,简单来说就是 STM32 可控制的引脚,STM32 芯片 的 GPIO引脚与外部设备连接起来,从而实现与外部通讯、控制以及数据采集的功能。
- 最基本的输出功能是由 STM32 控制引脚输出高、低电平,实现开关控制,如把 GPIO 引脚接入到 LED 灯,那就可以控制 LED 灯的亮灭,引脚接入到继电器或三极管,那就可以 通过继电器或三极管控制外部大功率电路的通断。
- 最基本的输入功能是检测外部输入电平,如把 GPIO 引脚连接到按键,通过电平高低区分按键是否被按下。
2.2.2 GPIO配置
- 配置时钟
查看《STM32中文参考手册》,可以看到时钟控制名字叫做 RCC,属于 AHB 总线。
手册P25页的系统结构可以看到时钟的从属关系,可以看出 AHB 总线包含 RCC 时钟控制,GPIO 是属于 APB2 的。
我们需要用到 GPIO 的A、B、C端口,查到其对应地址如下:
接下来,可以在手册 6.3.7小节找到 APB2 外设时钟使能寄存器(RCC_APB2ENR),偏移地址是0x18,所以 APB2 的地址就是0x4002 1018
。
以及端口 A、B、C 的位数,如下:
通过自行查找,现在就可以对时钟进行使能:
//RCC的AHB1时钟使能寄存器地址,强制转换成指针
#define RCC_APB2ENR *((unsigned volatile int*)0x40021018)
RCC_APB2ENR|=1<<2; //APB2-GPIOA外设时钟使能
RCC_APB2ENR|=1<<3; //APB2-GPIOB外设时钟使能
RCC_APB2ENR|=1<<4; //APB2-GPIOC外设时钟使能
- 输入输出模式设置
控制 LED 需要输出高电平或是低电平,所以需要配置为输出,所以我们这里需要配置为推挽输出。
STM32中,用端口配置
低
寄存器(GPIOx_CRL)
来配置引脚Px0-Px7
, 用端口配置高
寄存器(GPIOx_CRH)
来配置引脚Px8-Px15
。
我们回到流水灯问题,问题中我们需要 GPIOB、GPIOC、GPIOD 这3个端口控制红、黄、绿三个 LED 灯,所以这里配置三个引脚 PA 、PB 、PC
使用的寄存器是 GPIOB_CRH,下面我们来寻找对应的寄存器地址。
端口配置高寄存器时:
GPIOx,表示不管是 PA, PB 还是 PE,都能用。偏移地址是 0x04,意思是在基地址的基础上再加 0x04,所以,对于 GPIOB 来说就是 0x4001 0c04。
端口配置低寄存器时:
GPIOx_CRL 中包含 0-7 号引脚(控制GPIO低8位),每个引脚占用 4 个寄存器位。 低寄存器的偏移地址为0x00,MODE 位用来配置输出的速度, CNF 位用来配置各种输入输出模式。
对于GPIOA的A12
、GPIOB的B1
、GPIOC的C14
,配置如下:
/*GPIOA寄存器地址,强制转换为指针*/
#define GPIOA_CRH *((unsigned volatile int*)0x40010804)
/*GPIOB寄存器地址,强制转换为指针*/
#define GPIOB_CRL *((unsigned volatile int*)0x40010C00)
/*GPIOC寄存器地址,强制转换为指针*/
#define GPIOC_CRH *((unsigned volatile int*)0x40011004)
GPIOA_CRH&=0xFFF0FFFF; /*设置位 清零*/
GPIOA_CRH|=0x00020000; /*PA12推挽输出*/
GPIOB_CRL&=0xFFFFFF0F; /*设置位 清零*/
GPIOB_CRL|=0x00000020; /*PB1推挽输出*/
GPIOC_CRH&=0xF0FFFFFF; /*设置位 清零*/
GPIOC_CRH|=0x02000000; /*PC14推挽输出,设置最大速度为2Mhz*/
点亮 LED 灯需要输出低电平,我们需要寻找端口输出寄存器:
#define GPIOA_ODR *((unsigned volatile int*)0x4001080C)
#define GPIOB_ODR *((unsigned volatile int*)0x40010C0C)
#define GPIOC_ODR *((unsigned volatile int*)0x4001100C)
GPIOA_ODR&=1<<12; /*设置初始灯为亮*/
GPIOB_ODR|=0<<1; /*设置初始灯为灭*/
GPIOC_ODR|=0<<14; /*设置初始灯为灭*/
2.3 执行 C 语言代码
2.3.1 C代码
- 在之前的基础上再添加时延代码:
//延时函数
void Delay_ms( volatile unsigned int t)
{
unsigned int i;
while(t--)
for (i=0;i<800;i++);
}
- 整理之后的 C 代码如下,将其添加到工程文件的
main.c
文件中
/*GPIOA外设基地址*/
#define GPIOA_BASE 0x40010800
/*GPIOB外设基地址*/
#define GPIOB_BASE 0x40010C00
/*GPIOC外设基地址*/
#define GPIOC_BASE 0x40011000
/*RCC的AHB1时钟使能寄存器地址,强制转换成指针*/
#define RCC_APB2ENR *((unsigned volatile int*)0x40021018)
/*GPIOA寄存器地址,强制转换为指针*/
#define GPIOA_CRH *((unsigned volatile int*)0x40010804)
#define GPIOA_ODR *((unsigned volatile int*)0x4001080C)
/*GPIOB寄存器地址,强制转换为指针*/
#define GPIOB_CRL *((unsigned volatile int*)0x40010C00)
#define GPIOB_ODR *((unsigned volatile int*)0x40010C0C)
/*GPIOC寄存器地址,强制转换为指针*/
#define GPIOC_CRH *((unsigned volatile int*)0x40011004)
#define GPIOC_ODR *((unsigned volatile int*)0x4001100C)
/*延时函数*/
void Delay_ms( volatile unsigned int t)
{
unsigned int i;
while(t--)
for (i=0;i<800;i++);
}
/*主函数*/
int main()
{
RCC_APB2ENR|=1<<2; /*APB2-GPIOA外设时钟使能*/
RCC_APB2ENR|=1<<3; /*APB2-GPIOB外设时钟使能*/
RCC_APB2ENR|=1<<4; /*APB2-GPIOC外设时钟使能*/
GPIOA_CRH&=0xFFF0FFFF; /*设置位 清零*/
GPIOA_CRH|=0x00020000; /*PA12推挽输出*/
GPIOA_ODR&=1<<12; /*设置初始灯为亮*/
GPIOB_CRL&=0xFFFFFF0F; /*设置位 清零*/
GPIOB_CRL|=0x00000020; /*PB1推挽输出*/
GPIOB_ODR|=0<<1; /*设置初始灯为灭*/
GPIOC_CRH&=0xF0FFFFFF; /*设置位 清零*/
GPIOC_CRH|=0x02000000; /*PC14推挽输出*/
GPIOC_ODR|=0<<14; /*设置初始灯为灭*/
while(1)
{
GPIOA_ODR&= ~(1<<12); /*PA12高电平*/
Delay_ms(3000000);
GPIOA_ODR|= (1<<12); /*PA12低电平*/
Delay_ms(3000000);
GPIOB_ODR&= ~(1<<1); /*PB1高电平*/
Delay_ms(3000000);
GPIOB_ODR|= (1<<1); /*PB1低电平*/
Delay_ms(3000000);
GPIOC_ODR&= ~(1<<14); /*PC14高电平*/
Delay_ms(3000000);
GPIOC_ODR|= (1<<14); /*PC14低电平*/
Delay_ms(3000000);
}
}
2.3.2 编译形成 hex 文件
- 编译前准备
点击魔法棒
,在Output
界面下,勾选Create HEX File
,点击OK
,才能生成 hex 文件
- 点击编译,生成 hex 文件
3 电路效果
3.1 设备选择
1、stm32 核心板103f 一块
2、usb 转串口一块
3、面包板一块
4、LED 红、黄、绿三色各一只,导线若干
3.2 连接电路
USB 转 TTL 模块
和stm32f103c8t6
的连接如下图:
GND-G
3V3-3.3
RXD-A9
TXD-A10
-
BOOT0 与 BOOT1 配置启动方式
将 boot0 置1,boot1 置0,并且要按下 reset 键 -
GPIO 端口与 LED 连接
C 程序使用 GPIOA的A12
、GPIOB的B1
、GPIOC的C14
端口连接。 -
实际连接结果:
注意
:接线时尽量避免用杜邦线串联,可能接触不良或过程中松动。可用一根杜邦线,长度不够时可通过面包板卡槽连接两根杜邦线。
3.3 烧录运行
需要用到烧录软件,这里用的是 mcuisp
软件。可以从这得到:百度网盘下载链接:https://pan.baidu.com/s/1sjKJTMtg3cJLtsGOQhyJ3w 提取码:cyuu
注意
:网盘里的 CH340-driver 驱动文件也需要安装!
3.3.1 烧录
-
将
USB转TTL模块
插入电脑,打开mcuisp
,将之前生成的 hex 文件上传,选择DTR的低电平复位,RTs高电平进BootLoader
-
点击
读器件信息
,点击开始编程
,成功的话右侧会出现一切正常。
3.3.2 流水灯效果
4 总结
相信有很多小伙伴和我一样,都是第一次使用 STM32 板子进行开发,确实有点难度。通过整篇文章的篇幅也能看出,STM 使用寄存器操作过程比较复杂,尤其是在寻找寄存器地址时,需要对照参考手册查找,相当麻烦,其实,还有其他方法,比如通过 stm32CubeMX
,配合 Keil 使用寄存器地址的方式进行操作,这就相较简单,我们之后可以尝试一下。此外,文章流水灯代码使用的是 C 语言编写,读者若是对嵌入式有兴趣,也可以试着使用汇编语言进行点灯。
第一次学习这种开发,如有不妥之处,还请读者斧正。
5 参考资料
1、STM32寄存器的简介、地址查找,与直接操作寄存器
2、基于汇编和C语言STM32流水灯依次闪烁
3、STM32串口下载程序
4、百度网盘STM32参考资料包(包括STM32中文参考手册)提取码:luha