本文已参与「新人创作礼」活动,一起开启掘金创作之路。
一、寄存器
寄存器的功能是存储二进制代码,它是由具有存储功能的触发器组合起来构成的。一个触发器可以存储1位二进制代码,故存放n位二进制代码的寄存器,需用n个触发器来构成 (百度百科) 按我理解来说,寄存器就是控制你引脚输入输出的东西。
二、如何控制寄存器
寄存器是存储二进制代码,如何控制寄存器,就是通过改变他的二进制代码来控制啊! 主要运用到位操作,主要用到或运算,与运算,位移操作。在这些操作中大都是无符号操作的。
2.1、无符号操作之左移
符号 : << 原来的二进制
0100 0001
左移一位后的二进制
1000 0010
简单理解来说,左移就是整体往左边移动。 最左边的那个0呢?移走了,先不管他。最右边需需要补上一个0。
2.2、无符号操作之右移
符号: >> 道理与左移一样
0100 0001 >> 0010 0000
2.3、或运算
在二进制中,只要有1,那么运算过后就是1. 符合: | 举个例子:
0100 0010
和
0000 0000
进行或运算,那么运算结果就是
0100 0010
2.4与运算
两个数进行与运算,必须两个都是1才能是1,其余都是0 符号: & 举个例子
0100 0011
和
0100 1101
进行与运算,得到的结果是
0100 0001
2.5取反运算
取反就是把1变成0,把0变成1; 符号:~ 举个例子
0100 0011
取反后
1011 1100
通常取反和与运算在一起运用 例如: &=~
三、STM32的常用知识
3.1 STM32引脚判断
芯片四周是引脚,左下角的小圆点表示1脚,然后从1 脚起按照逆时针的顺序排列(所有芯片的引脚顺序都是逆时针排列的)
换成引脚就是标注就是这样的
(这张图应该逆时针旋转90度,野火参考书上是这样放的)
3.2 STM32运行理论
3.3.1 ICode 总线:
ICode 中的I 表示Instruction,即指令。我们写好的程序编译之后都是一条条指令,存放在从 中,内核要读取这些指令来执行程序就必须通过ICode 总线,它几乎每时每刻都需要被使用,它是专门用来取指的。
3.3.2 DCode 总线
DCode 中的D 表示Data,即数据,那说明这条总线是用来取数的。我们在写程序的时候,数据有常量和变量两种,常量就是固定不变的,用C 语言中的const 关键字修饰,是放到内部的FLASH 当中的,变量是可变的,不管是全局变量还是局部变量都放在内部的SRAM。因为数据可以被Dcode 总线和DMA总线访问,所以为了避免访问冲突,在取数的时候需要经过一个总线矩阵来仲裁,决定哪个总线在取数。
3.3.3 DCode 总线
系统总线主要是访问外设的寄存器,我们通常说的寄存器编程,即读写寄存器都是通过这根系统总线来完成的。
3.3.4 DMA 总线
DMA 总线也主要是用来传输数据,这个数据可以是在某个外设的数据寄存器,可以在SRAM,可以在内部的FLASH。因为数据可以被Dcode 总线和DMA 总线访问,所以为了避免访问冲突,在取数的时候需要经过一个总线矩阵来仲裁,决定哪个总线在取数。
3.3.5 FLASH
内部的闪存存储器即FLASH,我们编写好的程序就放在这个地方。内核通过ICode 总线来取里面的指令。
3.3.6 SRAM
内部的SRAM,即我们通常说的RAM,程序的变量,堆栈等的开销都是基于内部的SRAM。内核通过DCode 总线来访问它。
3.3.7 AHB 到APB 的桥
从AHB 总线延伸出来的两条APB2 和APB1 总线,上面挂载着STM32 各种各样的特色外设。我们经常说的GPIO、串口、I2C、SPI这些外设就挂载在这两条总线上,这个是我们学习STM32 的重点,就是要学会编程这些外设去驱动外部的各种设备。
四、储存器映射
存储器本身不具有地址信息,它的地址是由芯片厂商或用户分配,给存储器分配地址的过程就称为存储器映射。
看图这里很多外设都已经分配好了地址
4.1 储存器分类
有3 个块非常重要,也是我们最关心的三个块。Block0 用来设计成内部FLASH(存放代码),Block1 用来设计成内部RAM(变量),Block2 用来设计成片上的外设
4.2 BLOCK0 内部区域划分
4.3 BLOCK1 内部区域划分
4.4BLOCK2 内部区域划分
五、寄存器映射
这个给已经分配好地址的有特定功能的内存单元取别名的过程就叫寄存器映射。在我理解就是通过对应手册找到对应的GPIO口的名字对应的地址,进行操作(改变地址的高低电平)。
六、外设寄存器
(1)寄存器说明中首先列出了该寄存器中的名称,“(GPIOx_BSRR)(x=A…E)”这段的意思是该寄存器名为“GPIOx_BSRR”其中的“x”可以为A-E,也就是说这个寄存器说明适用于GPIOA、GPIOB 至GPIOE,这些GPIO 端口都有这样的一个寄存器。 (2)偏移地址是指本寄存器相对于这个外设的基地址的偏移。本寄存器的偏移地址是0x18,从参考手册中我们可以查到GPIOA 外设的基地址为0x4001 0800 ,我们就可以算出GPIOA的这个GPIOA_BSRR 寄存器的地址为:0x4001 0800+0x18也就是0x4001 0818. (3)紧接着的是本寄存器的位表,表中列出它的0-31 位的名称及权限。表上方的数字为位编号,中间为位名称,最下方为读写权限,其中w表示只写,r表示只读,rw表示可读写。本寄存器中的位权限都是w,所以只能写,如果读本寄存器,是无法保证读取到它真正内容的。 (4)。例如本寄存器中有两种寄存器位,分别为BRy 及BSy,其中的y 数值可以是0-15,这里的0-15 表示端口的引脚号,如BR0、BS0 用于控制GPIOx 第0 个引脚,若x 表示GPIOA,那就是控制GPIOA 的第0 引脚,而BR1、BS1 就是控制GPIOA 第1 个引脚。其中BRy 引脚的说明是“0:不会对相应的ODRx 位执行任何操作;1:对相应ODRx位进行复位”。这里的“复位”是将该位设置为0 的意思,而“置位”表示将该位设置为1;说明中的ODRx 是另一个寄存器的寄存器位,我们只需要知道ODRx 位为1 的时候,对应的引脚x 输出高电平,为0 的时候对应的引脚输出低电平即可(感兴趣的读者可以查询该寄存器GPIOx_ODR 的说明了解)。所以,如果对BR0 写入“1”的话,那么GPIOx 的第0个引脚就会输出“低电平”,但是对BR0 写入“0”的话,却不会影响ODR0 位,所以引脚电平不会改变。要想该引脚输出“高电平”,就需要对“BS0”位写入“1”,寄存器位BSy 与BRy 是相反的操作。
七、点灯之路
7.1 原理
最基本的输出功能是由 STM32 控制引脚输出高、低电平,实现开关控制。
7.2 步骤
(1)找你要的外设名称 (2)找外设对应的地址 (3)配置寄存器、时钟、算偏移量 (4)下载、运行。
7.3 程序框架
原理不写了,野火视频说得很详细了,记得加启动文件。
void SystemInit(void)
{
//函数体为空,目的是为了骗过编译器不报错
}
int main(void)
{
}
7.4 找寄存器
我的板子上说明有三色灯,RGB嘛三种颜色。
我们先去电亮PB1.
PB就是GPIO端口B,根据手册我们可以找到对应的地址
他的起始地址就是0X40010C00
是挂载在总线APB2上的
因为要输出,所以我们要把GPIO端口B配置成输出模式,因为这里输出数据寄存器只有低16位有效
所以我们配置寄存器的时候就配置低寄存器
这里可以看到偏移量是0x00,普通的推挽输出是00,输出模式是01. 那么我们配置寄存器的代码就出来了。 0001是二进制转为十进制就是 1 ,转为十六进制就是 0x01.
void SystemInit(void)
{
//函数体为空,目的是为了骗过编译器不报错
}
int main(void)
{
//配置寄存器模式
*(unsigned int *)0X40010C00 |= 0X01;
}
我配置寄存器PB1嘛,我让他输出低电平就好了。
偏移量是0Ch,就是十六进制的0X0C.
他的起始地址就是0X40010C00
所以输出端口级寄存器的地址就是
0X40010C00 + 0X0C = 0X40010C0C;
因为我们要把1变成低电平,而其他位置不变。 所以这里就有 &=~ 的这种算法,先取反,在或运算,因为0和任何值或运算都是0,1和任何值或运算都是任何值本身,所以我们这里取反后进行或运算。
void SystemInit(void)
{
//函数体为空,目的是为了骗过编译器不报错
}
int main(void)
{
//配置寄存器模式
*(unsigned int *)0X40010C00 |= 0X01;
//配置输出模式
*(unsigned int *)0X40010C0C &=~ 0X10;
}
下一步就是配置时钟了,因为我们STM32比较高级,时钟就是保安一样,你要让保安给你开门,你才能进出,所以我们要打开时钟,才能电亮灯。
刚才说了我们这个是挂载在APB2上的,是GPIOB嘛,所以就就找到了对应的时钟,如何打开时钟,和我们操作寄存器是一样的道理。
找地址:
起始地址:
0X40021000
找偏移量:
我们是端口B,算地址:
0X40021000 + 0X18 = 0X40021018;
配置第三位打开,二进制就是0000 1000,转为10进制就是8,转为16进制就是0X08; 所以代码就出来了
void SystemInit(void)
{
//函数体为空,目的是为了骗过编译器不报错
}
int main(void)
{
//配置寄存器模式
*(unsigned int *)0X40010C00 |= 0X01;
//配置输出模式
*(unsigned int *)0X40010C0C &=~ 0X10;
//打开时钟
*(unsigned int *)0X40021018 |= 0X08;
}
因为你要先打开时钟,才能操作,所以这里更改一下顺序
void SystemInit(void)
{
//函数体为空,目的是为了骗过编译器不报错
}
int main(void)
{
//打开时钟
*(unsigned int *)0X40021018 |= 0X08;
//配置寄存器模式
*(unsigned int *)0X40010C00 |= 0X01;
//配置输出模式
*(unsigned int *)0X40010C0C &=~ 0X10;
}
下载到板子上,就可以了,按一下复位键,就可以了(怎么下载可以看野火视频),我这里就不说了