在PWM方波中,通过调整高电平的占比,并以更快的速度切换高低电平,可以用数字信号模拟输出模拟信号。
本文中,将会使用PWM波控制LED小灯和电机作为示例。
输出比较模式
在输出比较模式下,原有的捕获寄存器变为比较寄存器,用于输入的GPIO口也变为输出脉冲信号。此模式下,我们需要先向比较寄存器中写入一个数值,然后根据选择模式输出不同的信号:
- 冻结模式(Frozen):输出通道的GPIO口会无视比较结果,保留旧的输出状态;
(何意味) - 强制有效(Forced Active):输出通道的GPIO口会无视比较结果,持续输出高电平;
(何意味x2) - 强制无效(Forced Inactive):输出通道的GPIO口会无视比较结果,持续输出低电平;
(何意味x3) - 匹配时有效(Active Level on Match):仅当计数器值和比较寄存器值相等时,输出有效电平,但不会自动重设为无效电平;
- 匹配时无效(Inactive Level on Match):仅当计数器值和比较寄存器值相等时,输出无效电平,但不会自动重设为有效电平;
- 匹配时翻转(Toggle on Match):仅当计数器值和比较寄存器值相等时,翻转当前的输出电平。
PWM原理
在PWM方波中,通过调整高电平的占比,并以非常快的速度切换高低电平,可以用数字信号模拟输出模拟信号。
例如,PWM波占空比为60%时,由于高低电平的切换非常迅速,在宏观上就可以认为输出了1.98V的电压。
此外,通过调整PWM波的占空比,频率等属性,还可以控制电机的转速,舵机的角度。
PWM模式
在默认的向上计数模式下:
- PWM模式1:当计数器值小于比较寄存器的值时,输出有效电平;当计数器的值大于或等于比较寄存器的值时,输出无效电平;
- PWM模式2:与PWM模式1相反,当计数器值小于比较寄存器的值时,输出有效电平;当计数器的值大于或等于比较寄存器的值时,输出无效电平。
上述的有效电平和无效电平,在从比较寄存器发出之后,还会进入输出控制器,默认地,有效电平对应高电平,无效电平对应低电平,我们也可以配置输出控制器使其翻转,使有效电平对应低电平,无效电平对应高电平。
TIP补充: 计数器除了向上计数模式之外,还有向下计数模式和中央对齐模式。
在向下计数模式中,则与向上计数模式相反:
- PWM模式1:当计数器值大于比较寄存器的值时,输出无效电平;当计数器的值小于或等于比较寄存器的值时,输出有效电平;
- PWM模式2:当计数器值大于比较寄存器的值时,输出有效电平;当计数器的值小于或等于比较寄存器的值时,输出无效电平。
中央对齐模式则是前半段向上计数模式,后半段向下计数模式。
这两种模式不常被使用,无需过多考虑。
芯片配置
在CubeMX中,左侧Timers点开任意定时器,先勾选Internal Clock,在右侧的Channel1到Channel4中可以选择不同的输出模式。例如Output Compare CH1表示在CH1处开启输出比较模式,PWM Generater CH1则表示在CH1开启PWM波输出;
例如选择TIM3的通道,并启用CH1、CH2、CH3的PWM输出,可以看到右侧的PA6,PA7,PB0已经分别被启用为TIM3_CH1,TIM3_CH2,TIM3_CH3,我们在这三个GPIO口接入LED灯,即可利用PWM波做出呼吸灯的效果。
在底下的Configuration栏目,可以进行更多的设置,例如此处将Prescaler设置为72-1,将Counter Period设置为100-1,则每秒计数72M/72=1M次,每秒输出1000000/100=10000个PWM波。继续向下则是PWM的设置:
- Mode:选择PWM模式1或者PWM模式2;
- Pulse:比较寄存器初始的值;
- Output compare preload:比较寄存器的影子寄存器机制,与自动重装载寄存器的影子寄存器机制相同;
- Fast Mode:是否启用高速模式;
- CH Polarity:极性翻转。
代码配置
呼吸灯
先启用CH1、CH2、CH3通道的PWM波输出:
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_2);HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_3);由上述的原理可知,PWM输出的占空比取决于比较寄存器,因此我们只需要修改比较寄存器中的值,即可修改输出PWM波的占空比,进而修改最终LED的亮度。
用于写入比较寄存器值的宏函数:
__HAL_TIM_SET_COMPARE(__HANDLE__, __CHANNEL__, __COMPARE__);其中:
__HANDLE__:要操作的定时器的句柄,即我们启用的&htim3;__CHANNEL__:要操作的通道,例如TIM_CHANNEL_1和TIM_CHANNEL_4;__COMPARE__:设置比较寄存器的值。
在死循环中写入对小灯的控制程序:
while (1) { for(int i=0;i<=100;i++){ if(i<=25){ __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, -2*i+50);//B __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_2, -2*i+100);//G __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_3, 2*i+50);//R }else if((i>25)&&(i<=50)){ __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, 2*i-50);//B __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_2, -2*i+100);//G __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_3, -2*i+150);//R }else if((i>50)&&(i<=75)){ __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, 2*i-50);//B __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_2, 2*i-100);//G __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_3, -2*i+150);//R }else if((i>75)&&(i<=100)){ __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, -2*i+250);//B __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_2, 2*i-100);//G __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_3, 2*i-150);//R } HAL_Delay(20); } }烧录程序后,可以看见小灯以一种十分炫酷的方式在闪烁。
控制电机
电机的输入电流通常达到一个很高的值,与芯片输出信号的小电流相差甚远,因此要驱动电机,我们还需要一块电机驱动,他能够基于单片机发送的小电流的信号,向电机输出大电流,避免单片机直接输出大电流。这里以drv8833电机驱动为例。
drv8833电机驱动使用两个输入通道IN1和IN2,因此我们需要为其配置两个PWM输出通道,其余的事务交给现成的电机驱动即可。
配合下一篇文章的旋转编码器,我们可以制作一个通过旋钮控制速度,按下旋钮直接使速度归零并自然减速的电机。
先引入需要的驱动和宏定义:
#include "drv8833.h"#include "oled.h"#include <stdint.h>#include <stdio.h>
#define SPEED_MAX 20#define SPEED_MIN 0#define SPEED_MID (SPEED_MAX+SPEED_MIN)/2#define SPEED_INITIAL 10-1#define BASE 32767
uint8_t speed=SPEED_INITIAL;然后在main中写入:
HAL_Delay(100);OLED_Init();DRV8833_Init();HAL_TIM_Encoder_Start(&htim1, TIM_CHANNEL_ALL);__HAL_TIM_SET_COUNTER(&htim1, BASE+1);while (1){ /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ uint16_t counter=__HAL_TIM_GET_COUNTER(&htim1); int16_t diff=(int16_t)(counter-BASE); if(diff){ int8_t newspeed=speed+diff; if(newspeed<0){ speed=SPEED_MIN; } else if(newspeed>20){ speed=SPEED_MAX; } else{ speed=newspeed; } if(speed>SPEED_MID){ DRV8833_Forward((speed-SPEED_MID)*10); } else if(speed<SPEED_MID){ DRV8833_Backward((SPEED_MID-speed)*10); } else if(speed==SPEED_MID){ DRV8833_Coast(); } __HAL_TIM_SET_COUNTER(&htim1, BASE); char message[20]=""; sprintf(message, "speed:%d",speed-SPEED_MID); OLED_NewFrame(); OLED_PrintString(0, 0, message, &font16x16, OLED_COLOR_NORMAL); OLED_ShowFrame(); }}在中断服务中,写入按下按钮使速度归零的程序:
#include "oled.h"#include "drv8833.h"#include <stdint.h>#include <stdio.h>
#define SPEED_MAX 20#define SPEED_MIN 0#define SPEED_MID (SPEED_MAX+SPEED_MIN)/2
extern uint8_t speed;
void EXTI15_10_IRQHandler(void){ /* USER CODE BEGIN EXTI15_10_IRQn 0 */ if(HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin)==GPIO_PIN_RESET){ HAL_Delay(10); if(HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin)==GPIO_PIN_RESET){ speed=SPEED_MID; DRV8833_Coast(); char message[20]=""; sprintf(message, "speed:%d",speed-SPEED_MID); OLED_NewFrame(); OLED_PrintString(0, 0, message, &font16x16, OLED_COLOR_NORMAL); OLED_ShowFrame(); } } /* USER CODE END EXTI15_10_IRQn 0 */ HAL_GPIO_EXTI_IRQHandler(KEY_Pin); /* USER CODE BEGIN EXTI15_10_IRQn 1 */
/* USER CODE END EXTI15_10_IRQn 1 */}烧录程序后,旋转旋钮即可对电机速度实现控制,并控制其正转与反转,以及停止。
总结
相关函数
HAL_TIM_PWM_Start(TIM_HandleTypeDef *htim, uint32_t Channel);//启用PWM波输出__HAL_TIM_SET_COMPARE(__HANDLE__, __CHANNEL__, __COMPARE__);//设置比较寄存器的值PWM模式
通过调整高电平的占比,实现用数字信号模拟输出模拟信号。
部分信息可能已经过时





