自寫簡易版從機Modbus


看這篇文章之前要對Modbus協議要有一個簡單的了解,本篇文章以STM32單片機為例寫一個簡易版的從機Modbus.

Modbus通信機制需要單片機兩個外設資源:串口和定時器。

 

 

 

設一個向上計數的定時器,計數周期為3.5個字符的時間。3.5個字符時間如何計算請參考這個https://zhidao.baidu.com/question/2266066387336737428.html

其實這個時間設長一點也沒關系,比如設個50ms,100ms甚至是1s,如果設為1s,主機的Modbus發送兩幀數據的間隔就不能低於1s,看完從機的具體實現就會明白為什么了。

用Stm32CubeIDE配置一個50ms中斷的定時器

 

 

 再配置串口

 

串口中斷回調函數如下:

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
  /* Prevent unused argument(s) compilation warning */
  UNUSED(huart);
  /* NOTE: This function Should not be modified, when the callback is needed,
           the HAL_UART_TxCpltCallback could be implemented in the user file
   */
    if(huart->Instance == huart1.Instance)
    {
        Rx_Buf[RxCount++] = aRx1Buffer;//把接收到的數據存入接收緩存數組中
        __HAL_TIM_SET_COUNTER(&htim7,0);//只要有數據進來就清空定時器計數器,如果沒有新的數據進來即從此刻開始計時,50ms后進入定時器中斷回調函數,此時意味着接收完一幀數據。
        HAL_TIM_Base_Start_IT(&htim7);//啟動定時器
        HAL_UART_Receive_IT(&huart1, (uint8_t *)&aRx1Buffer, 1);   //再開啟接收串口中斷
    }
}

 在定時器中斷回調函數如下:

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == htim7.Instance)
    {
        SLAVE_RS485_SEND_MODE;//485切換成發送模式,不再接受新的串口中斷
        HAL_TIM_Base_Stop_IT(&htim7);//停止定時器
        MB_Parse_Data();// 提取數據幀,進行解析數據幀
        MB_Analyze_Execute();//對接收到的數據進行分析並執行
        RxCount = 0;//清空接收計數
        SLAVE_RS485_RECEIVE_MODE;//數據處理完畢后,再重新接收串口中斷
    }
}
串口引腳接了485芯片,SLAVE_RS485_SEND_MODE和SLAVE_RS485_RECEIVE_MODE就是普通的IO口,用於控制485芯片發送與接收模式
#define SLAVE_RS485_SEND_MODE                     HAL_GPIO_WritePin(USART1_EN_GPIO_Port, USART1_EN_Pin, GPIO_PIN_SET)
#define SLAVE_RS485_RECEIVE_MODE              HAL_GPIO_WritePin(USART1_EN_GPIO_Port, USART1_EN_Pin, GPIO_PIN_RESET)

接下來看提取數據幀函數

/* 提取數據幀,進行解析數據幀 */
void MB_Parse_Data()
{
  PduData.Code = Rx_Buf[1];                   // 功能碼
  PduData.Addr = ((Rx_Buf[2]<<8) | Rx_Buf[3]);// 寄存器起始地址
  PduData.Num  = ((Rx_Buf[4]<<8) | Rx_Buf[5]);// 數量(Coil,Input,Holding Reg,Input Reg)
  PduData._CRC = MB_CRC16((uint8_t*)&Rx_Buf,RxCount-2);             // CRC校驗碼
  PduData.byteNums = Rx_Buf[6];               // 獲得字節數
}
PduData是一個結構體如下:
/* 類型定義 ------------------------------------------------------------------*/
typedef struct {
  __IO uint8_t  Code ;              // 功能碼
  __IO uint8_t byteNums;             // 字節數
  __IO uint16_t Addr ;            // 操作內存的起始地址
  __IO uint16_t Num;                 // 寄存器或者線圈的數量
  __IO uint16_t _CRC;                 // CRC校驗碼
  __IO uint8_t *ValueReg;           // 10H功能碼的數據
}PDUData_TypeDef;
MB_CRC16校驗函數請參考:https://www.cnblogs.com/lizhiqiang0204/p/12122928.html

