By TSBread
GPIO(General Purpose Input Output)通用输入输出口
引脚电平:0~3.3v,部分引脚(带 FT 的(Five Tolerate))可容忍输入5v
*可配置为 8 中模式:
GPIO_Mode_AIN模拟输入:模拟信号。GPIO 无效,引脚直接接入内部 ADC
GPIO_Mode_IN_FLOATING浮空输入:数组信号。可读取引脚电平,若引脚悬空,则电平不确定
GPIO_Mode_IPD下拉输入:数字信号。可读取引脚电平,内部连接下拉电阻,悬空时默认低电平
GPIO_Mode_IPU上拉输入:数字信号。可读取引脚电平,内部连接上拉电阻,悬空时默认高点平
GPIO_Mode_OUT_OD开漏输出:数字信号。可输出引脚电平,高电平为高阻态,低电平接 VSS
GPIO_Mode_OUT_PP推挽输出:数字信号。可输出引脚电平,高电平接 VDD,低电平接 VSS
GPIO_Mode_AF_OD复用开漏输出:由片上外设控制。高电平为高阻态,低电平接 VSS
GOIO_Mode_AF_PP复用推挽输出:由片上外设控制。高电平接 VDD,低电平接 VSS

APB2:APB2 总线,所有 GPIO 都挂载在 APB2 总线上。其中 GPIO 外设名称按照 GPIOA、GPIOB、GPIOC 来命名。每个 GPIO 外设共有 16 个引脚,从 0~15 编号,如 GPIOA的第0号引脚一般称为 PA0
GPIO 模块:每个 GPIO 模块内包含了寄存器和驱动器
寄存器:使得 CPU 内核可以通过 APB2 总线对其进行读写,以达到输出和读取电平的功能。寄存器的每一位对应一个引脚,输出寄存器写 1 对应引脚输出高电平,反之输出低电平;输入寄存器读取位 1 则对应引脚目前是高电平,反之低电平。(因为 STM32 为 32 为单片机,所以内部寄存器都是 32 位的,而 GPIO 寄存器只有 16 位,所以仅低 16 位有效)
驱动器:增加信号的驱动能力

从左至右大致分为:寄存器、驱动器、IO 引脚
从上至下大致分为:输入、输出
输入部分:
I/O 引脚:使用两个保护二极管对输入电压进行钳位,上接 ,下接 。若输入电压 > 3.3v 则上方二极管导通,从而将电流流入 VDD 而不是内部电路,若输入电压 < 0v 则下方二极管导通,从而将电流流入 VSS
上/下拉电阻选择:该开关可通过程序进行配置,上导通、下断开则为上拉输入模式;下导通、上断开则为下拉输入模式;上下都断开则为浮空输入模式
施密特触发器(文档误译为肖特基触发器):对输入电压进行整形,如果输入电压大于某一阈值,输出就会瞬间升为高电平;如果输入电压小于某一阈值,输出就会瞬间降为低电平。输出虽然是数字信号但由于干扰可能会产生各种失真,使用施密特触发器用来防止误判
输入数据寄存器:将通过施密特触发器整形后的波形写入,用程序读取数据寄存器对应的某一位数据便可得知端口的输入电平
至片上外设:
模拟输入:未整形波形,输出模拟量。可连接到 ADC 上,因为 ADC 需要接收模拟量
复用功能输入:整形后波形,输出数字量。连接到其他需要读取端口的外设上(如串口的输入引脚等)
输出部分:
输出可由输出数据寄存器或片上外设来进行控制,经过数据选择器到输出控制部分
输出数据寄存器:普通的 IO 口输出,每一位对应着相应的端口。该寄存器同时控制 16 个端口,且只能进行整体的读写(也可以先读取该寄存器,然后使用&=和|=的防止进行更改数据并写回该寄存器以达到控制单独一位的写入)
位设置/清除寄存器:可以用来单独操作输出数据寄存器的某一位,而不影响其他位。如果要将某一位置 1 则只需将位设置寄存器中对应位写 1 其他写 0 即可,若要置 0 则同理操作位清除寄存器即可
输出驱动器部分:上为 P-MOS,下为 N-MOS。在此可选择三种输出方式:
推挽(P-MOS、N-MOS 均有效。数据寄存器为 1 时,上管导通下管断开,高电平;为 0 时,下管导通上管断开,低电平):在此模式下高低电平均有较强的驱动能力,所以也称为强推输出模式
开漏(仅 N-MOS 有效。数据寄存器为 1 时,下管断开,高阻态;为 0 时,下管导通,低电平):该模式下仅低电平有驱动能力,高电平无驱动能力。该模式可用作通信协议的驱动方式,如 I2C 通信的引脚便是使用开漏模式,在多机通信的情况下该模式可以避免各个设备的相互干扰;也可用于输出 5v 的电平信号,在 IO 口外接一个上拉电阻到 5v 的电源,当输出低电平时由 N-MOS 直接接到 VSS,当输出高电平时由外 部的上拉电阻拉高至 5v
关闭(均无效):当引脚被配置为输入模式时使用该模式,端口的电平由外部信号来控制
*注意: 一个端口只能有一个输出,但可以有多个输入(输入模式下输出均无效,但输出模式下仍然可以进行输入)
根据上述所得,操作 STM32 的 GPIO 总共需要 3 个步骤:
使用 RCC(复位和时钟控制)开启 GPIO 的时钟
使用GPIO_Init函数初始化 GPIO
使用输出或输入的函数控制 GPIO
中断:在主程序运行过程中,出现了特定的中后段触发条件(中断源),使得 CPU 暂停当前正在运行的程序,转而去处理中断程序,处理完成后又返回原来被暂停的位置继续运行。使用中断可大大提高程序运行效率 (如串口数据发送,避免了为了防止数据被覆盖从而使得主程序不断查询是否有串口数据发送的事件被触发导致无法继续运行;若没有定时器中断那主程序只能依靠堵塞的 Delay 来实现定时的功能)
中断优先级:功能类似裁决器,当多个中断源同时申请中断时,CPU 根据中断源的轻重缓急进行裁决,优先相应更加紧急的中断源
中断嵌套:当一个中断程序正在运行时,又有新的更高优先级的中断源申请中断,CPU 将会中断当前的中断程序转而去处理新的更高级别的中断程序,处理完后再依次返回
中断向量表:



STM32 中断通道都包括:EXTI(外部中断)、TIM(定时器)、ADC(模数转化器)、USART(串口)、SPI、I2C、RTC(实体时钟)等多个外设
使用NVIC统一管理中断:每个中断都拥有 16 个可编程的右胸按等级,可对优先级进行分组,进一步设置抢占优先级和响应优先级。NVIC是 STM32 用来管理中断、分配优先级的
本节中(EXTI 外部中断)仅讨论上图中的中断资源为:EXTI0~4、EXTI9_5、EXTI15_10
中断地址:因为程序中的中断函数的地址是由编译器分配的,并非固定的。但中断跳转由于硬件限制只能跳转到固定的地址执行程序,所以将跳转到中断函数的代码写入固定的内存地址,从而当中断发生后跳转入固定的中断地址后再跳转进对应的中断函数处

NVIC 嵌套中断向量控制器:将所有中断接收并按优先级排序后传给 CPU

