基于STM32F4的多摩川协议通讯实现详解

1、介绍

       之前项目刚好有用到禹衡家的17位绝对值编码器,趁着周末有时间整理一下开发思路,同时也分享出来给有需要的人做做参考。

       说回编码器,我们都知道在伺服控制中,为了获取更高的位置精度,完成更精细的绝对定位,通常会采用绝对式光电编码器作为位置反馈传感器,其常见的两种主要是17位与23位,编码器厂商为了增强信息传输可靠性,编码信息一般会采用串行方式输出,通过特定的通讯协议控制。这里我们主要介绍一下17位绝对式光电编码器。

2、编码器

2.1、接口定义

说明:实际应用过程中,如果没有多圈值记录需求,可以不接电池,但如果需要长时间保留多圈值数据,比如多轴机器人的原点数据,这种情况则需要接入电池。

2.2、电气参数

说明:1、输出信号符合RS485接口,负载电流Imax:±50mA;

  2、消耗电流:≤120mA(无负载情况);

  3、绝缘阻抗10MΩ以上(0V与壳之间,DC500V);

2.2、通讯电路

说明:1、IC1为线性差分驱动器,功能等同于MAX485;

             2、IC2为线性差分接收器,功能等同于MAX485,推荐使用ADM485;

  3、匹配电阻:编码器匹配电阻R2为220Ω,推荐用户接收端匹配电阻R5≥120Ω;

  4、上/下拉电阻:编码器上/下拉电阻R1、R3为4.7KΩ,推荐用户接收端上/下拉电阻R4、R6不小于1KΩ。

2.3、协议

根据编码器电气参数与通讯电路,我们可以确定通讯方式,STM32采用USART模块,串口波特率设置2.5Mbps,以下是多摩川协议时序图:

说明:1、CF:控制域,用户发送命令CF与编码器返回的CF一致;

  2、SF:状态域,编码器返回错误信息与报警信息;

  3、DF:数据域,编码器返回数据;

  4、CRC:校验域,CRC多项式:G(X)=𝑋8+1,校验范围CF、SF、DF。

2.3.1、控制字CF

说明:1、根据时序图可以知道发送请求只需要发送CF即可,具体CF值可以根据上表分析,CF结构主要是start bit(0)+sink code+Data ID code+ID parity+Delimiter(1),这里注意一下开始位(0)与结束位(1),与串口无校验发送里面的开始位与结束位一样的嘛,所以我们其实只需要发送sink code+Data ID code+ID parity就可以了;

  2、根据时序图可以看到sink code 固定为010,剩下的Data ID code+ID parity看自己需求查表对应上就行,经常用的主要是ID0=0x02与ID3=0x1A,其余我就不说了,贴代码。

  3、这里注意一下,编码器返回的报文CF位必须与发送CF一致,在处理接收报文时需校验一下。

/* 宏定义声明 —————————————————————-*/

#define         DataId0             0x02                                        /*读取单圈*/

#define         DataId1             0x8A                                        /*读取圈数*/

#define         DataId2             0x92                                        /*读取编码器编号*/

#define         DataId3             0x1A                                        /*读取单圈+圈数*/

#define         DataId6             0x32                                        /*写EEPROM*/

#define         DataIdD             0xEA                                        /*读EEPEOM*/

#define         DataId7             0xBA                                        /*重置错误ERROR*/

#define         DataId8             0xC2                                        /*重置圈数*/

#define         DataIdC             0x62                                        /*重置圈数与ERROR*/  

2.3.2、状态字SF

说明:状态位SF主要反馈编码器状态,包括编码器是否过热、电池供电是否正常、数据解析是否正确等。

2.3.3、数据字DF

根据发出的CF不同,编码器返回的DF也会不同,具体的对应关系如下表:

说明:1、ABS0-ABS2:编码器单圈值,由于STM32的串口是小端模式,所以实际 编码器单圈位置值=(ABS2<<16)|(ABS1<<8)|(ABS0<<0);

  2、ABM0-ABM2:编码器圈数值,实际编码器圈数值=(ABM2<<16)|(ABM1<<8)|(ABM0<<0)。

说明:ALMC:编码器错误,不同数值都有其对应的故障,对应查表即可。

2.4.4、校验字CRC

说明:这里注意一下,我们采用的是小端异或的计算方式,根据多项式 G(X)=𝑋8+1 其对应的二进制数为0x101,其Ploy值为0x01,LSB First=0x80,我们需要将CF+SF+DF以字节形式分别于LSB First进行亦或处理,最后结果为CRC8校验结果,具体实现过程见代码。

