文章目录
- 前言
- 一、GPIO
-
- 1.什么是GPIO
- 2.GPIO的基本结构
- 3.GPIO的功能
- 4.GPIO的八种工作模式
- [5.APB 外设总线](#5.APB 外设总线)
- 7.GPIO使用流程
- 8.GPIO常用函数
- 9.GPIO工作模式的宏定义
- 二、中断
- 三、定时器
-
- 1.什么是TIM
- 2.定时器类型
- [3. 定时器的时钟源](#3. 定时器的时钟源)
- 4.定时器计数模式
- 5.定时中断基本结构
- 6.计数器计数频率和计数器溢出频率计算公式
- 7.RCC时钟树
- 8.定时器的主要功能
- 9.定时器中断配置流程
- 10.输出比较
- 11.输入捕获
- 12.输出比较和输入捕获的区别
- 13.定时器主从模式及应用场景
- 14.编码器接口
- 四、ADC
- 五、DMA
- 六、串口
-
- 1.概述
- 2.波特率、比特率,为什么双方波特率要相同
- 3.串口的参数及时序
- 4.流控
- 5.USART基本结构
- 6.同步通信和异步通信
- [7.UART 与 USART](#7.UART 与 USART)
- 七、I2C
- 七、SPI
-
- 1.概述
- 2.起始终止信号
- 3.SPI的四种模式
- 4.SPI时序
- 5.SPI外设
- [6.SPI的时钟是谁提供的 、数据采样时间点根据什么来决定、 为什么spi会有四种模式吗 而不是统一一个模式](#6.SPI的时钟是谁提供的 、数据采样时间点根据什么来决定、 为什么spi会有四种模式吗 而不是统一一个模式)
- 八、Unix、BKP、RTC
- 九、PWR
- 十、开门狗
-
- 1.概述
- 2.IWDG框图
- 3.IWDG键寄存器
- 4.WWDG结构框图及工作特性
- 5.IWDG超时时间和WWDG超时时间
- [6. IWDG与WWDG对比](#6. IWDG与WWDG对比)
- 十一、FLASH
前言
内容主要是根据B站江科大的STM32 视频,整理的一份笔记
一、GPIO
1.什么是GPIO
GPIO是通用的输入输出端口 ,是可编程控制的通用引脚 ,可通过软件配置为输入/输出引脚
2.GPIO的基本结构

寄存器:配置引脚模式、存放输出数据、读取输入数据。
驱动器:当引脚被设为输出模式时,驱动器负责将内部输出数据转换为强大的高电平(VDD)或低电平(GND),驱动外部电路
3.GPIO的功能
输入功能 :读取引脚电 ,例如:用来获取按键的信息输入
输出功能 :输出高低电平
复用功能 :部分GPIO可配置为特殊功能(如UART、SPI、I2C)
4.GPIO的八种工作模式
数字输入可读取引脚的高低电平,数字输出可输出引脚的高低电平
| 模式名称 | 性质 | 特征 |
|---|---|---|
| 浮空输入 | 数字输入 | 可读取引脚电平,悬空时电平状态不确定 |
| 上拉输入 | 数字输入 | 可读取引脚电平,内部上拉电阻,悬空时默认高电平 |
| 下拉输入 | 数字输入 | 可读取引脚电平,内部下拉电阻,悬空时默认低电平 |
| 模拟输入 | 模拟输入 | GPIO功能无效 ,引脚直接接入内部ADC模块 |
| 开漏输出 | 数字输出 | 可输出引脚电平,输出高电平为高阻态,低电平直接连接VSS(地) |
| 推挽输出 | 数字输出 | 可输出引脚电平,输出高电平连接VDD(电源),低电平连接VSS |
| 复用开漏输出 | 数字输出 | 由片上外设控制,高电平为高阻态,低电平连接VSS |
| 复用推挽输出 | 数字输出 | 由片上外设控制,高电平连接VDD,低电平连接VSS |
浮空/上拉/下拉输入

模拟输入

推挽/开漏输出

推挽 vs 开漏:
推挽: VDD 开漏: VDD
│ │
[P-MOS] [外部Rp]
│ │
───┤ 输出 ───┤ 输出
│ │
[N-MOS] [N-MOS]
│ │
GND GND
推挽: 能主动输出高和低, 驱动能力强
开漏: 只能拉低, 高电平靠外部上拉
为什么输入模式下,输出模式无效,而输出模式下,输入模式有效?
- 输入模式下输出无效:输出驱动器关闭(高阻态),无法提供电流改变引脚电平。
- 输出模式下输入有效:输入缓冲器始终连通,可以直接读回当前输出的逻辑电平
复用推挽输出/开漏输出

5.APB 外设总线

APB1(低速外设总线,频率通常为36MHz)
-
通用定时器 TIM2~TIM7
-
通信接口:USART2/3、UART4/5、I2C1/2、SPI2/3、CAN、USB
-
其他:独立看门狗(IWDG)、窗口看门狗(WWDG)、实时时钟(RTC)、电源管理(PWR)、数模转换器(DAC)
APB2(高速外设总线,频率可达72MHz)
- 所有GPIO(GPIOA~GPIOG)
- 通信接口:USART1、SPI1
- 模拟与高级定时器:ADC1/2/3、高级定时器 TIM1/TIM8
- 其他:外部中断(EXTI)、复用IO(AFIO)
7.GPIO使用流程
RCC常用的外设时刻控制函数
c
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);
1.使用RCC开启GPIO的时钟
示例
c
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
2.使用GPIO_init函数初始化GPIO
-
GPIO的结构体参数
GPIO_Pin:选择具体的引脚(如GPIO_Pin_0)。 GPIO_Mode:设置工作模式(输入/输出/复用/模拟)。 GPIO_Pull:配置上拉或下拉电阻(上拉、下拉、无上下拉)。 GPIO_Speed:输出驱动速度(低速、中速、高速、超高速)
示例
c
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
3.使用输入或输出的函数控制GPIO口
c
GPIO_ResetBits(GPIOA, GPIO_Pin_0);
8.GPIO常用函数

初始化和复位
GPIO_DeInit:复位指定 GPIO 端口的所有寄存器到默认值。
GPIO_AFIODeInit:复位 AFIO(复用功能 IO)外设。
GPIO_Init:根据结构体配置 GPIO 引脚的模式、速度、上下拉等。
GPIO_StructInit:给 GPIO_InitTypeDef 结构体填充安全的默认值。
读取函数
GPIO_ReadInputDataBit:读取指定引脚输入电平(0/1)。
GPIO_ReadInputData:读取整个端口的输入数据(16位值)。
GPIO_ReadOutputDataBit:读取指定引脚输出数据寄存器中的值(即上一次输出值)。
GPIO_ReadOutputData:读取整个端口的输出数据寄存器。
写入函数
GPIO_SetBits:将指定引脚输出为高电平(置1)。
GPIO_ResetBits:将指定引脚输出为低电平(清0)。
GPIO_WriteBit:按指定 BitAction(高/低)写入单个引脚。
GPIO_Write:一次性写入整个端口 16 位输出值。
9.GPIO工作模式的宏定义
1.GPIO_Mode_AIN 是模拟输入。
2. GPIO_Mode_IN_FLOATING 是浮空输入。
3. GPIO_Mode_IPD 是下拉输入。
4. GPIO_Mode_IPU 是上拉输入。
5.GPIO_Mode_Out_OD 是开漏输出。
6. GPIO_Mode_Out_PP 是推挽输出。
7.GPIO_Mode_AF_OD 是复用开漏。
8. GPIO_Mode_AF_PP 是复用推挽
二、中断
1.什么是中断
在主程序运行过程中,出现了特定的中断触发条件(中断源),使得CPU暂停当前正在运行的程序,转而去处理中断程序,处理完成后又返回原来被暂停的位置继续运行
中断系统
- 中断优先级
当有多个中断源同时申请中断时,CPU会根据中断源的轻重缓急进行裁决,优先响应更加紧急的中断源 - 中断嵌套
当一个中断程序正在运行时,又有新的更高优先级的中断源申请中断,CPU再次暂停当前中断程序,转而去处理新的中断程序,处理完成后依次进行返回
2.中断执行流程

3.STM32中断
68个可屏蔽中断通道,包括外部中断(EXTI)、定时器中断()、DMA中断、UART中断、SPI中断、I2C中断、ADC中断等- 使用
NVIC统一管理中断,每个中断通道都拥有16个可编程的优先等级,可对优先级进行分组,进一步设置抢占优先级和响应优先级
4.NVIC的基本结构

一个外设可能会同时占用多个中断通道,所以这里有n条线
5.NVIC优先级分组
这个分组方式整个芯片只能用一种,所以按理说这个分组的代码整个工程只需要执行一次就行了
- NVIC的中断优先级由
优先级寄存器的4位(0~15)决定,这4位可以进行切分,分为高n位的抢占优先级和低4-n位的响应优先级 - 抢占优先级高的可以
中断嵌套,响应优先级高的可以优先排队,抢占优先级和响应优先级均相同的按中断号排队
| 分组方式 | 抢占优先级 | 响应优先级 |
|---|---|---|
| 分组0 | 0位,取值为0 | 4位,取值为0~15 |
| 分组1 | 1位,取值为0~1 | 3位,取值为0~7 |
| 分组2 | 2位,取值为0~3 | 2位,取值为0~3 |
| 分组3 | 3位,取值为0~7 | 1位,取值为0~1 |
| 分组4 | 4位,取值为0~15 | 0位,取值为0 |
这个优先级的数是值越小,优先级越高,0就是最高的优先级,抢占优先级优先于响应优先级
6.EXTI外部中断
1.什么是EXTI
-
EXTI(Extern Interrupt)外部中断
-
作用:EXTI可以监测指定GPIO口的电平信号,当其指定的GPIO口产生电平变化时,EXTI将立即向NVIC发出中断申请,经过NVIC裁决后即可中断CPU主程序,使CPU执行EXTI对应的中断程序
-
支持的触发方式:上升沿/下降沿/双边沿/软件触发
-
支持的GPIO口:所有GPIO口,但相同的Pin不能同时触发中断
-
通道数:16个GPIO_Pin,外加PVD输出、RTC闹钟、USB唤醒、以太网唤醒(属于电源管理或系统唤醒相关的中断/事件,常用于低功耗模式(睡眠、停止、待机)下唤醒CPU)
-
触发响应方式:中断响应/事件响应
中断响应:CPU暂停当前工作,执行中断服务程序 事件响应:CPU不参与,不会触发中断,而是触发别的外设操作(比如,触发ADC转换、触发),属于外设之间的联合工作
2.EXTI基本结构

这里20线到其它外设,是用来事件响应的
3.AFIO复用IO
- AFIO主要用于引脚复用功能的选择和重定义
- 在STM32中,AFIO主要完成两个任务:复用功能引脚重映射、中断引脚选择
复用功能引脚重映射
把默认的复用功能的引脚换到重定义的这个位置来,就是用AFIO来完成的,这也是AFIO的一大主要功能

AFIO选择中断引脚的结果图

4.配置流程
按EXTI基本结构框图打通即可
1.开启外设时钟
- 1.1开始检测的GPIO口的时钟
示例
c
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
- 1.2开启AFIO外设的是时钟
c
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
2.初始化GPIO

3.使用AFIO函数设置中断引脚
AFIO函数放在GPIO库里
示例
c
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);
4.初始化EXTI

5.配置NVIC
这个优先级分组方式整个芯片只能用一种,所以按理说这个分组的代码整个工程只需要执行一次就行了

6.中断服务函数
中断处理完之后要记得清除中断标志位,否则会反复进入中断,导致程序卡死或异常

三、定时器
1.什么是TIM
- TIM(Timer)定时器
- 定时器可以对输入的时钟进行计数,并在计数值达到设定值时触发中断
- 16位计数器、预分频器、自动重装寄存器的时基单元,在72MHz计数时钟下可以实现最大59.65s的定时
- 不仅具备基本的定时中断功能,而且还包含内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等多种功能
- 根据复杂度和应用场景分为了高级定时器、通用定时器、基本定时器三种类型
2.定时器类型
| 类型 | 编号 | 总线 | 功能 |
|---|---|---|---|
| 高级定时器 | TIM1、TIM8 | APB2 | 拥有通用定时器全部功能,并额外具有重复计数器、死区生成、互补输出、刹车输入等功能 |
| 通用定时器 | TIM2、TIM3、TIM4、TIM5 | APB1 | 拥有基本定时器全部功能,并额外具有内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等功能 |
| 基本定时器 | TIM6、TIM7 | APB1 | 拥有定时中断、主模式触发DAC的功能 |
STM32F103C8T6定时器资源:TIM1、TIM2、TIM3、TIM4
基本定时器框图

预分频之前,连接的是基准计数时钟的输入,由于基本定时器只能选择内部时钟,内部是时钟的来源是RCC_TIMxCLK,这里的频率值一般都是系统的主频72MHz,所以通向时机单元的计数基准频率就是72MHz
时机单元
-
预分频器,它可以对这个72MHz的计数时钟进行预分频,这个预分频器是16位的,所以最大值可以写65535,也就是65536分频。预分频器就是对输入的基准频率提前进行一个分频的操作如果写0,这时就是不分频或者1分频,这时候输出频率=输入频率=72MHz 如果写1,那就是2分频,输出频率=输入频率/2=36HMz 如果写3,以此类推 所以预分频的值和实际的分频系数相差了1
计数器,计数器可以对分频之后的计数时钟进行计数,计数时钟每来一个上升沿,计数器的值就加1,这个计数器也是16位的,所以这里的值可以从0一直加到65535,如果在加的话,就回到0,所以计数器的值在计时的过程中会不断地自增运行,当自增运行到目标值时,产生中断,那就完成了定时任务。自动重装寄存器,存储目标的寄存器,它也是16位的,它存的是我们写入的计数目标
在运行的过程中,计数值不断自增,自动重装值是固定的目标,当计数值等于自动重装值时,也就是计时时间到了,那它就会产生一个中断信号,并且清零计数器,计数器就会自动开始下一次的计数计时

更新中断:计数值等于自动重装值产生的中断,这个更新中断之后就会通往NVIC,再配置好NVIC的定时器通道,定时器的更新中断就能够得到CPU的响应了
更新事件:计数值等于自动重装值产生的事件,更新事件不会触发中断,但可以触发内部其他电路的工作
通用寄存器框图
高级定时器框图

3. 定时器的时钟源
| 时钟源 | 信号路径 | 典型用途 |
|---|---|---|
| 内部时钟(CK_INT) | 内部 APB1/APB2总线时钟(PCLK)提供 | 基本定时中断 |
| 外部模式1(Tl1/Tl2) | 定时器的输入通道 TIMx_CH1 或 TIMx_CH2 上的外部信号 | 脉冲计数、频率测量 |
| 外部模式2(ETR) | ETR 引脚 | 外部高速脉冲计数 |
| 内部触发( ITRx) | 其他定时器的 TRGO | 定时器同步/级联 |
| 编码器模式(TI1/TI2) | TI1FP1 + TI2FP2(CH1 和 CH2 同时接 A/B 相) 硬件自动根据相位决定(递增/递减) | 旋转编码器测速与方向检测 |
补充说明
框图参考定时器类型中的通用定时器框图
1.内部时钟: CK_INT 直接供给到 CK_PSC 前的选择器,是默认时钟。
2.外部时钟模式2:路径是 ETR → 滤波器 → 边沿/预分频 → ETRF,作为 CK_PSC 的时钟
3.外部时钟模式1 :选择一个触发输入(TRGI)作为时钟。
TRGI 的来源包括:
TI1F_ED (CH1 边沿检测)
TI1FP1,TI2FP2
ETRF 外部ETRF输出信号
ITR0~ITR3 其他定时器
4.编码器模式:实际上是外部时钟模式1的特化,同时使用 TI1FP1 和 TI2FP2 两个信号,通过硬件自动处理方向,不需要软件干预。
4.定时器计数模式
| 模式 | 计数方向 | 更新事件产生时刻 |
|---|---|---|
| 向上计数 | 0 → ARR → 0(重新) | ARR → 0 时 |
| 向下计数 | ARR → 0 → ARR(重新) | 0 → ARR 时 |
| 中央对齐 | 0 → ARR → 0 → ARR ... | 到达 ARR 和到达 0 时 |
基本定时器只接受向上计数
注意误区
更新事件 是由定时器硬件自动产生的:每当计数器溢出(向上计数达到 ARR 后归零,或向下计数达到 0 后重载 ARR,或中央对齐到达端点)时,一定会触发一个更新事件。
而更新中断需要你额外使能 ,CPU 才会在发生更新事件时进入中断服务函数。
5.定时中断基本结构

如果是高级定时器的话,还会多一个重复计数器。同时这里的中断信号会在状态寄存器里置一个中断标志位,这个标志位会通过中断输出控制,到NVIC申请中断。
中断输出控制就是一个中断输出的允许位,如果需要某个中断,就允许一下。
6.计数器计数频率和计数器溢出频率计算公式
预分频时序

计数器计数频率:CK_CNT = CK_PSC / (PSC + 1)
计数时序

计数器溢出频率:CK_CNT_OV = CK_CNT / (ARR + 1)
= CK_PSC / (PSC + 1) / (ARR + 1)
计数器无预装时序
没有影子寄存器

计数器有预装时序
由影子寄存器

有无影子寄存器的区别
| 场景 | 有影子寄存器 | 无影子寄存器 |
|---|---|---|
| 写入 PSC/ARR/CCR 的时机 | 写入的是影子寄存器(缓冲) | 直接写入工作寄存器 |
| 新值生效时刻 | 下一个 更新事件(UEV) 发生时(溢出边界) | 立即生效(当前周期中间) |
| 对当前计数周期的影响 | 无影响,当前周期仍按旧值运行,周期结束时才切换 | 有影响,计数目标会以新值为目标计数 |
7.RCC时钟树

8.定时器的主要功能
(1) 定时中断
可配置定时器在设定时间后触发中断,实现周期性任务,如LED 闪烁。
(2) PWM(脉宽调制)
通过设置占空比,控制输出波形,用于电机控制、LED 亮度调节等。
(3) 输入捕获
记录外部信号的到达时间,例如测量信号频率或脉冲宽度。每个高级定时器和通用定时器都拥有4个输入捕获通道。
(4) 输出比较
在特定时间点改变输出状态,实现精确的事件触发或波形生成。
(5) 编码器接口
读取增量式编码器的脉冲信号,进行位移或角度测量,广泛用于运动控制。
9.定时器中断配置流程
按定时中断基本结构框图打通即可
1.使能定时器时钟
2.选择时机单元的时钟源
3.配置时机单元
4.配置输出中断控制,允许更新中断输出到NVIC
5.配置NVIC,在NVIC中打开定时器中断的通道,并分配一个优先级
6.运行控制,使能定时器
7.编写中断服务函数
示例


高级定时器功能------重复计数器注解
当 TIM_RepetitionCounter = N 时:
定时器计数器会连续溢出 N+1 次。
在经历了前 N 次溢出后,不会产生更新事件或中断。
只有在第 N+1 次溢出时,才会产生一个更新事件或中断。
解释时机单元初始化完了,为什么会触发更新事件

定时器时钟源选择外部时钟
函数原型
cpp
void TIM_ETRClockMode2Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler,
uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter);
TIM_ETRClockMode2Config 参数简洁总结:
-
TIMx:选择定时器(如 TIM2)
-
预分频器:对外部脉冲分频(OFF=1个脉冲计1次,DIV2=2个脉冲计1次...)
-
极性选择:用于选择在外部信号的上升沿还是下降沿进行计数。选择上升沿(NonInverted)或下降沿(Inverted)计数
-
数字滤波器:用于滤除外部输入信号上的高频噪声和抖动。(工作原理:其值定义了采样频率和连续有效采样次数,只有连续多次采样结果都一致时,信号变化才被认为有效。)0x00~0x0F,数值越大滤波越强(抗噪),信号延迟越大
示例
其余相同

10.输出比较
1.概述
- OC(Output Compare)输出比较
- 输出比较可以通过比较CNT与CCR寄存器值的关系,来对输出电平进行置1、置0或翻转的操作,用于输出一定频率和占空比的PWM波形
- 每个高级定时器和通用定时器都拥有4个输出比较通道
- 高级定时器的前3个通道额外拥有死区生成和互补输出的功能
2.PWM概述
-
PWM(Pulse Width Modulation)脉冲宽度调制
-
在具有
惯性的系统中,可以通过对一系列脉冲的宽度进行调制,来等效地获得所需要的模拟参量,常应用于电机控速等领域PWM参数: 频率 = 1 / TS 占空比 = TON / TS 分辨率 = 占空比变化步距
PWM输出的原理
PWM(脉冲宽度调制)是一种通过改变脉冲信号的占空比(高电平时间与周期的比值)来模拟模拟信号的技术。

3.输出比较模式
输出比较模式决定了当计数器(CNT)与捕获/比较寄存器(CCR)的值匹配时,参考信号(REF)的电平行为。以下是常见的输出比较模式:
| 模式 | 描述 |
|---|---|
| 冻结 | CNT=CCR时,REF保持为原状态 |
| 匹配时置有效电平 | CNT=CCR时,REF置有效电平 |
| 匹配时置无效电平 | CNT=CCR时,REF置无效电平 |
| 匹配时电平翻转 | CNT=CCR时,REF电平翻转 |
| 强制为无效电平 | CNT与CCR无效,REF强制为无效电平 |
| 强制为有效电平 | CNT与CCR无效,REF强制为有效电平 |
| PWM模式1 | 向上计数(常用) :CNT<CCR时,REF置有效电平,CNT≥CCR时,REF置无效电平 向下计数:CNT>CCR时,REF置无效电平,CNT≤CCR时,REF置有效电平 |
| PWM模式2 | 向上计数 :CNT<CCR时,REF置无效电平,CNT≥CCR时,REF置有效电平 向下计数:CNT>CCR时,REF置有效电平,CNT≤CCR时,REF置无效电平 |
说明:
- CNT:计数器当前值。
- CCR:捕获/比较寄存器值。
- REF:参考信号(输出比较的输出信号)。
- 有效电平/无效电平:通常指高电平/低电平,具体由极性配置决定。
- PWM模式1/2:是生成PWM波形最常用的两种模式,区别在于有效电平与计数比较关系的定义相反。
4.PWM基本结构

5.PWM频率、占空比、分辨率计算公式
分辨率是占空比变化的步距

这里定义的分辨率是占空比变化的最小步距
5.PWM配置流程
按PWM基本结构框图打通即可
1.使能相关时钟
2.选择时钟源,配置时机单元
3.配置输出比较单元
4.配置GPIO口,把PWM对应的GPIO口。初始化为复用推挽输出
5.运行控制,启动定时器

11.输入捕获
1.概述
- IC(Input Capture)输入捕获
- 输入捕获模式下,当通道输入引脚出现指定电平跳变时,当前CNT的值将被锁存到CCR中(
此时会产生捕获中断),可用于测量PWM波形的频率、占空比、脉冲间隔、电平持续时间等参数 - 每个高级定时器和通用定时器都拥有4个输入捕获通道
- 可配置为PWMI模式,同时测量频率和占空比
- 可配合主从触发模式,实现硬件全自动测量
2.频率测量


测频法适合测量高频信号,测周法适合测量低频信号
- 测频法,在闸门时间内,最好多一些上升沿,计次数量多一些,有助于减小误差,在计次很少时,误差会非常大,所以测频法要求信号频率要高一些
- 测周法,就要求频率要低一些,低频信号,周期比较长,计次就会比较多,有助于减小误差
中界频率
两种方法误差相等的频率点:
当 ( f_x > f_m ) 时,测频法更优。
当 ( f_x < f_m ) 时,测周法更优。
3. 输入捕获基本结构和PWMI基本结构
输入捕获基本结构

PWMI基本结构

PWMI模式,使用了两个通道同时捕获一个引脚,可以同时测量周期和占空比
- 首先,TI1FR1配置上升沿触发,触发捕获和清零CNT,正常捕获周期
- 其次,TI2FP2配置下降沿触发,通过交叉通道,去触发通道2的捕获单元
- CRR1就是一整个周期的计数值,CRR2就是高电平期间的计数值,CRR2/CRR1就是占空比
这里测量原理利用的就是频率测量中的测周法
其中的N,就是这里的CNT(这里的CNT是指一整个周期的计数),其中的标准频率fc,就是这里的计数器计时频率:CK_CNT = CK_PSC / (PSC + 1)
4.配置流程
按输入捕获基本结构框图打通即可
1.开启相关时钟
2.GPIO初始化,把GPIO配置成输入模式(一般上拉输入或浮空输入)
3.时钟源选择,配置时机单元
4.配置输入捕获单元
5.选择从模式的触发源
6.选择从模式触发之后执行的操作
7.使能定时器
示例

12.输出比较和输入捕获的区别
- 输出比较,引脚是
输出端口,输出比较是根据CNT与CCR的大小关系来执行输出动作 - 输入比较,引脚是
输入端口,输入捕获是接收到输入信号,将CNT的值锁存到CCR的动作
13.定时器主从模式及应用场景

主模式:可以将定时器内部的信号,映射到TRGO引脚,用于触发别的外设
从模式:接收其他外设的或者自身外设的一些信号,用于控制自身定时器的运行,也就是被别的信号控制,从模式可以选择一项来自动执行
触发源选择:就是选择从模式的触发信号源的触发源选择,选择指定的一个信号,得到TRGI,,TRGI去触发从模式
定时器的主从模式(Master-Slave Mode) 是一种让多个定时器协同工作的机制,通过将一个定时器(主定时器)的输出信号作为另一个定时器(从定时器)的触发源,实现定时器之间的同步、联动或事件触发控制。
常见触发方式:
- 复位模式(Reset Mode):TRGO 信号触发时,从定时器计数器清零并重新开始计数。
- 启动模式(Enable Mode):TRGO 信号触发时,从定时器开始计数(类似 "启动开关")。
- 停止模式(Disable Mode):TRGO 信号触发时,从定时器停止计数。
- 触发模式(Trigger Mode):TRGO 信号触发时,从定时器执行特定动作(如捕获当前计数值)。
典型应用场景:
-
1.定时器同步 例如:多通道数据采集系统中,需多个 ADC 同步采样,可通过主定时器触发多个从定时器,再由从定时器控制 ADC 采样时刻。
-
2.扩展定时周期: 当单个定时器的最大定时周期(受 PSC 和 ARR 位数限制)无法满足需求时,可通过主从级联扩展周期:
主定时器溢出时产生 TRGO 信号,触发从定时器计数一次。 总周期 = 主定时器周期 × 从定时器周期(类似 "分频器级联")。 -
3.脉冲宽度测量与频率计数主定时器用于生成已知频率的脉冲,从定时器配置为 "触发捕获模式",可测量外部信号的脉冲宽度或频率:
外部信号作为主定时器的触发源,从定时器捕获主定时器的计数值变化,计算脉冲时间。 -
4.从模式自动清零CNT :当定时器被配置为从模式下的
复位模式,一旦在特定触发输入(TRGI)上检测到有效边沿,硬件立即将计数器 CNT 清零,同时产生更新事件注意:
这里触发源选择,只有TI1P1和TI2P2,没有TI3和TI4的信号。 所以这里如果想使用从模式的自动清零CNT,就只能用用通道1和通道2。 对于通道3和通道4,就只能开启捕获中断,在中断里手动清零。不过这样,程序就会处于频繁中断的状态,比较消耗软件资源 -
5.PWM 波形联动 在电机驱动等场景中,需多个 PWM 通道严格同步(如三相逆变器的 U/V/W 三相PWM),可将主定时器的更新事件作为从定时器的触发信号,确保各相 PWM 的相位一致。
-
6.事件触发链 通过多级主从模式构建复杂的事件触发逻辑,例如: 定时器 1(主)触发定时器 2(从),定时器 2 再触发定时器 3(从从),实现 "链式触发" 的时序控制。
14.编码器接口
1.概述
- Encoder Interface 编码器接口
- 编码器接口可接收增量(正交)编码器的信号,根据编码器旋转产生的正交信号脉冲,自动控制CNT自增或自减,从而指示编码器的位置、旋转方向和旋转速度
- 每个高级定时器和通用定时器都拥有1个编码器接口
- 两个输入引脚借用了输入捕获的通道1和通道2

2.编码器接口基本结构

这里时机单元的时钟是编码器的时钟,编码器接口通过分频器控制CNT计数器的时钟,还根据编码器旋转方向控制CNT计数方向(增/减)
这里边沿检测极性选择不是上升沿下降沿的选择,而是高低电平的选择(编码器上升沿下降沿都有效),参数选择上升沿,就是信号直通过来,高低电平极性不反转,参数下降沿,信号通过一个非门过来,高低电平极性反转
3.配置流程
按编码器接口基本结构打通
1.开启相关时钟
2.配置GPIO,输入模式
3.配置时机单元
4.配置输入捕获单元
5.配置编码器接口模式
6.使能定时器
示例

注意
- 这里结构体变量的配置,调用
ICInit函数之后,就写入硬件寄存器了,所以Init之后,这个结构体我们可以换个值继续使用,不需要重新定义结构体 - 输入捕获里面也可以设置极性,和编码器接口配置设置极性,其实是配置的同一个
寄存器,这个Encoder函数位于ICInit函数下面,Encoder函数的配置会覆盖ICInit函数,否则相反,ICInit函数会覆盖Encoder函数的配置 - 如果
计数反向和你想要的不一致的话,解决方法:1.互换A、B两相,2.修改其中的极性,把任意一个极性反转一下,方向就会反过来 - 编码器模式下,
计数方向是被编码器接口托管的
补充:实现编码器测速应该怎么做
可以在固定的闸门时间,读一次CNT,然后清零(可以利用定时中断去做)
四、ADC
1.概述
- ADC(Analog-Digital Converter)模拟-数字转换器
- ADC可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量,建立模拟电路到数字电路的桥梁
12位逐次逼近型ADC,1us转换时间- 输入电压范围:0~3.3V,转换结果范围:0~4095
18个输入通道,可测量16个外部(GPIO口)和2个内部信号源(内部温度传感器和内部参考电压)规则组和注入组两个转换单元- 模拟看门狗自动监测输入电压范围
双ADC模式 就是两个ADC同时工作 ,它俩可以配合成同步模式、交叉模式等等模式,比如交叉模式,两个ADC交叉的对一个通道进行采样,这样进一步提升了采样率
STM32F103C8T6 ADC资源:ADC1、ADC2,10个外部输入通道
2.逐次逼近型ADC和ADC框图
逐次逼近型框图

工作原理
逐次逼近型ADC用二分法逐位比较:从最高位开始,猜测数值用DAC生成猜测电压与输入比较,保留或舍弃该位,依次确定所有位,当猜测电压与输入电压逼近时,即确定所以位,此时的猜测数值就是得到数字结果,即转换结果
ADC框图

规则组 :16个通道,单一数据寄存器(多通道时,需及时读取,否则数据被覆盖 )
注入组:4个通道,4个独立的数据寄存器
触发ADC转换信号 :
1.软件触发 ,程序中调用代码,启动转换
2.硬件触发 ,图中的触发源(图中开始触发部分),这些触发源主要来自定时器(由定时器各通道,还有定时器的主模式的输出),定时器可以通向ADC、DAC这些外设,用于触发转换 ,例如:这里可以用TIM3的更新事件选择为TRGO输出,然后ADC的触发源选择为TIM3的TRGO输出,这样TIM3的更新事件就能触发ADC转换了(实现硬件自动触发),整个过程不需要进中断,节省中断资源 ,这就是定时器触发的作用
VDDA和VSSA是内部模拟部分的电源,这里VDDA接3.3V,VSSA接GND,所以ADC的输入电压为0~3.3V
转换完成和模拟看门狗超出阈值都会置中断标志位,可以去NVIC申请中断
3.ADC基本结构

ADC的时钟CLOCK来自RCC,最大14MHz,也就只能6、8分频 ,可以回头查看RCC时钟树

4.ADC输入通道汇总表
| 通道 | ADC1 | ADC2 | ADC3 |
|---|---|---|---|
| 通道0 | PA0 | PA0 | PA0 |
| 通道1 | PA1 | PA1 | PA1 |
| 通道2 | PA2 | PA2 | PA2 |
| 通道3 | PA3 | PA3 | PA3 |
| 通道4 | PA4 | PA4 | PF6 |
| 通道5 | PA5 | PA5 | PF7 |
| 通道6 | PA6 | PA6 | PF8 |
| 通道7 | PA7 | PA7 | PF9 |
| 通道8 | PB0 | PB0 | PF10 |
| 通道9 | PB1 | PB1 | |
| 通道10 | PC0 | PC0 | PC0 |
| 通道11 | PC1 | PC1 | PC1 |
| 通道12 | PC2 | PC2 | PC2 |
| 通道13 | PC3 | PC3 | PC3 |
| 通道14 | PC4 | PC4 | |
| 通道15 | PC5 | PC5 | |
| 通道16 | 温度传感器 | ||
| 通道17 | 内部参考电压 |
说明:
- 表格中空白单元格表示该ADC不支持此通道。
- 通道16和17为内部通道,无需连接外部GPIO引脚。
5.ADC转换模式
单次转换,非扫描模式
连续转换,非扫描模式
单次转换,扫描模式
连续转换,扫描模式
非扫描模式下,只有序列1是有效的,可以在序列1指定想要转换的通道
单次转换模式下,启动转换只会转换一次,后续若想继续转换,则需再次启动
连续转换模式下,只需启动一次转换,后面会自动转换,并不断刷新最新的转换值
多通道,扫描模式下,要及时读取,负责数据会被覆盖
示意图
单次转换,非扫描模式

连续转换,非扫描模式

单次转换,扫描模式

连续转换,扫描模式

6.ADC中的数据对齐方式、转换时间、校准
数据对齐方式

ADC是12位的, 它的转换结果就是一个12位的数据, 但是这个数据寄存器是16位的 ,所以就存在一个数据对齐的问题(我们一般选用第一种)
如果选择左对齐直接读的话 得到的数据会比实际的大 因为数据左对齐实际上就是把数据左移了四次,二进制有个特点 就是数据左移一次 就等效于把这个数据乘二 那这里左移四次 就相当于把结果乘16 所以直接读的话会比实际值大16倍
数据左对齐的作用
0~4095,如果不需要这么高的分辨率,就可以选择左对齐,把数据的高八位取出来,做个简单的判断,这样就是舍弃了后面四位的精度,这样12位的ADC就退化成8位的ADC了,这就是左对齐的作用
转换时间
-
AD转换的步骤:采样,保持,量化,编码
-
STM32 ADC的总转换时间为:
TCONV = 采样时间 + 12.5个ADC周期 采样时间就是采样保持花费的时间,这个可以在程序中进行配置,采样时间越大,越能避免一些毛刺信号的干扰,不过转换时间也就越长 12.5个ADC周期是量化编码花费的时间
例如:当ADCCLK=14MHz,采样时间为1.5个ADC周期(ADC周期就是从RCC分频过来的ADCLK,最大14MHz)
TCONV = 1.5 + 12.5 = 14个ADC周期 = 1μs
校准
- ADC有一个内置自校准模式。校准可大幅减小因内部电容器组的变化而造成的准精度误差。校准期间,在每个电容器上都会计算出一个误差修正码(数字值),这个码用于消除在随后的转换中每个电容器上产生的误差
- 建议在每次上电后执行一次校准
- 启动校准前, ADC必须处于关电状态超过至少两个ADC时钟周期
7.ADC配置流程
按ADC基本结构框图打通即可
1.启动相关时钟,另外不要了ADCCLK分频器配置
2.配置GPIO,模拟输入
3.配置多路开关(接入规则/注入组)
4.配置ADC转换器
模拟看门狗和中断,按需配置
5.使能ADC
开启ADC后,对ADC进行一次校准,以减小误差
示例


如何减少数据的波动
1.采取滤波的方法,让AD值平滑一些,比如均值滤波,读10个或20个值,取平均值,作为滤波的AD值
2.裁剪分辨率,把数据的尾数去掉
3.当需要AD高于或低于某个阈值时,进行某个操作,由于波动,AD会在判断阈值附近来回跳变,这里就可以采用迟滞比较的方法来完成,设置两个阈值,高于某个阈值才执行某个操作,同理数据要低于另一个阈值时,才执行另一个操作,这样就能避免输出抖动的问题,和施密特触发器是一个原理
五、DMA
1.概述
- DMA(Direct Memory Access)直接存储器存取
- DMA可以提供外设和存储器 或者存储器和存储器 之间的高速数据传输,无须CPU干预,节省了CPU的资源
- 12个独立可配置的通道: DMA1(7个通道), DMA2(5个通道)
- 触发源:每个通道都支持软件触发和特定的硬件触发
- 传输数量寄存器
16位,范围是0~65535,所以传输个数最多是65536个 - DMA中断的核心是传输完成、半传输完成和传输错误三类
STM32F103C8T6 DMA资源:DMA1(7个通道)
外设也就是外设寄存器,一般是外设的数据寄存器DR(Data Register),比如ADC的数据寄存器、串口的数据寄存器等
存储器,指的是运行内存SRAM和程序存储器Flash,是存储变量数组和程序代码的地方
存储器到存储器的数据转运,一般使用软件触发
外设到存储器的数据转运,一般使用硬件触发
为什么外设到存储器的数据转运,不能使用软件触发?
使用软件触发 DAM就是一股脑的转运,以最快的速度把数据全部转换完成,外设到存储器的数据转运,不能使用软件触发,是因为外设的数据是有一定时机的,所以需要使用硬件触发,比如转运ADC数据,需要ADC每个通道数据转换完成之后,硬件触发完成一次DMA,之后DMA再转运。
特定的硬件触发,是由于每个DMA的通道,它的触发源是不一样的,要使用某个外设的触发源,就需要使用它连接的那个通道,而不是使用任意的通道,见下图,例如,这里通道1的硬件触发就是ADC1、定时器2的通道3、定时器4的通道1,具体使用那个外设是由对应的外设是否开启了DMA输出决定的,会有一个库函数用来开启DMA输出(例如,ADC_DMACmd)

2.存储器映像
在嵌入式系统(尤其是STM32等单片机)中,存储器映像(Memory Map,也常译作内存映射)指的是将系统中所有可访问的存储器和外设寄存器,统一分配到固定的、连续的物理地址空间中。
计算机系统的5大组成部分,运算器、控制器、存储器、输入和输出设备
在实际CPU设计中,运算器和控制器通常被集成在一起,称为中央处理器 (CPU) 。因此,有时也简称为三大核心部分:CPU、存储器、输入/输出设备 。
存储器两个重点,一个是存储器的内容,另一个是存储器的地址
下表以STM32为例的存储映像:
| 类型 | 起始地址 | 存储器 | 用途 |
|---|---|---|---|
| ROM | 0x0800 0000 | 程序存储器Flash (主闪存) | 存储C语言编译后的程序代码(运行程序一般也是从主闪存里面开始运行) |
| 0x1FFF F000 | 系统存储器 | 存储BootLoader,用于串口下载 | |
| 0x1FFF F800 | 选项字节(Flash的读保护、写保护,看门狗等配置) | 存储一些独立于程序代码的配置参数 | |
| RAM | 0x2000 0000 | 运行内存SRAM | 存储运行过程中的临时变量 |
| 0x4000 0000 | 外设寄存器 | 存储各个外设的配置参数 | |
| 0xE000 0000 | 内核外设寄存器 (NVIC,SysTick) | 存储内核各个外设的配置参数 |
存储器类型,分为两大类,ROM和RAM
ROM,只读存储器,是一种非易失性、掉电不丢失的存储器
RAM,随机存储器,是一种易失性、掉电丢失的存储器


3.DMA框图

总线矩阵的左端,是主动单元 ,也就是拥有存储器的访问权
总线矩阵的右端,是被动单元 ,它们的存储器只能被左边的主动单元读写
主动单元,内核由DCode和系统总线,可以访问右边的寄存器
-
Dcode总线是专门访问Flash的总线
-
系统总线是访问其他东西的
另外由于DMA需要转运数据,所以DMA也必须有访问的主动权,所以主动单元除了内核,剩下的就是DMA总线了
- 各通道可以设置它们转运数据的源地址和目标地址
- DMA1七个通道,DMA2五个通道
- 仲裁器,虽然多个通道可以独立转运数据,但DMA总线只有一条,所以这些通道不能同时使用这个总线,如果产生了冲突,那么仲裁器,就会根据通道的优先级决定,谁先试用,谁后使用
总线矩阵,也有个仲裁器,如果DMA和CPU都要访问同一个目标,那么DMA就会暂停CPU的访问,以防止冲突,不过总线仲裁器,会保证CPU得到一半的总线线宽,使得CPU能正常工作,默认通道号越小,优先级越高,可以在程序中配置优先级
这里AHB从设备,也就是DMA自身的寄存器,DMA作为一个外设,它自己也会有相应的配置寄存器,这里连接在了总线右边的AHB总线上,所以DMA既是总线矩阵的主动单元,可以读写各种寄存器,也是AHB总线上的被动单元,也能被读写,配置DMA参数
Flash,ROM只读存储器的一种,只能读取数据,不能写入,这就是为什么DMA目的地址写Flash转运会出错,当然Flash也不是绝对不能写入 ,可以通过配置Flash接口控制器,对Flash进行写入
外设寄存器,有的是只写,有的只读,得参考数据手册,数据寄存器是可以正常读写的
4.DMA基本结构

数据宽度:指定一次转运要按多大的数据宽度来进行,字节Byte(uint_8),半字HalfWord(uint_16)和字Word(uint_32)
DMA运行的几个条件
1.DMA必须使能
2.传输计次器必须大于0
3.触发源,必须有触发信号
软件触发和循环模式不能同时用,因为软件触发就是想把传输计数器清零,循环模式是清零后自动重装,如果同时使用的话,DMA就停不下来
写传输计数器时,必须要先关闭DMA再进行,不能在DMA开启时写传输计数器
当源数据和目标数据数据宽度不一致,怎么处理?
就像uint_8,uint16_t,uint32_t变量之间相互赋值一样
源数据<目标数据,高位补0
源数据>目标数据,高位舍弃

六、串口
1.概述
串口是一种应用十分广泛的通讯接口,串口成本低、容易使用、通信线路简单,可实现两个设备的互相通信
硬件电路

电平标准
电平标准是数据1和数据0的表达方式,是传输线缆中人为规定的电压与数据的对应关系,串口常用的电平标准有如下三种:
TTL电平:+3.3V或+5V表示1,0V表示0
RS232电平:-3-15V表示1,+3+15V表示0
RS485电平:两线压差+2+6V表示1,-2-6V表示0(差分信号)
2.波特率、比特率,为什么双方波特率要相同
波特率 vs 比特率(定义与区别)
- 波特率(Baud Rate):"每秒运了几车货"。它指的是信号每秒钟变化的次数(符号速率)。在电路层面,就是电压高低跳变的次数。
- 比特率(Bit Rate):"每秒卸了几件货"。它指的是每秒钟实际传输的二进制数据量(比特数),单位是 bps。
它们的关系(核心公式):比特率 = 波特率 × 每步传输的比特数
-
如果一辆车只装 1件 货(1个符号代表1个bit,即0或1),那么波特率 = 比特率。
-
如果一辆车装 2件 货(比如1个符号代表00/01/10/11),那么比特率 = 波特率 × 2。
为什么串口两端必须配置相同的波特率
-
因为串口没有时钟线。
-
串口只有两根线:TX(发)和 RX(收)。没有时钟线。
-
那接收方怎么知道什么时候该读数据?
-
串口的"约定"发送方和接收方提前约定好一个相同的波特率,然后各自用自己的时钟计时。
3.串口的参数及时序
- 波特率:串口通信的速率
- 起始位:标志一个数据帧的开始,固定为低电平
- 数据位:数据帧的有效载荷,1为高电平,0为低电平,低位先行
- 校验位:用于数据验证,根据数据位计算得来
- 停止位:用于数据帧间隔,固定为高电平
注意低位先行

4.流控
作用:避免发送设备发的太快,接收设备来不及处理,而导致的丢弃或覆盖数据的现象
- RTS(Request to Send,请求发送):输出信号。由发送方(或数据终端设备DTE)发出,告诉对方:"我这边准备好了,我想给你发数据。"
- CTS(Clear toSend,清除发送/允许发送):输入信号。由接收方(或数据通信设备DCE)发出,是对RTS的回应,告诉对方:"我这边缓冲区有空位,你现在可以发过来了。"
接线方式(交叉)
设备A的 RTS(输出) →接设备B的 CTS(输入)
设备A的 CTS(输入) ←接设备B的 RTS(输出)
工作流程:
- 发送端 检查自身CTS引脚:
如果CTS为低电平 (接收端允许发送),则通过TX发送数据。
如果CTS为高电平(接收端忙),则暂停发送。 - 接收端 通过RTS引脚控制发送端:
当接收缓冲区快满时,拉高RTS(通知发送端停止)。
当缓冲区空闲时,拉低RTS(允许继续发送)。
5.USART基本结构

发送器和接收器的波特率由波特率寄存器 BRR里的DIV确定
计算公式:波特率 = fPCLK2/1 / (16 * DIV)(PCLK是外设时钟)
一个数据位,有16个采样时钟

经过波特率发生器分频后,产生的时钟通向发送控制器和接收控制器,发送控制器和接收控制器用来控制发送移位和接收移位
6.同步通信和异步通信
同步通信 vs 异步通信
一句话区分
同步通信 = 双方共用同一个时钟,跟着节拍走。
异步通信 = 各用各的时钟,提前约定好速度。
用现实生活中的例子
同步通信 = 老师带着学生打拍子读课文
老师拍一下手,学生读一个字。节奏由老师一人控制,所有人同步。快慢全看老师的手。
异步通信 = 两个人约好每分钟念 100 个字
各看各的手表。只要你每分钟念 100 个,对方按每分钟 100 个听,就能对上。但如果谁的表快了或慢了,就乱了
- 同步通信:发送方必须等待接收方响应后才能继续执行后续操作
优点:
无额外的起始 / 停止位开销,有效数据占比高
传输速度快,适合大量数据连续传输
缺点:
硬件复杂,需要时钟同步机制
对时钟精度要求高
- 异步通信:发送方发送请求后立即执行后续操作,不等待响应
优点:
硬件简单,无需时钟线,节省线路资源
发送方和接收方时钟精度要求低
缺点:
每个数据帧都要附加起始位、停止位,额外开销大
传输速度相对较慢
7.UART 与 USART
| 特性 | UART | USART |
|---|---|---|
| 通信模式 | 仅异步 | 同步 + 异步 |
| 时钟信号 | 无(依赖波特率) | 同步模式需SCLK |
| 信号线 | TXD、RXD(最少2线) | 同步模式需SCLK(+其他控制线) |
| 速度 | 较低 | (受时钟偏差限制) |
| 应用场景 | 低速短距离 | (调试、传感器) |
| 应用场景 | 简单 | 较高(支持多协议) |
七、I2C
1.硬件电路设计,上拉电阻+开漏输出,为什么这样设计
为什么这样设计
如果是推挽输出(强上下拉) ,主机发送数据时是输出模式,接收数据时是输入模式,同理从机也是在输入和输出之间反复切换 ,如果总线时序协调不好 ,极有可能一个输出高电平,一个输出低电平,从而导致电源短路
为了避免总线没协调好导致电源短路这个问题,I2C的设计是设备禁止输出强上拉的高电平
开漏输出 ,所有设备只能输出低电平而不能输出高电平,为了避免高电平造成的引脚浮空/高阻态 ,所以在SCL和SDA各外置一个上拉电阻 ,这时输出高电平时就会通过一个电阻拉到高电平,这是一个弱上拉
所以采用外置若上拉电阻+开漏输出的电路结构
这种设计:
- 完全杜绝了电源短路的现象,保证电路的绝对安全
- 开漏输出加弱上拉的模式,同时兼具输入和输出的功能,开漏模式下,输出高电平就相当于断开引脚,所以输入之前,不需要切换到输入模式
- 这个模式有个线与 的现象,只要任意一个设备输出低电平,总线就处于低电平,只有所有设备输出高电平,总线才处于高电平
- 所有I2C设备的SCL连在一起,SDA连在一起
- 设备的SCL和SDA均要配置成开漏输出模式
- SCL和SDA各添加一个上拉电阻,阻值一般为4.7KΩ左右
2.I2C有什么特点
I2C总线是一种用于芯片之间进行通信的串行总线。它由两条线组成:串行时钟线(SCL)和串行数据线(SDA)
- SDA(Serial data)是数据线,D代表Data也就是数据,Send Data 也就是用来传输数据的
- SCL(Serial clock line)是时钟线,C代表Clock也就是时钟也就是控制数据发送的时序的。这种总线允许多个设备在同一条总线上进行通信
同步,半双工,支持一主多从、多主多从。传输数据是是高位先行
3.I2C时序
- 起始条件:SCL高电平期间,SDA从高电平切换到低电平
- 终止条件:SCL高电平期间,SDA从低电平切换到高电平

- 发送一个字节 :SCL低电平 期间,主机 将数据位依次放到SDA 线上(高位先行),从机 将在SCL高电平 期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节
- 接收一个字节 :SCL低电平 期间,从机 将数据位依次放到SDA 线上(高位先行),主机 将在SCL高电平 期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节
I2C从机地址的作用 :让主机在总线上精准找到并选中自己要通信的那个设备,避免冲突
从机设备地址,在I2C协议标准里分为7位地址和10位地址
一般芯片不同设备地址也不同,相同型号的芯片地址是一样的,那如果相同的芯片挂载在同一条总线怎么办?
一般I2C的从机设备地址,高位都是有商家决定,低位可以灵活切换,一般器件地址中的最后几位是可以在电路中改变的 ,例如MPU6050地址的最后一位,就是可以由板子上的AD0引脚电平决定,这个引脚接高电平,地址就是1101 001,接低电平就是1101 000
这样即使使用相同型号的芯片,挂载在同一个总线上,也可以通过切换地址低位的方式,保证每个设备地址都不一样
标准7位地址设备(以写操作为例)
发送设备地址 + 写位 :选择设备,为接下来的写操作做准备。
发送寄存器地址 :告知设备你准备操作哪个具体的寄存器。
发送数据:向刚才选定的寄存器写入数据。
10位地址设备(以写操作为例)
发送"头字节" + 写位 :头字节 11110 XX + R/W中的11110是10位地址的标识,XX是10位地址的最高两位。这一步的主要作用是选中该从机。
发送"第二字节" :发送10位地址的剩余8位。至此,从机就完成了内部的地址匹配,被成功选中。
发送寄存器地址 :这是和7位地址设备完全相同的步骤,用于指定从机内部的寄存器。
发送数据:向寄存器写入数据
指定地址写

连续写入多个字节,由于地址指针的自增,这样也就是实现了连续写入多个寄存器

当前地址读
- 对于指定设备(Slave Address),在当前地址指针指示的地址下,读取从机数据(Data)

补充说明:
- 在从机中,所有寄存器被分配到一个线性区域 中,并且会有一个单独的指针变量,指示着其中一个寄存器,这个指针上电默认,一般指向0地址,每次成功写入或读出一个数据字节 后,指针就会自动自增,指向下一个位置(指向下一个寄存器)
- 那么在调用当前地址读 的时序时,主机没有指定要读那个地址,从机就会返回当前指针指向的那个寄存器的值
- 那假设,调用指定地址写的时序,例如在0x19的位置写入0xAA,那么指针就会+1,指向0x1A的位置,此时调用当前地址读的时序,返回的就是0x1A的地址
- 基于此,可以实现指定地址读,先指定地址写,但不写入数据,再当前地址读(重新发送起始信号),此时返回的就是写地址时指定的寄存器,从而实现了指定地址读的逻辑
指定地址读
- 对于指定设备(Slave Address),在指定地址(Reg Address)下,读取从机数据(Data)

连续读多个读多个字节,由于地址指针会自增,所以这样可以连续读出一片区域的寄存器

这里在最后一个字节,主机给非应答 ,从机读到SDA为1,就只知道主机不想再读数据,此时从机就会释放总线,将SDA的控制权还给主机
4.总线仲裁机制
1、I2C 总线上可能在某一时刻有两个主控设备要同时向总线发送数据,这种情况叫做总线竞争 。
2、I2C总线具有多主控能力,可以对发生在 SDA 线上的总线竞争进行仲裁,其仲裁原则是这样的:假设主控器1 要发送的数据 DATA1 为 "101 ......";主控器 2 要发送的数据 DATA2 为 "1001 ......" 总线被启动后两个主控器在每发送一个数据位时都要对自己的输出电平进行检测,只要检测的电平与自己发出的电平一致,他们就会继续占用总线。在这种情况下总线还是得不到仲裁。当主控器 1 发送第 3 位数据 "1" 时(主控器 2 发送 "0" ),由于 "线与" 的结果 SDA 上的电平为 "0",这样当主控器 1 检测自己的输出电平时,就会测到一个与自身不相符的 "0" 电平。这时主控器 1 只好放弃对总线的控制权;因此主控器 2 就成为总线的唯一主宰者。不难看出:
- 对于整个仲裁过程主控器 1 和主控器 2 都不会丢失数据;
- 各个主控器没有对总线实施控制的优先级别,他们遵循 "
低电平优先" 的原则,即谁先发送低电平谁就会掌握对总线的控制权。
3、i2c 总线仲裁主要遵循三个机制:
- "线与" 机制: 多主机时,总线具有 "线与" 的逻辑功能,即只要有一个节点发送低电平时,总线上就表现为低电平。
- SDA 回读机制:总线被启动后,多个主机在每发送一个数据位时都要对自己的输出电平进行检测,只要检测的电平与自己发出的电平一致,就会继续占用总线。
- 低电平优先机制: 由于线与的存在,当多主机发送时,谁先发送低电平谁就会掌握对总线的控制权。
5.时钟同步和时钟拉伸是什么?
想象一个场景:多个人(多个主机)想一起推一辆车(总线)。车要往前走,需要大家统一节奏。但每个人推车的速度不一样(有人快,有人慢)。
问题 :如果推得快的人自顾自往前推,推得慢的人跟不上,车就会散架(数据冲突)。
I2C 时钟同步 就是一套规则:让推得慢的人决定整辆车的速度。快的人必须等慢的人
时钟同步:I2C总线上的SCL线是所有设备逻辑与的结果。当任一设备将SCL拉低,总线SCL就为低电平。只有当所有设备都释放SCL(高阻态),SCL才会变为高电平,这确保了最慢的设备也能跟上通信节奏。
那从机时钟拉伸呢?
从机有时也会拉低 SCL(告诉主机:"我还没准备好,你等我一下")。
这跟"多主机时钟同步"类似,也是利用同一个"线与"机制------从机拉低时钟,所有主机看见 SCL 是低的,就会自动等待,直到从机松手。
时钟拉伸:从设备可以通过持续拉低SCL来延长时钟周期。这样从设备可以有更多时间处理数据。主设备必须等待SCL实际变为高电平后才能继续。这是I2C协议中从设备控制通信速度的机制。
6.I2C的通信速率
- 在标准模式下,I2C的传输速率为100KHz
- 在快速模式下,I2C的传输速率为400KHz
- 在高速模式下,I2C的传输速率为3.4KHz
7.I2C总线数据有效性
I2C 数据的有效性完全由SCL 的电平状态决定:高电平时 SDA 必须稳定(有效数据),低电平时 SDA 可变化(信号切换)。
8.I2C基本结构(I2C外设)
一主多从的模式

流程
1.开启相关时钟
2.I2C对应的GPIO引脚配置为开漏输出
3.配置I2C
4.使能I2c
时钟占空比
- 当时钟频率小于等于100KHz,时钟的占空比为1:1
- 当时钟频率大于100KHz,也就是快速模式下,时钟占空比可配置为16:9、2:1(低电平比高电平时间)
- 为什么要增大低电平的比例 ,
3.1, SCL低电平期间数据变化,SCL高电平期间数据读取,数据变化需要一定的时间翻转波形(转换到低电平很快,开漏输出模式下,输出低电平是强下拉,而输出高电平是弱上拉,所当数据由电平到高电平就变换的比较慢 ,所以就给SCL低电平多分配一些资源,要不然SCL低电平期间,数据来不及变换,SCL高电平期间,读取的数据也没有),这上升沿也限制I2C的最大通信速度
3.2, 其次SCL低电平期间,数据变化也不是完全贴到下降沿的,也会一些延时,所以更有必要在低电平多分配一些时间了
这里时钟占空比为16:9
自身地址寄存器
- 自身地址寄存器用来设置自己的设备地址,STM32采用的是可变主机的模式,当作从机时,主机就能通过设备地址从机,可设置设备地址是七位地址还是十位地址
- 同时支持两个设备地址,双地址寄存器
主机发送

主机接收

9.硬件I2C与软件I2C
硬件I2C:通过专门的硬件电路实现,这些电路通常由微控制器或其他集成电路上的硬件模块提供支持。硬件I2C可以直接调用内部寄存器进行配置,利用芯片中的硬件I2C外设,自动执行时钟生成、起始终止条件生成、应答位收发、数据收发等功能,从而减轻CPU的负担。
软件I2C :通过软件控制GPIO(通用输入输出)管脚来模拟I2C协议的时序。这通常涉及到在程序中控制SCL(串行时钟)和SDA(串行数据)线的电平状态,以模拟I2C通信的起始、停止、数据发送
和接收等过程
10.I2C可以挂载多少个设备(理论上)
- I2C 总线地址为7位时,从机地址范围是 0~127,其中0 为广播地址,一般不用作从机地址,实际可控制的从器件地址是1~127,共 127个。
- 10位地址模式没有专门的广播地址,但与7位模式共用同一个 (0x00) 通用广播地址。最大可用从机地址数为 1023 个
广播地址 是 I2C 协议中一个特殊的通用呼叫地址,用于主机同时与总线上所有从机通信。
作用:主机发送广播地址时,所有支持广播的从机都会应答,并接收后续命令。典型用途:
- 复位所有从机
- 发送全局命令
- I2C协议没有限制总线上的最大设备数,但是规定了总线电容不得超过400pF,所以实际上I2C总线上的设备数推荐不超过8个。
11.什么是I2C死锁,怎么解决?
I2C死锁时的现象 :SCL一直为高,SDA一直为低。(注意,只有硬件I2C会出现死锁,软件模拟I2C不会出现死锁的现象)
硬件I2C为什么可能死锁?
根本原因:硬件 I2C 外设内部有一个状态机,它依赖总线上的事件(如从机返回应答)来驱动状态转换。
软件I2C为什么不会死锁?
根本原因:软件I2C没有"等待事件"的状态机,每个时钟脉冲、每个电平检测都是CPU主动执行的指令序列。
I2C死锁的原因:
- 一般有两种情况会导致死锁,一种是从设备向主机回复ACK应答时,主机意外复位,而从机不会自动复位;另一种是从设备向主机发送数据位是0时,主机意外复位,而从机不会自动复位。
- 这两种情况其实本质一样:当从设备将SDA数据线拉低时,主机意外复位,但从机不会自动复位,就会导致I2C死锁。
从机的视角:
它认为主机还会继续提供 SCL 时钟(因为通信没结束)。
它正等着 SCL 从高变低,以便释放 SDA 或改变下一个数据位。
但 SCL 一直保持高(因为主机复位后不再产生时钟,上拉电阻把它拉高)。
于是从机只能死死拉着 SDA 为低,无法松开。
主机的视角:主机复位后重新运行程序,它检测总线状态。
它发现 SDA 一直为低(被从机拉着)。
I2C 协议规定:总线空闲时 SDA 和 SCL 都应该为高。现在 SDA 为低,说明总线正被占用。
于是主机不敢发送起始条件(因为会破坏正在进行的通信),只能等待 SDA 变高。
结果:从机等 SCL 变低 → 永远不会发生(因为主机不给时钟)。
主机等 SDA 变高 → 永远不会发生(因为从机不释放)。
两者互相等待 → 死锁
总结:主机复位后停止产生时钟(SCL 固定高),从机在未完成的数据传输中只能保持 SDA 低(协议限制,I2C规定只有在SCL低电平时,SDA才允许变化),而主机检测到 SDA 低认为总线忙,双方互相等待,形成死锁。
常见的解决I2C死锁的方法:
- 选择带复位功能的从设备;主机检测到SDA被拉低超过指定时间后,主动复位从设备,前提是**总设备具有复位引脚,并且主机可以控制从设备的复位引脚*
- 在I2C总线上添加一个总线恢复设备,当检测到SDA数据线被拉低超过指定的时间时,就在总线上产生9个SCL时钟(8位数据+1位应答 为9个脉冲时钟),使从设备完成数据发送,进而释放SDA,从死锁中恢复出来。
12.I2C主从设备交换
静态切换(通信结束后切换)
- 1.当前主机发送停止条件(P),释放总线。
- 2.原从机(欲成为新主机)检测到总线空闲,主动发送起始条件(S),接管总线控制权
动态切换(不停止总线,直接换主)
- 1.原主机发送重复起始条件(Sr),保持总线控制权(不释放)。
- 2.原主机发送自己的从机地址(即它作为从模式时的地址),并带写标志。
- 3.原从机(欲成为新主机)识别到这个地址后,知道原主机想"让位",于是接管 SCL 时钟,成为新主机。
七、SPI
1.概述
-
SPI(Serial Peripheral Interface)是由Motorola公司开发的一种通用数据总线
-
四根通信线:SCK(Serial Clock)、MOSI(Master Output Slave Input)、MISO(Master Input Slave Output)、SS(Slave Select)
同步,全双工
-
支持总线挂载多设备(一主多从)
硬件设计

- 所有SPI设备的SCK、MOSI、MISO分别连在一起
- 主机另外引出多条SS控制线,分别接到各从机的SS引脚
输出引脚配置为推挽输出,输入引脚配置为浮空或上拉输入
推挽输出,高低电平均有较强的驱动能力
所以它的上升沿/下降沿都非常的迅速,因此它可以达到更高的传输速度(这里可以对比I2C开漏输出)
SPI协议并没有严格规定最大的传输速度,这个最大传输速度取决于芯片厂商的设计需求
与I2C对比I2C要实现半双工,经常输出/输入切换,另外又要实现多主机的时钟同步和时钟仲裁,这也使得I2C不能使用推挽输出
SPI不支持多主机,SPI是全双工,SPI的输出引脚始终是输出引脚,输入引脚始终是输入,基本不会出现冲突,所以SPI可以使用推挽输出
注意 ,SPI存在一个冲突点
主机一个是输入,但从机全是输出,如果从机始终是推挽输出,势必导致冲突
当从机片选引脚为高电平,也就是从机未选中,它的MISO必须为高阻态 ,这样就是可以防止一条线有多个输出,而导致的电平冲突的问题了

移位寄存器的时钟源,由主机提供,这里叫做波特率发生器
如果只想接收,不想发送,一般用0x00/0xFF去置换数据
2.起始终止信号
- 起始条件:SS从高电平切换到低电平
- 终止条件:SS从低电平切换到高电平

3.SPI的四种模式
四种模式主要取决于时钟极性(CPOL)和时钟相位(CPHA)
- 极性: 直接影响SPI总线"空闲时的时钟信号是高电平还是低电平"
CPOL = 1:表示空闲时是高电平 CPOL = 0:表示空闲时是低电平
- 相位: 直接决定SPI总线"从哪个跳变沿开始采样数据"
CPHA = 0:表示从第一个跳变沿,开始采样 CPHA = 1:表示从第二个跳变沿开始采样**,如果是第一个跳变沿采样,就需要在CS/SS片选被拉低时,就要立刻移除输出数据**
模式0
CPOL=0:空闲状态时,SCK为低电平
CPHA=0:SCK第一个边沿移入数据,第二个边沿移出数据


模式1
CPOL=0:空闲状态时,SCK为低电平
CPHA=1:SCK第一个边沿移出数据,第二个边沿移入数据

这里为第二个边沿移入数据 ,这里写错了

模式2
CPOL=1:空闲状态时,SCK为高电平
CPHA=0:SCK第一个边沿移入数据,第二个边沿移出数据


模式3
CPOL=1:空闲状态时,SCK为高电平
CPHA=1:SCK第一个边沿移出数据,第二个边沿移入数据


4.SPI时序
SPI里面有和I2C一样的地址指针,所以发送一个字节不终止就会依次写入到后面的存储空间里,同理读也是,就会依次读入后面的存储空间
发送指令
向SS指定的设备,发送指令(0x06)

指定地址写
向SS指定的设备,发送写指令(0x02),
随后在指定地址(Address23:0)下,写入指定数据(Data)

指定地址读
向SS指定的设备,发送读指令(0x03),
随后在指定地址(Address23:0)下,读取从机数据(Data)

5.SPI外设
- STM32内部集成了硬件SPI收发电路,可以由硬件自动执行时钟生成、数据收发等功能,减轻CPU的负担
- 可配置8位/16位数据帧、高位先行/低位先行
- 时钟频率: fPCLK / (2, 4, 8, 16, 32, 64, 128, 256)(由PCLK外设时钟分频而来)
- 支持多主机模型、主或从操作
- 可精简为半双工/单工通信
- 支持DMA
- 兼容I2S协议
STM32F103C8T6 硬件SPI资源:SPI1、SPI2

6.SPI的时钟是谁提供的 、数据采样时间点根据什么来决定、 为什么spi会有四种模式吗 而不是统一一个模式
SPI的时钟(SCLK)由主设备(Master) 提供。
数据采样时间由时钟极性 (CPOL)和时钟相位(CPHA)共同决定:
- CPOL(Clock Polarity):定义时钟空闲时的电平(0=低电平,1=高电平)。
- CPHA(Clock Phase):定义数据采样的边沿(0=第一个边沿采样,1=第二个边沿采样)。
四种模式(CPOL/CPHA组合)的存在是为了兼容不同厂商的从设备硬件设计。若强制单一模式,需所有从设备重新设计硬件,增加成本和复杂度。现有设备可能因时序不匹配无法通信
八、Unix、BKP、RTC
1.STM32有几个时钟源
①、HSI是高速内部时钟,RC振荡器,频率为8MHz,上电后默认的系统时时钟 SYSCLK = 8MHz
②、HSE是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz~16MHz。
③、LSI是低速内部时钟,RC振荡器,频率为40kHz,可用于独立看门狗IWDG、实时时钟RTC。
④、LSE是低速外部时钟,接频率为32.768kHz的石英晶体。
HSE:外部晶振STM32F103C8T6默认是8Mhz (4Mhz~16Mh
2.Unix时间戳
概述
- Unix 时间戳(Unix Timestamp)定义为从UTC/GMT的1970年1月1日0时0分0秒开始所经过的秒数,不考虑闰秒
- 时间戳存储在一个秒计数器中,秒计数器为32位/64位的整型变量
- 世界上所有时区的秒计数器相同,不同时区通过添加偏移来得到当地时间
GMT(Greenwich Mean Time)格林尼治标准时间是一种以地球自转为基础的时间计量系统。它将地球自转一周的时间间隔等分为24小时,以此确定计时标准
UTC(Universal Time Coordinated)协调世界时是一种以原子钟为基础的时间计量系统。它规定铯133原子基态的两个超精细能级间在零磁场下跃迁辐射9,192,631,770周所持续的时间为1秒。当原子钟计时一天的时间与地球自转一周的时间相差超过0.9秒时,UTC会执行闰秒来保证其计时与地球自转的协调一致
时间戳转换
C语言的time.h模块提供了时间获取和时间戳转换的相关函数,可以方便地进行秒计数器、日期时间和字符串之间的转换

| 函数 | 作用 |
|---|---|
| time_t time(time_t*); | 获取系统时钟 |
| struct tm* gmtime(const time_t*); | 秒计数器转换为日期时间(格林尼治时间) |
| struct tm* localtime(const time_t*); | 秒计数器转换为日期时间(当地时间) |
| time_t mktime(struct tm*); | 日期时间转换为秒计数器(当地时间) |
| char* ctime(const time_t*); | 秒计数器转换为字符串(默认格式) |
| char* asctime(const struct tm*); | 日期时间转换为字符串(默认格式) |
| size_t strftime(char*, size_t, const char*, const struct tm*); | 日期时间转换为字符串(自定义格式) |
3.BKP
- BKP(Backup Registers)备份寄存器
- BKP可用于存储用户应用程序数据。当VDD(2.0~3.6V) 电源被切断,他们仍然由VBAT(1.8~3.6V)维持供电。当系统在待机模式下被唤醒,或系统复位或电源复位时,他们也不会被复位
它主要用于:
- 掉电保存关键系统数据。
- 配合RTC实现精准计时。
- 通过侵入检测机制保护敏感信息。
4.RTC
- RTC(Real Time Clock)实时时钟
- RTC是一个独立的定时器,可为系统提供时钟和日历的功能
- RTC和时钟配置系统处于后备区域,系统复位时数据不清零,VDD(2.03.6V)断电后可借助VBAT(1.83.6V)供电继续走时
可选择三种RTC时钟源:
- HSE时钟除以128(通常为8MHz/128)
- LSE振荡器时钟(通常为32.768KHz)
- LSI振荡器时钟(40KHz)
RTC基本结构

RTC操作注意事项
执行以下操作将使能对BKP和RTC的访问:
- 设置RCC_APB1ENR的PWREN和BKPEN,使能PWR和BKP时钟
- 设置PWR_CR的DBP,使能对BKP和RTC的访问
若在读取RTC寄存器时,RTC的APB1接口曾经处于禁止状态,则软件首先必须等待RTC_CRL寄存器中的RSF位(寄存器同步标志)被硬件置1
也就是RTC等待同步,一般上电的时候调用一下这个函数就行
为什么需要这一步 :PCLK1和RTCCLK,PCLK1在主电源掉电时会停止,所以为了保证RTC主电源掉电正常工作,RTC的寄存器都是在RTCCLK的同步下变更的,当用RCLK1驱动的总线,去读取RTCCLK驱动的寄存器时,就会有一个时钟不同步的问题,RTC寄存器,只有在RTCCLK的上升沿更新,但是RCLK1的频率36MHz,远大于RTCCLK的频率32KHz,如果在APB1刚开启时,就立刻读取RTC寄存器,有可能RTC寄存器还没更新到APB1总线上,这样读取到的值就是错误的
必须设置RTC_CRL寄存器中的CNF位,使RTC进入配置模式后,才能写入RTC_PRL、RTC_CNT、RTC_ALR寄存器
对RTC任何寄存器的写操作,都必须在前一次写操作结束后进行。可以通过查询RTC_CR寄存器中的RTOFF状态位,判断RTC寄存器是否处于更新中。仅当RTOFF状态位是1时,才可以写入RTC寄存器
九、PWR
三种低功耗好模式:睡眠模式、停止模式、待机模式
睡眠模式:
- 执行完WFI/WFE 指令后,STM32进入睡眠模式,程序暂停运行,唤醒后程序从暂停的地方继续运行
- SLEEPONEXIT位决定STM32执行完WFI或WFE后,是立刻进入睡眠,还是等STM32从最低优先级的中断处理程序中退出时进入睡眠
- 在睡眠模式下,所有的I/O引脚都保持它们在运行模式时的状态
- WFI指令进入睡眠模式,可被任意一个NVIC响应的中断唤醒
- WFE指令进入睡眠模式,可被唤醒事件唤醒
睡眠模式对电路的影响:
只是把CPU时钟关了,对其他电路没有任何操作,CPU时钟关了,程序就会停止,不会再继续运行了,CPU不运行,芯片功耗就会降低
关闭电路的通常两个做法:
- 关闭时钟,所有的运算和涉及时序的操作都会暂停,但是寄存器和存储器里面保存的数据还可以维持,不会消失
- 关闭电源,电路直接断电,电路的操作和数据都会直接丢弃
因此,关闭电源,比关闭时间省点
停止模式:
- 执行完WFI/WFE 指令后,STM32进入停止模式,程序暂停运行,唤醒后程序从暂停的地方继续运行
- 1.8V供电区域的所有时钟都被停止,PLL、HSI和HSE被禁止,SRAM和寄存器内容被保留下来
- 在停止模式下,所有的I/O引脚都保持它们在运行模式时的状态
- 当一个中断或唤醒事件导致退出停止模式时,HSI被选为系统时钟
补充说明:
我们的程序,默认在SystemInit函数里配置的是使用HSE外部高速时钟,通过PLL倍频,得到72MHz主频,但进入停止模式后,PLL和HSE都停止了,而在退出停止模式时,并不会再自动开启PLL和HSE,而是默认用HSI的8MHz,直接作为主频,所以一般在停止模式唤醒后,第一时间就是重新启动HSE,配置主频为72MHz,只需要调用一下SystemInit就行
- 当电压调节器处于低功耗模式下,系统从停止模式退出时,会有一段额外的启动延时
- WFI指令进入停止模式,可被任意一个EXTI中断唤醒
- WFE指令进入停止模式,可被任意一个EXTI事件唤醒
停机模式对电路的影响 :
关闭了1.8V区域的时钟(把运行的高速时钟关了,CPU和外设暂停工作),不过由于没有关闭电源,所以CPU和外设寄存器数据都是维持原状的
补充说明HSI内部高速时钟和HSE外部高速时钟会关闭,LSI内部低速时钟和LSE外部低速时钟,并不会主动关闭 ,如果开启过这两个时钟,还可以继续运行
电压调节器 ,无论是开启 还是低功耗模式 都可以维持1.8V区域寄存器和存储器的数据内容 ,两者的区别就是,低功耗模式更省电一些,同时,低功耗模式正在唤醒时,要花更多的时间,相反,电压调节器开启的话,就是更耗电一些,唤醒更快了
待机模式:
- 执行完WFI/WFE 指令后,STM32进入待机模式,唤醒后程序从头开始运行
- 整个1.8V供电区域被断电,PLL、HSI和HSE也被断电,SRAM和寄存器内容丢失,只有备份的寄存器和待机电路维持供电
- 在待机模式下,所有的I/O引脚变为高阻态(浮空输入)
- WKUP引脚的上升沿、RTC闹钟事件的上升沿、NRST引脚上外部复位、IWDG复位退出待机模式
待机模式对电路的影响:
1.8V区域的时钟关闭 ,两个高速时钟关闭,电压调节器关闭 (意味着1.8V区域的电源关闭,内部的存储器和寄存器的数据全部丢失),并不会主动关闭LSI和LSE两个低速时钟,因为这两个低速时钟还要维持RTC和独立看门狗的运行

模式选择
- 执行WFI(Wait For Interrupt)或者WFE(Wait For Event)指令后,STM32进入低功耗模式
这两个指令也就是最终开启低功耗模式的触发条件,配置其他的寄存器都要在这两个指令之前

补充说明,如果想要在中断中调用WFI/WFE,并且想中断结束后再睡眠,可以考虑睡眠模式(等待中断退出)这个模式
十、开门狗
1.概述
- WDG(Watchdog)看门狗
- 看门狗可以监控程序的运行状态,当程序因为设计漏洞、硬件故障、电磁干扰等原因,出现卡死或跑飞现象时,看门狗能及时复位程序,避免程序陷入长时间的罢工状态,保证系统的可靠性和安全性
- 看门狗本质上是一个定时器,当指定时间范围内,程序没有执行喂狗(重置计数器)操作时,看门狗硬件电路就自动产生复位信号
STM32内置两个看门狗
-
独立看门狗(IWDG):独立工作,对时间精度要求较低
-
窗口看门狗(WWDG):要求看门狗在精确计时窗口起作用
独立看门狗(IWDG)由专用的低速时钟(LSI)驱动,即使主时钟发生故障它也仍然有效。
窗口看门狗由从APB1时钟分频后得到的时钟驱动,通过可配置的时间窗口来检测应用程序非正常的过迟或过早的操作
2.IWDG框图

防误操作功能:SR是只读的,这个不用保护,对PR和PLR的写操作,可以设置一个写保护措施,只有在键寄存器写入5555,才可以解除写保护,一旦写入其他值,PR和PLR再次被保护,从而防止了误操作
3.IWDG键寄存器
- 键寄存器本质上是控制寄存器,用于控制硬件电路的工作
- 在可能存在干扰的情况下,一般通过在整个键寄存器写入特定值来代替控制寄存器写入一位的功能,以降低硬件电路受到干扰的概率
| 写入键寄存器的值 | 作用 |
|---|---|
| 0xCCCC | 启用独立看门狗 |
| 0xAAAA | IWDG_RLR中的值重新加载到计数器(喂狗) |
| 0x5555 | 解除IWDG_PR和IWDG_RLR的写保护 |
| 0x5555之外的其他值 | 启用IWDG_PR和IWDG_RLR的写保护 |
4.WWDG结构框图及工作特性

- 如果把T6位看作是计数器的一部分,那就是整个计数器值减到0x40之后溢出
- 如果把T6位当成溢出标志位,低6位当做计数器,那就是低六位的计数值减到0之后溢出
工作特性
- 递减计数器T6:0的值小于0x40时,WWDG产生复位
- 递减计数器T6:0在窗口W6:0外被重新装载时,WWDG产生复位
- 递减计数器T6:0等于0x40时可以产生早期唤醒中断(EWI),用于重装载计数器以避免WWDG复位
补充说明:
这里的意思是,减到0x40时,产生中断,然后再减一个数,到0x3F时,产生复位,那这样,中断就是溢出的前一刻,所以在这个早期唤醒中断里,可以用来执行一些紧急操作,比如保存重要数据、关闭危险设备
- 定期写入WWDG_CR寄存器(喂狗)以避免WWDG复位

纵轴T6:0,包含T6位的CNT,递减计数器
5.IWDG超时时间和WWDG超时时间
IWDG超时时间
-
超时时间:TIWDG = TLSI × PR预分频系数 × (RL + 1)
-
其中:TLSI = 1 / FLSI
超时频率,就等于LSI的频率/预分频/重装值

WWDG超时时间
-
超时时间:
TWWDG = TPCLK1 × 4096 × WDGTB预分频系数 × (T5:0 + 1)
补充说明:
这里多乘一个4096,是因为PCLK1进来之后,先执行了一个固定的4096分频
-
窗口时间:
TWIN = TPCLK1 × 4096 × WDGTB预分频系数 × (T5:0 - W5:0)
其中:TPCLK1 = 1 / FPCLK1
这里W5:0是不包含T6位这一位的
超时时间,就是喂狗的最晚时间 窗口时间,就是喂狗的最早时间

6. IWDG与WWDG对比
| WDG独立看门狗 | WWDG窗口看门狗 | |
|---|---|---|
| 复位 | 计数器减到0后 | 计数器T5:0减到0后、过早重装计数器 |
| 中断 | 无 | 早期唤醒中断 |
| 时钟源 | LSI(40KHz) | PCLK1(36MHz) |
| 预分频系数 | 4、8、32、64、128、256 | 1、2、4、8 |
| 计数器 | 12位 | 6位(有效计数) |
| 超时时间 | 0.1ms~26214.4ms | 113us~58.25ms |
| 喂狗方式 | 写入键寄存器,重装固定值RLR | 直接写入计数器,写多少重装多少 |
| 防误操作 | 键寄存器和写保护 | 无 |
| 用途 | 独立工作,对时间精度要求较低 | 要求看门狗在精确计时窗口起作用 |
十一、FLASH
1.概述
- STM32F1系列的FLASH包含程序存储器、系统存储器和选项字节三个部分,通过闪存存储器接口(外设)可以对程序存储器和选项字节进行擦除和编程
- 读写FLASH的用途:
利用程序存储器的剩余空间来保存掉电不丢失的用户数据
通过在程序中编程(IAP),实现程序的自我更新 - 在线编程(In-Circuit Programming --ICP)用于更新程序存储器的全部内容,它通过JTAG、SWD协议或系统加载程序(Bootloader)下载程序
- 在程序中编程(In-Application Programming -- IAP)可以使用微控制器支持的任一种通信接口下载程序
程序升级:
自己写一个Bootlodader,放在程序更新时,不会覆盖的地方,需要更新程序时,我们控制程序跳转到这个自己写的Bootloader里来,在这里,我们就可以接收,任意一种通信接口传过来的数据,比如串口、USB、蓝牙转串口、WIFI转串口等等,这个传过来的程序,就是待更新的程序,然后控制FLASH读写,把收到的程序,写到程序正常运行的地方,写完之后,在控制程序跳转回到正常运行的地方,或者直接复位,这样程序就完成了自我升级复位,和系统存储器这个的Bootloader一样
FLASH基本结构


平时说的,芯片闪存容量是64K、128K,指的是主存储器的容量
2.FLASH解锁
-
FPEC共有三个键值:
RDPRT键 = 0x000000A5
KEY1 = 0x45670123
KEY2 = 0xCDEF89AB
-
解锁:
复位后,FPEC被保护,不能写入FLASH_CR
在FLASH_KEYR先写入KEY1,再写入KEY2,解锁
错误的操作序列会在下次复位前锁死FPEC和FLASH_CR
-
加锁:
设置FLASH_CR中的LOCK位锁住FPEC和FLASH_CR
使用指针访问指定的寄存器
-
使用指针读指定地址下的存储器:
uint16_t Data = *((__IO uint16_t *)(0x08000000));
-
使用指针写指定地址下的存储器:
*((__IO uint16_t *)(0x08000000)) = 0x1234;
-
其中: #define __IO volatile
3.程序存储器编程、页编程、全擦除
程序存储器编程

程序存储器页编程

程序存储器全擦除

4.选项字节

- RDP:写入RDPRT键(0x000000A5)后解除读保护
- USER:配置硬件看门狗和进入停机/待机模式是否产生复位
- Data0/1:用户可自定义使用
- WRP0/1/2/3:配置写保护,每一个位对应保护4个存储页(中容量)
选项字节编程
- 检查FLASH_SR的BSY位,以确认没有其他正在进行的编程操作
- 解锁FLASH_CR的OPTWRE位
- 设置FLASH_CR的OPTPG位为1
- 写入要编程的半字到指定的地址
- 等待BSY位变为0
- 读出写入的地址并验证数据
选项字节擦除
- 检查FLASH_SR的BSY位,以确认没有其他正在进行的闪存操作
- 解锁FLASH_CR的OPTWRE位
- 设置FLASH_CR的OPTER位为1
- 设置FLASH_CR的STRT位为1
- 等待BSY位变为0
- 读出被擦除的选择字节并做验证
OPTWRE 位是 FLASH_CR 寄存器中的一个控制位
它的作用:可以把它看作一个"编辑选项字节的使能开关"。只有当这个开关被打开(OPTWRE 位为1)时,CPU才能对选项字节进行编程(写入)或擦除操作
OPTPG 位是 FLASH_CR 寄存器中的另一个控制位,用于选择"选项字节编程"这个操作模式 。你将它设置为1,Flash控制器就明白接下来要执行的是编程(写入) 操作。
要进行"选项字节擦除",则需要设置的是 OPTER 位
5.器件电子签名
-
电子签名存放在闪存存储器模块的系统存储区域,包含的芯片识别信息在出厂时编写,不可更改,使用指针读指定地址下的存储器可获取电子签名
-
闪存容量寄存器:
基地址:0x1FFF F7E0
大小:16位
-
产品唯一身份标识寄存器:
基地址: 0x1FFF F7E8
大小:96位


