一、简介
PWM调制占空比技术是我们常用的一种用于调节输出信号平均功率的技术。它通过改变脉冲的宽度来控制输出到负载的能量,而保持脉冲频率恒定或在一定范围内变化,常见用于电机的输出功率控制,音频处理控制等。
二、原理
假设一个方波周期高电平与低电平各占整个周期的一半,我们就说占空比为50%,在快速的脉冲开关我们就能够控制设备输出的模拟电压,假设我们控制的设备输出是5V,周期是20ms,现在我们使用单片机定时器PWM模式把高电平占空比调制到25%也就是周期为20ms,周期内高电平时间为5ms,周期内低电平时间为15ms;高电平与周期的比值就是占空比 5/20=25% 最终调制得到的设备输出电压为 5V * 25% =1.25V模拟输出电压。
三、案例演示
3.1 简介:
我们以控制电机输出电压为例,当占空比100%也就是周期全是高电平时,电机输出的速度是全速,反之占空比越小速度则越慢,因为输出控制电压=设备输出电压*占空比。
3.2 电机参数:
笔者使用手上的一款24直流减速电机带编码器,理论满速为1400转/分钟左右,编码反馈一圈11个信号,减速比4.4。
3.3 驱动器:
想要驱动此电机,肯定是要借助驱动器的,这里我们选择一个比较便宜的驱动器。
四、代码示例
按照驱动器说明我们搭建好电路即可编写代码,注意上面的驱动器说明了频率范围,我们需要按照范围来进行配置。
4.1 定时器与编码器配置:
TIM_HandleTypeDef TIM_TimeBaseStructure; //定时器结构体声明
void Motor_TIM_Init(void); //声明函数
TIM_HandleTypeDef g_timx_chy_handle; /* 定时器x句柄 */
TIM_Encoder_InitTypeDef g_timx_encoder_chy_handle; /* 定时器编码器句柄 */
void Motor_BM_TIM_Init(void);
/*NVIC中断向量控制器函数*/
static void TIMx_BM_NVIC_Configuration(void)
{
HAL_NVIC_SetPriority(TIM8_UP_TIM13_IRQn, 0, 1);
HAL_NVIC_EnableIRQ(TIM8_UP_TIM13_IRQn);
}
/*这是一个用于初始化GPIO的函数*/
void Motor_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure; //GPIO初始化结构体声明
MOTOR_TBPUL_CLK();
GPIO_InitStructure.Mode = GPIO_MODE_AF_PP; //配置模式为复用推挽输出
GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_VERY_HIGH; //引脚速率
GPIO_InitStructure.Alternate = MOTOR_PUL_GPIO_AF; //设置为复用
GPIO_InitStructure.Pull =GPIO_PULLUP; //内部上拉
GPIO_InitStructure.Pin = MOTOR_TBPUL_PIN; //配置脉冲引脚
HAL_GPIO_Init(MOTOR_TBPUL_PORT, &GPIO_InitStructure); //初始化
Motor_TIM_Init(); //定时器初始化
Motor_BM_TIM_Init(); //编码器初始化
MOTOR_CHA_CLK(); //编码器反馈IO时钟
MOTOR_CHB_CLK(); //编码器反馈IO时钟
/*使能编码器复用通道引脚*/
GPIO_InitStructure.Pin=MOTOR_CHA_PIN|MOTOR_CHB_PIN; //配置反馈线
GPIO_InitStructure.Mode=GPIO_MODE_AF_PP; //配置模式
GPIO_InitStructure.Pull=GPIO_NOPULL; //配置无上下拉
GPIO_InitStructure.Alternate=MOTOR_CHB_GPIO_AF;
GPIO_InitStructure.Speed=GPIO_SPEED_FREQ_VERY_HIGH; //配置翻转速度
HAL_GPIO_Init(MOTOR_CHB_PORT, &GPIO_InitStructure); //初始化结构体
}
/*这是一个用于初始化PWM输出定时器的函数*/
void Motor_TIM_Init(void)
{
TIM_OC_InitTypeDef TIM_OCInitStructure;
GENERAL_TIM_CLK_ENABLE(); //打开定时器时钟
TIM_TimeBaseStructure.Instance=GENERAL_TIM; //配置定时器号
TIM_TimeBaseStructure.Init.Period=100-1; //ARR重装载值
TIM_TimeBaseStructure.Init.Prescaler=180-1; //定时器分频器 1M
TIM_TimeBaseStructure.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1; //频率为1分频
TIM_TimeBaseStructure.Init.CounterMode=TIM_COUNTERMODE_UP; //向上计数
HAL_TIM_PWM_Init(&TIM_TimeBaseStructure); //初始化结构体
TIM_OCInitStructure.OCMode=TIM_OCMODE_PWM1; //PWM模式配置这里为PWM1
TIM_OCInitStructure.Pulse=0; //默认占空比为0
TIM_OCInitStructure.OCFastMode = TIM_OCFAST_DISABLE;
/*当定时器计数值小于CCR1_Val时为高电平*/
TIM_OCInitStructure.OCPolarity = TIM_OCPOLARITY_HIGH;
/*配置PWM通道*/
HAL_TIM_PWM_ConfigChannel(&TIM_TimeBaseStructure, &TIM_OCInitStructure, MOTOR_PUL_CHANNEL_x);
HAL_TIM_PWM_Start(&TIM_TimeBaseStructure,MOTOR_PUL_CHANNEL_x); //开启PWM通道1
}
/*这是一个用于初始化用于编码器定时器的函数*/
void Motor_BM_TIM_Init(void)
__TIM8_CLK_ENABLE(); //打开定时器时钟
g_timx_chy_handle.Instance=TIM8; //定时器8
g_timx_chy_handle.Init.Prescaler=0; //自动加一就是1分频
g_timx_chy_handle.Init.Period=65535; //自动重装载值
g_timx_chy_handle.Init.ClockDivision= TIM_CLOCKDIVISION_DIV1; //不分频
g_timx_encoder_chy_handle.EncoderMode = TIM_ENCODERMODE_TI12; /* TI1,TI2都检测 011 4倍频*/
g_timx_encoder_chy_handle.IC1Polarity = TIM_ICPOLARITY_RISING; /* 边沿检测器设置,非反向 */
g_timx_encoder_chy_handle.IC1Selection = TIM_ICSELECTION_DIRECTTI; /* 通道1映射到IC1*/
g_timx_encoder_chy_handle.IC1Prescaler = TIM_ICPSC_DIV1; /* 不分频 */
g_timx_encoder_chy_handle.IC1Filter = 10; /* 滤波器设置 */
g_timx_encoder_chy_handle.IC2Polarity = TIM_ICPOLARITY_RISING; /* 边沿检测器设置,非反向 */
g_timx_encoder_chy_handle.IC2Selection = TIM_ICSELECTION_DIRECTTI; /* 通道2映射到IC2 */
g_timx_encoder_chy_handle.IC2Prescaler = TIM_ICPSC_DIV1; /* 一分频 */
g_timx_encoder_chy_handle.IC2Filter = 10; /* 滤波器设置 */
HAL_TIM_Encoder_Init(&g_timx_chy_handle, &g_timx_encoder_chy_handle); /*句柄初始化*/
HAL_TIM_Encoder_Start(&g_timx_chy_handle,TIM_CHANNEL_1); /* 开启编码器通道1 */
HAL_TIM_Encoder_Start(&g_timx_chy_handle,TIM_CHANNEL_2); /* 开启编码器通道2 */
TIMx_BM_NVIC_Configuration(); //初始化NVIC
__HAL_TIM_CLEAR_FLAG(&g_timx_chy_handle,TIM_IT_UPDATE); /* 清除更新中断 */
__HAL_TIM_ENABLE_IT(&g_timx_chy_handle,TIM_IT_UPDATE); /* 开启更新中断 */
}
/*这是一个中断服务函数将句柄放入绑定回调函数*/
void TIM8_UP_TIM13_IRQHandler(void)
{
HAL_TIM_IRQHandler(&g_timx_chy_handle);
}
volatile int g_timx_encode_count = 0; /* 用于记录溢出次数,向下溢出就-1,向上溢出就+1 */
/*这是一个中断回调函数*/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(__HAL_TIM_IS_TIM_COUNTING_DOWN(&g_timx_chy_handle)) /* 读取寄存器位判断CR1的DIR位 */
{
g_timx_encode_count--; /* DIR位为1,也就是递减计数 */
}
else
{
g_timx_encode_count++; /* DIR位为0,也就是递增计数 */
}
}
/*这是一个获取编码器值的函数*/
int32_t gtim_get_encode(void)
{
// printf("编码器原式步数:%d\r\n",__HAL_TIM_GET_COUNTER(&g_timx_chy_handle));
/* 计算当前总计数值,当前总计数值 = 计数器当前值 + 溢出次数*65536 */
return ( int32_t )(__HAL_TIM_GET_COUNTER(&g_timx_chy_handle) + g_timx_encode_count * 65536);
}
int32_t encode_old,encode_now,var;
/* 计算1分钟间隔的计数变化量,原理:
第一步:外部延时200ms调用一次函数,var*5*200ms*60=1分钟;
第二步:计算1分钟间隔编码器的输出脉冲个数var*5*200ms*60/4=脉冲个数,4为因为使用了4倍频;
第三步:计算1分钟间隔编码器转过的圈数var*5*200ms*60/4/11分辨率
第四步:除去减速齿轮比得到真正的转速var*5*200ms*60/4/11/4
*/
int32_t speed_computer(void)
{
encode_now = gtim_get_encode(); /* 获取当前计数值 */
var = encode_now - encode_old; /* 计算计数变化量200ms间隔 */
encode_old = encode_now; /* 保存这一次的计数值 */
return ( int32_t )( var * 5 * 60 / 4 / 11 / 4.4 ); /* 返回电机速度值 */
}
4.2 控制占空比大小逻辑:
/*旋转编码器控制逻辑 (外部中断扫描方式)*/
void xz_can(void)
{
if (SW_Scan() == KEY_ON) { //SW按钮触发
;
}
if (encoderCount != 0) { //旋转编码器被旋转
if (encoderCount > precision) { //大于精度阈值代表被正转
encoderCount = 0; //清零方便下次判断
if ((arr2 >= 0) && (arr2 < 50)) {
arr2++;
OLED_Show_Humi(40, 2, arr2 * 2); //更新数字内容 2倍显示速度
__HAL_TIM_SET_COMPARE(&TIM_TimeBaseStructure, MOTOR_PUL_CHANNEL_x, arr2*2); //修改CCRx寄存器值
}
} else if (encoderCount < -precision) { //小于负的精度阈值代表反转
encoderCount = 0;
if ((arr2 > 0) && (arr2 <= 50)) {
arr2--;
OLED_Show_Humi(40, 2, arr2 * 2);
__HAL_TIM_SET_COMPARE(&TIM_TimeBaseStructure, MOTOR_PUL_CHANNEL_x, arr2*2); //修改CCRx寄存器值
}
}
}
}
五、实验示例
使用旋转编码器作为占空比的调节器并通过OLED显示占空比调节情况可见电机的运行速度随着占空比的大小而变化。
六、总结
可见只要弄懂单片机定时器的PWM模式相关知识点,实现电机控制,呼吸灯控制,亮度控制等等简直信手拈来;需要完整源码请留下邮箱。