STM32实现基于RS485的简单的Modbus协议
背景
我这里用STM32实现,其实可以搬移到其他MCU,之前有项目使用STM32实现Modbus协议
这个场景比较正常,很多时候都能碰到
这里主要是Modbus和变频器通信
最常见的是使用Modbus实现传感器数据的采集,我记得之前用过一些传感器都是Modbus协议
这就需要MCU实现Modbus协议,不过实际使用的Modbus协议往往都是简化版本的
可能只是几条Modbus协议格式的指令而已
初学者,网上一搜Modubus协议,往往越看越糊涂
原理图
如下图所示,使用STM32 UART2,采用485接口设计引出
解释一下为什么这里的485电路设计的这么复杂
这里考虑485带电插拔操作,以及客户要求隔离功能等,所以硬件上设计比常用电路复杂很多
其实主要功能都是一致的
软件设计
初始化串口,这里写的比较复杂,因为考虑了串口2也就是485接口的波特率是可以配置的,并且配置后掉电保存,所以有个波特率的接口,当然同时也有校验位可配置
如下配置,串口采用中断模式,使用串口2,对应管脚PA2/PA3
void Bsp_usart2_cfg(u8 baud, u8 checkbit)
{ NVIC_InitTypeDef NVIC_InitStructure; GPIO_InitTypeDef GPIO_InitStructure;USART_InitTypeDef USART_InitStructure; u32 BaudRate;switch(baud){ case 0:{BaudRate = 300;break;}case 1:{BaudRate = 600;break;}case 2:{BaudRate = 1200;break;}case 3:{BaudRate = 2400;break;}case 4:{BaudRate = 4800;break;}case 5:{BaudRate = 9600;break;}case 6:{BaudRate = 19200;break;}case 7:{BaudRate = 38400;break;}case 8:{BaudRate = 57600;break;}case 9:{BaudRate = 115200;break;}default:{BaudRate = 9600;break;}}RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO,ENABLE);RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);/** USART2_TX -> PA2 , USART2_RX -> PA3*/ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure);USART_InitStructure.USART_BaudRate = BaudRate;///USART_InitStructure.USART_WordLength = USART_WordLength_9b;//9位数据USART_InitStructure.USART_WordLength = USART_WordLength_8b;//if(checkbit == 0)//USART_InitStructure.USART_StopBits = USART_StopBits_2;//1位停止位//elseUSART_InitStructure.USART_StopBits = USART_StopBits_1;//1位停止位if((checkbit == 0) || (checkbit == 3))USART_InitStructure.USART_Parity = USART_Parity_No;//else if(checkbit == 1)USART_InitStructure.USART_Parity = USART_Parity_Even;//偶校验else if(checkbit == 2)USART_InitStructure.USART_Parity = USART_Parity_Odd;//奇校验elseUSART_InitStructure.USART_Parity = USART_Parity_No;// USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //硬件流控制失能USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //发送和接受使能USART_Init(USART2, &USART_InitStructure); NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = USART2_IRQCHANNELPP;// 设置抢占优先级NVIC_InitStructure.NVIC_IRQChannelSubPriority = USART2_IRQCHANNELSP; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);// USART_ITConfig(USART2, USART_IT_RXNE, ENABLE); // 使能USART接收中断,这里先不开启接收中断USART_Cmd(USART2, ENABLE); USART_ClearITPendingBit(USART2, USART_IT_TC);//清除中断TC位while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET);//等待传输完成,否则第一位数据容易丢失}
串口2的中断处理函数如下
这里很简单,就是把串口2的数据收集起来放到队列comrx2xQueue中
void USART2_IRQHandler(void)
{portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE;uint8_t cChar;uint16_t msg;if (USART_GetFlagStatus(USART2, USART_FLAG_ORE) != RESET) // ORE中断{USART_ReceiveData(USART2);}if (USART_GetITStatus(USART2, USART_IT_RXNE) != RESET) // 接收数据中断 {cChar = USART_ReceiveData( USART2 );msg = MSG_USART_EVT | (cChar);xQueueSendFromISR( comrx2xQueue, &msg, &xHigherPriorityTaskWoken );}portEND_SWITCHING_ISR( xHigherPriorityTaskWoken );
}
在串口2的接收任务中进行
协议帧格式匹对
如下代码,使用状态机跳转到接收处理位置
void tskcomrx2( void *pvParameters )
{uint16_t Msg;QueueHandle_t pq = pvParameters;uint8_t stt = FSM_IDLE,*prx;uint16_t tmp16,len; while(1) {if( xQueueReceive( pq, &Msg, 20 ) == pdPASS ){if(MSG_NAME(Msg) == MSG_USART_EVT){tmp16 = MSG_DATA(Msg);//调试语句,打印接受数据到调试串口1
// while((USART1->SR&0x40)==0);//等待上一次发送完毕
// USART1->DR = tmp16;
// switch (stt) {case FSM_IDLE :{prx = StdDatBufIn2;*prx = 0;len = 0;if (tmp16 == FlashParagma.addr) {/*数据开始*/len++;*prx++ = tmp16;*prx = 0;stt = FSM_HEAD;}break;}case FSM_HEAD :{len++;*prx++ = tmp16;*prx = 0;if ((tmp16 == 0x03) || (tmp16 == 0x06)){stt = FSM_ASCII_DATA;}else{stt = FSM_IDLE;//异常处理}break;}case FSM_ASCII_DATA :{ len++;*prx++ = tmp16;*prx = 0; if(len > 7){//处理接收数据modbusEventPro(StdDatBufIn2, len);stt = FSM_IDLE;//异常处理}break;}default:{stt = FSM_IDLE;break;}} }/**end of if(MSG_NAME(pMsg)*/ } }/*end of while(1)*/
}/*end of void tskDatRxCOM1(void * pdata) */
根据modbus协议指令分类进行数据处理,代码如下
功能码03、06进行处理
// Modbus事件处理函数
void modbusEventPro(u8 *src, u16 len)
{u16 crc,rccrc;//收到数据包长度判定//通过读到的数据帧计算CRCcrc = Modbus_CRC16(&src[0], len - 2); // 读取数据帧CRCrccrc = src[len - 2] + src[len - 1] * 256;if(crc == rccrc) //CRC校验成功,开始分包{ if(src[0] == FlashParagma.addr) //检测是否是自己的地址{switch(src[1]) //分析modbus功能码{case 3: {Modbus_Func3(src, len);break;}case 6:{Modbus_Func6(src, len);break;}default:break; }}else if(src[0] == 0) //广播地址不予回应{} }
}
发送modbus协议指令,这里需要先把发送模式打开,发送数据完成后,注意要延时一段时间再切换为接收模式,这个延时时间需要自己根据调试情况进行实际调整
控制不同类型的从机,延时时间要求可能不太一样
void Modbus_USRAT2_SendStr(u8 *scr, u16 len)
{u16 i;// 开始返回Modbus数据Modbus_USART2_TX_Mode;vTaskDelay(5);for(i = 0; i < len; i++){while((USART2->SR&0x40)==0);//等待上一次发送完毕 USART2->DR = scr[i]; }vTaskDelay(5);Modbus_USART2_RX_Mode;
}
总结
这实现的比较简单,且常用的Modbus协议
协议格式如下,采用高字节在前方式
地址 | 功能码 | 从机地址 | 数据 | 校验 |
485从机地址 | 03H(读)、06H(写) | CRC | ||
1byte | 1byte | 2byte | 4byte | 2byte |
上述Modbus协议,实现03、06指令,即可完成对从机地址的读写。
上述代码实现,也是根据表格中的格式进行实现的,可以和代码对的上。
其他
网上搜集了一下关于RS485和Modbus协议的解释,这里拿出来比较关键的,供参考
关于RS485(主要是关注传输距离、接口线、电平)
RS-485是美国电子工业协会(EIA)在1983年批准了一个新的平衡传输标准(balanced transmission standard),EIA一开始将RS(Recommended Standard)做为标准的前缀,不过后来为了便于识别标准的来源,已将RS改为EIA/TIA。目前标准名称为TIA-485,但工程师及应用指南仍继续使用RS-485来称呼此标准。
RS-485仅是一个电气标准,描述了接口的物理层,像协议、时序、串行或并行数据以及链路全部由设计者或更高层协议定义。RS-485定义的是使用平衡(也称作差分)多点传输线的驱动器(driver)和接收器(receiver)的电气特性。
- 差分传输增加噪声抗扰度,减少噪声辐射
- 长距离链路,最长可达4000英尺(约1219米)
- 数据速率高达10Mbps(40英寸内,约12.2米)
- 同一总线可以连接多个驱动器和接收器
- 宽共模范围允许驱动器和接收器之间存在地电位差异,允许最大共模电压-7-12V
关于Modbus协议
MODBUS 是 OSI 模型第 7 层上的应用层报文传输协议,它在连接至不同类型总线或网络的设备之间提供客户机/服务器通信。
Modbus协议包括ASCII、RTU、TCP等,并没有规定物理层。此协议定义了控制器能够认识和使用的消息结构,而不管它们是经过何种网络进行通信的。标准的Modicon控制器使用RS232C实现串行的Modbus。Modbus的ASCII、RTU协议规定了消息、数据的结构、命令和就答的方式,数据通讯采用Maser/Slave方式,Master端发出数据请求消息,Slave端接收到正确消息后就可以发送数据到Master端以响应请求;Master端也可以直接发消息修改Slave端的数据,实现双向读写。
Modbus协议需要对数据进行校验,串行协议中除有奇偶校验外,ASCII模式采用LRC校验,RTU模式采用16位CRC校验,但TCP模式没有额外规定校验,因为TCP协议是一个面向连接的可靠协议。另外,Modbus采用主从方式定时收发数据,在实际使用中如果某Slave站点断开后(如故障或关机),Master端可以诊断出来,而当故障修复后,网络又可自动接通。因此,Modbus协议的可靠性较好。
对于Modbus的ASCII、RTU和TCP协议来说,其中TCP和RTU协议非常类似,我们只要把RTU协议的两个字节的校验码去掉,然后在RTU协议的开始加上5个0和一个6并通过TCP/IP网络协议发送出去即可。
1通讯传送方式:
通讯传送分为独立的信息头,和发送的编码数据。以下的通讯传送方式定义也与ModBusRTU通讯规约相兼容:
初始结构 = ≥4字节的时间
地址码 = 1 字节
功能码 = 1 字节
数据区 = N 字节
错误校检 = 16位CRC码
结束结构 = ≥4字节的时间
地址码:地址码为通讯传送的第一个字节。这个字节表明由用户设定地址码的从机将接收由主机发送来的信息。并且每个从机都有具有唯一的地址码,并且响应回送均以各自的地址码开始。主机发送的地址码表明将发送到的从机地址,而从机发送的地址码表明回送的从机地址。
功能码:通讯传送的第二个字节。ModBus通讯规约定义功能号为1到127。本仪表只利用其中的一部分功能码。作为主机请求发送,通过功能码告诉从机执行什么动作。作为从机响应,从机发送的功能码与从主机发送来的功能码一样,并表明从机已响应主机进行操作。如果从机发送的功能码的最高位为1(比如功能码大与此同时127),则表明从机没有响应操作或发送出错。
数据区:数据区是根据不同的功能码而不同。数据区可以是实际数值、设置点、主机发送给从机或从机发送给主机的地址。
CRC码:二字节的错误检测码。
相关文章:

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

springboot服务端接口公网远程调试 - 实现HTTP服务监听【端口映射】
文章目录 前言1. 本地环境搭建1.1 环境参数1.2 搭建springboot服务项目 2. 内网穿透2.1 安装配置cpolar内网穿透2.1.1 windows系统2.1.2 linux系统 2.2 创建隧道映射本地端口2.3 测试公网地址 3. 固定公网地址3.1 保留一个二级子域名3.2 配置二级子域名3.2 测试使用固定公网地址…...

zabbix监控之javasnmp自定义监控
1、客户端开启 java jmxremote 远程监控功能 上传 tomcat 软件包到 /opt 目录中 cd /opt tar zxvf apache-tomcat-9.0.16.tar.gz mv apache-tomcat-9.0.16 /usr/local/tomcat #配置 java jmxremote 远程监控功能 vim /usr/local/tomcat/bin/catalina.sh ...... #位置在 cygw…...

Inertial Explorer处理pospac数据总结
Inertial Explorer处理pospac数据的过程包括:1)从pospac提取出gps数据和imu数据;2)gps数据转成rinex格式;3)imu数据转成imr格式;4)IE对gps数据进行PPP解算;5)紧耦合融合解…...
tps和qps的区别是什么?怎么理解
区别:QPS指的是“每秒查询率”;而TPS指的是“事务数/秒”。理解:Tps即每秒处理事务数,对于一个页面的一次访问,形成一个Tps;而一次页面请求,可能产生多次对服务器的请求,服务器对这些…...

