[CW32F003系列] 【CW32F003ExPx StartKit开发板】超强增量伺服编码器测试工具

[复制链接]
3547|4
 楼主| 呐咯密密 发表于 2024-6-7 17:04 | 显示全部楼层 |阅读模式
本帖最后由 呐咯密密 于 2024-6-11 16:24 编辑

子标题:基于CW32F003的OLED+串口助手双显示伺服电机增量式编码器调试工具
前言

有幸参与芯源的开发板测评活动,此次旨在选用官方的开发板完成一次DEMO设计。本人此次的主题是设计一款可以获取增量式编码器的位置和电机旋转速度的测试设备,便于从业人员判断编码器的好坏和编码器的调零工作。获取的编码器数据通过OLED显示屏和串口助手打印,通过串口助手的绘图,更可直观展现位置波动和速度波动。

一、开发要求

1.完成MCU获取编码器脉冲数据。

2.完成开发板和串口助手的通信。

3.完成OLED的显示驱动。

4.完成按键切换速度显示和位置显示模式。

5.数据滤波处理。

二、系统架构
9bcecc55e7a6d4252c8ffd52ba9158af

简单的系统架构如上图所示,输入设备为开发板自带的两个按键,用于切换速度模式和位置模式,单片机通过定时器的编码器接口获取编码器的脉冲信号,OLED显示编码器位置和电机旋转速度,数据同步传输至上位机,可实时显示位置及速度。

三、开发环境
1.CW32F003ExPx StartKit开发板
1ab535782ed6b4ae7aaf630e4d44c85c
2.澜宭LME2500FE磁编码器(4对级,2500线)
98f7755fa57608251f41f2c599c3fc4f
3.0.96OLED显示模块
5452a6a04aa90d1806a3bcb8d04703df
4.创芯工坊PowerWtriter X1烧录器
5.电机旋转对拖台(包括整套的伺服驱动器加伺服电机)
28412c072a28866971a7120946ecd43f
6.纸飞机串口助手,用于图形化显示数据
四、详细流程
2c1694ff2ae377056369ae2f384fa88d

如上图所示,系统上电后完成MCU系统和外设的初始化,KEY1和KEY2亿外部中断的形式判断按键是否被按下。当KEY按下,OLED显示“Position mode”,表示已经进入位置模式,此时启动ATIM的编码器功能,通过编码器A+ B+ 的脉冲信号的数量和相位关系计算编码器位置,因为是4对级2500线,所以旋转一圈是10000个脉冲。该数据会在OLED和串口助手上显示。如果KEY2被按下,则进入速度模式,OLED显示“Speed mode”,此时除了ATIM外,还需启动BTIM1(用于产生高频时钟脉冲)和GTIM(周期获取高频脉冲和编码器脉冲),通过M/T算法计算电机旋转速度。计算后的速度会显示在OLED上,同时进**尔曼滤波后同时将原始速度和滤波后的速度打印在纸飞机串口助手上,便于观察速度波动。

