Modbus RTU主机代码实现——STM32
此专栏一共6篇文章
Modbus Poll 、Modbus Slave 软件使用-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(); } } |
更多推荐



所有评论(0)