zoukankan      html  css  js  c++  java
  • 自写简易版从机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 */
    设定从机地址


  • 相关阅读:
    2017-2018-1 20155320加分项目——pwd的实现
    2017-2018-1 20155306 20155320 20155326《信息安全技术》实验三——数字证书应用
    2017-2018-1 20155320 《信息安全系统设计基础》第八周学习总结
    2017-2018-1 20155320第八周课堂实践总结+课下作业
    2017-2018-1 20155320 《信息安全系统设计基础》第七周学习总结
    2017-2018-1 20155320 20155326 实验二 固件程序设计
    2017-2018-1 20155320第六周课堂实践总结
    2017-2018-1 20155320缓冲区溢出漏洞实验
    2017-2018-1 20155320 《信息安全系统设计基础》第六周学习总结
    2017-2018-1 20155318 实验三 实时系统报告
  • 原文地址:https://www.cnblogs.com/lizhiqiang0204/p/12108212.html
Copyright © 2011-2022 走看看