STM32CubeMX | Modbus RTU 主机协议栈实现(国产单片机、FreeModbus无缝使用)
STM32CubeMX | Modbus RTU 主机协议栈实现
目录
- 1、前言
- 2、协议栈API介绍
- 2.1 控制结构
- 2.2 主机读线圈状态(CMD1)
- 2.2 主机读离散量输入(CMD2)
- 2.2 主机读保持寄存器(CMD3)
- 2.2 主机读输入寄存器(CMD4)
- 2.2 主机写单个线圈(CMD5)
- 2.2 主机写单个寄存器(CMD6)
- 2.2 主机写多个线圈(CMD15)
- 2.2 主机写多个寄存器(CMD16)
- 3、移植前的基础工程生成
- 4、移植主机协议栈
- 5、移植测试验证
本章博客涉及代码,关注以下公众号,回复关键字mbrtu_master
获取下载链接!
1、前言
~~~~~~~~ modbus rtu在嵌入式方面非常的常见和使用,嵌入式linux中可以使用libmodbus这个库,但是对于嵌入式单片机,开源的有FreeModbus这个库,但是只是从机,对于modbus rtu主机的实现,网上却找不到开源的库,或者找到了但是不方便移植,使用者想要去使用还要去搞明白是怎么实现的,本博客基于以上原因,实现了一套modbus rtu主机协议栈。
本主机协议栈优点如下:
- 接口明确清晰,使用者无需关心协议栈内部实现
- 面向对象编程思想,使用C语言的struct作为一个modbus rtu主机的控制接口,此方法的好处是可以灵活的实现多个主机,例如:实现一个多主机的modbus pdu。
- 支持RTOS
- 可搭配FreeModbus协议栈无缝使用
- 移植简单、可很方便的移植到其他单片机如GD32、MM32等
- 源码简单、只有一个头文件、一个源文件、一个移植接口示例文件
2、协议栈API介绍
2.1 控制结构
typedef struct
{//// 收发数据缓存//uint8_t ucBuf[128];//// 收发数据状态//uint16_t usStatus;//// 如果使用了RTOS需要进行互斥,那么需要实现以下两个函数的绑定//void (*lock)(void);void (*unlock)(void);//// 微秒延时函数,用于等待超时//void (*delayms)(uint32_t nms);//// 定时器启动和停止函数//void (*timerStop)(void);void (*timerStart)(void);//// 发送数据函数,可以是串口、TCP等//uint32_t (*sendData)(const void* buf, uint32_t len);}MBRTUMaterTypeDef;
2.2 主机读线圈状态(CMD1)
/*** 主机读取线圈状态* @param ucSlaveAddress 从机地址* @param usAddress 要读取的线圈起始地址* @param usNum 要读取的线圈数量* @param usTimeout 超时时间,单位毫秒* @param pucCoilsBuffer 存储读取到的线圈状态,一个字节代表一个线圈状态,值范围:0/1* @return 0:成功 <0:执行失败*/
int MBRTUMasterReadCoils(MBRTUMaterTypeDef* psModbus, uint8_t ucSlaveAddress, uint16_t usAddress, uint16_t usNum, uint16_t usTimeout, uint8_t* pucCoilsBuffer)
2.2 主机读离散量输入(CMD2)
/*** 主机读取离散量输入* @param ucSlaveAddress 从机地址* @param usAddress 要读取的离散量起始地址* @param usNum 要读取的离散量数量* @param usTimeout 超时时间,单位毫秒* @param pucDiscBuffer 存储读取到的离散量输入状态,一个字节代表一个离散量的状态,值范围:0/1* @return 0:成功 <0:执行失败*/
int MBRTUMasterReadDiscreteInputs(MBRTUMaterTypeDef* psModbus, uint8_t ucSlaveAddress, uint16_t usAddress, uint16_t usNum, uint16_t usTimeout, uint8_t* pucDiscBuffer)
2.2 主机读保持寄存器(CMD3)
/*** 主机读取保持寄存器* @param ucSlaveAddress 从机地址* @param usAddress 要读取的保持寄存器起始地址* @param usNum 要读取的保持寄存器数量* @param usTimeout 超时时间,单位毫秒* @param pusRegBuffer 存储读取到的寄存器值* @return 0:成功 <0:执行失败*/
int MBRTUMasterReadHoldingRegisters(MBRTUMaterTypeDef* psModbus, uint8_t ucSlaveAddress, uint16_t usAddress, uint16_t usNum, uint16_t usTimeout, uint16_t* pusRegBuffer)
2.2 主机读输入寄存器(CMD4)
/*** 主机读取输入寄存器* @param ucSlaveAddress 从机地址* @param usAddress 要读取的输入寄存器起始地址* @param usNum 要读取的输入寄存器数量* @param usTimeout 超时时间,单位毫秒* @param pusRegBuffer 存储读取到的寄存器值* @return 0:成功 <0:执行失败*/
int MBRTUMasterReadInputRegisters(MBRTUMaterTypeDef* psModbus, uint8_t ucSlaveAddress, uint16_t usAddress, uint16_t usNum, uint16_t usTimeout, uint16_t* pusRegBuffer)
2.2 主机写单个线圈(CMD5)
2.2 主机写单个寄存器(CMD6)
2.2 主机写多个线圈(CMD15)
2.2 主机写多个寄存器(CMD16)
3、移植前的基础工程生成
基础工程这里我使用STM32CubeMX生成,使用的是STM32F103C8单片机,配置步骤如下,首先将时钟配置到72M:
配置串口1用于调试打印,配置串口3用于modbus主机通信:
配置用于检测3.5个字符超时时间的定时器,我配置成了5ms超时。
这里需要跟你实际使用的波特率进行超时时间的计算,以:波特率9600、8bit数据位、1bit停止位,奇校验、无流控为例,那么1s内就可以传输9600bits÷(8+1+1)=960bytes,那么3.5个字节的时间就是1000ms÷960×3.5≈3.65ms,所以,我设置5ms的超时时间是没有问题的。
开启定时器和串口中断,注意:串口的中断要比定时器中断等级高:
最后输出工程就可以了:
4、移植主机协议栈
主机协议栈源码就只有三个文件:
其中,mbrtu_master.h
和mbrtu_master.c
是协议栈实现,无需动,mbrtu_master_example.c
是移植参考示例。
下面讲解一下移植过程。
首先定义一个modbus主机的全局控制结构并初始化:
MBRTUMaterTypeDef MBRTUHandle =
{.delayms = delayms,.timerStart = timerStart,.timerStop = timerStop,.sendData = sendData,#ifdef USE_RTOS // 使用了RTOS那么需要实现互斥.lock = mutex_lock,.unlock = mutex_unlock,
#endif
};
注意:如果使用了实时系统,需要实现lock和unlock函数。
结构体中的函数实现如下:
#ifdef USE_RTOSstatic void mutex_lock(void)
{}static void mutex_unlock(void)
{}#endifstatic void timerStop(void)
{HAL_TIM_Base_Stop_IT(&htim3);
}static void timerStart(void)
{__HAL_TIM_SET_COUNTER(&htim3, 0);HAL_TIM_Base_Start_IT(&htim3);
}static void delayms(uint32_t nms)
{#ifdef USE_RTOSosDelay(nms);
#elseHAL_Delay(nms);
#endif
}static uint32_t sendData(const void* buf, uint32_t len)
{if(HAL_UART_Transmit(&huart3, (uint8_t *)buf, len, 100) != HAL_OK){len = 0;}return len;
}
将MBRTUMasterTimerISRCallback
函数放置于定时器中断函数中,对于HAL库那就是这样的:
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{if(htim->Instance == htim3.Instance){MBRTUMasterTimerISRCallback(&MBRTUHandle);}
}
将MBRTUMasterRecvByteISRCallback
函数放置于串口中断函数中,对于HAL库那就是这样的:
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{if(huart->Instance == huart3.Instance){MBRTUMasterRecvByteISRCallback(&MBRTUHandle, g_Uart3RxByte);HAL_UART_Receive_IT(&huart3, &g_Uart3RxByte, 1); // 注册接收}
}
重定向printf到串口1:
int fputc(int ch, FILE* fp)
{HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 100);return ch;
}
至此,就移植完毕了,测试函数如下:
int ret;
uint8_t ucBuf[10];
uint16_t usBuf[10];int main_example(void)
{// 定时器初始化,设置为3.5个字符的超时时间// Timer_Init();// 串口初始化,初始化波特率等// UART_Init();// 写单个线圈ret = MBRTUMasterWriteSingleCoil(&MBRTUHandle, 1, 0, 1, 500);printf(" write single coil %s. \r\n", ret < 0 ? "failed" : "ok");HAL_Delay(100);// 写单个寄存器ret = MBRTUMasterWriteSingleRegister(&MBRTUHandle, 1, 0, 0XAABB, 500);printf(" write single reg %s. \r\n", ret < 0 ? "failed" : "ok");HAL_Delay(100);// 写多个线圈memset(ucBuf, 0X01, 10);ret = MBRTUMasterWriteMultipleCoils(&MBRTUHandle, 1, 0, 10, ucBuf, 500);printf(" write coils %s. \r\n", ret < 0 ? "failed" : "ok");HAL_Delay(100);// 写多个寄存器memset(usBuf, 0XFF, 20);ret = MBRTUMasterWriteMultipleRegisters(&MBRTUHandle, 1, 0, 10, usBuf, 500);printf(" write regs %s. \r\n", ret < 0 ? "failed" : "ok");HAL_Delay(100);// 读线圈MBRTUMasterReadCoils(&MBRTUHandle, 1, 0, 10, 500, ucBuf);printf(" read coils %s. \r\n", ret < 0 ? "failed" : "ok");HAL_Delay(100);// 读离散量输入MBRTUMasterReadDiscreteInputs(&MBRTUHandle, 1, 0, 10, 500, ucBuf);printf(" read discs %s. \r\n", ret < 0 ? "failed" : "ok");HAL_Delay(100);// 读保持寄存器MBRTUMasterReadHoldingRegisters(&MBRTUHandle, 1, 0, 10, 500, usBuf);printf(" read hold regs %s. \r\n", ret < 0 ? "failed" : "ok");HAL_Delay(100);// 读输入寄存器MBRTUMasterReadInputRegisters(&MBRTUHandle, 1, 0, 10, 500, usBuf);printf(" read input regs %s. \r\n", ret < 0 ? "failed" : "ok");HAL_Delay(100);return 0;
}
5、移植测试验证
移植完毕了现在需要测试,测试你可以使用MobusSlave软件模拟测试,也可以选用选用FreeModbus作为从机,关于FreeModbus从机移植使用可以参考我的另一篇博客:STM32CubeMX | STM32 HAL库移植FreeModbus详细步骤。
STM32CubeMX | Modbus RTU 主机协议栈实现(国产单片机、FreeModbus无缝使用)相关推荐
- modbus RTU协议设备使用无线代替有线注意事项
1.设备有线连接 Modbus是由Modicon(现为施耐德电气公司的一个品牌)在1979年发明的,是全球第一个真正用于工业现场的总线协议.ModBus网络是一个工业通信系统,由带智能终端的可编程序控 ...
- Modbus协议栈应用实例之二:Modbus RTU从站应用
自从开源了我们自己开发的Modbus协议栈之后,有很多朋友建议我针对性的做几个示例.所以我们就基于平时我们的应用整理了几个简单但可以说明基本的应用方法的示例,这一篇中我们将使用协议栈实现一个Modbu ...
- Modbus协议栈应用实例之一:Modbus RTU主站应用
自从开源了我们自己开发的Modbus协议栈之后,有很多朋友建议我针对性的做几个示例.所以我们就基于平时我们的应用整理了几个简单但可以说明基本的应用方法的示例,在这一篇中我们先来使用协议栈实现Modbu ...
- Modbus协议栈实现Modbus RTU多主站支持
前面我们已经详细讲解过Modbus协议栈的开发过程,并且利用协议栈封装了Modbus RTU主站和从站,Modbus TCP服务器与客户端,Modbus ASCII主站与从站应用.但在使用过程中,我们 ...
- Modbus协议栈开发笔记之五:Modbus RTU Slave开发
Modbus在串行链路上分为Slave和Master,这一节我们就来开发Slave.对于Modbus RTU从站来说,需要实现的功能其实与Modbus TCP的服务器端是一样的.其操作过程也是一样的. ...
- 单片机modbus rtu通讯_【原创】永宏PLC系列RS485通讯方式
实现的功能,及应用的场合 本项目为弯管机设备改造工程,在不破坏设备原有的功能的情况下通过只更换设备原来的永宏PLC,使弯管机能够与埃斯顿机器人进行信号交互,通过机器人对弯管机进行上料.下料动作,即节约 ...
- Modbus RTU 51单片机从机工程源码与昆仑通泰触摸屏测试工程文件。支持485和232串口通信
Modbus RTU 51单片机从机工程源码与昆仑通泰触摸屏测试工程文件.支持485和232串口通信,该从机源码支持51系列和STC12系列单片机,支持功能码01,02,03,04,05,06,15, ...
- Modbus RTU 51单片机从机工程源码与昆仑通泰触摸屏测试工程文件
Modbus RTU 51单片机从机工程源码与昆仑通泰触摸屏测试工程文件. 支持485和232串口通信,该从机源码支持51系列和STC12系列单片机,支持功能码01,02,03,04,05,06,15 ...
- Modbus RTU 51单片机从机源码与组态软件通信支持485和232串口通信,该从机源码可直接用于51系列和STC12系列单片机的
Modbus RTU 51单片机从机源码与组态软件通信支持485和232串口通信,该从机源码可直接用于51系列和STC12系列单片机的,支持功能码01,02,03,04,05,06,0F,10等常用功 ...
最新文章
- [Ljava.lang.Object; cannot be cast to com.gxuwz.check.entity.SysClasses
- 《恐怖小说在中国》之四:恐怖小说的流行与陷阱?
- 结合hello world探讨gcc编译程序的过程
- CityEngine 2012与ArcGIS 10.2破解心得
- 计算机应用与科学专业简介,计算机应用技术学科专业简介
- 【数据结构基础笔记】【队列】
- Idea 插件 lombok 的安装和使用
- Fresco 二三事:图片处理之旋转、缩放、裁剪切割图片
- 安卓一步一步搭建组件化
- git lfs mac 安装_mac安装homebrew
- 手工收集awr报告_一个Oracle小白的AWR报告分析(一)
- 系统学习深度学习(一) --深度学习与神经网络关系
- BI_DBA_安装(3):安装informatic
- Java实现 LeetCode 592 分数加减运算(纯体力活)
- OSChina 周四乱弹 —— 八字欠备,五行缺胎
- 如何开通电子邮箱的SMTP功能
- 浏览器被hao123劫持首页处理
- 移动应用的黑盒测试(1)
- Magic3D(MyGUI)简单使用
- A-MPDU与A-MSDU各是什么意思