STM32开发(六)STM32F103 通信 —— RS485 Modbus通信编程详解
文章目录
- 一、基础知识点
- 二、开发环境
- 三、STM32CubeMX相关配置
- 1、STM32CubeMX基本配置
- 2、STM32CubeMX RS485 相关配置
- 四、Vscode代码讲解
- 五、结果演示以及报文解析
一、基础知识点
了解 RS485 Modbus协议技术 。本实验是基于STM32F103开发 实现 通过RS-485实现modbus协议。
准备好了吗?开始我的show time。
二、开发环境
1、硬件开发准备
主控:STM32F103ZET6
RS485收发器:SP3485P

2、软件开发准备
软件开发使用虚拟机 + VScode + STM32Cube 开发STM32,在虚拟机中直接完成编译下载。
该部分可参考:软件开发环境构建
三、STM32CubeMX相关配置
1、STM32CubeMX基本配置
本实验基于CubeMX详解构建基本框架 进行开发。
2、STM32CubeMX RS485 相关配置
(1)发送接收控制脚配置(GPIO配置)


gpio输出电平: 低(控制引脚默认低电平,芯片处于读状态)
gpio模式: 推挽输出
gpio上下拉设置: 不上下拉
gpio输出速度: 低速
gpio命名: RS485_DE_nRE (与硬件标识一致,便于代码编写)
(2)串口UART3配置

根据硬件引脚连接,RS485芯片连接UART3通信

基本配置: 实验波特率采用9600、数据位8bit、无奇偶校验、停止位1bit
数据方向: 接收发送

DMA配置: Add添加发送和接收的DMA,DMA参数保持默认状态
(3)中断配置

实验中接收数据采用空闲触发;发送数据采用DMA发送触发后发送完成中断
UART3总中断(USART3 global interrupt)必须打开(为了发送完成中断实现)
UART_RX (DMA1 channel3 global interrupt) DMA接收中断不打开,取消对钩(这里对钩无法改变,后续解决)
UART_TX (DMA1 channel2 global interrupt) DMA发送中断打开。

进行NVIC中断等级配置(0等级最高)
上述讲到无法取消DMA接收中断,原因是选中了强制DMA中断(右上角蓝色框,取消对钩就ok)
四、Vscode代码讲解
1、初始化相关中断
#ifdef STM32_F407_RS485_Modbusprintf("----DWB 此程序通过RS-485实现modbus协议----\r\n");__HAL_UART_ENABLE_IT(&huart3, UART_IT_IDLE); // 使能串口3空闲中断HAL_UART_Receive_DMA(&huart3, UART3.pucRec_Buffer, UART3_Rec_LENGTH);
#endif
2、RS485 结构体 以及函数实现
typedef struct
{uint8_t* pucSend_Buffer; //发送缓存指针 uint8_t* pucRec_Buffer; //接收缓存指针 void (*SendArray)(uint8_t*, uint16_t); //串口发送数组void (*SendString)(uint8_t*); //串口发送字符串void (*RS485_Set_SendMode)(void); //RS-485接口设置为发送模式void (*RS485_Set_RecMode)(void); //RS-485接口设置为接收模式/* data */
} UART_t;// 串口发数组
static void SendArray(uint8_t* p_Arr,uint16_t LEN)
{UART3.RS485_Set_SendMode(); HAL_UART_Transmit_DMA(&huart3,p_Arr,LEN);
}// RS485接口设置发送模式
static void RS485_Set_SendMode()
{HAL_GPIO_WritePin(RS485_DE_nRE_GPIO_Port, RS485_DE_nRE_Pin,,GPIO_PIN_SET);
}// RS485接口设置接收模式
static void RS485_Set_RecMode()
{HAL_GPIO_WritePin(RS485_DE_nRE_GPIO_Port, RS485_DE_nRE_Pin,,GPIO_PIN_RESET);
}
3、RS485 Modbus发送
重构接收回调函数(整个DMA发送过程后面有讲解)
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{/* Prevent unused argument(s) compilation warning */if(huart->Instance == huart3.Instance){UART3.RS485_Set_RecMode();}
}
4、RS485 Modbus接收
接收使用空闲中断 ,在串口总中断中添加空闲中断检测。
void USART3_IRQHandler(void)
{/* USER CODE BEGIN USART3_IRQn 0 */if(__HAL_UART_GET_FLAG(&huart3, UART_FLAG_IDLE)) // 判断空闲中断标志位{__HAL_UART_CLEAR_IDLEFLAG(&huart3); // 1、清除中断标志位HAL_UART_IdleCallback(&huart3); // 2、空闲中断回调函数}/* USER CODE END USART3_IRQn 0 */HAL_UART_IRQHandler(&huart3);/* USER CODE BEGIN USART3_IRQn 1 *//* USER CODE END USART3_IRQn 1 */
}
在 Drivers/STM32F1xx_HAL_Driver/Inc/stm32f1xx_hal_uart.h 文件中回调函数并没有串口空闲中断回调函数

