第六讲——定时器编码器模式+电机驱动
文章目录
前言
本章将介绍如何使用STM32读取电机编码器时序,从而计算出电机转速。同时搭配对电机的PWM渐变控制。使用的微处理器是STM32F407VET6。
编程逻辑,手机APP发送数据——>控制板蓝牙接收数据——>根据数据计算出占空比——>控制电机——>读取编码器时序——>得到转速
一、前提知识
1.编码器概述
编码器是一种将角位移或者角速度转换成一连串电数字脉冲的旋转式传感器,我们可以通过编码器测量到底位移或者速度信息。编码器从输出数据类型上分,可以分为增量式编码器和绝对式编码器。
从编码器检测原理上来分,还可以分为光学式、磁式、感应式、电容式。常见的是光电编码器(光学式)和霍尔编码器(磁式)。
2.编码器原理
光电编码器是一种通过光电转换将输出轴上的机械几何位移量转换成脉冲或数字量的传感器。光电编码器是由光码盘和光电检测装置组成。光码盘是在一定直径的圆板上等分地开通若干个长方形孔。由于光电码盘与电动机同轴,电动机旋转时,检测装置检测输出若干脉冲信号,为判断转向,一般输出两组存在一定相位差的方波信号。
霍尔编码器是一种通过磁电转换将输出轴上的机械几何位移量转换成脉冲或数字量的传感器。霍尔编码器是由霍尔码盘和霍尔元件组成。霍尔码盘是在一定直径的圆板上等分地布置有不同的磁极。霍尔码盘与电动机同轴,电动机旋转时,霍尔元件检测输出若干脉冲信号,为判断转向,一般输出两组存在一定相位差的方波信号。
可以看到两种原理的编码器目的都是获取AB 相输出的方波信号,其使用方法也是一样,下面是一个简单的示意图。
以下是我使用的编码器电机,如图
这是一款增量式输出的霍尔编码器。编码器有AB相输出,所以不仅可以测速,还可以辨别转向。根据上图的接线说明可以看到,我们只需给编码器电源5V 供电,在电机转动的时候即可通过 AB 相输出方波信号。编码器自带了上拉电阻,所以无需外部上拉,可以直接连接到单片机10读取。
3.编码器软件四倍频技术
先看编码器输出波形,如图
这里,我们是通过软件的方法实现四倍频。首先可以看到上图编码器输出的AB相波形,正常情况下我们使用M法测速的时候,会通过测量单位时间内A相输出的脉冲数来得到速度信息。常规的方法,我们只测量A相(或B相)的上升沿或者下降沿,也就是上图中对应的数字 1234 中的某一个,这样就只能计数 3次。而四倍频的方法是测量A相和B相编码器的上升沿和下降沿。这样在同样的时间内,可以计数12次(3个1234的循环)。这就是软件四倍频的原理。
4.定时器计时方式
如下图,当选择在TI1和TI2处均计数,此时就可以进行软件四倍频了。下图给出了定时器递增和递减的所有结果。
当选择,定时器向上计数,TI1和TI2处均计数,TI1FP1与TI1FP2不反向(TI1FP1 和 TI2FP2 是进行输入滤波器和极性选择后 TI1 和 TI2 的信号),此时计数器计时工作如下
从而读取计数器值可以换算得到转速,读取计数器技术方向可以得到电机转向。
二、使用CubeMX建立工程
这里只说明关于定时器部分的配置介绍,其他外设配置说明见往期文章
将定时器1的Combined Channels选择为Encoder mode。
预分频PSC为0(转载时自动加1,PSC=1表示不分频),因为此时定时器相当为输入捕获模式,定时器的频率就为编码器AB相时序的频率,所有不需要分频。
选择向上计数
重装载值为30000,在这里可以理解为定时器计数上限。只要保证在计数时间内计数不到达到这个上限即可
Repetition Counter (RCR- 8 bits value)表示重复计数器,意思就是当计数器可以计数(RCR+1)个ARR,只有高级定时器才具有此功能,这里设置为0,不需要。
不使能计数自动重装载,程序控制归0。
Encoder Mode设置为Encoder Mode TI1 and TI2,表示在TI1和TI2处均计数,使用软件四倍频。
Polarity选择Rising Edge,这里不是字面上升沿的意思,如图,在编码器模式下Rising Edge表示不反相,Falling Edge 表示反相。
不反相的计数方式为1.4章节的第二张图,反相的计数方式如下图,这里选择不反相。
Prescaler Division Ratio 选择不分频输出
Input Filter 选择不滤波,我前面关于没有滤波器的解释,这里说明一下,设置Input Filter会改变采样频率,具体如图所示。
Fsampling 为采样频率,Fck_int 为时钟频率,Fdts是定时器时钟 Fck_int频率经过倍频得到的,由CKD控制 ,当CKD为0时,Fdts=Fck_int
外部信号通过IO口复用到TI1,经过一个滤波器。这里滤波器的作用只有两个,如果TI1是模拟信号(也就是一段波),那么滤波器可以设置 对TI1 输入的模拟信号的采样频率。如果TI1是数字信号(一组高低电平序列),那么滤波器由事件计数器组成,可以设置时间N的大小,每 N 个事件才视为一个有效信号。
我这里使用了两个电机,TIM4用于读取第二个电机编码器时序,所以TIM4与TIM1同样配置,如果只有一个电机TIM4可以不设置。
将TIM3的CH3和CH4设置为PWM输出模式,分别对应两个电机的PWM通道。关于PWM输出的详细见第五讲。这里只给出图片,不进行解释。
需要注意的一点就是,PSC和ARR的取值,电机手册上会写它的脉宽调制频率范围,我这里设置为21KHz,我把ARR设置为100是为了便于计算占空比。
接着设置TIM5,设置成25ms中断一次
定时器的设置就完成了,我这里还设置了USART串口,是为了蓝牙通讯和上位机通讯,往期文章有过介绍,这里不再重复介绍。
keil工程代码编写
tim.c
使能定时器1的CH1和CH2通道的比较捕获功能和定时器计数使能
/* USER CODE BEGIN TIM1_Init 2 */
LL_TIM_CC_EnableChannel(TIM1,LL_TIM_CHANNEL_CH1);
LL_TIM_CC_EnableChannel(TIM1,LL_TIM_CHANNEL_CH2);
LL_TIM_EnableCounter(TIM1);
/* USER CODE END TIM1_Init 2 */
TIM4与TIM1同样初始化
/* USER CODE BEGIN TIM4_Init 2 */
LL_TIM_CC_EnableChannel(TIM4,LL_TIM_CHANNEL_CH1);
LL_TIM_CC_EnableChannel(TIM4,LL_TIM_CHANNEL_CH2);
LL_TIM_EnableCounter(TIM4);
/* USER CODE END TIM4_Init 2 */
初始化TIM3
/* USER CODE BEGIN TIM3_Init 2 */
LL_TIM_EnableAllOutputs(TIM3);//使能定时器输出
LL_TIM_EnableCounter(TIM3); //使能计数
LL_TIM_CC_EnableChannel(TIM3,LL_TIM_CHANNEL_CH3); //使能输出比较通道
LL_TIM_CC_EnableChannel(TIM3,LL_TIM_CHANNEL_CH4); //使能输出比较通道
/* USER CODE END TIM3_Init 2 */
初始化TIM5
/* USER CODE BEGIN TIM5_Init 2 */
LL_TIM_EnableCounter(TIM5); //使能计数
LL_TIM_EnableIT_UPDATE(TIM5); //使能更新中断
/* USER CODE END TIM5_Init 2 */
注意LL库生成的定时器初始化会警告,处理见第五讲
usart.c
开启UART5的数据非空中断
/* USER CODE BEGIN UART5_Init 2 */
LL_USART_EnableIT_RXNE(UART5);
/* USER CODE END UART5_Init 2 */
stm32f4xx.c
每次进入TIM5的更新中断就表示过了25ms,在中断中设置的占空比与电机本身的占空比做比较,然后每25ms该变化1%,使电机本身的占空比渐变的成为设置的占空比,如果直接将设置的占空比写入,会因为与电机本身的占空比相差过大,导致突变,对电机损害极大。
同时开始每4次计数开启编码器读取,则100ms内读取一次,读取前先关闭定时器计数,然后读取两个电机的编码器计数和方向,读取完将计数贵0,最后重新开启定时器计数。
/* USER CODE BEGIN TD */
unsigned char UART5_RXNE = 0,BLE_RX;
unsigned char pwm_left_new,pwm_left_now;
unsigned char pwm_right_new,pwm_right_now;
unsigned char coder_t = 0;
unsigned int tim1_cnt,tim4_cnt;
unsigned char tim1_cnt_direction,tim4_cnt_direction;
/* USER CODE END TD */
void TIM5_IRQHandler(void)
{
/* USER CODE BEGIN TIM5_IRQn 0 */
static u8 coder_t_i=0;
if(LL_TIM_IsActiveFlag_UPDATE(TIM5)==SET)//25ms
{
LL_TIM_ClearFlag_UPDATE(TIM5);
coder_t_i++;
if(coder_t_i==4)//编码器每100ms读取一次
{
coder_t_i = 0;
LL_TIM_DisableCounter(TIM1);
LL_TIM_DisableCounter(TIM4);
coder_t = 1;
tim1_cnt = LL_TIM_GetCounter(TIM1);//获取编码器计数器值
tim1_cnt_direction = LL_TIM_GetDirection(TIM1);//获取编码器计数方向
tim4_cnt = LL_TIM_GetCounter(TIM4);
tim4_cnt_direction = LL_TIM_GetDirection(TIM4);
LL_TIM_SetCounter(TIM1,0);//清空定时器计数值
LL_TIM_SetCounter(TIM4,0);
LL_TIM_EnableCounter(TIM1);
LL_TIM_EnableCounter(TIM4);
}
if(pwm_left_new>pwm_left_now)
{
pwm_left_now++;
LL_TIM_OC_SetCompareCH3(TIM3,pwm_left_now);
}
else if(pwm_left_new<pwm_left_now)
{
pwm_left_now--;
LL_TIM_OC_SetCompareCH3(TIM3,pwm_left_now);
}
if(pwm_right_new>pwm_right_now)
{
pwm_right_now++;
LL_TIM_OC_SetCompareCH4(TIM3,pwm_right_now);
}
else if(pwm_right_new<pwm_right_now)
{
pwm_right_now--;
LL_TIM_OC_SetCompareCH4(TIM3,pwm_right_now);
}
}
/* USER CODE END TIM5_IRQn 0 */
/* USER CODE BEGIN TIM5_IRQn 1 */
/* USER CODE END TIM5_IRQn 1 */
}
当接收到蓝牙指令后,在中断将指令保存在BLE_RX 中,并打开UART5_RXNE 标志位
void UART5_IRQHandler(void)
{
/* USER CODE BEGIN UART5_IRQn 0 */
if(LL_USART_IsActiveFlag_RXNE(UART5))
{
LL_USART_ClearFlag_RXNE(UART5);
BLE_RX = LL_USART_ReceiveData8(UART5);
UART5_RXNE = 1;
}
/* USER CODE END UART5_IRQn 0 */
/* USER CODE BEGIN UART5_IRQn 1 */
/* USER CODE END UART5_IRQn 1 */
}
电机运行逻辑如图所示
main.c
首先先拉低控制引脚
/* USER CODE BEGIN 2 */
LL_GPIO_ResetOutputPin(motor_ina1_GPIO_Port,motor_ina1_Pin);
LL_GPIO_ResetOutputPin(motor_inb1_GPIO_Port,motor_inb1_Pin);
LL_GPIO_ResetOutputPin(motor_ina2_GPIO_Port,motor_ina2_Pin);
LL_GPIO_ResetOutputPin(motor_inb2_GPIO_Port,motor_inb2_Pin);
/* USER CODE END 2 */
接着在while里面针对蓝牙输入的指令BLE_RX去改变电机的运行状态,并且每次以10%的占空比为基准进行加减,然后对占空比设置一个上限。
如果是正转,将编码器定时器计数的值10就是1s内计数器脉冲个数,再/(134)就是1s内码盘转圈个数,再/5.18就是1s内电机转圈个数,从而得到电机r/s。
如果是反转,要用定时器重装载值减去编码器定时器计数的值,得到的才为编码器脉冲个数。
代码如下
while (1)
{
if(UART5_RXNE)
{
UART5_RXNE = 0;
switch(BLE_RX)
{
case 0x31:
{
LL_GPIO_ResetOutputPin(motor_ina1_GPIO_Port,motor_ina1_Pin);
LL_GPIO_SetOutputPin(motor_inb1_GPIO_Port,motor_inb1_Pin);
LL_GPIO_ResetOutputPin(motor_ina2_GPIO_Port,motor_ina2_Pin);
LL_GPIO_SetOutputPin(motor_inb2_GPIO_Port,motor_inb2_Pin);
pwm_left_new += 10;
pwm_right_new += 10;
printf("前进\r\n");
}; break;
case 0x32:
{
pwm_left_new = 0;
pwm_right_new = 0;
printf("停止\r\n");
}; break;
case 0x33:
{
LL_GPIO_SetOutputPin(motor_ina1_GPIO_Port,motor_ina1_Pin);
LL_GPIO_ResetOutputPin(motor_inb1_GPIO_Port,motor_inb1_Pin);
LL_GPIO_SetOutputPin(motor_ina2_GPIO_Port,motor_ina2_Pin);
LL_GPIO_ResetOutputPin(motor_inb2_GPIO_Port,motor_inb2_Pin);
pwm_left_new += 10;
pwm_right_new += 10;
printf("后退\r\n");
}; break;
default:
break;
}
BLE_RX=0;
printf("左轮PWM:%d ",pwm_left_new);
printf("右轮PWM:%d \n",pwm_right_new);
if(pwm_left_new>=100)
{
pwm_left_new = 100;
printf("左轮已最大转速 \n");
}
if(pwm_right_new>=100)
{
pwm_right_new = 100;
printf("右轮已最大转速 \n");
}
if(tim1_cnt_direction==0)
{
printf("左轮正转,转速:%.2f R/s ",tim1_speed);
}
else if(tim1_cnt_direction==1)
{
printf("左轮反转,转速:%.2f R/s ",tim1_speed);
}
if(tim4_cnt_direction==0)
{
printf("右轮正转,转速:%.2f R/s\n",tim4_speed);
}
else if(tim4_cnt_direction==1)
{
printf("右轮反转,转速:%.2f R/s\n",tim4_speed);
}
}
if(coder_t)
{
coder_t = 0;
if(tim1_cnt_direction==LL_TIM_COUNTERDIRECTION_UP)
{
tim1_speed = tim1_cnt*10/(13*5.18*4);//计算转速,单位为转每秒
printf("左轮正转,转速:%.2f R/s ",tim1_speed);
}
else if(tim1_cnt_direction==LL_TIM_COUNTERDIRECTION_DOWN)
{
tim1_speed = (30000-tim1_cnt)*10/(13*5.18*4);//计算转速,单位为转每秒
printf("左轮反转,转速:%.2f R/s ",tim1_speed);
}
if(tim4_cnt_direction==LL_TIM_COUNTERDIRECTION_UP)
{
tim4_speed = tim4_cnt*10/(13*5.18*4);
printf("右轮正转,转速:%.2f R/s\n",tim4_speed);
}
else if(tim4_cnt_direction==LL_TIM_COUNTERDIRECTION_DOWN)
{
tim4_speed = (30000-tim4_cnt)*10/(13*5.18*4);
printf("右轮反转,转速:%.2f R/s\n",tim4_speed);
}
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
四、效果展示
定时器编码器模式+电机控制
此效果与本文代码实现效果有些差别,仅供参考
五、工程下载
链接:https://pan.baidu.com/s/1sSsGl7viutpDtkUsIqhMuQ?pwd=1234
提取码:1234
结言
时间匆忙,有错误之处还望大家指出,(可能有些错别字,不影响阅读就好)