此专栏一共6篇文章

Modbus Poll 软件安装-CSDN博客

Modbus Slave 软件安装-CSDN博客

Modbus Poll 、Modbus Slave 软件使用-CSDN博客

Modbus RTU-CSDN博客

Modbus RTU主机代码实现——STM32-CSDN博客

Modbus RTU 从机代码实现——STM32-CSDN博客

外设配置

选用芯片——STM32f103C6T6

UART1

用于485多机通信

阻塞发送,中断接收

波特率115200

1起始位 8数据位 0校验位 1停止位

Timer3

用于T3.5判断

向上计数,72分频,重装载值100——100us中断

UART2

用于主机程序调试——将从机回复数据发给串口助手

阻塞发送,中断接收

波特率115200

1起始位 8数据位 0校验位 1停止位

主机程序

仅提供一个思路,具体项目里怎么写还需自己发挥

一共涉及8个文件

LH_ModbusMaster.c

LH_RS485.c

LH_UART.c

LH_ISR.c

main.c

LH_ModbusMaster.h

LH_RS485.h

LH_UART.h

LH_ModbusMaster.c

//*******************************************************

//作者------LH

//时间------2025.01.05

//模块功能--Modbus RTU 主机程序 给出示例--03H  04H  10H

//并将从机回复的报文 通过UART2发给串口助手

//*******************************************************

 

 

#include "stm32f1xx_hal.h"

#include "LH_ModbusMaster.h"

#include "LH_RS485.h"

#include "LH_UART.h"

#include "LH_LED.h"

 

 

 

//波特率115200   T3.5 = 1000000/115200*(1+8+0+1)*3.5 = 303us

//波特率115200   T3.5 = 1000000/115200*(1+8+1+1)*3.5 = 334us

uint32_t    guw_ModM_RxT35 = 334//3.5字符时间 us

uint32_t    guw_ModM_RXTCnt = 0;   //接收字符时间间隔

 

 

//发送/接收缓冲区

uint8_t     guca_ModM_RxBuff[256];//接收缓冲区-每帧最大256字节

uint8_t     guca_ModM_TxBuff[256];//发送缓冲区-每帧最大256字节

 

//发送辅助变量

struct ModM_TxPara

{

  uint8_t  SlaveID;

  uint8_t  FunctionCode;

  uint16_t StartRegAddr;

  uint16_t OpRegNum;

};

 

struct ModM_TxPara gst_ModM_TxPara;

 

 

//接收辅助变量

uint16_t    guh_ModM_RxLength = 0;//接收数据长度

uint8_t     guc_ModM_RXEndFlag = 0; //接收结束标志

uint8_t     guc_ModM_RxOneByte[1];  //UART接收缓冲位置

 

 

 

//主机初始化程序

void LH_ModM_Init(void)

{

  //-------------------------------

 

  //------------------------------

 

  //开启USART接收中断

  LH_UART1_Start(guc_ModM_RxOneByte, 1);

 

}

 

 

//接收中断服务函数

extern UART_HandleTypeDef huart1;

void LH_ModM_ReceiveISR()

{

  if (guh_ModM_RxLength<256) //一次帧接收字节<256

  {

    guca_ModM_RxBuff[guh_ModM_RxLength++] = guc_ModM_RxOneByte[0];

    guw_ModM_RXTCnt = 0;

  }

  else

  {

    //一次接收超过256字节就不接收了

 

  }

 

  HAL_UART_Receive_IT(&huart1, guc_ModM_RxOneByte, 1); //再次开中断

 

}

 

 

 

//接收完成判断程序

//需要一个定时器 100us中断

//在定时器中断服务函数里调用

void LH_ModM_RXT35ISR(void) //100us中断

{

  guw_ModM_RXTCnt += 100;

 

  if ((guw_ModM_RXTCnt >  guw_ModM_RxT35)) //字符间隔超过T35

  {

    if (guh_ModM_RxLength != 0) //已经开始接收

    {

      guc_ModM_RXEndFlag =1;

    }

  }

 

}

 

 

 

 

//CRC16计算程序

uint16_t LH_ModM_GetCRC16(uint8_t *Data, uint16_t length)

