05-PWM捕获与输出
本文介绍了基于蓝桥杯嵌入式组国信长天板子的频率捕获与PWM输出实现方法。在捕获模式中,详细说明了两种配置方式:一种是直接/间接捕获模式,通过PB4和PA15引脚捕获旋钮频率;另一种是中断捕获模式,需开启NVIC。同时提供了PWM输出配置方法,使用PA7引脚输出1kHz、50%占空比的PWM波,并给出了频率和占空比修改函数。文中包含完整的驱动代码实现,包括捕获值计算、LCD显示以及按键控制PWM参数
(本文基于蓝桥杯嵌入式组国信长天板子编写)
剩余工程配置可参考:蓝桥杯嵌入式组工程创建举例
首先介绍的是捕获模式
根据原理图可以知道PB4跟PA15分别连接J9,J10来捕获旋钮的输出频率,所以我们可以做出如下引脚配置

以下介绍两种捕获的设置模式
第一种不用打开NVIC,将主通道(我这里是第一通道)设置为直接捕获,次通道(我这里是第二通道)设置为间接捕获,然后将PSC设置为80-1,因为配置时钟时是配置成80M,频率 = 系统时钟 / (PSC + 1) / (ARR + 1),后续只需要修改ARR就能改变频率,可以直接用1e6 / (ARR+ 1)。 占空比 = 间接通道 。(还需要将通道一设置为上升沿触发,通道二设置为下降沿触发,截图漏了)
配置完成之后编写对应驱动程序
首先需要自行打开定时器IC捕获,该方法使用到了通道一与通道二,所以都需要打开
HAL_TIM_IC_Start(&htim2,TIM_CHANNEL_1);
HAL_TIM_IC_Start(&htim2,TIM_CHANNEL_2);
HAL_TIM_IC_Start(&htim3,TIM_CHANNEL_1);
HAL_TIM_IC_Start(&htim3,TIM_CHANNEL_2);

以下是读取计算函数以及屏幕显示函数
uint32_t R39_freq,R40_freq;
uint8_t R39_Duty,R40_Duty;
void TIM_process(void){
R40_freq = 1e6 / (HAL_TIM_ReadCapturedValue(&htim2,TIM_CHANNEL_1) + 1);
R40_Duty = (HAL_TIM_ReadCapturedValue(&htim2,TIM_CHANNEL_2) + 1) * 100 / (HAL_TIM_ReadCapturedValue(&htim2,TIM_CHANNEL_1) + 1);
R39_freq = 1e6 / (HAL_TIM_ReadCapturedValue(&htim3,TIM_CHANNEL_1) + 1);
R39_Duty = (HAL_TIM_ReadCapturedValue(&htim3,TIM_CHANNEL_2) + 1) * 100 / (HAL_TIM_ReadCapturedValue(&htim3,TIM_CHANNEL_1) + 1);
}
uint8_t lcd_buff[30];
void lcd_process(){
if(lcd_mode == 1){
sprintf((char *)lcd_buff,"Time1:%d ",time1);
LCD_DisplayStringLine(Line0,lcd_buff);
sprintf((char *)lcd_buff,"Time2:%d ",time2);
LCD_DisplayStringLine(Line1,lcd_buff);
sprintf((char *)lcd_buff,"Time3:%d ",time3);
LCD_DisplayStringLine(Line2,lcd_buff);
sprintf((char *)lcd_buff,"Time4:%d ",time4);
LCD_DisplayStringLine(Line3,lcd_buff);
}else if(lcd_mode == 0){
}
sprintf((char *)lcd_buff,"r37_val:%.2f ",r37_val);
LCD_DisplayStringLine(Line4,lcd_buff);
sprintf((char *)lcd_buff,"r38_val:%.2f ",r38_val);
LCD_DisplayStringLine(Line5,lcd_buff);
sprintf((char *)lcd_buff,"R39_freq:%d ",R39_freq);
LCD_DisplayStringLine(Line6,lcd_buff);
sprintf((char *)lcd_buff,"R40_freq:%d ",R40_freq);
LCD_DisplayStringLine(Line8,lcd_buff);
sprintf((char *)lcd_buff,"R39_Duty:%d%% ",R39_Duty);
LCD_DisplayStringLine(Line7,lcd_buff);
sprintf((char *)lcd_buff,"R40_Duty:%d%% ",R40_Duty);
LCD_DisplayStringLine(Line9,lcd_buff);
}
把所有函数都放在while(1)里轮询。

最终效果如下,频率输出值在700-30000范围内则是正常了

第二种则是需要打开NVIC,但只需要配置一个通道即可,默认上升沿触发即可

不同点在于启动时调用HAL_TIM_IC_Start_IT(&htim2,TIM_CHANNEL_1)函数而不是HAL_TIM_IC_Start(&htim2,TIM_CHANNEL_1),然后需要在中断里进行处理

以下为中断函数以及计算函数
注意点在于,浮点计算不能在中断里面进行,就是代码里面注释掉的部分,亲测如果放在了中断里面,如果同时开两个,两个都扭到最大时单片机会死机!!!展示效果是跟上图一致就不再次上传
uint32_t freq_text2 = 0,freq_text3;
uint8_t tim2_up,tim3_up;
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim){
if(htim == &htim2){
freq_text2 = __HAL_TIM_GetCounter(&htim2);
__HAL_TIM_SetCounter(&htim2,0);
tim2_up = 1;
// if(freq_text2 != 0) R40_freq = 1e6 / freq_text2;
}
if(htim == &htim3){
freq_text3 = __HAL_TIM_GetCounter(&htim3);
__HAL_TIM_SetCounter(&htim3,0);
tim3_up = 1;
// if(freq_text3 != 0) R39_freq = 1e6 / freq_text3;
}
}
void Main_Handle(){
if(tim2_up == 1){
if(freq_text2 != 0){
R40_freq = 1e6 / freq_text2;
}
R40_Duty = TIM2->CCR1 / TIM2->ARR * 100;
tim2_up = 0;
}
if(tim3_up == 1){
if(freq_text3 != 0) R39_freq = 1e6 / freq_text3;
R40_Duty = TIM3->CCR1 / TIM3->ARR * 100;
tim3_up = 0;
}
}
然后是PWM输出介绍
在比赛题目里会要求不同的引脚输出,我这里使用PA7展示。
如图所示将PSC设置为79,ARR设置为999,计算得到的频率为1khz,同时设置Pulse为500,占空比为500/(999 + 1) = 50%

生成工程后根据上面所展示捕获模式,将J10跳线帽拔开用杜邦线短接PA7观察

可以观测到频率与占空比与计算结果一致,说明配置正确。
接下来分享修改输出频率与占空比的函数,初始频率根据题目要求设定,每次传入更新后的值
uint32_t current_freq = 1000; // 初始频率 1kHz,存储当前值
uint32_t current_duty = 10; // 初始占空比 10%,存储当前值
void PWM_UpDate_Set(uint32_t freq,uint8_t duty){
if(freq == 0) return;
//计算新的arr
uint32_t new_arr = (1e6 / freq) - 1;
//计算新的ccr,保持占空比
uint32_t new_pulse = duty * (new_arr + 1) / 100;
//写入寄存器
__HAL_TIM_SET_AUTORELOAD(&htim17,new_arr);
__HAL_TIM_SET_COMPARE(&htim17,TIM_CHANNEL_1,new_pulse);
}
关于该函数的使用可以参考以下,我设置了四个按键,KB1为+1khz,KB2为-1khz,KB3为+10%,KB4为-10%。按键具体代码参考03-按键驱动代码分享

最终完成修改

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐
所有评论(0)