基于STM32的MODBUS-RTU框架的实现

这篇具有很好参考价值的文章主要介绍了基于STM32的MODBUS-RTU框架的实现。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

基于STM32的MODBUS-RTU框架的实现

---------------------------------------------------------------------------------------手动分割线--------------------------------------------------------------------------------

---------------------------------------------------------------------------------------文章开始--------------------------------------------------------------------------------

一、协议简介

Modbus是一种串行通信协议,是Modicon公司(现在的施耐德电气Schneider Electric)于1979年为使用可编程逻辑控制器(PLC)通信而发表。Modbus已经成为工业领域通信协议的业界标准(De facto),并且现在是工业电子设备之间常用的连接方式。Modbus比其他通信协议使用的更广泛的主要原因有:

  1. 公开发表并且无版权要求
  2. 易于部署和维护
  3. 对供应商来说,修改移动本地的比特或字节没有很多限制

Modbus允许多个 (大约240个) 设备连接在同一个网络上进行通信,举个例子,一个测量温度和湿度的装置,并且将结果发送给计算机。在数据采集与监视控制系统(SCADA)中,Modbus通常用来连接监控计算机和远程终端控制系统(RTU)。

Modbus协议目前存在用于串口、以太网以及其他支持互联网协议的网络的版本。对于串行连接,存在两个变种,它们在数值数据表示不同和协议细节上略有不同。Modbus RTU是一种紧凑的,采用二进制表示数据的方式,Modbus ASCII是一种人类可读的,冗长的表示方式。

本文介绍的为MODBUS-RTU协议在STM32单片机上的实现。

二、协议框架

MODBUS的帧(报告)形式:RTU帧。框架的一般形式如下图所示:
基于STM32的MODBUS-RTU框架的实现
MODBUS的帧根据主从方式分为两种:询问帧和应答帧。

下图为RTU帧的询问帧:
基于STM32的MODBUS-RTU框架的实现
下图为RTU帧的一般应答帧:
基于STM32的MODBUS-RTU框架的实现
下图为一般潜在长度的帧的响应格式:
基于STM32的MODBUS-RTU框架的实现

三、与标准的RTU帧的差异

标准RTU帧:
使用RTU模式,消息发送至少要以3.5个字符时间的停顿间隔开始。在网络波特率下多样的字符时间,这是最容易实现的。
传输的第一个域是设备地址。可以使用的传输字符是十六进制的0…9,A…F。网络设备不断侦测网络总线,包括停顿间隔时间内。
当第一个域(地址域)接收到,每个设备都进行解码以判断是否发往自己的。
在最后一个传输字符之后,一个至少3.5个字符时间的停顿标定了消息的结束。一个新的消息可在此停顿后开始。

整个消息帧必须作为一连续的流转输。如果在帧完成之前有超过1.5个字符时间的停顿时间,接收设备将刷新不完整的消息并假定下一字节是一个新消息的地址域。
同样地,如果一个新消息在小于3.5个字符时间内接着前个消息开始,接收的设备将认为它是前一消息的延续。
这将导致一个错误,因为在最后的CRC域的值不可能是正确的。

STM32的处理方式:
采用标准的RTU帧实现每一帧的数据分割有点麻烦,需要使用单独的定时器来进行接收字符时间判断。
在STM32上,采用串口空闲接收中断实现对每帧数据的分割,从而简化STM32上的MODBUS的RTU协议,实现快速实现。

四、串口空闲接收中断

关于串口空闲中断网上有很多教程,我这里就直接提供代码,后续有需要再单独出个帖子。


//空闲中断初始化函数
XL_StatusTypeDef XL_UartIdle_DMA_Init(UART_HandleTypeDef *huart,uint8_t *pData)
{
	__HAL_UART_CLEAR_IDLEFLAG(huart);       //清除空闲中断标志
	__HAL_UART_ENABLE_IT(huart, UART_IT_IDLE);//启动串口空闲中断
	return (XL_StatusTypeDef)HAL_UARTEx_ReceiveToIdle_DMA(huart,pData,XL_UartRx_Len);//开启DMA接收
}

//空闲中断回调函数
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
	void XL_Uart_Idle(UART_HandleTypeDef *huart, uint8_t *pData,uint16_t Size);
	if(huart == &hlpuart1)
		XL_Uart_Idle(huart,LPU1_RxBuff.pData,Size);
	if(huart == &huart3)
		XL_Uart_Idle(huart,U3_RxBuff.pData,Size);	
}

五、RTU协议框架

//申明函数
ModbusState BackCheckFunction(uint8_t subcode);
ModbusState ReadEventFunction(void);