{

  uint16_t CRC16 = 0xffff// CRC校验码

  uint16_t Polynomial = 0xa001; //生成多项式

 

  for (int i = 0; i < length; i++)

  {

    CRC16 ^= Data[i];

 

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

    {

      if ((CRC16 & 0x0001) != 0) //x&1 = 1

      {

        CRC16 >>= 1;

        CRC16 ^= Polynomial;

      }

      else

      {

        CRC16 >>= 1;

      }

    }

  }

 

  return CRC16;

}

 

 

 

//设置发送参数

void LH_ModM_WriteTxPara(uint8_t SlaveID, uint8_t  FunctionCode, uint16_t StartRegAddr, uint16_t OpRegNum)

{

  gst_ModM_TxPara.SlaveID = SlaveID;

  gst_ModM_TxPara.FunctionCode = FunctionCode;

  gst_ModM_TxPara.StartRegAddr = StartRegAddr;

  gst_ModM_TxPara.OpRegNum = OpRegNum;

}

 

 

 

//主机发送各指令函数--各功能码函数

void LH_ModM_03H(void)

{

  guca_ModM_TxBuff[0] = gst_ModM_TxPara.SlaveID;

  guca_ModM_TxBuff[1] = gst_ModM_TxPara.FunctionCode;

  guca_ModM_TxBuff[2] = (gst_ModM_TxPara.StartRegAddr >> 8);

  guca_ModM_TxBuff[3] = gst_ModM_TxPara.StartRegAddr;

  guca_ModM_TxBuff[4] = (gst_ModM_TxPara.OpRegNum >> 8);

  guca_ModM_TxBuff[5] = gst_ModM_TxPara.OpRegNum;

 

  uint16_t TxCRC;

  TxCRC = LH_ModM_GetCRC16(guca_ModM_TxBuff, 6);

 

  guca_ModM_TxBuff[6] = TxCRC ;

  guca_ModM_TxBuff[7] = (TxCRC >> 8); //先发送CRC低位 后发送高位

 

  LH_ModM_SendData(guca_ModM_TxBuff, 8);

 

}

 

//主机发送各指令函数--各功能码函数

void LH_ModM_04H(void)

{

  guca_ModM_TxBuff[0] = gst_ModM_TxPara.SlaveID;

  guca_ModM_TxBuff[1] = gst_ModM_TxPara.FunctionCode;

  guca_ModM_TxBuff[2] = (gst_ModM_TxPara.StartRegAddr >> 8);

  guca_ModM_TxBuff[3] = gst_ModM_TxPara.StartRegAddr;

  guca_ModM_TxBuff[4] = (gst_ModM_TxPara.OpRegNum >> 8);

  guca_ModM_TxBuff[5] = gst_ModM_TxPara.OpRegNum;

 

  uint16_t TxCRC;

  TxCRC = LH_ModM_GetCRC16(guca_ModM_TxBuff, 6);

 

  guca_ModM_TxBuff[6] = TxCRC ;

  guca_ModM_TxBuff[7] = (TxCRC >> 8); //先发送CRC低位 后发送高位

 

 

  LH_ModM_SendData(guca_ModM_TxBuff, 8);

 

}

 

void LH_ModM_10H(void)

{

  guca_ModM_TxBuff[0] = gst_ModM_TxPara.SlaveID;

  guca_ModM_TxBuff[1] = gst_ModM_TxPara.FunctionCode;

  guca_ModM_TxBuff[2] = (gst_ModM_TxPara.StartRegAddr >> 8);

  guca_ModM_TxBuff[3] = gst_ModM_TxPara.StartRegAddr;

  guca_ModM_TxBuff[4] = (gst_ModM_TxPara.OpRegNum >> 8);

  guca_ModM_TxBuff[5] = gst_ModM_TxPara.OpRegNum;

 

  guca_ModM_TxBuff[6] = 2*gst_ModM_TxPara.OpRegNum;

 

  //要写的数据--请根据实际需求赋值

  for (int i = 0 ; i < gst_ModM_TxPara.OpRegNum ; i++)

  {

    guca_ModM_TxBuff[7 + i*2] = 0 ;//8

    guca_ModM_TxBuff[8 + i*2] = i ;//8

 

  }

 

  uint16_t TxCRC;

  TxCRC = LH_ModM_GetCRC16(guca_ModM_TxBuff, 7+2*gst_ModM_TxPara.OpRegNum);

 

  guca_ModM_TxBuff[7 + 2*gst_ModM_TxPara.OpRegNum] = TxCRC ;

  guca_ModM_TxBuff[8 + 2*gst_ModM_TxPara.OpRegNum] = (TxCRC >> 8); //先发送CRC低位 后发送高位

 

 

  LH_ModM_SendData(guca_ModM_TxBuff, 11+2*gst_ModM_TxPara.OpRegNum);

 

}

 

 