【Java系列】深入解析枚举类型
序言 即便平凡的日子仿佛毫无波澜,但在某个特定的时刻,执着的努力便会显现出它的价值和意义。 希望这篇文章能让你不仅有一定的收获,而且可以愉快的学习,如果有什么建议,都可以留言和我交流 问题 思考一下这寄个问题&a…...

网络原理(五):IP 协议
目录 认识IP 地址 子网掩码 作用 动态分配IP 地址 NAT 机制 认识MAC地址 MAC地址如何工作 网络设备和相关技术 集线器:转发所有端口 交换机:MAC地址转换表转发 主机&路由器:ARP缓存表ARP寻址 路由器:路由NAPT 数…...

MySQL---空间索引、验证索引、索引特点、索引原理
1. 空间索引 MySQL在5.7之后的版本支持了空间索引,而且支持OpenGIS几何数据模型 空间索引是对空间数据类型的字段建立的索引,MYSQL中的空间数据类型有4种,分别是: 类型 含义 说明 Geometry 空间数据 任何一种空间类型 Poi…...

选择合适的 MQTT 云服务:一文了解 EMQX Cloud Serverless、Dedicated 与 BYOC 版本
引言 EMQX Cloud 是基于 EMQX Enterprise 构建的一款全托管云原生 MQTT 消息服务。为了满足不同客户的需求,EMQX Cloud 提供了三种版本供客户选择:Serverless 版、专有版和 BYOC 版。 本文将简要介绍这三个版本的核心区别,并通过三个用户故…...