//modbus函数框架
ModbusState XL_Modbus_RTU_Frame(uint8_t Addr,uint8_t *pData,uint16_t len)
{
	//判断地址是否正确、及数据长度
	if(len<3 || Addr != pData[0] )
		return MB_NULL;
	
	//通信为低位在前
	uint16_t crc  = crc16tablefast(pData,len-2);
	
	//判断CRC
	if(pData[len-2] != (uint8_t )crc||pData[len-1] != (uint8_t )(crc>>8) )
		return MB_NULL;
	
	//对应功能码的函数检查
	ModbusState State = LenCheck((FunCode)pData[1],pData,len);
	if(State != MB_OK)
	{
		//返回错误功能码
		ErrorSend((FunCode)pData[1],State);
		return MB_ERROR;
	}
	
	//接收处理计数++
	EventCountNum++;
	
	switch(pData[1])
	{
		case ReadReg: //读取多个寄存器
			ReadRegFunction(pData,len);
			break;
		
		case ReadInputReg: //读取多个输入寄存器
			ReadRegFunction(pData,len);
			break;
		
		case WriteSingleReg: //写入单个寄存器
			WriteRegFunction(pData,len);
			break;
			
		case ReadEventCount: //读取事件计数
			ReadEventFunction();
			break;	
		
		case WriteReg: //写入多个寄存器
			WriteRegFunction(pData,len);
			break;	
		
		default: //异常回复,不存在的功能码
			ErrorSend((FunCode)pData[1],UNFUNCODE);
			break;
	}
	
	
	return MB_OK;
}


//寄存器映射--MARK()
ModbusState RegMap(uint16_t regaddr,uint16_t *reg,ModbusState code )
{
	//先进行地址偏移
	
	/*这部分代码需要自己实现*/
	
	return MB_OK;
}


//读取多个寄存器
ModbusState ReadRegFunction(uint8_t *pData,uint16_t len)
{
	//定义发送数组
	uint8_t bData[310] = {pData[0],pData[1],2*((pData[4]<<8)+pData[5])};
	//定义返回数据长度
	uint16_t blen = 2*((pData[4]<<8)+pData[5])+5;
	//寄存器开始地址
	uint16_t addr = (pData[2]<<8)+pData[3];
	//读取寄存器数
	uint16_t allnum =  (pData[4]<<8)+pData[5];
	
	//遍历寄存器
	for(int i = 0 ;i<allnum;i++)
	{
		uint16_t reg;
		RegMap(addr+i,&reg,MB_READ);
		bData[3+i*2] = reg>>8;
		bData[3+i*2+1] = reg;
	}
	
	//发送数据出去
	MBSendCRC(bData,blen);
	return MB_OK;
}


//写入寄存器
ModbusState WriteRegFunction(uint8_t *pData,uint16_t len)
{
	//寄存器开始地址
	uint16_t addr = (pData[2]<<8)+pData[3];
	//定义发送数组
	uint8_t bData[8] = {pData[0],pData[1],pData[2],pData[3]};
	//单个寄存器
	if(pData[1] == WriteSingleReg)
	{
		//填充长度
		bData[4] = 0;
		bData[5] = 1;
		uint16_t preg = (pData[4]<<8)+pData[5];
		RegMap(addr,&preg,MB_WRITE);
	}
	
	
	//多个寄存器
	if(pData[1] == WriteReg)
	{
		//填充长度
		bData[4] = pData[4];
		bData[5] = pData[5];
		//读取字节数
		uint16_t allnum =  pData[6]/2;				
		//遍历寄存器
		for(int i = 0 ;i<allnum;i++)
		{
			uint16_t preg = (pData[7+i*2]<<8)+pData[7+i*2+1];
			RegMap(addr+i,&preg,MB_WRITE);
		}
	}	
	//发送数据出去
	MBSendCRC(bData,8);
	return MB_OK;		
	
}


//modbus的通信状态校验的函数
ModbusState BackCheckFunction(uint8_t subcode)
{
	switch(subcode)
	{
		case 0x00: //返回询问数据
		{
			uint8_t data[8] = {SlaveAddr,BackCheck,0x00,0x00,0x00,0x01};
			MBSendCRC(data,8);
			break;		
		}

		default:
			ErrorSend(BackCheck,UNFUNCODE);
			break;
	}
	return MB_OK;
}


//modbus的读取事件计数的函数
ModbusState ReadEventFunction(void)
{
	uint8_t data[8] = {SlaveAddr,ReadEventCount,0x00,0x00,0x00,EventCountNum};
	
	MBSendCRC(data,8);
	
	return MB_OK;
}