//主机发送数据程序

//发送命令时调用

void LH_ModM_SendData(uint8_t *TxBuff, uint16_t length)

{

  LH_RS485_SendData(TxBuff, length);

}

 

 

//主机发送指令执行程序——1s周期调用

//示例 自己按实际需求修改

uint8_t guc_ModM_CmdFlag = 3;

void LH_ModM_SendCmd(void)

{

  switch (guc_ModM_CmdFlag)

  {

    case 03:

    {

      LH_ModM_WriteTxPara(0x09, 0x03, 2, 5);

      LH_ModM_03H();

      guc_ModM_CmdFlag = 4;

      break;

    }

 

    case 04:

    {

      LH_ModM_WriteTxPara(0x09, 0x04, 1, 3);

      LH_ModM_04H();

      guc_ModM_CmdFlag = 16;

      break;

    }

 

    case 16:

    {

      LH_ModM_WriteTxPara(0x09, 0x10, 2, 5);

      LH_ModM_10H();

      guc_ModM_CmdFlag = 3;

      break;

    }

 

    default :

      guc_ModM_CmdFlag = 3;

      break;

  }

 

 

 

}

 

 

 

 

//主机处理从机响应数据

//下一次发送指令前调用

void LH_ModM_ProcessRxData(void)

{

  if (guc_ModM_RXEndFlag == 1) //接收帧结束

  {

    LH_LED_Change();

    uint16_t CalRxBuffCRC, RecRXBuffCRC;

    CalRxBuffCRC = LH_ModM_GetCRC16(guca_ModM_RxBuff, guh_ModM_RxLength-2);

 

    RecRXBuffCRC = guca_ModM_RxBuff[guh_ModM_RxLength - 2]

                   | (((uint16_t)guca_ModM_RxBuff[guh_ModM_RxLength-1])<<8);//接收CRC

 

    if (CalRxBuffCRC==RecRXBuffCRC) //CRC校验正确

    {

      //LH_LED_Change();

 

      switch (guca_ModM_RxBuff[1]) //根据不同的功能码进行处理

      {

        case 03:

        {

          //根据实际项目需求 设计处理操作

          break;

        }

 

        case 04:

        {

          //根据实际项目需求 设计处理操作

          break;

        }

 

        case 16:

        {

          //根据实际项目需求 设计处理操作

          break;

        }

 

        default :

          //其他功能码 还未实现

          break;

      }

    }

    else//错误码

    {

      //根据实际项目需求 设计处理操作

    }

 

  }

 

 

  //串口2将接收数据发给上位机

  LH_UART2_SendData(guca_ModM_RxBuff, guh_ModM_RxLength);

 

  guc_ModM_RXEndFlag = 0;

  guh_ModM_RxLength = 0;

  guw_ModM_RXTCnt = 0;

 

}

LH_ModbusMaster.h

 

#ifndef __LH_MODBUSMASTER_H

#define __LH_MODBUSMASTER_H

 

#include "stdint.h"

 

void LH_ModM_Init(void);

 

void LH_ModM_WriteTxPara(uint8_t SlaveID , uint8_t  FunctionCode , uint16_t StartRegAddr , uint16_t OpRegNum);

 

void LH_ModM_03H(void);

void LH_ModM_04H(void);

void LH_ModM_10H(void);

 

void LH_ModM_SendData(uint8_t *p ,uint16_t length);

void LH_ModM_ReceiveISR(void);

 

 

void LH_ModM_SendCmd(void);//主机发送指令程序——1s周期调用一次

 

void  LH_ModM_ProcessRxData(void); //主机处理从机回复数据——下次发送指令前调用

 

void LH_ModM_RXT35ISR(void); //T35判断程序

 

 

#endif

 

LH_RS485.c

//*******************************************************

//作者------LH

//时间------2025.01.05

//模块功能--485发送 接收数据

//用的485电路是自控流向的,所以不需要引脚控制流向

//*******************************************************

 

#include "LH_RS485.h"

#include "LH_UART.h"

#include "LH_ModbusSlave.h"

#include "LH_ModbusMaster.h"

 

//485发送函数

void LH_RS485_SendData(uint8_t *TxBuff, uint16_t length)

