返回列表 发新帖我要提问本帖赏金: 100.00元(功能说明)

[STM32] 手把手教你怎么用STM32让相对编码器说话

[复制链接]
1849|3
 楼主| grhr 发表于 2021-3-9 00:33 | 显示全部楼层 |阅读模式
#申请原创# @21小跑堂  

编码器的由来和原理
若要对伺服系统中的电机进行高精度控制,需要准确的转子角度位置,这时候自然会想到,如果能张江转子每一圈进行细分,这样每次转多少角度便能精确知道。在这样的背景下,相对编码器就诞生了。
在网上找到下文这个图,很形象的表征了相对编码器的原理。



如图所示,在码盘上平均开出很多个等间距的槽,一段是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(数码管)
  
74HC595
SPI
Pin
SCLK
Pin11(shift)
SPICLK
PB13
RCLK
Pin12(Storage)
NSS
PB12
DIO
Pin14(datainput)
SPIMOSI
PC3
QH
Pin9(dataoutput)
SPIMISO
PC2
SPI配置代码如下(配置了SPI几个pin脚的定义,时钟,SPI模式等):
  1. void SPI_Digital_Tube_Config(void)
  2. {
  3.         SPI_InitTypeDef  SPI_InitStructure;
  4.   GPIO_InitTypeDef GPIO_InitStructure;
  5.         
  6.           /* Disable the SPI peripheral */
  7.   SPI_Cmd(SPI2, DISABLE);
  8.   /* Enable SCK, MOSI, MISO and NSS GPIO clocks */
  9.         RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);
  10.   RCC_AHBPeriphClockCmd(SPI_Digital_Tube_SCK_GPIO_CLK |
  11.                                                                                                 SPI_Digital_Tube_MOSI_GPIO_CLK|
  12.                                                                                                 SPI_Digital_Tube_NSS_GPIO_CLK, ENABLE);
  13.   
  14.   /* SPI pin mappings */
  15.   GPIO_PinAFConfig(SPI_Digital_Tube_SCK_GPIO_PORT, SPI_Digital_Tube_SCK_SOURCE, SPI_Digital_Tube_SCK_AF);
  16.   GPIO_PinAFConfig(SPI_Digital_Tube_MOSI_GPIO_PORT, SPI_Digital_Tube_MOSI_SOURCE, SPI_Digital_Tube_MOSI_AF);
  17.   GPIO_PinAFConfig(SPI_Digital_Tube_MISO_GPIO_PORT, SPI_Digital_Tube_MISO_SOURCE, SPI_Digital_Tube_MISO_AF);
  18.   GPIO_PinAFConfig(SPI_Digital_Tube_NSS_GPIO_PORT, SPI_Digital_Tube_NSS_SOURCE, SPI_Digital_Tube_NSS_AF);
  19.   
  20.   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
  21.   GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  22.   GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_DOWN;
  23.   GPIO_InitStructure.GPIO_Speed = GPIO_Speed_Level_3;

  24.   /* SPI SCK pin configuration */
  25.   GPIO_InitStructure.GPIO_Pin = SPI_Digital_Tube_SCK_PIN;
  26.   GPIO_Init(SPI_Digital_Tube_SCK_GPIO_PORT, &GPIO_InitStructure);

  27.   /* SPI  MOSI pin configuration */
  28.   GPIO_InitStructure.GPIO_Pin =  SPI_Digital_Tube_MOSI_PIN;
  29.   GPIO_Init(SPI_Digital_Tube_MOSI_GPIO_PORT, &GPIO_InitStructure);

  30.   /* SPI MISO pin configuration */
  31.   GPIO_InitStructure.GPIO_Pin = SPI_Digital_Tube_MISO_PIN;
  32.   GPIO_Init(SPI_Digital_Tube_MISO_GPIO_PORT, &GPIO_InitStructure);
  33.   
  34.   /* SPI NSS pin configuration */
  35.   GPIO_InitStructure.GPIO_Pin = SPI_Digital_Tube_NSS_PIN;
  36.   GPIO_Init(SPI_Digital_Tube_NSS_GPIO_PORT, &GPIO_InitStructure);

  37.         GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
  38.         GPIO_InitStructure.GPIO_Pin = SPI_Digital_Tube_NSS_PIN;
  39.         GPIO_Init(SPI_Digital_Tube_NSS_GPIO_PORT, &GPIO_InitStructure);
  40.   
  41.   /* SPI configuration -------------------------------------------------------*/
  42.   SPI_I2S_DeInit(SPI2);
  43.   SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
  44.   SPI_InitStructure.SPI_DataSize = SPI_DataSize_16b;
  45.   SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
  46.   SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
  47. //  SPI_InitStructure.SPI_NSS = SPI_NSS_Hard;
  48.   SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
  49.   SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;
  50.   SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
  51.   SPI_InitStructure.SPI_CRCPolynomial = 7;
  52.         SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
  53.   SPI_Init(SPI2, &SPI_InitStructure);

  54.   /* Initialize the FIFO threshold */
  55.   SPI_RxFIFOThresholdConfig(SPI2, SPI_RxFIFOThreshold_QF);
  56.   
  57.   /* Enable the SPI peripheral */
  58.   SPI_Cmd(SPI2, ENABLE);
  59.   
  60. //  /* Enable NSS output for master mode */
  61. //  SPI_SSOutputCmd(SPI2, ENABLE);
  62. }