六、代码实现
1.USART
  1. //UARTx
  2. #define  DEBUG_USARTx                   CW_UART1
  3. #define  DEBUG_USART_CLK                RCC_APB2_PERIPH_UART1
  4. #define  DEBUG_USART_APBClkENx          RCC_APBPeriphClk_Enable2
  5. #define  DEBUG_USART_BaudRate           9600
  6. #define  DEBUG_USART_UclkFreq           48000000

  7. //UARTx GPIO
  8. #define  DEBUG_USART_TX_GPIO_CLK        RCC_AHB_PERIPH_GPIOB
  9. #define  DEBUG_USART_RX_GPIO_CLK        RCC_AHB_PERIPH_GPIOB
  10. #define  DEBUG_USART_TX_GPIO_PORT       CW_GPIOB   
  11. #define  DEBUG_USART_TX_GPIO_PIN        GPIO_PIN_1
  12. #define  DEBUG_USART_RX_GPIO_PORT       CW_GPIOB
  13. #define  DEBUG_USART_RX_GPIO_PIN        GPIO_PIN_0

  14. //GPIO AF
  15. #define  DEBUG_USART_AFTX               PB01_AFx_UART1TXD()
  16. #define  DEBUG_USART_AFRX               PB00_AFx_UART1RXD()
  17. void USART_GPIO_Configuration(void)
  18. {
  19.   GPIO_InitTypeDef GPIO_InitStructure;

  20.   //UART TX RX 复用
  21.   DEBUG_USART_AFTX;                     
  22.   DEBUG_USART_AFRX;                     

  23.   GPIO_InitStructure.Pins = DEBUG_USART_TX_GPIO_PIN;
  24.   GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
  25.   GPIO_Init(DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure);
  26.    
  27.   GPIO_InitStructure.Pins = DEBUG_USART_RX_GPIO_PIN;
  28.   GPIO_InitStructure.Mode = GPIO_MODE_INPUT_PULLUP;
  29.   GPIO_Init(DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStructure);
  30. }

  31. /**
  32. * [url=home.php?mod=space&uid=247401]@brief[/url] 配置UART
  33. *
  34. */
  35. void UART_Configuration(void)
  36. {
  37.   USART_InitTypeDef USART_InitStructure;

  38.   USART_InitStructure.USART_BaudRate = DEBUG_USART_BaudRate;
  39.   USART_InitStructure.USART_Over = USART_Over_16;
  40.   USART_InitStructure.USART_Source = USART_Source_PCLK;
  41.   USART_InitStructure.USART_UclkFreq = DEBUG_USART_UclkFreq;
  42.   USART_InitStructure.USART_StartBit = USART_StartBit_FE;
  43.   USART_InitStructure.USART_StopBits = USART_StopBits_1;
  44.   USART_InitStructure.USART_Parity = USART_Parity_No ;
  45.   USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
  46.   USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
  47.   USART_Init(DEBUG_USARTx, &USART_InitStructure);         
  48. }
  49. /**
  50. * [url=home.php?mod=space&uid=247401]@brief[/url] Retargets the C library printf function to the USART.
  51. *
  52. */
  53. PUTCHAR_PROTOTYPE
  54. {
  55.   USART_SendData_8bit(DEBUG_USARTx, (uint8_t)ch);

  56.   while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_TXE) == RESET);

  57.   return ch;
  58. }

板载的串口转USB在硬件上是无电气连接的,此处需要自行跳线处理,此处使用PB0和PB1作为串口通信。波特率使用9600。2.OLED模块

OLED使用软件模拟SPI的方式驱动,硬件连接如下:

