STM32F4的modbus通讯

Modbus程序

Modbus的功能实现应该是:

1、从机

从机应该时刻处于接收状态,以便于主机能够随时访问从机数据。具体的实现思路为:从机将485的使能位使能为接收状态,然后当接收到数据的时候将数据存储到一个接收缓存中,当接收完成标志位为1 的时候,对存储的数据进行解析。然后返回相应的数据

2、主机

上位机模仿无线模块,通过USART1向主机发送查询请求,主机接收到之后。通过485模块直接向从机发送查询/修改等命令然后再将收到的数据发送回上位机。

从机代码

一、主函数

int main(void)
{
   
     
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2
	delay_init(168);		//延时初始化 
	uart_init(4800);	//串口初始化波特率为115200
	RS485_Init(4800);
	LED_Init();		  		//初始化与LED连接的硬件接口  
	TIM3_Int_Init(100-1,8400-1);//10ms
	while(1)
	{
   
   
		if(RecvData_Flag ==1){
   
   
			Baowenjiexi();
			RecvData_Flag=0;
		}
		
	}
}

在主函数中通过 uart_init(4800) 和 RS485_Init(4800); 分别设置uart1 和uart2 的波特率

二、定时器函数(定时器中断)

timer.h

#ifndef _TIMER_H
#define _TIMER_H

#include "sys.h"

extern u16  RecvData_Flag;//数据是否接收完成:0为未接收;1为接收完成
extern u16  Time_last;    // 距离上次接收数据过去多少时间
extern u16  RecvData_Length;//接收到的数据长度
void TIM3_Int_Init(u16 arr,u16 psc);
#endif

timer.c

#include "timer.h"
#include "led.h"
//通用定时器3中断初始化
//arr:自动重装值。
//psc:时钟预分频数
//定时器溢出时间计算方法:Tout=((arr+1)*(psc+1))/Ft us.
//Ft=定时器工作频率,单位:Mhz
//这里使用的是定时器3!
void TIM3_Int_Init(u16 arr,u16 psc)
{
   
   
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);  ///使能TIM3时钟
	
  TIM_TimeBaseInitStructure.TIM_Period = arr; 	//自动重装载值
	TIM_TimeBaseInitStructure.TIM_Prescaler=psc;  //定时器分频
	TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数模式
	TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1; 
	
	TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);//初始化TIM3
	
	TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE); //允许定时器3更新中断
	TIM_Cmd(TIM3,ENABLE); //使能定时器3
	
	NVIC_InitStructure.NVIC_IRQChannel=TIM3_IRQn; //定时器3中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x01; //抢占优先级1
	NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x03; //子优先级3
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
	NVIC_Init(&NVIC_InitStructure);
	
}
u16  RecvData_Flag=0;//数据是否接收完成:0为未接收;1为接收完成
u16  RecvData_Length=0;//接收到的数据长度
u16  Time_last=0;    // 距离上次接收数据过去多少时间

//定时器3中断服务函数
void TIM3_IRQHandler(void)
{
   
   
	if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET) //溢出中断
	{
   
   
		if(Time_last<60000){
   
   
			Time_last++;
		}
		if((Time_last>10)&&(RecvData_Length!=0)){
   
   
			RecvData_Flag=1;
		}
		TIM_ClearITPendingBit(TIM3,TIM_IT_Update);  //清除中断标志位
	}
	
}

通过使用定时器中断当延时到达十毫秒并且接收数据的长度不等于零的时候我们就认定信息接收完毕,就将接收完毕状态位RecvData_Flag置为1

三、Modbus相关函数

modbus.h

#ifndef _MODBUS_H
#define _MODBUS_H

#include "sys.h"


extern u8 SendBuf[100];
void Baowenjiexi(void);
void Data_Send(u8 *sendbuf,u16 send_len);
void Data_Send1(u8 *sendbuf,u16 send_len);
u16 get_CRC(u8  *buf,u16 len);

#endif

modbus.c