使用TIM6作为定时器,配置代码如下(1ms定时周期):
  1. static void BASIC_TIM_Mode_Config(void)
  2. {
  3.     TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
  4.     BASIC_TIM_APBxClock_FUN(BASIC_TIM_CLK, ENABLE);
  5.     TIM_TimeBaseStructure.TIM_Period = BASIC_TIM_Period;//1ms        
  6.     TIM_TimeBaseStructure.TIM_Prescaler= BASIC_TIM_Prescaler;//47
  7.                 TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
  8.                 TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;
  9.                 TIM_TimeBaseStructure.TIM_RepetitionCounter=0;        
  10.     TIM_TimeBaseInit(BASIC_TIM, &TIM_TimeBaseStructure);        
  11.     TIM_ClearFlag(BASIC_TIM, TIM_FLAG_Update);  
  12.     TIM_ITConfig(BASIC_TIM,TIM_IT_Update,ENABLE);        
  13.     TIM_Cmd(BASIC_TIM, ENABLE);        
  14. }
实际上每次只会有一个数码管亮,为了较好的视觉体验,将数码管进行千位百位十位个位循环显示,这样做的好处是4个数码管轮流显示,其亮度相同,避免出现一个数码管过亮的情形,影响视觉体验。数码管代码如下:
  1. void DisplayNumber(uint16_t num)
  2. {
  3.         uint8_t mythousandNum,myhundredNum,mytenNum,myunitNum=0;
  4.         if(num>9999)num=9999;
  5.         mythousandNum=num/1000%10;
  6.         myhundredNum=num/100%10;
  7.         mytenNum=num/10%10;
  8.          myunitNum=num%10;
  9.         switch(mydisplaybit)
  10.         {
  11.                 case thousaud:
  12.                         Display16(mythousandNum,4);
  13.                         mydisplaybit=hundred;
  14.                 break;
  15.                 case hundred:
  16.                         Display16(myhundredNum,3);
  17.                         mydisplaybit=ten;
  18.                 break;
  19.                 case        ten:
  20.                         Display16(mytenNum,2);
  21.                         mydisplaybit=unit;
  22.                 break;
  23.                 case        unit:
  24.                         Display16(myunitNum,1);
  25.                         mydisplaybit=thousaud;
  26.                 break;
  27.                 default:
  28.                         Display16(mythousandNum,4);
  29.                         mydisplaybit=hundred;
  30.                 break;
  31.         }
  32. }

  33. static void Display16(uint8_t num,uint8_t place)
  34. {
  35.         GPIO_ResetBits(SPI_Digital_Tube_NSS_GPIO_PORT, SPI_Digital_Tube_NSS_PIN);
  36.         uint16_t Temp=((Num[num])<<8)+((0x01)<<(place-1));
  37.         SPI2_Send_Byte16(Temp);
  38.         GPIO_SetBits(SPI_Digital_Tube_NSS_GPIO_PORT, SPI_Digital_Tube_NSS_PIN);
  39. }
然后,每隔0.5s累加一次。在定时器中累计
  1. void TIM6_DAC_IRQHandler()
  2. {
  3.         static uint16_t counter=0;
  4.         static uint16_t num_buffer=0;
  5.         if ( TIM_GetITStatus( BASIC_TIM, TIM_IT_Update) != RESET )
  6.         {        
  7.                 counter++;
  8.                 if(counter>499)
  9.                 {
  10.                         num_buffer++;
  11.                         counter=0;
  12.                 }
  13.                 DisplayNumber(num_buffer);
  14.                 TIM_ClearITPendingBit(BASIC_TIM , TIM_FLAG_Update);                  
  15.         }        
  16. }
显示的效果如下:
所以,初试成功。
试验第二步,让编码器说话。
首先,在STM32中配置编码器。

使用PA6和PA7作为定时器3的通道1和通道2,进行下图模式的计数。