SCLK-->PB0DIN-->PB1
RES-->PB3
DC-->PB4
CS-->GND
OLED.C
  1. #include "oled.h"
  2. #include "stdlib.h"
  3. #include "oledfont.h"           
  4. #include "cw32f003_gpio.h"

  5. //OLED的显存
  6. //存放格式如下.
  7. //[0]0 1 2 3 ... 127
  8. //[1]0 1 2 3 ... 127
  9. //[2]0 1 2 3 ... 127
  10. //[3]0 1 2 3 ... 127
  11. //[4]0 1 2 3 ... 127
  12. //[5]0 1 2 3 ... 127
  13. //[6]0 1 2 3 ... 127
  14. //[7]0 1 2 3 ... 127      

  15. #if OLED_MODE==1
  16. //向SSD1106写入一个字节。
  17. //dat:要写入的数据/命令
  18. //cmd:数据/命令标志 0,表示命令;1,表示数据;
  19. void OLED_WR_Byte(u8 dat,u8 cmd)
  20. {
  21. DATAOUT(dat);     
  22. if(cmd)
  23.    OLED_DC_Set();
  24. else
  25.    OLED_DC_Clr();     
  26. OLED_CS_Clr();
  27. OLED_WR_Clr();  
  28. OLED_WR_Set();
  29. OLED_CS_Set();   
  30. OLED_DC_Set();  
  31. }           
  32. #else
  33. //向SSD1106写入一个字节。
  34. //dat:要写入的数据/命令
  35. //cmd:数据/命令标志 0,表示命令;1,表示数据;
  36. void OLED_WR_Byte(uint8_t dat,uint8_t cmd)
  37. {
  38. uint8_t i;     
  39. if(cmd)
  40.    OLED_DC_Set();
  41. else
  42.    OLED_DC_Clr();   
  43. OLED_CS_Clr();
  44. for(i=0;i<8;i++)
  45. {     
  46.   OLED_SCLK_Clr();
  47.   if(dat&0x80)
  48.      OLED_SDIN_Set();
  49.   else
  50.      OLED_SDIN_Clr();
  51.   OLED_SCLK_Set();
  52.   dat<<=1;   
  53. }         
  54. OLED_CS_Set();
  55. OLED_DC_Set();      
  56. }
  57. #endif

  58. void OLED_Set_Pos(unsigned char x, unsigned char y)
  59. {
  60. OLED_WR_Byte(0xb0+y,OLED_CMD);
  61. OLED_WR_Byte(((x&0xf0)>>4)|0x10,OLED_CMD);
  62. OLED_WR_Byte((x&0x0f)|0x01,OLED_CMD);
  63. }      
  64. //开启OLED显示   
  65. void OLED_Display_On(void)
  66. {
  67. OLED_WR_Byte(0X8D,OLED_CMD);  //SET DCDC命令
  68. OLED_WR_Byte(0X14,OLED_CMD);  //DCDC ON
  69. OLED_WR_Byte(0XAF,OLED_CMD);  //DISPLAY ON
  70. }
  71. //关闭OLED显示     
  72. void OLED_Display_Off(void)
  73. {
  74. OLED_WR_Byte(0X8D,OLED_CMD);  //SET DCDC命令
  75. OLED_WR_Byte(0X10,OLED_CMD);  //DCDC OFF
  76. OLED_WR_Byte(0XAE,OLED_CMD);  //DISPLAY OFF
  77. }         
  78. //清屏函数,清完屏,整个屏幕是黑色的!和没点亮一样!!!   
  79. void OLED_Clear(void)  
  80. {  
  81. uint8_t i,n;      
  82. for(i=0;i<8;i++)  
  83. {  
  84.   OLED_WR_Byte (0xb0+i,OLED_CMD);    //设置页地址(0~7)
  85.   OLED_WR_Byte (0x00,OLED_CMD);      //设置显示位置—列低地址
  86.   OLED_WR_Byte (0x10,OLED_CMD);      //设置显示位置—列高地址   
  87.   for(n=0;n<128;n++)OLED_WR_Byte(0,OLED_DATA);
  88. } //更新显示
  89. }

  90. //在指定位置显示一个字符,包括部分字符
  91. //x:0~127
  92. //y:0~63
  93. //mode:0,反白显示;1,正常显示     
  94. //size:选择字体 16/12
  95. void OLED_ShowChar(uint8_t x,uint8_t y,uint8_t chr)
  96. {      
  97. unsigned char c=0,i=0;
  98.   c=chr-' ';//得到偏移后的值   
  99.   if(x>Max_Column-1){x=0;y=y+2;}
  100.   if(SIZE ==16)
  101.    {
  102.    OLED_Set_Pos(x,y);
  103.    for(i=0;i<8;i++)
  104.    OLED_WR_Byte(F8X16[c*16+i],OLED_DATA);
  105.    OLED_Set_Pos(x,y+1);
  106.    for(i=0;i<8;i++)
  107.    OLED_WR_Byte(F8X16[c*16+i+8],OLED_DATA);
  108.    }
  109.    else {
  110.     OLED_Set_Pos(x,y+1);
  111.     for(i=0;i<6;i++)
  112.     OLED_WR_Byte(F6x8[c][i],OLED_DATA);   
  113.    }
  114. }
  115. //m^n函数
  116. uint32_t oled_pow(uint8_t m,uint8_t n)
  117. {
  118. uint32_t result=1;  
  119. while(n--)result*=m;   
  120. return result;
  121. }      
  122. //显示2个数字
  123. //x,y :起点坐标  
  124. //len :数字的位数
  125. //size:字体大小
  126. //mode:模式 0,填充模式;1,叠加模式
  127. //num:数值(0~4294967295);      
  128. void OLED_ShowNum(uint8_t x,uint8_t y,uint32_t num,uint8_t len,uint8_t size)
  129. {         
  130. uint8_t t,temp;
  131. uint8_t enshow=0;         
  132. for(t=0;t<len;t++)
  133. {
  134.   temp=(num/oled_pow(10,len-t-1))%10;
  135.   if(enshow==0&&t<(len-1))
  136.   {
  137.    if(temp==0)
  138.    {
  139.     OLED_ShowChar(x+(size/2)*t,y,' ');
  140.     continue;
  141.    }else enshow=1;
  142.      
  143.   }
  144.    OLED_ShowChar(x+(size/2)*t,y,temp+'0');
  145. }
  146. }
  147. //显示一个字符号串
  148. void OLED_ShowString(uint8_t x,uint8_t y,uint8_t *chr)
  149. {
  150. unsigned char j=0;
  151. while (chr[j]!='\0')
  152. {  OLED_ShowChar(x,y,chr[j]);
  153.    x+=8;
  154.   if(x>120){x=0;y+=2;}
  155.    j++;
  156. }
  157. }
  158. //显示汉字
  159. void OLED_ShowCHinese(uint8_t x,uint8_t y,uint8_t no)
  160. {            
  161. uint8_t t,adder=0;
  162. OLED_Set_Pos(x,y);
  163.     for(t=0;t<16;t++)
  164.   {
  165.     OLED_WR_Byte(Hzk[2*no][t],OLED_DATA);
  166.     adder+=1;
  167.      }
  168.   OLED_Set_Pos(x,y+1);
  169.     for(t=0;t<16;t++)
  170.    {
  171.     OLED_WR_Byte(Hzk[2*no+1][t],OLED_DATA);
  172.     adder+=1;
  173.       }     
  174. }
  175. /***********功能描述:显示显示BMP图片128×64起始点坐标(x,y),x的范围0~127,y为页的范围0~7*****************/
  176. void OLED_DrawBMP(unsigned char x0, unsigned char y0,unsigned char x1, unsigned char y1,unsigned char BMP[])
  177. {  
  178. unsigned int j=0;
  179. unsigned char x,y;
  180.   
  181.   if(y1%8==0) y=y1/8;      
  182.   else y=y1/8+1;
  183. for(y=y0;y<y1;y++)
  184. {
  185.   OLED_Set_Pos(x0,y);
  186.     for(x=x0;x<x1;x++)
  187.      {      
  188.       OLED_WR_Byte(BMP[j++],OLED_DATA);      
  189.      }
  190. }
  191. }

  192. //初始化SSD1306         
  193. void OLED_Init(void)
  194. {   
  195.           GPIO_InitTypeDef GPIO_InitStruct;

  196.         GPIO_InitStruct.IT = GPIO_IT_NONE;
  197.         GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  198.         GPIO_InitStruct.Pins = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_3 | GPIO_PIN_4 | GPIO_PIN_5;
  199.         GPIO_Init(CW_GPIOA, &GPIO_InitStruct);
  200.   
  201.   OLED_RST_Set();
  202.         FirmwareDelay(500000);
  203.   OLED_RST_Clr();
  204. FirmwareDelay(200000);
  205.   OLED_RST_Set();        
  206.   
  207.   OLED_WR_Byte(0xAE,OLED_CMD);//--turn off oled panel
  208.   OLED_WR_Byte(0x00,OLED_CMD);//---set low column address
  209.   OLED_WR_Byte(0x10,OLED_CMD);//---set high column address
  210.   OLED_WR_Byte(0x40,OLED_CMD);//--set start line address  Set Mapping RAM     Display Start Line (0x00~0x3F)
  211. OLED_WR_Byte(0x81,OLED_CMD);//--set contrast control register
  212. OLED_WR_Byte(0xCF,OLED_CMD); // Set SEG Output Current Brightness
  213. OLED_WR_Byte(0xA1,OLED_CMD);//--Set SEG/Column Mapping     0xa0左右反置 0xa1正常
  214. OLED_WR_Byte(0xC8,OLED_CMD);//Set COM/Row Scan Direction   0xc0上下反置 0xc8正常
  215. OLED_WR_Byte(0xA6,OLED_CMD);//--set normal display
  216. OLED_WR_Byte(0xA8,OLED_CMD);//--set multiplex ratio(1 to 64)
  217. OLED_WR_Byte(0x3f,OLED_CMD);//--1/64 duty
  218. OLED_WR_Byte(0xD3,OLED_CMD);//-set display offset Shift Mapping RAM Counter (0x00~0x3F)
  219. OLED_WR_Byte(0x00,OLED_CMD);//-not offset
  220. OLED_WR_Byte(0xd5,OLED_CMD);//--set display clock divide ratio/oscillator frequency
  221. OLED_WR_Byte(0x80,OLED_CMD);//--set divide ratio, Set Clock as 100 Frames/Sec
  222. OLED_WR_Byte(0xD9,OLED_CMD);//--set pre-charge period
  223. OLED_WR_Byte(0xF1,OLED_CMD);//Set Pre-Charge as 15 Clocks & Discharge as 1 Clock
  224. OLED_WR_Byte(0xDA,OLED_CMD);//--set com pins hardware configuration
  225. OLED_WR_Byte(0x12,OLED_CMD);
  226. OLED_WR_Byte(0xDB,OLED_CMD);//--set vcomh
  227. OLED_WR_Byte(0x40,OLED_CMD);//Set VCOM Deselect Level
  228. OLED_WR_Byte(0x20,OLED_CMD);//-Set Page Addressing Mode (0x00/0x01/0x02)
  229. OLED_WR_Byte(0x02,OLED_CMD);//
  230. OLED_WR_Byte(0x8D,OLED_CMD);//--set Charge Pump enable/disable
  231. OLED_WR_Byte(0x14,OLED_CMD);//--set(0x10) disable
  232. OLED_WR_Byte(0xA4,OLED_CMD);// Disable Entire Display On (0xa4/0xa5)
  233. OLED_WR_Byte(0xA6,OLED_CMD);// Disable Inverse Display On (0xa6/a7)
  234. OLED_WR_Byte(0xAF,OLED_CMD);//--turn on oled panel
  235. OLED_WR_Byte(0xAF,OLED_CMD); /*display ON*/
  236. OLED_Clear();
  237. OLED_Set_Pos(0,0);  
  238. }  



