90人参与 • 2024-08-01 • 嵌入式
最近在学习编码电机以及尝试使用编码电机测速。遇到了很多问题,花费了很多时间,在这里做一个记录,对自己学习到的知识进行一个总结
找了很多资料,看了很多视频,这些太多了,以至于让我不知道究竟哪一个是正确的,今天看这个,明天看这个,导致自己的学习效率低下
当然,有很多大佬的文章和资料给了我很大的启发
这个电机我玩了四天,把自己觉得重要的东西和大家分享一下
现在一般都是用编码器电机,参加比赛啥的,编码电机常用于测速,所以编码电机就成了一个必须学习的知识点
编码器被广泛应用于电机测速,实现电机闭环控制。
相关的知识点有:定时器的输出比较(输出pwm)、定时器的输入捕获,定时器的编码器接口、速度控制
编码电机其实就是一个带有编码器的电机,我的这个电机是一个增量式的带霍尔传感器的电机
电机的型号是jgb37-520电机
下方是电机的参数
主要关注的就是电机的额定电压 12v
电机的减速比 30(这个很重要)
编码器的参数
主要关注编码器的线数 11线 (也就是说电机转一圈会产生11个脉冲)
供电电压 5v
输出类型 方波
编码器的连接
一般这种编码器都有六根线
两边靠外的两根线是电机电源线
往里两根是编码器的电源线
中间两根是编码器的a,b相
具体大概是啥意思呢?
就是电机转动的时候编码器会通过编码电机的a相和b相输出两个正交的方波
通过输出的两个方波就可以对电机进行测速
和识别电机的方向
整体结构采用洞洞板+tb6612+stm32c8t6+编码电机(起初采用的是这种结构)
后面采用stm32zet6+tb6612+洞洞板+编码电机+12v电源(原因是c8t6烧坏了,哭😥)
主控stm32c8t6 or stm32zet6
电机驱动 tb6612(由于上一个l298n烧了)
520霍尔编码电机
12v电源
这里展示驱动一个编码电机的示例,毕竟先从一个电机玩起,弄懂后后面就会使用的更加得心应手啦
主要使用到了定时器的pwm模式(输出比较)功能
大家一定要认真接线,看清出每根线的作用,不要随便接线,一不小心电机驱动就烧了,或者是单片机烧了(在学习的时候就烧了一个单片机,人民币-15)
注意这个是我实现的接线,大家可以根据自己单片机的片上资源合理选择,选择合适的io口
电机驱动
tb6612 | c8t6 |
---|---|
stby | 高电平(+3.3v) |
ain1 | pb14 |
ain2 | pb15 |
pwma | pa8 (tim1-ch1) |
ao1 | 电机电源+ |
ao2 | 电机电源- |
vm | 12v |
vcc | 3.3v |
gnd | 和单片机共地 |
编码器
编码器的a、b相 | c8t6 |
---|---|
a相 | pa0 (tim2-ch1) |
b相 | pa1 (tim2-ch2) |
首先了解一下tb6612
下图是tb6612驱动模块
原理图
stby接高电平 清零电机全部停止
置 1 通过 ain1 ain2, bin1,bin2 引脚来控制正反转
pwm引脚控制占空比
vm: 接 12v 以内电源
vcc: 接 5v 电源
gnd: 接电源负极
下图是驱动逻辑
可以看出in引脚控制正反转,pwm引脚控制速度
使用定时器的pwm模式
生成一个需要的 占空比可调的 频率 符合要求
的方波信号。
方波信号的频率不宜过高或者过低,过高容易导致电机驱动的晶闸管经常处于开关状态–发热巨大;过低则容易产生噪音,对电机也低频的冲击
这里输出pwm信号的定时器是tim1-ch1
设置成pwm模式,频率和占空比可调
有关定时器pwm模式,可以看其他大佬的文章和资料,看看手册
可以看看江科大的教学视频,比我讲的详细多了,也很好理解
我贴出视频链接,大家学习32的时候可以跟他
tim输出比较,pwm模式
下方的pwm模式的代码作为一个参考
motor.h
#ifndef __motor_h
#define __motor_h
#include "sys.h"
#define pwma tim1->ccr1 //pa8 pwma tim1_ch1
#define ain2 pbout(15)
#define ain1 pbout(14)
void motor_pwm_init(u16 arr,u16 psc);
void motor_setspeed(u8 mode ,u16 speed);
#endif
motor.c
void motor_init(void) //in引脚初始化
{
gpio_inittypedef gpio_initstructure;
rcc_apb2periphclockcmd(rcc_apb2periph_gpiob, enable); //使能pb端口时钟
gpio_initstructure.gpio_pin = gpio_pin_14|gpio_pin_15; //端口配置
gpio_initstructure.gpio_mode = gpio_mode_out_pp; //推挽输出
gpio_initstructure.gpio_speed = gpio_speed_50mhz; //50m
gpio_init(gpiob, &gpio_initstructure); //根据设定参数初始化gpiob
}
void motor_pwm_init(u16 arr,u16 psc) //pwm引脚初始化
{
gpio_inittypedef gpio_initstructure;
tim_timebaseinittypedef tim_timebasestructure;
tim_ocinittypedef tim_ocinitstructure;
motor_init();
rcc_apb2periphclockcmd(rcc_apb2periph_gpioa | rcc_apb2periph_tim1 | rcc_apb2periph_afio,enable);//开启时钟
//输出tim1 ch1
gpio_initstructure.gpio_pin = gpio_pin_8; //tim_ch1
gpio_initstructure.gpio_mode = gpio_mode_af_pp; //复用推挽输出
gpio_initstructure.gpio_speed = gpio_speed_50mhz;
gpio_init(gpioa, &gpio_initstructure);
tim_timebasestructinit(&tim_timebasestructure);//初始化定时器。
tim_timebasestructure.tim_period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
tim_timebasestructure.tim_prescaler =psc; //设置用来作为timx时钟频率除数的预分频值 不分频
tim_timebasestructure.tim_clockdivision = 0; //设置时钟分割:tdts = tck_tim
tim_timebasestructure.tim_countermode = tim_countermode_up; //tim向上计数模式
tim_timebaseinit(tim1, &tim_timebasestructure); //根据tim_timebaseinitstruct中指定的参数初始化timx的时间基数单位
tim_ocinitstructure.tim_ocmode = tim_ocmode_pwm1; //选择定时器模式:tim脉冲宽度调制模式1
tim_ocinitstructure.tim_outputstate = tim_outputstate_enable; //比较输出使能
tim_ocinitstructure.tim_pulse = 0; //设置待装入捕获比较寄存器的脉冲值
tim_ocinitstructure.tim_ocpolarity = tim_ocpolarity_high; //输出极性:tim输出比较极性高
tim_oc1init(tim1, &tim_ocinitstructure); //根据tim_ocinitstruct中指定的参数初始化外设timx
tim_ctrlpwmoutputs(tim1,enable); //moe 主输出使能 高级定时器一定要写这个语句
tim_arrpreloadconfig(tim1, enable); //使能timx在arr上的预装载寄存器
tim_cmd(tim1, enable); //使能tim1
}
void motor_setspeed(u8 mode ,u16 speed) //mode 代表正反转 speed pwm占空比即速度
{
pwma = speed;
if(mode==1)
{
ain1 = 1;
ain2 = 0;
}
else {
ain1 = 0;
ain2 = 1;
}
}
编码器一般应用于电机控制,使用pwm驱动电机,然后再使用编码器测量速度,再使用pid算法进行闭环控制
记住下面这句话
在一定的时间内,电机转动一圈,通过霍尔传感器的a、b两相输出一定数量的脉冲,我们可以根据一定时间内的脉冲数计算出电机的瞬时速度。
采用的是定时器的编码器接口模式
,stm32中的定时器只有tim1-5和tim8才有编码器接口功能,而且只有ch1通道和ch2通道有用。
原理
:接收编码器的a、b相产生的正交信号,根据编码器产生的正交信号脉冲,自动控制cnt自增或自减,根据计数方向和编码器的信号关系来指示编码器的位置、旋转方向和旋转速度,利用脉冲值来计算电机的转动位移
这个可以参考手册里定时器的编码器模式,比我讲的清楚多了
定时器的编码器接口托管了输入捕获的前两个接口
还有一句话记住,编码器模式下就相当于一个带有方向选择的外部时钟
具体配置流程就是
时钟–>gpio–>时基单元配置–>编码器接口配置–>开启定时器–>读取一个时间段内的脉冲–>计算电机旋转轴转速
使用这个函数把定时器设置为编码器接口模式
tim_encoderinterfaceconfig(tim3, tim_encodermode_ti12, tim_icpolarity_rising, tim_icpolarity_rising);
采用的是编码器模式3,在ti1和ti2边沿都计数
,也就是在一个周期内对a相和b相的上升沿下降沿都计数
,一个周期内计4次
,所以采用这种模式后,相应的计数值(cnt)就会变成4倍,这就是很多资料里说的四倍频计数
。
采用的是定时器2的编码器接口模式,通道1和通道2捕获
encoder.c
/**
* @brief 把tim2初始化为编码器接口模式
* @param psc 预分频系数
* @param arr 自动重装载值
* @retval none
*/
void encoder_init_tim2(uint16_t psc,uint16_t arr)
{
tim_timebaseinittypedef tim_timebasestructure;
tim_icinittypedef tim_icinitstructure;
gpio_inittypedef gpio_initstructure;
//nvic_inittypedef nvic_initstructure;
//使能定时器2的时钟
rcc_apb1periphclockcmd(rcc_apb1periph_tim2, enable);
//使能pb端口时钟
rcc_apb2periphclockcmd(rcc_apb2periph_gpioa, enable);
//端口配置
gpio_initstructure.gpio_pin = gpio_pin_0|gpio_pin_1;
//浮空输入
gpio_initstructure.gpio_mode = gpio_mode_in_floating;
//根据设定参数初始化gpiob
gpio_init(gpioa, &gpio_initstructure);
tim_timebasestructinit(&tim_timebasestructure);
// 预分频器
tim_timebasestructure.tim_prescaler = psc;
//设定计数器自动重装值
tim_timebasestructure.tim_period = arr;
//选择时钟分频:不分频
tim_timebasestructure.tim_clockdivision = tim_ckd_div1;
//tim向上计数
tim_timebasestructure.tim_countermode = tim_countermode_up;
tim_timebaseinit(tim2, &tim_timebasestructure);
//使用编码器模式3
tim_encoderinterfaceconfig(tim2, \
tim_encodermode_ti12, \
tim_icpolarity_rising, \
tim_icpolarity_rising);
tim_icstructinit(&tim_icinitstructure);
tim_icinitstructure.tim_icfilter = 10;
tim_icinit(tim2, &tim_icinitstructure);
//清除tim的更新标志位
tim_clearflag(tim2, tim_flag_update);
tim_itconfig(tim2, tim_it_update, enable);
//reset counter
tim_setcounter(tim2,0);
tim_cmd(tim2, enable);
}
/**
* @brief 单位时间读取编码器计数
* @param timx 定时器
* @retval 速度值 是编码器返回的脉冲
*/
int read_encoder()
{
int encoder_tim;
encoder_tim= (short)tim2 -> cnt;
//encoder_tim= (int)((int16_t)(tim4->cnt));;
tim2 -> cnt=0;
return encoder_tim;
}
encoder.h
#ifndef __encoder_h
#define __encoder_h
#include "sys.h"
void encoder_init_tim2(uint16_t psc,uint16_t arr);
int read_encoder();
#endif
在一个时间周期t0内,定时的读取编码器产生的脉冲,以我的编码器为例(11线,减速比30),转一圈会产生1320个脉冲(因为采用的是编码模式3)
这个1320 = 11 * 30 * 4
通过在固定的周期t0内,产生的脉冲就相当于路程,而这个固定的周期就相当于时间
所以速度就等于 在t0这段时间获取到的脉冲总数/(编码器单圈产生的总脉冲数*t0)
对于我的电机就是
在看了前面之后,应该对编码器模式和编码器测速有了一个大概的认识,知道了它测速的原理,但肯定有好多疑问,我把我学习过程中遇到的问题和解决方法做一个总结,你肯定也有这些疑问,不要着急,看下去。
所以我们应该如何设置?
psc呢?
psc没有必要设置,因为我要计数的本来就是电机转动一圈产生的真实脉冲,所以psc给0就好啦
arr呢?
目前在各种论坛和博客和资料中有两种版本。
第一种,根据电机的线数和减速比来设置,比如我的电机是11线,减速比30,转动一圈的脉冲数是1320,这个值就可以设置为1320。
产生的脉冲数恰好是你定时器溢出的时候,溢出一次记录一次,这个的次数就是电机的圈数(当然这种误差很大)
也就是说电机转一圈正好是1320,当cnt计数到arr时,计数器就会清零并且重新计数,所以这个arr就是电机转一圈产生的脉冲数的最大值。
第二种,直接设置成定时器arr的最大值,也就是65535(2^16-1),这样设置的目的就是无论你电机产生多少脉冲,都可以记录,且不会溢出。
不过使用65535的话,就要在最开始的时候初始化编码器模式提前把cnt清零,然后再开始计数。再在一个周期内定时读取脉冲数,再清零,这个脉冲也是周期内读取到的脉冲值。
电机旋转一圈能产生脉冲,那么我们就能记录一段时间产生的脉冲数来计算速度
可以通过判断cr1寄存器中的dir位,这个位是计数方向位
正转就是cnt向上计数(dir==0)
反转就是cnt向下计数(dir==1)
就是上方说的,把arr设置为电机旋转一周产生的脉冲数
电机转一圈,cnt达到arr,溢出,进中断,设置一个变量++(正转),–(反转)
规定某个某个时钟周期内,读取有多少脉冲,从而计算转速
这里采用的是m法测速,测出的是电机是多少转/s
脉冲相当于路程,某个时钟周期相当于时间
这个上方有描述,可以往上翻翻
检查一下接线,从硬件开始,一步一步排查,对应的引脚是否正确
检查电源,编码器的电源是否打开,相应的pwm波是否有效
硬件确认没有错误,检查软件,编码器接口是否打开,pwm模式是否输出,电机in引脚是否配置
硬件展示
测速展示
可以看到当转速为1的时候,产生的脉冲是66-68,而我设置的闸门时间是50ms,结合电机转一圈是1320个脉冲,也就是说测量的脉冲数几乎正确。
(66*2=132) 和编码器的线数完全吻合,测速成功!!!
这个编码器花费了我几乎四天的时间,也可能是自己比较小白,不懂得如何通过电机转一圈产生的脉冲数来计算速度,第一天就实现了读取脉冲
但是后面几天执着于测速,没有采取正确的方法,导致自己无线内耗,浪费了大量时间。
由于网上资料繁杂,找不到自己想要的,浪费了很多时间,很多只是一笔带过,没有系统的讲解原理和方法,我也不知道自己这篇文章是否正解
所以将这个学习总结分享给大家
当然,这都是在借鉴了前人的肩膀下,谢谢各位大佬和优秀的文章,我会在下方贴出自己觉得值得一看大佬们的链接,大家可以一看
欢迎大家指错,看到了就会修改,大家一起共同进步。
带霍尔传感器编码器的直流减速电机测速原理讲解(附源码)-openedv-开源电子网
带编码器的直流减速电机——基于stm32f407_编码器直流减速电机磁极数目_谁还不是个程序猿的博客
谢谢各位大佬的文章,让我受益匪浅,站在前人的肩膀下才能看的更远
您想发表意见!!点此发布评论
版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。
发表评论