单片机
返回首页

STM32 在串口通信时运用MODBUS协议

2025-02-18 来源:cnblogs

  最近一个项目用到了MODBUS协议,就学习了一下,这里做一下记录以免后续忘记。

  要用到MODBUS肯定要先知道是MOBUS协议,这里呢我们就又要先理解协议的含义了。

  所谓的协议是什么?就是互相之间的约定嘛,如果不让别人知道那就是暗号。现在就来定义一个新的最简单协议。

  例如:  协议:  “A”--“LED灭”  “B”--“报警”  “C”--“LED亮” 。

  单片机接收到“A”控制一个LED灭,单片机接收到“B”控制报警,单片机接收到“A”控制一个LED亮。那么当收到对应的信息就执行相应的动作,这就是协议。

  MODBUS 一包数据主要组成有 :   设备地址 功能码  数据长度  数据  CRC 

  先来简单分析一条MODBUS-RTU报文,例如:

  这个包数据的意思是往设备地址为 0x01 的设备中,执行 0x1f 代号的功能 ,这里假设 0x1f 代号功能码代表的是保存数据再Flash,那这包数据的意思就是,控制 0x01 设备,执行 0x1f 代号功能保存后面4位数据到 MCU 的 Flash 中,其中数据长度是指后面数据字节数的大小,最后两个 CRC 校验是除了最后两位 CRC 前面所有数据经过CRC运算出来的校验码,用来保证数据的准确性。

  协议大概就是这么些内容,因为只是简单的应用,就没有深入去研究了,知道这些运用到STM32上已经够了,下面的是CRC校验的代码(重点):


/***

 函数名:uint16_t Crc(uint8_t Rxbuff[],uint8_t Rx_len)

 说  明:Modbus协议CRC校验

 传入值:Rxbuff[] 串口接收的数据,len串口接收的数据长度

 传出值:返回两个字节的CRC校验码,高位在前,低位在后

**/

uint16_t Crc(uint8_t Rxbuff[],uint8_t Rx_len)

