# STM32-高精度的PWM输出与输入捕获 **Repository Path**: Yancl0416/pwm-output-and-input-capture ## Basic Information - **Project Name**: STM32-高精度的PWM输出与输入捕获 - **Description**: 使用正点原子STM32F1精英版,实现高精度的PWM输出与高精度的输入捕获,适用于低频段0.3Hz-1000Hz,如需要输出高频段或者检测高频段,则需要配置预分频为72-1,计算的数值,带入1e6 - **Primary Language**: Unknown - **License**: GPL-3.0 - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2024-12-30 - **Last Updated**: 2024-12-31 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # PWM输出与输入捕获 ## 实验平台介绍 * ==STM32F103ZET6,72M主频,正点原子F1精英版== 使用正点原子STM32F1精英版,实现高精度的PWM输出与高精度的输入捕获,适用于低频段0.3Hz-1000Hz,如需要输出高频段或者检测高频段,则需要配置预分频为72-1,计算的数值,带入1e6 ## PWM输出 PWM输出`TIM1`的配置参数有个预分频系数`Prescaler(PSC)`,配置成(`72-1`) `Counter Period (ARR)`配置成(`1000-1`),默认就是72M/72/1000 = 1KHz * 使用引脚`PA8` 初始化`PWM` ```c HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1); // PA8 初始化代码 ``` PWM输出 ```c void PWM_Set(void) { if (uwTick - param.PWM_Tick < 1000) // 1s return; param.PWM_Tick = uwTick; HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1); // PA8 param.Set_PA8_Freq = 1000; // TIM1_CH1 = 1KHz// 设置固定频率 // 设置自动重装载值,重装载值 = 1M / Param.Set_PA6_Freq ,之前是设置72-1的预分频 __HAL_TIM_SetAutoreload(&htim1, 1e6 / param.Set_PA8_Freq - 1); param.Set_PA8_Duty = 50.0f; __HAL_TIM_SetCompare(&htim1, TIM_CHANNEL_1, (1e6 / param.Set_PA8_Freq - 1) * param.Set_PA8_Duty / 100 + 1); // 设置高电平时间多长,就是设置占空比 } ``` ## PWM输入捕获 PWM输入捕获`TIM2`使用两个通道`CH1、CH2`,也配置`72-1`的预分频 使用两个通道来检测频率和占空比 * 使用引脚`PA0`来检测, `PA8`-`PA0`引脚相互连接 ```c void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { if (htim->Instance == TIM2) // 如果是TIM2 { if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1) // 中断消息来源, 选择直接输入的通道 { Frequency.ccr1_value_a = __HAL_TIM_GET_COUNTER(&htim2) + 1; // Frequency.ccr1_value_a = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); // 将计时的数值读取到变量里 直接通道 Frequency.ccr1_value_b = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_2); // 间接模式读取到的数据 // if (Frequency.ccr1_value_b <= 30) Frequency.ccr1_value_b = 0; if (Frequency.ccr1_value_a > Frequency.ccr1_value_b) { Frequency.Duty_1 = (Frequency.ccr1_value_b / 1.0 / Frequency.ccr1_value_a) * 100; // 占空比 __HAL_TIM_SetCounter(htim, 0); // 将计时值清零 Frequency.Value1 = (36000000) / (Frequency.ccr1_value_a+1); // 计算频率,frq1 = (72M/72)/(ccrl_val1 + 1) } else { Frequency.Duty_1 = (Frequency.ccr1_value_a / 1.0 / Frequency.ccr1_value_b) * 100; // 占空比 __HAL_TIM_SetCounter(htim, 0); // 将计时值清零 Frequency.Value1 = (36000000) / (Frequency.ccr1_value_b+1); // 计算频率,frq1 = (72M/72)/(ccrl_val1 + 1) } HAL_TIM_IC_Start(htim, TIM_CHANNEL_1); // 计算完频率,重新开启定时器3通道1 HAL_TIM_IC_Start(htim, TIM_CHANNEL_2); // 计算完频率,重新开启定时器3通道2 } } } ``` ## 问题&debug * 这样做能实现输入捕获,效果也非常好!能实现比较高频率的PWM输出和输入捕获。 * 但是有个问题,就是如果输入的频率太低,比如1Hz,那么定时器计数器溢出,导致计数器值特别大,导致计算频率时,除数特别大,导致频率计算错误!!! ==这里是debug时候才发现的,设置频率为16Hz,查看寄存器TIM1->ARR=62xxx(六万两千多),设置频率为15Hz,查看寄存器TIM1->ARR=xx(这里的xx表示只有几十,不大于100)== 所以这里不是输入捕获的问题,而是PWM输出的问题,PWM输出频率太低,导致ARR寄存器值特别大,导致计数器溢出,导致计数器值特别大,导致频率计算错误!!! ## 解决方法 1. 在CubeMX修改PWM的预分频值(`TIM1->Prescaler`),改成7200-1 2. 修改输入捕获的预分频值(`TIM2->Prescaler`),改成7200-1 ==这样的配置能输出0.3-1000Hz的PWM,输入捕获的频率也能达到0.3Hz-1000Hz!!!== >修改思路: https://www.cnblogs.com/zxr-blog/p/17966424#_label0 ### 修改PWM输出函数 ```c void PWM_Set(void) { if (uwTick - param.PWM_Tick < 1000) // 1s return; param.PWM_Tick = uwTick; HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1); // PA8 // 设置固定频率 // param.Set_PA8_Freq = 20; // TIM1_CH1 = 20Hz 这里注释掉是因为在debug窗口里修改了 // 设置自动重装载值,重装载值 = 1M / Param.Set_PA6_Freq ,之前是设置80-1的预分频 __HAL_TIM_SetAutoreload(&htim1, 10000 / param.Set_PA8_Freq - 1); // 这里修改了,是10000(72M/7200),之前是1e6(72M/72) // param.Set_PA8_Duty = 50.0f; // 这里注释掉是因为在debug窗口里修改了 __HAL_TIM_SetCompare(&htim1, TIM_CHANNEL_1, (10000 / param.Set_PA8_Freq - 1) * param.Set_PA8_Duty / 100 + 1); // 这里修改了,是10000(72M/7200),之前是1e6(72M/72) } ``` ### 修改PWM输入捕获函数 >参考文章: https://www.cnblogs.com/zxr-blog/p/17966424#_label0 ```c void PWM_calculation(void) { if (capture_flag == 1) /* 捕获完成 */ { diff_value1_last = diff_value1; diff_value2_last = diff_value2; if (capture_value[2] >= capture_value[0]) /* 计时没有溢出 */ { diff_value1 = capture_value[2] - capture_value[0]; /* 取差值 */ } else /* 计时有溢出 */ { diff_value1=(0xffffffff+1)+capture_value[2]-capture_value[0]; /* 取差值 */ } if (capture_value[1] >= capture_value[0]) /* 计时没有溢出 */ { diff_value2=capture_value[1]-capture_value[0]; /* 取差值 */ } else /* 计时有溢出 */ { diff_value2=(0xffffffff+1)+capture_value[1]-capture_value[0]; /* 取差值 */ } if ((diff_value1 > 65535) || (diff_value2 > 65535)) //如果差值溢出了,就舍弃本次的值,复用上一次的值 { diff_value1 = diff_value1_last; //做了溢出判断,如果溢出了,就复用上一次的值 diff_value2 = diff_value2_last; } Frequency.Value1 = 10000.0f / diff_value1; Frequency.Duty_1 = diff_value2 * 100.0f / diff_value1; capture_flag = 0; //标志位清零,为下一次捕获做准备 HAL_TIM_IC_Start_IT(&htim2,TIM_CHANNEL_1); /*开启下一轮捕获*/ } } //回调函数 void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { if (htim==&htim2) { switch (capture_state) { case 0: { capture_value[0]=HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_1); /*读取捕获开始时间*/ __HAL_TIM_SET_CAPTUREPOLARITY(htim,TIM_CHANNEL_1,TIM_INPUTCHANNELPOLARITY_FALLING); /*切换为下降沿捕获*/ capture_state=1; /*标志完成第一次捕获*/ } break; case 1: { capture_value[1]=HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_1); /*读取第二次捕获时间*/ __HAL_TIM_SET_CAPTUREPOLARITY(htim,TIM_CHANNEL_1,TIM_INPUTCHANNELPOLARITY_RISING); /*切换为上升沿捕获*/ capture_state=2; /*标志完成第二次捕获*/ } break; case 2: { capture_value[2]=HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_1); /*读取捕获结束时间*/ HAL_TIM_IC_Stop_IT(htim,TIM_CHANNEL_1); /*停止捕获*/ capture_state=0; /*标志完成第三次捕获,重新置0,准备下一轮捕获*/ capture_flag=1; /*标志捕获完成*/ } break; default: // printf("running error!\n"); break; } } } ``` ### .h文件 ```c #ifndef __CONTROL_H #define __CONTROL_H #include "stm32f1xx_hal.h" #include "tim.h" #include "lcd.h" #include /* 定义结构体 */ typedef struct { uint32_t LCD_Tick; uint32_t PWM_Tick; // PWM定时 函数减速 float Set_PA8_Freq; // float Set_PA8_Duty; uint8_t flag_led; // LED控制 } Param_TypeDef; extern Param_TypeDef param; typedef struct { unsigned int ccr1_value_a; unsigned int ccr1_value_b; float Value1; // 频率1 float Duty_1; // 占空比1 } Frequency_TypeDef; extern Frequency_TypeDef Frequency; void PWM_Set(void); void LCD_Show_proc(void); void PWM_calculation(void); #endif ``` ### .c文件 ```c #include "control.h" // TIM1 PWM // TIM2 IC // TIM6 Base Param_TypeDef param; Frequency_TypeDef Frequency; // 频率 结构体 uint8_t capture_state = 0, capture_flag = 0; uint32_t capture_value[3]; uint32_t diff_value1, diff_value2; uint32_t diff_value1_last, diff_value2_last; void PWM_Set(void) { if (uwTick - param.PWM_Tick < 1000) // 1s return; param.PWM_Tick = uwTick; HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1); // PA8 // 设置固定频率 // param.Set_PA8_Freq = 20; // TIM1_CH1 = 1KHz // 设置自动重装载值,重装载值 = 1M / Param.Set_PA6_Freq ,之前是设置80-1的预分频 __HAL_TIM_SetAutoreload(&htim1, 10000 / param.Set_PA8_Freq - 1); // param.Set_PA8_Duty = 50.0f; __HAL_TIM_SetCompare(&htim1, TIM_CHANNEL_1, (10000 / param.Set_PA8_Freq - 1) * param.Set_PA8_Duty / 100 + 1); // 设置高电平时间多长,就是设置占空比 } void PWM_calculation(void) { if (capture_flag == 1) /* 捕获完成 */ { diff_value1_last = diff_value1; diff_value2_last = diff_value2; if (capture_value[2] >= capture_value[0]) /* 计时没有溢出 */ { diff_value1 = capture_value[2] - capture_value[0]; /* 取差值 */ } else /* 计时有溢出 */ { diff_value1=(0xffffffff+1)+capture_value[2]-capture_value[0]; /* 取差值 */ } if (capture_value[1] >= capture_value[0]) /* 计时没有溢出 */ { diff_value2=capture_value[1]-capture_value[0]; /* 取差值 */ } else /* 计时有溢出 */ { diff_value2=(0xffffffff+1)+capture_value[1]-capture_value[0]; /* 取差值 */ } if ((diff_value1 > 65535) || (diff_value2 > 65535)) //如果差值溢出了,就舍弃本次的值,复用上一次的值 { diff_value1 = diff_value1_last; //做了溢出判断,如果溢出了,就复用上一次的值 diff_value2 = diff_value2_last; } Frequency.Value1 = 10000.0f / diff_value1; Frequency.Duty_1 = diff_value2 * 100.0f / diff_value1; capture_flag = 0; //标志位清零,为下一次捕获做准备 HAL_TIM_IC_Start_IT(&htim2,TIM_CHANNEL_1); /*开启下一轮捕获*/ } } void LCD_Show_proc(void) { if (uwTick - param.LCD_Tick < 1000) // 250ms return; param.LCD_Tick = uwTick; char tempStr[30]; sprintf(tempStr, "Set PWM Fre:%.1fHz ", param.Set_PA8_Freq); lcd_show_string(30, 4 * 20, 200, 16, 16, tempStr, RED); sprintf(tempStr, "Set PWM Duty:%.2f%% ", param.Set_PA8_Duty); lcd_show_string(30, 5 * 20, 200, 16, 16, tempStr, RED); sprintf(tempStr, "Get PWM Fre:%.1fHz ", Frequency.Value1); lcd_show_string(30, 7 * 20, 200, 16, 16, tempStr, DARKBLUE); sprintf(tempStr, "Get PWM Duty:%.2f%% ", Frequency.Duty_1); lcd_show_string(30, 8 * 20, 200, 16, 16, tempStr, DARKBLUE); } // 定时器中断回调函数 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { static uint16_t LED_cnt; if (htim->Instance == TIM6) // 如果是定时器2的中断 - 1ms一次 { if (LED_cnt++ >= 1000) // 1000ms - 2S LED闪烁 { LED_cnt = 0; param.flag_led = !param.flag_led; } } } /* USER CODE BEGIN 4 */ //回调函数 void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { if (htim==&htim2) { switch (capture_state) { case 0: { capture_value[0]=HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_1); /*读取捕获开始时间*/ __HAL_TIM_SET_CAPTUREPOLARITY(htim,TIM_CHANNEL_1,TIM_INPUTCHANNELPOLARITY_FALLING); /*切换为下降沿捕获*/ capture_state=1; /*标志完成第一次捕获*/ } break; case 1: { capture_value[1]=HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_1); /*读取第二次捕获时间*/ __HAL_TIM_SET_CAPTUREPOLARITY(htim,TIM_CHANNEL_1,TIM_INPUTCHANNELPOLARITY_RISING); /*切换为上升沿捕获*/ capture_state=2; /*标志完成第二次捕获*/ } break; case 2: { capture_value[2]=HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_1); /*读取捕获结束时间*/ HAL_TIM_IC_Stop_IT(htim,TIM_CHANNEL_1); /*停止捕获*/ capture_state=0; /*标志完成第三次捕获,重新置0,准备下一轮捕获*/ capture_flag=1; /*标志捕获完成*/ } break; default: // printf("running error!\n"); break; } } } /* USER CODE END 4 */ ``` ## 增加需求 需要输出两路频率和占空比不同的信号,并且需要输入捕获两路PWM信号,计算两路信号的频率和占空比,并显示在LCD上。 解决方案 * 增加两个定时器,一个用于捕获一路PWM信号,一个用于捕获另一路PWM信号。 * TIM3(PA6引脚)用于第二路PWM输出,TIM4(PD12)用于第二路PWM输入捕获。 要设置定时器TIM4的NVIC,否则不能进入TIM4的中断,导致无法捕获到PWM信号。