/******************************************************************************

  * 函数功能:   CRC8校验函数

  * 输入参数:   *message:接收的数据指针

                len:数据长度

  * 返 回 值:      CRC8校验值

    * 说    明: 多项式:G(X)=X^8+1 LSB first  Poly: 0000 0001

                        LSB first  : 1000 0000 =0X80

*****************************************************************************/  

uint8_t APP_Math_CRC8_ChkValue(uint8_t *message, uint8_t len)

{

    uint8_t crc_val;

    uint8_t i;

    crc_val = 0;

    while(len–)

    {

        crc_val ^= *message++;

        for(i = 0;i < 8;i++)

        {

            if(crc_val & 0x01)

                crc_val = (crc_val >> 1) ^ 0X80;

            else

                crc_val >>= 1;

        }

    }

    return crc_val;

}

3、单片机

3.1、单片机

当时项目为了上手快一点,又怕串口波特率不够,所以就选了个STM32F405RGT6,168MHz主频绰绰有余,现在想想有点杀鸡用牛刀了,这里主要提一下接下来要用到的串口与DMA。STM32F405有16数据流DMA,支持FIFOs模式与触发模式,4 个 USART/2 个 UART(速率可达10.5 Mbit/s,支持ISO7816接口)。

3.2、串口+DMA

 串口(UART)是一种低速的串行异步通信,通常使用的波特率小于或等于115200bps,对于这种数据量不大的通信场景,一般没必要使用DMA,或者说使用DMA并未能充分发挥出DMA的作用。当面对数量大或者高波特率时,就必须使用DMA以释放CPU资源,因为高波特率可能带来CPU资源过度浪费的问题。这里编码器电气参数要求通讯速率≥2.5MHz,我们一般选择将串口波特率设置为2.5MHz,同时为了不占用太多CPU资源,CF发送与编码器数据接收都会采用DMA,具体寄存器设置如下:

/******************************************************************************

  * 函数功能:   USART2+DMA初始化函数

  * 输入参数:   无

  * 返 回 值:      无

    * 说    明:   外部调用-多摩川编码器1

*****************************************************************************/  

void BSP_Encoder1_InitConfig(void)