接下來看最重要的對接收到的數據進行分析並執行,我們以簡單的寫線圈寄存器為例
/**
  * 函數功能: 對接收到的數據進行分析並執行
  * 輸入參數: 無
  * 返 回 值: 異常碼或0x00
  * 說    明: 判斷功能碼,驗證地址是否正確.數值內容是否溢出,數據沒錯誤就發送響應信號
  */
uint8_t MB_Analyze_Execute(void )
{
  uint16_t ExCode = EX_CODE_NONE;
    uint16_t tem_crc;
    if(PduData._CRC !=((Rx_Buf[RxCount-1])<<8 | Rx_Buf[RxCount-2]))
    {
        /* Modbus異常響應 */
      ExCode = EX_CODE_02H;            // 異常碼02H
       return ExCode;
    }

  /* 根據功能碼分別做判斷 */
  switch(PduData.Code)
  {
   /* ---- 01H  02H 讀取離散量輸入(Coil Input)---------------------- */
    case FUN_CODE_01H:
        break;
        case FUN_CODE_02H:
        break;
        case FUN_CODE_03H:
        break;
        case FUN_CODE_04H:
        break;
        case FUN_CODE_05H:
            /* 寫入一個線圈值 */
      if(PduData.Num == 0xFF00)
      {
         usCoilBuf[PduData.Addr] = 1;//把這個PduData.Addr地址的線圈寄存器寫1
      }
      else
      {
         usCoilBuf[PduData.Addr] = 0;//把這個PduData.Addr地址的線圈寄存器寫0
      }
        MB_SendRX();//把接收到的數據原封不動的發送出去(即應答)
        break;
  }
  /* 數據幀沒有異常 */
  return ExCode; //   EX_CODE_NONE
}

 

 uint8_t    usCoilBuf[100]   ;//定義100個線圈寄存器
 uint16_t   usRegInputBuf[100] ;//定義100個輸入寄存器
 uint16_t   usRegHoldingBuf[100] ;//定義100個保持寄存器
__IO uint8_t Rx_Buf[256]; // 接收緩存,最大256字節
__IO uint8_t Tx_Buf[256]; // 發送緩存,最大256字節
__IO uint16_t RxCount = 0; // 接收字符計數

 

數據分析執行函數MB_Analyze_Execute除了驗證校驗位,還應該對寄存器的地址進行檢查是否越位。上面的執行函數MB_Analyze_Execute只是對功能碼05H寫單個線圈寄存器進行回應操作,接下來分別補充10H寫多個保持寄存器和04H讀多個輸入寄存器的回應操作。

    case FUN_CODE_10H://寫入多個保持寄存器
        for(int i = 0;i < PduData.Num;i++)
        {
            usRegHoldingBuf[PduData.Addr + i] = (Rx_Buf[i*2+7] << 8) | (Rx_Buf[i*2+8] << 0);
        }
        //響應寫多個保持寄存器
        Tx_Buf[0] = Rx_Buf[0];//從機地址
        Tx_Buf[1] = Rx_Buf[1];//功能碼
        Tx_Buf[2] = Rx_Buf[2];//起始地址高位
        Tx_Buf[3] = Rx_Buf[3];//起始地址低位
        Tx_Buf[4] = Rx_Buf[4];//數量高位
        Tx_Buf[5] = Rx_Buf[5];//數量低位

        tem_crc = MB_CRC16((uint8_t*)&Tx_Buf,6);             // CRC校驗碼
        Tx_Buf[6] = (uint8_t)tem_crc;//CRC高位
        Tx_Buf[7] = (uint8_t)(tem_crc >> 8);//CRC低位
        HAL_UART_Transmit(&huart1, (uint8_t*)&Tx_Buf, 8, HAL_MAX_DELAY);
    break;

 

        case FUN_CODE_04H://寫多個輸入寄存器
            Tx_Buf[0] = Rx_Buf[0];//從機地址
            Tx_Buf[1] = PduData.Code;//功能碼
            Tx_Buf[2] = PduData.Num * 2;//發送字節數

            for(uint8_t i = 0; i <PduData.Num;i++)
            {
                //把對應地址的輸入寄存器寫入發送緩沖數組中
                Tx_Buf[i*2+3] = (uint8_t)(usRegInputBuf[PduData.Addr+i] >> 8);
                Tx_Buf[i*2+4] = (uint8_t)(usRegInputBuf[PduData.Addr+i] >> 0);
            }

            tem_crc = MB_CRC16((uint8_t*)&Tx_Buf,PduData.Num * 2 +3);             // CRC校驗碼
            Tx_Buf[PduData.Num * 2 +3] = (uint8_t)tem_crc;
            Tx_Buf[PduData.Num * 2 +4] = (uint8_t)(tem_crc >> 8);//填寫校驗位
            HAL_UART_Transmit(&huart1, (uint8_t*)&Tx_Buf, PduData.Num * 2 +5, HAL_MAX_DELAY);//串口發送
        break;

 

