一、关于STM32芯片寄存器
1.什么是寄存器
提到单片机,就不得不提到寄存器。根据百度百科介绍,寄存器是中央处理器内的组成部分。寄存器是有限存贮容量的高速存贮部件,它们可用来暂存指令、数据和地址。
简单来说,寄存器就是存放东西的东西。从名字来看,跟火车站寄存行李的地方好像是有关系的。只不过火车站行李寄存处,存放的行李;寄存器可能存放的是指令、数据或地址。 存放数据的寄存器是最好理解的,如果你需要读取一个数据,直接到这个寄存器所在的地方来问问他,数据是多少就行了。问寄存器这个动作,叫做访问寄存器。不同的数据会存放在不同的寄存器,例如引脚PA2与PB8的高低电平数据(1或0)肯定放在不同的寄存器里,那么怎么区分不同的寄存器呢?通过地址,不同的寄存器有不同的地址,就像老张行李寄存处在101号店铺,老王行李寄存处在258号店铺。 指令、地址寄存器与数据寄存器类似,里边存放的都是0和1,毕竟单片机也只认识机器码,机器码都是0或1,只是特别的规定下,数据寄存器里面存放的0和1表示数据,指令寄存器里存放的表示指令。
2.怎么找到某个寄存器的地址
想要找到某个寄存器的地址,可以查看《STM32中文参考手册_V10》,手册可以在这里下载,但是手册中没有直接给出所有的寄存器的地址,需要读者稍加计算,具体可查看这里。
3.寄存器映射
我们知道,存储器本身没有地址,给存储器分配地址的过程叫存储器映射,那什么叫 寄存器映射?寄存器到底是什么? 在存储器 Block2 这块区域,设计的是片上外设,它们以四个字节为一个单元,共 32bit, 每一个单元对应不同的功能,当我们控制这些单元时就可以驱动外设工作。我们可以找到 每个单元的起始地址,然后通过 C 语言指针的操作方式来访问这些单元,如果每次都是通 过这种地址的方式来访问,不仅不好记忆还容易出错,这时我们可以根据每个单元功能的 不同,以功能为名给这个内存单元取一个别名,这个别名就是我们经常说的寄存器,这个 给已经分配好地址的有特定功能的内存单元取别名的过程就叫寄存器映射。
二、GPIO端口设置
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.GPIO框图剖析
3.初始化设置概述
1:打开GPIO口的时钟 2:初始化GPIO口(选择推挽输出) 3:设置低电平
三、点亮LED流水灯
1.打开GPIO口的时钟信号
查阅手册查到时钟信号的地址: GPIO的地址: 则可以计算出时钟寄存器地址为0x40021018,则打开三个IO口的时钟(本例拟打开A、B、C三个IO口)需要将三个位都置1。
#define RCC_APB2ENR (*(unsigned int *)0x40021018)
// 打开时钟
RCC_APB2ENR |= (1<<2); // 打开 GPIOA 时钟
RCC_APB2ENR |= (1<<3); // 打开 GPIOB 时钟
RCC_APB2ENR |= (1<<4); // 打开 GPIOC 时钟
2.GPIO初始化
由 GPIO 的结构决定了 GPIO 可以配置成以下8种模式: 本例采用推免输出模式,因为在输出模式中,推挽模式时双 MOS 管以轮流方式工作,输出数据寄存器 GPIOx_ODR可控制 I/O 输出高低电平。输出速度可配置,有 2MHz\10MHz\50MHz的选项。此处的输出速度即 I/O 支持的高低电平状态最高切换频率,支持的频率越高,功耗越大,如果功耗要求不严格,把速度设置成最大即可。在输出模式时施密特触发器是打开的,即输入可用,通过输入数据寄存器 GPIOx_IDR可读取 I/O 的实际状态。 具体操作如下: 端口1-7为低,端口8-15为高。每个引脚由四个位控制。 对A、B、C端口,代码如下:
#define GPIOA_CRL (*(unsigned int *)0x40010800)
#define GPIOB_CRL (*(unsigned int *)0x40010C00)
#define GPIOC_CRH (*(unsigned int *)0x40011004)
// 配置 GPIO 口为推免输出
// GPIOA----最后四位为0001
GPIOA_CRL |= (1<<0); // 最后一位变1
GPIOA_CRL &= ~(0xE<<0); // 倒数2、3、4位变0
// GPIOB----最后四位为0001
GPIOB_CRL |= (1<<0); // 最后一位变1
GPIOB_CRL &= ~(0xE<<0); // 倒数2、3、4位变0
// GPIOC----前四位为0001
GPIOC_CRH |= (1<<28); // 第四位变1
GPIOC_CRH &= ~(0xE0000000); // 前三位变0
3.设置低电平
输出高电平则为1,低电平则为0。 对A、B、C端口,代码如下:
#define GPIOA_ODR (*(unsigned int *)0x4001080C)
#define GPIOB_ODR (*(unsigned int *)0x40010C0C)
#define GPIOC_ODR (*(unsigned int *)0x4001100C)
GPIOA_ODR &= ~(1<<0); //最后一位变为0
GPIOB_ODR &= ~(1<<0); //最后一位变为0
GPIOC_ODR &= ~(1<<15); //倒数16位变为0
四、C语言全代码及软硬结合操作
1.创建项目
本例采用Keil5编写程序代码,具体操作见用Keil uVision5创建纯汇编语言的STM32工程。 不同之处是: 1)芯片选择STM32F103C8 2)当出现下图时,直接叉掉,不做选择 将野火资料中的下图中的文件复制到新建的项目下:
右击文件夹,选择Add Existing Files to Group Source Group 1(或双击文件夹),选择All FIles,选择刚刚添加的启动文件,Add,Add之后Close: 可以看到Add成功了 打开魔术棒,如下图所示勾选Create HEX File: 在main.c中写入如下代码:
#define GPIOB_BASE 0x40010C00
#define GPIOC_BASE 0x40011000
#define GPIOA_BASE 0x40010800
#define RCC_APB2ENR (*(unsigned int *)0x40021018)
#define GPIOB_CRL (*(unsigned int *)0x40010C00)
#define GPIOC_CRH (*(unsigned int *)0x40011004)
#define GPIOA_CRL (*(unsigned int *)0x40010800)
#define GPIOB_ODR (*(unsigned int *)0x40010C0C)
#define GPIOC_ODR (*(unsigned int *)0x4001100C)
#define GPIOA_ODR (*(unsigned int *)0x4001080C)
void SystemInit(void);
void Delay_ms(volatile unsigned int);
void Delay_ms( volatile unsigned int t)
{
unsigned int i;
while(t--)
for (i=0;i<800;i++);
}
int main(){
// 开启时钟
RCC_APB2ENR |= (1<<3); // 开启 GPIOB 时钟
RCC_APB2ENR |= (1<<4); // 开启 GPIOC 时钟
RCC_APB2ENR |= (1<<2); // 开启 GPIOA 时钟
// 设置 GPIO 为推挽输出
// 设置 GPIOB 最后四位为 0001 (B0)
GPIOB_CRL |= (1<<0); // 最后一位设置为1
GPIOB_CRL &= ~(0xE); // 倒数二、三、四位设置为0
// 设置 GPIOC 前四位为 0001 (C15)
GPIOC_CRH |= (1<<28); // 第四位设置为1
GPIOC_CRH &= ~(0xE0000000); // 前三位设置为0
// 设置 GPIOA 最后四位为 0001 (A0)
GPIOA_CRL |= (1<<0); // 最后一位设置为1
GPIOA_CRL &= ~(0xE); // 倒数二、三、四位设置为0
// 3个LED初始化为不亮(即高点位)
GPIOB_ODR |= (1<<0); // 最后一位设置为1
GPIOC_ODR |= (1<<15); // 倒数第15位设置为1
GPIOA_ODR |= (1<<0); // 最后一位设置为1
while(1){
GPIOB_ODR &= ~(1<<0); // 点灯1
Delay_ms(1000000);
GPIOB_ODR |= (1<<0); // 灭灯1
GPIOC_ODR &= ~(1<<15); // 点灯2
Delay_ms(1000000);
GPIOC_ODR |= (1<<15); // 灭灯2
GPIOA_ODR &= ~(1<<0); // 点灯3
Delay_ms(1000000);
GPIOA_ODR |= (1<<0); // 灭灯3
}
}
//函数为空,目的是为了骗过编译器不报错
void SystemInit(){
}
2.连接电路
根据设计的程序连接电路: 对于USB转TTL模块和stm32f103c8t6连接: GND — GND 3v3 — 3v3 TXD — A10 RXD — A9 总电路如图所示:
3.编译程序
点击如图按钮,编译程序,生成hex文件 将电路板连接到电脑,打开mcuisp,上传HEX文件到stm32f103c8t6上: 如上图之后,程序就开始跑了,如下图gif所示,依次亮灯: 值得注意的点是,LED灯的连法是根据程序连的,正极连面包板正极,负极分别接STM芯片的A0、B0、C15。
五、用示波器仿真查看代码是否正确
1.设置魔法棒信息
点开魔法棒,如图设置
2.设置示波器
点击如图所示按键 设置输出口(灯接的芯片位置) 因为我的灯连的芯片管脚分别是A0、B0、C15,并且是GPIO口,所以输入(以A0为例):GPIOA_IDR.0 ,之后回车,然后依照上图设置bit,颜色。具体输入形式可以参考如何使用Keil5中的虚拟示波器进行软件仿真
3.调试运行
点击如图所示按钮 一段时间后,可以看到Logic Analyzer里面三个波形的显示,观察图像可以看到与代码一致,说明正确执行了。
六、总结
总结下来,这个对初学者真的有很大的难度,尤其是代码的编写,我就参考了很多大佬写的代码,另外就是参考了代码,还要读懂代码,不然接线就会遇到问题,正负极和哪个接口的确认都是很困扰的,但能够做下来,确实受益匪浅。
七、参考资料
STM32 F103之点亮LED流水灯 (STM32入门学习) STM32最小系统下载程序方法 STM32串口下载程序 如何使用Keil5中的虚拟示波器进行软件仿真
|