#include "modbus.h"
#include "usart.h"
#include "timer.h"
#include "crc16.h"
#include "rs485.h"
u8 Modbus_SendBuf[100];
u8 Modbus_RecvBuf[100];
u16 modbus_io[100];
u16 modbus_id=1;   //id
u16 modbus_code;//modbus功能码
u16 modbus_CRC;//校验位
u16 modbus_Package_sum=0;
u16  i,t;
void Data_Send(u8 *sendbuf,u16 send_len)
{
   
   
			RS485_TX_EN=1;
			while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);//等待发送结束
			for(t=0;t<send_len;t++)
			{
   
   
				USART_SendData(USART2, sendbuf[t]);         //向串口2发送数据
				while(USART_GetFlagStatus(USART2,USART_FLAG_TC)!=SET);//等待发送结束
			}
			RS485_TX_EN=0;
}
void Data_Send1(u8 *sendbuf,u16 send_len)
{
   
   
			while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);//等待发送结束
			for(t=0;t<send_len;t++)
			{
   
   
				USART_SendData(USART1, sendbuf[t]);         //向串口1发送数据
				while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);//等待发送结束
			}
}
void Baowenjiexi(){
   
   
	//printf("%02X",RecvData_Length);
	Data_Send1(Modbus_RecvBuf,RecvData_Length);
	//RecvData_Length=0;
	
	//需增加校验位计算
	modbus_CRC=crc16tablefast(Modbus_RecvBuf,RecvData_Length-2);
	//printf("0x%02X\r\n",modbus_CRC);
	//printf("0x%02X\r\n",Modbus_RecvBuf[RecvData_Length-2]<<8|Modbus_RecvBuf[RecvData_Length-1]);
	//printf("\r\n报文校验...\r\n");
	if(modbus_CRC==(Modbus_RecvBuf[RecvData_Length-2]<<8|Modbus_RecvBuf[RecvData_Length-1]))//校验是否通过
	{
   
   
		//printf("\r\n报文校验通过:\r\n");
		if(modbus_id==Modbus_RecvBuf[0])
		{
   
   
			//printf("\r\nID通过\r\n");
			modbus_code=Modbus_RecvBuf[1];
			switch(modbus_code){
   
   
				case 3:
					//主机要求从机反馈一些内容
				Modbus_SendBuf[0]=modbus_id;//ID
				Modbus_SendBuf[1]=3;				//功能码			
				Modbus_SendBuf[2]=(Modbus_RecvBuf[4]<<8|Modbus_RecvBuf[5])*2;//字节码长度=寄存器长度x2
				for(modbus_Package_sum=0;modbus_Package_sum<Modbus_SendBuf[2];modbus_Package_sum+=2)
				{
   
   
					Modbus_SendBuf[3+modbus_Package_sum]=modbus_io[Modbus_RecvBuf[4]<<8|Modbus_RecvBuf[5]+modbus_Package_sum/2>>8];
					Modbus_SendBuf[4+modbus_Package_sum]=modbus_io[Modbus_RecvBuf[4]<<8|Modbus_RecvBuf[5]+modbus_Package_sum/2];
				}
				Modbus_SendBuf[3+modbus_Package_sum]=crc16tablefast(Modbus_SendBuf,3+modbus_Package_sum)>>8;
				Modbus_SendBuf[4+modbus_Package_sum]=crc16tablefast(Modbus_SendBuf,3+modbus_Package_sum);
				
				Data_Send(Modbus_SendBuf,5+modbus_Package_sum);
				RecvData_Length=0;
					break;
				case 6:
					for(i=0;i<RecvData_Length;i++)
					{
   
   
						Modbus_SendBuf[i] = Modbus_RecvBuf[i];
					}
					
					Data_Send(Modbus_SendBuf,RecvData_Length);
					RecvData_Length=0;
					break;
				case 16:
					for(i=0;i<RecvData_Length;i++)
					{
   
   
						Modbus_SendBuf[i] = Modbus_RecvBuf[i];
					}
					
					Data_Send(Modbus_SendBuf,RecvData_Length);
					RecvData_Length=0;
					break;
			}	
		}
		
	}else{
   
   
		//printf("\r\n报文校验未通过:\r\n");
		RecvData_Length=0;
	}
	
	
}

Data_Send(u8 *sendbuf,u16 send_len)以及Data_Send1(u8 *sendbuf,u16 send_len)函数分别表示向串口2以及串口1发送数据,数据的内容为sendbuf的内容,而send_len则代表发送数据的长度。