/*
 * cus_modbus.c
 *
 *  Created on: Dec 25, 2019
 *      Author: LiZhiqiang
 */
#include "stm32g0xx_hal.h"
#include "cus_modbus.h"
#include "usart.h"
 uint16_t   usDiscreteInputStart                             ;
 uint8_t    usDiscreteInputBuf[DISCRETE_INPUT_NDISCRETES/8]  ;
 uint16_t   usCoilStart                                      ;
 uint8_t    usCoilBuf[COIL_NCOILS/8]                         ;
 uint16_t   usRegInputStart                                  ;
 uint16_t   usRegInputBuf[REG_INPUT_NREGS]                   ;
 uint16_t   usRegHoldingStart                                ;
 uint16_t   usRegHoldingBuf[REG_HOLDING_NREGS]               ;

__IO uint8_t Rx_Buf[256];            // 接收緩存,最大256字節
__IO uint8_t Tx_Buf[256];            // 發送緩存,最大256字節
__IO uint8_t tmp_Rx_Buf;             // 臨時接收緩存
__IO uint16_t RxCount = 0;      // 接收字符計數
__IO uint8_t Addr_Slave = 3;//從機地址
PDUData_TypeDef PduData;

// CRC 高位字節值表
static const uint8_t auchCRCHi[] = {
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
    0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
    0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
    0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
    0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
    0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
    0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
    0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
    0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
    0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40
} ;
// CRC 低位字節值表
static const uint8_t auchCRCLo[] = {
    0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06,
    0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD,
    0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09,
    0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A,
    0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, 0x14, 0xD4,
    0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
    0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3,
    0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4,
    0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A,
    0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29,
    0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED,
    0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
    0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60,
    0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67,
    0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F,
    0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68,
    0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E,
    0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
    0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71,
    0x70, 0xB0, 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92,
    0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C,
    0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B,
    0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B,
    0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
    0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42,
    0x43, 0x83, 0x41, 0x81, 0x80, 0x40
};


void MB_10H_WR_NReg(uint16_t* _AddrOffset,uint16_t _RegNum , uint8_t* _Datebuf);

//把接受到的數據再回復出去
void MB_SendRX()
{
        HAL_UART_Transmit(&huart1, (uint8_t*)&Rx_Buf, RxCount, HAL_MAX_DELAY);
}
/* 函數體 --------------------------------------------------------------------*/
/**
  * 函數功能: Modbus CRC16 校驗計算函數
  * 輸入參數: pushMsg:待計算的數據首地址,usDataLen:數據長度
  * 返 回 值: CRC16 計算結果
  * 說    明: 計算結果是高位在前,需要轉換才能發送
  */
uint16_t MB_CRC16(uint8_t *_pushMsg,uint8_t _usDataLen)
{
  uint8_t uchCRCHi = 0xFF;
  uint8_t uchCRCLo = 0xFF;
  uint16_t uIndex;
  while(_usDataLen--)
  {
    uIndex = uchCRCLo ^ *_pushMsg++;
    uchCRCLo = uchCRCHi^auchCRCHi[uIndex];
    uchCRCHi = auchCRCLo[uIndex];
  }
  return (uchCRCHi<<8|uchCRCLo);
}

