✅作者简介:嵌入式入坑者,与大家一起加油,希望文章能够帮助各位!!!!
📃个人主页:@rivencode的个人主页
🔥系列专栏:玩转STM32
💬保持学习、保持热爱、认真分享、一起进步!!
一.基本定时器
1.基本定时器简介
基本定时器TIM6和TIM7各包含一个16位自动装载计数器
,由各自的可编程预分频器驱动。它们可以作为通用定时器提供时间基准,特别地可以为数模转换器(DAC)提供时钟。实际上,它们在芯片内部直接连接到DAC并通过触发输出直接驱动DAC。这2个定时器是互相独立的,不共享任何资源
2.时基单元
● 16位自动重装载累加计数器
● 16位可编程(可实时修改)预分频器,用于对输入的时钟按系数为1~65536之间的任意数值分频
● 触发DAC的同步电路
● 在更新事件(计数器溢出)时产生中断/DMA请求
时基单元包含:
● 计数器寄存器(TIMx_CNT)
● 预分频寄存器(TIMx_PSC)
● 自动重装载寄存器(TIMx_ARR)
自动重装载寄存器是预加载的,每次读写自动重装载寄存器时,实际上是通过读写预加载寄存器实现,根据TIMx_CR1寄存器中的自动重装载预加载使能位(ARPE),写入预加载寄存器的内容能够立即或
在每次更新事件时,传送到它的影子寄存器。
影子寄存器的存在起到一个缓冲的作用,用户值->寄存器->影子寄存器->起作用,如果不使用影子寄存器则用户值在写到寄存器之后则里面起作用
也就是说如果使能了TIMx_CR1寄存器中的自动重装载预加载使能位(ARPE),写入到重装载寄存器ARR中的值不会立马起作用而是,每次更新事件时过后,传送到它的影子寄存器,然后再起作用**
当TIMx_CR1寄存器的UDIS位为’0’,则每当计数器达到溢出值时,硬件发出更新事件;软件也可以产生更新事件;
计数器由预分频输出CK_CNT驱动,设置TIMx_CR1寄存器中的计数器使能位(CEN)使能计数器计数。
注意:实际的设置计数器使能信号CNT_EN相对于CEN滞后一个时钟周期。
所以如果ARR的值设置为1,但实际上计数次数为ARR+1=2
预分频器
预分频可以以系数介于1至65536之间的任意数值对计数器时钟分频。它是通过一个16位寄存器(TIMx_PSC)的计数实现分频。因为TIMx_PSC控制寄存器具有缓冲(具有影子寄存器),可以在运行过程中改变它的数值,新的预分频数值将在下一个更新事件时起作用。
计数模式
计数器从0累加计数到自动重装载数值(TIMx_ARR寄存器),然后重新从0开始计数并产生一个计数器溢出事件,也就是向上计数,而通用定时器与高级定时器还有向下计数,和中心对齐,基本定时器只能是向上计数。
设置TIMx_CR1中的UDIS位可以禁止产生UEV事件,这可以避免在写入预加载寄存器时更改影子寄存器。==在清除UDIS位为’0’之前,将不再产生更新事件,但计数器和预分频器依然会在应产生更新事件时重新从0开始计数(但预分频系数不变)。==另外,如果设置了TIMx_CR1寄存器中的URS(选择更新请求),设置UG位可以产生一次更新事件UEV,但不设置UIF标志(即没有中断或DMA请求)
当发生一次更新事件时,所有寄存器会被更新并(根据URS位)设置更新标志(TIMx_SR寄存器的UIF位):
自动重装载预加载使能位(ARPE)
时钟源
定时器时钟 TIMxCLK,即内部时钟 CK_INT,经 APB1 预分频器后分频提供,如果APB1 预分频系数等于 1,则频率不变,否则频率乘以 2,库函数中 APB1 预分频的系数是 2,即 PCLK1=36M,所以定时器时钟 TIMxCLK=36*2=72M。
定时时间的计算
*定时器的定时时间等于计数器的中断周期乘以中断的次数。计数器在 CK_CNT 的驱动下,计一个数的时间则是 CK_CLK 的倒数,等于:1/(TIMxCLK/(PSC+1)),产生一次中断的时间则等于:1/(CK_CLK * ARR)。如果在中断服务程序里面设置一个变量 time,用来 记 录 中 断的 次 数,那 么 就 可 以计 算 出我们 需 要 的 定时 时 间等于 : 1/CK_CLK *
(ARR+1)time。
定时器初始化结构体详解
(1) TIM_Prescaler:定时器预分频器设置,时钟源经该预分频器才是定时器时钟,它设定TIMx_PSC 寄存器的值。可设置范围为 0 至 65535,实现 1 至 65536 分频。
(2) TIM_CounterMode:定时器计数方式,可是在为向上计数、向下计数以及三种中心对齐模式。基本定时器只能是向上计数,即 TIMx_CNT 只能从 0 开始递增,并且无需初始化。
(3) TIM_Period:定时器周期,实际就是设定自动重载寄存器的值,在事件生成时更新到影子寄存器。可设置范围为 0 至 65535。
(4) TIM_ClockDivision:时钟分频,设置定时器时钟 CK_INT 频率与数字滤波器采样时钟频率分频比,基本定时器没有此功能,不用设置。
(5) TIM_RepetitionCounter:重复计数器,属于高级控制寄存器专用寄存器位,利用它可以非常容易控制输出 PWM 的个数,后面会讲解到。
所以基本定时器我们只要用到分频值PSC与重装载ARR
3.基本定时器定时实验
由于代码十分简单直接上代码
main.c
int main(void)
{
//LED 端口初始化
LED_GPIO_Config();
//基本定时器初始化
BASIC_TIM_Init();
while(1)
{
//500次更新中断(500ms),LED翻转一次
if( g_time==500 )
{
LED1_TOGGLE
g_time=0;
}
}
}
BasicTim.h
#ifndef __BASICTIM_H
#define __BASICTIM_H
#include "stm32f10x.h"
#define BASIC_TIM6
#ifdef BASIC_TIM6
#define BASIC_TIM TIM6
#define BASIC_TIM_APBxClock_FUN RCC_APB1PeriphClockCmd
#define BASIC_TIM_CLK RCC_APB1Periph_TIM6
#define BASIC_TIM_Prescaler (72-1)
#define BASIC_TIM_Period (1000-1)
#define BASIC_TIM_IRQn TIM6_IRQn
#define BASIC_TIM_IRQHandler TIM6_IRQHandler
#else
#define BASIC_TIM TIM6
#define BASIC_TIM_APBxClock_FUN RCC_APB1PeriphClockCmd
#define BASIC_TIM_CLK RCC_APB1Periph_TIM6
#define BASIC_TIM_Prescaler (72-1)
#define BASIC_TIM_Period (1000-1)
#define BASIC_TIM_IRQn TIM6_IRQn
#define BASIC_TIM_IRQHandler TIM6_IRQHandler
#endif /* BASIC_TIM6 */
void BASIC_TIM_Init(void);
#endif /*__BASICTIM_H*/
BasicTim.c
#include "BasicTim.h"
static void BASIC_TIM_NVIC_Config(void)
{
NVIC_InitTypeDef NVIC_InitStructrue;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitStructrue.NVIC_IRQChannel = BASIC_TIM_IRQn;
NVIC_InitStructrue.NVIC_IRQChannelPreemptionPriority =1;
NVIC_InitStructrue.NVIC_IRQChannelSubPriority =1;
NVIC_InitStructrue.NVIC_IRQChannelCmd =ENABLE;
NVIC_Init(&NVIC_InitStructrue);
}
static void BASIC_TIM_Config(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructrue;
//开启定时器时钟
BASIC_TIM_APBxClock_FUN(BASIC_TIM_CLK,ENABLE);
//时钟预分频
TIM_TimeBaseInitStructrue.TIM_Prescaler =BASIC_TIM_Prescaler;
//重装载寄存器
TIM_TimeBaseInitStructrue.TIM_Period =BASIC_TIM_Period;
TIM_TimeBaseInit(BASIC_TIM,&TIM_TimeBaseInitStructrue);
TIM_ClearFlag(BASIC_TIM,TIM_FLAG_Update);
TIM_ITConfig(BASIC_TIM,TIM_IT_Update,ENABLE);
TIM_Cmd(BASIC_TIM, ENABLE);
}
void BASIC_TIM_Init(void)
{
BASIC_TIM_NVIC_Config();
BASIC_TIM_Config();
}
中断服务函数
二.高级/通用定时器
高级定时器有所有定时器的功能,所以以高级定时器讲解为主,通用定时器就是在高级对定时器做减法
高级控制定时器比通用定时器增加了可编程死区互补输出、重复计数器、带刹车(断路)功能,这些功能都是针对工业电机控制方面,而且比通用定时器多了个重复。
高级定时器的功能:
TIM1和TIM8定时器的功能包括:
● 16位向上、向下、向上/下自动装载计数器
● 16位可编程(可以实时修改)预分频器,计数器时钟频率的分频系数为1~65535之间的任意
数值
● 多达4个独立通道:
─ 输入捕获
─ 输出比较
─ PWM生成(边缘或中间对齐模式)
─ 单脉冲模式输出
● 死区时间可编程的互补输出
● 使用外部信号控制定时器和定时器互联的同步电路
●允许在指定数目的计数器周期之后更新定时器寄存器的重复计数器
● 刹车输入信号可以将定时器输出信号置于复位状态或者一个已知状态
● 如下事件发生时产生中断/DMA:
─ 更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或者内部/外部触发)
─ 触发事件(计数器启动、停止、初始化或者由内部/外部触发计数)
─ 输入捕获
─ 输出比较
─ 刹车信号输入
● 支持针对定位的增量(正交)编码器和霍尔传感器电路
● 触发输入作为外部时钟或者按周期的电流管理
高级/通用定时器的时基单元与基本定时器一致,这里就不赘言啦。
不过高级定时器多了个重复计数器。
1.计数器模式
重复计数寄存器
在定时器发生上溢或下溢
事件是递减重复计数器的值,只有当重复计数器为 0 时才会生成更新事件。在发生 N+1个上溢或下溢事件(N 为 RCR 的值)时产生更新事件。
重复计数器在下述任一条件成立时递减(递减一次):
● 向上计数模式下每次计数器溢出时
●向下计数模式下每次计数器下溢时
●中央对齐模式下每次上溢和每次下溢时
虽然这样限制了PWM的最大循环周期为128(重复计数器是8位的),但它能够在每个PWM周期2次更新占空比。在中央对齐模式下,因为波形是对称的,如果每个PWM周期中仅刷新一次比较寄存器,则最大的分辨率为2xTck。
当更新事件由软件产生(通过设置TIMx_EGR 中的UG位)或者通过硬件的从模式控制器产生,则无论重复计数器的值是多少,立即发生更新事件,并且TIMx_RCR寄存器中的内容被重载入到重复计数器。
1.向上计数模式
在向上计数模式中,计数器从0计数到自动加载值(TIMx_ARR计数器的内容),然后重新从0开始计数并且产生一个计数器溢出事件。
如果使用了重复计数器功能,在向上计数达到设置的重复计数次数(TIMx_RCR)时,产生更新事件(UEV);否则每次计数器溢出时就会产生更新事件。
当发生一个更新事件时,所有的寄存器都被更新,硬件同时(依据URS位)设置更新标志位(TIMx_SR寄存器中的UIF位)
● 重复计数器被重新加载为TIMx_RCR寄存器的内容。
● 自动装载影子寄存器被重新置入预装载寄存器的值(TIMx_ARR)。
● 预分频器的缓冲区被置入预装载寄存器的值(TIMx_PSC寄存器的内容)
2.向下计数模式
在向下模式中,计数器从自动装入的值(TIMx_ARR计数器的值)开始向下计数到0,然后从自动装入的值重新开始并且产生一个计数器向下溢出事件,如果使用了重复计数器,当向下计数重复了重复计数寄存器(TIMx_RCR)中设定的次数后,将产生更新事件(UEV),否则每次计数器下溢时就产生更新事件
当发生更新事件时,所有的寄存器都被更新,并且(根据URS位的设置)更新标志位(TIMx_SR寄存器中的UIF位)也被设置。
● 重复计数器被重置为TIMx_RCR寄存器中的内容
● 预分频器的缓存器被加载为预装载的值(TIMx_PSC寄存器的值)。
● 当前的自动加载寄存器被更新为预装载值(TIMx_ARR寄存器中的内容)。注:自动装载在计数器重载入之前被更新,因此下一个周期将是预期的值。
3.中央对齐模式(向上/向下计数)
在中央对齐模式,计数器从0开始计数到自动加载的值(TIMx_ARR寄存器)−1,产生一个计数器溢出事件,然后向下计数到1并且产生一个计数器下溢事件;然后再从0开始重新计数。在此模式下,不能写入TIMx_CR1中的DIR方向位。它由硬件更新并指示当前的计数方向。
可以在每次计数上溢和每次计数下溢时产生更新事件;也可以通过(软件或者使用从模式控制器)设置TIMx_EGR寄存器中的UG位产生更新事件。然后,计数器重新从0开始计数,预分频器也重新从0开始计数。
其他两个模式依次类推
2.时钟选择
计数器时钟可由下列时钟源提供:
● 内部时钟(CK_INT)
● 外部时钟模式1:外部输入引脚
● 外部时钟模式2:外部触发输入ETR
● 内部触发输入(ITRx):使用一个定时器作为另一个定时器的预分频器。如可以配置一个定时器Timer1而作为另一个定时器Timer2的预分频器。
-
1.内部时钟
如果禁止了从模式控制器(SMS=000),则CEN、DIR(TIMx_CR1寄存器)和UG位(TIMx_EGR寄存器)是事实上的控制位,并且只能被软件修改(UG位仍被自动清除)。只要CEN位被写成’1’,预分频器的时钟就由内部时钟CK_INT提供
-
2.外部时钟源模式1
这是其中一个时钟信号输入引脚
当TIMx_SMCR寄存器的SMS=111时,此模式被选中。计数器可以在选定输入端的每个上升沿或下降沿计数。
1)时钟信号输入引脚
当使用外部时钟模式 1 的时候,时钟信号来自于定时器的输入通道,总共有 4 个,分别为 TI1/2/3/4,即 TIMx_CH1/2/3/4。具体使用哪一路信号,由 TIM_CCMRx 的位CCxS[1:0]配置,其中 CCMR1 控制 TI1/2,CCMR2 控制 TI3/4。
假设要配置要配置向上计数器在T12输入端的上升沿计数,使用下列步骤:
2)滤波器
如果来自外部的时钟信号的频率过高或者混杂有高频干扰信号的话,我们就需要使用滤波器对信号重新采样,来达到降频或者去除高频干扰的目的,具体的由 TIMx_CCMRx的位 ICxF[3:0]配置。
配置TIMx_CCMR1寄存器的IC2F[3:0],选择输入滤波器带宽(如果不需要滤波器,保持IC2F=0000)
3)边沿检测
边沿检测的信号来自于滤波器的输出,在成为触发信号之前,需要进行边沿检测,决定是上升沿有效还是下降沿有效,具体的由 TIMx_CCER 的位 CCxP 配置。
配置TIMx_CCER寄存器的CC2P=0,选定上升沿极性
4)触发选择
当使用外部时钟模式1时,触发源有两个,一个是滤波后的定时器输入 1(TI1FP1)和滤波后的定时器输入 2(TI2FP2),具体的由 TIMxSMCR 的位 TS[2:0]配置。
配置TIMx_SMCR寄存器中的TS=110,选定TI2作为触发输入源
5)从模式选择
选定了触发源信号后,最后我们需把信号连接到 TRGI 引脚,让触发信号成为外部时钟模式 1 的输入,最终等于 CK_PSC,然后驱动计数器 CNT 计数。具体的配置TIMx_SMCR 的位 SMS[2:0]为 111 即可选择外部时钟模式 1。
6)使能计数器
设置TIMx_CR1寄存器的CEN=1,启动计数器
当上升沿出现在TI2,计数器计数一次,且TIF标志被设置。
外部时钟1配置库函数
-
3.外部时钟源模式2
选定此模式的方法为:令TIMx_SMCR寄存器中的ECE=1 计数器能够在外部触发ETR的每一个上升沿或下降沿计数
例如,要配置在ETR下每2个上升沿计数一次的向上计数器,使用下列步骤
1)时钟信号输入引脚
当使用外部时钟模式 2 的时候,时钟信号来自于定时器的特定输入通道 TIMx_ETR,只有 1 个。
2)外部触发极性
来自 ETR 引脚输入的信号可以选择为上升沿或者下降沿有效,具体的由 TIMx_SMCR的位 ETP 配置。
选择ETR的上升沿检测,置TIMx_SMCR寄存器中的ETP=0
3)外部触发预分频器
由于 ETRP 的信号的频率不能超过 TIMx_CLK(72M)的 1/4,当触发信号的频率很高的情况下,就必须使用分频器来降频,具体的由 TIMx_SMCR 的位 ETPS[1:0]配置。
设置预分频器,置TIMx_SMCR寄存器中的ETPS[1:0]=01
3)滤波器
如果 ETRP 的信号的频率过高或者混杂有高频干扰信号的话,我们就需要使用滤波器对 ETRP 信号重新采样,来达到降频或者去除高频干扰的目的。具体的由 TIMx_SMCR 的位 ETF[3:0]配置,其中的 fDTS是由内部时钟 CK_INT 分频得到,具体的由 TIMx_CR1 的位CKD[1:0]配置。
整个定时器的滤波器都是一样的功能。
本例中不需要滤波器,置TIMx_SMCR寄存器中的ETF[3:0]=0000
4)从模式选择
经过滤波器滤波的信号连接到 ETRF 引脚后,触发信号成为外部时钟模式 2 的输入,最终等于 CK_PSC,然后驱动计数器 CNT 计数。具体的配置 TIMx_SMCR 的位 ECE 为 1即可选择外部时钟模式 2。
开启外部时钟模式2,写TIMx_SMCR寄存器中的ECE=1
6)使能计数器
启动计数器,写TIMx_CR1寄存器中的CEN=1
计数器在每2个ETR上升沿计数一次
外部时钟模式2配置库函数
-
4.内部触发输入(ITRx)
使用一个定时器作为另一个定时器的预分频器。如可以配置一个定时
器Timer1而作为另一个定时器Timer2的预分频器。
● 配置定时器1为主模式,它可以在每一个更新事件UEV时输出一个周期性的触发信号。在TIM1_CR2寄存器的MMS=’010’时,每当产生一个更新事件时在TRGO1上输出一个上升沿
信号。
● 连接定时器1的TRGO1输出至定时器2,设置TIM2_SMCR寄存器的TS=’001’,配置定时器2为使用ITR1作为内部触发的从模式。
● 然后把从模式控制器置于外部时钟模式1(TIM2_SMCR寄存器的SMS=111);这样定时器2即可由定时器1周期性的上升沿(即定时器1的计数器溢出)信号驱动。
● 最后,必须设置相应(TIMx_CR1寄存器)的CEN位分别启动两个定时器。
3.输出比较产生PWM波
什么是PWM
脉冲宽度调制(PWM),是英文“Pulse Width Modulation”的缩写,简称脉宽调制,是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术,广泛应用在从测量、通信到功率控制与变换的许多领域中。
输出比较的输出控制中,参考信号 OCxREF 在经过死区发生器之后会产生两路带死区的互补信号(通道 1~3 才有互补信号,通道 4 没有,其余跟通道1 ~ 3 一样),这两路带死区的互补信号然后就进入输出控制电路,如果没有加入死区控制,那么进入输出控制电路的信号就直接是 OCxREF。
1.PWM模式
脉冲宽度调制模式可以产生一个由TIMx_ARR寄存器确定频率、由TIMx_CCRx寄存器确定占空比的信号。
在TIMx_CCMRx寄存器中的OCxM位写入’110’(PWM模式1或’111’(PWM模式2),能够独立地设置每个OCx输出通道产生一路PWM。
先以简单的通道4位例,不带互补输出和死区控制
这里我都以通道1的寄存器位讲解,通道4是一模一样
控制OC1_REF的输出
选择有效电平,假设选择高电平有效
当选择PWM模式1,高电有效时,向上计数时
当选择PWM模式2,高电有效时,向上计数时
最后就是打开通道输出的开关
必须通过设置TIMx_CCMRx寄存器的OCxPE位使能相应的预装载寄存器,最后还要设置TIMx_CR1寄存器的ARPE位,(在向上计数或中心对称模式中)使能自动重装载的预装载寄存器。
对应库函数
相关库函数
-
PWM 边沿对齐模式
● 向上计数配置
下面是一个PWM模式1的例子。当TIMx_CNT<TIMx_CCRx时,PWM参考信号OCxREF为=高,否则为低。特殊情况(如果TIMx_CCRx中的比较值大于自动重装载值(TIMx_ARR),则OCxREF保持为’1’。如果比较值为0,则OCxREF保持为’0’。) 下图为TIMx_ARR=8时边沿对齐的PWM波形实例
首先要知道一个比较中断标记位
● 向上计数配置
下面是一个PWM模式1的例子。当TIMx_CNT<TIMx_CCRx时,PWM参考信号OCxREF为=高,否则为低。特殊情况(如果TIMx_CCRx中的比较值大于自动重装载值(TIMx_ARR),则OCxREF保持为’1’。如果比较值为0,则OCxREF保持为’0’。) 下图为TIMx_ARR=8时边沿对齐的PWM波形实例
● 向下计数的配置
当TIMx_CR1寄存器的DIR位为高时执行向下计数。参看13.3.2节。
在PWM模式1,当TIMx_CNT>TIMx_CCRx时参考信号OCxREF为低,否则为高。如果
TIMx_CCRx中的比较值大于TIMx_ARR中的自动重装载值,则OCxREF保持为’1’。该模式下不能产生0%的PWM波形。
PWM 中央对齐模式
当TIMx_CR1寄存器中的CMS位不为’00’时为中央对齐模式(所有其他的配置对OCxREF/OCx信号都有相同的作用)。根据不同的CMS位设置(中央对齐模式1、2、3),比较标志可以在计数器向上计数时被置1、在计数器向下计数时被置1、或在计数器向上和向下计数时被置1,TIMx_CR1寄存器中的计数方向位(DIR)由硬件更新,不要用软件修改它。
下图给出了一些中央对齐的PWM波形的例子
● TIMx_ARR=8
● PWM模式1
● TIMx_CR1寄存器的CMS=01,在中央对齐模式1下,当计数器向下计数时设置比较标志。
三种模式中央对齐模式中断标志何时置位:
中央对齐模式1(CSM=01):
只在计数器向下计数时,且TIMx_CNT的值与TIMx_CCR1的值相等CCXIF位被置位
中央对齐模式2(CSM=10):
只在计数器向上计数时,且TIMx_CNT的值与TIMx_CCR1的值相等CCXIF位被置位
中央对齐模式3(CSM=11):
在计数器向上和向下计数时,且TIMx_CNT的值与TIMx_CCR1的值相等CCXIF位均被置位。**
稍微画的有点乱了,不过噢配合文字还是可以理解清楚的
在看野火的就很好理解了
在中心对齐模式下,计数器 CNT 是工作做递增/递减模式下。开始的时候,计数器CNT 从 0 开始计数到自动重载值减 1(ARR-1),生成计数器上溢事件;然后从自动重载值开始向下计数到 1 并生成计数器下溢事件。之后从 0 开始重新计数。
图 32-14 是 PWM1 模式的中心对齐波形,ARR=8,CCR=4。第一阶段计数器 CNT 工作在递增模式下,从 0 开始计数,当 CNT<CCR 的值时,OCxREF 为有效的高电平,当CCR=<CNT<<ARR 时,OCxREF 为无效的低电平。第二阶段计数器 CNT 工作在递减模式从 ARR 的值开始递减,当 CNT>CCR 时,OCxREF 为无效的低电平,当 CCR=>CNT>=1时,OCxREF 为有效的高电平。
4.通用定时器PWM输出实验
-
实验目的
使用定时器3的PWM功能,输出占空比可变PWM波,用来驱动LED灯,从而达到LED亮度由暗变亮,又从亮变暗,如此循环。 - 实验原理
一般人眼睛对于80Hz 以上刷新频率则完全没有闪烁感。频率太小的话 看起来就会闪烁
那么我们平时见到的LED灯,当它的频率大于50Hz的时候,人眼就会产生视觉暂留效果,基本就看不到闪烁了,而是一个常亮的LED灯。
但是如果是10毫秒内,5毫秒打开,5毫秒关闭,(频率100Hz) 这时候灯光的亮灭速度赶不上开关速度(LED灯还没完全亮就又熄灭了),由于视觉暂留作用 人眼不感觉电灯在闪烁,而是感觉灯的亮度少了 因为高电平时间(占空比)为50% 亮度也就为之前的50%
频率很高时,看不到闪烁,占空比越大,LED越亮;
频率很低时,可看到闪烁,占空比越大,LED越亮。
所以,在频率一定下,可以用不同占空比改变LED灯的亮度。使其达到一个呼吸灯的效果
我们选这PWM模式1、向上计数、高电平有效,然后输出一个PWM波,通过调解CRR寄存器的值,来改变PWM的占空比
首先我们知道
所以占比空越大高电平的时间越长灯就会越暗,所以我们先将占空比调成100%灯全灭,然后将占空比慢慢变小灯就会慢慢变亮(到最亮),然后又将占空比调大又慢慢熄灭,然后反复如此就是呼吸灯了
直接上代码:
main.c
#include "stm32f10x.h"
#include "bsp_led.h"
#include "GeneralTim.h"
#define SOFT_DELAY Delay(0x0FFF1);
void Delay(__IO u32 nCount);
int main(void)
{
uint16_t led0pwmval=600;
uint8_t dir=1;
/* LED 端口初始化 */
LED_GPIO_Config();
//通用定时器初始化
GENERAL_TIM_Init();
while(1)
{
SOFT_DELAY
if(dir==1)
{
led0pwmval--;
}
else
{
led0pwmval++;
}
if(led0pwmval<300)
{
dir=0;
}
if(led0pwmval==600)
{
dir=1;
}
TIM_SetCompare3(GENERAL_TIM,led0pwmval);
}
}
void Delay(__IO uint32_t nCount) //简单的延时函数
{
for(; nCount != 0; nCount--);
}
GeneralTim.c
#include "GeneralTim.h"
static void GENERAL_TIM_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(GENERAL_TIM_CH3_GPIO_CLK,ENABLE);
GPIO_InitStructure.GPIO_Mode =GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin =GENERAL_TIM_CH3_PIN;
GPIO_InitStructure.GPIO_Speed =GPIO_Speed_50MHz;
GPIO_Init(GENERAL_TIM_CH3_PORT,&GPIO_InitStructure);
}
static void GENERAL_TIM_Mode_Config(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructrue;
TIM_OCInitTypeDef TIM_OCInitStructrue;
//开启定时器时钟
GENERAL_TIM_APBxClock_FUN(GENERAL_TIM_CLK,ENABLE);
//时钟预分频
TIM_TimeBaseInitStructrue.TIM_Prescaler =GENERAL_TIM_Prescaler;
//重装载寄存器
TIM_TimeBaseInitStructrue.TIM_Period =GENERAL_TIM_Period;
//向上计数
TIM_TimeBaseInitStructrue.TIM_CounterMode =TIM_CounterMode_Up;
TIM_TimeBaseInitStructrue.TIM_ClockDivision =TIM_CKD_DIV1;
TIM_TimeBaseInit(GENERAL_TIM,&TIM_TimeBaseInitStructrue);
//选择PWM1模式
TIM_OCInitStructrue.TIM_OCMode =TIM_OCMode_PWM1;
//使能输出通道
TIM_OCInitStructrue.TIM_OutputState =TIM_OutputState_Enable;
//高电平有效
TIM_OCInitStructrue.TIM_OCPolarity =TIM_OCPolarity_High;
TIM_OCInitStructrue.TIM_Pulse =600;
TIM_OC3Init(GENERAL_TIM, &TIM_OCInitStructrue);
//CRR3预装载使能
TIM_OC3PreloadConfig(GENERAL_TIM,TIM_OCPreload_Enable);
TIM_Cmd(GENERAL_TIM, ENABLE);
}
void GENERAL_TIM_Init(void)
{
GENERAL_TIM_GPIO_Config();
GENERAL_TIM_Mode_Config();
}
GeneralTim.h
#ifndef __GENERALTIM_H
#define __GENERALTIM_H
#include "stm32f10x.h"
// TIM3 输出比较通道
#define GENERAL_TIM_CH3_GPIO_CLK RCC_APB2Periph_GPIOB
#define GENERAL_TIM_CH3_PORT GPIOB
#define GENERAL_TIM_CH3_PIN GPIO_Pin_0
#define GENERAL_TIM TIM3
#define GENERAL_TIM_APBxClock_FUN RCC_APB1PeriphClockCmd
#define GENERAL_TIM_CLK RCC_APB1Periph_TIM3
#define GENERAL_TIM_Prescaler 0
#define GENERAL_TIM_Period (600-1)
void GENERAL_TIM_Init(void);
#endif /*__GENERALTIM_H */
5.高级定时器死区时间的互补输出PWM实验
进入输出控制电路的信号会被分成两路,一路是原始信号,一路是被反向的信号,具体的由寄存器 CCER 的位 CCxP 和 CCxNP 控制。经过极性选择的信号是否由 OCx 引脚输出到外部引脚 CHx/CHxN 则由寄存器 CCER 的位 CxE/CxNE 配置。
如果加入了断路(刹车)功能,则断路和死区寄存器 BDTR 的 MOE、OSSI 和 OSSR
这三个位会共同影响输出的信号。
为什么要有死区时间
高级控制定时器(TIM1和TIM8)能够输出两路互补信号,并且能够管理输出的瞬时关断和接通。这段时间通常被称为死区,用户应该根据连接的输出器件和它们的特性(电平转换的延时、电源开关的延时等)来调整死区时间。
死区发生器
在生成的参考波形 OCxREF 的基础上,可以插入死区时间,用于生成两路互补的输出信号 OCx 和 OCxN,死区时间的大小具体由 BDTR 寄存器的位 DTG[7:0]配置。死区时间的大小必须根据与输出信号相连接的器件及其特性来调整。下面我们简单举例说明下带死区的PWM 信号的应用,我们以一个板桥驱动电路为例
在这个半桥驱动电路中,Q1 导通,Q2 截止,此时我想让 Q1 截止 Q2 导通,肯定是要先让Q1 截止一段时间之后,再等一段时间才让 Q2 导通,那么这段等待的时间就称为死区时间,因为 Q1 关闭需要时间(由 MOS 管的工艺决定)。如果 Q1 关闭之后,马上打开 Q2,那么此时一段时间内相当于 Q1 和 Q2 都导通了,这样电路会短路。
死区时间应该调为多少用户应该根据连接的输出器件和它们的特性(电平转换的延时、电源开关的延时等)来调整死区时间
如何配置死区时间
断路功能
断路功能就是电机控制的刹车功能,使能断路功能时,根据相关控制位状态修改输出信号电平。在任何情况下,OCx 和 OCxN 输出都不能同时为有效电平,这关系到电机控制常用的 H 桥电路结构原因。
断路源可以是时钟故障事件,由内部复位时钟控制器中的时钟安全系统(CSS)生成,也可以是外部断路输入 IO,两者是或运算关系。
系统复位启动都默认关闭断路功能,将断路和死区寄存器(TIMx_BDTR)的 BKE 为置 1,使能断路功能。可通过 TIMx_BDTR 寄存器的 BKP 位设置设置断路输入引脚的有效电平,设置为 1 时输入 BRK 为高电平有效,否则低电平有效。
发送断路时,将产生以下效果:
1.TIMx_BDTR 寄存器中主输出模式使能(MOE)位被清零,输出处于无效、空闲或复位状态;
2. 根据相关控制位状态控制输出通道引脚电平;当使能通道互补输出时,会根据情况自动控制输出通道电平;
3. 将 TIMx_SR 寄存器中的 BIF 位置 1,并可产生中断和 DMA 传输请求。
4.如果 TIMx_BDTR 寄存器中的 自动输出使能(AOE)位置 1,则 MOE 位会在发生下一个 UEV 事件时自动再次置 1。
具体MOE、OSSI、OSSR、CCXE、CCXNE四个位如何影响OCX与OCXN输出状态看下图
定时器比较输出初始化结构体
(1) TIM_OCMode:比较输出模式选择,总共有八种,常用的为 PWM1/PWM2。它设定CCMRx 寄存器 OCxM[2:0]位的值。
(2) TIM_OutputState:比较输出使能,决定最终的输出比较信号 OCx 是否通过外部引脚输出。它设定 TIMx_CCER 寄存器 CCxE/CCxNE 位的值。
(3) TIM_OutputNState:比较互补输出使能,决定 OCx 的互补信号 OCxN 是否通过外部引脚输出。它设定 CCER 寄存器 CCxNE 位的值。
(4) TIM_Pulse:比较输出脉冲宽度,实际设定比较寄存器 CCR 的值,决定脉冲宽度。可设置范围16位为 0 至 65535。
(5) TIM_OCPolarity:比较输出极性,可选 OCx 为高电平有效或低电平有效。它决定着定时器通道有效电平。它设定 CCER 寄存器的 CCxP 位的值。
(6) TIM_OCNPolarity:比较互补输出极性,可选 OCxN 为高电平有效或低电平有效。它设定 TIMx_CCER 寄存器的 CCxNP 位的值。
(7) TIM_OCIdleState:空闲状态时通道输出电平设置,可选输出 1 或输出 0,即在空闲状态(BDTR_MOE 位为 0)时,经过死区时间后定时器通道输出高电平或低电平。它设定CR2 寄存器的 OISx 位的值。
(8) TIM_OCNIdleState:空闲状态时互补通道输出电平设置,可选输出 1 或输出 0,即在空闲状态(BDTR_MOE 位为 0)时,经过死区时间后定时器互补通道输出高电平或低电平,设定值必须与 TIM_OCIdleState 相反。它设定是 CR2 寄存器的 OISxN 位的值。
断路和死区初始化结构体
(1) TIM_OSSRState:运行模式下的关闭状态选择,它设定 BDTR 寄存器 OSSR 位的值。
(2) TIM_OSSIState:空闲模式下的关闭状态选择,它设定 BDTR 寄存器 OSSI 位的值。
(3) TIM_LOCKLevel:锁定级别配置, BDTR 寄存器 LOCK[1:0]位的值。
(4) TIM_DeadTime:配置死区发生器,定义死区持续时间,可选设置范围为 0x0 至 0xFF。它设定 BDTR 寄存器 DTG[7:0]位的值。
(5) TIM_Break:断路输入功能选择,可选使能或禁止。它设定 BDTR 寄存器 BKE 位的值。
(6) TIM_BreakPolarity:断路输入通道 BRK 极性选择,可选高电平有效或低电平有效。它设定 BDTR 寄存器 BKP 位的值。
(7) TIM_AutomaticOutput:自动输出使能,可选使能或禁止,它设定 BDTR 寄存器 AOE位的值。
TIM_高级定时器_PWM带死区时间带刹车控制的互补输出实验
Advance_Tim.h
#ifndef __ADVANCETIM_H
#define __ADVANCETIM_H
#include "stm32f10x.h"
/************高级定时器TIM参数定义,只限TIM1和TIM8************/
// 当使用不同的定时器的时候,对应的GPIO是不一样的,这点要注意
// 这里我们使用高级控制定时器TIM1
#define ADVANCE_TIM TIM1
#define ADVANCE_TIM_APBxClock_FUN RCC_APB2PeriphClockCmd
#define ADVANCE_TIM_CLK RCC_APB2Periph_TIM1
// PWM 信号的频率 F = TIM_CLK/{(ARR+1)*(PSC+1)}
#define ADVANCE_TIM_PERIOD (8-1)
#define ADVANCE_TIM_PSC (9-1)
#define ADVANCE_TIM_PULSE 4
// TIM1 输出比较通道
#define ADVANCE_TIM_CH1_GPIO_CLK RCC_APB2Periph_GPIOA
#define ADVANCE_TIM_CH1_PORT GPIOA
#define ADVANCE_TIM_CH1_PIN GPIO_Pin_8
// TIM1 输出比较通道的互补通道
#define ADVANCE_TIM_CH1N_GPIO_CLK RCC_APB2Periph_GPIOB
#define ADVANCE_TIM_CH1N_PORT GPIOB
#define ADVANCE_TIM_CH1N_PIN GPIO_Pin_13
// TIM1 输出比较通道的刹车通道
#define ADVANCE_TIM_BKIN_GPIO_CLK RCC_APB2Periph_GPIOB
#define ADVANCE_TIM_BKIN_PORT GPIOB
#define ADVANCE_TIM_BKIN_PIN GPIO_Pin_12
void ADVANCE_TIM_Init(void);
#endif /* __ADANCETIM_H */
Advance_Tim.c
#include "Advance_Tim.h"
void ADVANCE_TIM_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(ADVANCE_TIM_CH1_GPIO_CLK,ENABLE);
GPIO_InitStructure.GPIO_Mode =GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin =ADVANCE_TIM_CH1_PIN;
GPIO_InitStructure.GPIO_Speed =GPIO_Speed_50MHz;
GPIO_Init(ADVANCE_TIM_CH1_PORT,&GPIO_InitStructure);
RCC_APB2PeriphClockCmd(ADVANCE_TIM_CH1N_GPIO_CLK,ENABLE);
GPIO_InitStructure.GPIO_Mode =GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin =ADVANCE_TIM_CH1N_PIN;
GPIO_InitStructure.GPIO_Speed =GPIO_Speed_50MHz;
GPIO_Init(ADVANCE_TIM_CH1N_PORT,&GPIO_InitStructure);
RCC_APB2PeriphClockCmd(ADVANCE_TIM_BKIN_GPIO_CLK,ENABLE);
GPIO_InitStructure.GPIO_Mode =GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin =ADVANCE_TIM_BKIN_PIN;
GPIO_InitStructure.GPIO_Speed =GPIO_Speed_50MHz;
GPIO_Init(ADVANCE_TIM_BKIN_PORT,&GPIO_InitStructure);
GPIO_ResetBits(ADVANCE_TIM_BKIN_PORT,ADVANCE_TIM_BKIN_PIN);
}
void ADVANCE_TIM_Mode_Config(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
ADVANCE_TIM_APBxClock_FUN(ADVANCE_TIM_CLK,ENABLE);
TIM_TimeBaseInitStructure.TIM_Prescaler =ADVANCE_TIM_PSC;
TIM_TimeBaseInitStructure.TIM_Period =ADVANCE_TIM_PERIOD;
TIM_TimeBaseInitStructure.TIM_ClockDivision =TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode =TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_RepetitionCounter =0;
TIM_TimeBaseInit(ADVANCE_TIM,&TIM_TimeBaseInitStructure);
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCInitStructure.TIM_OutputNState =TIM_OutputNState_Enable;
TIM_OCInitStructure.TIM_OutputState =TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse =ADVANCE_TIM_PULSE;
TIM_OCInitStructure.TIM_OCMode =TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OCIdleState =TIM_OCIdleState_Set;
TIM_OCInitStructure.TIM_OCNIdleState =TIM_OCNIdleState_Reset;
TIM_OCInitStructure.TIM_OCNPolarity =TIM_OCNPolarity_High;
TIM_OCInitStructure.TIM_OCPolarity =TIM_OCPolarity_High;
TIM_OC1Init(ADVANCE_TIM, &TIM_OCInitStructure);
TIM_OC1PreloadConfig(ADVANCE_TIM, TIM_OCPreload_Enable);
/*-------------------刹车和死区结构体初始化-------------------*/
// 有关刹车和死区结构体的成员具体可参考BDTR寄存器的描述
TIM_BDTRInitTypeDef TIM_BDTRInitStructure;
TIM_BDTRInitStructure.TIM_OSSRState = TIM_OSSRState_Enable;
TIM_BDTRInitStructure.TIM_OSSIState = TIM_OSSIState_Enable;
TIM_BDTRInitStructure.TIM_LOCKLevel = TIM_LOCKLevel_1;
// 这里配置的死区时间为152ns,根据需要想配置多少就配置多少
TIM_BDTRInitStructure.TIM_DeadTime = 11;
TIM_BDTRInitStructure.TIM_Break = TIM_Break_Enable;
// 当BKIN引脚检测到高电平的时候,输出比较信号被禁止,就好像是刹车一样
TIM_BDTRInitStructure.TIM_BreakPolarity = TIM_BreakPolarity_High;
//设置了AOE,MOE 位会在发生下一个UEV事件时自动再次置1,也就是BKIN检测到高电平刹车之后\
然后变为低电平时,通道又自动恢复输出
TIM_BDTRInitStructure.TIM_AutomaticOutput = TIM_AutomaticOutput_Enable;
TIM_BDTRConfig(ADVANCE_TIM, &TIM_BDTRInitStructure);
// 使能计数器
TIM_Cmd(ADVANCE_TIM, ENABLE);
// 主输出使能,当使用的是通用定时器时,这句不需要
TIM_CtrlPWMOutputs(ADVANCE_TIM, ENABLE);
}
void ADVANCE_TIM_Init(void)
{
ADVANCE_TIM_GPIO_Config();
ADVANCE_TIM_Mode_Config();
}
main.c
#include "stm32f10x.h"
#include "bsp_usart.h"
#include "Advance_Tim.h"
int main(void)
{
ADVANCE_TIM_Init();
while(1)
{
}
}
感觉理论很复杂但其实代码非常简单
我把每句代码配置的寄存器位,总结了一下。
实验效果:
6.
6.高级定时器输入捕获-测量脉宽
其实前面的滤波与边沿检测与上面讲得的外部时钟模式1是共用一个电路的
输入捕获可以对输入的信号的上升沿,下降沿或者双边沿进行捕获,常用的有测量输入信号的脉宽和测量 PWM 输入信号的频率和占空比这两种。
输入捕获的大概的原理就是,当捕获到信号的跳变沿的时候,把计数器 CNT 的值锁存到捕获寄存器 CCR 中,把前后两次捕获到的 CCR 寄存器中的值相减,就可以算出脉宽或者频率。如果捕获的脉宽的时间长度超过你的捕获定时器的周期,就会发生溢出,这个我们需要做额外的处理。
当发生捕获事件时,相应的CCxIF标志(TIMx_SR寄存器)被置1,如果开放了中断或者DMA操作,则将产生中断或者DMA请求,写CCxIF=0可清除CCxIF,或读取存储在TIMx_CCRx寄存器中的捕获数据也可清除CCxIF。
如果发生捕获事件时CCxIF标志已经为高,那么重复捕获标CCxOF(TIMx_SR寄存器)被置1。写CCxOF=0可清除CCxOF。(相当于已经捕获到值但是并没有读出,所以下一次又捕获即重复捕获)
1)捕获通道
输入通道和捕获通道的区别,输入通道是用来输入信号的,捕获通道是用来捕获输入信号的通道,一个输入通道的信号可以同时输入给两个捕获通道后面的PWM捕获就用到了,这里脉宽捕获只需一个捕获通道即可
● 选择有效输入端:TIMx_CCR1必须连接到TI1输入,所以写入TIMx_CCMR1寄存器中的CC1S=01,只要CC1S不为’00’,通道被配置为输入,并且TIMx_CCR1寄存器变为只读。
● 根据输入信号的特点,配置输入滤波器为所需的带宽(即输入为TIx时,输入滤波器控制位是TIMx_CCMRx寄存器中的ICxF位)。假设输入信号在最多5个内部时钟周期的时间内抖动,我们须配置滤波器的带宽长于5个时钟周期;因此我们可以(以fDTS频率)连续采样8次,以确认在TI1上一次真实的边沿变换,即在TIMx_CCMR1寄存器中写入IC1F=0011,但我们这里不需要滤波
至于fDTS时钟频率是fCK_INT分频而来,前面外部时钟模式1的时候已经讲了
● 选择TI1通道的有效转换边沿,在TIMx_CCER寄存器中写入CC1P=0(上升沿)。
● 配置输入预分频器。在本例中,我们希望捕获发生在每一个有效的电平转换时刻(每一个边沿都检测),因此预分频器被禁止(写TIMx_CCMR1寄存器的IC1PS=00)。
● 设置TIMx_CCER寄存器的CC1E=1,允许捕获计数器的值到捕获寄存器中。
● 如果需要,通过设置TIMx_DIER寄存器中的CC1IE位允许相关中断请求,通过设置TIMx_DIER寄存器中的CC1DE位允许DMA请求。
当发生一个输入捕获时:
● 产生有效的电平转换时,计数器的值被传送到TIMx_CCR1寄存器。
● CC1IF标志被设置(中断标志)。当发生至少2个连续的捕获时,而CC1IF未曾被清除,CC1OF也被置1(重复捕获)。
● 如设置了CC1IE位,则会产生一个中断。
● 如设置了CC1DE位,则还会产生一个DMA请求
定时器输入捕获初始化结构体
实验目的:
将一个按键输出引脚作为输入捕获引脚,然后用按键来模拟一个高电平脉宽,没有按按键时默认是低电平,按键按下产生一个上升沿,按住按键高电平持续,然后松开按键产生一个下降沿,然后捕获这一段的高电平脉宽的时间
实验原理:
硬件连接
直接上代码
main.c
#include "stm32f10x.h"
#include "bsp_usart.h"
#include "General_Tim.h"
int main(void)
{
float time;
// TIM 计数器的驱动时钟
uint32_t TIM_PscCLK = 72000000 / (GENERAL_TIM_PSC+1);
/* 串口初始化 */
USART_Config();
/* 定时器初始化 */
GENERAL_TIM_Init();
printf ( "\r\n按下K1,测试K1按下的时间\r\n" );
while (1)
{
if(TIM_ICUserValueStructure.Capture_FinishFlag == 1)
{
// 计算高电平时间的计数器的值
time = TIM_ICUserValueStructure.Capture_Period * (GENERAL_TIM_PERIOD+1) +
(TIM_ICUserValueStructure.Capture_CcrValue+1);
// 打印高电平脉宽时间
printf ( "\r\n测得高电平脉宽时间:%f s\r\n",time/TIM_PscCLK);
TIM_ICUserValueStructure.Capture_FinishFlag = 0;
}
}
}
General_Tim.h
#ifndef __GENERALTIM_H
#define __GENERALTIM_H
#include "stm32f10x.h"
#define GENERAL_TIM TIM5
#define GENERAL_TIM_APBxClock_FUN RCC_APB1PeriphClockCmd
#define GENERAL_TIM_CLK RCC_APB1Periph_TIM5
#define GENERAL_TIM_PERIOD 0XFFFF
#define GENERAL_TIM_PSC (72-1)
#define GENERAL_TIM_IRQ TIM5_IRQn
#define GENERAL_TIM_IRQHandler TIM5_IRQHandler
#define GENERAL_TIM_CH1_GPIO_CLK RCC_APB2Periph_GPIOA
#define GENERAL_TIM_CH1_PORT GPIOA
#define GENERAL_TIM_CH1_PIN GPIO_Pin_0
#define GENERAL_TIM_CHANNEL_x TIM_Channel_1
// 定时器输入捕获用户自定义变量结构体声明
typedef struct
{
uint8_t Capture_FinishFlag; // 捕获结束标志位
uint8_t Capture_StartFlag; // 捕获开始标志位
float Capture_CcrValue; // 捕获寄存器的值
float Capture_Period; // 自动重装载寄存器更新标志
}TIM_ICUserValueTypeDef;
//声明结构体变量
extern TIM_ICUserValueTypeDef TIM_ICUserValueStructure;
void GENERAL_TIM_Init(void);
#endif /* __GENERALTIM_H */
General_Tim.c
#include "General_Tim.h"
//定义一个结构体变量
TIM_ICUserValueTypeDef TIM_ICUserValueStructure={0};
void GENERAL_TIM_NVIC_Config(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitStructure.NVIC_IRQChannel =TIM5_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority =1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority =1;
NVIC_InitStructure.NVIC_IRQChannelCmd =ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
void GENERAL_TIM_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
//初始化GPIO 浮空输入
RCC_APB2PeriphClockCmd(GENERAL_TIM_CH1_GPIO_CLK, ENABLE);
GPIO_InitStructure.GPIO_Pin = GENERAL_TIM_CH1_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GENERAL_TIM_CH1_PORT, &GPIO_InitStructure);
}
void GENERAL_TIM_Mode_Config(void)
{
// 开启定时器时钟,即内部时钟CK_INT=72M
GENERAL_TIM_APBxClock_FUN(GENERAL_TIM_CLK,ENABLE);
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
// 自动重装载寄存器的值,累计TIM_Period+1个频率后产生一个更新中断
TIM_TimeBaseStructure.TIM_Period=GENERAL_TIM_PERIOD;
// 驱动CNT计数器的时钟 = Fck_int/(psc+1)
TIM_TimeBaseStructure.TIM_Prescaler= GENERAL_TIM_PSC;
// 时钟分频因子 ,配置死区时间时需要用到
TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
// 计数器计数模式,设置为向上计数
TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;
// 重复计数器的值,没用到不用管
TIM_TimeBaseStructure.TIM_RepetitionCounter=0;
// 初始化定时器
TIM_TimeBaseInit(GENERAL_TIM, &TIM_TimeBaseStructure);
TIM_ICInitTypeDef TIM_ICInitStructure;
// 配置输入捕获的通道,需要根据具体的GPIO来配置
TIM_ICInitStructure.TIM_Channel = GENERAL_TIM_CHANNEL_x;
// 输入捕获信号的极性配置
TIM_ICInitStructure.TIM_ICPolarity = TIM_OCPolarity_High;
// 输入通道和捕获通道的映射关系,有直连和非直连两种
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
// 输入的需要被捕获的信号的分频系数
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
// 输入的需要被捕获的信号的滤波系数
TIM_ICInitStructure.TIM_ICFilter = 0;
// 定时器输入捕获初始化
TIM_ICInit(GENERAL_TIM, &TIM_ICInitStructure);
// 清除更新和捕获中断标志位
TIM_ClearFlag(GENERAL_TIM,TIM_FLAG_Update | TIM_FLAG_CC1);
// 开启更新和捕获中断
TIM_ITConfig(GENERAL_TIM,TIM_IT_CC1 | TIM_IT_Update,ENABLE);
//使能计数器
TIM_Cmd(GENERAL_TIM, ENABLE);
}
void GENERAL_TIM_Init(void)
{
GENERAL_TIM_NVIC_Config();
GENERAL_TIM_GPIO_Config();
GENERAL_TIM_Mode_Config();
}
中断服务函数
void GENERAL_TIM_IRQHandler(void)
{
//被捕获的信号的周期大于定时器的最长定时时间,产生更新中断,每产生一次\
更新中断Capture_Period结构体成员变量加1
if(TIM_GetITStatus(GENERAL_TIM,TIM_IT_Update)!=RESET)
{
TIM_ICUserValueStructure.Capture_Period++;
//清除更新中断标志位
TIM_ClearITPendingBit(GENERAL_TIM,TIM_IT_Update);
}
//上升沿捕获中断
if(TIM_GetITStatus(GENERAL_TIM,TIM_IT_CC1)!=RESET)
{
//第一次捕获
if(TIM_ICUserValueStructure.Capture_StartFlag == 0)
{
//计数器清0
TIM_SetCounter(GENERAL_TIM,0);
// 自动重装载寄存器更新标志清0
TIM_ICUserValueStructure.Capture_Period = 0;
// 存捕获比较寄存器的值的变量的值清0
TIM_ICUserValueStructure.Capture_CcrValue = 0;
//将第二次中断配置为下降沿中断
TIM_OC1PolarityConfig(GENERAL_TIM,TIM_OCPolarity_Low);
//开始捕获标志置1
TIM_ICUserValueStructure.Capture_StartFlag = 1;
}
//第二次下降沿捕获中断
else
{
// 获取捕获比较寄存器的值,若没有产生更新中断则这个值就是捕获到的高电平的时间的值
TIM_ICUserValueStructure.Capture_CcrValue=(float)TIM_GetCapture1(GENERAL_TIM);
// 当第二次捕获到下降沿之后,就把捕获边沿配置为上升沿,好开启新的一轮捕获
TIM_OC1PolarityConfig(GENERAL_TIM,TIM_OCPolarity_High);
// 开始捕获标志清0
TIM_ICUserValueStructure.Capture_StartFlag = 0;
// 捕获完成标志置1
TIM_ICUserValueStructure.Capture_FinishFlag = 1;
}
//清除捕获中断标志位
TIM_ClearITPendingBit(GENERAL_TIM,TIM_IT_CC1);
}
}
7.TIM—高级定时器-PWM输入捕获实验
PWM输入模式
该模式是输入捕获模式的一个特例,除下列区别外,操作与输入捕获模式相同,但是它可以更加方便的测量PWM信号的占空比和频率:
● 两个ICx信号被映射至同一个TIx输入。
也就是说一个输入信号被两个捕获通道进行捕获,其中一个捕获周期一个捕获占空比
● 这2个ICx信号为边沿有效,但是极性相反。
一个配置成上升沿检测,一个配置成下沿检测
● 其中一个TIxFP信号被作为触发输入信号,而从模式控制器被配置成复位模式。
例如,你需要测量输入到TI1上的PWM信号的长度(周期)(TIMx_CCR1寄存器)和占空比(TIMx_CCR2寄存器),具体步骤如下(取决于CK_INT的频率和预分频器的值)
● 选择TIMx_CCR1的有效输入:置TIMx_CCMR1寄存器的CC1S=01(选中TI1)。
● 选择TI1FP1的有效极性(用来捕获数据到TIMx_CCR1中和清除计数器):置CC1P=0(上升沿有效)。
● 选择TIMx_CCR2的有效输入:置TIMx_CCMR1寄存器的CC2S=10(选中TI1)。
● 选择TI1FP2的有效极性(捕获数据到TIMx_CCR2):置CC2P=1(下降沿有效)。
● 选择有效的触发输入信号:置TIMx_SMCR寄存器中的TS=101(选择TI1FP1)。
● 配置从模式控制器为复位模式:置TIMx_SMCR中的SMS=100。
● 使能捕获:置TIMx_CCER寄存器中CC1E=1且CC2E=1。
实验目的:
先让一个定时器输出比较输出一个PWM波,然后由另外一个定时器来捕获PWM波的占空比和周期
原理上面已经讲得一清二楚了
直接上代码:
输出的PWM波的代码就不上了
AdvanceTim.h
#ifndef __ADVANCETIM_H
#define __ADVANCETIM_H
#include "stm32f10x.h"
#define ADVANCE_TIM TIM1
#define ADVANCE_TIM_APBxClock_FUN RCC_APB2PeriphClockCmd
#define ADVANCE_TIM_CLK RCC_APB2Periph_TIM1
// 输入捕获能捕获到的最小的频率为 72M/( (ARR+1)*(PSC+1) )
#define ADVANCE_TIM_PERIOD (1000-1)
#define ADVANCE_TIM_PSC (72-1)
#define ADVANCE_TIM_CH1_GPIO_CLK RCC_APB2Periph_GPIOA
#define ADVANCE_TIM_CH1_PORT GPIOA
#define ADVANCE_TIM_CH1_PIN GPIO_Pin_8
#define ADVANCE_TIM_CHANNEL_x TIM_Channel_1
#define ADVANCE_TIM_IRQ TIM1_CC_IRQn
#define ADVANCE_TIM_IRQHandler TIM1_CC_IRQHandler
extern uint16_t IC1Value;
extern uint16_t IC2Value;
extern float DutyCycle;
extern float Frequency;
void ADVANCE_TIM_Init(void);
#endif /* __ADVANCETIM_H */
AdvanceTim.c
#include "AdvanceTim.h"
uint16_t IC1Value;
uint16_t IC2Value;
float DutyCycle;
float Frequency;
void ADVANCE_TIM_NVIC_Config(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitStructure.NVIC_IRQChannel =ADVANCE_TIM_IRQ;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority =1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority =1;
NVIC_InitStructure.NVIC_IRQChannelCmd =ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
void ADVANCE_TIM_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
//初始化GPIO 浮空输入
RCC_APB2PeriphClockCmd(ADVANCE_TIM_CH1_GPIO_CLK, ENABLE);
GPIO_InitStructure.GPIO_Pin = ADVANCE_TIM_CH1_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(ADVANCE_TIM_CH1_PORT, &GPIO_InitStructure);
}
void ADVANCE_TIM_Mode_Config(void)
{
// 开启定时器时钟,即内部时钟CK_INT=72M
ADVANCE_TIM_APBxClock_FUN(ADVANCE_TIM_CLK,ENABLE);
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
// 自动重装载寄存器的值,累计TIM_Period+1个频率后产生一个更新中断
TIM_TimeBaseStructure.TIM_Period=ADVANCE_TIM_PERIOD;
// 驱动CNT计数器的时钟 = Fck_int/(psc+1)
TIM_TimeBaseStructure.TIM_Prescaler= ADVANCE_TIM_PSC;
// 时钟分频因子 ,配置死区时间时需要用到
TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
// 计数器计数模式,设置为向上计数
TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;
// 重复计数器的值,没用到不用管
TIM_TimeBaseStructure.TIM_RepetitionCounter=0;
// 初始化定时器
TIM_TimeBaseInit(ADVANCE_TIM, &TIM_TimeBaseStructure);
TIM_ICInitTypeDef TIM_ICInitStructure;
// 配置输入捕获的通道,需要根据具体的GPIO来配置
TIM_ICInitStructure.TIM_Channel = ADVANCE_TIM_CHANNEL_x;
// 输入捕获信号的极性配置
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
// 输入通道和捕获通道的映射关系,有直连和非直连两种
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
// 输入的需要被捕获的信号的分频系数
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
// 输入的需要被捕获的信号的滤波系数
TIM_ICInitStructure.TIM_ICFilter = 0;
// 初始化PWM输入模式
TIM_PWMIConfig(ADVANCE_TIM, &TIM_ICInitStructure);
// 选择输入捕获的触发信号
TIM_SelectInputTrigger(ADVANCE_TIM, TIM_TS_TI1FP1);
// 选择从模式: 复位模式
// PWM输入模式时,从模式必须工作在复位模式,当捕获开始时,计数器CNT会被复位
TIM_SelectSlaveMode(ADVANCE_TIM, TIM_SlaveMode_Reset);
TIM_SelectMasterSlaveMode(ADVANCE_TIM,TIM_MasterSlaveMode_Enable);
// 清除更新和捕获中断标志位
TIM_ClearFlag(ADVANCE_TIM, TIM_FLAG_CC1);
// 开启更新和捕获中断
TIM_ITConfig(ADVANCE_TIM,TIM_IT_CC1,ENABLE);
//使能高级控制定时器,计数器开始计数
TIM_Cmd(ADVANCE_TIM, ENABLE);
}
void ADVANCE_TIM_Init(void)
{
ADVANCE_TIM_NVIC_Config();
ADVANCE_TIM_GPIO_Config();
ADVANCE_TIM_Mode_Config();
}
main.c
#include "stm32f10x.h"
#include "bsp_usart.h"
#include "GeneralTim.h"
#include "AdvanceTim.h"
int main(void)
{
//串口初始化
USART_Config();
//通用定时器初始化
GENERAL_TIM_Init();
//高级定时器初始化
ADVANCE_TIM_Init();
while (1)
{
}
}
中断服务函数
void ADVANCE_TIM_IRQHandler(void)
{
//清除中断标志位
TIM_ClearITPendingBit(ADVANCE_TIM,TIM_IT_CC1);
//获取输入捕获值
IC1Value =TIM_GetCapture1(ADVANCE_TIM);
IC2Value =TIM_GetCapture2(ADVANCE_TIM);
if( IC1Value !=0)
{
DutyCycle=(float)((IC2Value+1)*100/(IC1Value+1));
Frequency=72000000/(ADVANCE_TIM_PSC+1)/(IC1Value+1);
printf("\r\n 占空比:%0.2f%% 频率:%0.2fHZ \r\n",DutyCycle,Frequency);
}
}
注意:
因为只有TI1FP1和TI2FP2连到了从模式控制器(自动复位的计数器的值),所以PWM输入模式只能使用TIMx_CH1 TIMx_CH2信号。文章来源:https://www.toymoban.com/news/detail-805558.html
三.总结
高级定时器这一章内容非常多,而且比较难懂,我建议是多看看代码究竟操作的寄存器位是那些,而且多看看编程手册这是最详细最权威的,文章来源地址https://www.toymoban.com/news/detail-805558.html
到了这里,关于STM32高级定时器详解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!