1. PWM测量
1.1 基本流程
在介绍PWM输入模式之前,我们先脱离开单片机,先介绍下PWM频率和占空比的概念,以及测量的思想。
-
频率:单位时间内脉冲信号的次数。
-
占空比:脉冲信号中高电平时间与周期时间的比例。
对于频率的测量,只需要获取到一个周期(高电平持续时间+低电平持续时间)的时间即可。占空比呢,需要我们对高电平时间进行测量,再与周期作比即可。
既然我们要对时间进行较为精确的测量,肯定要用到定时器了,我们可以简单设计下定时器测量的流程:
- 配置好定时器参数
- 在第一个上升沿复位计数器,开始计数
- 在下降沿记录当前计数值(cnt1)
- 在第二个上升沿时记录当前计数值(cnt2)
- 按以下方式计算频率和占空比即可
高电平计数 = cnt1 + 1
周期计数 = cnt2 + 1
频率 = 定时器时钟频率 / (定时器分频数 + 1) / 周期计数
占空比 = 高电平计数 / 周期计数 * 100
注:“+1”是因为计数器从0开始计数,所以经过的计数周期为计数值加1。
这里参考air001,举个实际栗子:
定时器时钟频率:48MHz
分频数:47
重载值:65535
假设下降沿计数cnt1为4999,第二个上升沿计数cnt2为9999,那么该PWM的高电平计数就是4999+1 = 5000,周期计数是9999+1 = 10000,进而有
细心的你会发现,如果计数溢出怎么办,对于该定时器配置,在单个重载周期(开启到溢出)内,最大的cnt2为65535,那么可以测得最小的频率是:
如果测量的PWM信号频率高于该值,不会出现溢出问题(在获取到cnt2之后,复位计数了),低于该频率必然会出现溢出问题,如果知道待测PWM信号频率的大致范围,可以通过调整分频值和重载值,避免溢出。聪明的你也能想到解决溢出的方法,那就是每次都记录下周期内溢出的次数,最后计算的时候加上若干次溢出的计数即可,之后的文章中,就先不考虑溢出的问题了~
1.2 输入捕获方式
到了这里,可以进入到实操环节了,有了上面的铺垫,使用一般输入捕获就可以解决问题啦。步骤就像下面这样:
- 依然是配置定时器~
- 配置输入捕获(IC)中断,边沿设置为上升沿
- 开启定时器及中断
- 中断函数内
- 清中断标志
- 第一次上升沿触发
- 计数器复位
- 边沿设置为下降沿
- 下降沿触发
- 记录当前计数值
- 边沿设置为上升沿
- 第二次上升沿触发
- 记录当前计数值
- 可以计算频率和占空比了
本文重点是后面的PWM输入模式,这里就不详细展开叙述了
1.3 PWM输入模式
输入捕获模式完全可以实现我们的PWM测量功能,但是中断中处理的内容有点多,并且还需要额外的变量标记当前捕获状态,那么还有什么其他方式吗?
PWM输入模式就是专门为此设计的,算是输入捕获的一个特例。
1.3.1 芯片手册
以下是air001手册中的描述(TIM3):
简单来说,就是将输入信号分给两个通道,一个负责上升沿(测频率),一个负责下降沿(测占空比),并且从模式设置为复位,这样每次上升沿都会触发复位,也就是每个周期都重新计数啦,是不是又和前面说的一样了~
下面是其中一些定义,还可参考TIM3架构图:
Icx->Input capture x 输入捕获通道
Tix->Timer input x 定时器输入通道(输入引脚)
TixFP->Timer Input x Filtered Pin 定时器输入的滤波信号
CC->Capture/Compare 捕获/比较
1.3.2 主要寄存器
这里主要涉及到的寄存器有如下几个:
⬇️TIM3_CCRx(TIM3捕获/比较寄存器x):输入情况下包含了ICx事件传输的计数值
💡该寄存器保存了ICx通道边沿触发时的计数值,无须手动去记录计数值,直接读该寄存器即可
⬇️TIM3_CCMR1(TIM3捕获/比较模式寄存器1):主要是配置ICx和TIx之间的映射(CCxS位),以及其他ICx配置
💡将IC1(负责周期)绑定到TI1定时器输入:CC1S = 01
IC2(负责占空比)绑定到TI1定时器输入:CC2S = 10
⬇️TIM3_CCER(TIM3捕获/比较使能寄存器1):ICx的使能和极性设定
💡IC1上升沿:CC1NP/CC1P = 00
IC2下降沿:CC1NP/CC1P = 00
⬇️TIM3_SMCR(TIM3从模式控制寄存器):从模式设定
💡从模式触发输入选择为TI1FP1: TS=101
从模式设置为复位模式:SMS = 100
1.3.3 配置流程
清楚了主要的寄存器,实际配置起来就很流畅啦,这里直接从代码看流程~
1. 定时器配置
默认内部时钟(系统时钟调到了48MHz),重载值65536-1,分频值48-1,上升计数
(这里没写引脚的配置,TIM3对应的有PA6,复用引脚即可)
// .h
#define INPUT_TIM TIM3
#define INPUT_TIM_CH_freq TIM_CHANNEL_1
#define INPUT_TIM_CH_duty TIM_CHANNEL_2
#define INPUT_TIM_ARR ((uint32_t)(65536 - 1))
#define INPUT_TIM_PSC ((uint32_t)(48 - 1))
// .c
TIM_HandleTypeDef TIM_Input_Handle;
TIM_Input_Handle.Instance = INPUT_TIM;
//不用指定channel,后面IC1配置了直接连接
//TIM_Input_Handle.Channel = INPUT_TIM_CH_freq;
TIM_Input_Handle.Init.Period = INPUT_TIM_ARR;
TIM_Input_Handle.Init.Prescaler = INPUT_TIM_PSC;
TIM_Input_Handle.Init.CounterMode = TIM_COUNTERMODE_UP;
TIM_Input_Handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
TIM_Input_Handle.Init.RepetitionCounter = 0;
HAL_TIM_IC_Init(&TIM_Input_Handle);
2. 输入捕获IC1,IC2配置
IC1上升沿,直接连接(TI1),不分频,不滤波(根据实际调整)
IC2下降沿,间接连接(TI1),不分频,不滤波(根据实际调整)
// .c
TIM_IC_InitTypeDef TIM_IC_Config;
TIM_IC_Config.ICPolarity = TIM_ICPOLARITY_RISING;
TIM_IC_Config.ICSelection = TIM_ICSELECTION_DIRECTTI;
TIM_IC_Config.ICPrescaler = TIM_ICPSC_DIV1;
TIM_IC_Config.ICFilter = 0;
// 实际就是对CCER和CCMR寄存器进行操作
HAL_TIM_IC_ConfigChannel(&TIM_Input_Handle, &TIM_IC_Config,
INPUT_TIM_CH_freq);
TIM_IC_Config.ICPolarity = TIM_ICPOLARITY_FALLING;
TIM_IC_Config.ICSelection = TIM_ICSELECTION_INDIRECTTI;
HAL_TIM_IC_ConfigChannel(&TIM_Input_Handle, &TIM_IC_Config,
INPUT_TIM_CH_duty);
3. 从模式配置
从模式复位模式,触发选择TI1FP1,上升沿,不分频,不滤波(根据实际调整)
// .c
TIM_SlaveConfigTypeDef TIM_Slave_Config;
TIM_Slave_Config.SlaveMode = TIM_SLAVEMODE_RESET;
TIM_Slave_Config.InputTrigger = TIM_TS_TI1FP1;
TIM_Slave_Config.TriggerPolarity = TIM_ICPOLARITY_RISING;
TIM_Slave_Config.TriggerPrescaler = TIM_ICPSC_DIV1;
TIM_Slave_Config.TriggerFilter = 0;
// 对smcr寄存器的操作
HAL_TIM_SlaveConfigSynchro(&TIM_Input_Handle, &TIM_Slave_Config);
4. 中断配置
使能TIM3中断(其他板子可能有单独的IC中断名),启动IC(函数内部包含了定时器启动)
// .h
#define INPUT_TIM_IRQ TIM3_IRQn
// .c
HAL_NVIC_SetPriority(INPUT_TIM_IRQ, 0, 0);
HAL_NVIC_EnableIRQ(INPUT_TIM_IRQ);
HAL_TIM_IC_Start_IT(&TIM_Input_Handle, INPUT_TIM_CH_freq);
HAL_TIM_IC_Start_IT(&TIM_Input_Handle, INPUT_TIM_CH_duty);
5. 中断函数
函数内读取CCR1和CCR2,并计算当前频率和占空比
// air001xx_it.c
void TIM3_IRQHandler(void){
HAL_TIM_IRQHandler(&TIM_Input_Handle);
}
// .c
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) {
// 读CCR1和CCR2寄存器
if (htim->Instance == INPUT_TIM && htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1) {
uint32_t period = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1) + 1;
uint32_t pulse = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_2) + 1;
freq = (float)SystemCoreClock / (TIM_Input_Handle.Instance->PSC + 1) / period;
duty = (float)pulse / period * 100;
}
}
至此freq和duty检测完毕~
1.3.4 测试一下
之前用TIM1生成了一个带死区刹车的互补pwm,正好用来测试下,配置如下:
// .h
#define OUTPUT_TIM TIM1
#define OUTPUT_TIM_ARR ((uint32_t)(10000 - 1))
#define OUTPUT_TIM_PSC ((uint32_t)(48 - 1))
// .c
TIM_Output_Handle.Instance = OUTPUT_TIM;
TIM_Output_Handle.Init.CounterMode = TIM_COUNTERMODE_UP;
TIM_Output_Handle.Init.Period = OUTPUT_TIM_ARR;
TIM_Output_Handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
TIM_Output_Handle.Init.Prescaler = OUTPUT_TIM_PSC;
HAL_TIM_PWM_Init(&TIM_Output_Handle);
可知周期为100Hz,设定比较值为5000,占空比正好50%,将TIM1输出解到TIM3的CH1输入引脚即可,通过串口查看一下
纳尼,为什么CCR1没问题,但是CCR2差一些呢,原因是忘了还有死区时间,关了死区就没问题啦~
2. 代码
tim3.h
#ifndef __TIM3_H__
#define __TIM3_H__
#ifdef __cplusplus
extern "C" {
#endif
#include "main.h"
#define INPUT_TIM TIM3
#define INPUT_TIM_IRQ TIM3_IRQn
#define INPUT_TIM_CLK_ENABLE() __HAL_RCC_TIM3_CLK_ENABLE()
#define INPUT_TIM_CH_freq TIM_CHANNEL_1
#define INPUT_TIM_CH_duty TIM_CHANNEL_2
#define INPUT_TIM_CHx_CLK() __HAL_RCC_GPIOA_CLK_ENABLE()
#define INPUT_TIM_CHx_PORT GPIOA
#define INPUT_TIM_CHx_PIN GPIO_PIN_6
#define INPUT_TIM_CHx_PIN_AF GPIO_AF1_TIM3
#define INPUT_TIM_ARR ((uint32_t)(65536 - 1))
#define INPUT_TIM_PSC ((uint32_t)(48 - 1))
extern TIM_HandleTypeDef TIM_Input_Handle;
extern TIM_IC_InitTypeDef TIM_IC_Config;
extern uint16_t freq;
extern uint16_t duty;
void TIM_Input_Init(void);
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim);
#ifdef __cplusplus
}
#endif
#endif
tim3.c
#include "tim3.h"
TIM_HandleTypeDef TIM_Input_Handle;
TIM_IC_InitTypeDef TIM_IC_Config;
uint16_t freq = 0;
uint16_t duty = 0;
void TIM_Input_Init(void) {
TIMx_GPIO_Input_Configuration();
TIMx_Input_NVIC_Configuration();
TIMx_Input_Configuration();
}
static void TIMx_GPIO_Input_Configuration(void) {
INPUT_TIM_CHx_CLK();
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.Pin = INPUT_TIM_CHx_PIN;
GPIO_InitStruct.Alternate = INPUT_TIM_CHx_PIN_AF;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(INPUT_TIM_CHx_PORT, &GPIO_InitStruct);
}
static void TIMx_Input_NVIC_Configuration(void) {
HAL_NVIC_SetPriority(INPUT_TIM_IRQ, 0, 0);
HAL_NVIC_EnableIRQ(INPUT_TIM_IRQ);
}
static void TIMx_Input_Configuration(void) {
INPUT_TIM_CLK_ENABLE();
TIM_Input_Handle.Instance = INPUT_TIM;
// TIM_Input_Handle.Channel = INPUT_TIM_CH_freq;
TIM_Input_Handle.Init.Period = INPUT_TIM_ARR;
TIM_Input_Handle.Init.Prescaler = INPUT_TIM_PSC;
TIM_Input_Handle.Init.CounterMode = TIM_COUNTERMODE_UP;
TIM_Input_Handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
TIM_Input_Handle.Init.RepetitionCounter = 0;
HAL_TIM_IC_Init(&TIM_Input_Handle);
TIM_IC_Config.ICPolarity = TIM_ICPOLARITY_RISING;
TIM_IC_Config.ICSelection = TIM_ICSELECTION_DIRECTTI;
TIM_IC_Config.ICPrescaler = TIM_ICPSC_DIV1;
TIM_IC_Config.ICFilter = 0;
HAL_TIM_IC_ConfigChannel(&TIM_Input_Handle, &TIM_IC_Config,
INPUT_TIM_CH_freq);
TIM_IC_Config.ICPolarity = TIM_ICPOLARITY_FALLING;
TIM_IC_Config.ICSelection = TIM_ICSELECTION_INDIRECTTI;
HAL_TIM_IC_ConfigChannel(&TIM_Input_Handle, &TIM_IC_Config,
INPUT_TIM_CH_duty);
TIM_SlaveConfigTypeDef TIM_Slave_Config;
TIM_Slave_Config.SlaveMode = TIM_SLAVEMODE_RESET;
TIM_Slave_Config.InputTrigger = TIM_TS_TI1FP1;
TIM_Slave_Config.TriggerPolarity = TIM_ICPOLARITY_RISING;
TIM_Slave_Config.TriggerPrescaler = TIM_ICPSC_DIV1;
TIM_Slave_Config.TriggerFilter = 0;
HAL_TIM_SlaveConfigSynchro(&TIM_Input_Handle, &TIM_Slave_Config);
HAL_TIM_IC_Start_IT(&TIM_Input_Handle, INPUT_TIM_CH_freq);
HAL_TIM_IC_Start_IT(&TIM_Input_Handle, INPUT_TIM_CH_duty);
}
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) {
if (htim->Instance == INPUT_TIM && htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1) {
// 通道1捕获周期(上升沿)
uint32_t period = HAL_TIM_ReadCapturedValue(htim, INPUT_TIM_CH_freq) + 1;
// 通道2捕获占空比(下降沿)
uint32_t pulse = HAL_TIM_ReadCapturedValue(htim, INPUT_TIM_CH_duty) + 1;
freq = (float)SystemCoreClock / (TIM_Input_Handle.Instance->PSC + 1) / period;
duty = (float)pulse / period * 100;
}
}
实际项目最好还是把配置的函数写成这样,方便调试:
if (HAL_TIM_IC_Start_IT(&TimHandle, TIM_CHANNEL_1) != HAL_OK)
{
Error_Handler();
}