/* 提取數據幀,進行解析數據幀 */
void MB_Parse_Data()
{
  PduData.Code = Rx_Buf[1];                   // 功能碼
  PduData.Addr = ((Rx_Buf[2]<<8) | Rx_Buf[3]);// 寄存器起始地址
  PduData.Num  = ((Rx_Buf[4]<<8) | Rx_Buf[5]);// 數量(Coil,Input,Holding Reg,Input Reg)
  PduData._CRC = MB_CRC16((uint8_t*)&Rx_Buf,RxCount-2);             // CRC校驗碼
  PduData.byteNums = Rx_Buf[6];               // 獲得字節數
  PduData.ValueReg = (uint8_t*)&Rx_Buf[7];                          // 寄存器值起始地址
  PduData.PtrCoilOffset = PduData.PtrCoilbase + PduData.Addr;       // 離散量的內存起始地址
  PduData.PtrHoldingOffset = PduData.PtrHoldingbase + PduData.Addr; // 保持寄存器的起始地址
}

/**
  * 函數功能: 對接收到的數據進行分析並執行
  * 輸入參數: 無
  * 返 回 值: 異常碼或0x00
  * 說    明: 判斷功能碼,驗證地址是否正確.數值內容是否溢出,數據沒錯誤就發送響應信號
  */