uvc驱动ioctl分析下
uvc驱动ioctl分析下 文章目录 uvc驱动ioctl分析下uvc_ioctl_enum_input枚举输入uvc_query_ctrl__uvc_query_ctrluvc_ioctl_g_input 获取输入uvc_ioctl_s_input 设置输入uvc_query_v4l2_ctrluvc_ioctl_queryctrl查询控制器uvc_ioctl_query_ext_ctrl查询扩展控制器 uvc_ioctl_g_c…...

数据库可视化神器,你在用哪一款呢
唠嗑部分 在我们日常开发中,作为开发者,与数据库是肯定要打交道的,比如MySQL,Oracle、sqlserver… 那么数据库可视化工具,你用什么呢?小白今天将常用地几款工具列一下,各位小伙伴如有喜欢的自…...
CMD与DOS脚本编程【第三章】
预计更新 第一章. 简介和基础命令 1.1 介绍cmd/dos脚本语言的概念和基本语法 1.2 讲解常用的基础命令和参数,如echo、dir、cd等 第二章. 变量和运算符 2.1 讲解变量和常量的定义和使用方法 2.2 介绍不同类型的运算符和运算规则 第三章. 控制流程和条件语句 3.1 介…...

多激光雷达手眼标定
手眼标定方法已经有很多博客进行解析,但是都是针对机器人的手(夹爪)眼睛(相机)进行标定。例如: 标定学习笔记(四)-- 手眼标定详解 手眼标定_全面细致的推导过程 本文主要描述多激光…...

