通过野火开发板学习单片机
1.STM32介绍
1.1 STM32分类
-
从内核上分有Cortex-M0、M3、M4 和M7
-
F1 代表了基础型,基于Cortex-M3 内核,主频为72MHZ
F4 代表了高性能,基于Cortex-M4 内核,主频180M。
1.2 STM32命名
1.3 参考手册和数据手册
- 数据手册:用于芯片选型和设计原理图
- 参考手册:用于编程时查阅
1.4 STM32架构
- Icode总线
– 该总线讲M3内核的指令总线与闪存指令接口向量,完成指令预取 - 4个驱动单元
- Dcode总线
- M3内核的Dcode总线与闪存的数据接口相连(完成常量加载和调用)
- 系统总线(S-bus)
- M3内核的系统总线到总线矩阵,总线矩阵协调内核和DMA的访问
- 通用DMA1总线
- DMA的AHB主控接口与总线矩阵相联
- 通用DMA2
- 以太网DMA(部分设备)
- Dcode总线
- 4个被动单元
- 内部SRAM
- 内部FLASH
- FSMC(灵活的静态存储器控制器,可以扩展内存如SRAM NORFLASH)
- AHB到APB的桥,连接APB设备
- 总线矩阵
- 协调内核系统总线和DMA主控总线之间的访问和仲裁。
- AHB外设通过总线矩阵与系统总线相连,允许DMA访问。
- AHB/APB桥
- 两个桥在AHB和APB之间提供同步连接。
- APB1操作速度限于36MHz
- APB2操作于全速,最高72MHz
在使用外设前,必须设置寄存器RCC_AHBENR
1.5 存储器映射
-
所有的被控单元FLASH、RAM、FSMC、AHB到APB的桥(片上外设)共同排列在一个4GB的地址空间,通过他们的地址来操作他们。
-
存储器本身不具有地址,是由芯片厂家或用户分配的,这个过程叫做存储器映射。
-
给存储器再分配一个地址叫做存储器重映射。
-
数据以小端格式存放在存储器中(最低地址在最低字节,最高地址在最高字节)
-
可访问的存储器空间背分成8个主要块,每个512MB。
-
Block0:用来设计成内部的FLASH
- ZET6 和VET6的FLASH都是512k,属于大容量,RCT6是256k
- 存储了ST出厂时烧写的bootloader(自举程序,用户无法修改,可以通过串口下载)
- 用户程序放在FLASH,即0x0800 0000 开始的512k空间。
-
Block1:用来设计成内部的RAM
- ZET6和VET6都是64KB
- 默认地址为0x20000000
-
Block2:用来设计成片上的外设
- 根据总线速度不同,分为AHB、APB1和APB2
- 根据总线速度不同,分为AHB、APB1和APB2
-
-
寄存器映射
- 以block2为例,设计的是片上外设,他们以4个字节为一个单位,共32bit,每个单元对应不同的功能,控制这些单元时就可以驱动外设工作。
- 找到每个单元的起始地址,通过指针操作,来访问这些单元。给这个内存单元取个别名就叫寄存器映射。
- 如GPIOB端口输出寄存器为ODR(地址0x40010C0C),32bit的低16bit有效,对应着16个外部IO,写0/1 对应的IO则输出低、高电平。
- 通过寄存器的方式来访问,更容易记忆,不容易出错
1 // GPIOB 端口全部输出高电平
2 *(unsigned int*)(0x4001 0C0C) = 0xFFFF;
1 // GPIOB 端口全部输出高电平
2 #define GPIOB_ODR (unsigned int*)(GPIOB_BASE+0x0C)
3 * GPIOB_ODR = 0xFF;
为了方便操作,可以直接把指针操作* 也定义到寄存器别名里文章来源:https://www.toymoban.com/news/detail-812968.html
1 // GPIOB 端口全部输出高电平
2 #define GPIOB_ODR *(unsigned int*)(GPIOB_BASE+0x0C)
3 GPIOB_ODR = 0xFF;
- 片上外设地址映射
- 不同总线挂载不同外设
- APB1 挂载低速外设
- APB2和AHB挂载高速外设
- 对应总线最低地址称为基地址,也是挂载在该总线上的首个外设地址。
- GPIO属于告诉外设,挂载到APB2总线上。
- GPIOB为例介绍寄存器地址列表,每个GPIO都有这些寄存器,每个占4个字节 32bit
- 每个GPIO都有两个配置寄存器(GPIOx_CRL、GPIOx_CRH)
- 两个32位数据寄存器(GPIOx_IDR、GPIOx_ODR)
- 一个32位置位/复位寄存器(GPIOx_BSRR)
- 一个16位复位寄存器(GPIOx_BRR)
- 一个32位锁定寄存器(GPIOx_LCKR)
- 每个GPIO可由寄存器配置位多种模式
- 输入浮空
- 输入上拉
- 输入下拉
- 模拟输入
- 开漏输出
- 推挽式输出
- 推挽式复用功能
- 开漏复用功能
- 设置清楚寄存器
- GPIOx中的x都是从A-E
- 地址偏移:是相对于这个外设的基地址的偏移即GPIOA的外设基地址偏移就是0x40010800 + 0x10
- 寄存器位表,描述了每个bit的名称和权限以及对应的操作。y的取值是0-15,即对应GPIOA.1脚
- 注意对BRy写1:是清除,即设置低电平,写0无效
- 对BSy写1:是设置,即设置为高电平,写0无效
- 封装总线和外设基地址
- 为了方便理解和记忆,把总线基地址和外设基地址都以相应的宏定义
1 /* 外设基地址*/
2 #define PERIPH_BASE ((unsigned int)0x40000000)
3
4 /* 总线基地址*/
5 #define APB1PERIPH_BASE PERIPH_BASE
6 #define APB2PERIPH_BASE (PERIPH_BASE + 0x00010000)
7 #define AHBPERIPH_BASE (PERIPH_BASE + 0x00020000)
8
9
10 /* GPIO 外设基地址*/
11 #define GPIOA_BASE (APB2PERIPH_BASE + 0x0800)
12 #define GPIOB_BASE (APB2PERIPH_BASE + 0x0C00)
13 #define GPIOC_BASE (APB2PERIPH_BASE + 0x1000)
14 #define GPIOD_BASE (APB2PERIPH_BASE + 0x1400)
15 #define GPIOE_BASE (APB2PERIPH_BASE + 0x1800)
16 #define GPIOF_BASE (APB2PERIPH_BASE + 0x1C00)
17 #define GPIOG_BASE (APB2PERIPH_BASE + 0x2000)
18
19
20 /* 寄存器基地址,以GPIOB 为例*/
21 #define GPIOB_CRL (GPIOB_BASE+0x00)
22 #define GPIOB_CRH (GPIOB_BASE+0x04)
23 #define GPIOB_IDR (GPIOB_BASE+0x08)
24 #define GPIOB_ODR (GPIOB_BASE+0x0C)
25 #define GPIOB_BSRR (GPIOB_BASE+0x10)
26 #define GPIOB_BRR (GPIOB_BASE+0x14)
27 #define GPIOB_LCKR (GPIOB_BASE+0x18)
- 首先定义片上外设基地址PERIPH_BASE
- 接着在此基础上,加入个总线低地址偏移,得到APB1、APB2总线低基地址
- 在其上再加上外设地址的偏移,得到GPIOA-G的外设地址
- 最后在外设地址上假如各寄存器的地址偏移,得到特定寄存器的地址。
有了具体的地址,就可以使用指针读写该寄存器了。
1 /* 控制GPIOB 引脚0 输出低电平(BSRR 寄存器的BR0 置1) */
2 *(unsigned int *)GPIOB_BSRR = (0x01<<(16+0));
3
4 /* 控制GPIOB 引脚0 输出高电平(BSRR 寄存器的BS0 置1) */
5 *(unsigned int *)GPIOB_BSRR = 0x01<<0;
6
7 unsigned int temp;
8 /* 读取GPIOB 端口所有引脚的电平(读IDR 寄存器) */
9 temp = *(unsigned int *)GPIOB_IDR;
- 该代码使用(Unsigned int *)把GPIOB_BSRR宏的数值强制转换成了地址
- 再取* 并赋值,是对改地址的写操作。
- 读寄存器也是用取指针操作,把寄存器中的数据取到变量里,从而获取外设的状态。
- 封装寄存器列表
- 如果对每个地址都进行定义,会显得分繁琐,可以使用结果提对一组GPIO进行封装
1 typedef unsigned int uint32_t; /* 无符号32 位变量*/
2 typedef unsigned short int uint16_t; /* 无符号16 位变量*/
3
4 /* GPIO 寄存器列表*/
5 typedef struct {
6 uint32_t CRL; /*GPIO 端口配置低寄存器地址偏移: 0x00 */
7 uint32_t CRH; /*GPIO 端口配置高寄存器地址偏移: 0x04 */
8 uint32_t IDR; /*GPIO 数据输入寄存器地址偏移: 0x08 */
9 uint32_t ODR; /*GPIO 数据输出寄存器地址偏移: 0x0C */
10 uint32_t BSRR; /*GPIO 位设置/清除寄存器地址偏移: 0x10 */
11 uint32_t BRR; /*GPIO 端口位清除寄存器地址偏移: 0x14 */
12 uint16_t LCKR; /*GPIO 端口配置锁定寄存器地址偏移: 0x18 */
13 } GPIO_TypeDef;
- 使用GPIO_TypeDef结构体,其中的7个成员变量名就是对应的寄存器名称。
- 利用了C语言结构体内变量的存储空间是连续的,32位变量占4个字节,16位占2个字节
- 假如这个结构体首地址位0x40010C00,那么第二个成员变量就是+0x04
- 这样GPIO外设定义的寄存器地址就可以一一对应了,可以通过结构体的形式访问寄存器
1 GPIO_TypeDef * GPIOx; //定义一个GPIO_TypeDef 型结构体指针GPIOx
2 GPIOx = GPIOB_BASE; //把指针地址设置为宏GPIOB_BASE 地址
3 GPIOx->IDR = 0xFFFF;
4 GPIOx->ODR = 0xFFFF;
5
6
7 uint32_t temp;
8 temp = GPIOx->IDR; //读取GPIOB_IDR 寄存器的值到变量temp 中
- 先用 GPIO_TypeDef 类型定义一个结构体指针,并将其指向GPIOB_BASE
- 可以使用->ODR来读写寄存器。
1 /* 使用GPIO_TypeDef 把地址强制转换成指针*/
2 #define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)
3 #define GPIOB ((GPIO_TypeDef *) GPIOB_BASE)
4 #define GPIOC ((GPIO_TypeDef *) GPIOC_BASE)
5 #define GPIOD ((GPIO_TypeDef *) GPIOD_BASE)
6 #define GPIOE ((GPIO_TypeDef *) GPIOE_BASE)
7 #define GPIOF ((GPIO_TypeDef *) GPIOF_BASE)
8 #define GPIOG ((GPIO_TypeDef *) GPIOG_BASE)
9 #define GPIOH ((GPIO_TypeDef *) GPIOH_BASE)
10
11
12
13 /* 使用定义好的宏直接访问*/
14 /* 访问GPIOB 端口的寄存器*/
15 GPIOB->BSRR = 0xFFFF; //通过指针访问并修改GPIOB_BSRR 寄存器
16 GPIOB->CRL = 0xFFFF; //修改GPIOB_CRL 寄存器
17 GPIOB->ODR =0xFFFF; //修改GPIOB_ODR 寄存器
18
19 uint32_t temp;
20 temp = GPIOB->IDR; //读取GPIOB_IDR 寄存器的值到变量temp 中
21
22 /* 访问GPIOA 端口的寄存器*/
23 GPIOA->BSRR = 0xFFFF;
24 GPIOA->CRL = 0xFFFF;
25 GPIOA->ODR =0xFFFF;
26
27 uint32_t temp;
28 temp = GPIOA->IDR; //读取GPIOA_IDR 寄存器的值到变量temp 中
- 我们可以直接使用宏定义好各端口的首地址
- 使用时直接用该宏访问寄存器即可。
- 这部分的工作都已经由固件库帮我们完成了
- 寄存器操作
- 寄存器操作主要是修改寄存器的某几位,且要保持其他值不变
- 利用C语言的位操作
例:假设a变量代表寄存器,假设已有数值,需要将其清零,其他位保持不变文章来源地址https://www.toymoban.com/news/detail-812968.html
1 //定义一个变量a = 1001 1111 b (二进制数)
2 unsigned char a = 0x9f;
3
4 //对bit2 清零
5
6 a &= ~(1<<2);
7
8 //括号中的1 左移两位,(1<<2) 得二进制数:0000 0100 b
9 //按位取反,~(1<<2) 得1111 1011 b
10 //假如a 中原来的值为二进制数: a = 1001 1111 b
11 //所得的数与a 作”位与&”运算,a = (1001 1111 b)&(1111 1011 b),
12 //经过运算后,a 的值a=1001 1011 b
13 // a 的bit2 位被被零,而其它位不变。
1 //若把a 中的二进制位分成2 个一组
2 //即bit0、bit1 为第0 组,bit2、bit3 为第1 组,
3 // bit4、bit5 为第2 组,bit6、bit7 为第3 组
4 //要对第1 组的bit2、bit3 清零
5
6 a &= ~(3<<2*1);
7
8 //括号中的3 左移两位,(3<<2*1) 得二进制数:0000 1100 b
9 //按位取反,~(3<<2*1) 得1111 0011 b
10 //假如a 中原来的值为二进制数: a = 1001 1111 b
11 //所得的数与a 作”位与&”运算,a = (1001 1111 b)&(1111 0011 b),
12 //经过运算后,a 的值a=1001 0011 b
13 // a 的第1 组的bit2、bit3 被清零,而其它位不变。
14
15 //上述(~(3<<2*1)) 中的(1) 即为组编号; 如清零第3 组bit6、bit7 此处应为3
16 //括号中的(2) 为每组的位数,每组有2 个二进制位; 若分成4 个一组,此处即为4
17 //括号中的(3) 是组内所有位都为1 时的值; 若分成4 个一组,此处即为二进制数“1111 b”
18
19 //例如对第2 组bit4、bit5 清零
20 a &= ~(3<<2*2);
-
- 对变量的某几位进行赋值
- 在上边清零操作之后,可以方便的对某几位写入所需要的数值
1 //a = 1000 0011 b
2 //此时对清零后的第2 组bit4、bit5 设置成二进制数“01 b ”
3
4 a |= (1<<2*2);
5 //a = 1001 0011 b,成功设置了第2 组的值,其它组不变
-
- 对某个位进行取反操作,可以直接使用取反命令
1 //a = 1001 0011 b
2 //把bit6 取反,其它位不变
3
4 a ^=(1<<6);
5 //a = 1101 0011 b
到了这里,关于STM32单片机开发-01 STM32介绍的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!