NVIC 的中断优先级由优先级寄存器的 4 位(0~15)决定,这 4 位可以进行切分,分为高 n 位的抢占优先级和低 4-n 位的响应优先级。
响应优先级和抢占优先级均相同的中断按照中断向量表的**中断号(优先级)**进行排序
响应优先级:当前一个中断处理完后,优先处理响应优先级高的中断
抢占优先级:即使前一个中断未处理完,也要优先处理抢占优先级高的中断直到该中断处理完
EXTI(Extern Interrupt)外部中断
EXTI 可以检测指定 GPIO 口的电平信号,当其指定的 GPIO 口产生电平变化时,EXTI 将立即向 NVIC 发出中断申请,经过 NVIC 裁决后即可中断 CPU 主程序,使 CPU 执行 EXTI 对应的中断程序
支持的触发方式:上升沿 / 下降沿 / 双边沿 / 软件触发
EXTI 中断支持所有的 GPIO 口,但相同的 Pin 不能同时触发中断(如 PA0 和 PB0 不能同时使用)
通道数:16 个 GPIO 的 Pin、PVD 输出、RTC 闹钟、USB 唤醒、以太网唤醒
触发响应方式:中断响应(向 NVIC 申请中断) / 事件响应(当电平引脚发生变化时可以选择直接触发中断或是触发事件,若触发事件则中断信号不会传给 CPU,而是传给其他外设,用来触发其他外设的操作(如 ADC 转换、触发 DMA 等))

