笔记整理自B站UP主江科大自化协教程《STM32入门教程-2023持续更新中》,所用单片机也为教程推荐单片机。
大致内容
第一部分:定时器基本定时的功能,定时器每隔这个时间产生一个中断,来实现每隔一个固定时间执行一段程序的目的,比如要做一个时钟、秒表或者使用一些程序算法的时候都需要用到定时中断这个功能
第二部分:定时器输出比较的功能,最常见的用途就是产生PWM波形,用于驱动电机等设备
第三部分:定时器输入捕获的功能,使用输入buhuo这个模块来实现测量方波频率的例子
第四部分:定时器的编码器接口,使用编码器接口能够更加方便地读取正交编码器的输出波形,在编码电机测速中,应用广泛
使用定时器的外部时钟,可以提供一个更加精准的时钟来计时或者也可以把外部时钟当做一个计数器,用来统计引脚上电平翻转的次数。
输入捕获简介
IC(Input Capture)输入捕获,输入捕获模式下,当通道输入引脚出现指定电平跳变时(上升沿或者下降沿),当前CNT的值将被锁存到CCR中,可用于测量PWM波形的频率、占空比、脉冲间隔、电平持续时间等参数
每个高级定时器和通用定时器都拥有4个输入捕获通道
可配置为PWMI模式,同时测量频率和占空比
可配合主从触发模式,实现硬件全自动测量
输出比较对比输入捕获
输出比较:引脚是输出端口,根据CNT和CCR的大小关系来执行输出动作
输入捕获:引脚是输入端口,接收到输入信号,执行CNT锁存到CCR的动作
异或门执行逻辑
当三个输入引脚的任何一个有电平翻转时,输出引脚就产生一次电平翻转
异或门还是为三相无刷电机服务的,无刷电机有3个霍尔传感器检测转子的位置,可以根据转子的位置进行换相,在三个通道接上无刷电机的霍尔传感器,定时器作为无刷电机的接口定时器驱动换相电路工作。
一个通道灵活切换两个引脚,两个通道同时捕获一个引脚
输入捕获基本结构
经过预分频之后的时钟频率就是驱动CNT的标准频率fc(标准频率=72M/预分频系数),当TI1FP1出现上升沿之后,CNT的当前计数值转运到CCR1里,同时触发源选择,选中TI1FP1为触发信号,从模式选择复位操作,这样TI1FP1的上升沿也会通过上面这一路去触发CNT清零(这里肯定是先捕获再清零)。
看左上角的图:信号出现一个上升沿,CCR1=CNT,把CNT的值转运到CCR1中,这是输入捕获自动执行的,然后CNT=0,清零计数器,这是从模式自动执行的,然后在一个周期之内,CNT在标准时钟的驱动下,不断自增,CNT从上升沿开始,从0开始计数,一直++,直到下一次上升沿来临,...
综上分析,这个电路工作时,CCR1始终保持为最新一个周期的计数值,这个计数值就是测周法图中的计次N,运用公式fc/N得到频率即可
注意事项
CNT的值是有上限的,ARR一般设置最大65535,CNT最大也只能计65535个数,如果信号频率太低,CNT计数值可能会溢出
从模式的触发源选择,只有TI1FP1和TI2FP2,没有TI3和TI4信号,所以如果想使用从模式自动清零CNT,就只能用通道1和通道2,对于通道3和通道4就只能开启捕获中断,在中断里手动清零了,不过这样,程序就会处于频繁中断的状态,比较消耗软件资源。
PWMI基本结构
使用两个通道同时捕获一个引脚,可以同时测量周期和占空比
TI1FP1配置上升沿触发,触发捕获和清零CNT,正常捕获周期;TI1FP2配置为下降沿触发,通过交叉通道,去触发通道2的捕获单元
看左上角的图:CCR1就是一整个周期的计数值,CCR2就是高电平期间的计数值,CCR2/CCR1就是占空比。
输入捕获模式测频率代码讲解
参考上一节PWM驱动LED呼吸灯的代码PWM.c
需要在此基础上再加一个函数——调节PWM的频率
计数器更新频率=PWM频率:Freq = CK_PSC / (PSC + 1) / (ARR + 1)
如果调节ARR还会影响到占空比,所以这里固定ARR,只调节PSC
TIM_PrescalerConfig——单独写入PSC的函数
设置PWM频率的主体代码
void PWM_SetPrescaler(uint16_t Prescaler)
{
/*
第三个参数TIM_PSCReloadMode重装模式
TIM_PSCReloadMode_Update:预分频器在更新事件重装
TIM_PSCReloadMode_Immediate:预分频器立即重装
即写入的值是立刻生效还是在更新事件生效
*/
TIM_PrescalerConfig(TIM2, Prescaler, TIM_PSCReloadMode_Immediate);
}
输入捕获代码部分
按照上图来配置
第一步——RCC开启时钟,GPIO和TIM
第二步——GPIO初始化,把GPIO配置成输入模式
第三步——配置时基单元,让CNT计数器在内部时钟的驱动下自增运行
第四步——配置输入捕获单元(包括滤波器、极性、直连通道or交叉通道、分频器)
第五步——选择从模式的触发源(触发源选择位TI1FP1)
第六步——选择触发之后执行的操作,执行Reset操作
第七步——开启定时器
看一下相关的库函数stm32f10x_tim.h文件
TIM_ICInit——配置输入捕获单元,单一配置一个通道
TIM_PWMIConfig——配置输入捕获单元类似TIM_ICInit,但可以快速配置两个通道(PWMI模式)
TIM_ICStructInit——给输入捕获结构体赋初始值
TIM_SelectInputTrigger——选择输入触发源TRGI(从模式),本节用TI1FP1
TIM_SelectOutputTrigger——选择输出触发源TRGO(主模式)
TIM_SelectSlaveMode——选择从模式
TIM_SetIC1Prescaler、TIM_SetIC2Prescaler、TIM_SetIC3Prescaler、TIM_SetIC4Prescaler——分别单独配置通道1/2/3/4的分频器
TIM_GetCapture1、TIM_GetCapture2、TIM_GetCapture3、TIM_GetCapture4——分别读取4个通道的CCR
在输出比较模式下,CCR是只写的,要用TIM_SetCompare1、TIM_SetCompare2、TIM_SetCompare3、TIM_SetCompare4;输入捕获模式下,CCR是只读的,要用TIM_GetCapture1、TIM_GetCapture2、TIM_GetCapture3、TIM_GetCapture4读出。
第一步——开启RCC时钟
//第一步开启RCC时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
第二步——配置GPIO
//配置GPIO
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //上拉输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
第三步——配置时基单元
//选择内部时钟
TIM_InternalClockConfig(TIM3);
//初始化时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数的模式
TIM_TimeBaseInitStructure.TIM_Period = 65536 - 1; //ARR自动重装器的值 16位的计数器可以满量程计数
TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1; //PSC预分频器的值 标准频率 = 72M / 72 = 1M
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器的值
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);
第四步——初始化输入捕获单元
//初始化输入捕获单元,计划使用TIM的CH1
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_ICInit(TIM3, &TIM_ICInitStructure);
第五步——配置TRGI触发源为TI1FP1
//配置TRGI触发源为TI1FP1
TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);
第六步——配置从模式为Reset
//配置从模式为RESET
TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);
第七步——开启定时器
//开启定时器
TIM_Cmd(TIM3, ENABLE);
源代码
#include "stm32f10x.h" // Device header
void IC_Init(void)
{
//第一步开启RCC时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //输入捕获用定时器3,输出定时器2
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
//配置GPIO
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //上拉输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//选择内部时钟
TIM_InternalClockConfig(TIM3);
//初始化时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数的模式
TIM_TimeBaseInitStructure.TIM_Period = 65536 - 1; //ARR自动重装器的值 16位的计数器可以满量程计数
TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1; //PSC预分频器的值 标准频率 = 72M / 72 = 1M
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器的值
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);
//初始化输入捕获单元,计划使用TIM的CH1
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_ICInit(TIM3, &TIM_ICInitStructure);
//配置TRGI触发源为TI1FP1
TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);
//配置从模式为RESET
TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);
//开启定时器
TIM_Cmd(TIM3, ENABLE);
}
uint32_t IC_GetFreq(void)
{
/*
根据公式fx=fc/N,标准频率fc = 72M/预分频系数 = 72M / 72 = 1M = 1000000,N就是CCR的值
*/
return 1000000 / (TIM_GetCapture1(TIM3) + 1);
}
PWMI模式测频率和占空比代码讲解
按照上图来配置
第一步——RCC开启时钟
第二步——配置GPIO
第三步——配置时基单元
这三步和上面一样
第四步——初始化输入捕获
配置成两个通道同时捕获同一个引脚的模式
第一种配置方法
//初始化输入捕获单元,计划使用TIM的CH1
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_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的CH1
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_ICInit(TIM3, &TIM_ICInitStructure);
//上述代码可直接简化成一个函数完成配置,在函数中会自动把剩下的一个通道初始化成相反的配置
TIM_PWMIConfig(TIM3, &TIM_ICInitStructure);
通道1、直连、上升沿——通道2、交叉、下降沿
第五步——配置TRGI触发源为TI1FP1
第六步——配置从模式为Reset
第七步——开启定时器
这三步和之前一样
//配置TRGI触发源为TI1FP1
TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);
//配置从模式为RESET
TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);
//开启定时器
TIM_Cmd(TIM3, ENABLE);
源代码
#include "stm32f10x.h" // Device header
void IC_Init(void)
{
//第一步开启RCC时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //输入捕获用定时器3,输出定时器2
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
//配置GPIO
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //上拉输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//选择内部时钟
TIM_InternalClockConfig(TIM3);
//初始化时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数的模式
TIM_TimeBaseInitStructure.TIM_Period = 65536 - 1; //ARR自动重装器的值 16位的计数器可以满量程计数
TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1; //PSC预分频器的值 标准频率 = 72M / 72 = 1M
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器的值
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);
//初始化输入捕获单元,计划使用TIM的CH1
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_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_PWMIConfig(TIM3, &TIM_ICInitStructure);
//配置TRGI触发源为TI1FP1
TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);
//配置从模式为RESET
TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);
//开启定时器
TIM_Cmd(TIM3, ENABLE);
}
uint32_t IC_GetFreq(void)
{
/*
根据公式fx=fc/N,标准频率fc = 72M/预分频系数 = 72M / 72 = 1M = 1000000,N就是CCR的值
*/
return 1000000 / (TIM_GetCapture1(TIM3) + 1);
}
uint32_t IC_GetDuty(void)
{
//高电平的计数值存在CCR2中,整个周期的计数值存在CCR1中
return (TIM_GetCapture2(TIM3) + 1) * 100 / (TIM_GetCapture1(TIM3) + 1);
}
感谢抽出宝贵时间阅读的各位小读者们,创作不易,如果感觉有帮助的话,帮忙点个赞再走吧!你们的支持是我创作的动力,希望能带给大家更多优质的文章。