{

    GPIO_InitTypeDef    GPIO_InitStructure;

    USART_InitTypeDef USART_InitStructure;

    DMA_InitTypeDef     DMA_InitStructure;

    NVIC_InitTypeDef    NVIC_InitStructure;

   

    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);

    GPIO_PinAFConfig(GPIOA,GPIO_PinSource2,GPIO_AF_USART2);                                         /*UART2-TX*/

    GPIO_PinAFConfig(GPIOA,GPIO_PinSource3,GPIO_AF_USART2);                                         /*UART2-RX*/

    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;

    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;                                                       /*UART2-TX*/

    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;

    GPIO_Init(GPIOA, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;

    GPIO_Init(GPIOA, &GPIO_InitStructure);                                                          /*UART2-RX*/

   

    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);  

    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_15;                                                    /*DIR1-PC15*/

    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;  

    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_OUT;

    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;

    GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_UP;  

    GPIO_Init(GPIOC,    &GPIO_InitStructure);  

    GPIO_ResetBits(GPIOC,GPIO_Pin_15);                                                              /*初始化关闭通信*/

   

    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1,ENABLE);                                             /*串口发DMA配置*/

    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);                                                 /*抢占优先级0-3,响应优先级0-3*/

    NVIC_InitStructure.NVIC_IRQChannel=DMA1_Stream6_IRQn;              

    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;                                         /*抢占优先级1*/

    NVIC_InitStructure.NVIC_IRQChannelSubPriority=0;

    NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;

    NVIC_Init(&NVIC_InitStructure);                                                                 /*DMA发送中断*/

    DMA_DeInit(DMA1_Stream6);                                                                       /*DMA通道配置*/

    while(DMA_GetCmdStatus(DMA1_Stream6)!=DISABLE){}    

    DMA_InitStructure.DMA_Channel = DMA_Channel_4;

    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&USART2->DR);                             /*外设地址*/

    DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)Ed_Tx_Buf1;                                   /*内存地址*/

    DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;                                         /*dma传输方向*/

    DMA_InitStructure.DMA_BufferSize = USART_MAX_TX_LEN;                                            /*设置DMA在传输时缓冲区的长度*/

    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;                                /*设置DMA的外设递增模式,一个外设*/

    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;                                         /*设置DMA的内存递增模式*/

    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;                         /*外设数据字长*/

    DMA_InitStructure.DMA_MemoryDataSize = DMA_PeripheralDataSize_Byte;                             /*内存数据字长*/

    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;                                                   /*设置DMA的传输模式*/

    DMA_InitStructure.DMA_Priority = DMA_Priority_High;                                             /*设置DMA的优先级别*/

    DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;                                          /*指定如果FIFO模式或直接模式将用于指定的流:不使能FIFO模式*/  

    DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;                               /*指定了FIFO阈值水平*/  

    DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;                                     /*指定的Burst转移配置内存传输*/

    DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;                             /*指定的Burst转移配置外围转移*/

    DMA_Init(DMA1_Stream6, &DMA_InitStructure);                                                     /*配置DMA1的通道*/    

    DMA_ITConfig(DMA1_Stream6,DMA_IT_TC,ENABLE);                                                    /*使能中断*/

   

    DMA_DeInit(DMA1_Stream5);                                                                       /*串口接收DMA配置*/

    while(DMA_GetCmdStatus(DMA1_Stream5)!=DISABLE){}        

    DMA_InitStructure.DMA_Channel = DMA_Channel_4;

    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&USART2->DR);                             /*外设地址*/

    DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)Ed_Rx_Buf1;                                   /*内存地址*/

    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;                                         /*DMA传输方向*/

    DMA_InitStructure.DMA_BufferSize = USART_MAX_RX_LEN;                                            /*设置DMA在传输时缓冲区的长度*/

    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;                                /*设置DMA的外设递增模式,一个外设*/

    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;                                         /*设置DMA的内存递增模式*/

    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;                         /*外设数据字长*/

    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;                                 /*内存数据字长*/

    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;                                                   /*设置DMA的传输模式*/

    DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;                                         /*设置DMA的优先级别*/

    DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;                                          /*指定如果FIFO模式或直接模式将用于指定的流 : 不使能FIFO模式*/    

    DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;                               /*指定了FIFO阈值水平*/    

    DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;                                     /*指定的Burst转移配置内存传输*/      

    DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;                             /*指定的Burst转移配置外围转移*/

    DMA_Init(DMA1_Stream5, &DMA_InitStructure);                                                     /*配置DMA1的通道*/  

    DMA_Cmd(DMA1_Stream5,ENABLE);                                                                   /*使能通道*/

    USART_InitStructure.USART_BaudRate = ENCODER1_BAUDRATE;                                         /*配置8 1 0*/

    USART_InitStructure.USART_WordLength = USART_WordLength_8b;

    USART_InitStructure.USART_StopBits = USART_StopBits_1;

    USART_InitStructure.USART_Parity = USART_Parity_No ;

    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;

    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;

    USART_Init(USART2, &USART_InitStructure);

   

    NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;                                               /*通道设置为串口中断*/  

    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;                                       /*中断占先等级*/

    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;                                              /*中断响应优先级*/

    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;                                                 /*打开中断*/  

    NVIC_Init(&NVIC_InitStructure);                                                                 /*配置中断*/  

   

    USART_DMACmd(USART2,USART_DMAReq_Tx,ENABLE);                                                    /*采用DMA方式发送*/

    USART_DMACmd(USART2,USART_DMAReq_Rx,ENABLE);                                                    /*采用DMA方式接收*/

    USART_ITConfig(USART2,USART_IT_TC,DISABLE);                                                     /*中断配置*/

    USART_ITConfig(USART2,USART_IT_RXNE,DISABLE);

    USART_ITConfig(USART2,USART_IT_TXE,DISABLE);

    USART_ITConfig(USART2,USART_IT_IDLE,ENABLE);                                                    /*开启USART2空中断*/

    USART_Cmd(USART2, ENABLE);                                                                      /*启动串口*/  

}

另附DMA1、DMA2每个控制的8个数据流和其对应的通道:

 

4、代码实现

至此串口DMA初始化完成,接下来就是具体的发送与接收,发送直接采用DMA传输完成中断,接收稍微需要点处理,因为我们接收的是不定长度的数据,所以需要通过串口的空闲中断来判断数据接收结束,数据接收完毕之后需要对CF、CRC进行校验,校验通过才可认为DF数据可用,具体代码实现如下:

/******************************************************************************

  * 函数功能:       USART2中断处理函数

  * 输入参数:   无

  * 返 回 值:      无

    * 说    明:   外部调用-多摩川编码器1

*****************************************************************************/  

void USART2_IRQHandler(void)                                                                        /*串口2中断服务程序*/