OLED.H
  1. #ifndef __OLED_H
  2. #define __OLED_H
  3. #include "cw32f003.h"



  4. //OLED模式设置
  5. //0:4线串行模式
  6. //1:并行8080模式
  7. #define OLED_MODE 0
  8. #define SIZE 16
  9. #define XLevelL  0x00
  10. #define XLevelH  0x10
  11. #define Max_Column 128
  12. #define Max_Row  64
  13. #define Brightness 0xFF
  14. #define X_WIDTH  128
  15. #define Y_WIDTH  64   

  16. //-----------------OLED端口定义----------------         
  17. #define OLED_SCLK_Clr() (CW_GPIOA->BRR=bv0)//CLK
  18. #define OLED_SCLK_Set() (CW_GPIOA->BSRR=bv0)

  19. #define OLED_SDIN_Clr() (CW_GPIOA->BRR=bv1)//DIN
  20. #define OLED_SDIN_Set() (CW_GPIOA->BSRR=bv1)

  21. #define OLED_RST_Clr() (CW_GPIOA->BRR=bv3)//RES
  22. #define OLED_RST_Set() (CW_GPIOA->BSRR=bv3)

  23. #define OLED_DC_Clr() (CW_GPIOA->BRR=bv4)//DC
  24. #define OLED_DC_Set() (CW_GPIOA->BSRR=bv4)      

  25. #define OLED_CS_Clr()  (CW_GPIOA->BRR=bv5)//CS
  26. #define OLED_CS_Set()  (CW_GPIOA->BSRR=bv5)

  27. #define OLED_CMD  0 //写命令
  28. #define OLED_DATA 1 //写数据

  29. //OLED控制用函数
  30. void OLED_WR_Byte(uint8_t dat,uint8_t cmd);     
  31. void OLED_Display_On(void);
  32. void OLED_Display_Off(void);                    
  33. void OLED_Init(void);
  34. void OLED_Clear(void);
  35. void OLED_DrawPoint(uint8_t x,uint8_t y,uint8_t t);
  36. void OLED_Fill(uint8_t x1,uint8_t y1,uint8_t x2,uint8_t y2,uint8_t dot);
  37. void OLED_ShowChar(uint8_t x,uint8_t y,uint8_t chr);
  38. void OLED_ShowNum(uint8_t x,uint8_t y,uint32_t num,uint8_t len,uint8_t size);
  39. void OLED_ShowString(uint8_t x,uint8_t y, uint8_t *p);  
  40. void OLED_Set_Pos(unsigned char x, unsigned char y);
  41. void OLED_ShowCHinese(uint8_t x,uint8_t y,uint8_t no);
  42. void OLED_DrawBMP(unsigned char x0, unsigned char y0,unsigned char x1, unsigned char y1,unsigned char BMP[]);

  43. #endif /* __MAIN_H */