即效果如下:
代码如下
  1. void TIM3_EncoderConfig(void)
  2. {
  3.   TIM_ICInitTypeDef  TIM_ICInitStructure;
  4.         TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
  5.   GPIO_InitTypeDef GPIO_InitStructure;
  6.   NVIC_InitTypeDef NVIC_InitStructure;
  7.   
  8.   HALL_TIM_APBxClock_FUN(ENCODER_TIM_CLK, ENABLE);

  9.   /* GPIOA clock enable */
  10.   RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE); //PA6 & PA7
  11.         RCC_AHBPeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);        
  12.         /* phase A & B*/
  13.   GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_6|GPIO_Pin_7;
  14.   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
  15.   GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  16.   GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  17.   GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
  18.   GPIO_Init(GPIOA, &GPIO_InitStructure);
  19.   GPIO_PinAFConfig(GPIOA, GPIO_PinSource6, GPIO_AF_1);//TIM3_CH1
  20.         GPIO_PinAFConfig(GPIOA, GPIO_PinSource7, GPIO_AF_1);//TIM3_CH2
  21.         
  22.         TIM_DeInit(TIM3);
  23.         TIM_TimeBaseStructure.TIM_Period =0xffff;
  24.   TIM_TimeBaseStructure.TIM_Prescaler =0;
  25.         TIM_TimeBaseStructure.TIM_ClockDivision =TIM_CKD_DIV1;
  26.         TIM_TimeBaseStructure.TIM_CounterMode =TIM_CounterMode_Up;
  27.         TIM_TimeBaseInit(TIM3,&TIM_TimeBaseStructure);
  28.   
  29.   TIM_EncoderInterfaceConfig(TIM3,TIM_EncoderMode_TI12,TIM_ICPolarity_BothEdge,TIM_ICPolarity_BothEdge);
  30.   
  31.         TIM_ICStructInit(&TIM_ICInitStructure);
  32.   TIM_ICInitStructure.TIM_ICFilter = 0;
  33.   TIM_ICInit(TIM3, &TIM_ICInitStructure);
  34.   
  35.   // Clear all pending interrupts
  36.   TIM_ClearFlag(TIM3, TIM_FLAG_Update);
  37.   TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);

  38. //Reset counter
  39.   TIM_SetCounter(TIM3,0);
  40.   TIM_Cmd(TIM3, ENABLE);
  41.         
  42.           /* Enable the TIM1 global Interrupt */
  43.   NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;
  44.   NVIC_InitStructure.NVIC_IRQChannelPriority = 0;
  45.   NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  46.   NVIC_Init(&NVIC_InitStructure);
  47. }
然后在中断服务函数中,将编码器的相对值计算出来,并根据编码器计数的相对变化,计算出电机的转速。具体代码如下:
  1. void TIM6_DAC_IRQHandler()
  2. {
  3.         static uint16_t counter=0;
  4.         static uint16_t num_buffer=0;
  5.         static uint16_t temp_now=0;
  6.         static uint16_t temp_pre=0;
  7.         static uint16_t speed=0;
  8.         if ( TIM_GetITStatus( BASIC_TIM, TIM_IT_Update) != RESET )
  9.         {        
  10.                 counter++;
  11.                 temp_now=(TIM_GetCounter(TIM3)&0xffff);
  12.                 if(counter>499)
  13.                 {
  14.                         num_buffer=(temp_now-temp_pre)>0?temp_now-temp_pre:temp_pre-temp_now;
  15.                         speed=100*num_buffer*60/64;
  16.                         counter=0;
  17.                 }
  18.                 DisplayNumber(speed);
  19.                 if(counter%10==0)temp_pre=temp_now;        
  20.                 TIM_ClearITPendingBit(BASIC_TIM , TIM_FLAG_Update);                  
  21.         }        
  22. }
同时,为了防止TIM3中断溢出,记得清除中断标志位
  1. void TIM3_IRQHandler ()
  2. {  
  3.         if(TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)
  4.         {
  5.                 TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
  6.         }
  7. }
实际效果如下图所示(东西太多,手机不好拍动图,只能静物显示),可知,当电机电压9.32V时,转速为843rpm。当电压为18.7V时,转速为1687rpm。编码器的波形也用示波器显示出来了。还不错哈,哈哈哈



结论

本文使用STM32F0 discovery开发板,完成了编码器计数和电机转速的计算,并通过数码管将电机转速实时显示出来。








本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

×

打赏榜单

21小跑堂 打赏了 100.00 元 2021-03-09
理由:恭喜通过原创奖审核!请多多加油哦!

刘东君 发表于 2021-3-19 16:53 | 显示全部楼层
厉害啊
xyz549040622 发表于 2021-3-22 23:13 | 显示全部楼层
不错,确实讲的比较详细,支持一下。
515192147 发表于 2021-3-29 15:49 | 显示全部楼层
本帖最后由 515192147 于 2021-3-29 15:52 编辑

应该 和 一个步进电机 联调起来,如步进电机 高速给1000个脉冲,收到是1000个脉冲,才能 判断 是否 程序正确?
我们有 GDF103C8T6开发的CAN总线步进电机控制器,带一个编码器,
10000Hz输出,编码器也没有问题



并可开放源码,详细见:
https://bbs.21ic.com/forum.php?mod=viewthread&tid=3003716&page=1&extra=#pid11352836

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

×
您需要登录后才可以回帖 登录 | 注册

本版积分规则

18

主题

274

帖子

4

粉丝
快速回复 在线客服 返回列表 返回顶部