{

    if(USART_GetITStatus(USART2,USART_IT_IDLE)!=RESET)                                              /*空闲中断触发*/

    {

        DMA_Cmd(DMA1_Stream5, DISABLE);                                                             /* 暂时关闭DMA数据尚未处理 */

        ED_RX_LEN1 = USART_MAX_RX_LEN – DMA_GetCurrDataCounter(DMA1_Stream5);                       /* 获取接收到的数据长度*/

        BSP_Encoder1_DMA_DataProcess(Ed_Tx_Buf1,Ed_Rx_Buf1,ED_RX_LEN1);                             /* 报文数据处理*/

        DMA_ClearFlag(DMA1_Stream5,DMA_FLAG_TCIF5);                                                 /* 清DMA标志位 */

        DMA_SetCurrDataCounter(DMA1_Stream5,USART_MAX_RX_LEN);                                      /* 重新赋值计数值,必须大于等于最大可能接收到的数据帧数目 */

        DMA_Cmd(DMA1_Stream5, ENABLE);                                                              /*打开DMA*/

        USART_ReceiveData(USART2);                                                                  /*清除空闲中断标志位,接收函数有清标志位的作用*/

        Ed_Flag_Rx_Busy1 = 1;                                                                       /*状态位更新-USART2接收完成*/            

    }

    if(USART_GetFlagStatus(USART2,USART_IT_TXE)==RESET)                                             /*串口发送完成*/

    {

        Ed_Flag_Tx_Busy1=0;                                                                                                                    

        USART_ITConfig(USART2,USART_IT_TC,DISABLE);

    }

}

/******************************************************************************

    * 函数功能:     DMA1_Stream6中断处理函数

    * 输入参数:     无

    * 返 回 值:    无

    * 说    明:   外部调用-多摩川编码器1

*****************************************************************************/  

void DMA1_Stream6_IRQHandler(void)

{

    if(DMA_GetFlagStatus(DMA1_Stream6,DMA_FLAG_TCIF6)!=RESET)                                       /*等待DMA1_Steam6传输完成*/

    {

        DMA_ClearFlag(DMA1_Stream6,DMA_FLAG_TCIF6);                                                 /*清除DMA1_Steam6传输完成标志*/

        DMA_Cmd(DMA1_Stream6,DISABLE);                                                                          /*关闭使能*/

        USART_ITConfig(USART2,USART_IT_TC,ENABLE);                                                  /*打开串口发送完成中断*/

    }

}

/******************************************************************************

    * 函数功能:     DMA_USART2发送函数

    * 输入参数:     *data:发送的数据指针

                    size:数据长度

    * 返 回 值:    无

    * 说    明:   外部调用-多摩川编码器1

*****************************************************************************/  

void BSP_Encoder1_DMA_SendBuffer(u8 *data,u16 size)                                                            

{

    Ed_Flag_Tx_Busy1=1;                                                                             /*状态位更新-USART2发送中*/

    memcpy(Ed_Tx_Buf1,data,size);                                                                   /*复制数据到DMA发送缓存区*/

    while (DMA_GetCmdStatus(DMA1_Stream6) != DISABLE);                                              /*确保DMA可以被设置*/

    DMA_SetCurrDataCounter(DMA1_Stream6,size);                                                      /*设置数据传输长度*/

    DMA_Cmd(DMA1_Stream6,ENABLE);                                                                   /*打开DMA数据流,开始发送*/

}

/******************************************************************************

    * 函数功能:     DMA_USART2数据处理函数

    * 输入参数:     *data:接收的数据指针

                    size:数据长度

    * 返 回 值:    无

    * 说    明:   外部调用-多摩川编码器2

*****************************************************************************/  

void    BSP_Encoder1_DMA_DataProcess(u8 *txdata,u8 *rxdata,u16 size)

{

    uint8_t     crc=0;

    uint8_t     buf[USART_MAX_RX_LEN]={0};  

    for(int i=0;i<size;i++)

        buf[i]=*rxdata++;

    if(buf[0]==*txdata)

    {

        crc=APP_Math_CRC8_ChkValue(buf,size-1);

        if(crc==buf[size-1])

        {

            Encoder1_CodeValue=buf[4]<<16|buf[3]<<8|buf[2];

        }

        else

        {

            Encoder_ReadFaultNum++;

            if(Encoder_ReadFaultNum>ED_READ_FAULT_MAXNUM)

                Encoder_ReadStatus=1;          

        }

    }

    else

    {

            Encoder_ReadFaultNum++;

            if(Encoder_ReadFaultNum>ED_READ_FAULT_MAXNUM)

                Encoder_ReadStatus=1;

    }

}

5、结语

整个项目其实难度并不大,对我个人而言花费时间多的主要还是在搞懂DMA、CRC校验原理、发送\接收中断处理这上面。本人对单片机也还是小白阶段,很多东西都是重头开始吸收学习,不过万变不离其宗,多花点时间搞清楚原理,很多不解的地方都会豁然开朗。技术分享就到这里了,希望能对你有所帮助!!

物联沃分享整理
物联沃-IOTWORD物联网 » 基于STM32F4的多摩川协议通讯实现详解

发表评论