重构空闲中断回调函数
void HAL_UART_IdleCallback(UART_HandleTypeDef *huart)
{if(huart->Instance == huart3.Instance){Modbus.Protocol_Analysis(&UART3); // 接收数据解析HAL_UART_Receive_DMA(&huart3, UART3.pucRec_Buffer, UART3_Rec_LENGTH); // 重新开启接收DMA(在数据解析中会暂时关闭接收DMA)}
}
5、Modbus 收发数据详解
(1)Modbus结构体
typedef struct
{uint16_t addr;void (*Protocol_Analysis)(UART_t*); } Modbus_t;
(2)Modbus接收数据整体框架
#define UART_Order_Index 8
#define FunctionCode_Read_Register 0x03
#define FunctionCode_Write_Register 0x06
#define UART3_Send_LENGTH 20
#define UART3_Rec_LENGTH 20static void Protocol_Analysis(UART_t* UART)
{UART_t* const COM_UART = UART;uint8_t i = 0, Index = 0;// 1、关闭接收HAL_UART_AbortReceive(&huart3);// 2、整理接收数据for(i=0; i<UART3_Rec_LENGTH; i++){if(Index == 0){if(*(COM_UART->pucRec_Buffer+i) != Modbus.addr)continue;}*(COM_UART->pucRec_Buffer + Index) = *(COM_UART->pucRec_Buffer + i);// 取7字节if(Index == UART_Order_Index) break; Index++;}// 4、校验码CRC_16.CRC_Value = CRC_16.CRC_Check(COM_UART->pucRec_Buffer, 6);CRC_16.CRC_H = (u_int8_t)(CRC_16.CRC_Value >> 8);CRC_16.CRC_L = (u_int8_t)CRC_16.CRC_Value;if(((*(COM_UART->pucRec_Buffer+6) == CRC_16.CRC_L) && (*(COM_UART->pucRec_Buffer+7) == CRC_16.CRC_H))||((*(COM_UART->pucRec_Buffer+6) == CRC_16.CRC_H) && (*(COM_UART->pucRec_Buffer+7) == CRC_16.CRC_L))){//校验地址if((*(COM_UART->pucRec_Buffer+0)) == Modbus.addr){// 5、数据处理if((*(COM_UART->pucRec_Buffer+1)) == FunctionCode_Read_Register){Modbus_Read_Register(COM_UART);}else if((*(COM_UART->pucRec_Buffer+1)) == FunctionCode_Write_Register){Modbus_Wrtie_Register(COM_UART);} }}//清缓存for(i=0;i<UART3_Rec_LENGTH;i++){*(COM_UART->pucRec_Buffer+i) = 0x00;}
}
Modbus_Read_Register函数数据解析(协议数据:地址码+功能码+数据长度(字节)+发送数据+CRC)连续读取从设备寄存器值返回给主设备。
static void Modbus_Read_Register(UART_t* UART)
{UART_t* const COM_UART = UART;//校验地址if((*(COM_UART->pucRec_Buffer+2) == 0x9C) && (*(COM_UART->pucRec_Buffer+3) == 0x41)){回应数据//地址码*(COM_UART->pucSend_Buffer+0) = Modbus.addr;//功能码*(COM_UART->pucSend_Buffer+1) = FunctionCode_Read_Register;//数据长度(字节)*(COM_UART->pucSend_Buffer+2) = 2;//发送数据// deep status*(COM_UART->pucSend_Buffer+3) = 0;*(COM_UART->pucSend_Buffer+4) = Deep.Read_Deep();*(COM_UART->pucSend_Buffer+5) = 0;*(COM_UART->pucSend_Buffer+6) = 0x66;//插入CRCCRC_16.CRC_Value = CRC_16.CRC_Check(COM_UART->pucSend_Buffer,7); //计算CRC值CRC_16.CRC_H = (uint8_t)(CRC_16.CRC_Value >> 8);CRC_16.CRC_L = (uint8_t)CRC_16.CRC_Value;*(COM_UART->pucSend_Buffer+7) = CRC_16.CRC_L;*(COM_UART->pucSend_Buffer+8) = CRC_16.CRC_H;//发送数据UART3.SendArray(COM_UART->pucSend_Buffer,9);}
}
Modbus_Wrtie_Register函数数据解析。从主设备获取控制从设备外设的数值,解析后控制外设。
static void Modbus_Wrtie_Register(UART_t* UART)
{UART_t* const COM_UART = UART;uint8_t i=0;//回应数据for(i=0;i<8;i++){*(COM_UART->pucSend_Buffer+i) = *(COM_UART->pucRec_Buffer+i);}//发送数据UART3.SendArray(COM_UART->pucSend_Buffer,8);//解析数据,控制外设if((*(COM_UART->pucRec_Buffer+2) == 0x9C) && (*(COM_UART->pucRec_Buffer+3) == 0x42)){if(*(COM_UART->pucRec_Buffer+5) == Deep_Status_ON )Deep.Deep_Enable();elseDeep.Deep_Disable();}
}
为什么要使能DMA发送完成中断才会触发UART的发送完成中断?
答案就在代码里,带大家解析一遍相关代码:
// 调用HAL_UART_Transmit_DMA函数实现DMA发送
HAL_UART_Transmit_DMA-> huart->hdmatx->XferCpltCallback = UART_DMATransmitCplt; // 设置发送完成回调函数static void UART_DMATransmitCplt(DMA_HandleTypeDef *hdma)
{UART_HandleTypeDef *huart = (UART_HandleTypeDef *)((DMA_HandleTypeDef *)hdma)->Parent;/* DMA Normal mode*/if ((hdma->Instance->CCR & DMA_CCR_CIRC) == 0U){huart->TxXferCount = 0x00U;/* Disable the DMA transfer for transmit request by setting the DMAT bitin the UART CR3 register */CLEAR_BIT(huart->Instance->CR3, USART_CR3_DMAT);/* Enable the UART Transmit Complete Interrupt */SET_BIT(huart->Instance->CR1, USART_CR1_TCIE); // 当DMA发送完成后,会使能串口发送完成中断}/* DMA Circular mode */else{
#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)/*Call registered Tx complete callback*/huart->TxCpltCallback(huart);
#else/*Call legacy weak Tx complete callback*/HAL_UART_TxCpltCallback(huart);
#endif /* USE_HAL_UART_REGISTER_CALLBACKS */}
}
当DMA发送完成后,会使能串口发送完成中断。配置打开UART3中断总开关。
HAL_UART_IRQHandler-> if (((isrflags & USART_SR_TC) != RESET) && ((cr1its & USART_CR1_TCIE) != RESET))-> UART_EndTransmit_IT(huart);-> HAL_UART_TxCpltCallback(huart); // 回调函数为弱函数,可重构
五、结果演示以及报文解析
实验测试使用USB转RS485工具。从设备板子上A B接口连接USB转RS485工具上对应A B接口。主设备为PC端安装的MThings进行Modbus收发数据测试。有兴趣的小伙伴可以体验下MTings官网

发送数据报文解析

[2023-03-05 13:13:03-802]COM34-发送:01 06 9c 42 00 01 c6 4e
[2023-03-05 13:13:03-827]COM34-接收:01 06 9c 42 00 01 c6 4e
0x01:主机要查询的从设备地址
0x06:功能码 修改写操作
0x9c 0x42:寄存器地址0x9c42转十进制地址为40,002
0x00 0x01:写入地址的数值为0x01 (控制从设备蜂鸣器打开)
0xc6 0x4e:CRC校验码
[2023-03-05 13:13:04-980]COM34-发送:01 06 9c 42 00 00 07 8e
[2023-03-05 13:13:05-012]COM34-接收:01 06 9c 42 00 00 07 8e
0x01:主机要查询的从设备地址
0x06:功能码 修改写操作
0x9c 0x42:寄存器地址0x9c42转十进制地址为40,002
0x00 0x01:写入地址的数值为0x00 (控制从设备蜂鸣器关闭)
0xc6 0x4e:CRC校验码
接收数据报文解析

[2023-03-05 13:41:54-954]COM34-发送:01 06 9c 42 00 01 c6 4e
[2023-03-05 13:41:54-977]COM34-接收:01 06 9c 42 00 01 c6 4e
0x01:从设备地址
0x06:功能码 修改写操作
0x9c 0x42:寄存器地址0x9c42转十进制地址为40,002
0x00 0x01:写入地址的数值为0x00 (控制从设备蜂鸣器关闭)
0xc6 0x4e:CRC校验码
[2023-03-05 13:41:56-289]COM34-发送:01 03 9c 41 00 02 ba 4f
0x01:主机要查询的从设备地址
0x03:功能码 查询读操作
0x9c 0x42:寄存器地址0x9c41转十进制地址为40,001
0x00 0x02:读取两个数据(一个数据2字节)
0xba 0x4f:CRC校验码
[2023-03-05 13:41:56-320]COM34-接收:01 03 02 00 01 00 66 d9 a3
0x01:告诉主机自己从设备地址
0x03:功能码 读操作
0x00 0x01:读出第一个数据为0x01,当前蜂鸣器打开状态
0x00 0x66:读取第二个数据为0x66(该值是本猿在代码中写死的值,后续功能会结合本章节modbus功能通信,敬请期待)
0xd9 0xa3:CRC校验码
相关文章:
STM32开发(六)STM32F103 通信 —— RS485 Modbus通信编程详解
文章目录一、基础知识点二、开发环境三、STM32CubeMX相关配置1、STM32CubeMX基本配置2、STM32CubeMX RS485 相关配置四、Vscode代码讲解五、结果演示以及报文解析一、基础知识点 了解 RS485 Modbus协议技术 。本实验是基于STM32F103开发 实现 通过RS-485实现modbus协议。 准备…...
AcWing1049.大盗阿福题解
前言如果想看状态机的详解,点机这里:dp模型——状态机模型C详解1049. 大盗阿福阿福是一名经验丰富的大盗。趁着月黑风高,阿福打算今晚洗劫一条街上的店铺。这条街上一共有 N家店铺,每家店中都有一些现金。阿福事先调查得知,只有当…...
python日志模块,loggin模块
python日志模块,loggin模块loggin模块日志的格式处理器种类日志格式的参数使用loggin模块 logging库采用模块化方法,并提供了几类组件:记录器,处理程序,过滤器和格式化程序。 记录器(Logger)&a…...
接口自动化入门-TestNg
目录1.TestNg介绍2、TestNG安装3、TestNG使用3.1 编写测试用例脚本3.2 创建TestNG.xml文件(1)创建testng.xml文件(2)修改testng.xml4、测试报告生成1.TestNg介绍 TestNg是Java中开源的自动化测试框架,灵感来源于Junit…...
Spring AOP —— 详解、实现原理、简单demo
目录 一、Spring AOP 是什么? 二、学习AOP 有什么作用? 三、AOP 的组成 3.1、切面(Aspect) 3.2、切点(Pointcut) 3.3、通知(Advice) 3.4、连接点 四、实现 Spring AOP 一个简…...
(蓝桥真题)异或数列(博弈)
题目链接:P8743 [蓝桥杯 2021 省 A] 异或数列 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 样例输入: 4 1 1 1 0 2 2 1 7 992438 1006399 781139 985280 4729 872779 563580 样例输出: 1 0 1 1 分析:容易想到对于异或最大值…...
4万字数字政府建设总体规划方案WORD
本资料来源公开网络,仅供个人学习,请勿商用。部分资料内容: 我省“数字政府”架构 (一) 总体架构。 “数字政府”总体架构包括管理架构、业务架构、技术架构。其中,管理架构体现“管运分离”的建设运营模式…...
CCF/CSP 201709-2公共钥匙盒100分
试题编号:201709-2试题名称:公共钥匙盒时间限制:1.0s内存限制:256.0MB问题描述:问题描述 有一个学校的老师共用N个教室,按照规定,所有的钥匙都必须放在公共钥匙盒里,老师不能带钥…...
【OC】Blocks模式
1. Block语法 Block语法完整形式如下: ^void (int event) {printf("buttonId:%d event%d\n", i, event); }完整形式的Block语法与一般的C语言函数定义相比,仅有两点不同。 没有函数名。带有“^”(插入记号)。 因为O…...
软件设计师教程(七)计算机系统知识-操作系统知识
软件设计师教程 软件设计师教程(一)计算机系统知识-计算机系统基础知识 软件设计师教程(二)计算机系统知识-计算机体系结构 软件设计师教程(三)计算机系统知识-计算机体系结构 软件设计师教程(…...
蓝桥杯2023/3/2
1. 小蓝正在学习一门神奇的语言,这门语言中的单词都是由小写英文字母组 成,有些单词很长,远远超过正常英文单词的长度。小蓝学了很长时间也记不住一些单词,他准备不再完全记忆这些单词,而是根据单词中哪个字母出现得最…...
【IoT】创业成功不可或缺的两个因素:能力和趋势
今天就来谈谈能力和趋势究竟哪个更重要的问题。 在谈成功的十大要素时,我曾经讲到: 一命、二运、三风水,这三个要素几乎不涉及任何个人的努力。 而趋势跟这三个要素又是息息相关的,这也类似雷军所说的飞猪理论。 只要风足够大&…...
2020蓝桥杯真题日期格式 C语言/C++
问题描述 小蓝要处理非常多的数据, 其中有一些数据是日期。 在小蓝处理的日期中有两种常用的形式: 英文形式和数字形式。 英文形式采用每个月的英文的前三个宁母作为月份标识, 后面跟两位数字 表示日期, 月份标识第一个字母大写, 后两个字母小写, 日期小于 10 时要补 前导 0s…...
总时差与自由时差
定义总时差(总浮动时间)(TF,Total Free Time,不耽误项目总进度)LS(Latest Start)-ES(Earliest Start)LF(Latest Finish)-EF࿰…...
LeetCode两个数组的交集-跳跃游戏- 最长有效括号
两个数组的交集 给定两个数组 nums1 和 nums2 ,返回 它们的交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。 示例 1: 输入:nums1 [1,2,2,1], nums2 [2,2] 输出:[2] 示例 2: 输入&…...
mysql普通索引与唯一索引怎么选择
学习mysql普通索引与唯一索引选择记录总结,学习链接:http://gk.link/a/11YG8从mysql查询操作分析:普通索引:查到满足条件的第一条记录后,还会继续查找下一条记录,直到出现满足条件的记录出现后停止检索唯一…...
JavaWeb开发(三)3.5——Java的反射机制
一、反射机制的概念 指在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法,对于任意一个对象,都能调用它的任意一个方法。这种动态获取信息,及动态调用对象方法的功能叫java语言的反射机制。 Java反射机制的核心是在程序运行时动…...
Python每日一练(20230305)
目录 1. 正则表达式匹配 ★★★ 2. 寻找旋转排序数组中的最小值 II ★★★ 3. 删除排序链表中的重复元素 II ★★ 1. 正则表达式匹配 给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 . 和 * 的正则表达式匹配。 . 匹配任意单个字符* 匹配零个或多个…...
SpringBoot三种方法实现定时发送邮件的案例
前言 小编我将用CSDN记录软件开发之路上所学的心得与知识,有兴趣的小伙伴可以关注一下!也许一个人独行,可以走的很快,但是一群人结伴而行,才能走的更远!让我们在成长的道路上互相学习,让我们共…...
opengl、opengl es、webgl介绍与opengl开发入门
1、OpenGL OpenGL(英语:Open Graphics Library,译名:开放图形库或者“开放式图形库”)常用于CAD、虚拟现实、科学可视化程序和电子游戏开发。OpenGL的高效实现(利用了图形加速硬件)存在于Windo…...
铭豹扩展坞 USB转网口 突然无法识别解决方法
当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…...
K8S认证|CKS题库+答案| 11. AppArmor
目录 11. AppArmor 免费获取并激活 CKA_v1.31_模拟系统 题目 开始操作: 1)、切换集群 2)、切换节点 3)、切换到 apparmor 的目录 4)、执行 apparmor 策略模块 5)、修改 pod 文件 6)、…...
k8s从入门到放弃之Ingress七层负载
k8s从入门到放弃之Ingress七层负载 在Kubernetes(简称K8s)中,Ingress是一个API对象,它允许你定义如何从集群外部访问集群内部的服务。Ingress可以提供负载均衡、SSL终结和基于名称的虚拟主机等功能。通过Ingress,你可…...
Vue3 + Element Plus + TypeScript中el-transfer穿梭框组件使用详解及示例
使用详解 Element Plus 的 el-transfer 组件是一个强大的穿梭框组件,常用于在两个集合之间进行数据转移,如权限分配、数据选择等场景。下面我将详细介绍其用法并提供一个完整示例。 核心特性与用法 基本属性 v-model:绑定右侧列表的值&…...
3.3.1_1 检错编码(奇偶校验码)
从这节课开始,我们会探讨数据链路层的差错控制功能,差错控制功能的主要目标是要发现并且解决一个帧内部的位错误,我们需要使用特殊的编码技术去发现帧内部的位错误,当我们发现位错误之后,通常来说有两种解决方案。第一…...
定时器任务——若依源码分析
分析util包下面的工具类schedule utils: ScheduleUtils 是若依中用于与 Quartz 框架交互的工具类,封装了定时任务的 创建、更新、暂停、删除等核心逻辑。 createScheduleJob createScheduleJob 用于将任务注册到 Quartz,先构建任务的 JobD…...
【C语言练习】080. 使用C语言实现简单的数据库操作
080. 使用C语言实现简单的数据库操作 080. 使用C语言实现简单的数据库操作使用原生APIODBC接口第三方库ORM框架文件模拟1. 安装SQLite2. 示例代码:使用SQLite创建数据库、表和插入数据3. 编译和运行4. 示例运行输出:5. 注意事项6. 总结080. 使用C语言实现简单的数据库操作 在…...
Swagger和OpenApi的前世今生
Swagger与OpenAPI的关系演进是API标准化进程中的重要篇章,二者共同塑造了现代RESTful API的开发范式。 本期就扒一扒其技术演进的关键节点与核心逻辑: 🔄 一、起源与初创期:Swagger的诞生(2010-2014) 核心…...
【碎碎念】宝可梦 Mesh GO : 基于MESH网络的口袋妖怪 宝可梦GO游戏自组网系统
目录 游戏说明《宝可梦 Mesh GO》 —— 局域宝可梦探索Pokmon GO 类游戏核心理念应用场景Mesh 特性 宝可梦玩法融合设计游戏构想要素1. 地图探索(基于物理空间 广播范围)2. 野生宝可梦生成与广播3. 对战系统4. 道具与通信5. 延伸玩法 安全性设计 技术选…...
AspectJ 在 Android 中的完整使用指南
一、环境配置(Gradle 7.0 适配) 1. 项目级 build.gradle // 注意:沪江插件已停更,推荐官方兼容方案 buildscript {dependencies {classpath org.aspectj:aspectjtools:1.9.9.1 // AspectJ 工具} } 2. 模块级 build.gradle plu…...