SQL执行过程
1. select 语句执行过程 一条 select 语句的执行过程如上图所示 1、建立连接 连接器会校验你输入的用户名和密码是否正确,如果错误会返回提示,如果正确,连接器会查询当前用户对于的权限。连接器的作用就是校验用户权限 2、查询缓存 MySQL…...

K8S 部署 seata
文章目录 创建 Deployment 文件创建 ConfigMap 文件创建 Service 文件运行访问高可用部署踩坑 官方文档 k8s中volumeMounts.subPath的巧妙用法 创建 Deployment 文件 deploymemt.yaml namespace:指定命名空间image:使用 1.5.2 版本的镜像ports…...

ClickHouse:(二)数据类型
1.整型 固定长度的整型分为:有符号和无符合整型 有符号整型无符号整型类型范围类型范围Int8 -128 : 127 UInt8 0 : 255 Int16 -32768 : 32767 UInt16 0 : 65535 Int32 -2147483648 : 2147483647 UInt32 0 : 4294967295 Int64 -9223372036854775808 : 9223372036854…...

项目文档(request页面代码逻辑)
项目文档 目录 项目文档 1. 封装请求基地址 代码 2. 添加请求拦截器并设置请求头 作用 代码部分 3. 添加响应拦截器 作用 代码 4. token过期问题处理 5. 无感刷新 作用 代码 6. refresh_token过期处理 解决方式 1. 封装请求基地址 在src目录下 放上一个专门写…...

后端传到前端的JSON数据大写变小写--2023
问题复现:1. 首先我先说一下,我用了lombok,事实证明和这个也有关系 前端这里写的也是按照驼峰命名来写的 控制台打印出来的数据 后台打印出来的数据 解决方法: 1. 重写get/set方法 因为我在实体类上标注了Data注解 重写get/se…...