3.按键驱动
按键直接采用了板载的按键,分别是KEY1-->PB5,KEY2-->PB6,采用中断的方式获取按键状态,用于切换速度和位置模式。
  1. void GPIOB_IRQHandlerCallback(void)
  2. {
  3.     if (CW_GPIOB->ISR_f.PIN5)
  4.     {
  5.         GPIOB_INTFLAG_CLR(bv5);
  6.                 //KEY1按下,进入位置模式
  7.                 Key_Flag = 1;
  8.                 CW_ATIM->CNT = 0;
  9.                 CW_GTIM->CNT = 0;
  10.                 CW_BTIM1->CNT = 0;
  11.                 ATIM_Cmd(DISABLE);
  12.                 GTIM_Cmd(DISABLE);
  13.                 BTIM_Cmd(CW_BTIM1, DISABLE);
  14.                 ATIM_configuration(9999);
  15.                 OLED_Clear();
  16.                 OLED_ShowString(0,0,"Position mode");        
  17.     }

  18.     if (CW_GPIOB->ISR_f.PIN6)
  19.     {
  20.         GPIOB_INTFLAG_CLR(bv6);
  21.                 //KEY2按下,进入速度模式
  22.                 Key_Flag = 2;
  23.                 CW_ATIM->CNT = 0;
  24.                 CW_GTIM->CNT = 0;
  25.                 CW_BTIM1->CNT = 0;
  26.                 BTIM_configuration();
  27.                 ATIM_configuration(65535);
  28.                 GTIM_configuration();
  29.                 OLED_Clear();
  30.                 OLED_ShowString(0,0,"Speed mode");               
  31.     }
  32. }