uint8_t MB_Analyze_Execute(void )
{
  uint16_t ExCode = EX_CODE_NONE;
    uint16_t tem_crc;
    MB_Parse_Data();
  /* 校驗功能碼 */
  if( IS_NOT_FUNCODE(PduData.Code) ) // 不支持的功能碼
  {
    /* Modbus異常響應 */
    ExCode = EX_CODE_01H;            // 異常碼01H
    return ExCode;
  }
    if(PduData._CRC !=((Rx_Buf[RxCount-1])<<8 | Rx_Buf[RxCount-2]))
    {
        /* Modbus異常響應 */
    ExCode = EX_CODE_02H;            // 異常碼02H
    return ExCode;
    }

  /* 根據功能碼分別做判斷 */
  switch(PduData.Code)
  {
    case FUN_CODE_01H://讀線圈寄存器
        Tx_Buf[0] = Rx_Buf[0];//從機地址
        Tx_Buf[1] = PduData.Code;//功能碼
        if(PduData.Num % 8 == 0)//如果讀取線圈的數量是8的整數倍,則返回字節數Tx_Buf[2] = PduData.Num / 8
            Tx_Buf[2] = PduData.Num / 8;
        else//如果不是8的整數倍,則加一
            Tx_Buf[2] = PduData.Num / 8 + 1;

        for(int i = 0; i < Tx_Buf[2];i++)
        {
            Tx_Buf[3 + i] =  (usCoilBuf[PduData.Addr+7 + i*8] << 7) | (usCoilBuf[PduData.Addr +6 +i*8] << 6)
                            |(usCoilBuf[PduData.Addr+5 + i*8] << 5) | (usCoilBuf[PduData.Addr +4 +i*8] << 4)
                            |(usCoilBuf[PduData.Addr+3 + i*8] << 3) | (usCoilBuf[PduData.Addr +2 +i*8] << 2)
                            |(usCoilBuf[PduData.Addr+1 + i*8] << 1) | (usCoilBuf[PduData.Addr +0 +i*8] << 0);
        }
        tem_crc = MB_CRC16((uint8_t*)&Tx_Buf,Tx_Buf[2]+3);             // CRC校驗碼
        Tx_Buf[Tx_Buf[2]+3] = (uint8_t)tem_crc;
        Tx_Buf[Tx_Buf[2]+4] = (uint8_t)(tem_crc >> 8);
        HAL_UART_Transmit(&huart1, (uint8_t*)&Tx_Buf, Tx_Buf[2] +5, HAL_MAX_DELAY);

    break;

    case FUN_CODE_02H://讀離散輸入寄存器,其實讀離散輸入寄存器和讀線圈寄存器類似
        Tx_Buf[0] = Rx_Buf[0];//從機地址
        Tx_Buf[1] = PduData.Code;//功能碼
        if(PduData.Num % 8 == 0)//如果讀取線圈的數量是8的整數倍,則返回字節數Tx_Buf[2] = PduData.Num / 8
            Tx_Buf[2] = PduData.Num / 8;
        else//如果不是8的整數倍,則加一
            Tx_Buf[2] = PduData.Num / 8 + 1;

        for(int i = 0; i < Tx_Buf[2];i++)
        {
            Tx_Buf[3 + i] =  (usDiscreteInputBuf[PduData.Addr+7 + i*8] << 7) | (usDiscreteInputBuf[PduData.Addr +6 +i*8] << 6)
                            |(usDiscreteInputBuf[PduData.Addr+5 + i*8] << 5) | (usDiscreteInputBuf[PduData.Addr +4 +i*8] << 4)
                            |(usDiscreteInputBuf[PduData.Addr+3 + i*8] << 3) | (usDiscreteInputBuf[PduData.Addr +2 +i*8] << 2)
                            |(usDiscreteInputBuf[PduData.Addr+1 + i*8] << 1) | (usDiscreteInputBuf[PduData.Addr +0 +i*8] << 0);
        }
        tem_crc = MB_CRC16((uint8_t*)&Tx_Buf,Tx_Buf[2]+3);             // CRC校驗碼
        Tx_Buf[Tx_Buf[2]+3] = (uint8_t)tem_crc;
        Tx_Buf[Tx_Buf[2]+4] = (uint8_t)(tem_crc >> 8);
        HAL_UART_Transmit(&huart1, (uint8_t*)&Tx_Buf, Tx_Buf[2] +5, HAL_MAX_DELAY);
    break;

    case FUN_CODE_03H://響應讀保持寄存器
        Tx_Buf[0] = Rx_Buf[0];
        Tx_Buf[1] = PduData.Code;
        Tx_Buf[2] = PduData.Num * 2;

        for(uint8_t i = 0; i <PduData.Num;i++)
        {
            Tx_Buf[i*2+3] = (uint8_t)(usRegHoldingBuf[PduData.Addr+i] >> 8);
            Tx_Buf[i*2+4] = (uint8_t)(usRegHoldingBuf[PduData.Addr+i] >> 0);
        }

        tem_crc = MB_CRC16((uint8_t*)&Tx_Buf,PduData.Num * 2 +3);             // CRC校驗碼
        Tx_Buf[PduData.Num * 2 +3] = (uint8_t)tem_crc;
        Tx_Buf[PduData.Num * 2 +4] = (uint8_t)(tem_crc >> 8);
        HAL_UART_Transmit(&huart1, (uint8_t*)&Tx_Buf, PduData.Num * 2 +5, HAL_MAX_DELAY);
    break;

    case FUN_CODE_04H://響應讀輸入寄存器
        Tx_Buf[0] = Rx_Buf[0];
        Tx_Buf[1] = PduData.Code;
        Tx_Buf[2] = PduData.Num * 2;

        for(uint8_t i = 0; i <PduData.Num;i++)
        {
            Tx_Buf[i*2+3] = (uint8_t)(usRegInputBuf[PduData.Addr+i] >> 8);
            Tx_Buf[i*2+4] = (uint8_t)(usRegInputBuf[PduData.Addr+i] >> 0);
        }

        tem_crc = MB_CRC16((uint8_t*)&Tx_Buf,PduData.Num * 2 +3);             // CRC校驗碼
        Tx_Buf[PduData.Num * 2 +3] = (uint8_t)tem_crc;
        Tx_Buf[PduData.Num * 2 +4] = (uint8_t)(tem_crc >> 8);
        HAL_UART_Transmit(&huart1, (uint8_t*)&Tx_Buf, PduData.Num * 2 +5, HAL_MAX_DELAY);
    break;

    case FUN_CODE_05H:
        /* 寫入一個線圈值 */
        if(PduData.Num == 0xFF00)
        {
            usCoilBuf[PduData.Addr] = 1;
        }
        else
        {
            usCoilBuf[PduData.Addr] = 0;
        }
        MB_SendRX();//返回發送的指令作為響應
    break;

    case FUN_CODE_06H://寫單個保持寄存器
        usRegHoldingBuf[PduData.Addr] = (Rx_Buf[4] << 8) | (Rx_Buf[5] << 0);
        MB_SendRX();//返回發送的指令作為響應
    break;

    case FUN_CODE_10H://寫入多個保持寄存器
        for(int i = 0;i < PduData.Num;i++)
        {
            usRegHoldingBuf[PduData.Addr + i] = (Rx_Buf[i*2+7] << 8) | (Rx_Buf[i*2+8] << 0);
        }
        //響應寫多個保持寄存器
        Tx_Buf[0] = Rx_Buf[0];//從機地址
        Tx_Buf[1] = Rx_Buf[1];//功能碼
        Tx_Buf[2] = Rx_Buf[2];//起始地址高位
        Tx_Buf[3] = Rx_Buf[3];//起始地址低位
        Tx_Buf[4] = Rx_Buf[4];//數量高位
        Tx_Buf[5] = Rx_Buf[5];//數量低位

        tem_crc = MB_CRC16((uint8_t*)&Tx_Buf,6);             // CRC校驗碼
        Tx_Buf[6] = (uint8_t)tem_crc;//CRC高位
        Tx_Buf[7] = (uint8_t)(tem_crc >> 8);//CRC低位
        HAL_UART_Transmit(&huart1, (uint8_t*)&Tx_Buf, 8, HAL_MAX_DELAY);
    break;
  }
  /* 數據幀沒有異常 */
  return ExCode; //   EX_CODE_NONE
}