{

    uint8_t len = Rx_len - 2;

    uint16_t crc_result = 0xffff;

    int crc_num =0;

    int xor_flag=0;

    for(int i=0;i    {

        crc_result ^= Rxbuff[i];

        crc_num = (crc_result&0x0001);

        for(int m=0;m<8;m++)

        {

            if(crc_num==1)

                xor_flag = 1;

            else

                xor_flag = 0;

            crc_result >>= 1;

            if(xor_flag)

                crc_result ^= 0xa001;

            crc_num = (crc_result & 0x0001);

        }

    }

    return crc_result;

}


下面的是MODBUS运用的举例代码,这里我对MODBUS协议做了些修改,在 “功能码 ” 跟 “数据长度 ” 间多加了个 “读写标志位”,实际项目时可以根据自己项目需求做一下修改也无可厚非:


/***

 函数名:void modbus(uint8_t Rxbuff[],uint8_t len)

 说  明:Modbus协议处理

 传入值:Rxbuff[] 串口接收的数据,len串口接收的数据长度

 传出值:无

**/

void modbus(uint8_t Rxbuff[],uint8_t len)

{

    uint8_t Read_Robot [1]={0};

    uint8_t Robot_add=0x01;

    uint8_t Robot = Get_Robot_Num();  //获取预先保存好的设备地址

    uint16_t crc=Crc(Rxbuff,len);

    uint16_t Rx_crc = Rxbuff[len-2]<<8 | Rxbuff[len-1];

    if((Robot==Rxbuff[0] ||Rxbuff[0] ==0xff) && Rx_crc==crc) //判断设备地址是否正确,预留在不知道地址的时候,使用虚拟地址0xff,防止忘记设备地址后,用虚拟地址可以修改

    {

        switch(Rxbuff[1])

        {

            case 0x01: //操作ID

                if(Rxbuff[2]==0x00) //读

                        Read(MCU_Rx_buff,ID_Flash_Add);

                else if(Rxbuff[2]==0x01) //写

                         Write(Rxbuff,len,ID_Flash_Add);

                break;

            case 0x02: //操作名称

                if(Rxbuff[2]==0x00) //读

                        Read(MCU_Rx_buff,Name_Flash_Add);

                else if(Rxbuff[2]==0x01) //写

                         Write(Rxbuff,len,Name_Flash_Add);

                break;

            case 0x03: //操作环节

                if(Rxbuff[2]==0x00) //读

                        Read(MCU_Rx_buff,Link_Flash_Add);

                else if(Rxbuff[2]==0x01) //写

                         Write(Rxbuff,len,Link_Flash_Add);

                break;

            case 0x04: //操作型号

                if(Rxbuff[2]==0x00) //读

                        Read(MCU_Rx_buff,Model_Flash_Add);

                else if(Rxbuff[2]==0x01) //写

                         Write(Rxbuff,len,Model_Flash_Add);

            break;

            case 0x05: //操作品牌

                if(Rxbuff[2]==0x00) //读

                        Read(MCU_Rx_buff,Brand_Flash_Add);

                else if(Rxbuff[2]==0x01) //写

                         Write(Rxbuff,len,Brand_Flash_Add);                

            break;

            case 0x06: //操作行数

                if(Rxbuff[2]==0x00) //读

                        Read(MCU_Rx_buff,Linage_Flash_Add);

                else if(Rxbuff[2]==0x01) //写

                         Write(Rxbuff,len,Linage_Flash_Add);    

                break;

            case 0x07: //操作幅宽

                if(Rxbuff[2]==0x00) //读

                        Read(MCU_Rx_buff,Wite_Flash_Add);

                else if(Rxbuff[2]==0x01) //写

                         Write(Rxbuff,len,Wite_Flash_Add);    

                break;

            case 0x08: //操作高度

                if(Rxbuff[2]==0x00) //读

                        Read(MCU_Rx_buff,Hight_Flash_Add);

                else if(Rxbuff[2]==0x01) //写

                         Write(Rxbuff,len,Hight_Flash_Add);                    

                break;

            case 0x09: //操作马力

                if(Rxbuff[2]==0x00) //读

                        Read(MCU_Rx_buff,Soup_Flash_Add);

                else if(Rxbuff[2]==0x01) //写

                         Write(Rxbuff,len,Soup_Flash_Add);                    

                break;

            case 0x0a: //操作合作社ID

                if(Rxbuff[2]==0x00) //读

                        Read(MCU_Rx_buff,Artel_Flash_Add);

                else if(Rxbuff[2]==0x01) //写

                         Write(Rxbuff,len,Artel_Flash_Add);                    

                break;

            case 0x21: //修改波特率

                if(Rxbuff[2]==0x00) //读

                {

                        Get_Boud_num();

                }

                else if(Rxbuff[2]==0x01) //写

                {

                      Boud_Set(Rxbuff);

                        HAL_UART_Transmit(&huart1,Rxbuff,len,0x1000);                    

                }

                break;

            case 0x22: //操作设备地址

                if(Rxbuff[2]==0x00) //读

                {

                        Read_Robot[0] = Get_Robot_Num();

                      HAL_UART_Transmit(&huart1,Read_Robot,1,0x1000);

                }

                else if(Rxbuff[2]==0x01) //写

                {

                    Robot_add=Rxbuff[5]; 

                    Robot_Num_Init(Robot_add);

                    HAL_UART_Transmit(&huart1,Rxbuff,len,0x1000);

                }                    

                break;

        }

    }

}


最后直接在串口接收处理函数里调用MODBUS函数即可,代码如下:


/***

 函数名:void Usart_RX(void)

 说  明:接收数据处理

 传入值:无

 传出值:无

**/

void Usart_RX(void)

{

    if(Rx_End_flag==1)        

  {       

      modbus(Rx_buffer,Rx_len);    //调用MODBUS协议处理函数                

      memset(Rx_buffer,0,sizeof(Rx_buffer)); //清空数组

      Rx_len=0;

      Rx_End_flag=0;//清除接收结束标志位

   }

    HAL_UART_Receive_DMA(&huart1,Rx_buffer,BUFFER_SIZE);

}


只要经过这些步骤,就可以在STM32上简单运用 MODBUS 协议了,其实 MODBUS 协议说难不难,它就跟我们平时自己定义的串口通信协议类似,有数据头、数据内容、数据尾,只不过更加科学规范罢了。


参考链接:https://wenku.baidu.com/view/9cdb001533687e21af45a9e2.html


进入单片机查看更多内容>>
相关视频
  • 【TI MSPM0 应用实战】智能小车+工业角度编码器+血氧仪+烟雾探测器!硬核参考设计详解!

  • 2022 Digi-Key KOL 系列: 你见过1GHz主频的单片机吗?Teensy 4.1开发板介绍

  • TI 新一代 C2000™ 微控制器:全方位助力伺服及马达驱动应用

  • MSP430电容触摸技术 - 防水Demo演示

  • 直播回放: Microchip Timberwolf™ 音频处理器在线研讨会

  • 基于灵动MM32W0系列MCU的指夹血氧仪控制及OTA升级应用方案分享

精选电路图
  • 1瓦四级调频发射机

  • 500W MOS场效应管电源逆变器,12V转110V/220V

  • 12V 转 28V DC-DC 变换器(基于 LM2585)

  • 红外开关

  • 12V转110V/220V 500W逆变器

  • DS1669数字电位器

    相关电子头条文章