▍编码器的由来和原理
若要对伺服系统中的电机进行高精度控制,需要准确的转子角度位置,这时候自然会想到,如果能张江转子每一圈进行细分,这样每次转多少角度便能精确知道。在这样的背景下,相对编码器就诞生了。
在网上找到下文这个图,很形象的表征了相对编码器的原理。
如图所示,在码盘上平均开出很多个等间距的槽,一段是led灯发出信号,另一端是接收器接收信号。如果信号能穿过码盘,则接收信号为高电平,反之则为低电平。这样当转子转起来以后,就不断的处高低电平。这就是编码器基本原理。
可以看到这里有三个信号,a/b/z,这时候就要想为什么要3个信号呢?如果仅仅对一圈做细分,命名一个信号就可以了。这就涉及到下面两个问题。
(1)如果是1个信号channel a,电机是正转还是反转就不知道了。需要一个相对的参考信号channel b,a和b相互呈一个角度,这样通过a和b的相对位置就能知道电机是顺时钟转还是逆时针转了。
(2)如果是2个信号,其中一旦有码盘有损坏,就可能出现检测结果无法校验的情况。举个例子,如果一圈开了16个槽,则每旋转一圈,正常情况下就有16个高低电平的信号出来。但如果一个槽坏了,实际上每转一圈只有15个信号出来,但这时如果仅仅通过channel a和channel b是无法判断的。在进行数据处理时还是认为16个信号为一圈,处理结果就有较大的偏差。为了避免这样的问题,补充z信号,一圈只出一个,这样就能相互交验了。一方面通过对a或者b计数,知道z是否有问题,反之对z信号计数就能知道a/b是否有问题。
所以就有了上图的z/a/b三个信号,共同组成了一个功能齐全的编码器。
在网上经常看到说a/b之间相互差90°,这个90°是认为360°为一个周期而言的。如下图所示。通过看a/b相对位置就知道电机是正转还是反转了。
实测波形,如下图所示(示波器不太好,有点毛刺)
正转
反转
▍使用stm32,让编码器说话
背景
stm32中提供了编码器接口,比较适用于相对编码器的应用场景。在手册中可以看到:
可以看到这里使用专用的模块就能完成相应的计数,通过数据的变化就能测出电机的转速。
所以,我想让编码器说话。在家翻箱倒柜以后,我准备了如下几个东西:
(1)带编码器的直流电机:这是作为编码器的载体使用,电机编码器的分辨率较低,每圈只有16个脉冲。但不影响测试。
(2)直流电源:用来直观的调电机的转速和正反转。
为了避免打广告的嫌疑,就不贴电源和电机图片了。
(3)stm32开发板:在家翻箱倒柜,找出2015年在21ic获得的stm32072 discovery板
(4)led数码管。用来通过编码器的数据处理,显示电机的转速。
试验第一步,让led数码管显示起来。
因为显示数据是最终目的。使用的这个板子,是集成了hc595锁存器的板子。相比于网上买的大部分51开发板数码管电机设计,使用两个hc595,可以大大减少pin脚的数量。网上使用的4位数码管,需要8个pin作为段选或者位选,非常麻烦。
根据hc595的手册,具有锁存加移位的特性(图中我标注所示)
最上面的3个sh-cp/ds/st-cp,像极了spi通信波形,只要合理配置,只需要3个信号线即可完成4数码管的轮流显示。
于是在开发板的pin做了如下硬件配置
pin(数码管) 74hc595spipin
sclkpin11(shift)spiclkpb13
rclkpin12(storage)nsspb12
diopin14(datainput)spimosipc3
qhpin9(dataoutput)spimisopc2
spi配置代码如下(配置了spi几个pin脚的定义,时钟,spi模式等):
void spi_digital_tube_config(void){ spi_inittypedef spi_initstructure; gpio_inittypedef gpio_initstructure;
/* disable the spi peripheral */ spi_cmd(spi2, disable); /* enable sck, mosi, miso and nss gpio clocks */ rcc_apb1periphclockcmd(rcc_apb1periph_spi2, enable); rcc_ahbperiphclockcmd(spi_digital_tube_sck_gpio_clk | spi_digital_tube_mosi_gpio_clk| spi_digital_tube_nss_gpio_clk, enable);
/* spi pin mappings */ gpio_pinafconfig(spi_digital_tube_sck_gpio_port, spi_digital_tube_sck_source, spi_digital_tube_sck_af); gpio_pinafconfig(spi_digital_tube_mosi_gpio_port, spi_digital_tube_mosi_source, spi_digital_tube_mosi_af); gpio_pinafconfig(spi_digital_tube_miso_gpio_port, spi_digital_tube_miso_source, spi_digital_tube_miso_af); gpio_pinafconfig(spi_digital_tube_nss_gpio_port, spi_digital_tube_nss_source, spi_digital_tube_nss_af);
gpio_initstructure.gpio_mode = gpio_mode_af; gpio_initstructure.gpio_otype = gpio_otype_pp; gpio_initstructure.gpio_pupd = gpio_pupd_down; gpio_initstructure.gpio_speed = gpio_speed_level_3;
/* spi sck pin configuration */ gpio_initstructure.gpio_pin = spi_digital_tube_sck_pin; gpio_init(spi_digital_tube_sck_gpio_port, &gpio_initstructure);
/* spi mosi pin configuration */ gpio_initstructure.gpio_pin = spi_digital_tube_mosi_pin; gpio_init(spi_digital_tube_mosi_gpio_port, &gpio_initstructure);
/* spi miso pin configuration */ gpio_initstructure.gpio_pin = spi_digital_tube_miso_pin; gpio_init(spi_digital_tube_miso_gpio_port, &gpio_initstructure);
/* spi nss pin configuration */ gpio_initstructure.gpio_pin = spi_digital_tube_nss_pin; gpio_init(spi_digital_tube_nss_gpio_port, &gpio_initstructure);
gpio_initstructure.gpio_mode = gpio_mode_out; gpio_initstructure.gpio_pin = spi_digital_tube_nss_pin; gpio_init(spi_digital_tube_nss_gpio_port, &gpio_initstructure);
/* spi configuration -------------------------------------------------------*/ spi_i2s_deinit(spi2); spi_initstructure.spi_direction = spi_direction_2lines_fullduplex; spi_initstructure.spi_datasize = spi_datasize_16b; spi_initstructure.spi_cpol = spi_cpol_low; spi_initstructure.spi_cpha = spi_cpha_1edge;// spi_initstructure.spi_nss = spi_nss_hard; spi_initstructure.spi_nss = spi_nss_soft; spi_initstructure.spi_baudrateprescaler = spi_baudrateprescaler_256; spi_initstructure.spi_firstbit = spi_firstbit_msb; spi_initstructure.spi_crcpolynomial = 7; spi_initstructure.spi_mode = spi_mode_master; spi_init(spi2, &spi_initstructure);
/* initialize the fifo threshold */ spi_rxfifothresholdconfig(spi2, spi_rxfifothreshold_qf);
/* enable the spi peripheral */ spi_cmd(spi2, enable);
// /* enable nss output for master mode */// spi_ssoutputcmd(spi2, enable);}
使用tim6作为定时器,配置代码如下(1ms定时周期):
static void basic_tim_mode_config(void){ tim_timebaseinittypedef tim_timebasestructure; basic_tim_apbxclock_fun(basic_tim_clk, enable); tim_timebasestructure.tim_period = basic_tim_period;//1ms tim_timebasestructure.tim_prescaler= basic_tim_prescaler;//47 tim_timebasestructure.tim_clockdivision=tim_ckd_div1; tim_timebasestructure.tim_countermode=tim_countermode_up; tim_timebasestructure.tim_repetitioncounter=0; tim_timebaseinit(basic_tim, &tim_timebasestructure); tim_clearflag(basic_tim, tim_flag_update); tim_itconfig(basic_tim,tim_it_update,enable); tim_cmd(basic_tim, enable); }
实际上每次只会有一个数码管亮,为了较好的视觉体验,将数码管进行千位百位十位个位循环显示,这样做的好处是4个数码管轮流显示,其亮度相同,避免出现一个数码管过亮的情形,影响视觉体验。数码管代码如下:
void displaynumber(uint16_t num){ uint8_t mythousandnum,myhundrednum,mytennum,myunitnum=0; if(num》9999)num=9999; mythousandnum=num/1000%10; myhundrednum=num/100%10; mytennum=num/10%10; myunitnum=num%10; switch(mydisplaybit) { case thousaud: display16(mythousandnum,4); mydisplaybit=hundred; break; case hundred: display16(myhundrednum,3); mydisplaybit=ten; break; case ten: display16(mytennum,2); mydisplaybit=unit; break; case unit: display16(myunitnum,1); mydisplaybit=thousaud; break; default: display16(mythousandnum,4); mydisplaybit=hundred; break; }}
static void display16(uint8_t num,uint8_t place){ gpio_resetbits(spi_digital_tube_nss_gpio_port, spi_digital_tube_nss_pin); uint16_t temp=((num[num])《《8)+((0x01)《《(place-1)); spi2_send_byte16(temp); gpio_setbits(spi_digital_tube_nss_gpio_port, spi_digital_tube_nss_pin);}
然后,每隔0.5s累加一次。在定时器中累计
void tim6_dac_irqhandler(){ static uint16_t counter=0; static uint16_t num_buffer=0; if ( tim_getitstatus( basic_tim, tim_it_update) != reset ) { counter++; if(counter》499) { num_buffer++; counter=0; } displaynumber(num_buffer); tim_clearitpendingbit(basic_tim , tim_flag_update); } }
所以,初试成功。
试验第二步,让编码器说话。
首先,在stm32中配置编码器。
使用pa6和pa7作为定时器3的通道1和通道2,进行下图模式的计数。
即效果如下:
代码如下
void tim3_encoderconfig(void){ tim_icinittypedef tim_icinitstructure; tim_timebaseinittypedef tim_timebasestructure; gpio_inittypedef gpio_initstructure; nvic_inittypedef nvic_initstructure;
hall_tim_apbxclock_fun(encoder_tim_clk, enable);
/* gpioa clock enable */ rcc_ahbperiphclockcmd(rcc_ahbperiph_gpioa, enable); //pa6 & pa7 rcc_ahbperiphclockcmd(rcc_apb1periph_tim3,enable); /* phase a & b*/ gpio_initstructure.gpio_pin = gpio_pin_6|gpio_pin_7; gpio_initstructure.gpio_mode = gpio_mode_af; gpio_initstructure.gpio_speed = gpio_speed_50mhz; gpio_initstructure.gpio_otype = gpio_otype_pp; gpio_initstructure.gpio_pupd = gpio_pupd_nopull; gpio_init(gpioa, &gpio_initstructure); gpio_pinafconfig(gpioa, gpio_pinsource6, gpio_af_1);//tim3_ch1 gpio_pinafconfig(gpioa, gpio_pinsource7, gpio_af_1);//tim3_ch2
tim_deinit(tim3); tim_timebasestructure.tim_period =0xffff; tim_timebasestructure.tim_prescaler =0; tim_timebasestructure.tim_clockdivision =tim_ckd_div1; tim_timebasestructure.tim_countermode =tim_countermode_up; tim_timebaseinit(tim3,&tim_timebasestructure);
tim_encoderinterfaceconfig(tim3,tim_encodermode_ti12,tim_icpolarity_bothedge,tim_icpolarity_bothedge);
tim_icstructinit(&tim_icinitstructure); tim_icinitstructure.tim_icfilter = 0; tim_icinit(tim3, &tim_icinitstructure);
// clear all pending interrupts tim_clearflag(tim3, tim_flag_update); tim_itconfig(tim3, tim_it_update, enable);
//reset counter tim_setcounter(tim3,0); tim_cmd(tim3, enable);
/* enable the tim1 global interrupt */ nvic_initstructure.nvic_irqchannel = tim3_irqn; nvic_initstructure.nvic_irqchannelpriority = 0; nvic_initstructure.nvic_irqchannelcmd = enable; nvic_init(&nvic_initstructure);}
然后在中断服务函数中,将编码器的相对值计算出来,并根据编码器计数的相对变化,计算出电机的转速。具体代码如下:
void tim6_dac_irqhandler(){ static uint16_t counter=0; static uint16_t num_buffer=0; static uint16_t temp_now=0; static uint16_t temp_pre=0; static uint16_t speed=0; if ( tim_getitstatus( basic_tim, tim_it_update) != reset ) { counter++; temp_now=(tim_getcounter(tim3)&0xffff); if(counter》499) { num_buffer=(temp_now-temp_pre)》0?temp_now-temp_pre:temp_pre-temp_now; speed=100*num_buffer*60/64; counter=0; } displaynumber(speed); if(counter%10==0)temp_pre=temp_now; tim_clearitpendingbit(basic_tim , tim_flag_update); } }
同时,为了防止tim3中断溢出,记得清除中断标志位
void tim3_irqhandler (){ if(tim_getitstatus(tim3, tim_it_update) != reset) { tim_clearitpendingbit(tim3, tim_it_update); }}
实际效果如下图所示(东西太多,手机不好拍动图,只能静物显示),可知,当电机电压9.32v时,转速为843rpm。当电压为18.7v时,转速为1687rpm。编码器的波形也用示波器显示出来了。还不错哈,哈哈哈。
▍结论
本文使用stm32f0 discovery开发板,完成了编码器计数和电机转速的计算,并通过数码管将电机转速实时显示出来。
原文标题:手把手教你怎么用stm32让相对编码器说话
文章出处:【微信公众号:嵌入式arm】欢迎添加关注!文章转载请注明出处。
什么是AI PC、AI手机?AI PC和AI手机出现的来龙去脉
什么是Register Renaming(寄存器重命名)/R
魅族Flyme 6今天公测:亮点很多,一定不会让煤油失望
云计算不被信任的原因是什么
中国智能锁行业混战 求稳才是大多数企业2020年的发展目标
如何用STM32让相对编码器说话?
iPhone8什么时候上市?iPhone8最新消息:iPhone8全面屏真惊艳,屏下指纹识别没有对手?
手机产业跨年大戏 2020开年就迎来了新硬战
工信部发布2020年通信业统计公报
干货!LED内置电源高低贵贱用眼睛就能看出来!
这台诺基亚能买4台iPhone7
连接器如何助力工业设备小型化数字化
LCD液晶拼接屏的使用寿命有多长
人工智能的进步到底会带我们走向怎样的世界?
vivoY55s开启预售,6000mAh超大电池1399元起
中国联通正式发布了总部ONS运营平台新建工程监控管理模块项目招标
DEKRA德凯扩建实验室新增声学及振动测试
戴尔:因英特尔CPU短缺造成产品供应紧张,将下调Q4季度的收入预期
备受广大果粉期待的2021全球开发者大会终于要来了
混合动力汽车电池均衡方案的研究