STM32 学习笔记

By TSBread

感谢

GPIO 输出

GPIO 简介

GPIO(General Purpose Input Output)通用输入输出口

引脚电平:0~3.3v,部分引脚(带 FT 的(Five Tolerate))可容忍输入5v

*可配置为 8 中模式:

GPIO 基本结构

APB2:APB2 总线,所有 GPIO 都挂载在 APB2 总线上。其中 GPIO 外设名称按照 GPIOA、GPIOB、GPIOC 来命名。每个 GPIO 外设共有 16 个引脚,从 0~15 编号,如 GPIOA的第0号引脚一般称为 PA0

GPIO 模块:每个 GPIO 模块内包含了寄存器驱动器

GPIO 位结构

从左至右大致分为:寄存器、驱动器、IO 引脚

从上至下大致分为:输入、输出

输入部分:

I/O 引脚:使用两个保护二极管对输入电压进行钳位,上接 VDD=3.3vVDD = 3.3v,下接 VSS=0vVSS = 0v。若输入电压 > 3.3v 则上方二极管导通,从而将电流流入 VDD 而不是内部电路,若输入电压 < 0v 则下方二极管导通,从而将电流流入 VSS

上/下拉电阻选择:该开关可通过程序进行配置,上导通、下断开则为上拉输入模式;下导通、上断开则为下拉输入模式;上下都断开则为浮空输入模式

施密特触发器(文档误译为肖特基触发器):对输入电压进行整形,如果输入电压大于某一阈值,输出就会瞬间升为高电平;如果输入电压小于某一阈值,输出就会瞬间降为低电平。输出虽然是数字信号但由于干扰可能会产生各种失真,使用施密特触发器用来防止误判

输入数据寄存器:将通过施密特触发器整形后的波形写入,用程序读取数据寄存器对应的某一位数据便可得知端口的输入电平

至片上外设


输出部分:

输出可由输出数据寄存器片上外设来进行控制,经过数据选择器到输出控制部分

输出数据寄存器:普通的 IO 口输出,每一位对应着相应的端口。该寄存器同时控制 16 个端口,且只能进行整体的读写(也可以先读取该寄存器,然后使用&=和|=的防止进行更改数据并写回该寄存器以达到控制单独一位的写入)

位设置/清除寄存器:可以用来单独操作输出数据寄存器的某一位,而不影响其他位。如果要将某一位置 1 则只需将位设置寄存器中对应位写 1 其他写 0 即可,若要置 0 则同理操作位清除寄存器即可

输出驱动器部分:上为 P-MOS,下为 N-MOS。在此可选择三种输出方式:

*注意: 一个端口只能有一个输出,但可以有多个输入(输入模式下输出均无效,但输出模式下仍然可以进行输入)

实操

根据上述所得,操作 STM32 的 GPIO 总共需要 3 个步骤:

  1. 使用 RCC(复位和时钟控制)开启 GPIO 的时钟

  2. 使用GPIO_Init函数初始化 GPIO

  3. 使用输出或输入的函数控制 GPIO

EXTI 外部中断

中断系统

中断:在主程序运行过程中,出现了特定的中后段触发条件(中断源),使得 CPU 暂停当前正在运行的程序,转而去处理中断程序,处理完成后又返回原来被暂停的位置继续运行。使用中断可大大提高程序运行效率 (如串口数据发送,避免了为了防止数据被覆盖从而使得主程序不断查询是否有串口数据发送的事件被触发导致无法继续运行;若没有定时器中断那主程序只能依靠堵塞的 Delay 来实现定时的功能)

中断优先级:功能类似裁决器,当多个中断源同时申请中断时,CPU 根据中断源的轻重缓急进行裁决,优先相应更加紧急的中断源

中断嵌套:当一个中断程序正在运行时,又有新的更高优先级的中断源申请中断,CPU 将会中断当前的中断程序转而去处理新的更高级别的中断程序,处理完后再依次返回

STM32 中断

中断向量表

STM32 中断通道都包括:EXTI(外部中断)、TIM(定时器)、ADC(模数转化器)、USART(串口)、SPI、I2C、RTC(实体时钟)等多个外设

使用NVIC统一管理中断:每个中断都拥有 16 个可编程的右胸按等级,可对优先级进行分组,进一步设置抢占优先级响应优先级NVIC是 STM32 用来管理中断、分配优先级的

本节中(EXTI 外部中断)仅讨论上图中的中断资源为:EXTI0~4、EXTI9_5、EXTI15_10

中断地址:因为程序中的中断函数的地址是由编译器分配的,并非固定的。但中断跳转由于硬件限制只能跳转到固定的地址执行程序,所以将跳转到中断函数的代码写入固定的内存地址,从而当中断发生后跳转入固定的中断地址后再跳转进对应的中断函数处