4.定时器
定时器部分为核心功能,速度和位置的数据来源均在此获取。
M/T法测速原理
以采样周期为基准,在采样时间T内,同时计算编码器输出的脉冲数量M1和高频脉冲数量M2,同时尽量保持两个计数时间的的严格同步,最大限度减小误差。
0424d6ab6edf7102ac86cd926479c51d
设电机旋转一圈编码器输出脉冲数为p,高频脉冲频率为fc,T时间内,高频脉冲计数为M2,编码器输出脉冲数为M1,则此时T时间内电机旋转圈数:
63bf32fdc69574bd9474a0ee758184c3
一秒电机旋转圈数为:
1e1def81bc2cd9b588498b60a03027fd
每分钟的转速为:
14c63ef5a0623a92fb125fc2184066c6
T可通过高频脉冲计数原理求得:
6ec8daa7b9bfba836442eff2879880d9
所以最终电机转速为:
6ec8daa7b9bfba836442eff2879880d9
在此应用中
/****CW_BTIM1  用于产生高频时钟脉冲****/
/****CW_ATIM  用于捕获编码器脉冲数****/
/****CW_GTIM  用于周期获取脉冲数和高频时钟脉冲****/

定时器CW_ATIM使用编码器功能,捕获M1的值,使用定时器CW_BTIM1产生高频脉冲,每当计数器到达就触发中断,获取M2的值,最后在定时器CW_GTIM中定时获取M1和M2的值,在主函数打印。

  1. /****CW_BTIM1  用于产生高频时钟脉冲****/
  2. void BTIM_configuration(void)
  3. {
  4.         BTIM_TimeBaseInitTypeDef BTIM_TimeBaseInitStruct;
  5.         BTIM_TimeBaseInitStruct.BTIM_Mode = BTIM_Mode_TIMER;
  6.         BTIM_TimeBaseInitStruct.BTIM_Period = 239;
  7.         BTIM_TimeBaseInitStruct.BTIM_Prescaler = BTIM_PRS_DIV1;

  8.         BTIM_TimeBaseInit(CW_BTIM1, &BTIM_TimeBaseInitStruct);
  9.         BTIM_ITConfig(CW_BTIM1, BTIM_IT_OV, ENABLE);
  10.         BTIM_Cmd(CW_BTIM1, ENABLE);
  11. }
  12. /****CW_ATIM  用于捕获编码器脉冲数****/
  13. void ATIM_configuration(uint32_t ReloadValue)
  14. {
  15.         ATIM_InitTypeDef ATIM_InitStruct;

  16.         ATIM_InitStruct.BufferState = DISABLE;
  17.         ATIM_InitStruct.ClockSelect = ATIM_CLOCK_PCLK;
  18.         ATIM_InitStruct.CounterAlignedMode = ATIM_COUNT_MODE_EDGE_ALIGN;
  19.         ATIM_InitStruct.CounterDirection = ATIM_COUNTING_UP;
  20.         ATIM_InitStruct.CounterOPMode = ATIM_OP_MODE_REPETITIVE;
  21.         ATIM_InitStruct.OverFlowMask = DISABLE;
  22.         ATIM_InitStruct.Prescaler = ATIM_Prescaler_DIV1;
  23.         ATIM_InitStruct.ReloadValue = ReloadValue;
  24.         ATIM_InitStruct.RepetitionCounter = 0;
  25.         ATIM_InitStruct.UnderFlowMask = DISABLE;

  26.         ATIM_Init(&ATIM_InitStruct);
  27.         ATIM_SlaverModeConfig(ATIM_SLAVER_TYPE_ENCODE3);
  28.         ATIM_TriggerSelect(ATIM_TRIGGER_SOURCE_IAFP);
  29.         ATIM_Cmd(ENABLE);

  30. }
  31. /****CW_GTIM  用于周期获取脉冲数和高频时钟脉冲****/
  32. void GTIM_configuration(void)
  33. {
  34.         GTIM_InitTypeDef GTIM_InitStruct;
  35.         BTIM_TimeBaseInitTypeDef BTIM_InitStruct;

  36.         GTIM_InitStruct.Mode = GTIM_MODE_TIME;
  37.         GTIM_InitStruct.OneShotMode = GTIM_COUNT_CONTINUE;
  38.         GTIM_InitStruct.Prescaler = GTIM_PRESCALER_DIV32;
  39.         GTIM_InitStruct.ReloadValue = 1499;
  40.         GTIM_InitStruct.ToggleOutState = DISABLE;
  41.         GTIM_TimeBaseInit(>IM_InitStruct);

  42.         GTIM_ITConfig(GTIM_IT_OV, ENABLE);

  43.         GTIM_Cmd(ENABLE);
  44. }

  45. void GTIM_IRQHandlerCallBack(void)
  46. {
  47.   if (GTIM_GetITStatus(GTIM_IT_OV))
  48.   {
  49.     GTIM_ClearITPendingBit(GTIM_IT_OV);
  50. //    PA07_TOG();
  51.                 m1 = CW_ATIM->CNT;
  52.                 if(TIM_NUM<point_num)
  53.                 {
  54.                         m1_data[TIM_NUM] = m1;
  55.                         m2_data[TIM_NUM] = m2;
  56.                         TIM_NUM++;
  57.                         m1 = 0;
  58.                         m2 = 0;
  59.                 }else
  60.                 {
  61.                         ATIM_Cmd(DISABLE);
  62.                         GTIM_Cmd(DISABLE);
  63.                         BTIM_Cmd(CW_BTIM1, DISABLE);
  64.                 }         
  65.   }
  66. }

  67. void BTIM1_IRQHandler(void)
  68. {
  69.   /* USER CODE BEGIN */
  70.   if (BTIM_GetITStatus(CW_BTIM1, BTIM_IT_OV))
  71.   {
  72.         BTIM_ClearITPendingBit(CW_BTIM1, BTIM_IT_OV);
  73.         m2++;
  74.   }
  75.   /* USER CODE END */
  76. }