//ModbusRTU的长度检测函数
ModbusState LenCheck(FunCode code,uint8_t *pData,uint16_t len)
{
	switch(pData[1])
	{
		case ReadReg: //读取多个寄存器
			if(pData[4]!= 0|| pData[5] > Max_RegNum)	//判断读取的最大长度是否超过125寄存器
				return LENERROR;			
			if(len != 8) 										 	//数据主机发送的不彻底或字节的数量是错误的
				return LENERROR;
			return MB_OK;											//通过筛选
			
		case ReadInputReg: //读取多个输入寄存器
			if(pData[4]!= 0|| pData[5] > Max_RegNum)	//判断读取的最大长度是否超过125寄存器
				return LENERROR;			
			if(len != 8) 										 	//数据主机发送的不彻底或字节的数量是错误的
				return LENERROR;
			return MB_OK;											//通过筛选		
			
		case WriteSingleReg: //写入单个寄存器
			if(len != 8) 										 	//数据主机发送的不彻底或字节的数量是错误的
				return LENERROR;
			return MB_OK;											//通过筛选				
		
		case BackCheck: //回送诊断校验,等待与陈铭讨论
			if(len < 8 || len>310)
				return LENERROR;
			return MB_OK;
		
		case ReadEventCount: //读取事件计数
			if(len < 4 || len>310)
				return LENERROR;
			return MB_OK;
			
		case WriteReg: //写入多个寄存器
			if(pData[4]!= 0|| pData[5] > Max_RegNum || len != (pData[6]+9)||(2*((pData[4]<<8)+pData[5]))!=pData[6])	//判断读取的最大长度是否超过125寄存器
				return LENERROR;
			return MB_OK;
			
		default: //异常回复
			return UNFUNCODE;
	}	
}



/*-----crc校验查表----------------
辅助完成CRC校验,是CRC校验的快速查表法
----------------------------------*/
const uint16_t crctalbeabs[] = { 
	0x0000, 0xCC01, 0xD801, 0x1400, 0xF001, 0x3C00, 0x2800, 0xE401, 
	0xA001, 0x6C00, 0x7800, 0xB401, 0x5000, 0x9C01, 0x8801, 0x4400 
};



/*-------CRC校验函数----------------
输入为字符指针(ptr)、字符指针长度(len)
//ptr为校验数组的指针,len为校验数组的元素个数
返回CRC校验结果,为16位
根据实际需求进行CRC校验码
----------------------------------*/
uint16_t crc16tablefast(uint8_t *ptr, uint16_t len) 
{
	uint16_t crc = 0xffff; 
	uint16_t i;
	uint8_t ch;
 
	for (i = 0; i < len; i++)
	{
		ch = *ptr++;
		crc = crctalbeabs[(ch ^ crc) & 15] ^ (crc >> 4);
		crc = crctalbeabs[((ch >> 4) ^ crc) & 15] ^ (crc >> 4);
	} 
	
	return crc;
}

//modbus 发送函数
void MBSend(uint8_t *pData,uint16_t len)
{
	//需要自己修改串口发送驱动函数
	//XL_Transmit(&hlpuart1,pData,len,Max_SendUart_Time);
	//XL_Transmit(&huart3,pData,len,Max_SendUart_Time);
}

//modbus 发送函数
void MBSendCRC(uint8_t *pData,uint16_t len)
{
	uint16_t crc  = crc16tablefast(pData,len-2);
	
	pData[len-2] = (uint8_t )crc;
	
	pData[len-1] = (uint8_t )(crc>>8);		
	
	//需要自己修改串口发送驱动函数
	//XL_Transmit(&hlpuart1,pData,len,Max_SendUart_Time);
	//XL_Transmit(&huart3,pData,len,Max_SendUart_Time);
}


//异常、错误代码发送
void ErrorSend(FunCode code,ModbusState state)
{
	uint8_t data[5] = {SlaveAddr,code+0x80,state};
	
	MBSendCRC(data,5);	
}

六、总结

工程文件(不需要积分):

https://download.csdn.net/download/qq_40824852/85022712?spm=1001.2014.3001.5503

目前采用STM32实现了部分MODBUS的简易框架,能实现较为简单的协议通信,后续有需求会更新。

----------------------------------------------------------------------------------到这里就结束了-------------------------------------------------------------------------------

时间流逝、年龄增长,是自己的磨炼、对知识技术的应用,还有那不变的一颗对嵌入式热爱的心!
基于STM32的MODBUS-RTU框架的实现

到这里就结束了,希望大家点赞o( ̄▽ ̄)d、关注(o)/~、评论(▽)!文章来源地址https://www.toymoban.com/news/detail-429440.html

