自写简易版从机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 */
设定从机地址


原文地址:https://www.cnblogs.com/lizhiqiang0204/p/12108212.html