NVIC 基本结构

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

NVIC 优先级分组

NVIC 的中断优先级由优先级寄存器的 4 位(0~15)决定,这 4 位可以进行切分,分为高 n 位的抢占优先级和低 4-n 位的响应优先级。

响应优先级和抢占优先级均相同的中断按照中断向量表的**中断号(优先级)**进行排序

响应优先级:当前一个中断处理完后,优先处理响应优先级高的中断

抢占优先级:即使前一个中断未处理完,也要优先处理抢占优先级高的中断直到该中断处理完

EXTI 简介

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 等))

EXTI 基本结构

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_5EXTI15_10。也就是说被包含的外部中断被触发时都会触发同一个中断函数,需要再在该中断函数中通过标志位来区分具体是哪个通道触发的中断

EXTI -> 其他外设:用来触发其他外设操作的,也就是上小节(#EXTI 简介)提到的事件响应

AFIO 复用 IO 口

AFIO 主要用于引脚复用功能的选择和重定义(数据选择器的作用)

在 STM32 中 AFIO 起到了两个作用:复用引脚重映射(也就是引脚映射表中的复用功能转换为重定义功能)、中断引脚选择

使用数据选择器在同号的 Pin 中选择其中一个的 GPIO 并传给 EXTI,因此不能选择同号 Pin 触发外部中断

EXTI 框图

*以一路通道为例:

AFIO -> 输入线 -> 边沿检测:根据检测结果与触发方式决定是触发上升沿还是下降沿还是二者都触发

边沿检测 -> 或门:硬件触发与软件中断寄存器接到同一个或门上,若任意一值为 1 则输出 1。此处软件中断寄存器为软件触发位置

或门 ->:上路触发中断,下路触发事件。

实操

根据上述所得,使用外设作为 EXTI 外部中断并执行程序总共需要 5 个步骤:

  1. 配置 RCC,将设计的外设时钟打开
  2. 配置 GPIO,选择端口为输入模式
  3. 配置 AFIO,选择对应 GPIO 和 Pin,连接到 EXTI
  4. 配置 EXTI,选择触发模式(此处是使用外设信号作为触发源则使用边沿触发方式)、触发响应方式(此处使用中断响应,因为是要执行程序)
  5. 配置 NVIC,给该中断选择一个适合的优先级

TIM 定时中断

TIM 简介

TIM(Timer)定时器

最基本的功能:定时器可以对输入的时钟(可靠的基准时钟,STM32 中使用主频 72MHz 的时钟)进行计数(也就是个计数器),并在计数值达到设定值时触发中断。如对 72MHz 计 72 个数也就是 1MHz=1us1MHz = 1us;计 72000 个数也就是 1KHz=1ms1KHz = 1ms

STM32 的定时器拥有:16 位计数器(用来执行计数定时的一个寄存器,每来一个时钟,计数器 + 1)、预分频器(对计数器的时钟进行分频,使得计数更加灵活)、自动重装寄存器(计数的目标值,要在多少个时钟时申请中断),这部分电路被称为时基单元,在 72MHz 计数时钟下可以实现最大 59.65s 的定时。

因以上三种寄存器均为 16 位,216=655362 ^ 16 = 65536,若预分频器和自动重装都设定到最大,那定时器的最大定时时间就是 59.65s(72M/65536/65536=中断频率72M / 65536 / 65536 = 中断频率,取1/中断频率=59.651 / 中断频率 = 59.65

STM32 的定时器支持级联模式(用一个定时器的输出当作另一个定时器的输入),若两个定时器级联,最大定时时间就是59.65s655366553659.65s * 65536 * 65536

STM32 的定时器不仅具备基本的定时中断功能,还包括内外时钟源选择输入捕获输出比较编码器接口主从触发模式等多种功能

定时器类型

计数器模式

基本定时器

从下往上看:预分频器计数器自动重装寄存器三者构成了最基本的计数计时电路,也被称为时基单元

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 位寄存器。当计数器值到达目标值时产生中断信号并清零计数器。

通用定时器

中间为时基单元,结构与基础定时器相同

上面为内外时钟源选择主从触发模式的结构

右下角为输出比较(OC)电路,总共 4 个通道(CH1~4),用于输出 PWM 波形,驱动电机

左下角为输入捕获(IC)电路,总共 4 个通道(CH1~4),用于测量方波的频率等

下面中间为捕获/比较寄存器,是输入捕获和输出比较电路共用的。因为输出比较和输入捕获不能同时使用,所以该寄存器是共用的,包括引脚

高级定时器

相比通用定时器而言,主要改动的是右下和左下部分

重复次数计数器:在申请中断的地方新增,可以实现每隔几个计数周期才会发生一次更新事件和更新中断。相当于对原本的中断信号再进行一次分频

输出比较模块

定时中断基本结构

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

预分频器时序

CK_PSC:预分频器的输入时钟,内部时钟为 72MHz

CNT_EN:计数器使能,高电平计数器运行,低电平计数器停止

CK_CNT:计数器时钟,既是预分频器的输出也是计数器的输入。计数器未使能时计数器时钟不运行,使能后前半段等于预分频器前的时钟,后半段二分频变为前时钟的一半

计数器寄存器:跟随时钟的上升沿不断自增,在 FC 后变为 0,由此可退出 ARR 自动重装值为 FC

更新事件(UEV):当达到重装值后产生一个脉冲

预分频缓冲器:为预分频控制寄存器的影子寄存器,用户读写预分频控制寄存器,但实际控制分频的为该寄存器。(结构图中带黑色阴影的寄存器都含有一个影子寄存器)

*计数器计数频率:CKCNT=CKPSC/(PSC+1)CK_CNT = CK_PSC / (PSC + 1)

计数器时序

CK_INT:内部时钟 72MHZ

CNT_EN:时钟使能,高电平开启

CK_CNT:计数器时钟,因为分频系数为 2 所以该频率为CK_INT / 2

计数器寄存器:在每个CK_CNT的上升沿自增。

计数器溢出:当计数到 0036 时溢出,到下一个上升沿清零

更新事件(UEV):当计数器溢出时产生一个脉冲

更新中断标志(UIF):当计数器溢出时置 1,只要为 1 就会申请中断,中断响应后需要在中断程序中手动清零,否则会一直申请中断

*计数器溢出频率:CKCNTOV=CKCNT/(ARR+1)=CKPSC/(PSC+1)/(ARR+1)CK_CNT_OV = CK_CNT / (ARR + 1) = CK_PSC / (PSC + 1) / (ARR + 1)

计数器无预装时序

*在此之前上面两个都是含有缓冲寄存器(影子寄存器)的情况,该时序为没有缓冲寄存器的时序情况

在计数的中途将自动加载寄存器的值从 FF 改为 36,计数器寄存器的值将直接在到达 36 时产生更新事件

计数器有预装时序

在计数的中途将自动加载寄存器的值从 F5 改为 36,因真正起作用的是影子寄存器,所以当前计数目标仍旧是 F5,直到达到 F5 产生更新事件才会将自动重装值改为 36

影子寄存器的作用就是为了让值的变化和更新事件保持同步,防止在运行途中更改从而造成错误

实操

根据上述所得,使用内部时钟进行定时中断总共需要 6 个步骤:

  1. RCC 开启时钟
  2. 选择时基单元的时钟源(此处选择内部时钟模式)
  3. 配置时基单元
  4. 配置输出中断,允许更新中断输出到 NVIC
  5. 配置 NVIC,在 NVIC 中打开定时器中断的通道,并分配一个优先级
  6. 运行控制,使能计数器

TIM 输出比较(PWM)

简介

OC(Output Compare)输出比较(其他缩写:IC(Input Capture)输入捕获、CC(Capture/Compare)输入捕获和输出比较单元)

输出比较可以通过比较 CNT 计数器与 CCR (捕获/比较寄存器) 寄存器值的关系,来对属于出电平进行置 1、置 0、翻转的操作,用于输出一定频率和占空比的 PWM 波形

每个通用和高级定时器都拥有 4 个输出比较通道。每个通道拥有独立的捕获/比较寄存器,但由于每个定时器只有一个 CNT 计数器,所以同一定时器不同通道的占空比可以不同,但频率是相同的

高级定时器前 3 个通道额外拥有死区生成和互补输出的功能

PWM 简介

PWM(Pulse Width Modulation)脉冲宽度调制

对于具有惯性的系统中,可以通过对一系列脉冲的宽度调制来等效的获得所需要的模拟参量,例如电机、LED 等

如上图所示,高低电平跳变的数字信号可以等效为蓝色虚线表示的模拟量。

上面电平时间 > 下面电平时间:等效的模拟量偏向于上面

上面电平时间 < 下面电平时间:等效的模拟量偏向于下面

参数:频率=1/Ts频率 = 1 / Ts占空比=Ton/Ts占空比 = Ton / Ts分辨率=占空比变化步距分辨率 = 占空比变化步距

频率:频率越快,等效模拟的信号就越平稳,但性能开销就越大(一般 PWM 频率都在 x Khz ~ x0 KHz)

占空比:占空比决定了 PWM 等效出来的模拟电压的大小(一般来说是线性关系的)

分辨率:如果占空比只能是 1%、2%、3%以 1%进行步距跳变则分辨为 1%,如果可以是 1.1%、1.2%、1.3%以 0.1%的步距跳变则分辨率为 0.1%。分辨率也就是占空比变化的精细程度,需要多高具体看项目需求。越是高频率与高分辨率则对硬件电路的要求就越高

通用输出比较通道

CNT>CCR1CNT > CCR1 / CNT=CCR1CNT = CCR1:让通道 1 的 CCR(捕获/比较寄存器)与 CNT 计数器进行比较,当满足以上两种条件中的任意一种时,给输出模式控制器传一个信号。

输出模式控制器:当获得信号后,就会改变它输出oc1ref(ref:参考信号)的高低电平。该控制器输入 CNT 和 CCR 的大小关系,输出 REF 的高低电平,可通过 OC1M 寄存器[2:0]部分配置更多模式,模式见下图 👇

至主模式控制器(信号向上):可以将oc1ref信号映射到主模式的TRGO输出上

极性选择(信号向右):选择是否要将输入的高低电平进行反转。给 CC1P 寄存器写 0,信号就会从上面(0 处)走信号电平不反转。写 1 则通过一个非门从下面(1 处)走,信号电平反转

输出使能电路:选择是否输出。通过置 CC1E 寄存器 1 或 0 实现

OC1:也就是 CH1 通道的引脚,可在引脚定义表中查询具体哪个 GPIO 口

PWM 基本结构

左上角为:时基单元和运行控制部分,时基单元左边为时钟源选择,详细在上一小节

CNT计数器值与CCR寄存器值进行比较 -> 输出模式控制器选择PWM1模式 -> 将REF输出到极性选择 -> 最后输出给GPIO

PWM1 模式逻辑参考右上图

参数计算

PWM 频率Freq=CKPSC/(PSC+1)/(ARR+1)Freq = CK_PSC / (PSC + 1) / (ARR + 1)

PWM 占空比Duty=CCR/(ARR+1)Duty = CCR / (ARR + 1)

PWM 分辨率Reso=1/(ARR+1)Reso = 1 / (ARR + 1)

随手写了个计算程序

实操

根据上述所得,使用 PWM 输出总共需要 5 个步骤:

  1. RCC 开启 TIM 外设和 GPIO 外设时钟
  2. 配置时基单元,包括前面的时钟源选择
  3. 配置输出比较单元,包括 CCR 值、输出比较模式、极性选择、输出使能
  4. 配置 GPIO,把 PWM 对应的 GPIO 口初始化为复用推挽输出的配置
  5. 运行控制,使能计数器

TIM 输入捕获

简介

IC(Input Capture)输入捕获

输入捕获模式下,当通道输入引脚出现指定电平跳变(上升沿或下降沿等,可通过程序配置)时,当前 CNT 的值将被所存到 CCR 中(读取 CNT 计数器值到 CCR 锁存器中),可用于测量 PWM 波形的频率、占空比、脉冲间隔、电平持续时间等参数

通用和高级定时器每个都拥有 4 个输入捕获的通道

可配置为PWMI 模式,同时测量频率和占空比

可配合主从触发模式,实现硬件全自动测量

频率测量

输入捕获模块结构

TIMx_CH1~4:OC 引脚,具体参考引脚定义表

输入滤波器和边沿检测器:滤波器对输入信号进行滤波,避免高频的毛刺信号导致误触发。边沿检测器与外部中断处类似,当出现指定的电平时就会触发后续电路。

此处每通道都有两路滤波器和检测器,所以分别输出两路经过处理的信号(TIxFP1TIxFP2(TIx Filter Polarity x))。以CH1CH2为例:其中 CH1 的TI1FP1输出给 CH1(自己)的后续电路;TI1FP2则输出给 CH2 的后续电路;而 CH2 的TI2FP1输出给 CH1 的后续电路;TI2FP2则输出给 CH2(自己)的后续电路。用途:

  1. 可以灵活切换后续捕获电路的输入,可以在 CH1 和 CH2 之间随意切换输入
  2. 将一个引脚的输入同时映射到两个捕获单元中(PWMI 模式的结构:CH1 对上升沿进行捕获(捕获周期),CH2 对下降沿进行捕获(捕获占空比))

预分频器:对前面输入的信号进行分频,然后输出信号触发捕获电路工作。捕获电路每触发一次,CNT 计数器的值就会向 CCR 寄存器转运一次,同时发生一个捕获事件(CCxI),该事件会在状态寄存器置标志位,同时也可产生中断(如果需要在捕获的瞬间处理一些事情,则可以开启捕获中断)

单个输入捕获通道结构

TI1:引脚输入信号

fDTS:滤波器的采样时钟来源

TIMx_CCMR1:寄存器的 ICF 位可以控制滤波器的参数,原理与其他滤波器相同

TI1F:经过滤波后的引脚输入信号

TIMx_CCER:使用CC1P位对边沿检测器控制数据选择器,选择TI1FP1输出触发上升沿还是下降沿信号,该信号通过数据选择器进入 CH1 后续捕获电路

TIMx_CCMR1CC1S位控制数据选择器对TI1FP1TI2FP1TRC数据进行选择(IC1)后进入分频器。ICPS位配置分频器,选择不分频、2 分频、4 分频、8 分频。

TIMx_CCERCC1E位控制输出使能或失能

至从模式控制器:可以在捕获之后自动完成 CNT 的清零工作

主从触发模式

主从触发模式 = 主模式 + 从模式 + 触发源选择

主模式:可以将定时器内部信号映射到 TRGO 引脚,用于触发其他外设

从模式:可以接收其他外设或自身外设的一些信号(TRGI),用于控制自身定时器的运行,被别的信号控制。可在列表中选择一项操作来自动执行

触发源选择:选择指定的一个信号,得到 TRGI。选择从模式的触发信号源,可认为是从模式的一部分

例 1:如想让 TI1FP1 信号自动触发 CNT 清零,在触发源选择处选择TI1FP1从模式执行Reset操作

例 2:想实现定时器的级联,可以选择一个定时器主模式输出更新模式信号到 TRGO,另一个定时器选择上一个定时器(ITRx)触发从模式,从模式选择执行外部时钟模式 1的操作,从而实现

主模式功能见下图 👇

触发选择功能见下图 👇

从模式功能见下图 👇

输入捕获基本结构

PWMI 基本结构

实操

根据上述所得,使用输入捕获测频率总共需要 7 个步骤:

  1. RCC 开启 GPIO 和 TIM 的时钟
  2. GPIO 初始化,配置为输入模式(一般选择上拉输入/浮空输入模式)
  3. 配置时基单元,让 CNT 计数器在内部时钟驱动下自增运行
  4. 配置输入捕获单元,包括滤波器、极性、直连或交叉通道、分频器等参数
  5. 选择从模式的触发源TI1FP1
  6. 选择触发之后的执行的操作,执行Reset操作
  7. 使能计时器。当我们需要读取最新的一个周期的频率时,直接读取 CCR1 的值,按照 fc(设 1Mhz) / 该值 即可

TIM 编码器接口

简介

Encoder Interface 编码器接口

编码器接口可接收增量(正交)编码器的信号,根据编码器旋转产生的正交信号脉冲,自动控制 CNT 自增或自减,从而指示编码器的位置、旋转方向和旋转角度

每个通用定时器和高级定时器都拥有 1 个编码器接口。如果一个定时器被配置为编码器接口模式,则基本无法作为他用(两个输入引脚借用了输入捕获的 CH1 和 CH2)。F103C8T6 只有 4 个定时器,最多接 4 个编码器

正交编码器

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

编码器接口基本结构

在该模式下,编码器接口的输出部分相当于从模式控制器,用来控制 CNT 的计数时钟和计数方向。此处不会使用 72MHz 的内部时钟与时基单元初始化

此处 ARR 也有效,一般设置为 65535(最大量程),利用补码的特性,很容易得到负数(uint16 转为 int16

工作模式

ADC 模数转换器

简介

ADC(Analog - Digital Converter)模拟-数字转换器

ADC 可以将引脚上连续变化的模拟电压转化为内存中存储的数字变量,建立模拟电路到数字电路的桥梁

STM32系列的 ADC 是 12 位(分辨率:0 ~ (2 ^ 12) - 1 = 0 ~ 4095)逐次逼近型(工作模式)ADC,1us 转化时间(转化频率:从 AD 转化开始到产生结果需要花费 1us 事件,对应 AD 转化频率为 1MHz)

STM32F103C8T6 的 ADC 资源:ADC1、ADC2、10 个外部输入通道

逐次逼近型 ADC

该图为 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 的参考电压

STM32 ADC 框图

输入

位于左中部分

模拟多路开关:可以选择多路通道,且在转换的时候被分为规则通道组(可以一次性最多选中 16 个通道。但只有 1 个规则通道数据寄存器,会导致组里后来转化的值覆盖前一次转化的值,一般配合#DMA在转换完成后转运数据)和注入通道组(最多可以一次选中 4 个通道。注入通道数据寄存器有 4 个,每个转化后的结果都不会被组里其他数据覆盖)。右边是多路开关的输出,进入到模数转换器(执行前面提到的逐次比较的过程),转换结构会直接存放在通道数据寄存器中,通过读取寄存器得到 ADC 转换的结果。一般都使用规则组,数据覆盖问题配合使用 DMA 转运转化后的数据

触发转化部分

位于左下部分。相当于独立 ADC IC 的START信号,表示开始进行转化

供电:VDDA 与 VSSA 是 ADC 的供电引脚,且与 VREF 相连

VREF:VREF+与 VREF-决定了 ADC 的参考电压,决定了 ADC 输入电压的范围。实际没有该引脚,在内部已经与供电引脚相连接

ADCCLK:ADC 的时钟,来自 ADC 的预分频器(源于 RCC(拥有 6、8 分频,结果分别为 14MHz、9MHz)),用于驱动内部逐次比较的时钟(也就是独立 ADC IC 的CLOCK输入)。

转换完成信号

位于上方标志位

如果开启 NVIC 对应的通道,则转换完成后的状态寄存器会触发中断

ADC 基本结构图

输入通道

在引脚定义框图中ADC12_INx意思是 ADC1 和 ADC2 的 INx 通道

ADC1 和 ADC2 的输入引脚全都相同,此处用途为 ADC 的高级功能,双 ADC 模式(配合组成同步模式、交叉模式(ADC1 和 ADC2 交叉地对一个通道进行采样,进一步提高采样率)等)

转换模式(规则组为例)

1. 单次转换,非扫描模式

2. 连续转换,非扫描模式

3. 单次转换,扫描模式

4. 连续转换,扫描模式

注意

触发控制

也就是每个组触发控制可以进行选择的部分

EXTI 线 11/TIM8_TRGO 事件:这个信号类型可以是引脚,也可以是定时器,具体需要用 AFIO 重映射来确定

数据对齐

一般使用数据右对齐,这样直接读取数据就是转换结果

左对齐一般用来降低采集精度(真有这种需求吗?)

转换时间

校准

硬件电路

实操

根据上述所得,使用软件触发 ADC 单次非扫描模式测量输入模拟电压总共需要 5 个步骤:

  1. RCC 开启 ADC 和 GPIO 的时钟,注意 ADCCLK 的分频器也得配置
  2. GPIO 配置模拟输入模式
  3. 配置多路开关,将左边通道接入右边规则组列表中
  4. 配置 ADC,使用结构体配置 AD 转换器和 AD 数据寄存器的参数。如果需要配置模拟看门狗则调用对应函数即可配置阈值和监测通道
  5. 开关控制,使用ADC_Cmd函数开启 ADC。根据手册建议可以对 ADC 进行校准,减小误差

在 ADC 工作时,如果想用软件触发转换、读取转换结果,都可以调用对应函数

DMA 直接存储器存取

DMA 简介

提供外设和存储器或者存储器和存储器之间的高速数据传输 ,无需 CPU 干预,节省了 CPU 的资源

外设:指外设的数据寄存器(Data Register),例如 ADC 的 DR、串口的 DR 等

存储器:指运行内存 SRAM 和程序存储器 Flash

STM32F103C8T6 仅含有 DMA1(7 个通道),每个通道都支持软件触发和特定的硬件触发

数据从一个地方转移到另一个地方就需要占用一个通道。多个通道之间可以各转各的,互不干扰

如果是存储器到存储器之间的转运则需要软件触发(如需要把 Flash 里一批数据转运到 SRAM 中)。如果是外设到存储器的转运则因外设数据有一定时机所以需要使用硬件触发(如转运 ADC 的数据,要等 ADC 每个 AD 转换完成后,硬件触发 DMA 再进行 DMA 转运)

特定的指:每个 DMA 通道的硬件触发源不同,外设要使用对应通道 DMA 就需要使用该通道连接的那个通道,不能任意选择通道(在#DMA 请求中有再次说明)

存储器映像

外设和存储器的转运本质上都是存储器和存储器之间的转运,只不过 STM32 特别指定了可以转运外设存储器而已

表中存储器分为两类:ROMRAM

ROM:只读存储器,非易失性、掉电不丢失的存储器

RAM:随机存储器,易失性、掉电丢失的存储器

*可用地址来确定数据存储器类型

选项字节再 ROM 区的最后面,下载程序时可以选择不刷新选项字节的内容,这样选项字节的配置可以保持不变。选项字节主要是 Flash 的读、写保护,看门狗等的配置

参考文档:STM32F103x8B 数据手册(中文)/ #存储器映像

DMA 框图

核心中包含 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 的硬件触发源

DMA 基本结构

方向:控制数据传输方向(外设到存储器/存储器到存储器)

外设&存储器参数

例:(ADC 扫描模式,用 DMA 进行数据转运):外设地址为 ADC_DR 寄存器。外设 DR 地址不用递增,否则将会转移其他寄存器的数据;而存储器的地址需要递增,每转运一个数据后就向后移动,否则就会覆盖上次转运的数据结果

图中外设与存储器仅作为名称,也可称为站点 A、B

传输计数器:用来指定总共需要转运几次。是自减计数器,当减到 0 时停止数据转运,且自增的地址也会恢复到起始地址的位置

自动重装器:决定了转运的模式(单次模式(如转运数组)、循环模式(如 ADC 扫描模式+连续转换))。当传输计数器到 0 时决定是否要恢复到最初的值。若不使用则 DMA 结束,否则立即重装到初始值

DMA 触发控制:决定 DMA 要在什么时机进行转运。可选择硬件触发或软件触发,由M2M(Memory to Memory)参数(0 / 1)决定

开关控制:也就是DMA_Cmd函数,用于给 DMA 使能

以上可得,DMA 的转运条件有:

  1. 开关控制:DMA_Cmd必须使能

  2. 传输计数器:值必须大于 0

  3. 触发源(M2M = 0 / 1):必须要有触发信号

触发一次进行一次转运,传输计数器自减一次。若没有自动重装时,无论是否触发,DMA 都不会再进行转运。若想再次开启则需要先给DMA_Cmd传入DISABLE关闭 DMA,再给传输计数器一个大于 0 的数后再给DMA_Cmd传入ENABLE开启 DMA,即可继续工作

在给传输计数器赋值时必须要先关闭 DMA 再进行(手册规定)

DMA 请求

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

该图为DMA1的请求映像,共有 7 个通道

每个通道都有一个数据选择器可以选择硬件触发软件触发

在此图中

同一通道多个触发源选择哪一个?:根据对应的外设是否开启 DMA 输出决定。如使用ADC1,则会有库函数ADC_DMACmd,使用该函数开启ADC1这一路的输出才有效;如选择TIM2_CH3则使用TIM_DMACmd来进行 DMA 输出控制。外设请求信号经过一个或门进入数据选择器,所以按理说全部开启都可以进行触发

仲裁器:类似于中断的优先级。通道号越小,优先级越高。也可在程序中配置优先级

数据宽度与对齐

*简单来讲

手册如下:

实操

const类型常量会被编译器划分到Flash中,如果是比较大的查找表或字库尽量加const节省SRAM空间

如果使用硬件触发,则需要在对应的硬件调用xxx_DMACmd开启触发信号的输出

如果需要 DMA 中断,则需要调用DMA_ITConfig开启中断输出,再在 NVIC 配置相应的中断通道,然后写中断函数即可

  1. 初始化 DMA,并将 DataA 数据转运到 DataB 中:

    1. RCC 开启 DMA 时钟
    2. 直接调用DMA_Init配置参数
    3. 使能,开启 DMA
  2. 使用 DMA 与 ADC 扫描模式相互配合实现模拟量自动采集与转运:

    1. RCC 开启 ADC、DMA、GPIO 时钟
    2. 配置 GPIO 为模拟输入
    3. 配置 ADC,并调用 ADC_DMACmd 开启 ADC 到 DMA 的通道,且配置为连续扫描模式
    4. 配置 DMA,配置为硬件触发,连续模式
    5. 使能 DMA、ADC
    6. 校准 ADC
    7. 软件开启 ADC,全套电路开始自动工作

USART 串口

通信接口

名称

双工

单工:数据只能从一个设备到另一个设备,不能反向转移

时钟

电平

设备

串口通信

硬件电路

电平标准

电平标准是数据 1 和数据 0 的表达方式,是传输线缆中人为规定的电压与数据的对应关系,串口常用的电平标准有如下三种:

串口参数及时序

USART 简介

USART(Universal Synchronous/Asynchronous Receiver/Transmitter)通用同步(STM32 的该模式只是多了个时钟输出功能,但不支持时钟输入。仅为兼容别的协议或者特殊用途而设计)/异步收发器

USART 是 STM32 内部集成的硬件外设,可根据数据寄存器的一个字节数据自动生成数据帧时序,从 TX 引脚发送出去,也可自动接收 RX 引脚的数据帧时序,拼接为一个字节数据,存放在数据寄存器里

USART 框图

因为将寄存器的位全都画出来了所以看着复杂(

左上角:

右上角:

串口的数据寄存器,发送或接收的字节数据就存储在此

左边:

右边:

中间:

下面:

USART 基本结构

数据帧

这里提供了 4 种模式:8 / 9 位字长,带 / 不带校验位

一般设置为:8 位不带校验位、9 位带校验位

下面的空闲帧和断开帧是局域网协议使用的

停止位分为 0.5、1、1.5、2,对应为一个数据位乘该系数的长度

起始位侦测

当输入电路侦测到第一个数据帧的起始位后,就会以波特率的频率,连续采样一帧数据

同时,从起始位开始,采样位置就要对齐到位的正中间(只要第一位对齐,后面肯定都是齐的)

采集时钟会以波特率的 16 倍进行采样(在一位的时间长度中可以进行 16 次采样)

检测到下降沿后就会开始以下检测步骤:

数据采样

1 ~ 16 是一个数据位的时间长度,在一个数据为总共有 16 个采样时钟

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

波特率发生器

实操

使用串口发送数据到电脑:

  1. RCC 开启 USART 和 GPIO 时钟
  2. GPIO 初始化,TX 配置为复用输出,RX 配置为输入
  3. 配置 USART(如果需要接收的功能还需要配置中断(ITConfig、NVIC))
  4. 开关控制使能 USART

USART 串口数据包

数据包概念

定义包头、包尾,将数据包裹起来

数据包一般分为:固定包长,含包头包尾可变包长,含包头包尾

状态机思想

I2C 通信协议

简介

硬件电路

I2C 经典电路模型

这是一个一主多从的模型,左边 CPU 作为总线的主机。

CPU:主机的权力很大,包括:对 SCL 线的完全控制、在空闲状态下主机可以主动对 SDA 的控制,只有在从机发送数据和从机应答的时候 SDA 控制权才会转交给从机

被控 IC:挂载在 I2C 总线上的从机,可以是任意的使用 I2C 通信协议的传感器、OLED、存储器、时钟模块等。从机的权利比较小,对于 SCL 时钟线,在任何时候都只能被动的读取,不允许控制 SCL 线。对于 SDA 数据线,从机不允许主动发起对 SDA 的控制,只有主机发送读取从机的命令或者从机应答的时候,从机才能短暂的取得 SDA 的控制权

为了避免因总线没协调好导致电源短路的问题,I2C 协议规定禁止所有设备输出强上拉的高电平,采用外置弱上拉电阻加开漏输出的电路结构

内部电路

左边为 SCL(SCLK 就是 SCL)

右边为 SDA(DATA 就是 SDA)

输入:信号从 SCLK 引脚进入,都可以通过一个数据缓冲器或者是施密特触发器进行输入(SCLK IN)。因为输入对电路没有任何影响,任何设备在任何时刻都是可以输入的

输出:因为采用的是开漏输出配置,所以只有强下拉(低电平)、浮空(高电平)。这样所有的设备都只能输出低电平而不能输出高电平。为了避免浮空产生的不稳定性,就需要在总线外 SCL 和 SDA 各外置一个上拉电阻,从而弱上拉到高电平

  1. 完全杜绝了电源短路现象,保证电路安全。电路无论何时都不会处于一个被同时强拉和强推的状态
  2. 避免了引脚模式的频繁切换。开漏 + 弱上拉的模式,同时兼具了输入和输出的功能
  3. 该模式会有一个线与(与门)的现象。只要有任意一个或多个设备输出了低电平,总线就处于低电平,只有所有设备都输出高电平,总线才会处于高电平。I2C 可以利用这个特性执行多主机模式下的时钟同步和总线仲裁

I2C 时序基本单元

起始条件和终止条件都是由主机产生的,从机必须保持浮空,否则就是多主机模型

两者时序分别和发送一个字节、接收一个字节的其中一位相同,可理解成发送一位和接收一位

I2C 时序

发送/接收多个字节:在指定地址后可以重复发送多个字节或读取多次值,且值会分别写入/读取不同寄存器中 (因为读写寄存器后会自增地址)

只读一个/读到最后一个字节:如果只想读一个字节或是读取的字节足够了,需要在主机给从机的应答位中发送非应答 1(SA:Send ACK)也就是该主机应答时不去拉低 SDA,从机读到 SCL 高电平时 SDA 也为高电平,则表示主机没有应答,从机释放总线,交出 SDA 控制权。如果主机应答,则从机会继续发送下一个数据,此时 SDA 会因为被从机控制,无法正常弹回高电平。

SPI 通信协议

SPI 通信

SPI 相比 I2C,少了应答机制

硬件电路

MISO 冲突问题:

SPI 唯一可能冲突的地方让你找着了,但是 SPI 协议有一条规定:

当从机的 SS 引脚为高电平(从机未被选中时),它的 MISO 引脚必须切换为高阻态模式

移位示意图

SPI 时序基本单元

SPI 共有 4 中模式,本质就是针对 SS 在空闲时的电平与时钟上升下降沿采样或发送数据进行选择 (可能是为了适应更多的模块而做出的妥协?为什么是协议妥协芯片,不太明白)

此处仅对模式 1 进行讨论

如果一个时序完成,但主机还想在同一个从机上发送或接收数据,则不用置 SS 高电平,继续发送时序即可

与 I2C 时序不同之处:

I2C 规定第一个有效数据流 (大概是已经确认从机地址后的数据流) 对应字节是寄存器地址,之后才是读写的数据。这样的模型也被称为读写寄存器模型

而SPI通常采用指令码加读写数据的模型