{

  LH_UART1_SendData(TxBuff, length);

}

 

void LH_RS485_ReceiveISR(void)

{

  //主机

  LH_ModM_ReceiveISR();

  //LH_ModM_ReceiveISR里再次开中断

 

  //从机

  //LH_ModS_ReceiveISR();

  //LH_ModS_ReceiveISR里再次开中断

 

}

LH_RS485.h

#ifndef __LH_RS485_H

#define __LH_RS485_H

 

#include "stdint.h"

 

//485发送函数

void LH_RS485_SendData(uint8_t *TxBuff ,uint16_t length);

 

void LH_RS485_ReceiveISR(void);

 

#endif

LH_UART.c

//*******************************************************

//作者------LH

//时间------2024.12.08

//模块功能--阻塞发送 中断接收

//*******************************************************

#include "stm32f1xx_hal.h"

#include "LH_UART.h"

#include "LH_RS485.h"

 

extern UART_HandleTypeDef huart1;//485 主从机Modbus通信

extern UART_HandleTypeDef huart2;//串口助手调试

 

 

//开启UART1接收中断

void LH_UART1_Start(uint8_t *RxBuff, uint16_t length)

{

  HAL_UART_Receive_IT(&huart1, RxBuff, length); //接收够字节长度才触发中断,且每次设置只进入一次中断

}

 

//UART1发送数据

void LH_UART1_SendData(uint8_t *TxBuff, uint16_t length)

{

  HAL_UART_Transmit(&huart1, TxBuff, length, 1); //阻塞发送

}

 

//UART1接收中断服务函数

void LH_UART1_ReceiveISR(void)

{

  LH_RS485_ReceiveISR();

  // LH_RS485_ReceiveISR 里再次开中断

}

 

//UART2发送数据

void LH_UART2_SendData(uint8_t *TxBuff, uint16_t length)

{

  HAL_UART_Transmit(&huart2, TxBuff, length, 1); //阻塞发送

}

LH_UART.h

#ifndef __LH_UART_H

#define __LH_UART_H

 

#include "stdint.h"

 

//开启UART1接收中断

void LH_UART1_Start(uint8_t *RxBuff ,uint16_t length);

//UART1发送数据

void LH_UART1_SendData(uint8_t *TxBuff ,uint16_t length);

//UART1中断接收数据

void LH_UART1_ReceiveISR(void );

 

 

//UART2发送数据

void LH_UART2_SendData(uint8_t *TxBuff ,uint16_t length);

#endif

LH_ISR.c

//*******************************************************

//作者------LH

//时间------2024.12.23

//模块功能--所有中断回调函数

//*******************************************************

#include "stm32f1xx_hal.h"

#include "LH_Delay.h"

#include "LH_AUART.h"

#include "LH_UART.h"

#include "LH_ModbusSlave.h"

#include "LH_ModbusMaster.h"

#include "LH_LED.h"

 

extern TIM_HandleTypeDef htim2;

extern UART_HandleTypeDef huart1;

 

//重写HAL库定时中断回调函数

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)

{

  if (htim->Instance == TIM3)

  {

    //主机

    LH_ModM_RXT35ISR();

    //从机

    // LH_ModS_RXT35ISR();

  }

}

 

 

//串口接收中断回调函数

uint8_t guc_RxOneByte[1];

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)

{

  if (huart->Instance == USART1)

  {

    LH_UART1_ReceiveISR();

    // LH_UART1_ReceiveISR 里再次开中断

  }

}

 

 

main.c

int main(void)

{

  HAL_Init();

  SystemClock_Config();

  MX_GPIO_Init();

  MX_TIM2_Init();

  MX_USART1_UART_Init();

  MX_TIM3_Init();

  MX_USART2_UART_Init();

  /* USER CODE BEGIN 2 */

  HAL_TIM_Base_Start(&htim2);

  HAL_TIM_Base_Start_IT(&htim3);

   

  LH_ModM_Init();

  LH_ModS_Init();

  /* USER CODE END 2 */

  while (1)

  {

        //主机——1s发送一次指令

        LH_Delay_Us(1000000);       

        LH_ModM_ProcessRxData();//处理上一次从机回复数据

        LH_ModM_SendCmd();//发送下一次报文指令

               

        //从机——10ms检查一次

        //LH_Delay_Us(10000);       

        //LH_ModS_ExeCmd();

  }

}

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