STM32 在串口通信時運用MODBUS協議


  最近一個項目用到了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<len;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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM