STM32 输入捕获模式测量频率和占空比 HAL库
前两种方案分别实现了PWM输入模式测量频率+占空比(2个TIM通道,占用中断资源)、和输入捕获模式测量频率(1个TIM通道,不占用中断资源,但无法测量占空比)
STM32 PWM输入模式测量频率和占空比 HAL库
STM32 输入捕获模式测频率 HAL库
接下来记录一种新的方案,使用测周法实现频率和占空比的同时测量,该方案的优势在于只占用一个TIM通道,并且可以在较高主频下实现1Hz-60kHz的频率测量。虽然该方案需要占用中断资源,且需要在中断中进行运算,但从实际测试来看,得益于较高的运行频率(168MHz),这种方案对程序的运行影响极小。
下面使用cubemx进行配置(所使用设备为F407,选择开发板、开启外部时钟和SW调试接口、Project Manager设置相关操作略过)
TIM工作模式配置
使用内部时钟,并将通道1和通道3设置为输入捕获模式
这里将PSC设置为最小,计数值设置为最大65535,以充分发挥硬件性能得到最大的频率测量范围。两个通道都设置为双边触发以同时捕获上升沿和下降沿
不要忘了开启定时器的对应中断
生成代码并编程
网上相关的教程很多,实现的方法也并不完全相同,但思路基本都是想通的,只要理解了整个思路,然后根据自己的任务需求进行调整就可以。这里讲一下大概的思路,然后给出代码。
首先是输入捕获回调函数HAL_TIM_IC_CaptureCallback(),这个函数在每次捕获上升/下降沿时都会触发,在触发时我们先进行判断(哪个定时器?哪个通道?上升沿还是下降沿?),然后就是测周法最关键的部分:计时,即计算高电平维持的时间以及低电平维持的时间。
此时__HAL_TIM_GET_COUNTER就起到了时钟的作用,只需要在每次触发输入捕获时用定时器当前计数值减去上一次触发输入捕获时的计数值PWM3_input.count_H3 – PWM3_input.last_count_H3即可得到高/低电平的持续时间。
但是,当频率较小时,有可能发生计数器已经记满了65535,而下一个上升/下降沿任然没有到来的情况。此时我们需要利用计时器重置时触发的HAL_TIM_PeriodElapsedCallback函数来记录定时器的溢出,并在计算时间时加上65535*溢出的次数即可。(注意!两个通道的溢出次数要单独记录!以免两个通道同时进行输入捕获时重置对方的溢出次数!)
此外,经过实际测试,加上中值滤波处理将使得结果更加稳定。关于滤波器,所采用的整体的思路是:把需要滤波的数据先保存至数组储存,在需要使用/采样时再进行滤波。这样即避免了重复计算浪费CPU资源或影响下一次采样,也便于控制变量的更新频率
输入捕获回调函数:
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if(htim == &htim3)
{
switch(htim->Channel)
{
// TIM3 CHANNEL_1 H3
case HAL_TIM_ACTIVE_CHANNEL_1:
{
static uint8_t count_low = 0, count_high = 0;
// The time point of this trigger and the last trigger
PWM3_input.last_count_H3 = PWM3_input.count_H3;
PWM3_input.count_H3 = __HAL_TIM_GET_COUNTER(&htim3);
// Determine rising edge or falling edge
if(65536*PWM3_input.overflow_H3 + PWM3_input.count_H3 > PWM3_input.last_count_H3)
{
if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_4) == GPIO_PIN_SET)
{
PWM3_input.low_H3[count_low] = 65536*PWM3_input.overflow_H3 + PWM3_input.count_H3 - PWM3_input.last_count_H3;
count_low++;
if(count_low > 9) count_low = 0;
}
else if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_4) == GPIO_PIN_RESET)
{
PWM3_input.high_H3[count_high] = 65536*PWM3_input.overflow_H3 + PWM3_input.count_H3 - PWM3_input.last_count_H3;
count_high++;
if(count_high > 9) count_high = 0;
}
}
// cleare the overflow value
PWM3_input.overflow_H3 = 0;
break;
}
// TIM3 CHANNEL_2 J3
case HAL_TIM_ACTIVE_CHANNEL_2:
{
static uint8_t count_low = 0, count_high = 0;
PWM3_input.last_count_J3 = PWM3_input.count_J3;
PWM3_input.count_J3 = __HAL_TIM_GET_COUNTER(&htim3);
if(65536*PWM3_input.overflow_J3 + PWM3_input.count_J3 > PWM3_input.last_count_J3)
{
if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_5) == GPIO_PIN_SET)
{
PWM3_input.low_J3[count_low] = 65536*PWM3_input.overflow_J3 + PWM3_input.count_J3 - PWM3_input.last_count_J3;
count_low++;
if(count_low > 9) count_low = 0;
}
else if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_5) == GPIO_PIN_RESET)
{
PWM3_input.high_J3[count_high] = 65536*PWM3_input.overflow_J3 + PWM3_input.count_J3 - PWM3_input.last_count_J3;
count_high++;
if(count_high > 9) count_high = 0;
}
}
PWM3_input.overflow_J3 = 0;
break;
}
default: break;
}
}
}
溢出处理(定时器更新中断):
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim == &htim3)
{
PWM3_input.overflow_H3 ++;
PWM3_input.overflow_J3 ++;
}
}
频率和占空比计算:
void PWM3_calculation(void)
{
static uint32_t fre_h3[10], duty_h3[10], fre_j3[10], duty_j3[10];
static uint8_t i = 0;
if(PWM3_input.flag == 1)
{
for(i=0; i<10; i++)
{
fre_h3[i] = 84000000/1/(PWM3_input.low_H3[i] + PWM3_input.high_H3[i]);
duty_h3[i] = PWM3_input.high_H3[i]*100/(PWM3_input.low_H3[i] + PWM3_input.high_H3[i]);
fre_j3[i] = 84000000/1/(PWM3_input.low_J3[i] + PWM3_input.high_J3[i]);
duty_j3[i] = PWM3_input.high_J3[i]*100/(PWM3_input.low_J3[i] + PWM3_input.high_J3[i]);
}
// calculate Fre and duty
PWM3_input.Fre_H3 = Median_filter(fre_h3, 10);
PWM3_input.Duty_H3 = Median_filter(duty_h3, 10);
PWM3_input.Fre_J3 = Median_filter(fre_j3, 10);
PWM3_input.Duty_J3 = Median_filter(duty_j3, 10);
// refresh the flag
PWM3_input.flag = 0;
}
}
其中flag用于控制采样频率,便于我们控制其更新周期与PID计算周期或其他变量更新的周期一致。
中值滤波:
uint16_t Median_filter(uint32_t *array, uint8_t length)
{
static uint16_t i=0, j=0, temp=0;
for(i=0; i<length; i++)
{
for(j=0; j+1<length-i; j++)
{
if(array[j]>array[j+1])
{
temp = array[j];
array[j] = array[j+1];
array[j+1] = temp;
}
}
}
return array[length/2];
}
初始化:
HAL_TIM_Base_Start_IT(&htim3);
HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_1);HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_2);
总结及注意事项
该方法测量频率在10Hz-30KHz范围内误差小于0.2%,在1HZ-60KHz范围内误差小于1%。
在频率小于100Hz时,理论上测周法的采样周期大于10ms。但通过flag的设置,可以强行在设置的周期内获取采样值,以保持项目中不同传感器数据更新周期的一致。但此时获取的采样值只是上一时刻未更新的数值,并不是真实的。但是但是!其实实际应用时(作为发动机转速传感器)频率输入低于100Hz的情况几乎很难遇到,而且0.2%也是足够的(很多场景下1%都是可以接受的),所以这种方案是完全能够应对的。
作者:电工小王(全国可飞)