零:STM32F103ZET6片上资源
其内部资源如下:
(1) 内核: 32 位 高性能 ARM Cortex-M3 处理器。 时钟: 高达 72M,实际还可以超频一点。 单周期乘法和硬件除法。
(2) IO 口: STM32F103ZET6: 144 引脚 112 个 IO, 大部分 IO 口都耐 5V(模拟通道除外), 支持调试: SWD 和 JTAG, SWD 只要 2 根数据线
(3) 存储器容量: 512K FLASH, 64K SRAM
(4) 时钟, 复位和电源管理:
①2.0~3.6V 电源和 IO 电压
②上电复位, 掉电复位和可编程的电压监控
③强大的时钟系统
-4~16M 的外部高速晶振-内部 8MHz 的高速 RC 振荡器
-内部 40KHz 低速 RC 振荡器, 看门狗时钟
-内部锁相环(PLL, 倍频) , 一般系统时钟都是外部或者内部高速时钟经过 PLL 倍频后得到
- 外部低速 32.768K 的晶振, 主要做 RTC 时钟源(5) 低功耗:
-睡眠, 停止和待机三种低功耗模式-可用电池为 RTC 和备份寄存器供电
(6) AD:
-3 个 12 位 AD(多达 21 个外部测量通道) -转换范围: 0-3.6V(参考电源电压)
-内部通道可以用于内部温度测量-内置参考电压
(7) DA:2 个 12 位 DA
(8) DMA:12 个 DMA 通道(7 通道 DMA1, 5 通道 DMA2) , 支持外设: 定时器,
ADC,DAC, SDIO,I2S,SPI,I2C,和 USART (9) 定时器: 多达 11 个定时器
-4 个通用定时器-2 个基本定时器-2 个高级定时器-1 个系统定时器-2 个看门狗定时器
(10) 通信接口: 多达 13 个通信接口
-2 个 I2C 接口-5 个串口
-3 个 SPI 接口-1 个 CAN2.0 -1 个 USB FS
一:存储器与寄存器
(1)存储器映射
存储器本身并不具有地址信息,它的地址是由芯片厂商或用户分配,给存储器分配地址的过程就叫做存储器映射,如果再重新分配一个地址叫做重映射。
ARM将4GB的存储空间平分成8个512MB的block,per block都有其特殊的用途,如下图所示
1.1block0
主要用于设计片内的Flash,STM32ZET6是512KB,将地址从低到高顺序依次是
0x0000 0000-0x0007 FFFF: 取决于 BOOT 引脚, 为 FLASH、 系统存储器、SRAM 的别名。
0x0008 0000-0x07FF FFFF: 预留。
0x0800 0000-0x0807 FFFF:片内 FLASH, 我们编写的程序就放在这一区域(512KB)。
0x0808 0000-0x1FFF EFFF: 预留。
0x1FFF F000-0x1FFF F7FF: 系统存储器, 里面存放的是 ST 出厂时烧写好的isp 自举程序, 用户无法改动。 使用串口下载的时候需要用到这部分程序。
ISP自举程序指的是在系统可编程(In-System Programming)技术中使用的一种特殊的启动程序。它允许用户直接在电路板上对空白或已编程的微控制器进行编程、擦除或更新,而无需将微控制器从电路板上取下。这一过程依赖于存储在微控制器内部ROM(系统存储器)中的一段自举程序,也称为bootloader。自举程序的主要任务是通过可用的串行外设(如USART、CAN、USB、I2C等)将应用程序下载到内部Flash存储器中。
自举程序通常由微控制器的制造商在生产线上写入,并且用户通常无法修改这部分程序。当微控制器配置为从系统存储器启动时,它将执行自举程序,该程序能够通过串行接口接收外部发送的程序代码,并将其写入微控制器的内部Flash中。不同的串行接口定义了相应的通信协议,这些协议包括兼容的命令集和数据传输序列。
在STM32微控制器中,自举程序存放在系统存储区,由ST公司在生产线上写入,用户不能修改。选择系统存储器启动模式时,微控制器就会进入系统存储区执行自举程序。自举程序主要用于通过串行接口重新编程Flash,支持的串行接口包括UART、I2C、SPI、CAN、USB等。自举程序中还包含了USART协议,用于在自举启动时与外部设备进行通信。自举程序上位机,即用于与自举程序通信的工具,通常是指FLASH编程工具,如STMFlashLoader和STM32CubeProgrammer。
0x1FFF F800-0x1FFF F80F: 选 项 字 节 , 用 于 配 置 读 写 保 护 、BOR 级别、 软件/硬件看门狗以及器件处于待机或停止模式下的复位。 当芯片不小心被锁住之后, 我们可以从 RAM 里面启动来修改这部分相应的寄存器位。
0x1FFF F810-0x1FFF FFFF: 预留。
1.2Block1
Block1用于设计片内的SRAM, 我们使用的 STM32F103ZET6 的 SRAM是64KB。
0x2000 0000-0x2000 FFFF: SRAM, 容量为 64KB。
0x2001 0000-0x3FFF FFFF: 预留。
1.3Block2
Block2用于设计片内外设,根据外设总线速度的不同,被分为AHB与APB两部分,APB又分为APB1与APB2总线。
0x4000 0000-0x4000 77FF: APB1 总线外设。0x4000 7800-0x4000 FFFF: 预留。
0x4001 0000-0x4001 3FFF: APB2 总线外设。0x4001 4000-0x4001 7FFF: 预留。
0x4001 8000-0x4002 33FF: AHB 总线外设。0x4002 4400-0x5FFF FFFF: 预留。
在 Block3/4/5 中还包含了 FSMC 扩展区域, 这 3 个块可用于扩展外部存储器,比如 SRAM, NORFLASH 和 NANDFLASH 等。
(2)什么是寄存器和寄存器映射
由于Cortex-M3内核是32位,所以存储器内部都是以四个字节为一个单元,每个单元对应不同的功能,同时每个单元有其对应的地址,想要操作这些单元也是通过指针访问其地址。
32外设多且复杂,通过写一大串对应存储单元地址来进行操控是麻烦且易出错,故通过对每个单元的功能进行总结,取一个别名,这个别名就是常规概念上的寄存器。
那么什么是寄存器映射?给已分配好地址且有特定功能的内存单元取别名的过程就叫寄存器映射。
比方说我们找到 0x4001 1010 这个单元地址,通过查阅相关资料了解到此单元具有 GPIOC 端口置位/复位功能。 因此为了更好区分此单元的功能和方便后续的程序开发,可以给这个单元取一个别名 GPIOC_BSRR, 那么这个 GPIOC_BSRR 就是寄存器, 并且这个寄存器地址就是 0x4001 1010。 这个过程就是寄存器映射。
(3)如何访问STM32寄存器内容
寄存器就是一些有特定功能的内存单元,所以访问STM32寄存器也就是操作STM32的内存单元,根据C语言指针的特点可以通过指针来操作STM32的内存单元。
3.1STM32外设地址映射
片上外设总共分为四条总线,根据外设速度的不同,不同总线挂载着不同的外设,APB1低速,APB2与AHB高速。相应总线的最低地址称为该总线的基地址,总线基地址也就是挂载在该总线上的首个外设地址。
APB1总线的地址最低,因此片上外设从这个地址开始,也被称为外设基地址。
以Block2为例,分为四大块,每块都有一个起始地址,也就是基地址,然后下一块的基地址就会与前一块地址出现偏差,这个差值就是偏移量,即相对于基地址的偏移量。
3.2查阅外设寄存器手册
以GPIOC_BSRR寄存器为例查看《STM32F1XX中文参考手册》
4代表寄存器名称GPIOx_BSRR 内的 x 表示的是 STM32GPIO 端口,范围A-E也就是说从GPIOA至GPIOE都存在这个寄存器。
5表示相对于GPIOx地址的偏移量,如果以GPIOC外设为例,基地址是0x4001 1000,那么本寄存器地址=0x4001 1000+0x10=0x4001 1010。
6是寄存器位编号,7代表其相应位的权限,W只写、R只读、RW可读可写,此寄存器由图可知权限为只写,无法保证读取到它真正的内容;而有些寄存器位权限为只读,一般用来表示32外设的某种工作状态。由32硬件自动更改。
8是寄存器说明中最核心的部分,它详细介绍了寄存器每一个位的功能,如图为例本寄存器有两种寄存器位,分别是BRy与BSy,其中y的数值是管教号,0-15.如 BR0、 BS0 用于控制 GPIOx 的第 0 个引脚,若 x 表示 GPIOC, 那就是控制 GPIOC 的第 0 引脚,而 BR1、 BS1 就是控制 GPIOC 第 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 是相反的操作。
3.3使用C语言封装寄存器
直接操作寄存器地址是非常麻烦的,比如使GPIOC的Pin0管教输出低电平,则首先需要知道GPIOC端口外设挂载在哪个总线,然后根据总线基地址与外设偏移地址的+各个寄存器的偏移量得出寄存器的基地址,再对各个位进行操作,十分繁琐。
总线和外设基地址封装
可以利用C语言的宏定义(在C语言中,宏定义(Macro Definition)是一种预处理指令,它允许程序员定义一些简短的标识符来代表较长的代码片段、值或表达式。宏定义在编译之前由预处理器处理,因此它们不占用内存空间,且在编译时被替换。),使难懂的地址变为我们易于理解的
1) 定义外设的基地址, 这个地址也是 Block2 的基地址。
2) 定义 APB2 总线基地址, 因为 Block2 的第一个总线是 APB1, 而 APB2 总线地址只需要加上对应的地址偏移量即可。
3) 定义 GPIO 外设基地址, 因为 GPIOC 是挂接在 APB2 总线上的, 所以找到对应的端口地址偏移量即可知道 GPIOC 端口基地址。
4) 定义 GPIO 外设寄存器基地址, 这里以 GPIOC 端口为例, 因为 GPIOC_CRL是 GPIOC 外设的第一个寄存器, 所以基地址就是 GPIOC 地址, 其他寄存器地址只需要在 GPIOC 基地址上加上相应的偏移量即可。
那么该如何使用C指针来操作读写?以GPIOCPin0输出电平为例:
//控制 GPIOC 第 0 管脚输出一个低电平
GPIOC_BSRR = (0x01<<(16+0));
//控制 GPIOC 第 0 管脚输出一个高电平
GPIOC_BSRR = (0x01<<0);
我们知道 GPIOC_BSRR 的值是这个寄存器的地址, 但是编译器不知道它是地址, 而是把它当做立即数, 所以我们必须要强制转换为(unsigned int *) 指针类型才可以对其操作, 这一点特别要注意。 然后再在前面加上一个“*” 作取指针操作, 表示对该地址内内容进行写, 读操作也同样使用“*” 取指针操作。
寄存器封装
STM32 的 GPIO 多,根据 GPIO 寄存器的特点,GPIOx都拥有一组功能相同的寄存器, 如 GPIOA_ODR/GPIOB_ODR/GPIOC_ODR 等等, 它们只是地址不一样。为了更方便地访问寄存器, 我们引入 C 语言中的结构体对寄存器进行封装, 具体代码如下:
typedef unsigned int uint32_t; /*无符号 32 位变量*/
typedef unsigned short int uint16_t; /*无符号 16 位变量*/
/* GPIO 寄存器列表 */
typedef struct {
uint32_t CRL; /*GPIO 端口配置低寄存器 地址偏移: 0x00 */
uint32_t CRH; /*GPIO 端口配置高寄存器 地址偏移: 0x04 */
uint32_t IDR; /*GPIO 数据输入寄存器 地址偏移: 0x08 */
uint32_t ODR; /*GPIO 数据输出寄存器 地址偏移: 0x0C */
uint32_t BSRR; /*GPIO 位设置/清除寄存器 地址偏移: 0x10 */
uint32_t BRR; /*GPIO 端口位清除寄存器 地址偏移: 0x14 */
uint16_t LCKR; /*GPIO 端口配置锁定寄存器 地址偏移: 0x18 */ }GPIO_TypeDef;
这段代码使用typedef关键字声明了GPIO_TypeDef 的结构体类型,结构体内有7各成员变量,变量名对应寄存器的名称,C语言语法规定结构体内变量的存储空间是连续的,32位变量占用4个字节空间、16位占用2个字节空间。
假如这个结构体的首地址为0x4001 1000(这也是第一个成员变量 CRL的地址),那么结构体中第二个成员变量 CRH 的地址即为 0x4001 1000 +0x04,加上的这个0x04正是代表CRH所占用的4个字节地址的偏移量,其余成员变量与此类似。
这样的地址偏移与 STM32 GPIO 外设定义的寄存器地址偏移一一对应, 只要给结构体设置好首地址, 就能把结构体内成员的地址确定下来, 然后就能以结构体的形式访问寄存器了, 比如将 GPIOC0 输出低电平, 具体代码如下:
GPIO_TypeDef * GPIOx; //定义一个 GPIO_TypeDef 型结构体指针 GPIOx
GPIOx = GPIOC_BASE; //把指针地址设置为宏 GPIOC_BASE 地址
GPIOx->BSRR =(1<<(16+0)); //通过指针访问并修改 GPIOC_BSRR 寄存器
这段代码先用 GPIO_TypeDef 类型定义一个结构体指针 GPIOx,并让指针指向 GPIOC 基地址 GPIOC_BASE,地址确定下来,然后根据 C 语言访问结构体的内容,用 GPIOx->BSRR 写寄存器。为了操作更简便灵活,我们直接使用宏定义好GPIO_TypeDef 类型的指针,而且指针指向各个 GPIO 端口的首地址,使用时我们直接用该宏访问寄存器即可。具体代码如下:
二:STM32中断系统
(1)中断
当 CPU 执行程序时,由于发生了某种随机的事件(外部或内部), 引起 CPU 暂时中断正在运行的程序,转去执行一段特殊的服务程序(中断服务子程序或中断处理程序), 以处理该事件, 该事件处理完后又返回被中断的程序继续执行, 这一过程就称为中断。
引发中断的称为中断源。 有些中断还能够被其他高优先级的中断所中断, 那么这种情况又叫做中断的嵌套。
Crotex-M3 内核支持 256 个中断, 其中包含了 16 个内核中断和 240 个外部中断。 但 STM32 并没有使用 M3 内核的全部东西, 而是只用了它的一部分。STM32F10x 芯片有 84 个中断通道, 包括 16 个内核中断和 68 个可屏蔽中断,对于STM32F103系列芯片只有60个可屏蔽中断, 在 STM32F107 系列才有 68 个。除了个别异常的优先级被定死外, 其它异常的优先级都是可编程的。 这些中断通道已按照不同优先级顺序固定分配给相应的外部设备。 从 STM32F10x 中文参考手册的中断向量表可以知道具体分配到那些外设,参考《STM32F10x 中文参考手册》 -9 中断和事件章节内容。
(2)NVIC(Nested Vectored Interrupt Controller)
NVIC 中文意思就是嵌套向量中断控制器,nest筑巢、嵌入 vector向量 。
它属于 M3 内核的一个外设, 控制着芯片的中断相关功能。由于 ARM 给 NVIC 预留了非常多的功能, 但对于使用 M3 内核设计芯片的公司可能就不需要这么多功能, 于是就需要在 NVIC 上裁剪。 ST 公司的 STM32F103 芯片内部中断数量就是 NVIC 裁剪后的结果。
上面说到 NVIC 控制着芯片的中断相关功能, 那么肯定有很多对应的寄存器,在固件库 core_cm3.h 文件内定义了一个 NVIC 结构体, 里面定义了相关寄存器,如下:
typedef struct {
__IO uint32_t ISER[8]; | //中断使能寄存器 | |
uint32_t RESERVED0[24]; | ||
__IO uint32_t ICER[8]; | //中断清除寄存器 | |
uint32_t RSERVED1[24]; | ||
__IO uint32_t ISPR[8]; | //中断使能悬起寄存器 | |
uint32_t RESERVED2[24]; | ||
__IO uint32_t ICPR[8]; | //中断清除悬起寄存器 | |
uint32_t RESERVED3[24]; | ||
__IO uint32_t IABR[8]; | //中断有效位寄存器 | |
uint32_t RESERVED4[56]; | ||
__IO uint8_t | IP[240]; | //中断优先级寄存器 |
uint32_t RESERVED5[644];uint32_t STIR; | //软件触发中断寄存器 | |
__O | ||
} | NVIC_Type; |
在配置中断时, 我们通常使用的只有 ISER、 ICER 和 IP 这三个寄存器, ISER 是中断使能寄存器, ICER 是中断清除寄存器, IP 是中断优先级寄存器。
在固件库 core_cm3.h 文件后面, 还提供了一些对 NVIC 操作的库函数, 这些函数都是遵循 CMSIS 标准, 所以只要是基于 Cortex-M3 内核的芯片都可以用这些函数来操作 NVIC。
2.1中断优先级
前面说了 STM32F103 芯片支持 60 个可屏蔽中断通道, 每个中断通道都具备自己的中断优先级控制字节(8 位, 理论上每个外部中断优先级可以设置为0-255, 数值越小, 优先级越高。 但是 STM32F103 中只使用 4 位, 高 4 位有效),用于表达优先级的高 4 位又被分组成抢占式优先级和响应优先级, 通常也把响应优先级称为“亚优先级” 或“副优先级” , 每个中断源都需要被指定这两种优先级。
高抢占式优先级的中断事件会打断当前的主程序或者中断程序运行, 俗称中断嵌套。 在抢占式优先级相同的情况下, 高响应优先级的中断优先被响应。
当两个中断源的抢占式优先级相同时, 这两个中断将没有嵌套关系, 当一个中断到来后, 如果正在处理另一个中断, 这个后到来的中断就要等到前一个中断处理完之后才能被处理。 如果这两个中断同时到达, 则中断控制器根据他们的响应优先级高低来决定先处理哪一个; 如果他们的抢占式优先级和响应优先级都相等, 则根据他们在中断表中的排位顺序决定先处理那一个, 越靠前的先执行。
第 0 组: 所有 4 位用于指定响应优先级
第 1 组: 最高 1 位用于指定抢占式优先级, 最低 3 位用于指定响应优先级第
2 组: 最高 2 位用于指定抢占式优先级, 最低 2 位用于指定响应优先级
第 3 组: 最高 3 位用于指定抢占式优先级, 最低 1 位用于指定响应优先级
第 4 组: 所有 4 位用于指定抢占式优先级
设置优先级分组可调用库函数 NVIC_PriorityGroupConfig()实现, 有关NVIC 中断相关的库函数都在库文件 misc.c 和 misc.h 中, 所以当使用到中断时 , 一 定 要 记 得 把 misc.c 和 misc.h 添 加 到 工 程 组 中 。
(3)中断配置
使用中断通常都需经过这几步:
1) 使能外设某个中断, 这个具体是由外设相关中断使能位来控制, 比如定时器有溢出中断, 这个可由定时器的控制寄存器中相应中断使能位来控制。
2) 设置中断优先级分组, 初始化 NVIC_InitTypeDef 结构体, 设置抢占优先级和响应优先级, 使能中断请求。
NVIC_InitTypeDef 结构体如下:
typedef struct {
uint8_t NVIC_IRQChannel; uint8_t NVIC_IRQChannelPreemptionPriority; uint8_t NVIC_IRQChannelSubPriority; FunctionalState NVIC_IRQChannelCmd; } NVIC_InitTypeDef; | //中断源//抢占优先级//响应优先级//中断使能或失能 |
1.NVIC_IRQChannel: 中断源的设置, 不同的外设中断, 中断源不一样, 自然名字也不一样, 所以名字不能写错, 否则不会进入中断。 中断源放在stm32f10x.h 文件的 IRQn_Type 结构体内
2.NVIC_IRQChannelPreemptionPriority: 抢占优先级, 具体的值要根据优先级分组来确定, 可以参考前面中断优先级分组内容。
3.NVIC_IRQChannelSubPriority: 响应优先级, 具体的值要根据优先级分组来确定, 可以参考前面中断优先级分组内容。
4.NVIC_IRQChannelCmd: 中断使能/失能设置, 使能配置为 ENABLE, 失能配置为 DISABLE。
3) 编写中断服务函数
配置好中断后如果有触发, 即会进入中断服务函数, 那么中断服务函数也有固定的函数名, 可以在 startup_stm32f10x_hd.s 启动文件查看, 启动文件提供的只是一个中断服务函数名, 具体实现什么功能还需要我们自己编写, 可以将中断服务函数放在 stm32f10x_it.c 文件内, 也可以放在自己的应用程序中。 通常我们把中断函数放在应用程序中。 这里提醒一下大家, 不要任意修改中断服务函数名, 因为启动文件内中断服务函数名已经固定, 如果要修改, 你还必须在启动文件内把原中断函数修改。
STM32F103 中指定中断优先级的寄存器位有 4 位, 这 4 位的分组方式如图
NVIC对中断优先级进行排列
NVIC的中断优先级由优先级寄存器的4位(0~15)决定,这4位可以进行切分,分为高n位的抢占优先级和低4-n位的响应优先级;
抢占优先级高的可以中断嵌套,响应优先级高的可以优先排队,抢占优先级和响应优先级均相同的按中断号排队.
三:EXTI(Extern Interrupt)外部中断
(1)定义
STM32F10x 外部中断/事件控制器(EXTI) 包含多达 20 个用于产生事件/中断请求的边沿检测器。 EXTI 的每根输入线都可单独进行配置, 以选择类型(中断或事件) 和相应的触发事件(上升沿触发、 下降沿触发或边沿触发) , 还可独立地被屏蔽。
(2)EXTI结构框图
EXTI 框图包含了 EXTI 最核心内容, 掌握了此框图, 对 EXTI 就有一个全局的把握, 在编程的时候思路就非常清晰。 EXTI 框图如图所示:
有很多信号线上都有标号 9 样的“20” 字样, 这个表示在控制器内部类似的信号线路有 20 个, 这与 STM32F10x 的 EXTI 总共有20 个中断/事件线是吻合的。 因此我们只需要理解其中一个的原理, 其他的 19个线路原理都是一样的。
EXTI 分为两大部分功能, 一个产生中断, 另一个产生事件, 这两个功能从硬件上就有所差别, 这个在框图中也有体现。 从图中标号 3 的位置处就分出了两条线路, 一条是 3-4-5 用于产生中断, 另一条是 3-6-7-8 用于产生事件。
(1) 首先看下产生中断的这条线路(1-2-3-4-5)
1.标号 1 为输入线, EXTI 控制器有 20 个中断/事件输入线, 这些输入线可以通过寄存器设置为任意一个 GPIO, 也可以是一些外设的事件, 输入线一般是存在电平变化的信号。
2.边沿检测电路, EXTI 可以对触发方式进行选择, 通过上升沿触发选择寄存器和下降沿触发选择寄存器对应位的设置来控制信号触发。 边沿检测电路以输入线作为信号输入端, 如果检测到有边沿跳变就输出有效信号 1 给红色框 3 电路, 否则输出无效信号 0。 而上升沿和下降沿触发选择这两个寄存器可以控制需要检测哪些类型的电平跳变过程, 可以是只有上升沿触发、 只有下降沿触发或者上升沿和下降沿都触发。
3.一个或门电路, 一端输入信号线由标号 2 提供, 一端由软件中断事件寄存器提供, 只要有一个为有效信号 1, 标号 3 电路则输出有效信号 1, 否则为无效信号 0。 软件中断事件寄存器允许我们使用软件来启动中断/事件线,这个在某些地方非常有用。
4.一个与门电路, 一端输入信号线由标号 3 电路输出提供, 一端由中断屏蔽寄存器提供, 只有当两者都为有效信号 1, 标号 4 电路才会输出有效信号 1, 否则输出无效。 这样我们就可以简单的控制中断屏蔽寄存器来实现是否产生中断的目的。 当我们把中断屏蔽寄存器设置为 1 时, 标号 4 输出就取决于标号3 电路的输出。 标号 3 电路输出的信号会被保存到挂起寄存器内, 如果确定标号3 电路输出为 1 就会把挂起寄存器对应位置 1。
5.将挂起寄存器内容输入到 NVIC 内, 从而实现系统中断事件的控制。
( 2) 最后我们再来看下产生事件这条线路( 1-2-3-6-7-8) , 前面 1-2-3都是一样的, 只是在 3 的输出后产生分歧。
6.其实就是一个与门电路, 一端来至标号 3 电路的输出信号, 一端来至事件屏蔽寄存器, 只有两者都为有效电平 1, 标号 6 输出才有效。 当事件屏蔽寄存器设置为 0 时, 不管标号 3 电路输出为 1 还是 0, 标号 6 电路输出均为 0。 当事件屏蔽寄存器设置为 1 时, 标号 6 电路输出取决于标号 3 电路输出, 这样就可以简单的控制事件屏蔽寄存器来实现是否产生事件的目的。
7.脉冲发生器电路, 其输入端只与标号 6 电路输出有关, 标号 6 输出有效,脉冲发生器才会输出一个脉冲信号。
8.脉冲信号, 由标号 7 脉冲发生器产生, 是事件线路的终端, 此脉冲信号可供其他外设电路使用, 比如定时器、 ADC 等。 这样的脉冲信号通常用来触发定时器、 ADC 等开始转换。
从上面 EXTI 框图可以看出, 中断线路最终会输入到 NVIC 控制器中, 从而会运行中断服务函数, 实现中断内功能, 这个是软件级的。 而事件线路最后产生的脉冲信号会流向其他的外设电路, 是硬件级的。 在 EXTI 框图最顶端可以看到,其外设接口时钟是由 PCLK2, 即 APB2 提供, 所以在后面使能 EXTI 时钟的时候一定要注意。
(3)外部中断/事件线映射
STM32F10x 的 EXTI 具有 20 个中断/事件线, 对应连接的外设说明如下表
从上表可知, STM32F10x 的 EXTI 供外部 IO 口使用的中断线有 16 根, 但是我们使用的 STM32F103 芯片却远远不止 16 个 IO 口, 那么 STM32F103 芯片怎么解决这个问题的呢? 因为 STM32F103 芯片每个 GPIO 端口均有 16 个管脚, 因此把每个 端 口 的 16 个 IO 对 应 那 16 根 中 断 线 EXTI0-EXTI15 。 比 如 : GPIOx.0-GPIOx.15(x=A,B,C,D,E, F,G)分别对应中断线 EXTI0-EXTI15, 这样一来每个中断线就对应了最多 7 个 IO 口, 比如: GPIOA.0、 GPIOB.0、 GPIOC.0、GPIOD.0、 GPIOE.0、 GPIOF.0、 GPIOG.0。 但是中断线每次只能连接一个在 IO 口上, 这样就需要通过 AFIO 的外部中断配置寄存器 1 的 EXTIx[3:0]位来决定对应的中断线映射到哪个 GPIO 端口上, 对于中断线映射到 GPIO 端口上的配置函数在stm32f10x_gpio.c 和 stm32f10x_gpio.h 中
2.3.1AFIO
AFIO主要用于引脚复用功能的选择和重定义
(4)EXTI配置步骤
(1) 使能 IO 口时钟, 配置 IO 口模式为输入
(2) 开启 AFIO 时钟, 设置 IO 口与中断线的映射关系
接下来我们需要将 GPIO 映射到对应的中断线上, 只要使用到外部中断, 就必须先使能 AFIO 时钟, 前面已经说了它是挂接在 APB2 总线上的, 所以使能 AFIO时钟库函数为:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
然后, 我们就可以把 GPIO 映射到对应的中断线上, 配置 GPIO 与中断线映射的库函数如下:
void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);
比如我们将中断线 0 映射到 GPIOA 端口, 那么就需要如下配置:
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);
这时候 GPIOA.0 管脚就与中断线 0 连接起来, 其他端口中断线的映射类似。
(3) 配置中断分组(NVIC) , 使能中断
我们知道 EXTI 产生中断线路最终是流向 NVIC 控制器的, 由 NVIC 调用中断服务函数, 因此我们需要对 NVIC 进行配置。
(4) 初始化 EXTI, 选择触发方式
配置好 NVIC 后, 我们还需要对中断线上的中断初始化, EXTI 初始化库函数如下:
void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct);
函 数 形 参 是 有 一 个 结 构 体 EXTI_InitTypeDef 类 型 的 指 针 变 量 , EXTI_InitTypeDef 结构体成员变量如下:
typedef struct {
uint32_t EXTI_Line; EXTIMode_TypeDef EXTI_Mode; | //中断/事件线//EXTI 模式 |
EXTITrigger_TypeDef EXTI_Trigger; //EXTI 触发方式 | |
FunctionalState EXTI_LineCmd; }EXTI_InitTypeDef;下面就来介绍下结构体内成员的意义: | //中断线使能或失能 |
EXTI_Line: EXTI 中断/事件线选择, 可配置参数为 EXTI0-EXTI20, 可参考上表 18.1.2。
EXTI_Mode: EXTI 模式选择, 可以配置为中断模式 EXTI_Mode_Interrupt 和事件模式 EXTI_Mode_Event。
EXTI_Trigger : 触 发 方 式 选 择 , 可 以 配 置 为 上 升 沿 触 发EXTI_Trigger_Rising、 下降沿触发 EXTI_Trigger_Falling、 上升沿和下降沿触发 EXTI_Trigger_Rising_Falling。
EXTI_LineCmd: 中断线使能或者失能, 配置 ENABLE 为使能, DISABLE 为失能, 我们这里要使用外部中断, 所以需使能。
EXTI3_IRQHandler
EXTI4_IRQHandler
EXTI9_5_IRQHandler
EXTI15_10_IRQHandler
从函数名可以看到, 前面 0-4 个中断线都是独立的函数, 中断线 5-9 共用一个 函 数 EXTI9_5_IRQHandler , 中 断 线 10-15 也 共 用 一 个 函 数EXTI15_10_IRQHandler, 所以要在编写对应中断服务函数时要注意。
(5) 编写 EXTI 中断服务函数
所有中断函数都在 STM32F1 启动文件中, 不知道中断函数名的可以打开启动文件查找。 这里我们使用到的是外部中断, 其函数名如下:
EXTI0_IRQHandler
EXTI1_IRQHandler
EXTI2_IRQHandler
四:TIM(timer)定时器
STM32F1 的定时器功能非常强大, 其包含 2 个基本定时器(TIM6、 TIM7) 、 4 个通用定时器(TIM2-TIM5) 和 2 个高级定时器(TIM1、 TIM8) , 共计 8 个, 与 51单片机定时器的功能和数量相比优势非常明显。
(1)定时器介绍
基本定时器的功能最为简单, 类似于 51 单片机内定时器。 通用定时器是在基本定时器的基础上扩展而来, 增加了输入捕获与输出比较等功能。 高级定时器又是在通用定时器基础上扩展而来, 增加了可编程死区互补输出、 重复计数器、 带刹车(断路)功能, 这些功能主要针对工业电机控制方面。
如果学会了使用通用定时器, 那么你也学会了基本定时器用法, 至于高级定时器, 只是比通用定时器多了那几个功能, 你只要把对应的那几个功能对照中文参考手册看下即可
(2) 通用定时器
STM32F1 的通用定时器包含一个 16 位自动重载计数器(CNT) , 该计数器由可编程预分频器(PSC) 驱动。 STM32F1 的通用定时器可用于多种用途, 包括测量输入信号的脉冲宽度(输入捕获)或者生成输出波形(输出比较和 PWM)等。使用定时器预分频器和 RCC 时钟控制器预分频器, 脉冲长度和波形周期可以在几个微秒到几个毫秒间调整。 STM32F1 的每个通用定时器都是完全独立的, 没有互相共享的任何资源。
STM32F1 的通用定时器 TIMx (TIM2-TIM5 )具有如下功能:
(1) 16 位向上、 向下、 向上/向下自动装载计数器(TIMx_CNT) 。
(2) 16 位可编程(可以实时修改)预分频器(TIMx_PSC), 计数器时钟频率的分频系数为 1~65535 之间的任意数值。
(3) 4 个独立通道(TIMx_CH1-4) , 这些通道可以用来作为:
A. 输入捕获 B. 输出比较
C. PWM 生成(边缘或中间对齐模式) D. 单脉冲模式输出
(4) 可使用外部信号(TIMx_ETR) 控制定时器, 且可实现多个定时器互连(可以用 1 个定时器控制另外一个定时器) 的同步电路。
(5) 发生如下事件时产生中断/DMA 请求:
A. 更新: 计数器向上溢出/向下溢出, 计数器初始化(通过软件或者内部/外部触发)
B. 触发事件(计数器启动、 停止、 初始化或者由内部/外部触发计数) C. 输入捕获
D. 输出比较
(6) 支持针对定位的增量(正交)编码器和霍尔传感器电路
(7) 触发输入作为外部时钟或者按周期的电流管理
(3)通用定时器结构框图
在通用定时器结构框图中我们可以看到有两种奇怪的箭头, 它们的意义如下
我们把通用定时器结构框图分成 5 个子模块, 按照顺序依次进行简单介绍。
(1) 标号 1: 时钟源
通用定时器的时钟来源有 4 种可选:
A.内部时钟(CK_INT)
B.外部时钟模式 1: 外部输入引脚 TIx(x=1,2,3,4)
C.外部时钟模式 2: 外部触发输入 ETR
D.内部触发输入(ITRx(x=0,1,2,3) )
通用定时器时钟来源这么多, 具体选择哪个可以通过 TIMx_SMCR 寄存器的相关位来设置, 定时器相关寄存器的介绍可以参考《STM32F10x 中文参考手册》-通用定时器-寄存器章节详细了解。 这里的 CK_INT 时钟是从 APB1 倍频得来的, 除非 APB1 的时钟分频数设置为 1(一般都不会是 1) , 否则通用定时器 TIMx的时钟是 APB1 时钟的 2 倍, 当 APB1 的时钟不分频的时候, 通用定时器 TIMx的时钟就等于 APB1 的时钟。 这里还要注意的就是高级定时器的时钟不是来自APB1, 而是来自 APB2, 这个在库文件 stm32f10x_rcc.h 也可以查找到, 如下:
通常我们都是将内部时钟(CK_INT) 作为通用定时器的时钟来源, 而且通用定时器的时钟是 APB1 时钟的 2 倍, 即 APB1 的时钟分频数不为 1。 所以通用定时器的时钟频率是 72MHz。定时器的时钟是 APB1 时钟的 2 倍, 即 APB1 的时钟分频数不为 1。 所以通用定时器的时钟频率是 72MHz。
(2) 标号 2: 控制器
通用定时器控制器部分包括触发控制器、 从模式控制器以及编码器接口。 触发控制器用来针对片内外设输出触发信号, 比如为其它定时器提供时钟和触发DAC/ADC 转换。 从模式控制器可以控制计数器复位、 启动、 递增/递减、 计数。编码器接口专门针对编码器计数而设计。
(3) 标号 3: 时基单元
通用定时器时基单元包括 3 个寄存器, 分别是计数器寄存器(TIMx_CNT)、 预分频器寄存器(TIMx_PSC)、 自动重载寄存器(TIMx_ARR)。 高级定时器中还有一个重复计数寄存器(TIMx_RCR) , 通用和基本定时器是没有的。 通用定时器这三个寄存器都是 16 位有效。 而高级定时器的 TIMx_RCR 寄存器是 8 位有效。
在这个时基单元中, 有个预分频器寄存器(TIMx_PSC), 用于对计数器时钟频率进行分频, 通过寄存器内的相应位设置, 分频系数值可在 1 到 65536 之间。由于从模式控制寄存器具有缓冲功能, 因此预分频器可实现实时更改, 而新的预分频比将在下一更新事件发生时被采用。
在时基单元中, 还有个计数寄存器(TIMx_CNT), 通用定时器计数方式有向上计数、 向下计数、 向上向下计数(中心对齐计数) 。 下面分别来介绍下这几种计数方式:
1.向上计数
在向上(递增) 计数模式下, 计数器从 0 开始计数, 每来一个 CK_CNT 脉冲计数器就加 1, 直到等于自动重载值(TIMx_ARR 寄存器的内容) , 然后重新从 0开始计数并生成计数器上溢事件。 每次发生计数器上溢时会生成更新事件(UEV),或将 TIMx_EGR 寄存器中的 UG 位置 1(通过软件或使用从模式控制器) 也可以生成更新事件。 通过软件将 TIMx_CR1 寄存器中的 UDIS 位置 1 可禁止 UEV 事件。 这可避免向预装载寄存器写入新值时更新影子寄存器。 在 UDIS 位写入 0 之前不会产生任何更新事件。 不过, 计数器和预分频器计数器都会重新从 0 开始计数(而预分频比保持不变) 。 此外, 如果设置 TIMx_CR1 寄存器中相应的中断位置 1, 也会产生中断事件。
2.向下计数
在向下(递减) 计数模式下, 计数器从自动重载值( TIMx_ARR 寄存器的内容)开始递减计数到 0, 然后重新从自动重载值开始计数并生成计数器下溢事件。每次发生计数器下溢时会生成更新事件, 或将 TIMx_EGR 寄存器中的 UG 位置 1 (通过软件或使用从模式控制器) 也可以生成更新事件。 通过软件将 TIMx_CR1寄存器中的 UDIS 位置 1 可禁止 UEV 更新事件。 这可避免向预装载寄存器写入新值时更新影子寄存器。 在 UDIS 位写入 0 之前不会产生任何更新事件。 不过,计数器会重新从当前自动重载值开始计数, 而预分频器计数器则重新从 0 开始计数(但预分频比保持不变) 。 此外, 如果设置 TIMx_CR1 寄存器中相应的中断位置 1, 也会产生中断事件。
3.向上向下计数(中心对齐计数)
在中心对齐模式下, 计数器从 0 开始计数到自动重载值( TIMx_ARR 寄存器的内容) -1, 生成计数器上溢事件; 然后从自动重载值开始向下计数到 1 并生成计数器下溢事件。 之后从 0 开始重新计数, 如此循环执行。 每次发生计数器上溢和下溢事件都会生成更新事件。
在时基单元中, 还有个自动重载寄存器(TIMx_ARR), 该寄存器是用来放与CNT 计数器比较的值。 自动重载寄存器(TIMx_ARR)的控制受 TIMx_CR1 寄存器中ARPE 位决定, 当 ARPE=0 时, 自动重载寄存器(TIMx_ARR)不进行缓冲, 寄存器内容直接传送到影子寄存器。 当 APRE=1 时, 在每一次更新事件( UEV) 时, 才把预装载寄存器(ARR) 的内容传送到影子寄存器。
(4) 标号 4: 输入捕获
输入捕获可以对输入的信号的上升沿, 下降沿或者双边沿进行捕获, 通常用于测量输入信号的脉宽、 测量 PWM 输入信号的频率及占空比。
在输入捕获模式下, 当相应的 ICx 信号检测到跳变沿后, 将使用捕获/比较寄存器(TIMx_CCRx)来锁存计数器的值。 发生捕获事件时, 会将相应的 CCxIF 标志( TIMx_SR 寄存器) 置 1, 并可发送中断或 DMA 请求(如果已使能) 。 如果发生捕获事件时 CCxIF 标志已处于高位, 则会将重复捕获标志 CCxOF( TIMx_SR寄存器) 置 1。 可通过软件向 CCxIF 写入 0 来给 CCxIF 清零, 或读取存储在TIMx_CCRx 寄存器中的已捕获数据。 向 CCxOF 写入 0 后会将其清零。
19.3 硬件设计
19.4 软件设计
19.5 实验现象
定时器可以对输入的时钟进行计数,并在计数值达到设定值时触发中断。
1 Hz = 1 s,1 MHz = 秒=1us。
定时器有16位计数器、预分频器、自动重装寄存器(时基单元),在72MHz计数时钟下可以实现最大59.65s的定时
TIM仅具备基本的定时中断功能,而且还包含内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等多种功能根据复杂度和应用场景分为了高级定时器、通用定时器、基本定时器三种类型。
STM32F103ZET6定时器资源有?
(1)基本定时器
写1二分频,写二三分频,PSC16位预分频器;
CNT对PSC的技术时钟进行计数,每来一个上升沿+1,16位 0-65535;
存储目标值寄存器=自动重装寄存器16位,UI又叫做更新中断,然后去往NVIC,U是事件
(2)通用定时器
基本定时器仅支持向上计数,通用定时器、高级定时器支持向上、向下、中央对齐这三种模式;
对于基本定时器,只能选择内部时钟,即系统时钟72MHz。而对于通用计时器,可以使用很多外部时钟:
TIMX_ETR(可以通过查找引脚定义确定位置,假设为PA0,则可以通过给PA0接上一个外部方波时钟),再通过配置内部的极性选择、边沿检测、预分频电路,再配置输入滤波电路,通过对外部时钟进一步的整型
TRGI外部时钟的又一输入(其他功能暂且不谈),其中包括ETRF、ITR、CH1引脚、CH2引脚。
ITR0-ITR3的作用是实现定时器的级联
2.1定时器中断基本结构
高级定时器还会多一个重复计数器
2.1.1预分频器时序
CK_CNT(计数器时钟频率)=TCLK(定时器输入时钟频率)/PSC+1
一个计数周期的工作流程:
在一分频下,也就是没有打开TCLK计数使能,计数器时钟与之相同,打开后设置二分频,频率变为TCLK的一半;当计数值增加到与ARR重装值相等时,至下一个时钟来临,计数器清零,并产生一个更新事件。
预分频控制寄存器是供设置预分频的作用,但实际随意更改频率可能会引起负面影响(在一个计数周期未结束内),故又另设置预分频缓冲器(影子计数器):只有等一个计数周期结束后才会导入新的预分频值。
预分频计数器实际也是也是通过计数来分频。
2.1.2计数器时序
计数器溢出频率:CK-CNT-OV(计数器溢出频率)=CK_CNT/(ARR+1)=TCLK/(PSC+1)/(ARR+1)
溢出时间只需要再取个倒数。
2.3固态库函数配置
定时器中断初始化
开启TIM2外设时钟;
使能TIM2TIM_Cmd;
选择时钟源TIM_InternalClockConfig;
配置时基单元,其中有一个滤波器频率设定,
这个频率可以是来自系统的分频或不分频;
中断输出允许TIM_ITConfig;
NVIC中断分组NVIC_PriorityGroupConfig;
NVIC配置NVIC_Init;
配置定时器中断函数 ,可以复制到使用它的地方
void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
{
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
(3)输出比较(OC)
CCR是自行设定的值,CNT计数自增,通过共用CNT与CCR比较可以生成四路PWM波形。
OC(Output Compare)输出比较输出比较可以通过比较CNT与CCR寄存器值的关系,来对输出电平进行置1、置0或翻转的操作,用于输出一定频率和占空比的PWM波形。
每个高级定时器和通用定时器都拥有4个输出比较通道,高级定时器的前3个通道额外拥有死区生成和互补输出的功能。
3.1PWM
脉冲宽度调制在具有惯性的系统中,可以通过对一系列脉冲的宽度进行调制,来等效地获得所需要的模拟参量,常应用于电机控速等领域.
PWM频率越高,模拟效果越好,但消耗也就越大
分辨率就是占空比变化的细腻程度,即最小占空比
3.2PWM配置
由此可知,PWM频率等于计数器的更新频率,PWM占空比等于捕获比较寄存器与自动重装寄存器的比值
若将GPIO的引脚控制权交给片上外设,只能将引脚模式配置为复用推挽输出功能。
(5)输入捕获(IC)
输入捕获与输出比较共用一个计数器与四个通道,当使用输出比较功能时输入捕获则不能再使用。
输入捕获模式下,当通道输入引脚出现指定电平跳变时,当前CNT的值将被锁存到CCR中,可用于测量PWM波形的频率、占空比、脉冲间隔、电平持续时间等参数.每个高级定时器和通用定时器都拥有4个输入捕获通道可配置为PWMI模式,同时测量频率和占空比可配合主从触发模式,实现硬件全自动测量.
四:ADC模数转换器
(Analog-Digital Converter)模拟-数字转换器
ADC可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量,建立模拟电路到数字电路的桥梁。
工作模式:12位逐次逼近型ADC,工作频率:1us(1MHz)转换时间
ADC有18个输入通道,可测量16个外部和2个内部信号源(内部温度传感器与内部参考电压,基准电压的作用是在芯片供电不规范时进行校准)
规则组和注入组两个转换单元.
模拟看门狗自动监测输入电压范围
(1)32ADC框图
(2)ADC0809
(3)ADC基本结构
(4)输入通道
(5)固态库函数配置
开启RCC时钟;
设置ADC时钟;RCC_ADCCLKConfig;<14MHz;
GPIO初始化;GPIO_Init;
规则组通道配置;ADC_RegularChannelConfig;
ADC初始化;ADC_Init;
ADC使能;ADC_Cmd;
ADC校准(待纠);ADC_ResetCalibration(ADC1); //固定流程,内部有电路会自动执行校准
while (ADC_GetResetCalibrationStatus(ADC1) == SET);
ADC_StartCalibration(ADC1);
while (ADC_GetCalibrationStatus(ADC1) == SET);
五:DMA
(Direct Memory Access)直接存储器存取
DMA可以提供外设和存储器或者存储器和存储器之间的高速数据传输,无须CPU干预,节省了CPU的资源
12个独立可配置的通道: DMA1(7个通道), DMA2(5个通道) 每个通道都支持软件触发和特定的硬件触发。
(1)DMA框图
(2)DMA基本结构
传输计数器是一种自减的计数器;当触发源是软件触发,是连续触发,将传输计数器清零,一般使用在存储器到存储器的转运情况;使用硬件触发一般是外设。在设置传输计数器时需先关闭DMA。
若不使用重装器,则是单次,若使用则是循环
M2M(memory to memory)数据选择器的控制位,用于选择硬件触发还是软件触发
在数据传输中,若被传输的数据宽度少,则补0,若多,则舍去
(3)DMA1请求映像
(4)DMA+ADC
单个ADC通道数据转换完成后,会产生DMA申请
(5)存储器映像
(6)Bootloader
想要通过串口进行程序的烧录,则需要配置BOOT引脚使其从系统存储器启动
(7)选项字节
选项字节是微控制器中用于存储关键配置参数的特定区域,包括读取和写入保护、用户配置、安全特性、引导加载程序设置、低功耗模式和JTAG/SWD配置等。这些参数对于定制芯片行为、增强安全性和优化性能至关重要,且通常通过专用的编程工具进行访问和修改。不同的微控制器选项字节的具体实现和配置可能有所不同,因此开发者需要参考相应芯片的数据手册来正确操作。
(8)固态库函数配置
开启DMA的时钟;
DMA_Init DMA初始化;
{
源与终点基地址的设置;
源与终点数据宽度的设置,二者要对照相同;
选择源与终点地址是否自增,二者间的传输方向;
BufferSize转运数据大小(计数器次数)的设置;
DMA模式设置,选择循环或者单次;
并对DMA的触发方式进行设置M2M,可以选软件或者固定触发源;
给通道配置优先级
}
DMA使能;
六:通信协议
(1)串口(Serial Port)
USART,又名串口,是一种通用的串行通信接口,它支持同步和异步(也就是常说的串行)数据传输模式。USART集成了串行通信所需的大部分硬件功能,包括波特率生成、数据缓冲、发送和接收数据等。串口有时也被叫做COM口。
UART(Universal Asynchronous Receiver/Transmitter):USART也常与UART混淆,尽管严格来说UART仅支持异步通信模式,而USART支持同步和异步两种模式。在实际应用中,这两个术语经常被互换使用。
它定义了数据传输的物理特性,如电气信号、电压水平、连接器类型、电缆布局等。
串口可以用于实现多种不同的通信协议。
(1)通信协议(Communication Protocol)
通信协议是一套规则和标准,用于在通信过程中控制数据的格式、传输速率、数据的组织方式、同步方法、错误检测和纠正等。
协议定义了数据在物理介质上传输的逻辑方式,确保数据能够在不同的设备或系统之间正确地传输和解释。
通信协议可以在不同的物理介质上实现,包括串口、以太网、无线网络等。
通信的目的:将一个设备的数据传送到另一个设备,扩展硬件系统
全双工,通信双方能够同时进行双向通信,一般有两个,发送线路与接收线路互不影响
半双工, 只有一根数据线,不能同时双向通信
单工,数据只能从一个设备到另一个设备
有时钟线同步通信,无时钟线异步通信,并且异步通信双方还要约定一个采样频率,增加一些帧头帧尾等,进行采样位置的对齐
简单双向串口通信有两根通信线(发送端TX和接收端RX),TX与RX要交叉连接当只需单向的数据传输时,可以只接一根通信线当电平标准飞一致时,需要加电平转换芯片。
1.1硬件电路
简单双向串口通信有两根通信线(发送端TX和接收端RX)T,X与RX要交叉连接,当只需单向的数据传输时,可以只接一根通信线;当电平标准不一致时,需要加电平转换芯片。
1.2电平标准
数据1和数据0的表达方式,是传输线缆中人为规定的电压与数据的对应关系,串口常用的电平标准有如下三种:
TTL电平:+3.3V或+5V表示1,0V表示0;
RS232电平:-3~-15V表示1,+3~+15V表示0
RS485电平:两线压差+2~+6V表示1,-2~-6V表示0(差分信号)
1.3数据帧
数据帧(Data Frame)是计算机网络中用于封装和传输数据的一个基本单元,它由帧头、数据载荷和帧尾组成。帧头包含源和目的的MAC地址、帧类型等控制信息,用于网络设备识别和处理帧;数据载荷则是实际传输的数据,如来自网络层的IP数据报;帧尾通常包含错误检测代码,如CRC校验值,以确保数据的完整性。数据帧的设计使得网络能够在物理媒介上准确地传输信息,并提供必要的同步、寻址和错误检测机制,是网络通信中不可或缺的组成部分。
起始位:标志一个数据帧的开始,固定为低电平
数据位:数据帧的有效载荷,1为高电平,0为低电平,低位先行
校验位:用于数据验证,根据数据位计算得来
停止位:用于数据帧间隔,固定为高电平
1.4波特率(串口间约定好的速度参数)
串口通信的速率,每秒传输码元的个数,一个码元可以是多个比特,波特率的单位是“波特”(Baud),有时也简称为“bps”(symbols per second)。在某些情况下,人们可能会错误地将“bps”(bits per second)用作波特率的单位,但严格来说,bps是指每秒传输的比特数。在二进制调制下一个码元就是一个bit,高电平表示1,低电平表示0。比特率是每秒传输的比特数。
1.5USART框图
波特率发生器实际就是分频器;
当TXE标志位置1,表明发送寄存器为空,新的数据可以放到数据寄存器,当RXNE标志位置1,则可以从数据寄存器读出,同时内部电路会自动执行添加帧头帧尾
NRTS(request to Send),请求发送,输出脚,决定当前能不能接收;NCTS(clear to send),请求输入,用于接收NRTS信号,输入脚,,用于接收NRTS信号,N表示低电平有效
当两个支持流控的串口进行传输数据时,一个串口向另一个串口发送数据,发送串口的TX接对方的RX,nCTS接对方的nRTS,若nRTS置0,则表明当前能发送数据,若处理不过来,则置1
1.6基本结构图
1.7起始位侦测
1.8数据采样
1.9波特率发生器
1.10数据包
将数据进行打包与分割,方便接收方识别
可变包长,含包头包尾
(2)IIC(Inter IC BUS)
由Philips公司开发的一种通用数据总线 两根通信线:SCL(Serial Clock)、SDA(Serial Data)
同步,半双工 带数据应答 支持总线挂载多设备(一主多从、多主多从)
2.1硬件电路
所有I2C设备的SCL连在一起,SDA连在一起;设备的SCL和SDA均要配置成开漏输出模式
SCL和SDA各添加一个上拉电阻,阻值一般为4.7KΩ左右
2.2IIC时序基本单元
起始条件:SCL高电平期间,SDA从高电平切换到低电平 ;
终止条件:SCL高电平期间,SDA从低电平切换到高电平
发送一个字节:SCL低电平期间,主机将数据位依次放到SDA线上(高位先行),然后释放SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节。
接收一个字节:SCL低电平期间,从机将数据位依次放到SDA线上(高位先行),然后释放SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节(主机在接收之前,需要释放SDA)
发送应答:主机在接收完一个字节之后,在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答,若从机继续要发送数据,主机则要应答,不应答从机不再发送数据
接收应答:主机在发送完一个字节之后,在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA)
2.3IIC时序
指定地址写 :对于指定设备(Slave Address),在指定地址(Reg Address)下,写入指定数据(Data)
第一个八位中,七位是地址,最后一位是读写控制,随后是RA应答位,主机发送一个字节后,从机接收到后立马拉住SDA,产生应答0,若要连续发送字节,停止位P(stop)可以最后发送,并且在发送一个字节后地址指针会立马+1,总结指定地址写,先选择从机,并确定模式写,再选择从寄存器地址,最后发送数据,每次操作都会有应答确认。
当前地址读: 对于指定设备(Slave Address),在当前地址指针指示的地址下,读取从机数据(Data)
总结,从机的选择与读写确认,应答,读数据,应答,停止;因为在确认读模式之后,主机立马进行读操作,没有时间指定地址,主机只能根据从机当前地址指针指定的地址进行读入,并在读入后地址指针自动+1,想要指定地址从机的寄存器地址进行读入,需要结合指定地址写与指定地址读操作。
指定地址读 对于指定设备(Slave Address),在指定地址(Reg Address)下,读取从机数据(Data)
2.4MPU6050简介
MPU6050是一个6轴姿态传感器,可以测量芯片自身X、Y、Z轴的加速度、角速度参数,通过数据融合,可进一步得到姿态角,常应用于平衡车、飞行器等需要检测自身姿态的场景
3轴加速度计(Accelerometer):测量X、Y、Z轴的加速度
3轴陀螺仪传感器(Gyroscope):测量X、Y、Z轴的角速度
2.5硬件I2C
STM32内部集成了硬件I2C收发电路,可以由硬件自动执行时钟生成、起始终止条件生成、应答位收发、数据收发等功能,减轻CPU的负担
支持多主机模型,支持7位/10位地址模式, 支持不同的通讯速度,标准速度(高达100 kHz),快速(高达400 kHz)
支持DMA 兼容SMBus协议
2.6I2C框图
2.7硬件I2C基本结构
微控制器默认在总线是从设备,只有当发送开始信号后才变为主设备。
2.7.1主机发送
2.7.2主机接收
(3)SPI(Serial Peripheral Interface)
Motorola公司开发的一种通用数据总线 四根通信线:SCK(Serial Clock)、MOSI(Master Output Slave Input)、MISO(Master Input Slave Output)、SS(Slave Select) 同步,全双工 支持总线挂载多设备(一主多从,不支持多主机)
3.1硬件电路
所有SPI设备的SCK、MOSI、MISO分别连在一起 主机另外引出多条SS控制线,分别接到各从机的SS引脚 输出引脚配置为推挽输出,输入引脚配置为浮空或上拉输入。
主机、从机都要共地;时钟线完全由主机占有,配置为推挽输出;SPI额外一条SS(select slave)置低电平来选择外设
从机未被选中,从机输出引脚必须被配置为高阻态,SS为低电平时,MISO才能设置为推挽输出。
3.2移位示意图
上升沿移位,下降沿输入。
3.3SPI时序基本单元
起始条件:SS从高电平切换到低电平
终止条件:SS从低电平切换到高电平
交换一个字节(模式0) CPOL=0:空闲状态时,SCK为低电平 CPHA=0:SCK第一个边沿移入数据,第二个边沿移出数据
交换一个字节(模式1)
CPOL(Clock Polarity,决定空闲状态是高电平还是低电平)=0:空闲状态时,SCK为低电平
CPHA(Clock Phase,决定第几个边沿移入数据)=1:SCK第一个边沿移出数据,第二个边沿移入数据
MISO中间线被当作高阻态,数据操作结束后再返回高阻态
交换一个字节(模式0)
CPOL=0:空闲状态时,SCK为低电平
CPHA=0:SCK第一个边沿移入数据,第二个边沿移出数据
SS下降沿就开始移出数据
3.4SPI时序
向SS指定的设备,发送指令(0x06)
指定地址写
向SS指定的设备,发送写指令(0x02),随后在指定地址(Address[23:0])下,写入指定数据(Data)
指定地址读
向SS指定的设备,发送读指令(0x03),随后在指定地址(Address[23:0])下,读取从机数据(Data)
3.5W25Q64
W25Qxx系列是一种低成本、小型化、使用简单的非易失性存储器,常应用于数据存储、字库存储、固件程序存储等场景
存储介质:Nor Flash(闪存) 时钟频率:80MHz / 160MHz (Dual SPI) / 320MHz (Quad SPI) 存储容量(24位地址):
3.5.1芯片框图
3.6Flash
Flash存储器是一种非易失性存储器,这意味着即使在断电的情况下,它也能保持存储的数据。因此,Flash存储器不会因为掉电而丢失数据。优势,容量大价格低。
Flash存储器的这种特性使其非常适合用于存储需要长期保存的数据,如固件、操作系统、应用程序和用户数据等。常见的Flash存储设备包括U盘、SSD(固态硬盘)和智能手机中的存储卡。
3.6.1注意事项
写入操作时:
写入操作前,必须先进行写使能 每个数据位只能由1改写为0,不能由0改写为1;
写入数据前必须先擦除,擦除后,所有数据位变为1; 擦除必须按最小擦除单元(扇区)进行;
连续写入多字节时,最多写入一页的数据,超过页尾位置的数据,会回到页首覆盖写入(页缓存区只有256byte)(设置缓存区的意义,因为Flash写入的速度太慢了);
写入操作结束后,芯片进入忙状态,不响应新的读写操作
读取操作时:
直接调用读取时序,无需使能,无需额外操作,没有页的限制,读取操作结束后不会进入忙状态,但不能在忙状态时读取
3.7硬件SPI
STM32内部集成了硬件SPI收发电路,可以由硬件自动执行时钟生成、数据收发等功能,减轻CPU的负担
可配置8位/16位数据帧、高位先行/低位先行;时钟频率: fPCLK / (2, 4, 8, 16, 32, 64, 128, 256) 支持多主机模型、主或从操作 可精简为半双工/单工通信 支持DMA 兼容I2S协议(数字音频传输协议)
I2C、SPI都是高位先行,串口是低位先行。
MOSI一位一位的接收移位寄存器的数据,
3.7.1波特率发生器
波特率发生器是用于产生约定通信速率的设备,它从输入时钟转换出需要的波特率clk。 波特率发生器的作用是通过分频器分频系统时钟,以控制数据传输的速率。
3.8基本结构
3.9SPI模式
3.9.1主模式全双工连续传输
3.9.2非连续传输
非连续传输与连续传输的区别是,连续传输是连续发送数据1、发送数据2、接收数据1、发送数据3、接收数据2、发送数据4、接收数据3,交换的流程是交错的,而非连续传输每次在发送完数据之后还会等到与之相对的读数据,再发送新数据。
七:BKP与RTC
(1)Unix时间戳(Unix Timestamp)
定义为从UTC/GMT的1970年1月1日0时0分0秒开始所经过的秒数,不考虑闰秒 时间戳存储在一个秒计数器中,秒计数器为32位/64位的整型变量 世界上所有时区的秒计数器相同,不同时区通过添加偏移来得到当地时间
1.1UTC/GMT
GMT(Greenwich Mean Time)格林尼治标准时间是一种以地球自转为基础的时间计量系统。它将地球自转一周的时间间隔等分为24小时,以此确定计时标准,但是由于潮汐力、地球自转等因素,导致GMT会发生变化,故被遗弃。
UTC(Universal Time Coordinated)协调世界时是一种以原子钟为基础的时间计量系统。它规定铯133原子基态的两个超精细能级间在零磁场下跃迁辐射9,192,631,770周所持续的时间为1秒。当原子钟计时一天的时间与地球自转一周的时间相差超过0.9秒时,UTC会执行闰秒来保证其计时与地球自转的协调一致
1.2时间戳转换
(2)BKP(Backup Registers)
备份寄存器 BKP,可用于存储用户应用程序数据。当VDD(2.0~3.6V)电源被切断,他们仍然由VBAT(1.8~3.6V)维持供电。当系统在待机模式下被唤醒,或系统复位或电源复位时,他们也不会被复位。
TAMPER引脚产生的侵入事件将所有备份寄存器内容清除 ;RTC引脚输出RTC校准时钟、RTC闹钟脉冲或者秒脉冲 存储RTC时钟校准寄存器 用户数据存储容量: 20字节(中容量和小容量)/ 84字节(大容量和互联型)
2.1基本结构
2.2备份域
备份域(Backup Domain)在STM32微控制器中是一块特殊的存储区域,它包括实时时钟(RTC)、RTC备份寄存器以及备份SRAM(BKP SRAM)。以下是备份域的一些关键特性和功能:
-
独立电源供电:备份域拥有自己的电源电路,可以在主电源VDD断电时,通过VBAT(电池)供电,确保RTC、RTC备份寄存器和备份SRAM中的数据不会丢失。
-
数据保持:备份域中的数据在系统复位、电源复位、待机唤醒等情况下都能保持不变,这解决了因意外断电导致数据丢失的问题。
-
访问控制:访问备份域需要特定的步骤,包括使能电源接口时钟、使能备份域访问等。复位后,备份域(RTC 寄存器、RTC 备份寄存器和备份 SRAM)将受到保护,以防止意外的写访问。
-
RTC备份寄存器:STM32微控制器的RTC模块通常提供了多个RTC备份寄存器,用于存储用户定义的备份数据。这些数据在断电或复位情况下通常会保持不变,因为它们存储在低功耗RAM中。
-
备份SRAM:某些STM32芯片提供了备份SRAM,例如STM32F系列芯片有4K的备份SRAM。这部分SRAM可以用于存储掉电不丢失的数据(需要RTC纽扣电池支持)。
-
初始化和使用:要使用备份域中的各部分功能(RTC、RTC备份寄存器、备份SRAM),需要先进行初始化,包括使能相关时钟、使能备份域访问等步骤。
-
跨电源周期保持数据:备份域的设计允许在VBAT供电的情况下,即使VDD电源断电,备份域中的数据也能被保持。这对于需要在电源周期之间保持关键数据的应用非常重要。
-
保护机制:备份域在硬件上提供了对寄存器的保护,以防止在复位后意外写入备份寄存器。
(3)RTC(Real Time Clock)
实时时钟,RTC是一个独立的定时器,可为系统提供时钟和日历的功能 RTC和时钟配置系统处于后备区域,系统复位时数据不清零,VDD(2.0~3.6V)断电后可借助VBAT(1.8~3.6V)供电继续走时 32位的可编程计数器,可对应Unix时间戳的秒计数器 20位的可编程预分频器,可适配不同频率的输入时钟
可选择三种RTC时钟源: HSE时钟除以128(通常为8MHz/128) LSE振荡器时钟(通常为32.768KHz) LSI振荡器时钟(40KHz)。一般选择LSE振荡器时钟,32.768KHz,只有这一个时钟可以接BKP备份寄存器。
3.1框图
重装寄存器设定好初始值(如32767),DIV是自减计数器,每来一个时钟沿就-1,当减到0时,再来一个时钟脉冲,就会恢复重装值,实现分频,最终将产生的1Hz的分频给CNT。
RTC_CNT是Unix时间戳秒计数器,再通过time.h头文件产生年月日秒分时星期,ALR(32寄存器)(alarm)可以作为闹钟,也可以用来退出待机模式,当ALR与CNT值相同时,产生闹钟,并且只能产生一次闹钟,每次使用后都要重新设置。
三个时钟源,Second秒中断,CNT每计数一次产生一次中断,OverflowCNT溢出中断,一般不会产生,Alarm闹钟中断源,产生中断后再通过中断使能进入NVIC.
3.2基本结构
3.3注意事项
执行以下操作将使能对BKP和RTC的访问:
设置RCC_APB1ENR的PWREN和BKPEN,使能PWR和BKP时钟;设置PWR_CR的DBP,使能对BKP和RTC的访问 ;
若在读取RTC寄存器时,RTC的APB1接口曾经处于禁止状态,则软件首先必须等待RTC_CRL寄存器中的RSF位(寄存器同步标志)被硬件置1 ;
必须设置RTC_CRL寄存器中的CNF位,使RTC进入配置模式后,才能写入RTC_PRL、RTC_CNT、RTC_ALR寄存器; 对RTC任何寄存器的写操作,都必须在前一次写操作结束后进行。可以通过查询RTC_CR寄存器中的RTOFF状态位,判断RTC寄存器是否处于更新中。仅当RTOFF状态位是1时,才可以写入RTC寄存器。
八:PWR(Power Control)
电源控制 ,PWR负责管理STM32内部的电源供电部分,可以实现可编程电压监测器和低功耗模式的功能
可编程电压监测器(PVD)可以监控VDD电源电压,当VDD下降到PVD阀值以下或上升到PVD阀值之上时,PVD会触发中断,用于执行紧急关闭任务
低功耗模式包括睡眠模式(Sleep)、停机模式(Stop)和待机模式(Standby),可在系统空闲时,降低STM32的功耗,延长设备使用时间
(1)电源框图
(2)上电复位、掉电复位
当电压大于POR解除复位,小于PDR时复位,设置两个阈值防止电压波动;
(3)PVD(Programmable Voltage Detector)
PVD阈值电压可以通过程序设置;当VDD与VDDA电压下降到PVD阈值,开始进入PVD检测,PVD会产生输出中断,进行系列操作,也是双阈值防止波动,在低于最低阈值后进入复位监测。
(4)低功耗模式
4.1睡眠
(WFI)Wait for interrupt ,中断唤醒(WFE)Wait for Event,时间唤醒
关闭时钟,所有运算与程序都会暂停,关闭电源电路操作数据都会丢失。
电压调节器(1.8V区域电源,如果关闭就意味着断电)
执行完WFI/WFE指令后,STM32进入睡眠模式,程序暂停运行,唤醒后程序从暂停的地方继续运行
SLEEPONEXIT位决定STM32执行完WFI或WFE后,是立刻进入睡眠,还是等STM32从最低优先级的中断处理程序中退出时进入睡眠; 在睡眠模式下,所有的I/O引脚都保持它们在运行模式时的状态
WFI指令进入睡眠模式,可被任意一个NVIC响应的中断唤醒 WFE指令进入睡眠模式,可被唤醒事件唤醒
4.2停机
在STM32微控制器的PWR(电源管理)模块中,PDDS和LPDS是两个重要的控制位,它们与低功耗模式的进入和退出密切相关。
PDDS(Power Down Deep Sleep):PDDS位用于区分进入的是停机模式(Stop mode)还是待机模式(Standby mode)。当PDDS设置为0时,微控制器进入停机模式;当PDDS设置为1时,微控制器进入待机模式。
LPDS(Low Power Deep Sleep):LPDS位用于控制电压调节器在进入深度睡眠模式时的工作状态。当LPDS设置为0时,电压调节器工作在正常模式;当LPDS设置为1时,电压调节器进入低功耗模式,进一步降低功耗,但会增加从停止模式唤醒的延迟。
执行完WFI/WFE指令后,STM32进入停止模式,程序暂停运行,唤醒后程序从暂停的地方继续运行,停止模式与睡眠模式并无区别,二者都不会断掉电源,数据也不会丢失
1.8V供电区域的所有时钟都被停止,PLL、HSI和HSE被禁止,SRAM和寄存器内容被保留下来;在停止模式下,所有的I/O引脚都保持它们在运行模式时的状态 当一个中断或唤醒事件导致退出停止模式时,HSI被选为系统时钟(PLL与HSE都被关闭,一般要重新配置时钟);当电压调节器处于低功耗模式下,系统从停止模式退出时,会有一段额外的启动延时;
WFI指令进入停止模式,可被任意一个EXTI中断唤醒 WFE指令进入停止模式,可被任意一个EXTI事件唤醒
4.3待机
执行完WFI/WFE指令后,STM32进入待机模式,唤醒后程序从头开始运行(最大的区别), 整个1.8V供电区域被断电,PLL、HSI和HSE也被断电,SRAM和寄存器内容丢失,只有备份的寄存器和待机电路维持供电 ;
在待机模式下,所有的I/O引脚变为高阻态(浮空输入)WKUP引脚的上升沿、RTC闹钟事件的上升沿、NRST引脚上外部复位、IWDG复位退出待机模式
停机与待机都不会去关闭LSI,LSE;
(5)模式选择
执行WFI(Wait For Interrupt)或者WFE(Wait For Event)指令后,STM32进入低功耗模式,配置其他寄存器都要在这两个指令之前。
九:WDG(Watchdog)
看门狗 看门狗可以监控程序的运行状态,当程序因为设计漏洞、硬件故障、电磁干扰等原因,出现卡死或跑飞现象时,看门狗能及时复位程序,避免程序陷入长时间的罢工状态,保证系统的可靠性和安全性
看门狗本质上是一个定时器,当指定时间范围内,程序没有执行喂狗(重置计数器)操作时,看门狗硬件电路就自动产生复位信号
STM32内置两个看门狗,独立看门狗(IWDG):独立工作,对时间精度要求较低。窗口看门狗(WWDG):要求看门狗在精确计时窗口起作用。
(1)IWDG框图
键寄存器本质上是控制寄存器,用于控制硬件电路的工作 在可能存在干扰的情况下,一般通过在整个键寄存器写入特定值来代替控制寄存器写入一位的功能,以降低硬件电路受到干扰的概率
通过键寄存器
超时时间:TIWDG = TLSI × PR预分频系数 × (RL + 1) 其中:TLSI = 1 / FLSI
(2)WWDG框图
最高位T6被当作标志位,当T6位从1000000变为0111111时,WWDG产生复位;
WDGA使能位,使能窗口看门狗;_CFR是写入的最早界限,通过比较二者寄存器的值来产生复位
2.1工作特性
递减计数器T[6:0]的值小于0x40时,WWDG产生复位 ;递减计数器T[6:0]在窗口W[6:0]外被重新装载时, WWDG产生复位
递减计数器T[6:0]等于0x40时可以产生早期唤醒中断(EWI),用于重装载计数器以避免WWDG复位
定期写入WWDG_CR寄存器(喂狗)以避免WWDG复位
2.2WWDG超时时间
超时时间: TWWDG = TPCLK1 × 4096 × WDGTB预分频系数 × (T[5:0] + 1) 窗口时间: TWIN = TPCLK1 × 4096 × WDGTB预分频系数 × (T[5:0] - W[5:0]) 其中:TPCLK1 = 1 / FPCLK1
(3)WWDG与IWDG对比
十:FLASH
STM32F1系列的FLASH包含程序存储器、系统存储器和选项字节三个部分,通过闪存存储器接口(外设)可以对程序存储器和选项字节进行擦除和编程
读写FLASH的用途:利用程序存储器的剩余空间来保存掉电不丢失的用户数据;通过在程序中编程(IAP),实现程序的自我更新。
在线编程(In-Circuit Programming – ICP)用于更新程序存储器的全部内容,它通过JTAG、SWD协议或系统加载程序(Bootloader)下载程序
在程序中编程(In-Application Programming – IAP)可以使用微控制器支持的任一种通信接口下载程序。
闪存参考手册被单独列出
(1)闪存模块组织
(2)基本结构
(3)FLASH解锁
FPEC共有三个键值:RDPRT键 = 0x000000A5 KEY1 = 0x45670123KEY2 = 0xCDEF89AB
解锁:复位后,FPEC被保护,不能写入FLASH_CR;
在FLASH_KEYR先写入KEY1,再写入KEY2,解锁;
错误的操作序列会在下次复位前锁死FPEC和FLASH_CR
加锁:设置FLASH_CR中的LOCK位锁住FPEC和FLASH_CR
(4)使用指针访问存储器
使用指针读指定地址下的存储器:uint16_t Data = *((__IO uint16_t *)(0x08000000));
使用指针写指定地址下的存储器:*((__IO uint16_t *)(0x08000000)) = 0x1234; 其中:#define __IO volatile
(5)程序存储器编程
word 32 halfword 16 byte 8
若是只写入一个字节保留另一半字节的数据,则要将整页的数据复制到SRAM,再使用SRAM随意更改数据;
STM32闪存会在写入前检查是否擦除,若未擦除,则不进行写入(除写入位全是0)
5.1程序存储器全擦除
5.2页擦除
(6)选项字节
在写入数据时要在带n的地方写入对应的反码,不然无效
RDP(Read Protect):写入RDPRT键(0x000000A5)后解除读保护
USER:配置硬件看门狗和进入停机/待机模式是否产生复位
Data0/1:用户可自定义使用 WRP0/1/2/3:配置写保护,每一个位对应保护4个存储页(中容量)
6.1选项字节擦除
检查FLASH_SR的BSY位,以确认没有其他正在进行的闪存操作
解锁FLASH_CR的OPTWRE位 设置FLASH_CR的OPTER位为1
设置FLASH_CR的STRT位为1 等待BSY位变为0 读出被擦除的选择字节并做验证
6.2选项字节写入
检查FLASH_SR的BSY位,以确认没有其他正在进行的编程操作
解锁FLASH_CR的OPTWRE位 设置FLASH_CR的OPTPG位为1
写入要编程的半字到指定的地址 等待BSY位变为0 读出写入的地址并验证数据
(7)器件电子签名
电子签名存放在闪存存储器模块的系统存储区域,包含的芯片识别信息在出厂时编写,不可更改,使用指针读指定地址下的存储器可获取电子签名
闪存容量寄存器:基地址:0x1FFF F7E0 大小:16位 产品唯一身份标识寄存器:基地址: 0x1FFF F7E8 大小:96位
十一:GPIO
(1)保护二极管
引脚内部加上这两个保护二极管可以防止引脚外部过高或过低的电压输入,当引脚高于VDD_FT或VDD时,上方的二极管导通吸收这个高电压当引脚电压低于 VSS 时下方的二极管导通,防止不正常电压引入芯片导致芯片烧毁。尽管 STM32 芯片内部有这样的保护, 但并不意味着 STM32 的引脚就无所不能, 如果直接将引脚连接大功率器件,比如电机,那么要么电机不转,要么烧坏芯片。如果要驱动一些大功率器件, 必须要加大功率及隔离电路驱动。也可以说 STM32引脚是用来做控制, 而不是做驱动使用的。
(2)上下拉电阻
从图中可以看到, 上拉和下拉电阻上都有一个开关, 通过配置上下拉电阻开关, 可以控制引脚的默认状态电平。 当开启上拉时引脚默认电压为高电平, 开启下拉时, 引脚默认电压为低电平, 这样就可以消除引脚不定状态的影响。 当然也可以将上拉和下拉的开关都关断, 这种状态我们称为浮空模式, 一旦配置成这个模式, 引脚的电压是不确定的, 如果用万用表测量此模式下管脚电压时会发现只有 1 点几伏, 而且还不时改变, 所以一般情况下我们都会给引脚设置成上拉或者下拉模式, 使它有一个默认状态。 STM32 上下拉及浮空模式的配置是通过GPIOx_CRL 和 GPIOx_CRH 寄存器控制的, 大家可以通过《STM32F1xx 中文参考手册》 查阅。
(3)P-MOS和N-MOS管
GPIO 引脚经过两个保护二极管后就分成两路,上面一路是“输入模式” ,下面一路是“输出模式” 。我们先讲输出模式,线路经过一个由 P-MOS 和 N-MOS管组成的单元电路,这让 GPIO 引脚具有了推挽和开漏两种输出模式。
所谓推挽输出模式,是根据 P-MOS 和 N-MOS 管的工作方式命名的。在该结构单元输入一个高电平时,P-MOS 管导通,N-MOS 管截止(可以将 P-MOS 当作 NPN三极管,N-MOS 当作PNP 三极管来看就非常清楚),对外输出高电平(3.3V)。在该单元输入一个低电平时, P-MOS 管截止,N-MOS 管导通,对外输出低电平(0V)。如果当切换输入高低电平时, 两个 MOS 管将轮流导通, 一个负责灌电流(电流输出到负载) , 一个负责拉电流(负载电流流向芯片) , 使其负载能力和开关速度都比普通的方式有很大的提高。
在开漏输出模式时, 不论输入是高电平还是低电平, P-MOS 管总处于关闭状态。 当给这个单元电路输入低电平时, N-MOS 管导通, 输出即为低电平。 当输入高电平时, N-MOS 管截止, 这个时候引脚状态既不是高电平, 又不是低电平, 我们称之为高阻态。 如果想让引脚输出高电平, 那么引脚必须外接一个上拉电阻,由上拉电阻提供高电平。
在开漏输出模式中还有一个特点, 引脚具有“线与” 关系。 就是说如果有很多个开漏输出模式的引脚接在一起, 只要有一个引脚为低电平, 其他所有管脚都为低, 即把所有引脚连接在一起的这条总线拉低了。 只有当所有引脚输出高阻态时这条总线的电平才由上拉电阻的 VDD 决定。 如果 VDD 连接的是 3.3V, 那么引脚输出的就是 3.3V, 如果 VDD 连接的是 5V, 那么引脚输出的就是 5V。 因此如果想要让 STM32 管脚输出 5V, 可以选择开漏输出模式, 然后在外接上拉电阻的电源 VDD 选择 5V 即可, 前提是这个 STM32 引脚是容忍 5V 的。 开漏输出模式一般应用在 I2C、 SMBUS 通讯等需要“线与” 功能的总线电路中。 还可以用在电平不匹配的场合中, 就如上面说的输出 5V 一样。
推挽输出模式一般应用在输出电平为 0-3.3V 而且需要高速切换开关状态(推挽输出可以很快速的把引脚电平拉高拉低)的场合。 除了必须要用开漏输出模式的场合, 我们一般选择推挽输出模式。 要配置引脚是开漏输出还是推挽输出模式可以使用 GPIOx_CRL 和 GPIOx_CRH 寄存器, 寄存器详细内容可以参考《STM32F1xx 中文参考手册》 “通用和复用 I/O(GPIO 和AFIO) ” 章节。
(4)输入、输出数据寄存器
输入数据寄存器是由 IO 口经过上下拉电阻、 施密特触发器引入。 当信号经过触发器, 模拟信号将变为数字信号 0 或 1, 然后存储在输入数据寄存器中, 通过读取输入数据寄存器 GPIOx_IDR 就可以知道 IO 口的电平状态。
前面提到的双 MOS 管结构电路的输入信号, 是由 GPIO“输出数据寄存器GPIOx_ODR”提供的, 因此我们通过修改输出数据寄存器的值就可以修改 GPIO 引脚的输出电平。 而“置位/复位寄存器 GPIOx_BSRR” 可以通过修改输出数据寄存器的值从而影响电路的输出。
(5)复用功能输入、输出
STM32的GPIO引脚具有第二功能,当使用复位功能的时通过其他外设复用功能输出信号与GPIO数据寄存器一起连接到双MOS管电路的输入,其中梯形结构是用来选择使用复用功能还是普通IO口功能。
(6)模拟输入输出
当GPIO引脚用于ADC采集电压的输入通道时,用作“模拟输入”功能,此时信号不能经过施密特触发器(施密特触发器会将信号转换为只有0与1两种状态),当 GPIO 引脚用于 DAC 作为模拟电压输出通道时, 此时作为“模拟输出” 功能, DAC 的模拟信号输出就不经过双 MOS 管结构了, 模拟信号直接通过管脚输出。
(7)堆栈的概念
栈(Stack):栈本意是存储货物或客人的临时场所,是一段连续内存空间,栈是用于存储程序运行时的临时变量、函数调用的参数以及局部变量的内存区域。栈的内存分配和回收是自动的,由编译器和操作系统管理。栈的内存分配遵循先进后出的原则,即最后分配的内存会最早被回收。栈的空间通常有限,因为它是连续的内存区域,如果栈空间耗尽,会导致栈溢出。
地址最大的地方被称作栈底,最小的地方被称作栈顶,栈顶有一个专用的指针SP(时刻指向栈顶),可以随时获取栈顶的数据
堆(Heap):堆是用于动态内存分配的内存区域。与栈不同,堆的内存分配和回收是手动的,由程序员控制。堆允许更灵活的内存管理,因为程序员可以根据需要分配任意大小的内存块。但是,这也意味着程序员需要负责释放不再使用的内存,否则会导致内存泄漏。
栈的内存分配是自动的,由编译器管理;堆的内存分配是手动的,由程序员控制。栈的内存回收是自动的,当函数返回时,其栈帧会被自动销毁;堆的内存回收需要程序员显式释放。栈的空间通常较小,而堆的空间通常较大,但受限于操作系统和可用内存。栈的访问速度通常比堆快,因为栈是连续的内存区域,而堆中的内存块是不连续的。
7.1栈帧(Stack Frame)
栈(Stack)中用于存储函数调用相关数据的特定区域。每当一个函数被调用时,操作系统或运行时环境会在栈上创建一个新的栈帧,这个栈帧包含了该函数执行期间所需的所有信息。栈帧的主要作用是保持函数调用的上下文,确保函数能够正确地执行和返回。栈帧通常包含以下信息:
-
返回地址:当函数执行完毕后,返回地址告诉程序接下来应该执行哪里的代码。这个地址通常是函数调用后的下一条指令的地址。
-
参数传递:函数调用时传递给函数的参数通常存储在栈帧中,以便函数内部可以访问这些参数。
-
局部变量:函数内部定义的局部变量存储在栈帧中,每个栈帧都有专门区域用于存储这些变量。
-
寄存器保存区:在函数调用过程中,可能需要保存和恢复调用者函数的一些寄存器值,以确保函数调用不会影响调用者的环境。
-
栈指针:指向当前栈帧的顶部或底部,取决于系统的栈增长方向(向上或向下)。
当函数调用另一个函数时,新的栈帧会被创建并压入栈中,这个过程称为“压栈”。当函数返回时,当前的栈帧会从栈中弹出,这个过程称为“弹栈”,同时恢复上一个栈帧的状态,继续执行调用者的代码。
栈帧的管理对于程序的执行流程至关重要,它确保了每个函数调用都能够正确地保存和恢复现场,使得程序能够按照预期执行。栈帧的大小和布局可能会因编译器、操作系统和处理器架构的不同而有所差异。
7.2栈指针(Stack Pointer,简称SP)
在计算机架构中,栈顶指针是一个特殊的寄存器,它用于指向当前栈顶的位置。栈顶是栈中用于存储下一个元素的位置,栈顶指针的作用包括但不限于以下几点:
-
跟踪栈顶位置:栈顶指针始终指向栈顶的当前位置,无论是栈增长方向是向上(向高地址)还是向下(向低地址),SP都能准确指示栈顶。
-
内存访问:当进行函数调用、局部变量分配或返回地址存储等操作时,处理器使用栈顶指针来访问栈内存。例如,当一个新函数被调用时,返回地址通常被推送到栈上,处理器会通过减少SP的值来为新的栈帧腾出空间。
-
函数调用和返回:在函数调用过程中,栈顶指针用于管理栈帧的压栈(push)和弹栈(pop)操作。当一个函数开始执行时,它的栈帧被压入栈中,当函数返回时,它的栈帧被弹出栈,这些操作都依赖于SP的更新。
-
保护栈空间:栈顶指针有助于防止栈溢出和栈下溢。如果SP的值低于某个阈值,可能会触发栈溢出异常;如果SP的值异常增高,可能会触发栈下溢异常。
-
同步栈状态:在多线程环境中,每个线程都有自己的栈和对应的栈顶指针,以保持线程栈状态的独立性。
-
调试和异常处理:在调试程序时,栈顶指针可以帮助开发者跟踪函数调用栈,这对于调试和诊断程序错误非常重要。同时,在异常处理中,栈顶指针用于恢复到异常发生时的上下文环境。
栈顶指针是处理器状态的一部分,它的值在函数调用、中断处理、异常处理等操作中不断变化,以确保栈的正确操作和程序的正确执行。在不同的处理器架构中,栈顶指针的具体实现和行为可能有所不同。
7.3栈溢出(Stack Overflow)和栈下溢(Stack Underflow)
两种与栈(Stack)相关的运行时错误,它们通常发生在程序的栈内存管理不当时。
栈溢出是指程序在栈上分配的内存超过了栈的实际大小。这通常发生在以下情况:
-
无限递归:函数无限递归调用自己,每次递归调用都会在栈上创建一个新的栈帧,直到栈空间耗尽。
-
局部变量过大:在函数中声明了过大的局部变量数组或数据结构,导致一次性占用过多栈空间。
-
大量函数调用:大量的函数调用导致栈帧累积,超出了栈的容量。
栈溢出的后果可能包括程序崩溃、异常终止或不可预测的行为,因为栈内存被破坏了。在某些情况下,栈溢出还可能被利用作为安全漏洞,比如缓冲区溢出攻击。
栈下溢是指程序试图访问栈上不存在的内存,即栈指针(Stack Pointer, SP)的值低于它的合法范围。栈下溢的后果同样可能导致程序崩溃或不可预测的行为,因为它破坏了正常的程序执行流程和内存管理。
(8)工作模式
通过GPIO内部的结构关系,决定了GPIO可以配置为下面几种模式:
8.1输入模式(模拟、上拉下拉、浮空)
输入模式下,施密特触发器打开,输出被禁止。可以通过输入数据寄存器GPIOx_IDR 读取 I/O 状态。上拉和下拉输入很好理解, 默认的电平由上拉或者下拉决定。 浮空输入的电平是不确定的, 完全由外部的输入决定, 一般接按键的时候可以使用这个模式。模拟输入则用于 ADC 采集。
8.2输出模式(推挽、开漏)
在输出模式中, 推挽模式时双 MOS 管以推挽方式工作, 输出数据寄存器GPIOx_ODR 可控制 I/O 输出高低电平。 开漏模式时, 只有 N-MOS 管工作, 输出数 据 寄 存 器 可 控 制 I/O 输 出 高 阻 态 或 低 电 平 。 输 出 速 度 可 配 置 , 有2MHz\25MHz\50MHz 的选项。此处的输出速度即 I/O 支持的高低电平状态最高切换频率, 支持的频率越高, 功耗越大, 如果功耗要求不严格, 把速度设置成最大即可。 在输出模式时, 施密特触发器是打开的, 即输入可用, 通过输入数据寄存器 GPIOx_IDR 可读取 I/O 的实际状态。
8.3复用功能
复用功能模式中, 输出使能, 输出速度可配置, 可工作在开漏及推挽模式,但是输出信号源于其它外设, 输出数据寄存器 GPIOx_ODR 无效; 输入可用, 通过输入数据寄存器可获取 I/O 实际状态, 但一般直接用外设的寄存器来获取该数据信号
8.4模拟输入输出(上下拉无影响)
模拟输入输出模式中, 双 MOS 管结构被关闭, 施密特触发器停用, 上/下拉也被禁止。 其它外设通过模拟通道进行输入输出。 通过对 GPIO 寄存器写入不同的参数, 就可以改变 GPIO 的应用模式, 再强调一下, 要了解具体寄存器时一定要查阅《STM32F1xx 参考手册》 中对应外设的寄存器说明。 在 GPIO 外设中, 通过设置“端口配置寄存器 GPIOx_CRL 和 GPIOx_CRH” 可配置 GPIO 的工作模式和输出速度。 CRH 控制端口的高八位, CRL 控制端口的低八位。
十二:固件配置
(1)启动文件
名称startup_stm32f10x_hd.s,启动文件是使用汇编语言写好了基本程序,当STM32芯片上电后会首先执行这里的汇编程序,从而建立起C语言的运行环境,这里使用的汇编指令是Cortex-M3内核支持的指令。
startup_stm32f10x_hd.s 文件是由 ST 官方提供的, 一般有需要也是在官方的基础上修改, 不会自己完全重写。 该文件可以从 KEIL5 安装目录找到, 也可以从 ST 库里面找到, 找到该文件后把启动文件添加到工程里面即可。 不同型号的芯片以及不同编译环境下使用的汇编文件是不一样的, 但功能相同。
对于启动文件这部分我们主要总结它的功能, 不详细讲解里面的代码, 其功能如下:
初始化堆栈指针 SP;
初始化程序计数器指针 PC;
设置堆、 栈的大小;
设置中断向量表的入口地址;
配置外部 SRAM 作为数据存储器(这个由用户配置, 一般的开发板可没有外部 SRAM) ;
调用 SystemInit() 函数配置 STM32 的系统时钟。
设置 C 库的分支入口“ __main” (最终用来调用 main 函数) ;
总之启动文件的作用是在外部定义一个SystemInit 函数设置 STM32 的时钟; STM32 上电后, 会执行 SystemInit 函数, 最后执行我们 C 语言中的 main 函数。
(2)CMSIS标准
CMSIS 就是统一各芯片厂商固件库内函数的名称, 比如在系统初始化的时候使用的是 SystemInit 这个函数名, 那么 CMSIS 标准就是强制所有使用 Cortex 核设计芯片的厂商内固件库系统初始化函数必须为这个名字, 不能修改。 又比如对 GPIO 口输出操作的函数: GPIO_SetBits, 此函数名也是不能随便定义的。
(3)库目录及其文件介绍
ST公司基于CMSIS标准创建了一套基于STM32F10x的固件库,可以在ST公司的官网下载
3.1_htmresc 文件夹:
ST公司的LOGO图标,这个文件不用管
3.2Libraries文件夹
有两个子目录,CMIS文件夹用于存放CMSIS标准的文件,包括STM32启动文件、ARM Cortex内核文件和对应外设头文件stm32f10x.h。STM32F10x_StdPeriph_Driver 文件夹用于存放STM32 外设驱动文件,inc 目录用于存放外设的头文件, src 目录用于存放外设的源文件。 从这些源文件的命名就可以知道对应文件的功能, 比如stm32f10x_gpio.c 文件, 包含了对 STM32 的 GPIO 寄存器的操作函数等, 如果要对 STM32 GPIO 操作可以调用这个文件内的函数, 但是要记得添加对应的头文件,如 stm32f10x_gpio.h。 此文件夹内文件在后面创建库函数模板的时候会重点使用
十三:STM32时钟系统
(1):时钟树框图
时钟系统对于单片机是非常重要的,它能为单片机工作提供一个稳定的机器周期期从而使系统能够正常运行。 时钟系统犹如人的心脏, 一旦有问题整个系统就崩溃。STM32属于高级单片机,内部有很多外设,但不是所有的外设使用同一时钟频率工作,如RTC和WDG,它只需要30几KHz的时钟频率即可工作,所以内部时钟源就有很多选择。
STM32系统复位后首先进入SystemInit 函数进行时钟的设置, 将 STM32F1 系统时钟设置为 72MHz(开发板上使用的 STM32F103ZET6 最大可达到 72M(超频除外) ),然后进入主函数。 那么这个系统时钟大小如何得来, 其他外设的时钟又如何划分,这些问题都可以通过一张时钟树图找到答案, 只要理解好时钟树, STM32 一切时钟的来龙去脉就会非常清楚。
在 STM32 时钟系统中, 有 5 个重要的时钟源, 分别是 LSI、 LSE、 HSI、 HSE、PLL。 按照时钟频率分可分为高速时钟源和低速时钟源, 在这 5 个中 HSI, HSE 以及 PLL 属于高速时钟, LSI 和 LSE 属于低速时钟。 按照时钟来源可分为外部时钟源和内部时钟源, 外部时钟源就是在 STM32 晶振管脚处接入外部晶振的方式获取时钟源, 其中 HSE 和 LSE 是外部时钟源, 其他的是内部时钟源。 下面我们就按照上图中数字顺序来介绍。
(1) 图标 1 HSI 是内部高速时钟, RC 振荡器, 频率为 8MHz。 可作为系统时钟或 PLL 锁相环的输入。
(2) 图标 2 HSE 是外部高速时钟, 芯片的 23 和 24 引脚即为外部高速晶振管脚。 可通过外接一个频率范围是 4-16MHz 的时钟或者晶振, 普中开发板上接的是一个 8MHz 的外部晶振。 HSE 可以作为系统时钟和 PLL 锁相环输入, 还可以经过 128 分频后输入给 RTC。
(3) 图标 3 LSI 是内部低速时钟, RC 振荡器, 频率大约为 40K, 可供独立看门狗和 RTC 使用, 并且独立看门狗只能使用 LSI 时钟。
(4) 图标 4 LSE 是外部低速时钟, 我们开发板上 STM32 芯片的 PC14 和 PC15即为外部低速时钟管脚。 通常在此管脚上外接一个 32.768KHz 的晶振, 供 RTC使用。 我们开发板上已经外接了一个 32.768K 的晶振。
(5)图标 5 PLL 是锁相环, 用于倍频输出, 因为开发板外部高速晶振也只有 8M,而我们这块芯片的最大时钟频率是 72M, 因此可通过 PLL 锁相环来倍频。 从图标5 中可以看到, PLL 时钟输入源可选择为 HSI/2、 HSE 或者 HSE/2, 时钟源经过 2-16倍频后输入给 PLLCLK, 如果系统时钟选择由 PLLCLK 提供, 则 PLLCLK 最大值不要超过 72M。
PLL 时钟源的输入信号要先经过一个 PLLMUL 倍频器,将 HSE 或 HSI 倍频(2-16)后输入给 PLLCLK, 如果系统时钟源 SYSCLK 选择 PLLCLK作为它的来源, 则最大值不能超过 72M。 虽然可以做超频处理, 但会打破系统的稳定性, 这个是不划算的。 假如 PLLSRC 的时钟来源由 HSE 提供, 我们开发板使用的 HSE 是 8M 晶振, 经过 PLLMUL 9 倍频后可以输出 72M 时钟频率给 PLLCLK。总结: 如果我们选择 HSE 是 PLL 的时钟源, PLL 是 SYSCLK 的时钟源, 即 SYSCLK为 72MHz, 这个也是我们库函数模板中 SystemInit 所配置的最终系统时钟。
(A) MCO 是 STM32 的一个时钟输出 IO(PA8), 它可以选择一个时钟信号输出, 可以选择为 PLL 输出的 2 分频、 HSI、 HSE 或者系统时钟。 这个时钟可以用来给外部其他系统提供时钟源。
(B) RTC 时钟。 从图中线的流向可知, RTC 时钟来源可以是内部低速的 LSI时钟, 外部低速 LSE 时钟(32.768K) , 还可以通过 HSE 128 分频后得到。
(C) USB 时钟。 STM32 中有一个全速功能的 USB 模块, 其串行接口引擎需要一个频率为 48MHz 的时钟源, 该时钟源只能从 PLL 输出端获取, 可以选择为1.5 分频或者 1 分频, 也就是当需要使用 USB 模块时, PLL 必须使能, 并且PLLCLK 时钟频率配置为 48MHz 或 72MHz。
(D) SYSCLK 系统时钟。 它是 STM32 中绝大部分部件工作的时钟源。 它的时钟来源可以由 HSI、 HSE、 PLLCLK 提供, 相信大家选择 STM32F1 这种高级芯片,都希望有一个比较大的时钟频率, 因此选择 PLLCLK 作为系统时钟。 PLLCLK 又是从 HSE 或 HSI 经过 PLL 倍频得到。 根据前面 PLL 计算关系大家就可以算出系统时钟是多少。(E) 其他所有外设。 从时钟图上可以看出, 其他所有外设的时钟最终来源都是 SYSCLK。 SYSCLK 通过 AHB 分频器分频后送给各模块使用。 这些模块包括:
①、 AHB 总线、 内核、 内存和 DMA 使用的 HCLK 时钟。
②、 通过 8 分频后送给 Cortex 系统定时器时钟, 即 SysTick。
③、 直接送给 Cortex 的空闲运行时钟 FCLK。
④、 送给 APB1 分频器。 APB1 分频器输出一路供 APB1 外设使用(PCLK1,最大频率 36MHz), 另一路送给定时器(Timer)1、 2 倍频使用。
⑤、 送给 APB2 分频器。 APB2 分频器分频输出一路供 APB2 外设使用(PCLK2, 最大频率 72MHz), 另一路送给定时器(Timer)1 倍频器使用。
⑥、 送给 ADC 分频器。 ADC 分频器经过 2、 4、 6、 8 分频后送给 ADC1/2/3 使用, ADC 最大频率为 14M。
⑦、 二分频后送给 SDIO 使用。
其中需要理解的是 APB1 和 APB2 的区别, APB1 上面连接的是低速外设,包括电源接口、 备份接口、 CAN、 USB、 I2C1、 I2C2、 UART2、 UART3 等, APB2上面连接的是高速外设包括 UART1、 SPI1、 Timer1、 ADC1、 ADC2、 GPIO 等。大家可以简单这样记忆: 2>1, 所以 APB2 的速度大于 APB1 的速度。
在时钟树图中我们还可以得到一个重要信息, 大多数有关时钟输出部分都有一个使能控制, 比如 AHB 总线、 APB1 外设、 APB2 外设、 内核时钟等。 当需要使用某个时钟的时候一定要开启它的使能, 否则将不工作。 在前面我们介绍库函数点亮一个 LED 实验的时候就使能了 GPIO 的外设时钟, 如果不开启, LED 将不工作
(2)时钟配置函数
2.1SystemInit()
调用 SystemInit 函数之后, 首先是选择 HSI 作为系统时钟。在设置完相关寄存器后才换成 HSE 作为系统时钟, 接下来 SystemInit 函数内部会调用 SetSysClock()函数。 这个函数内部是根据宏定义设置系统时钟频率。 函数如下:
static void SetSysClock(void) { # ifdef SYSCLK_FREQ_HSE SetSysClockToHSE();
#elif defined SYSCLK_FREQ_24MHz SetSysClockTo24();
#elif defined SYSCLK_FREQ_36MHz SetSysClockTo36();
#elif defined SYSCLK_FREQ_48MHz SetSysClockTo48();
#elif defined SYSCLK_FREQ_56MHz SetSysClockTo56();
#elif defined SYSCLK_FREQ_72MHz SetSysClockTo72();
#endif
在system_stm32f10x.c 文件的开头就有对此宏定义, 系统默认的宏定义是72MHz, 如下:
#define SYSCLK_FREQ_72MHz 72000000
如果你要设置为 36MHz, 只需要注释掉上面代码, 然后加入下面代码即可:
#define SYSCLK_FREQ_36MHz 36000000
根据该函数内部实现过程可知, 直接调用 SetSysClockTo72()函数, 此函数功能是将系统时钟 SYSCLK 设置为 72M, AHB 总线时钟设置为 72M, APB2 总线时钟设置为 72M, APB1 总线时钟设置为 36M, PLL 时钟设置为 72M。 函数具体实现可以打开库函数查看。大家只要知道 SystemInit 函数执行完, 时钟大小设置如下:
SYSCLK(系统时钟) =72MHz
AHB 总线时钟(HCLK=SYSCLK) =72MHz
APB1 总线时钟(PCLK1=SYSCLK/2) =36MHz
APB2 总线时钟(PCLK2=SYSCLK/1) =72MHz
PLL 主时钟 =72MHz
这些时钟值要记住。
2.2stm32f10x_rcc.c and .h
stm32f10x_rcc.h 文件中有很多的宏定义和时钟使能函数的声明。 这些时钟函数可大致分为三类。 一类是外设时钟使能函数, 一类是时钟源和倍频因子配置函数, 还有一类是外设复位函数。 当然还有几个获取时钟源配置的函数。 下面就来简单介绍下这些函数的使用。
时钟使能函数包括外设时钟使能和时钟源使能。
2.2.1外设时钟使能相关函数
void RCC_AHBPeriphClockCmd(uint32_t RCC_AHBPeriph, FunctionalState NewState);
void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState);
void RCC_APB1PeriphClockCmd(uint32_t RCC_APB1Periph, FunctionalState NewState);
上面 3 个时钟使能函数也正是 STM32 的 3 条总线 。 由于 STM32 的外设都是挂接在 AHB 和 APB 总线上的, 所以要使能外设时钟, 也就是使能对应外设所挂接的总线时钟。 比如 GPIO 外设它是挂接在 APB2 总线上的, 如果使用 GPIO 外设, 就需要先调用
void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState);
函数使能 APB2 时钟。
2.2.2时钟源使能函数
STM32 有 5 大类时钟源:
void RCC_HSICmd(FunctionalState NewState);
void RCC_LSICmd(FunctionalState NewState);
void RCC_PLLCmd(FunctionalState NewState);
void RCC_RTCCLKCmd(FunctionalState NewState);
这些函数都是用来使能相应的时钟源, 比如我们要使能 PLL 时钟, 那么就调用 RCC_PLLCmd 函数, 函数有一个形参, 和前面外设时钟的第二个参数一样, 如果为 ENABLE 表示使能, DISABLE 表示失能。
另外一类时钟函数——时钟源和倍频因子配置函数。 这类函数主要用来选择相应的时钟源和配置时钟倍频因子, 比如系统时钟, 它可以由HSE、 HSI 或者 PLLCLK 作为它的时钟源, 具体选择哪个, 就是通过时钟源配置函数实现。 比如我们设置 HSE 作为系统时钟源, 那么调用的函数就是RCC_SYSCLKConfig(RCC_SYSCLKSource_HSE);//配置时钟源为 HSE
在前面也介绍了 APB1 的时钟频率是 HCLK(又称Core Clock or AHB ClockHCLK是核心时钟,也称为AHB(Advanced High-performance Bus)时钟,它为微控制器的CPU核心提供时钟信号) 的 2 分频。 那么可以调用下面这个函数来实现:
RCC_PCLK1Config(RCC_HCLK_Div2);//设置低速 APB1 时钟(PCLK1)
时钟倍频因子配置函数主要用来修改系统的时钟频率。
2.2.3外设复位函数
void RCC_APB1PeriphResetCmd(uint32_t RCC_APB1Periph, FunctionalState NewState);
void RCC_APB2PeriphResetCmd(uint32_t RCC_APB2Periph, FunctionalState NewState);
在 STM32F10x 高容量的芯片中没有 RCC_AHBPeriphResetCmd 函数。 这类函数与前面讲解的外设时钟使能函数用法一样, 只不过外设时钟使能函数是用于使能外设时钟, 而这类函数是用于外设复位.
十四:STM32位带操作
(1)位带操作
51 单片机使用过位操作, 通过关键字 sbit 对单片机 IO 口进行位定义。 但是 STM32 没有这样的关键字, 而是通过访问位带别名区来实现。位带别名区(Bit-Band Alias Region)是ARM Cortex-M系列处理器中的一种内存映射技术,它允许对单个位进行直接的读写操作。在STM32微控制器中,位带操作主要涉及两个区域:SRAM区和外设区,每个区域都有对应的位带别名区。
位带别名区的设计主要是为了方便对位带区单个比特位进行读写操作。在某些应用场景下,需要频繁地对位带区某个特定的比特位进行读写操作,位带别名区的出现,就是为了解决这个问题。它通过为位带区每个比特位分配一个独立的物理地址,以便能够直接对某个特定的比特位进行读写操作,从而避免了手动进行位运算的麻烦和出错风险
STM32F1 中有两个区域支持位带操作, 一个是 SRAM 区的最低 1MB 范围, 一个是片内外设区的最低 1MB 范围(APB1、 APB2、 AHB 外设)。
SRAM 的最低 1MB 区域, 地址范围是 0X2000 0000-0X200F FFFF。 片内外设最低 1MB 区域, 地址范围是 0X4000 0000-0X400F FFFF, 在这个地址范围内包括了 APB1、 APB2、 AHB 总线上所有的外设寄存器。
在 SRAM 区中还有 32MB 空间, 其地址范围是 0X2200 0000-0X23FF FFFF, 它是 SRAM 的 1MB 位带区膨胀后的位带别名区, 前面已经说过位带操作, 要实现位操作即将每一位膨胀成一个 32 位的字, 因此 SRAM 的 1MB 位带区就膨胀为 32MB的位带别名区, 通过访问位带别名区就可以实现访问位带中每一位的目的。
片内外设区的 32MB 的空间也是一样的原理。 片内外设区的 32MB 地址范围是0X4200 0000-0X43FF FFFF。通常使用位带操作都是在外设区,在外设区中应用比较多的也就是GPIO 外设, SRAM 区内很少使用位操作。
位带操作将位带区中的每一位膨胀成位带别名区中的一个 32 位的字, 通过访问位带别名区中的字就实现了访问位带区中位的目的。 因此我们就可以使用指针来访问位带别名区的地址, 从而实现访问位带区内位的目的。
1.1位带别名区与位带地址转换方式:
外设位带别名区地址,对于片上外设位带区的某个比特, 记它所在字节的地址为 A,位序号为 n, n值的范围是 0-7, 则该比特在别名区的地址为:
AliasAddr=0x42000000+(A0x40000000)*8*4 +n*4
0x42000000 是外设位带别名区的起始地址, 0x40000000 是外设位带区的起始地址, (A-0x40000000) 表示该比特前面有多少个字节, 一个字节有 8 位, 所以*8, 一个位膨胀后是 4 个字节, 所以*4, n 表示该比特在 A 地址的序号, 因为一个位经过膨胀后是四个字节, 所以也*4。
(2) SRAM 位带别名区地址
对于 SRAM 位带区的某个比特, 记它所在字节的地址为 A,位序号为 n, n 值的范围是 0-7, 则该比特在别名区的地址为:
AliasAddr= =0x22000000+ (A-0x20000000)*8*4 +n*4
0x22000000 是 SRAM 位带别名区的起始地址, 0x20000000 是 SRAM 位带区的起始地址, (A-0x20000000) 表示该比特前面有多少个字节, 一个字节有 8 位,所以*8, 一个位膨胀后是 4 个字节, 所以*4, n 表示该比特在 A 地址的序号,因为一个位经过膨胀后是四个字节, 所以也*4。
上面我们已经把外设位带别名区地址和 SRAM 位带别名区地址使用公式表示出来, 为了操作方便, 我们将这两个公式进行合并, 通过一个宏来定义, 并把位带地址和位序号作为这个宏定义的参数。 公式如下:
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2))
addr & 0xF0000000 是为了区分我们操作的是 SRAM 还是外设, 实际上就是获取最高位的值是 4 还是 2。 如果操作的是外设, 那么 addr & 0xF0000000 结果就是 0x40000000, 后面+0x2000000 就等于 0X42000000, 0X42000000 是外设别名区的起始地址。 如果操作的是 SRAM, 那么 addr & 0xF0000000 结果就是0x20000000, 后面+0x2000000 就等于 0X22000000, 0X22000000 是 SRAM 别名区的起始地址。
addr & 0x000FFFFF 屏 蔽 了 高 三 位 , 相 当 于 减 去 0X20000000 或 者0X40000000, 屏蔽高三位是因为 SRAM 和外设的位带区最高地址是 0X200F FFFF和 0X400F FFFF, SRAM 或者外设位带区上任意地址减去其对应的起始地址, 总是低 5 位 有 效 , 所 以 这 里 屏 蔽 高 3 位 就 相 当 于 减 去 了 0X20000000 或 者0X40000000。 <<5 相当于*8*4, <<2 相当于*4, 其作用在前面已经分析过。
最后就可以通过指针形式来操作这些位带别名区地址, 实现位带区对应位的操作。 代码如下:
//把 addr 地址强制转换为 unsigned long 类型的指针
#define MEM_ADDR(addr) | *((volatile unsigned long | *)(addr)) |
//把位带别名区内地址转换为指针 , 获取地址内的数据 | ||
#define BIT_ADDR(addr, bitnum) | MEM_ADDR(BITBAND(addr, bitnum)) |
这里说明下 volatile 关键字, volatile 提醒编译器它后面所定义的变量随时都有可能改变, 因此编译后的程序每次需要存储或读取这个变量的时候, 都会直接从变量地址中读取数据。 如果没有 volatile 关键字, 则编译器可能优化读取和存储, 可能暂时使用寄存器中的值, 如果这个变量由别的程序更新了的话,将出现不一致的现象。 更详细的内容大家可以百度查找。
十五:SysTick系统定时器
STM32F1 内部 SysTick 系统定时器,通过配置 SysTick 实现精确延时。 学习可以参考《Cortex M3 权威指南(中文)》 chpt13 Cortex-M3 。
(1) SysTick 定时器介绍
SysTick(System Tick Timer )系统滴答定时器。 它是 Cortex-M3 内核的一个外设,被嵌入在 NVIC 中。 它是24 位向下递减的定时器, 每计数一次所需时间为1/SYSTICK, SYSTICK 是系统定时器时钟, 它可以直接取自系统时钟, 还可以通过系统时钟 8 分频后获取, 当定时器计数到 0 时, LOAD 寄存器自动重装定时器初值, 重新向下递减计数, 如此循环往复。 如果开启 SysTick 中断的话, 当定时器计数到 0, 将产生一个中断信号。 因此只要知道计数的次数就可以准确得到它的延时时间。
SysTick 定时器通常应用在操作系统中, 为其提供时钟周期。
(2) SysTick 定时器操作
在 STM32F1 库函数中, 并没有提供相应的 SysTick 定时器配置函数,操作 SysTick 定时器就需要了解它的寄存器, 只有 4 个, 分别是 CTRL、 LOAD、 VAL、 CALIB。 这些寄存器都可以在《Cortex M3 权威指南(中文)》查阅。
2.1SysTick定时器寄存器
2.1.1CTRL 寄存器
CTRL 是 SysTick 定时器的控制及状态寄存器。 其相应位功能如下
CLKSOUTCE 位是用于选择 SysTick 定时器时钟来源, 如果该位为 1, 表示其时钟是由系统时钟直接提供即 72M。 如果为 0, 表示其时钟是由系统时钟八分频后提供即 72/8=9M。
2.1.2LOAD 寄存器
LOAD 是 SysTick 定时器的重装载数值寄存器。 其相应位功能如下
因为 STM32F1 的 SysTick 定时器是一个 24 位递减计数器, 因此重装载寄存器中只使用到了低 24 位, 即 bit0-bit23。 当系统复位时, 其值为 0。
2.1.3VAL 寄存器
VAL 是 SysTick 定时器的当前数值寄存器。 其相应位功能如下
同样只有 bit0-bit23 有效, 复位时值为 0
2.1.4CALIB 寄存器
CALIB 是 SysTick 定时器的校准数值寄存器。 其相应位功能如下
2.2SysTick定时器操作步骤
SysTick 定时器的操作可以分为 4 步:
(1) 设置 SysTick 定时器的时钟源。
(2) 设置 SysTick 定时器的重装初始值(如果要使用中断的话, 就将中断使能打开) 。
(3) 清零 SysTick 定时器当前计数器的值。
(4) 打开 SysTick 定时器。