)
一、编码器的通讯协议参考某品牌编码器的规格书做一些简单的说明。编码器通讯线缆的定义如下编码器通讯时串口的波特率为2500000bits/秒2.5M数据位8位奇偶校验位无停止位1位。每次通讯的数据帧分为若干字节每个字节的发送和接收由 1 个起始位、8 个数据位和 1 个停止位来实现低位在前高位在后。二、编码器数据解析伺服电机驱动器和伺服电机 的编码器之间通电之后做了很多信息的沟通我们可以在驱动器和编码器之间的通讯线缆上把485通讯的数据线D和D-上搭接2根线出来接到485转USB的通讯器上通讯器需要支持2.5M的波特率注意有些使用的通讯器注明可以支持3M或6M但是波特率大于1M时只支持1M、2M、3M、4M、5M、6M这几个整数波特率并不支持2.5M的波特率然后用串口助手软件就可以监听到驱动器和编码器之间都交换了哪些数据信息如下图上图中刚开始几个字节不是一个完整的通讯帧数据从第7个字节开始才是完整的通讯帧1A 1A 20 19 F9 0A 17 FF FF FF C0 F2。 驱动器1A 编码器1A 20 19 F9 0A 17 FF FF FF C0 F2。驱动器上电启起初始化之后驱动器发送1A指令给编码器编码器回复1A 20 19 F9 0A 17 FF FF FF C0 F2其中第1个字节1A是命令码请求全角度数据第2个字节20是状态位转换成二进制就是0010 0000其中Bit5位有异常因此这位被置1。参考上面的图片说明Bit5置1是下面3个状态多圈计数错误、电池故障、电池电压低。第3~5个字节19 F9 0A 是电机当前的角度低位在前高位在后所以要交换一下变成0A F9 19 换算成十进制是719129这个编码器是21位编码器所以当前角度是719129*360/8388608 30.86166859度第6个字节是编码器IDID是17第7~9个字节FF FF FF是电机转动的圈数当前的圈数是65535圈注意此处是16进制能记录的最大圈数是2个字节65535圈第10个字节是报警位00表示一切正常如果有异常某位会被置1接收的数据就不是00此处是C0转换成二进制就是1100 0000参考上面的图片说明Bit6和Bit7置1表示电池故障和电池电压低应该是编码器没有接上电池所致。第11个字节是CRCF2驱动器会核对CRC来判断编码器回复的信息是否正确如果正确驱动器会使用此数据如果CRC错误驱动器应该会丢弃此条数据信息然后给出驱动器的报警代码。上电之后的第一条1A指令实际是用于测试编码器工作是否正常编码器和驱动器之间的通讯是否正常。如果正常接下来驱动器一般读取编码器的EEPROM里面所保存的电机参数信息。三、编码器USART模块接收指令之后回复数据的两种处理方式MCU通过USART模块接收的指令集中包含不同的单字节指令和不同长度的多字节指令根据接收的不同指令再回复相对应的数据信息。USART模块的波特率定义为2.5M、数据宽度为8位、开始位1位、停止位1位、无奇偶校验位所以接收一个字节是10个Bit2.5M的波特率时接收或发送一个字节的时间是4微秒驱动器发送完一个指令之后通常要求编码器尽快回复数据中间的间隔时间要求小于6微秒当然间隔时间越短越好。编码器只有接收完第一个指令之后才知道需要继续接收指令还是要回复数据同时编码器的MCU要AD采样以计算角度和多圈计数、确定是否需要报警位置1等许多计算工作因此回复数据必须使用DMA的发送方式来释放MCU的使用时间通常USART模块接收指令回复数据常见的处理方法有以下2种方式1. 使用USART串口空闲中断DMA接收模式实现以上要求上位机停止发送指令之后USART模块一段时间没有收到数据就会触发USART的串口空闲中断在中断函数里再根据DMA接收的数据内容进行相应的处理。在这里DMA接收时设置的接收字节数量需要大于上位机发送的指令字节数量例如上位机发送的指令字节数量最大是12个字节DMA接收的字节数量可以设置成13个字节以上否则在USART的串口空闲中断触发之前就会先触发DMA接收完成中断如果确实需要接收很多数据时可以在DMA接收完成中断函数里面把接收到这些数据做好处理清空DMA接收缓冲区继续接收上位机传送的下一段数据这个问题不在本文中进行讨论。USART模块的Rx端口的状态转换为停止位状态表示当前字节接收完毕停止位状态持续一段时间没有收到下一个字节时才会触发空闲中断进入空闲中断触发函数里面才会根据接收的指令内容再打开USART模块的DMA发送方式回复数据。USART模块接收完最后一个字节到触发串口空闲中断中间的间隔时间至少是大于一个字节的接收时间的对于2.5M的波特率一个字节的接收时间是4微秒因此USART模块接收完最后一个字节到触发串口空闲中断进入串口空闲中断函数里面根据指令字节再回复相应有的数据内容这中间的间隔时间实际测量下来至少会大于8微秒以上通常不能满足编码器产品的应用需求驱动器通常把这个间隔时间设置为小于10微秒一般是连续4次或10次超过10微秒没有收到编码器的回复信息就判断编码器为通讯故障。2. 使用USART字节接收中断进入中断函数之后由于根据接收的第一个字节内容可以明确知道此次USART模块接收的是单字节指令还是几个字节长度的多字节指令如果是单字节指令则打开USART模块的DMA发送方式回复数据如果是多字节指令则打开USART模块的DMA接收方式接收更多的指令内容。使用USART字节接收中断接收完第一个字节进入中断函数之后根据接收的第一个字节内容判断是多字节指令打开DMA接收模式继续接收上位机的多字节指令内容。实际应用的时候发现NXP的MKL16、MKV10、MKV30等系列芯片都可以接收到完整的上位机多字节指令内容。而国产的多款MCU在实际应用中USART模块接收完第一个字节触发接收中断之后在中断函数里面再打开USART模块的DMA接收方式或许是DMA模块的速度问题DMA接收方式无法正确接收到多字节指令的第2个字节指令从而导致MCU无法正确回复数据。此处要想接收到完整的多字节指令内容可以使用以下方法来处理1. 之前就一直打开DMA接收根据接收的第一个字节内容可以明确知道后面需要接收多字节指令的字节数量因此可以根据接收的第一个字节内容设置定时在DMA接收完全部指令之后再根据指令内容及时回复相应的数据内容2. 可以使用2个USART模块USART1开启字节接收中断USART2开启DMA接收此处由于事前不知道DMA需要接收的字节数量因此也只能在USART1字节接收中断函数里面设置定时定时到了之后就关闭USART2的DMA接收功能。四、一种编码器USART快速回复数据的处理方法国产MCU在使用上述两种方法处理USART回复数据时很难将间隔时间做到8微秒以下这里介绍一种处理方式来缩短编码器的间隔时间。如下图所示硬件上将USART模块的Rx端口同时接入另外一个I/O端口软件上打开USART模块的DMA接收方式关闭USART模块和接收相关的所有中断设置关闭DMA接收相关的所有中断同时设置I/O端口A的下降沿中断当USART模块接收到数据时I/O端口A会产生下降沿中断USART模块的DMA接收方式会接收到所有的指令字节。在I/O端口A产生的下降沿中断函数里面先打开USART模块的发送使能延迟3微秒左右等待USART模块接收完第一个字节波特率2.5M时USART模块接收一个字节需要4微秒但是进入下降沿中断函数打开USART模块的发送使能都需要一点点时间延迟时间设置为4微秒减去这一点点时间就可以接收完第一个字节了因此可以适当调整此延迟时间以在接收完第一个字节的瞬间读取此字节内容进行判断根据接收到的第一个字节内容决定是否关闭USART模块的接收功能和DMA接收操作或者是继续延迟不同的时间以等待DMA接收完所有的指令字节。如下图所示接收完数据之后可以在3微秒之内回复数据主要实现代码如下// //函数名称enc_usart_com_init//函数返回//参数说明//功能概要编码器电路板初始化usart串口com函数// void enc_usart_com_init(void){GPIO_Config_T GPIO_configStruct;//使能GPIO端口时钟和Usart1模块时钟RCM_EnableAPB2PeriphClock( ENC_COM_USART_PORT_CLK | ENC_COM_USART_CLK );//Configure USART Tx as alternate function push-pullGPIO_configStruct.mode ENC_COM_USART_AF_PP;GPIO_configStruct.pin ENC_COM_USART_TX_PIN;GPIO_configStruct.speed GPIO_SPEED_50MHz;GPIO_Config(ENC_COM_USART_PORT, GPIO_configStruct);//Configure USART Rx as input floatingGPIO_configStruct.mode GPIO_MODE_IN_FLOATING;GPIO_configStruct.pin ENC_COM_USART_RX_PIN;GPIO_Config(ENC_COM_USART_PORT, GPIO_configStruct);USART_Config_T USART_ConfigStruct;USART_ConfigStruct.baudRate ENC_COM_USART_BAND_25M;USART_ConfigStruct.hardwareFlow USART_HARDWARE_FLOW_NONE;USART_ConfigStruct.mode USART_MODE_TX_RX;USART_ConfigStruct.parity USART_PARITY_NONE;USART_ConfigStruct.stopBits USART_STOP_BIT_1;USART_ConfigStruct.wordLength USART_WORD_LEN_8B;//设置串口 USART configurationUSART_Config(ENC_COM_USART, USART_ConfigStruct);//先关闭USART串口功能模块USART_Disable(ENC_COM_USART);}// //函数名称enc_usart_dma_tx_init//函数返回//参数说明//功能概要编码器串口usart的dma发送初始化函数// void enc_usart_dma_tx_init(void){DMA_Config_T dmaConfig;//使能DMA模块的时钟: AMP32F103只有1个DMA1模块RCM_EnableAHBPeriphClock(RCM_AHB_PERIPH_DMA1);//定义DMA设置参数dmaConfig.peripheralBaseAddr ENC_COM_USART_TDATA_ADDRESS;dmaConfig.memoryBaseAddr (uint32_t)EncUsartDataBuff;dmaConfig.dir DMA_DIR_PERIPHERAL_DST;dmaConfig.bufferSize (uint32_t)ENC_USART_DMA_DataSize;dmaConfig.peripheralInc DMA_PERIPHERAL_INC_DISABLE;dmaConfig.memoryInc DMA_MEMORY_INC_ENABLE;dmaConfig.peripheralDataSize DMA_PERIPHERAL_DATA_SIZE_BYTE;dmaConfig.memoryDataSize DMA_MEMORY_DATA_SIZE_BYTE;dmaConfig.loopMode DMA_MODE_NORMAL;dmaConfig.priority DMA_PRIORITY_HIGH;dmaConfig.M2M DMA_M2MEN_DISABLE;//设置DMA1通道4的配置参数DMA_Config(ENC_COM_USART_DMA_CHANNEL_TX, dmaConfig);//先禁用DMA1的通道4DMA_Disable(ENC_COM_USART_DMA_CHANNEL_TX);}// //函数名称enc_usart_dma_rx_init//函数返回//参数说明//功能概要编码器串口usart的接收dma初始化函数// void enc_usart_dma_rx_init(void){DMA_Config_T dmaConfig;//使能DMA模块的时钟: AMP32F103只有1个DMA1模块RCM_EnableAHBPeriphClock(RCM_AHB_PERIPH_DMA1);//DMA config//dmaConfig.peripheralBaseAddr ENC_COM_USART_RDATA_ADDRESS;dmaConfig.memoryBaseAddr (uint32_t)EncUsartDataBuff;dmaConfig.dir DMA_DIR_PERIPHERAL_SRC;dmaConfig.bufferSize (uint32_t)ENC_USART_DMA_DataSize;dmaConfig.peripheralInc DMA_PERIPHERAL_INC_DISABLE;dmaConfig.memoryInc DMA_MEMORY_INC_ENABLE;dmaConfig.peripheralDataSize DMA_PERIPHERAL_DATA_SIZE_BYTE;dmaConfig.memoryDataSize DMA_MEMORY_DATA_SIZE_BYTE;dmaConfig.loopMode DMA_MODE_NORMAL;dmaConfig.priority DMA_PRIORITY_MEDIUM;dmaConfig.M2M DMA_M2MEN_DISABLE;//设置DMA1通道5的配置参数DMA_Config(ENC_COM_USART_DMA_CHANNEL_RX, dmaConfig);//先禁用DMA1的通道5DMA_Disable(ENC_COM_USART_DMA_CHANNEL_RX);}////函数名称enc_uart_rxisr_pin_init//函数返回//参数说明//功能概要Usart1的Rx引脚同时接入PA15利用下降沿产生接收中断//void enc_uart_rxisr_pin_init(void){GPIO_Config_T GPIO_configStruct;EINT_Config_T EINT_configStruct;//使能Usart接收Rx引脚端口的时钟RCM_EnableAPB2PeriphClock( RCM_APB2_PERIPH_AFIO );//使能Usart接收Rx引脚端口的时钟RCM_EnableAPB2PeriphClock( ENC_USART_RxISR_GPIO_CLK );//定义Usart接收Rx引脚端口为浮接输入功能GPIO_configStruct.mode GPIO_MODE_IN_PU;GPIO_configStruct.pin ENC_USART_RxISR_PIN;GPIO_Config(ENC_USART_RxISR_GPIO_PORT, GPIO_configStruct);//定义Usart接收Rx引脚端口为中断触发源引脚GPIO_ConfigEINTLine(ENC_USART_RxISR_SOURCE_PORT, ENC_USART_RxISR_SOURCE_PIN);//定义Usart接收Rx引脚端口中断触发源定义下降沿触发EINT_configStruct.line ENC_USART_RxISR_LINE;EINT_configStruct.mode EINT_MODE_INTERRUPT;EINT_configStruct.trigger EINT_TRIGGER_FALLING;EINT_configStruct.lineCmd ENABLE;EINT_Config(EINT_configStruct);//清除USART1接收引脚产生的中断标识位//EINT_ClearIntFlag(ENC_USART_RxISR_LINE);//使能USART接收引脚下降沿中断设置优先权为最高/NVIC_EnableIRQRequest(ENC_USART_RxISR_IRQn, 0x00u, 0x00u);}////函数名称ENC_USART_RxISR_IRQHandler//函数返回//参数说明//功能概要编码器USART1的Rx引脚接入PB4以触发下降沿中断//void ENC_USART_RxISR_IRQHandler(void){if( EINT_ReadIntFlag(ENC_USART_RxISR_LINE) ){//清除USART1接收Rx引脚产生的中断标识位//EINT_ClearIntFlag(ENC_USART_RxISR_LINE);//禁止USART1接收Rx引脚下降沿触发中断//NVIC_DisableIRQRequest(ENC_USART_RxISR_IRQn);//enc_usart_led_on( );//打开USART串口模块的传送功能USART_EnableTx(ENC_COM_USART);//判断条件是否满足需要用户权限设置uint8_t judgeresult 0x00u;//此处理论上是延迟4分之一微秒//实际操作下来只延迟了4微秒//刚好接收完第1个命令字节divid_us_delay(0x02u);//把接收的第一个字节赋值给串口发送内存数组MyEncoder.UsartCmd EncUsartDataBuff[0x00u];//根据接收的第一个字节做回复判断switch ( MyEncoder.UsartCmd ){default://一个周期是62.5微秒如果命令错误可以延时等待32微秒//让错误的指令全部发送完毕us_delay(0x20u);//禁用DMA接收数据禁用DMA1的通道5DMA_Disable(ENC_COM_USART_DMA_CHANNEL_RX);//正常编译时打开串口接收等待enc_usart_com_wait_rx( );//根据串口接收命令的时钟调整相位enc_usart_pll_adjust();break;case 0x02u:/*//串口接收错误指令时默认回复0x02u指令的内容//回复0x02u指令内容共6个字节短角度数据内容//CF SF AN0 AN1 AN2 CRC*///禁用DMA接收数据功能禁用DMA1的通道5DMA_Disable(ENC_COM_USART_DMA_CHANNEL_RX);//打开编码器USART串口的DMA发送功能////此处要确认DMA发送的速度看能不能达到5微秒内回复数据enc_usart_dma_send_ascii((uint8_t *)EncUsartDataBuff, ENC_USART_DMA_0x02Size);//继续赋值给串口的发送数组process_usart_dma_cmd02_reply_data();//根据串口接收命令的时钟调整相位enc_usart_pll_adjust();break;case 0x1Au:/*//回复0x1Au指令内容共11个字节全角度数据内容//CF SF AN0 AN1 AN2 ID MT0 MT1 MT2 AF CRC*///打开编码器USART串口的DMA发送功能////此处要确认DMA发送的速度看能不能达到5微秒内回复数据enc_usart_dma_send_ascii((uint8_t *)EncUsartDataBuff, ENC_USART_DMA_0x1ASize);//禁用DMA接收数据功能禁用DMA1的通道5DMA_Disable(ENC_COM_USART_DMA_CHANNEL_RX);//继续赋值给串口的发送数组process_usart_dma_cmd1A_reply_data();//根据串口接收命令的时钟调整相位enc_usart_pll_adjust();break;case 0x8Au:/*//回复0x8Au指令内容共6个字节短多圈数据内容//CF SF MT0 MT1 MT2 CRC*///禁用DMA接收数据功能禁用DMA1的通道5DMA_Disable(ENC_COM_USART_DMA_CHANNEL_RX);//打开编码器USART串口的DMA发送功能////此处要确认DMA发送的速度看能不能达到5微秒内回复数据enc_usart_dma_send_ascii((uint8_t *)EncUsartDataBuff, ENC_USART_DMA_0x8ASize);//继续赋值给串口的发送数组process_usart_dma_cmd8A_reply_data();//根据串口接收命令的时钟调整相位enc_usart_pll_adjust();break;}}而使用单字节接收中断方式接收完数据之后需要7微秒以上才能正确回复数据