声明该文档没有使用任何AIGC内容,主要参考了STM32F10xxx的参考手册。
硬件结构
核心:时基单元
该部分是实现”定时”功能的基础,虽然名字是”时基”,但应该被看作是一个计数器。
其输入是来自触发控制器的时钟信号,可暂时先视作系统时钟信号。
硬件组成
| 组件 | 运行逻辑 | 相关组件 |
|---|---|---|
| 16位预分频器 PSC | 遇到上升沿时输出一个脉冲给CNT,然后等待 N 个脉冲 (不输出) | 对应的影子寄存器 |
| 16位计数器 CNT | 每遇到一个上升沿,寄存器就执行一次计数 (有多种模式) | “触发控制器”的CNT控制器 |
| 16位自动重装载寄存器 ARR | 根据向上/向下/中央对齐模式操作CNT,并产生更新事件与更新中断 | 对应的影子寄存器 |
例如,有一个 72MHz 的时钟信号作为输入。若设置为向上计数,PSC = (7200-1),ARR = (10000-1)。则预分频器会输出 的信号,又由于ARR会在 CNT==(10000-1) 的时候产生更新事件,所以最终的效果就是每秒产生一次更新。
关于中断和事件在此处,更新事件只会影响 CNT 与硬件电路(如 TRGO),而各个中断可以配置 NVIC 或 DMA 来处理
影子寄存器(预加载寄存器):在定时器运行的过程中,可能需要对PSC或ARR做修改,而计数器仍在运行,这可能会诱发不可预料的错误。而影子寄存器可以先记录要修改的目标值,在下一次触发更新事件时,再将修改同步到实际寄存器
影子寄存器需要手动激活例如ARR的影子寄存器,需要使用
TIM_ARRPreloadConfig()函数使能
向上计数模式
计数器从 0 开始向上计数到 ARR,然后产生一个更新事件并将 CNT 置 0
向下计数模式
计数器从 ARR 开始向下计数到 0,然后产生一个更新事件并将 CNT 置 ARR
中央对齐模式
ToDo
通用定时器 的结构与运行逻辑
对于通用定时器,应该将其视为一个“可编程的通用计数器”。其主要结构如下:
触发器信号输入部分
- 内部时钟 CK_INT:来自RCC的 TIMxCLK
- ETRF (处理后的外部时钟信号):由 ETR (外部触发引脚) 的信号经过 极性选择、边沿检测、预分频 (Prescaler) 得到 ETRP,再经过滤波得到 ETRF,发送到触发控制器用于外部时钟模式2(直接输入时基单元)
- ITR (Internal Trigger 内部触发信号):由四路 ITRx 信号经过 MUX (多路复用器)得到
- TRC (Timer Remote Control 定时器遥控信号):由 ITR 与 TI1F_ED (TI1信号经过滤波和双边沿检测,常用于 三相无刷直流电机) 经过 MUX 得到
- TRGI (Trigger Input 触发器输入信号):由 ETRF、TRC、TI1FP1、TI2FP2 经过 触发选择器(TS) 得到,作为从模式的输入信号
触发控制器
作为整个定时器的信号路由,负责处理输入与输出,并控制CNT的工作。可以选择以下时钟源:
- 内部时钟模式:(默认状态)使用系统时钟,CK_INT 信号。
- 外部时钟模式2:直接使用 ETRF 信号,注意不是从 TRGI 得到的信号。
所选的时钟源的信号将被直接发送到CK_PSC,而不经过从模式控制器。这意味着它可以与从模式中的复位、门控、触发模式一起使用,这也是两种外部时钟模式的区别。
时钟源的配置初始化定时器时,默认会使用内部时钟作为时钟源。
触发控制器可以同时开启两种模式:
-
从模式 (Slave controller mode):接收外部信号 TRGI,控制CNT
-
外部时钟模式1:来自TRGI的信号将被发送到 CK_PSC,作为时基单元的预分频器的输入。
-
复位模式:每次收到信号,CNT将被清零
-
门控模式:只有在收到信号时,CNT才会计数
-
触发模式:收到信号,CNT将被使能
-
编码器模式:这是一种特殊的从模式,开启后从模式控制器将自动控制 CNT 进行自增或自减。相当于使用了一个带有方向选择的外部时钟。
- 编码器模式1:2倍频。只在 A 相跳变时计数,看 B 相电平判方向
- 编码器模式2:2倍频。只在 B 相跳变时计数,看 A 相电平判方向
- 编码器模式3:4倍频 (最常用)。AB 两相跳变时都计数,看另一相电平判方向,精度最高
关于编码器模式下的ARR通常将ARR设置为65535,这样就可以利用补码的特性来很好的处理正反转 例如,编码器接口收到了两个反转信号,CNT 变为65534。又收到两个正转信号,CNT 先加到65535,再加一次就是0
-
-
主模式 (Master controller mode):将内部的事件输出为外部信号 TRGO(发往其它定时器实现级联,或发往 DAC/ADC)
- 复位:复位事件发生时,发送一个正脉冲
- 使能:将 CNT_EN 作为 TRGO,只要计数器在运行就是高电平
- 更新:当更新事件发生时,发送一个正脉冲
- 比较脉冲:在输入通道1 (IC1) 发生一次捕获或一次比较成功时,将要设置 CC1IF 标志时,发送一个正脉冲
- 四种比较模式:对应四个通道 ICx,将 OCxREF (Output Compare x Reference Signal 输出比较 x 参考信号) 作为 TRGO
输入/输出部分
该部分有四路,每路有一个输入捕获单元和一个输出比较单元(只能选择一个开启)。该部分的核心是 CCRx(捕获/比较寄存器),输入与输出都要操作该寄存器。 CCRx 有对应的影子寄存器(预装载寄存器),可配置开启。
输入捕获单元
以第一路通道举例。信号从 TIMx_CH1 外部引脚输入,经过滤波和边沿检测得到 TI1,之后分为两路。TI1FP1 通往 IC1 的 MUX,TI1FP2 通往 IC2 的 MUX;经过 预分频 后得到触发信号,触发时 CNT 将被复制到 CCR1 (捕获/比较寄存器)。
关于编码器模式而在编码器模式中,信号不经过边沿检测器就直接进入了编码器接口,因此 TIM_ICPolarity 在这里指的是 是否要对输入信号做反相处理。如果发现编码器接口中得到的数值与实际正好相反,就可以改变某一个 IC 的 TIM_ICPolarity,省去了换线的麻烦。
输出比较单元
对于 OCx (OC1 ~ OC4),它会 根据 CNT、CCRx 与设定的输出比较模式,将结果输出到 OCxREF,并触发 CCxI 中断,若触发该中断时,中断标志位非0 (即之前的中断未被处理完),会将 CC1OF 置1(表示系统未能及时处理捕获信号)
有下面几种输出比较模式:
-
冻结:REF 保持原状态
-
匹配时置有效电平:CNT==CCRx 时,REF 置有效电平(不适合输出连续信号)
有效电平在通用计时器中,有效电平即为高电平。
-
匹配时置无效电平:CNT==CCRx 时,REF 置无效电平(不适合输出连续信号)
-
匹配时电平翻转:CNT==CCRx 时,REF 电平翻转
输出一个占空比始终为50%的波形设置CNT=0,定时器就会在每次更新时翻转电平,因此周期为ARR的两倍,
-
强制为无效电平:REF 强制为无效电平
-
强制为有效电平:REF 强制为有效电平
-
PWM模式1
- 向上计数: 时,REF 置有效电平; 时, REF 置无效电平
- 向下计数: 时,REF 置有效电平; 时, REF 置无效电平
-
PWM模式2
- 向上计数: 时,REF 置有效电平; 时, REF 置无效电平
- 向下计数: 时,REF 置有效电平; 时, REF 置无效电平
关于PWM模式模式2 与 模式1 的输出完全反相;一般较少使用向下计数。对于模式1的向上计数,有如下参数: PWM频率: Freq = CK_PSC / (PSC + 1) / (ARR + 1) PWM占空比: Duty = CCR / (ARR + 1) PWM分辨率: Reso = 1 / (ARR + 1),也就是占空比最小变化步距,越小越好。
快速使能模式仅当开启从模式并由外部信号触发,且使用PWM输出时,该模式才有用。 开启该模式后,会跳过部分同步逻辑,缩短信号的输出延迟(牺牲微小的逻辑同步稳定性) 使用
TIM_OCxFastConfig开启
注意输入捕获 和 输出比较 不能同时开启,因为它们共用 CCRx
输出控制
负责将 REF 转化为 GPIO 的输出
- 极性选择:设置有效电平极性(为高电平或低电平),参数为
TIM_OCPolarity - 输出使能:参数为
TIM_OutputState
基本定时器 的结构与运行逻辑
基本定时器由一个最简单的触发控制器和一个时基单元组成,大概只能处理简单的定时任务。
高级定时器 的结构与运行逻辑
相比通用定时器,其在时基单元后连接了 重复次数计数器;在输出部分增加了 DTG 死区生成电路;输出引脚变为了两个互补的输出,可用来控制三相无刷电机;接入了刹车输入与时钟安全系统,用于在意外情况切断输出。
ToDo
基于标准库的代码
展开代码在下面的代码块中,点击 collapsed lines 即可展开代码
初始化一个最简单的通用定时器
以下代码用于初始化使用内部时钟的,每秒触发一次中断的TIM2。
流程:
- 初始化 TIM2 时钟
- 初始化 TIM2
- 打开 TIM2 的更新中断
- 配置 NVIC 处理更新中断
void TIM_Config(){20 collapsed lines
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); TIM_InternalClockConfig(TIM2); // 配置TIM2使用内部时钟 TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; // 对定时器时钟的分频操作 TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; // 向上计数 TIM_TimeBaseInitStructure.TIM_Prescaler = 0; // 预分频器 TIM_TimeBaseInitStructure.TIM_Period = 10 - 1; // 自动重装载寄存器的值 TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
TIM_ClearFlag(TIM2, TIM_IT_Update); // 避免启用后立刻触发的中断 TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_Init(&NVIC_InitStructure);
TIM_Cmd(TIM2, ENABLE);}注意设置NVIC优先级分组
NVIC_SetPriorityGrouping(NVIC_PriorityGroup_2);
初始化一个通用定时器,用于输出PWM
该示例可用于驱动SG90舵机(让PA1输出周期为20ms的PWM)
流程:
- 开启 GPIO 与 TIM 的时钟
- 配置时基单元
- 配置输出比较单元
- 初始化 GPIO
- 开启 TIM
void PWM_Config(){28 collapsed lines
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
// 72MHz * 0.020 = 72*20000 TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; // 配置时钟分频,此处设置为不分频 TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; // 向上计数 TIM_TimeBaseInitStructure.TIM_Prescaler = 72-1; // 预分频 (PSC) TIM_TimeBaseInitStructure.TIM_Period = 20000-1; // 周期 (ARR) TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
TIM_OCInitTypeDef TIM_OCInitStructure; TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; // PWM1模式 TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; // 有效电平为高电平 TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; // 输出使能 TIM_OCInitStructure.TIM_Pulse = 1500; // CCR初始值为1500 (高电平宽度 1.5ms) TIM_OC2Init(TIM2, &TIM_OCInitStructure);
GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure);
TIM_OC2PreloadConfig(TIM2, TIM_OCPreload_Enable); // 开启CCR的影子寄存器 TIM_ARRPreloadConfig(TIM2, ENABLE); // 开启ARR的影子寄存器
TIM_Cmd(TIM2, ENABLE);}初始化一个通用定时器,用测周法测量 PWM
测周法:在两个上升沿内,以标准频率 记次,得到 N,则频率
该示例中,。
每个上升沿,IC1 都会将 CNT 复制到 CCR1。随即从模式控制器将 CNT 复位。因此只需要读取CCR1,就可以得到最新的N。
周期值(注意单位换算):TIM_GetCapture1(TIM3)+1
频率值:(1000000)/(TIM_GetCapture1(TIM3)+1)
注意核算可测量的最小频率值此处的最小频率为
同理,将 TI1FP2 信号输入 IC2,并在每个下降沿将 CNT 复制到 CCR2,就可以得到高电平在一个周期中的占比,通过计算可以得到占空比(此处计算结果为千分值) (TIM_GetCapture2(TIM3)+1)*1000/(TIM_GetCapture1(TIM3)+1)
流程:
- 开启 GPIO 与 TIM 的时钟
- 配置时基单元
- 配置输入捕获单元
- 选择从模式-复位模式
- 选择输入触发信号的来源 (TRGI)
- 初始化 GPIO
- 开启 TIM
void PWMInput_Config(){38 collapsed lines
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; // 注意此处使用了下拉输入 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure);
// 72MHz = 72*1000000 (1s) TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; // 配置时钟分频,此处设置为不分频 TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; // 向上计数 TIM_TimeBaseInitStructure.TIM_Prescaler = 72-1; // 预分频 (PSC) TIM_TimeBaseInitStructure.TIM_Period = 65536-1; // 周期 (ARR) TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);
TIM_ICInitTypeDef TIM_ICInitStructure; TIM_ICInitStructure.TIM_Channel = TIM_Channel_1; // 配置IC1 TIM_ICInitStructure.TIM_ICFilter = 0xF; // 不启用滤波 TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; // 上升沿触发 TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; // 不分频 TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; // TI1 连接到 IC1(直连) TIM_ICInit(TIM3, &TIM_ICInitStructure);
TIM_ICInitStructure.TIM_Channel = TIM_Channel_2; TIM_ICInitStructure.TIM_ICFilter = 0xF; TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Falling; TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_IndirectTI; TIM_ICInit(TIM3, &TIM_ICInitStructure);
TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset); // 选择从模式-复位模式 TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1); // 选择 TI1FP1 作为 TRGI
TIM_ARRPreloadConfig(TIM3, ENABLE);
TIM_Cmd(TIM3, ENABLE);}初始化一个通用定时器,用标准库的PWMI模式测PWM
PWMI模式在硬件层面是不存在的,是标准库为了简化程序而引入的函数。如果使用它,则上节代码中配置输入捕获的部分可以改为:
TIM_ICInitTypeDef TIM_ICInitStructure; TIM_ICInitStructure.TIM_Channel = TIM_Channel_1; TIM_ICInitStructure.TIM_ICFilter = 0xF; TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; TIM_PWMIConfig(TIM3, &TIM_ICInitStructure);关于TIM_PWMIConfig()只需要传入一个通道的初始化结构体,函数会自动将另一个通道设置成对应的配置。
例如:传入 通道1,直连,上升沿。函数就会自动配置 通道2,交叉,下降沿。
但注意不能传入通道3或通道4
初始化一个通用定时器,读取编码器
流程:
- 开启 GPIO 与 TIM 的时钟
- 初始化 GPIO 为输入模式
- 配置时基单元(不分频)
- 配置输入捕获单元
- 配置编码器接口模式
- 开启 TIM
void EncoderInput_Config(){28 collapsed lines
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 注意此处使用了上拉输入 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure);
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; TIM_TimeBaseStructInit(&TIM_TimeBaseInitStructure); TIM_TimeBaseInitStructure.TIM_Prescaler = 0; // 预分频 (PSC) TIM_TimeBaseInitStructure.TIM_Period = 0xFFFF; // 周期 (ARR) TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);
TIM_ICInitTypeDef TIM_ICInitStructure; TIM_ICStructInit(&TIM_ICInitStructure); TIM_ICInitStructure.TIM_Channel = TIM_Channel_1; TIM_ICInitStructure.TIM_ICFilter = 0xF; // 滤波拉满 TIM_ICInit(TIM3, &TIM_ICInitStructure); TIM_ICInitStructure.TIM_Channel = TIM_Channel_2; TIM_ICInitStructure.TIM_ICFilter = 0xF; // 滤波拉满 TIM_ICInit(TIM3, &TIM_ICInitStructure);
// 开启TIM3的编码器接口,模式3,两个通道均不反相 TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);
TIM_Cmd(TIM3, ENABLE);}如何测速由于硬件限制,是无法做到硬件层面的测速的。最佳的方法就是再配置一个定时器,定时去读取编码器测量的结果,并计算速度。