AFIO:中断引脚选择。本质是一个数据选择器,因为 EXTI 只有 16 个 GPIO_Pin 通道所以进行筛选。AFIO 会在每个 GPIO 中选择一个连接到 EXTI 中,也因此相同的 Pin 不能同时触发中断(对于 PA0、PB0、PC0 这些 Pin,在通过 AFIO 选择后只有其中一个能连接到 EXTI 的通道 0 上,其他 Pin 同理)
EXTI -> NVIC:EXTI 总共输入 20 个通道,但传入 NVIC 的外部中断 5~9 和 10~15 合并为EXTI9_5、EXTI15_10。也就是说被包含的外部中断被触发时都会触发同一个中断函数,需要再在该中断函数中通过标志位来区分具体是哪个通道触发的中断
EXTI -> 其他外设:用来触发其他外设操作的,也就是上小节(#EXTI 简介)提到的事件响应

AFIO 主要用于引脚复用功能的选择和重定义(数据选择器的作用)
在 STM32 中 AFIO 起到了两个作用:复用引脚重映射(也就是引脚映射表中的复用功能转换为重定义功能)、中断引脚选择
使用数据选择器在同号的 Pin 中选择其中一个的 GPIO 并传给 EXTI,因此不能选择同号 Pin 触发外部中断

*以一路通道为例:
AFIO -> 输入线 -> 边沿检测:根据检测结果与触发方式决定是触发上升沿还是下降沿还是二者都触发
边沿检测 -> 或门:硬件触发与软件中断寄存器接到同一个或门上,若任意一值为 1 则输出 1。此处软件中断寄存器为软件触发位置
或门 ->:上路触发中断,下路触发事件。
触发中断:触发后会先置一个挂起寄存器,相当于中断的标志位。通过读取请求挂起寄存器的值来判断哪个通道触发了中断。在此路中中断屏蔽寄存器和一个与门组成一个类似开关的功能来决定是否将挂起寄存器的输出发送到 NVIC
触发事件:在此路中事件屏蔽寄存器和一个与门组成 一个类似开关的功能来决定是否将信号继续发送到其他外设。
脉冲发生器:输出一个电平脉冲,用来触发其他外设的根据上述所得,使用外设作为 EXTI 外部中断并执行程序总共需要 5 个步骤:
TIM(Timer)定时器
最基本的功能:定时器可以对输入的时钟(可靠的基准时钟,STM32 中使用主频 72MHz 的时钟)进行计数(也就是个计数器),并在计数值达到设定值时触发中断。如对 72MHz 计 72 个数也就是 ;计 72000 个数也就是
STM32 的定时器拥有:16 位计数器(用来执行计数定时的一个寄存器,每来一个时钟,计数器 + 1)、预分频器(对计数器的时钟进行分频,使得计数更加灵活)、自动重装寄存器(计数的目标值,要在多少个时钟时申请中断),这部分电路被称为时基单元,在 72MHz 计数时钟下可以实现最大 59.65s 的定时。
因以上三种寄存器均为 16 位,,若预分频器和自动重装都设定到最大,那定时器的最大定时时间就是 59.65s(,取)
STM32 的定时器支持级联模式(用一个定时器的输出当作另一个定时器的输入),若两个定时器级联,最大定时时间就是
STM32 的定时器不仅具备基本的定时中断功能,还包括内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等多种功能

计数器模式:
向上计数模式(基础定时器):从 0 开始自增计数直到达到目标值,然后清零同时申请中断
向上计数模式(基础定时器、通用定时器、高级定时器):从目标值开始自减计数直到达到 0,然后重装同时申请中断
中央对其模式(基础定时器、通用定时器、高级定时器):从 0 开始自增到目标值,然后申请中断,再继续向下自减到 0,再次申请中断,依次循环

从下往上看:预分频器、计数器、自动重装寄存器三者构成了最基本的计数计时电路,也被称为时基单元
PSC 预分频器:对 72MHz 的计数时钟进行预分频,= 0 -> 不分频(也就是 1 分频。此时输出频率 = 输入频率 = 72MHz),= 1 -> 2 分频(输出频率 = 输入频率 / 2 = 36MHz),= 2 -> 3 分频,以此类推 (实际分频系数 = 预分频器值 + 1.因预分频器是 16 位的,也就是可以写 65535,实际分频系数 = 65535 + 1 = 65536 分频)。CK_PSC连接基准计数时钟的输入。由于基本定时器只能选择内部时钟,所以CK_PSC直接连接到输入端(内部时钟CK_INT),内部时钟的来源是RCC_TIMxCLK,频率值一般都是系统的主频 72MHz
CNT 计数器:对预分频后的计数时钟进行计数,计数时钟每来一个上升沿,计数器的值就 + 1(向上计数模式)。计数器也是 16 位,可以从 0 加到 65535,再加则会从 0 重新开始
ARR 自动重装载寄存器:存储写入的计数目标,同为 16 位寄存器。当计数器值到达目标值时产生中断信号并清零计数器。
UI表示会产生中断信号(这种计数值等于自动重装值产生的中断被称为更新中断),中断产生后通向NVIC
U表示会产生一个事件(被称为更新事件),不会触发中断,但可以触发内部其他电路工作

中间为时基单元,结构与基础定时器相同
上面为内外时钟源选择和主从触发模式的结构
内外时钟源选择:时钟源不仅可以选择内部的 72MHz 时钟(CK_INT)还可以选择外部时钟
TIMx_ETR引脚上的外部时钟,需要配置TIMx_ETR -ETR-> 极性选择、边沿检测和预分频器 -ETRP-> 输入滤波 -ETRF-> CK_PSC,这一路也叫做外部时钟模式 2
TRGI触发输入,可以触发定时器的从模式。可当作外部时钟来使用,该路被称为外部时钟模式 1。该模式下可通过的外部时钟有:
ETR:引脚信号(可通过上路来当作时钟,也可通过下路到数据选择器来当时钟。二者等价,但下路输出会占用触发输入的通道)
ITR:来自其他定时器的时钟信号(主模式的输出 TRGO 可以通向其他定时器,连接到其他定时器的 ITR 引脚(实现定时器级联的功能))
从定时器的ITRx与主定时器的TRGO连接方式见下图 👇

例如:我要实现两个定时器级联的模式,先初始化 TIM3,使用主模式将更新事件映射到其TRGO上,再初始化 TIM2,对应 TIM3TRGO的是ITR2,接着选择时钟为外部时钟模式 1。这样 TIM3 的更新事件就可以驱动 TIM2 的时基单元,也就完成了两个定时器的级联功能
TI1F_ED:连接着输入捕获单元的 CH1 引脚,也就是从TIMx_CH1获取时钟,ED(Edge)的含义为边沿,也就是通过该路输入的时钟,上升沿和下降沿均有效
TI1FP1:连接到 CH1 引脚的时钟
TI2FP2:连接到 CH2 引脚的时钟
TRGO:定时器的主模式输出,可以把内部的一些事件映射到TRGO引脚上,用于触发其他定时器、DAC、ADC
右下角为输出比较(OC)电路,总共 4 个通道(CH1~4),用于输出 PWM 波形,驱动电机
左下角为输入捕获(IC)电路,总共 4 个通道(CH1~4),用于测量方波的频率等
下面中间为捕获/比较寄存器,是输入捕获和输出比较电路共用的。因为输出比较和输入捕获不能同时使用,所以该寄存器是共用的,包括引脚

相比通用定时器而言,主要改动的是右下和左下部分
重复次数计数器:在申请中断的地方新增,可以实现每隔几个计数周期才会发生一次更新事件和更新中断。相当于对原本的中断信号再进行一次分频
输出比较模块:
DTG和输出比较电路:(Dead Time Generate)死区生成电路,右边输出引脚由原本的一路输出变成两路互补的输出。此处一般用作驱动三相无刷电机所以仅 CH1~3 有该互补输出功能
TIMx_BKIN:刹车输入功能,给电机驱动提供安全保障。当刹车引脚产生信号或内部时钟失效,控制电路会自动切断电机的输出

*此图中高低定时器的重复次数计数器未画出,应在时基单元输出到中断控制之间

CK_PSC:预分频器的输入时钟,内部时钟为 72MHz
CNT_EN:计数器使能,高电平计数器运行,低电平计数器停止
CK_CNT:计数器时钟,既是预分频器的输出也是计数器的输入。计数器未使能时计数器时钟不运行,使能后前半段等于预分频器前的时钟,后半段二分频变为前时钟的一半
计数器寄存器:跟随时钟的上升沿不断自增,在 FC 后变为 0,由此可退出 ARR 自动重装值为 FC
更新事件(UEV):当达到重装值后产生一个脉冲
预分频缓冲器:为预分频控制寄存器的影子寄存器,用户读写预分频控制寄存器,但实际控制分频的为该寄存器。(结构图中带黑色阴影的寄存器都含有一个影子寄存器)
*计数器计数频率:

CK_INT:内部时钟 72MHZ
CNT_EN:时钟使能,高电平开启
CK_CNT:计数器时钟,因为分频系数为 2 所以该频率为CK_INT / 2
计数器寄存器:在每个CK_CNT的上升沿自增。
计数器溢出:当计数到 0036 时溢出,到下一个上升沿清零
更新事件(UEV):当计数器溢出时产生一个脉冲
更新中断标志(UIF):当计数器溢出时置 1,只要为 1 就会申请中断,中断响应后需要在中断程序中手动清零,否则会一直申请中断
*计数器溢出频率:

*在此之前上面两个都是含有缓冲寄存器(影子寄存器)的情况,该时序为没有缓冲寄存器的时序情况
在计数的中途将自动加载寄存器的值从 FF 改为 36,计数器寄存器的值将直接在到达 36 时产生更新事件

在计数的中途将自动加载寄存器的值从 F5 改为 36,因真正起作用的是影子寄存器,所以当前计数目标仍旧是 F5,直到达到 F5 产生更新事件才会将自动重装值改为 36
影子寄存器的作用就是为了让值的变化和更新事件保持同步,防止在运行途中更改从而造成错误
根据上述所得,使用内部时钟进行定时中断总共需要 6 个步骤:
OC(Output Compare)输出比较(其他缩写:IC(Input Capture)输入捕获、CC(Capture/Compare)输入捕获和输出比较单元)
输出比较可以通过比较 CNT 计数器与 CCR (捕获/比较寄存器) 寄存器值的关系,来对属于出电平进行置 1、置 0、翻转的操作,用于输出一定频率和占空比的 PWM 波形
每个通用和高级定时器都拥有 4 个输出比较通道。每个通道拥有独立的捕获/比较寄存器,但由于每个定时器只有一个 CNT 计数器,所以同一定时器不同通道的占空比可以不同,但频率是相同的
高级定时器前 3 个通道额外拥有死区生成和互补输出的功能
PWM(Pulse Width Modulation)脉冲宽度调制

对于具有惯性的系统中,可以通过对一系列脉冲的宽度调制来等效的获得所需要的模拟参量,例如电机、LED 等
如上图所示,高低电平跳变的数字信号可以等效为蓝色虚线表示的模拟量。
上面电平时间 > 下面电平时间:等效的模拟量偏向于上面
上面电平时间 < 下面电平时间:等效的模拟量偏向于下面

参数:、、
频率:频率越快,等效模拟的信号就越平稳,但性能开销就越大(一般 PWM 频率都在 x Khz ~ x0 KHz)
占空比:占空比决定了 PWM 等效出来的模拟电压的大小(一般来说是线性关系的)
分辨率:如果占空比只能是 1%、2%、3%以 1%进行步距跳变则分辨为 1%,如果可以是 1.1%、1.2%、1.3%以 0.1%的步距跳变则分辨率为 0.1%。分辨率也就是占空比变化的精细程度,需要多高具体看项目需求。越是高频率与高分辨率则对硬件电路的要求就越高

/ :让通道 1 的 CCR(捕获/比较寄存器)与 CNT 计数器进行比较,当满足以上两种条件中的任意一种时,给输出模式控制器传一个信号。
输出模式控制器:当获得信号后,就会改变它输出oc1ref(ref:参考信号)的高低电平。该控制器输入 CNT 和 CCR 的大小关系,输出 REF 的高低电平,可通过 OC1M 寄存器[2:0]部分配置更多模式,模式见下图 👇

冻结:当正在输出 PWM 时,向暂停一会输出,可以使用该模式使输出暂停(REF 高低电平保持为暂停前一刻的电平状态)
匹配时置有效电平:有效电平是高等定时器的说法,类似于置高电平
匹配时置无效电平:无效电平是高等定时器的说法,类似于置低电平
匹配时电平翻转:字面意思。可以方便的输出一个频率可调,占空比始终为 50%的 PWM 波形
强制为无效电平:与 CNT 和 CCR 值无关,强制将 REF 输出为低电平
强制为有效电平:与 CNT 和 CCR 值无关,强制将 REF 输出为高电平
至主模式控制器(信号向上):可以将oc1ref信号映射到主模式的TRGO输出上
极性选择(信号向右):选择是否要将输入的高低电平进行反转。给 CC1P 寄存器写 0,信号就会从上面(0 处)走信号电平不反转。写 1 则通过一个非门从下面(1 处)走,信号电平反转
输出使能电路:选择是否输出。通过置 CC1E 寄存器 1 或 0 实现
OC1:也就是 CH1 通道的引脚,可在引脚定义表中查询具体哪个 GPIO 口

左上角为:时基单元和运行控制部分,时基单元左边为时钟源选择,详细在上一小节
CNT计数器值与CCR寄存器值进行比较 -> 输出模式控制器选择PWM1模式 -> 将REF输出到极性选择 -> 最后输出给GPIO
PWM1 模式逻辑参考右上图
PWM 频率:
PWM 占空比:
PWM 分辨率:
根据上述所得,使用 PWM 输出总共需要 5 个步骤:
IC(Input Capture)输入捕获
输入捕获模式下,当通道输入引脚出现指定电平跳变(上升沿或下降沿等,可通过程序配置)时,当前 CNT 的值将被所存到 CCR 中(读取 CNT 计数器值到 CCR 锁存器中),可用于测量 PWM 波形的频率、占空比、脉冲间隔、电平持续时间等参数
通用和高级定时器每个都拥有 4 个输入捕获的通道
可配置为PWMI 模式,同时测量频率和占空比
可配合主从触发模式,实现硬件全自动测量

测频法(左边):适合测量高频信号。在闸门时间 T 内,对上升沿计次,得到 N,则频率 fx = N / T。根据频率定义可将 T 设为 1s,则计数 N 得到结果 x Hz。或是给 T * 系数 = 1s 即可
测周法(右边):适合测量低频信号。两个上升沿内,以标准频率 fc 计次,得到 N,则频率 fx = fc / N。周期的倒数就是频率,如果测出一个周期的时间,取倒数就是频率。在一个周期内使用一个已知的频率 fc(使用定时器获得),计一个数的时间为 1 / fc,周期内总过计了 N 个数,则周期是 N / fc,取倒数则为频率
中界频率:测频法与测周法误差相等的频率点

TIMx_CH1~4:OC 引脚,具体参考引脚定义表
异或门:当 CH1~3 中的任何一个引脚发生电平翻转时,输出产生一次电平翻转
输入滤波器和边沿检测器:滤波器对输入信号进行滤波,避免高频的毛刺信号导致误触发。边沿检测器与外部中断处类似,当出现指定的电平时就会触发后续电路。
此处每通道都有两路滤波器和检测器,所以分别输出两路经过处理的信号(
TIxFP1、TIxFP2(TIx Filter Polarity x))。以CH1和CH2为例:其中 CH1 的TI1FP1输出给 CH1(自己)的后续电路;TI1FP2则输出给 CH2 的后续电路;而 CH2 的TI2FP1输出给 CH1 的后续电路;TI2FP2则输出给 CH2(自己)的后续电路。用途:
预分频器:对前面输入的信号进行分频,然后输出信号触发捕获电路工作。捕获电路每触发一次,CNT 计数器的值就会向 CCR 寄存器转运一次,同时发生一个捕获事件(CCxI),该事件会在状态寄存器置标志位,同时也可产生中断(如果需要在捕获的瞬间处理一些事情,则可以开启捕获中断)

TI1:引脚输入信号
fDTS:滤波器的采样时钟来源
TIMx_CCMR1:寄存器的 ICF 位可以控制滤波器的参数,原理与其他滤波器相同
TI1F:经过滤波后的引脚输入信号
TIMx_CCER:使用CC1P位对边沿检测器控制数据选择器,选择TI1FP1输出触发上升沿还是下降沿信号,该信号通过数据选择器进入 CH1 后续捕获电路
TIMx_CCMR1:CC1S位控制数据选择器对TI1FP1、TI2FP1、TRC数据进行选择(IC1)后进入分频器。ICPS位配置分频器,选择不分频、2 分频、4 分频、8 分频。
TIMx_CCER:CC1E位控制输出使能或失能
至从模式控制器:可以在捕获之后自动完成 CNT 的清零工作

主从触发模式 = 主模式 + 从模式 + 触发源选择
主模式:可以将定时器内部信号映射到 TRGO 引脚,用于触发其他外设
从模式:可以接收其他外设或自身外设的一些信号(TRGI),用于控制自身定时器的运行,被别的信号控制。可在列表中选择一项操作来自动执行
触发源选择:选择指定的一个信号,得到 TRGI。选择从模式的触发信号源,可认为是从模式的一部分
例 1:如想让 TI1FP1 信号自动触发 CNT 清零,在触发源选择处选择
TI1FP1,从模式执行Reset操作
例 2:想实现定时器的级联,可以选择一个定时器主模式输出更新模式信号到 TRGO,另一个定时器选择上一个定时器(
ITRx)触发从模式,从模式选择执行外部时钟模式 1的操作,从而实现
主模式功能见下图 👇

触发选择功能见下图 👇

从模式功能见下图 👇



根据上述所得,使用输入捕获测频率总共需要 7 个步骤:
TI1FP1Reset操作Encoder Interface 编码器接口
编码器接口可接收增量(正交)编码器的信号,根据编码器旋转产生的正交信号脉冲,自动控制 CNT 自增或自减,从而指示编码器的位置、旋转方向和旋转角度
每个通用定时器和高级定时器都拥有 1 个编码器接口。如果一个定时器被配置为编码器接口模式,则基本无法作为他用(两个输入引脚借用了输入捕获的 CH1 和 CH2)。F103C8T6 只有 4 个定时器,最多接 4 个编码器

首先将 A 相和 B 相的所有边沿作为计数器的计数时钟,出现边沿信号时,就计数自增或自减。计数的方向根据另一相的状态来确定正反转

在该模式下,编码器接口的输出部分相当于从模式控制器,用来控制 CNT 的计数时钟和计数方向。此处不会使用 72MHz 的内部时钟与时基单元初始化
此处 ARR 也有效,一般设置为 65535(最大量程),利用补码的特性,很容易得到负数(uint16 转为 int16)


ADC(Analog - Digital Converter)模拟-数字转换器
ADC 可以将引脚上连续变化的模拟电压转化为内存中存储的数字变量,建立模拟电路到数字电路的桥梁
STM32系列的 ADC 是 12 位(分辨率:0 ~ (2 ^ 12) - 1 = 0 ~ 4095)逐次逼近型(工作模式)ADC,1us 转化时间(转化频率:从 AD 转化开始到产生结果需要花费 1us 事件,对应 AD 转化频率为 1MHz)
输入电压范围:0 ~ 3.3v,转化结果范围:0 ~ 4095。0v 对应结果 0,3.3v 对应 4095,两者程线性关系
拥有 18 个输入通道,可测量 16 个外部(GPIO)和 2 个内部(内部温度传感器、内部参考电压(1.2v 左右的基准电压,不随外部供电电压变化而变化。该参考电压VERFINT与 ADC 参考电压Vref+应另当别论,Vref+取自 VCC 电压,随外部供电而改变))信号源
规则组(用于常规使用)和注入组(用于突发事件)两个转化单元,可以列一个组,一次性启动一个组,连续转化多个值
模拟看门狗自动检测输入电压范围。值高于或低于某个阈值这类的判断可以交给模拟看门狗自动执行,模拟看门狗可以检测指定的某些通道,当 AD 值高于它设定的上阈值或者低于下阈值时,它就会申请中断,可以在对应中断函数中执行相应操作。避免了不停的手动读值,再用 if 进行判断
STM32F103C8T6 的 ADC 资源:ADC1、ADC2、10 个外部输入通道

该图为 ADC0809 的内部结构图,是独立的 8 位逐次逼近型 ADC IC
IN0~7:8 路输入通道,通过通道选择开关选中其中一路(通过下面的由ADDA~C输入的通道号到地址锁存和译码来进行。ALE为锁存与使能信号)
比较器:电压比较器,将模拟电压值与编码数据对应。比较器输入端分别连接通过选择器选择后的输入待测电压,另一个是 DAC(输入数据,输出数据对应的电压。使用加权电阻网络实现)的电压输出端,通过比较输入端返回高点电平。如果 DAC 输出的电压较大就调小 DAC 数据,反之增大,直到 DAC 输出的电压与外部通道输入的电压近似相等,则 DAC 输入的数据就是外部电压的编码数据了。电压调节的过程就是通过逐次逼近 SAR 实现的
为了更快找到对应编码,一般会使用二分法来查找。8 位的 ADC,编码就是从 0 ~ 255.第一次比较给 DAC 输入 255 的一半(128)进行比较。如果 DAC 电压大了,则再给 128 的一半(64),...以此类推。如果其中 DAC 输出电压 32 小了,就给 32 ~ 64 的中间值然后继续比较。如果使用二进制表示,128、64、32 等正好是二进制每一位的位权,相当于对二进制从高位到低位依次判断是 1 还是 0 的过程(逐次逼近型)
EOC:End of Convert,转换结束信号
START:开始转化,输入一个脉冲开始
CLOCK:ADC 时钟,因为 ADC 内部是一步一步进行判断的
Vref:DAC 参考电压,将编码对应模拟电压,同时也决定了 ADC 的输入范围,ADC 的参考电压

输入:
位于左中部分
F103C8T6只有 10 个外部输入通道)全部输入模拟多路开关模拟多路开关:可以选择多路通道,且在转换的时候被分为规则通道组(可以一次性最多选中 16 个通道。但只有 1 个规则通道数据寄存器,会导致组里后来转化的值覆盖前一次转化的值,一般配合#DMA在转换完成后转运数据)和注入通道组(最多可以一次选中 4 个通道。注入通道数据寄存器有 4 个,每个转化后的结果都不会被组里其他数据覆盖)。右边是多路开关的输出,进入到模数转换器(执行前面提到的逐次比较的过程),转换结构会直接存放在通道数据寄存器中,通过读取寄存器得到 ADC 转换的结果。一般都使用规则组,数据覆盖问题配合使用 DMA 转运转化后的数据
触发转化部分:
位于左下部分。相当于独立 ADC IC 的START信号,表示开始进行转化
TIMx_CHx,TRGO定时器主模式输出))供电:VDDA 与 VSSA 是 ADC 的供电引脚,且与 VREF 相连
VREF:VREF+与 VREF-决定了 ADC 的参考电压,决定了 ADC 输入电压的范围。实际没有该引脚,在内部已经与供电引脚相连接
ADCCLK:ADC 的时钟,来自 ADC 的预分频器(源于 RCC(拥有 6、8 分频,结果分别为 14MHz、9MHz)),用于驱动内部逐次比较的时钟(也就是独立 ADC IC 的CLOCK输入)。
转换完成信号:
位于上方标志位处
如果开启 NVIC 对应的通道,则转换完成后的状态寄存器会触发中断