/**
  * 函數功能: 寫,讀N個寄存器
  * 輸入參數: _AddrOffset:偏移地址,_RegNum:寄存器數量,_Datebuf:數據指針
  * 返 回 值: 異常碼:04H或NONE
  * 說    明: 在_AddrOffset所指向的空間里寫入_RegNum*2個數據,並且讀取驗證是否寫入成功
  */
void MB_10H_WR_NReg(uint16_t* _AddrOffset,uint16_t _RegNum , uint8_t* _Datebuf)
{
  uint16_t i = 0;
  uint16_t Value = 0;

  for(i=0;i<_RegNum;i++)
  {
    Value = (uint16_t)((*_Datebuf<<8 ) | (*(_Datebuf+1)));
    *_AddrOffset++ = Value ;
    _Datebuf+=2;
  }
    MB_SendRX();
}
完整的 cus_modbus.c 文件
/*
 * cus_modbus.h
 *
 *  Created on: Dec 24, 2019
 *      Author: LiZhiqiang
 */

#ifndef INC_CUS_MODBUS_H_
#define INC_CUS_MODBUS_H_

#include "stm32g0xx_hal.h"
#include "cmsis_os.h"


/* 類型定義 ------------------------------------------------------------------*/
typedef struct {
  __IO uint8_t  Code ;              // 功能碼
  __IO uint8_t byteNums;             // 字節數
  __IO uint16_t Addr ;            // 操作內存的起始地址
  __IO uint16_t Num;                 // 寄存器或者線圈的數量
  __IO uint16_t _CRC;                 // CRC校驗碼
  __IO uint8_t *ValueReg;           // 10H功能碼的數據
  __IO uint8_t *PtrCoilbase;          // Coil和Input內存首地址
  __IO uint8_t *PtrCoilOffset;    // Coil和Input偏移內存首地址
  __IO uint16_t *PtrHoldingbase;  // HoldingReg內存首地址
  __IO uint16_t *PtrHoldingOffset;// HoldingReg內存首地址
}PDUData_TypeDef;