Baowenjiexi()是执行报文解析的函数,其是根据报文的格式进行报文拆分,由于接收到的报文是存储在Modbus_RecvBuf(例如:0x01 0x03 0x00 0x00 0x00 0x0A 0xC5 0xCD)中的,在Modbus格式中Modbus_RecvBuf[0](0x01)存储的是要进行通讯的地址,第二个字节Modbus_RecvBuf[1](0x03)是操作的类型,再往后的两个字节( 0x00 0x00)是代表主机要跟哪个id的从机通讯,最后两个字节(0xC5 0xCD)是校验码。在报文解析中,首先要校验的就是校验码,这决定了从机收到的信息是否正确。如果校验码正确,则对比当前收到的报文的id信息和本机的id信息是否一致,一致的话就进行相应操作码的反馈。

四、CRC校验码

CRC16.h

#ifndef __CRC16_H
#define __CRC16_H
#include "stm32f4xx.h"
//////////////////////////////////////////////////////////////////
//变量声明
extern uint8_t crc16_data[];
//////////////////////////////////////////////////////////////////	
//函数声明
uint16_t crc16tablefast(uint8_t *ptr, uint16_t len);
#endif

CRC16.c

#include "crc16.h"
typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
typedef int int32_t;
const uint16_t polynom = 0xA001
uint16_t crc16bitbybit(uint8_t *ptr, uint16_t len)
{
   
   
	uint8_t i;
	uint16_t crc = 0xffff;
 
	if (len == 0) {
   
   
		len = 1;
	}
	while (len
### STM32F4 Modbus RTU 485通信 示例代码及配置方法 #### 配置硬件接口 为了使STM32F4能够通过RS485总线与其他设备进行Modbus RTU协议下的通讯,需要先设置好硬件部分。这通常涉及到使用USART外设来发送和接收数据帧,并且要连接一个适合的485电平转换芯片以确保信号能够在差分线上正确传输[^1]。 #### 初始化串口参数 在软件层面,初始化用于RS485通信的UART端口是非常重要的一步。具体来说,应该调整波特率、停止位以及校验方式等参数使之匹配目标系统的设定。对于大多数应用而言,默认采用无奇偶校验、一位停止位的方式即可满足需求[^2]。 ```c // UART Initialization Function void MX_USART1_UART_Init(void) { huart1.Instance = USART1; huart1.Init.BaudRate = 9600; // 设置波特率为9600bps huart1.Init.WordLength = UART_WORDLENGTH_8B; huart1.Init.StopBits = UART_STOPBITS_1; huart1.Init.Parity = UART_PARITY_NONE; huart1.Init.Mode = UART_MODE_TX_RX; HAL_UART_Init(&huart1); } ``` #### 实现Modbus RTU协议栈 针对具体的Modbus RTU命令处理逻辑,则可以借助第三方库如FreeModbus来进行简化开发过程。该库提供了完整的API函数支持各种标准操作,比如读取寄存器(功能码`0x03`)或者写单个寄存器等功能[^3]。 ```c #include "mb.h" #include "mbconfig.h" eMBErrorCode eMBRegHoldingCB(U16 *pu16Regs, U16 usAddr, U16 usNRegs, eMBRegisterMode eMode); int main() { /* Initialize the hardware */ SystemClock_Config(); MX_GPIO_Init(); MX_USART1_UART_Init(); /* Start modbus protocol stack */ eMBInit(MB_RTU, SLAVE_ID, MB_BAUDRATE, MB_PARITY); eMBEnable(); while (true) { eMBPoll(); // Polling function to handle incoming requests } } /* Callback function for handling register access */ eMBErrorCode eMBRegHoldingCB(U16 *pu16Regs, U16 usAddr, U16 usNRegs, eMBRegisterMode eMode) { static uint16_t au16Regs[] = { ... }; // Define your registers here if ((usAddr >= REG_INPUT_START) && (usAddr + usNRegs <= REG_INPUT_NREGS)) { switch(eMode){ case MB_REG_READ: memcpy(pu16Regs, &au16Regs[usAddr], usNRegs * sizeof(uint16_t)); break; case MB_REG_WRITE: memcpy(&au16Regs[usAddr], pu16Regs, usNRegs * sizeof(uint16_t)); break; default: return MB_EINVAL; } return MB_ENOERR; }else{ return MB_EINVAL; } } ``` 上述代码展示了如何利用FreeModbus库快速搭建起一个简单的Modbus RTU服务器模型,在实际项目中可以根据业务场景进一步扩展此基础框架。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值