在引脚定义框图中ADC12_INx意思是 ADC1 和 ADC2 的 INx 通道
ADC1 和 ADC2 的输入引脚全都相同,此处用途为 ADC 的高级功能,双 ADC 模式(配合组成同步模式、交叉模式(ADC1 和 ADC2 交叉地对一个通道进行采样,进一步提高采样率)等)
1. 单次转换,非扫描模式:
非扫描模式:规则组的序列中只有第一个有效,在里面写入要转换的通道
单次转化:触发转换后,ADC 就会对序列 1 的通道进行模数转换。当转换完成,转换结果将会放在数据寄存器中,同时给 EOC 标志位置 1

2. 连续转换,非扫描模式:
非扫描模式:规则组的序列中只有第一个有效,在里面写入要转换的通道
连续转换:当一次转换结束后不会停止,而是立即进行下一轮的转换,然后一直持续下去。这样子只需要最开始触发一次,之后就可以一直转换了,不用再判断是否结束后再次开启转换

3. 单次转换,扫描模式:
扫描模式:可以在序列中写入多个需要转换的通道(通道位置任意、允许重复,初始化结构体中需要定义序列中待转换通道的数目,ADC 将按顺序将转换的结果都存入同一个寄存器中(规则组)
单次转化:触发转换后,ADC 就会对序列 1 的通道进行模数转换。当转换完成,转换结果将会放在数据寄存器中,同时给 EOC 标志位置 1

4. 连续转换,扫描模式:
扫描模式:可以在序列中写入多个需要转换的通道(通道位置任意、允许重复,初始化结构体中需要定义序列中待转换通道的数目,ADC 将按顺序将转换的结果都存入同一个寄存器中(规则组)
连续转换:当一次转换结束后不会停止,而是立即进行下一轮的转换,然后一直持续下去。这样子只需要最开始触发一次,之后就可以一直转换了,不用再判断是否结束后再次开启转换

注意:

也就是每个组触发控制可以进行选择的部分
EXTI 线 11/TIM8_TRGO 事件:这个信号类型可以是引脚,也可以是定时器,具体需要用 AFIO 重映射来确定
一般使用数据右对齐,这样直接读取数据就是转换结果
左对齐一般用来降低采集精度(真有这种需求吗?)

AD 转换的步骤:采样,保持,量化,编码
STM32 ADC 的总转换时间为:
采样时间:采样保持所花费的时间,程序中可配置。采样的时间越大,越能避免一些毛刺信号的干扰,但转化时间也会相应延长
12.5 个 ADC 周期:量化编码所花费的时间。因为是 12 位 ADC 所以需要花费 12 个周期,剩下 0.5 个周期可能是做其他事情而花费。ADC 周期是从 RCC 分频过来的ADCCLK(最大 14MHz)
例:当 ADCCLK = 14MHz,采样时间位 1.5 个 ADC 周期:
ADC 有一个内置自校准模式。校准可以大幅减小因内部电容器组的变化而造成的精度误差。校准期间,在每个电容器上都会计算出一个误差修正码(数字值),这个码用于消除在随后的转换中每个电容器上产生的误差
建议在每次上电后执行一次校准
启动校准前,ADC 必须处于关电状态超过至少两个 ADC 时钟周期
注意:该过程为自动的,仅需在 ADC 初始化最后添加几行代码即可

根据上述所得,使用软件触发 ADC 单次非扫描模式测量输入模拟电压总共需要 5 个步骤:
ADC_Cmd函数开启 ADC。根据手册建议可以对 ADC 进行校准,减小误差在 ADC 工作时,如果想用软件触发转换、读取转换结果,都可以调用对应函数
提供外设和存储器或者存储器和存储器之间的高速数据传输 ,无需 CPU 干预,节省了 CPU 的资源
外设:指外设的数据寄存器(Data Register),例如 ADC 的 DR、串口的 DR 等
存储器:指运行内存 SRAM 和程序存储器 Flash
STM32F103C8T6 仅含有 DMA1(7 个通道),每个通道都支持软件触发和特定的硬件触发
数据从一个地方转移到另一个地方就需要占用一个通道。多个通道之间可以各转各的,互不干扰
如果是存储器到存储器之间的转运则需要软件触发(如需要把 Flash 里一批数据转运到 SRAM 中)。如果是外设到存储器的转运则因外设数据有一定时机所以需要使用硬件触发(如转运 ADC 的数据,要等 ADC 每个 AD 转换完成后,硬件触发 DMA 再进行 DMA 转运)
特定的指:每个 DMA 通道的硬件触发源不同,外设要使用对应通道 DMA 就需要使用该通道连接的那个通道,不能任意选择通道(在#DMA 请求中有再次说明)

外设和存储器的转运本质上都是存储器和存储器之间的转运,只不过 STM32 特别指定了可以转运外设存储器而已
表中存储器分为两类:ROM和RAM
ROM:只读存储器,非易失性、掉电不丢失的存储器
RAM:随机存储器,易失性、掉电丢失的存储器
*可用地址来确定数据存储器类型
选项字节再 ROM 区的最后面,下载程序时可以选择不刷新选项字节的内容,这样选项字节的配置可以保持不变。选项字节主要是 Flash 的读、写保护,看门狗等的配置
参考文档:STM32F103x8B 数据手册(中文)/ #存储器映像

核心中包含 CPU 和内核外设等,其余均可看作存储器
总线矩阵:总线矩阵的左端为主动单元(拥有存储器的访问权),右端为被动单元(存储器只能被左端主动单元读写)
DCode 总线专门访问 Flash,系统总线访问其他存储器,由于 DMA 要转运数据所以也必须要有访问主动权
由此可得主动单元包括:CPU 内核、DMA 总线
仲裁器作用:用于调度各个通道,防止产生冲突。由于 DMA 总线只有一条所以当通道传递数据冲突时根据通道优先级进行传输
在总线矩阵中也含有仲裁器,若 CPU 和 DMA 都要访问同一个目标则会使 DMA 优先,暂停 CPU 访问以防止冲突,不过总线仲裁器仍然会保证 CPU 得到一半的总线带宽
AHB 从设备:用于配置 DMA 参数,也就是 DMA 自身的配置寄存器。因连接到了右边的 AHB 总线,所以 DMA 既是总线矩阵的主动单元,可读写各种存储器也是 AHB 上的被动单元。通过CPU内核->系统->总线矩阵->AHB总线->AHB从设备就可以对 DMA 进行配置了
DMA 请求:用于硬件触发 DMA 的数据转运。连接着各个外设所以是 DMA 的硬件触发源

方向:控制数据传输方向(外设到存储器/存储器到存储器)
外设&存储器参数:
起始地址:该地址决定数据是从哪里来到哪里去的
数据宽度:指定一次转运要按多大的数据宽度来进行(字节 Byte(uint8_t)、半字 HalfWord(uint16_t)、字 Word(uint32_t))。如转运 ADC 的数据,其数据宽度为 16 位,所以参数选择半字
地址是否自增:指定一次转运完成后,下一次转运是否要把地址移动到下一个位置去,类似指针 p++的效果。
例:(ADC 扫描模式,用 DMA 进行数据转运):外设地址为 ADC_DR 寄存器。外设 DR 地址不用递增,否则将会转移其他寄存器的数据;而存储器的地址需要递增,每转运一个数据后就向后移动,否则就会覆盖上次转运的数据结果
图中外设与存储器仅作为名称,也可称为站点 A、B
传输计数器:用来指定总共需要转运几次。是自减计数器,当减到 0 时停止数据转运,且自增的地址也会恢复到起始地址的位置
自动重装器:决定了转运的模式(单次模式(如转运数组)、循环模式(如 ADC 扫描模式+连续转换))。当传输计数器到 0 时决定是否要恢复到最初的值。若不使用则 DMA 结束,否则立即重装到初始值
DMA 触发控制:决定 DMA 要在什么时机进行转运。可选择硬件触发或软件触发,由M2M(Memory to Memory)参数(0 / 1)决定
软件触发(M2M = 1):并非是调用某个函数一次触发一次,而是以最快的速度连续不断的触发 DMA,直到传输计数器为 0 结束该轮循环,可理解为连续触发。软件触发与循环模式(自动重装器)不能同时使用,否则 DMA 将会进入死循环。软件触发一般适用于存储器到存储器的转运,因为是软件启动,不需要时机,且想尽快完成
硬件触发(M2M = 0):硬件触发源可选择 ADC、串口、定时器等。因为其需要一定的时机,如 ADC 转化完成、串口收到数据、定时器到设定时间等,在硬件到达时机时发送信号触发 DMA 进行转运
开关控制:也就是DMA_Cmd函数,用于给 DMA 使能
以上可得,DMA 的转运条件有:
开关控制:DMA_Cmd必须使能
传输计数器:值必须大于 0
触发源(M2M = 0 / 1):必须要有触发信号
触发一次进行一次转运,传输计数器自减一次。若没有自动重装时,无论是否触发,DMA 都不会再进行转运。若想再次开启则需要先给DMA_Cmd传入DISABLE关闭 DMA,再给传输计数器一个大于 0 的数后再给DMA_Cmd传入ENABLE开启 DMA,即可继续工作
在给传输计数器赋值时必须要先关闭 DMA 再进行(手册规定)

该图 👇 为上小节 DMA 触发源选择 👆 的详细结构

该图为DMA1的请求映像,共有 7 个通道
每个通道都有一个数据选择器可以选择硬件触发或软件触发
在此图中:
侧边 EN 位:决定该数据选择器是否工作,EN = 0 时不工作,EN = 1 时工作
软件触发(MEM2MEM 位):当该值 = 1 时使用软件触发,= 0 时使用硬件触发。如果使用软件触发则无通道的限制,可以任意选择
外设请求信号:按照硬件源选择对应通道。如需要用 ADC1ADC1来触发就必须选择通道 1,如果需要定时器 2 的更新事件TIM2_UP来触发就必须选择通道 2
同一通道多个触发源选择哪一个?:根据对应的外设是否开启 DMA 输出决定。如使用ADC1,则会有库函数ADC_DMACmd,使用该函数开启ADC1这一路的输出才有效;如选择TIM2_CH3则使用TIM_DMACmd来进行 DMA 输出控制。外设请求信号经过一个或门进入数据选择器,所以按理说全部开启都可以进行触发
仲裁器:类似于中断的优先级。通道号越小,优先级越高。也可在程序中配置优先级
*简单来讲:
小转大:高位补 0
大转小:舍弃高位
手册如下:

const类型常量会被编译器划分到Flash中,如果是比较大的查找表或字库尽量加const节省SRAM空间
如果使用硬件触发,则需要在对应的硬件调用xxx_DMACmd开启触发信号的输出
如果需要 DMA 中断,则需要调用DMA_ITConfig开启中断输出,再在 NVIC 配置相应的中断通道,然后写中断函数即可
初始化 DMA,并将 DataA 数据转运到 DataB 中:
DMA_Init配置参数使用 DMA 与 ADC 扫描模式相互配合实现模拟量自动采集与转运:
通信的目的:将一个设备的数据传送到另一个设备,扩展硬件系统
通信协议:制定通信的规则,通信双方按照协议规则进行数据收发

名称:
USART:
I2C:
SPI:
CAN:
USB:
双工:
单工:数据只能从一个设备到另一个设备,不能反向转移
时钟:
电平:
设备:
串口是一种应用十分广泛的通信接口,串口成本低、容易使用、通信线路简单,可实现两个设备的互相通信
单片机的串口可以使单片机与单片机、单片机与电脑(如串口通信:可以用来调试程序,打印信息)、单片机与各式各样的模块相互通信,极大的扩展了单片机的应用范围,增强了单片机的系统的硬件实力

TX与RX要交叉连接。A 发送到 B 接收,A 接收到 B 发送
当电平标准不一致时,需要加电平转换芯片。一般直接从控制器里出来的信号都是 TTL 电平(晶体管-晶体管逻辑电平),相同的电平才能相互通信
电平标准是数据 1 和数据 0 的表达方式,是传输线缆中人为规定的电压与数据的对应关系,串口常用的电平标准有如下三种:


USART(Universal Synchronous/Asynchronous Receiver/Transmitter)通用同步(STM32 的该模式只是多了个时钟输出功能,但不支持时钟输入。仅为兼容别的协议或者特殊用途而设计)/异步收发器
USART 是 STM32 内部集成的硬件外设,可根据数据寄存器的一个字节数据自动生成数据帧时序,从 TX 引脚发送出去,也可自动接收 RX 引脚的数据帧时序,拼接为一个字节数据,存放在数据寄存器里
自带波特率发生器(常用9600 / 115200),最高达 4.5Mbits/s
可配置数据位长度(8(常用) / 9)、停止位长度(0.5 / 1(常用) / 1.5 / 2)
可选校验位(无校验(常用) / 奇校验 / 偶校验)
支持同步模式(多了个时钟 CLK 输出)、硬件流控制(告诉发送端是否准备好接收数据,防止因发送过快而导致数据被覆盖)、DMA(UASRT 支持 DMA 进行数据转运)、智能卡(用于 NFC)、IrDA(用于红外通信,不是红外遥控器的通信)、LIN(局域网通信协议)
STM32F103C8T6 的 USART 资源:USART1(APB2)、USART2(APB1)、USART3(APB1)

因为将寄存器的位全都画出来了所以看着复杂(
左上角:
TX、RX:发送和接收引脚。RX 从发送移位寄存器输出,TX 输入到接收移位寄存器
SW_RX、IRDA_OUT/IN:智能卡和 IrDA 通信的引脚
右上角:
串口的数据寄存器,发送或接收的字节数据就存储在此
发送数据寄存器(TDR)、接收数据寄存器(RDR):(Transmit DR、Receive DR)。硬件中含有两个寄存器但是在软件中二者共占同一个地址,程序上都用 DR 来表示。TDR 只写、RDR 只读,当执行 DR写操作时,数据写入TDR;当执行 DR读操作时,数据写入RDR
发送移位寄存器:把一个字节的数据一位一位的移出去,对应串口协议的数据位。当发送数据寄存器(TDR)写入一个数据,硬件就会检测到数据被写入,并检查当前移位寄存器是否有数据正在移位。如果没有,就立即将 TDR 数据移入发送移位寄存器中。当数据从 TDR 移动到发送移位寄存器时,会置一个标志位 TXE(TX Empty)发送寄存器空,如果该标志位置 1,就可以在 TDR 写入下一个数据了(当 TXE 置 1 时,仅是数据从 TDR 中被转移,但还未发送出去)。发送控制器就会驱动发送移位寄存器向右将数据一位一位输出至 TX 引脚
接收移位寄存器:与发送移位寄存器类似,数据从 RX 引脚通向接收移位寄存器,在接收控制器的驱动下,一位一位的读取 RX 电平到高位,再进行右移。当接收一个字节完成后,会将数据立即转移到 RDR 中,并置 RXNE 标志位(RX Not Empty)接收数据寄存器非空。当检测到 RXNE 置 1 后,就可以读取数据了
发送控制器:用来控制发送移位寄存器的工作
接收控制器:用来控制接收移位寄存器的工作
左边:
n的意思是低电平有效右边:
中间:
唤醒单元:进行一个寻址的过程,用于实现与多设备通信的功能
USART 中断控制:中断申请位就是 SR 状态寄存器中的各种标志位,其中TXE(发送寄存器空)、RXNE(接收寄存器非空)比较重要
下面:
波特率发生器:也就是将 72MHz 分频的分频器。时钟输入是,之后该时钟进行分频,除以USARTDIV的分频系数(参考 USART_BRR,分为整数部分(DIV_Mantissa)和小数部分(DIV_Fraction)),最后除以 16,从而得到发送器时钟和接收器时钟,通向控制部分。
USART_BRR:如果 TE(TX Enable)为 1,则发送器使能,发送部分的波特率有效。如果 RE(RX Enable)为 1,则接收器使能,接收部分的波特率有效。


这里提供了 4 种模式:8 / 9 位字长,带 / 不带校验位
一般设置为:8 位不带校验位、9 位带校验位
下面的空闲帧和断开帧是局域网协议使用的
停止位分为 0.5、1、1.5、2,对应为一个数据位乘该系数的长度

当输入电路侦测到第一个数据帧的起始位后,就会以波特率的频率,连续采样一帧数据
同时,从起始位开始,采样位置就要对齐到位的正中间(只要第一位对齐,后面肯定都是齐的)
采集时钟会以波特率的 16 倍进行采样(在一位的时间长度中可以进行 16 次采样)
检测到下降沿后就会开始以下检测步骤:
判断条件要求每 3 位中至少应该有 2 个 0,如果没有噪声则全是 0,满足情况。如果有轻微的噪声,导致 3 位中只有 2 个 0,另一个是 1,也算检测到起始位,但是会在状态寄存器中置 NE(Noise Error)噪声标志位
如果 3 位里只有 1 个 0 或没有,那么前面检测的下降沿可能是噪声导致,电路就会忽略前面的数据,重新开始捕捉下降沿
如果通过了起始位侦测,那么接收状态将由空闲变为接收起始位。同时第 8、9、10 次采样的位置就正好是起始位的正中间,之后接收数据位时也都在第 8、9、10 位进行采样

1 ~ 16 是一个数据位的时间长度,在一个数据为总共有 16 个采样时钟
由于起始位已经对齐了采样时钟,这里就直接在 8、9、10 次采样数据位。为了保证数据的可靠性,这里是连续采样 3 次。此处检测噪声规则与前面起始位噪声检测一致为 2:1 且如果有噪声会置 NE 标志位

发送器和接收器的波特率由波特率寄存器 BRR 里的 DIV 决定
计算公式:
例:要配置 USART 波特率为 9600。,则 。将十进制数转为二进制得 ,则写入寄存器中:,
使用串口发送数据到电脑:
定义包头、包尾,将数据包裹起来
数据包一般分为:固定包长,含包头包尾、可变包长,含包头包尾
能够记住不同状态的机制,在不同状态执行不同的操作,同时还要进行状态的合理转移
其中每个阶段使用一个变量去表示
固定包长:

可变包长:

I2C 总线(Inter IC BUS)是一种通用数据总线
两根通信线:SCL(Serial Clock)、SDA(Serial Data)
同步,半双工
带数据应答
支持总线挂载多设备(一主多从、多主多从)

I2C 经典电路模型
这是一个一主多从的模型,左边 CPU 作为总线的主机。
CPU:主机的权力很大,包括:对 SCL 线的完全控制、在空闲状态下主机可以主动对 SDA 的控制,只有在从机发送数据和从机应答的时候 SDA 控制权才会转交给从机
被控 IC:挂载在 I2C 总线上的从机,可以是任意的使用 I2C 通信协议的传感器、OLED、存储器、时钟模块等。从机的权利比较小,对于 SCL 时钟线,在任何时候都只能被动的读取,不允许控制 SCL 线。对于 SDA 数据线,从机不允许主动发起对 SDA 的控制,只有主机发送读取从机的命令或者从机应答的时候,从机才能短暂的取得 SDA 的控制权
为了避免因总线没协调好导致电源短路的问题,I2C 协议规定禁止所有设备输出强上拉的高电平,采用外置弱上拉电阻加开漏输出的电路结构
设备的 SCL 和 SDA 均要配置成开漏输出模式
SCL 和 SDA 各添加一个上拉电阻,阻值一般为 4.7KΩ 左右

内部电路
左边为 SCL(SCLK 就是 SCL)
右边为 SDA(DATA 就是 SDA)
输入:信号从 SCLK 引脚进入,都可以通过一个数据缓冲器或者是施密特触发器进行输入(SCLK IN)。因为输入对电路没有任何影响,任何设备在任何时刻都是可以输入的
输出:因为采用的是开漏输出配置,所以只有强下拉(低电平)、浮空(高电平)。这样所有的设备都只能输出低电平而不能输出高电平。为了避免浮空产生的不稳定性,就需要在总线外 SCL 和 SDA 各外置一个上拉电阻,从而弱上拉到高电平
线与(与门)的现象。只要有任意一个或多个设备输出了低电平,总线就处于低电平,只有所有设备都输出高电平,总线才会处于高电平。I2C 可以利用这个特性执行多主机模式下的时钟同步和总线仲裁起始条件和终止条件都是由主机产生的,从机必须保持浮空,否则就是多主机模型
起始条件:SCL 高电平期间,SDA 从高电平切换到低电平

终止条件:SCL 高电平期间,SDA 从低电平切换到高电平

发送一个字节:SCL 低电平期间,主机将数据位(0 / 1)依次放到 SDA 线上(高位先行),然后释放 SCL,从机将 SCL 高电平期间读取数据为,所以 SCL 高电平期间 SDA 不允许有数据变化,依次循环上述过程 8 次,即可发送一个字节

接收一个字节:SCL 低电平期间,从机将数据位(0 / 1)依次放到 SDA 线上(高位先行),然后释放 SCL,主机将 SCL 高电平期间读取数据为,所以 SCL 高电平期间 SDA 不允许有数据变化,依次循环上述过程 8 次,即可接收一个字节(主机在接收之前,需要释放 SDA。相当于切换成输入模式)

接收应答:主机在发送完一个字节之后,在下一个时钟接收一位数据,判断从机是否应答,数据 0 表示应答,数据 1 表示非应答(主机在接收之前,需要释放 SDA)。
每发送一个字节就要紧跟着调用接收应答的时序,用来判断从机有没有收到刚才给它的数据。如果从机收到:在应答位这里,主机释放 SDA 的时候,从机就应该立刻把 SDA 拉下来,然后在 SCL 高电平期间,主机读取应答位。如果应答位为 0,就说明从机确实收到了

发送应答:主机在接受完一个字节之后,再下一个时钟发送一位数据,数据 0 表示应答,数据 1 表示非应答。也就是告诉从机,收到数据可以继续发送。如果主机没应答,从机就会释放 SDA,将 SDA 控制权交回主机

两者时序分别和发送一个字节、接收一个字节的其中一位相同,可理解成发送一位和接收一位
指定地址写:通过从机地址(Slave Address)来确定指定设备,并且对于该指定的设备在其内部的寄存器指定地址(Reg Address)下,写入指定的数据(Data)
指定设备:
起始位:SDA 拉低,SCL 高电平,实现 SCL 高电平期间 SDA 下降沿 数据位:写入 7 位从机地址 + 1 位读写位(0 为写,1 为读) 应答位:主机释放 SDA(低变高电平)的瞬间被从机拉低(产生应答),主机在 SCL 高电平读取 SDA,如果低电平则说明从机接收到数据

指定地址:
主机以高位优先写入数据0x19,在 MPU6050 处为指定寄存器的地址

指定数据
主机以高位优先写入数据0xAA,在上一发送的字节指定的寄存器中写入指定的值 0xAA
停止位:主机先拉低 SDA,然后释放 SCL,在 SCL 高电平期间 SDA 的上升沿

当前地址读:通过从机地址(Slave Address)来确定指定设备,并且对于该指定的设备在当前地址指针指示的地址下,读取从机的数据(Data)

指定设备
与当前地址写的指定设备一致,都是决定从机。不同的是读写位此次置 1(表示接下来执行读操作)
当前地址指针
从机中,所有的寄存器被分配到了一个线性区域中,且拥有以后个独立的指针变量指示着其中的一个寄存器(该指针上电一般默认指向 0 地址),且每写入和读出一个字节后都会自动自增一次,移动到下一个位置。当主机执行当前地址读的操作时,从机就会将该指针指向的寄存器中的值返回发送给主机。
假设在指定地址写的时序中在 0x19 的位置写入了 0xAA,则这个指针就会从指向0x19 + 1的位置——0x1A。当再次调用当前地址读的时序,返回的就是0x1A地址下的值,再调用一次就是0x1B的值
*由于当前地址读并不能指定读的地址,所以该时序用的并不多
指定地址读:通过从机地址(Slave Address)来确定指定设备,并且对于该指定的设备在其内部的寄存器指定地址(Reg Address)下,读取指定的数据(Data)
其实就是用指定地址写来将指针指向到要读寄存器地址,然后另起一个时序再使用当前地址读的时序去读指定地址
一般也将指定地址读称作复合格式(官方规定该格式是一整个数据帧,也就是:起始 -> 重复起始 -> 停止)
如图,左边时序为指定地址写的指定设备、指定地址。右边时序为当前地址读

从指定地址写到当前地址读的切换时序图

前面指定地址写确定了后面要读的地址为 0x19,但不进行数据写入,因此不会触发指针自增。而是再来个起始条件 Sr(Start Repeat)也就是重复起始条件,相当于另起一个时序
新的起始条件后重新寻址找到从机并且置读写位为读,接着主机接收一个字节,该值就是 0x19 地址下的数据
发送/接收多个字节:在指定地址后可以重复发送多个字节或读取多次值,且值会分别写入/读取不同寄存器中 (因为读写寄存器后会自增地址)
只读一个/读到最后一个字节:如果只想读一个字节或是读取的字节足够了,需要在主机给从机的应答位中发送非应答 1(SA:Send ACK)也就是该主机应答时不去拉低 SDA,从机读到 SCL 高电平时 SDA 也为高电平,则表示主机没有应答,从机释放总线,交出 SDA 控制权。如果主机应答,则从机会继续发送下一个数据,此时 SDA 会因为被从机控制,无法正常弹回高电平。
四根通信线:
SCK/SCLK/CLK/CK:串行时钟线
MOSI/DO:主机 M 输出 O、从机 S 输入 I
MISO/DI:主机 M 输入 I、从机 S 输出 O
SS/NSS/CS:从机选择。为低电平则选中从机,低电平有效
SPI 相比 I2C,少了应答机制
主机的 MOSI、SCK 均输出,从机的 MISO 均输出,三根线所有 SPI 设备都要连接
主机发出 SS 连接对应从机
主机输出引脚配置为推挽输出,输入引脚配置为浮空或上拉输入
MISO 冲突问题:
SPI 唯一可能冲突的地方让你找着了,但是 SPI 协议有一条规定:
当从机的 SS 引脚为高电平(从机未被选中时),它的 MISO 引脚必须切换为高阻态模式

上升沿:
波特率发生器每产生一次上升沿,移位寄存器就向左移动一位数据
主机数据从 MOSI 输出,从机从 MOSI 接收,并置于从机移位寄存器右边
从机数据类似,由 MISO 进行通信
下降沿:
当波特率产生下降沿时,主机和从机都会进行数据采样输入
主机会采样 MISO 的电平,从机会采样 MOSI 的电平,并分别加入各自移位寄存器的最低位
起始条件:SS 从高电平切换到低电平
终止条件:SS 从低电平切换到高电平
SPI 共有 4 中模式,本质就是针对 SS 在空闲时的电平与时钟上升下降沿采样或发送数据进行选择 (可能是为了适应更多的模块而做出的妥协?为什么是协议妥协芯片,不太明白)
此处仅对模式 1 进行讨论
交换一个字节(模式 1)
CPOL=0 (时钟极性):空闲状态时,SCK 为低电平
CPHA=1 (时钟相位):SCK 第一个边沿移出数据,第二个边沿移入数据

如果一个时序完成,但主机还想在同一个从机上发送或接收数据,则不用置 SS 高电平,继续发送时序即可
与 I2C 时序不同之处:
I2C 规定第一个有效数据流 (大概是已经确认从机地址后的数据流) 对应字节是寄存器地址,之后才是读写的数据。这样的模型也被称为读写寄存器模型
而SPI通常采用指令码加读写数据的模型