CW_ATIM 作为高级定时器,拥有编码器的功能,我们将其通道ATIM_CH1A和通道ATIM_CH1B接到编码器的A和B相。编码器功能属于定时器的从模式,通过函数ATIM_SlaverModeConfig(ATIM_SLAVER_TYPE_ENCODE3);这是为编码器模式3。

CW_BTIM1 用于产生一个100K的高频时钟脉冲,并使能溢出中断,每产生一个脉冲则将M2的值累加1;

CW_GTIM  用于周期获取脉冲数和高频时钟脉冲数,并使能溢出中断,中断到达后读取CW_ATIM的计数值,该值的变化量与编码器输出脉冲相同,计算两次之间的差值即为每次采样脉冲间隔的编码器脉冲数。中断中将M1和M2的值存入数组,在主函数进行速度计算并输出。

  1.   while(1)
  2.   {

  3.          if(Key_Flag == 2)
  4.          {
  5.                 if(TIM_NUM>point_num-2)
  6.                 {                        
  7.                         for(uint16_t p=5;p<point_num-5;p++)
  8.                         {
  9.                                 m1calc = m1_data[p]-m1_data[p+1];
  10.                                 if(m1_data[p]<m1_data[p+1])
  11.                                 {
  12.                                         m1calc = m1calc+65535;                                                
  13.                                 }
  14.                                 Speed_num[p] = (float)(60*m1calc*20)/(m2_data[p]);
  15.                                 kalman_height = kalmanFilter(&KFP_height,Speed_num[p]);
  16.         //                                                printf("{Speed:%d,%d,%d}\n",(int)(Speed_num[p]+0.5),m1calc,m2_data[p]);
  17.                                 printf("{Speed:%d,%d}\n",(int)Speed_num[p],(int)(kalman_height+0.5));                                       
  18.                         }
  19.                         OLED_ShowNum(0,3,(int)Speed_num[50],4,16);
  20.                         TIM_NUM = 0;
  21.                         ATIM_Cmd(ENABLE);
  22.                         GTIM_Cmd(ENABLE);
  23.                         BTIM_Cmd(CW_BTIM1, ENABLE);
  24.                 }         
  25.          }else if(Key_Flag == 1)
  26.          {
  27.                 Plus_Value = CW_ATIM->CNT;
  28.                  printf("{Angle Value: %d}\n",Plus_Value);
  29.                  if( (GetTick() - cnt) >10000)
  30.                  {
  31.                         cnt = GetTick();
  32.                  }
  33.                  OLED_ShowNum(0,3,Plus_Value,4,16);
  34.          }

  35.   }
  36. }