/* 宏定義 --------------------------------------------------------------------*/
#define MB_SLAVEADDR            0x0001
#define MB_ALLSLAVEADDR         0x00FF

#define FUN_CODE_01H            0x01  // 功能碼01H
#define FUN_CODE_02H            0x02  // 功能碼02H
#define FUN_CODE_03H            0x03  // 功能碼03H
#define FUN_CODE_04H            0x04  // 功能碼04H
#define FUN_CODE_05H            0x05  // 功能碼05H
#define FUN_CODE_06H            0x06  // 功能碼06H
#define FUN_CODE_10H            0x10  // 功能碼10H

/* 本例程所支持的功能碼,需要添加新功能碼還需要在.c文件里面添加 */
#define IS_NOT_FUNCODE(code)  (!((code == FUN_CODE_01H)||\
                                 (code == FUN_CODE_02H)||\
                                 (code == FUN_CODE_03H)||\
                                 (code == FUN_CODE_04H)||\
                                 (code == FUN_CODE_05H)||\
                                 (code == FUN_CODE_06H)||\
                                 (code == FUN_CODE_10H)))

#define EX_CODE_NONE           0x00  // 異常碼 無異常
#define EX_CODE_01H            0x01  // 異常碼
#define EX_CODE_02H            0x02  // 異常碼 校驗錯誤
#define EX_CODE_03H            0x03  // 異常碼
#define EX_CODE_04H            0x04  // 異常碼



/* ----------------------- modbus reg lengh Defines ------------------------------------------*/
/* ----------------------- modbus 各個寄存器數據長度,允許用戶調用的數據----------------------*/
#define DISCRETE_INPUT_START        1
#define DISCRETE_INPUT_NDISCRETES   96
#define COIL_START                  1
#define COIL_NCOILS                 96
#define REG_INPUT_START             1
#define REG_INPUT_NREGS             100
#define REG_HOLDING_START           1
#define REG_HOLDING_NREGS           100
/* ----------------------- modbus Static variables defines------------------------------------*/
extern uint16_t   usDiscreteInputStart                             ;
extern uint8_t    usDiscreteInputBuf[DISCRETE_INPUT_NDISCRETES/8]  ;
extern uint16_t   usCoilStart                                      ;
extern uint8_t    usCoilBuf[COIL_NCOILS/8]                         ;
extern uint16_t   usRegInputStart                                  ;
extern uint16_t   usRegInputBuf[REG_INPUT_NREGS]                   ;
extern uint16_t   usRegHoldingStart                                ;
extern uint16_t   usRegHoldingBuf[REG_HOLDING_NREGS]               ;

extern __IO uint8_t Addr_Slave;
extern __IO uint8_t Rx_Buf[256];    // 接收緩存,最大256字節
extern __IO uint8_t Tx_Buf[256];    // 接收緩存,最大256字節
extern __IO uint8_t tmp_Rx_Buf;     // 接收緩存
extern  __IO uint16_t RxCount;      // 接收字符計數
extern PDUData_TypeDef PduData;
void MB_Parse_Data();
uint8_t MB_Analyze_Execute(void );

#endif /* INC_CUS_MODBUS_H_ */
完整的cus_modbus.h文件

 設定從機地址的話,只需要在定時器回調函數中限制一下即可

/* USER CODE BEGIN 1 */
__IO uint8_t Addr_Slave = 3;//設定從機地址
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == htim7.Instance)
    {
        SLAVE_RS485_SEND_MODE;//485切換成發送模式,不再接受新的串口中斷
        HAL_TIM_Base_Stop_IT(&htim7);//停止定時器
        if(Rx_Buf[0] == Addr_Slave)//只處理本從機的命令
        {
            MB_Analyze_Execute();//對接收到的數據進行分析並執行
        }
        RxCount = 0;//清空接收計數
        SLAVE_RS485_RECEIVE_MODE;//數據處理完畢后,再重新接收串口中斷
    }
}
/* USER CODE END 1 */
設定從機地址

 

 

 




免責聲明!

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



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