学习【菜鸟教程】【C++ 类 对象】【C++ 类的静态成员】
链接 1. 教程 可以使用 static 关键字来把类成员定义为静态的。当我们声明类的成员为静态时,这意味着无论创建多少个类的对象,静态成员都只有一个副本。 静态成员在类的所有对象中是共享的。如果不存在其他的初始化语句,在创建第一个对象时…...
计算机四大件笔记
啊~数据库、操作系统、计算机网络、Linux start 操作系统 并发和并行 并发是同一时间段内发生了多个事情,多任务之间互相抢占资源。 并行是在同一时间点内发生了多个事情,多任务之间不互相抢占资源,只有多CPU的情况下才能并行。 例如&a…...

智慧医疗能源事业线深度画像分析(上)
引言 医疗行业作为现代社会的关键基础设施,其能源消耗与环境影响正日益受到关注。随着全球"双碳"目标的推进和可持续发展理念的深入,智慧医疗能源事业线应运而生,致力于通过创新技术与管理方案,重构医疗领域的能源使用模式。这一事业线融合了能源管理、可持续发…...
React Native 开发环境搭建(全平台详解)
React Native 开发环境搭建(全平台详解) 在开始使用 React Native 开发移动应用之前,正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南,涵盖 macOS 和 Windows 平台的配置步骤,如何在 Android 和 iOS…...
在鸿蒙HarmonyOS 5中实现抖音风格的点赞功能
下面我将详细介绍如何使用HarmonyOS SDK在HarmonyOS 5中实现类似抖音的点赞功能,包括动画效果、数据同步和交互优化。 1. 基础点赞功能实现 1.1 创建数据模型 // VideoModel.ets export class VideoModel {id: string "";title: string ""…...

SCAU期末笔记 - 数据分析与数据挖掘题库解析
这门怎么题库答案不全啊日 来简单学一下子来 一、选择题(可多选) 将原始数据进行集成、变换、维度规约、数值规约是在以下哪个步骤的任务?(C) A. 频繁模式挖掘 B.分类和预测 C.数据预处理 D.数据流挖掘 A. 频繁模式挖掘:专注于发现数据中…...

UE5 学习系列(三)创建和移动物体
这篇博客是该系列的第三篇,是在之前两篇博客的基础上展开,主要介绍如何在操作界面中创建和拖动物体,这篇博客跟随的视频链接如下: B 站视频:s03-创建和移动物体 如果你不打算开之前的博客并且对UE5 比较熟的话按照以…...

全球首个30米分辨率湿地数据集(2000—2022)
数据简介 今天我们分享的数据是全球30米分辨率湿地数据集,包含8种湿地亚类,该数据以0.5X0.5的瓦片存储,我们整理了所有属于中国的瓦片名称与其对应省份,方便大家研究使用。 该数据集作为全球首个30米分辨率、覆盖2000–2022年时间…...
使用van-uploader 的UI组件,结合vue2如何实现图片上传组件的封装
以下是基于 vant-ui(适配 Vue2 版本 )实现截图中照片上传预览、删除功能,并封装成可复用组件的完整代码,包含样式和逻辑实现,可直接在 Vue2 项目中使用: 1. 封装的图片上传组件 ImageUploader.vue <te…...
数据库分批入库
今天在工作中,遇到一个问题,就是分批查询的时候,由于批次过大导致出现了一些问题,一下是问题描述和解决方案: 示例: // 假设已有数据列表 dataList 和 PreparedStatement pstmt int batchSize 1000; // …...

第 86 场周赛:矩阵中的幻方、钥匙和房间、将数组拆分成斐波那契序列、猜猜这个单词
Q1、[中等] 矩阵中的幻方 1、题目描述 3 x 3 的幻方是一个填充有 从 1 到 9 的不同数字的 3 x 3 矩阵,其中每行,每列以及两条对角线上的各数之和都相等。 给定一个由整数组成的row x col 的 grid,其中有多少个 3 3 的 “幻方” 子矩阵&am…...

学习STC51单片机32(芯片为STC89C52RCRC)OLED显示屏2
每日一言 今天的每一份坚持,都是在为未来积攒底气。 案例:OLED显示一个A 这边观察到一个点,怎么雪花了就是都是乱七八糟的占满了屏幕。。 解释 : 如果代码里信号切换太快(比如 SDA 刚变,SCL 立刻变&#…...