到了这里,关于基于STM32的MODBUS-RTU框架的实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • STM32开发之Modbus协议(RTU从站)

    说明 1、本文不做协议格式的讲解,只做实现,如需了解协议格式,自行搜索 2、本文不依赖于硬件相关的资源,建立在硬件通讯之上,通过回调的形式和对应的硬件进行关联 3、相关协议内容参照,上一篇RTU主站 宏定义(modbus_core_define) crc校验(modbus_core_crc) 头文件 源文件

    2024年02月11日
    浏览(48)
  • STM32开发之Modbus协议(主站RTU)

    在单片机方面,针对于通讯常用的协议之一modbus,这里将modbus协议和硬件之间的关系完全独立出来,硬件和协议之间的联系采用的是回调的方式进行一个关联。 1、此协议可直接移植,并不需要关心硬件相关的。 2、modbus相关协议概念自行查找,本文只做代码的实现。 宏定义(

    2024年02月12日
    浏览(45)
  • 用C++QT实现一个modbus rtu通讯程序框架

    下面是一个简单的Modbus RTU通讯程序框架的示例,使用C++和QT来实现: 具体的数据处理将根据需求进行扩展和实现,如写入数据和处理异常等。另外,需要根据实际情况设置正确的串口参数和设备地址,并确保与Modbus设备的正确连接。在编译和运行程序之前,还需要在项目的

    2024年02月06日
    浏览(59)
  • STM32实现基于RS485的简单的Modbus协议

    我这里用STM32实现,其实可以搬移到其他MCU,之前有项目使用STM32实现Modbus协议 这个场景比较正常,很多时候都能碰到 这里主要是Modbus和变频器通信 最常见的是使用Modbus实现传感器数据的采集,我记得之前用过一些传感器都是Modbus协议 这就需要MCU实现Modbus协议,不过实际使

    2024年02月08日
    浏览(59)
  • [STC32F12K54入门第三步]USART1+Modbus RTU从机

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 提示:以下是本篇文章正文内容,下面案例可供参考 想要了解的去看看我STM32高级篇的Modbus RTU的文章或者自己去网上看看。其实很简单。就是一些报文格式,然后解析格式。本文主要是驱动程序。注意

    2024年02月12日
    浏览(53)
  • Profibus-DP转modbus RTU网关modbus rtu协议

    捷米JM-DPM-RTU网关在Profibus总线侧实现主站功能,在Modbus串口侧实现从站功能。可将ProfibusDP协议的设备(如:E+H流量计、倍福编码器等)接入到Modbus网络中;通过增加DP/PA耦合器,也可将Profibus PA从站接入Modbus网络。在Modbus串口侧提供RS485和RS232两种电平接口。 捷米JM-DPM-RTU网关

    2024年02月10日
    浏览(44)
  • Modbus RTU通信应用

    1.1 概述         Modbus串行通信协议是Modicon公司在1970年开发的。         Modbus串行通信协议有Modbus ASCII和Modbus RTU两种模式,Modbus RTU协议通信效率较高,应用更加广泛。         Modbus RTU协议是基于RS232和RS485串行通信的一种协议,数据通信采用主从方式进行传送,主站

    2024年02月15日
    浏览(53)
  • # 项目一:‍‍‍STM32+串口DMA+RS485+MODBUS+传感器实现SO2的测试

    通过STM32控制传感器实现气体浓度的测量:RS485+MODBUS+串口DMA+定时器。 其中,USART2负责控制数据的发送和接受,USART3负责将询问帧、应答帧以及处理后数据打印出来,TIM1负责每隔1秒发送一次询问帧。 持续更新手中的项目(导师给的活)经验。。。 打工人加油🐱‍🚀🐱‍🚀

    2024年02月11日
    浏览(39)
  • MODBUS RTU通讯常见错误代码

    错误代码 MB_MASTER 报错8200(端口正忙于处理传送请求),如何处理? 此情况是由于MB_MASTER的DONE或ERROR均

    2024年02月13日
    浏览(55)
  • 关于STM32F4和GD32F4以太网,LAN8720+lwip+freemodbus,实现modbus tcp

    关于STM32F4和GD32F4以太网,LAN8720+lwip+freemodbus 这里使用了大佬 小灰灰搞电子 的代码,文章看 STM32F407+LAN8720移植Lwip和freeModbus实现MODBUS TCP 代码看 STM32F407+LAN8720+LWIP移植freemodbus TCP.zip 他的代码是基于正点原子F407的板子开发的,如果是别的板子,需要修改引脚 小灰灰的代码里,没

    2024年02月14日
    浏览(39)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包