案例中:m1calc为M1,100000为fc,即编码器输出脉冲数。m2_data[p]为M2,即高频脉冲计数。kalmanFilter()函数为卡尔曼滤波器。
  1. //1. 结构体类型定义
  2. typedef struct
  3. {
  4.     float LastP;//上次估算协方差 初始化值为0.02
  5.     float Now_P;//当前估算协方差 初始化值为0
  6.     float out;//卡尔曼滤波器输出 初始化值为0
  7.     float Kg;//卡尔曼增益 初始化值为0
  8.     float Q;//过程噪声协方差 初始化值为0.001
  9.     float R;//观测噪声协方差 初始化值为0.543
  10. }KFP;//Kalman Filter parameter

  11. //2. 以速度为例 定义卡尔曼结构体并初始化参数
  12. KFP KFP_height={0.02,0,3000,0,0.001,0.005};

  13. /**
  14. *卡尔曼滤波器
  15. *@param KFP *kfp 卡尔曼结构体参数
  16. *   float input 需要滤波的参数的测量值(即传感器的采集值)
  17. *[url=home.php?mod=space&uid=266161]@return[/url] 滤波后的参数(最优值)
  18. */
  19. float kalmanFilter(KFP *kfp,float input)
  20. {
  21.      //预测协方差方程:k时刻系统估算协方差 = k-1时刻的系统协方差 + 过程噪声协方差
  22.      kfp->Now_P = kfp->LastP + kfp->Q;
  23.      //卡尔曼增益方程:卡尔曼增益 = k时刻系统估算协方差 / (k时刻系统估算协方差 + 观测噪声协方差)
  24.      kfp->Kg = kfp->Now_P / (kfp->Now_P + kfp->R);
  25.      //更新最优值方程:k时刻状态变量的最优值 = 状态变量的预测值 + 卡尔曼增益 * (测量值 - 状态变量的预测值)
  26.      kfp->out = kfp->out + kfp->Kg * (input -kfp->out);//因为这一次的预测值就是上一次的输出值
  27.      //更新协方差方程: 本次的系统协方差付给 kfp->LastP 威下一次运算准备。
  28.      kfp->LastP = (1-kfp->Kg) * kfp->Now_P;
  29.      return kfp->out;
  30. }


最终效果如下:
位置模式:
e48280df5255d4024438b0f763dd46f1
ecb4757cfd8c721593ca87ff7160d752
速度模式:
7d075f52534e00dad94dc7ba07cf2703
713b40d707f4441e6af08cf44a3fff65


[media=x,500,375]【基于芯源CW32F003ExPx StartKit开发板的增量编码器测试工具】 https://www.bilibili.com/video/B ... 4fb109c73151ce629de[/media]

CW32F003_Encode_Test.rar (6.29 MB, 下载次数: 6)


小小蚂蚁举千斤 发表于 2024-6-11 16:00 | 显示全部楼层
这个应用非常不错,对于MCU来说读取脉冲数也非常重要
AdaMaYun 发表于 2024-6-13 20:34 | 显示全部楼层
读取脉冲是MCU内部读取的?
OKAKAKO 发表于 2024-6-21 21:20 | 显示全部楼层
楼主应该就是计数器的原理?
中国龙芯CDX 发表于 2024-6-26 16:35 | 显示全部楼层
基于CW32F003的OLED+串口助手双显示伺服电机增量式编码器调试工具
您需要登录后才可以回帖 登录 | 注册

本版积分规则

认证:苏州澜宭自动化科技嵌入式工程师
简介:本人从事磁编码器研发工作,负责开发2500线增量式磁编码器以及17位、23位绝对值式磁编码器,拥有多年嵌入式开发经验,精通STM32、GD32、N32等多种品牌单片机,熟练使用单片机各种外设。

568

主题

4085

帖子

56

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