尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

Modbus-RTU 协议移植实现指南【GD32】

发布时间:2026/9/29 8:47:56

资讯中心
01
ARTICLE

Modbus-RTU 协议移植实现指南【GD32】

Modbus-RTU 协议移植实现指南【GD32】
1. 引言Modbus-RTU 是一种基于串行链路的工业通信协议广泛应用于 PLC、传感器、仪表等设备之间的数据交换。本文介绍如何将 Modbus-RTU 协议栈移植到目标平台涵盖协议原理、移植步骤、代码实现和验证方法。2. Modbus-RTU 协议基础Modbus-RTU 采用主从架构主机发起请求从机响应。报文格式如下字段长度说明地址码1 字节从机地址1-247功能码1 字节如 0x03 读保持寄存器、0x06 写单个寄存器数据区N 字节寄存器地址、数量、数据等CRC162 字节低字节在前高字节在后帧间隔要求报文帧之间至少间隔 3.5 个字符时间帧内字符间隔不超过 1.5 个字符时间。3. 移植前的准备工作首先准备一个空白的工程本文演示的是在 GD32F450 Freertos 条件下移植 modbus 协议需要移植 Freertos 操作系统可以看我上两期内容。然后物理层是 RS485 进行测试通讯参考电路和工程文件移植前需要确认mcu含有以下硬件和软件资源串口资源确认目标平台可用的 UART 外设及中断支持。定时器用于实现 3.5 字符时间和 1.5 字符时间的帧间隔检测。源码移植参考下载地址GitHub - cwalter-at/freemodbus: BSD licensed MODBUS RTU/ASCII and TCP slave · GitHubhttps://github.com/cwalter-at/freemodbus简单介绍一下这些文件都是什么名称类型作用简写modbus/目录协议栈核心源码RTU/ASCII/TCP 及通用功能码实现demo/目录官方移植示例适配不同硬件/OS供参考doc/目录说明文档、API 手册、移植指南tools/目录辅助测试工具或配置脚本bsd.txt文件BSD 开源许可证可自由修改商用保留版权Changelog.txt文件版本更新日志修复/新增记录CMakeLists.txt文件CMake 构建脚本gpl.txt文件GPL 许可证可能涉及 demo 或工具中的第三方代码lgpl.txt文件LGPL 许可证同上README.md文件项目入口说明简介、编译、移植入门4. 裁剪核心文件大部分文件我们是不需要的只保留/modbus和/demo就好了4.1 /modbus名称类型作用简写删ascii/目录Modbus ASCII 模式源码三选一functions/目录各种 Modbus 功能码的实现保留include/目录协议栈公共头文件保留rtu/目录Modbus RTU 模式源码三选一tcp/目录Modbus TCP 模式源码三选一mb.c文件协议栈核心状态机与事件调度入口保留本文展示移植协议 modbus-rtu 这边直接把其他无关的文件删除4.2 /demo类别目录名作用简写 / 适用平台通用/裸机BARE裸机移植模板最通用的参考无 OS适合自己新建工程WIN32Windows 平台裸机示例常用于 PC 端模拟测试LINUXLinux 平台裸机示例STM32 系列STM32_CMAKESTM32 CMake 构建目前最常用的 STM32 参考常用单片机ATSAM3SAtmel SAM3S 系列ATSAM3S_FREERTOSAtmel SAM3S FreeRTOSLPC214XNXP LPC214x 系列MSP430TI MSP430 系列MCF5235Freescale MCF5235STR71XST STR71x 系列Z8ENCOREZilog Z8 EncoreHCS08Freescale HCS08AVR/AVR2560Atmel AVR / ATmega2560AT91SAM7X_ROWLEYAtmel AT91SAM7X Rowley 工具链TCP/网络相关LINUXTCPLinux 下的 Modbus TCP 示例WIN32TCPWindows 下的 Modbus TCP 示例MCF5235TCPMCF5235 的 TCP 示例STR71XTCPSTR71x 的 TCP 示例其它工具链STR71XGCCSTR71x GCC 工具链这里选用的芯片是 GD32 没有对应的我们直接选择这个 BARE 就好了然后我们可以把东西全部移动到一个目录下面5. 移植到工程文件5.1 文件介绍在 /functions 这个主要是核心的函数文件名对应功能码作用简写能否裁剪mbfunccoils.c01, 05, 15读写线圈Coils用不到线圈可删mbfuncdisc.c02, 04读离散输入Discrete Inputs用不到可删mbfuncinput.c04读输入寄存器Input Registers用不到可删mbfuncholding.c03, 06, 16读写保持寄存器Holding Registers通常必留最常用mbfuncdiag.c08诊断Diagnostics一般可删除非上位机需要mbfuncfile.c20, 21读写文件记录File Record几乎用不到可删mbfuncother.c07, 11, 12, 17读异常状态、获取事件计数等通常可删mbutils.c——工具函数如 CRC 计算、字节序转换不能删5.2 移植到工程大概目录如此记得添加.h文件地址第一次尝试编译Build started: Project: gd32f450test *** Using Compiler V5.06 update 7 (build 960), folder: D:\keil5\keil535\ARM\ARMCC\Bin Build target Target 1 compiling portserial.c... ..\modbus\BARE\port\portserial.c(42): error: #147-D: declaration is incompatible with BOOL xMBPortSerialInit(UCHAR, ULONG, UCHAR, eMBParity, UCHAR) (declared at line 72 of ..\modbus\include\mbport.h) xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity ) ..\modbus\BARE\port\portserial.c(71): warning: #177-D: function prvvUARTTxReadyISR was declared but never referenced static void prvvUARTTxReadyISR( void ) ..\modbus\BARE\port\portserial.c(81): warning: #177-D: function prvvUARTRxISR was declared but never referenced static void prvvUARTRxISR( void ) ..\modbus\BARE\port\portserial.c: 2 warnings, 1 error compiling mb.c... ..\modbus\mb.c(49): error: #5: cannot open source input file mbascii.h: No such file or directory #include mbascii.h ..\modbus\mb.c: 0 warnings, 1 error .\Objects\gd32f450test.axf - 2 Error(s), 2 Warning(s). Target not created. Build Time Elapsed: 00:00:01有第一个地方提示我们没有添加这个 mbascii.h 。源码保证可移植性一般都是开了两个的我们这里只用到 rtu 所以在mbconfig.h里面给他关掉就好了mbport.h里这个函数 xMBPortSerialInit 的声明是5 个参数但是我们只有四个点击这个 portserial.c 文件修改 xMBPortSerialInitBOOL xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity, UCHAR ucStopBits ) { return TRUE; }再次编译然后提示我们应用层需要提供四个函数这个先不急了先完成驱动适配编写先。Build started: Project: gd32f450test *** Using Compiler V5.06 update 7 (build 960), folder: D:\keil5\keil535\ARM\ARMCC\Bin Build target Target 1 compiling main.c... linking... .\Objects\gd32f450test.axf: Error: L6218E: Undefined symbol eMBRegCoilsCB (referred from mbfunccoils.o). .\Objects\gd32f450test.axf: Error: L6218E: Undefined symbol eMBRegDiscreteCB (referred from mbfuncdisc.o). .\Objects\gd32f450test.axf: Error: L6218E: Undefined symbol eMBRegHoldingCB (referred from mbfuncholding.o). .\Objects\gd32f450test.axf: Error: L6218E: Undefined symbol eMBRegInputCB (referred from mbfuncinput.o). Not enough information to list image symbols. Not enough information to list load addresses in the image map. Finished: 2 information, 0 warning and 4 error messages. .\Objects\gd32f450test.axf - 4 Error(s), 0 Warning(s). Target not created. Build Time Elapsed: 00:00:016. 驱动编写说一下需要修改的驱动文件6.1 porttimer.c首先说一下为什么协议需要这个定时器 Modbus RTU 用定时器是因为它没有帧头帧尾只能靠“静默时间”来划分一帧。要点说明帧定界靠时间一帧结束后总线静默T3.53.5 个字符时间从机才认为帧结束。T3.5 随波特率变9600 时约 4ms115200 时只有约 0.3ms软件循环测不准必须用硬件定时器。定时器作用收到字节→启动/清零定时器T3.5 内没新字节→中断触发→协议栈判定帧结束。还有 T1.5帧内两字节间隔超过 1.5 字符时间判定帧出错丢弃。对比ASCII 靠:和 CRLF 定界TCP 靠长度字段都不需要 T3.5。Modbus-RTU 的核心规定只有这一条两帧数据之间必须有 ≥ 3.5 个字符时间的空闲间隔用来判断一帧数据结束。比如 9600 波特率、8N1 格式1 个字符时间 10bit / 9600 ≈ 1.04ms那 3.5 个字符时间 ≈ 3.64ms这就是 Modbus 要求的帧间隔超时。Modbus 对 T3.5 超时有个补充规定当波特率 19200bps 时T3.5 超时固定为 1750us1.75ms不再按波特率计算。在 mbrtu.c 中有说明eMBErrorCode eMBRTUInit( UCHAR ucSlaveAddress, UCHAR ucPort, ULONG ulBaudRate, eMBParity eParity, UCHAR ucStopBits ) { eMBErrorCode eStatus MB_ENOERR; ULONG usTimerT35_50us; ( void )ucSlaveAddress; ENTER_CRITICAL_SECTION( ); /* Modbus RTU uses 8 Databits. */ if( xMBPortSerialInit( ucPort, ulBaudRate, 8, eParity, ucStopBits ) ! TRUE ) { eStatus MB_EPORTERR; } else { /* If baudrate 19200 then we should use the fixed timer values * t35 1750us. Otherwise t35 must be 3.5 times the character time. */ if( ulBaudRate 19200 ) { usTimerT35_50us 35; /* 1800us. */ } else { /* The timer reload value for a character is given by: * * ChTimeValue Ticks_per_1s / ( Baudrate / 11 ) * 11 * Ticks_per_1s / Baudrate * 220000 / Baudrate * The reload for t3.5 is 1.5 times this value and similary * for t3.5. */ usTimerT35_50us ( 7UL * 220000UL ) / ( 2UL * ulBaudRate ); } if( xMBPortTimersInit( ( USHORT ) usTimerT35_50us ) ! TRUE ) { eStatus MB_EPORTERR; } } EXIT_CRITICAL_SECTION( ); return eStatus; }我们这里使用115200使用的是usTimerT35_50us ( 7UL * 220000UL ) / ( 2UL * ulBaudRate );然后它被传给xMBPortTimersInit( ( USHORT ) usTimerT35_50us )对应的就是我们 porttimer.c 文件中的我用115200的话比19200大也就是会传入这个35所以为了达到1750us我们必须提供一个可以实现50us的定时器所以我们需要给他提供一个可以定时的函数这个我单独拉了一个文件time.cvoid Timer1_Init(uint16_t period) { timer_parameter_struct timer_initpara; rcu_periph_clock_enable(RCU_TIMER1); rcu_timer_clock_prescaler_config(RCU_TIMER_PSC_MUL4); timer_struct_para_init(timer_initpara); timer_deinit(TIMER1); /* TIMER1 configuration */ timer_initpara.prescaler 200-1; timer_initpara.alignedmode TIMER_COUNTER_EDGE; timer_initpara.counterdirection TIMER_COUNTER_UP; timer_initpara.period period*50 - 1; timer_initpara.clockdivision TIMER_CKDIV_DIV1; timer_initpara.repetitioncounter 0; timer_init(TIMER1,timer_initpara); /* auto-reload preload enable */ timer_auto_reload_shadow_enable(TIMER1); timer_interrupt_enable(TIMER1,TIMER_INT_UP); nvic_irq_enable(TIMER1_IRQn, 10, 0); /* TIMER1 enable */ timer_enable(TIMER1); }然后我们提供了这个函数之后回到porttimer.c文件填入BOOL xMBPortTimersInit( USHORT usTim1Timerout50us ) { Timer1_Init(usTim1Timerout50us); return TRUE; }下面介绍一下剩下三个函数是什么函数什么时候被协议栈调用干什么xMBPortTimersInit(35)初始化时eMBInit里配置定时器不启动vMBPortTimersEnable()收到字节时 / T3.5 超时后清零计数启动定时器vMBPortTimersDisable()T3.5 超时后 / 停止时停止定时器prvvTIMERExpiredISR 是把定时器中断转告给 FreeModbus 协议栈。所以这个文件参考/* * FreeModbus Libary: BARE Port * Copyright (C) 2006 Christian Walter woltisil.at * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * File: $Id$ */ /* ----------------------- Platform includes --------------------------------*/ #include port.h #include include.h #include time.h /* ----------------------- Modbus includes ----------------------------------*/ #include mb.h #include mbport.h /* ----------------------- static functions ---------------------------------*/ static void prvvTIMERExpiredISR( void ); /* ----------------------- Start implementation -----------------------------*/ BOOL xMBPortTimersInit( USHORT usTim1Timerout50us ) { Timer1_Init(usTim1Timerout50us); return TRUE; } inline void vMBPortTimersEnable( ) { timer_counter_value_config(TIMER1, 0); timer_interrupt_flag_clear(TIMER1, TIMER_INT_FLAG_UP); timer_enable(TIMER1); } inline void vMBPortTimersDisable( ) { timer_disable(TIMER1); timer_counter_value_config(TIMER1, 0); } static void prvvTIMERExpiredISR( void ) { ( void )pxMBPortCBTimerExpired( ); } void TIMER1_IRQHandler(void) { if(timer_interrupt_flag_get(TIMER1, TIMER_INT_FLAG_UP)) { timer_interrupt_flag_clear(TIMER1, TIMER_INT_FLAG_UP); prvvTIMERExpiredISR(); } }6.2 portserial.c函数修改xMBPortSerialInitGD32 USART 初始化GPIO、波特率、校验、停止位、NVICvMBPortSerialEnable开关收发中断 RS485 方向控制xMBPortSerialPutByteusart_data_transmit(USART5, ucByte)xMBPortSerialGetByte*pucByte usart_data_receive(USART5)需要新增一个串口中断收字节调prvvUARTRxISR()发完调prvvUARTTxReadyISR()下面是初始化一个引脚大家以自己板子上的为主/*------------------------------ 头文件 ------------------------------ */ /*------------------------------ 头文件 ------------------------------- */ #include gd32f4xx.h #include gpio.h /*------------------------------ 宏定义 -------------------------------- */ /*------------------------------ 全局变量定义 -------------------------- */ /*------------------------------ 枚举定义 ------------------------------ */ /*------------------------------ 静态函数声明 -------------------------- */ /*------------------------------ 类型定义 ------------------------------ */ /*------------------------------ 结构体定义 ---------------------------- */ /*------------------------------ 联合体定义 ---------------------------- */ /*------------------------------ 联合体定义 ---------------------------- */ /*------------------------------ 外部函数声明 -------------------------- */ /*------------------------------ 静态函数 ------------------------------ */ /*------------------------------ 外部函数 ------------------------------ */ /****************************************************************************** 函数名称 void RS485_Init(uint32_t baudrate) 函数版本 1.00 功能说明 初始化串口引脚 输入参数 波特率 输出参数 无 返回值 无 其它说明 ******************************************************************************/ void RS485_Init(uint32_t baudrate) { //开时钟 rcu_periph_clock_enable(RCU_GPIOC); rcu_periph_clock_enable(RCU_USART5); //配置 TX (PC6) 和 RX (PC7) gpio_af_set(GPIOC, GPIO_AF_8, GPIO_PIN_6 | GPIO_PIN_7); gpio_mode_set(GPIOC, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_6 | GPIO_PIN_7); gpio_output_options_set(GPIOC, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_6 | GPIO_PIN_7); //配置 USART5 usart_deinit(USART5); usart_baudrate_set(USART5, baudrate); usart_word_length_set(USART5, USART_WL_8BIT); usart_stop_bit_set(USART5, USART_STB_1BIT); usart_parity_config(USART5, USART_PM_NONE); usart_hardware_flow_rts_config(USART5, USART_RTS_DISABLE); usart_hardware_flow_cts_config(USART5, USART_CTS_DISABLE); usart_receive_config(USART5, USART_RECEIVE_ENABLE); usart_transmit_config(USART5, USART_TRANSMIT_ENABLE); usart_enable(USART5); //配置 NVIC 并开接收中断 nvic_irq_enable(USART5_IRQn, 8, 0); usart_interrupt_enable(USART5, USART_INT_RBNE); //配置 RS485 方向引脚 (PC8) gpio_mode_set(GPIOC, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_8); gpio_output_options_set(GPIOC, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_8); gpio_bit_set(GPIOC, GPIO_PIN_8); }因为 Modbus RTU 标准只用 TX/RX 两根线根本不用 RTS/CTS。RS485 是半双工收发共用一对差分线方向靠一个 DE/RE 引脚你的 PC8控制。如果开了 RTS/CTS反而会占用额外引脚、干扰通信。值得讲的也就是这个 vMBPortSerialEnable 函数主要负责开关中断和控制收发口。/* * FreeModbus Libary: BARE Port * Copyright (C) 2006 Christian Walter woltisil.at * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * File: $Id$ */ #include gd32f4xx.h #include gd32f4xx_libopt.h #include gd32f4xx_misc.h #include port.h #include gpio.h /* ----------------------- Modbus includes ----------------------------------*/ #include mb.h #include mbport.h #include SEGGER_RTT.h /* ----------------------- static functions ---------------------------------*/ static void prvvUARTTxReadyISR( void ); static void prvvUARTRxISR( void ); /* ----------------------- Start implementation -----------------------------*/ void vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable ) { if(xRxEnable)//打开接收中断 { gpio_bit_set(GPIOC, GPIO_PIN_8); usart_interrupt_enable(USART5, USART_INT_RBNE); } else { usart_interrupt_disable(USART5, USART_INT_RBNE); } if(xTxEnable)//发送 { gpio_bit_reset(GPIOC, GPIO_PIN_8); usart_interrupt_enable(USART5, USART_INT_TC); prvvUARTTxReadyISR(); } else { usart_interrupt_disable(USART5, USART_INT_TC); } } BOOL xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity, UCHAR ucStopBits ) { RS485_Init(ulBaudRate); return TRUE; } BOOL xMBPortSerialPutByte( CHAR ucByte ) { usart_data_transmit(USART5, (uint8_t)ucByte); volatile UCHAR send_byte (UCHAR)ucByte; SEGGER_RTT_printf(0, [TX] 0x%02X\r\n, send_byte); return TRUE; } BOOL xMBPortSerialGetByte( CHAR * pucByte ) { *pucByte (CHAR)usart_data_receive(USART5); SEGGER_RTT_printf(0, xMBPortSerialGetByte called, byte: 0x%02X\r\n, *pucByte); return TRUE; } /* Create an interrupt handler for the transmit buffer empty interrupt * (or an equivalent) for your target processor. This function should then * call pxMBFrameCBTransmitterEmpty( ) which tells the protocol stack that * a new character can be sent. The protocol stack will then call * xMBPortSerialPutByte( ) to send the character. */ static void prvvUARTTxReadyISR( void ) { pxMBFrameCBTransmitterEmpty( ); } /* Create an interrupt handler for the receive interrupt for your target * processor. This function should then call pxMBFrameCBByteReceived( ). The * protocol stack will then call xMBPortSerialGetByte( ) to retrieve the * character. */ static void prvvUARTRxISR( void ) { pxMBFrameCBByteReceived( ); } void USART5_IRQHandler(void) { if(RESET ! usart_interrupt_flag_get(USART5, USART_INT_FLAG_RBNE)) { prvvUARTRxISR(); } if(RESET ! usart_interrupt_flag_get(USART5, USART_INT_FLAG_TC)) { prvvUARTTxReadyISR(); } }6.3 四个回调函数Build started: Project: gd32f450test *** Using Compiler V5.06 update 7 (build 960), folder: D:\keil5\keil535\ARM\ARMCC\Bin Build target Target 1 compiling gpio.c... compiling portserial.c... linking... .\Objects\gd32f450test.axf: Error: L6218E: Undefined symbol eMBRegCoilsCB (referred from mbfunccoils.o). .\Objects\gd32f450test.axf: Error: L6218E: Undefined symbol eMBRegDiscreteCB (referred from mbfuncdisc.o). .\Objects\gd32f450test.axf: Error: L6218E: Undefined symbol eMBRegHoldingCB (referred from mbfuncholding.o). .\Objects\gd32f450test.axf: Error: L6218E: Undefined symbol eMBRegInputCB (referred from mbfuncinput.o). Not enough information to list image symbols. Not enough information to list load addresses in the image map. Finished: 2 information, 0 warning and 4 error messages. .\Objects\gd32f450test.axf - 4 Error(s), 0 Warning(s). Target not created. Build Time Elapsed: 00:00:00报错这里有提示要我们写什么函数对应功能码作用返回值eMBRegHoldingCB03 / 06 / 16读写保持寄存器MB_ENOERR成功MB_ENOREG地址非法eMBRegInputCB04读输入寄存器同上eMBRegCoilsCB01 / 05 / 15读写线圈同上eMBRegDiscreteCB02读离散输入同上我们重新写个文件 modbus.c 文件来实现一下在这个eMBRegHoldingCB里面因为03是只读06和16都要写所以要区分读和写mb.h中有一个定义typedef enum { MB_REG_READ, // 主站要读 MB_REG_WRITE // 主站要写 } eMBRegisterMode;所以我们可以这写eMBErrorCode eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode ) { eMBErrorCode eStatus MB_ENOERR; int iRegIndex; //地址越界检查 if( ( usAddress S_REG_HOLDING_STD_NREGS ) || ( usAddress usNRegs S_REG_HOLDING_STD_NREGS ) ) { return MB_ENOREG; } iRegIndex ( int )usAddress; switch ( eMode ) { /* 主站读把数组里的值搬到 pucRegBuffer*/ case MB_REG_READ: while( usNRegs 0 ) { *pucRegBuffer ( UCHAR )( usSRegHoldBufSTD[iRegIndex] 8 ); *pucRegBuffer ( UCHAR )( usSRegHoldBufSTD[iRegIndex] 0xFF ); iRegIndex; usNRegs--; } break; /* 主站写把 pucRegBuffer 里的值搬到数组缓冲区 */ case MB_REG_WRITE: while( usNRegs 0 ) { usSRegHoldBufSTD[iRegIndex] *pucRegBuffer 8; usSRegHoldBufSTD[iRegIndex] | *pucRegBuffer; iRegIndex; usNRegs--; } break; } return eStatus; }然后04功能码主要也是写就是把数据放进数组缓冲区就好了后面两个不常用总代码如下/*------------------------------ 头文件 ------------------------------ */ /*------------------------------ 头文件 ------------------------------- */ #include include.h #include modbus.h /*------------------------------ 宏定义 -------------------------------- */ /* 寄存器数量 */ #define S_REG_HOLDING_STD_NREGS 128 #define S_REG_INPUT_VERSION_NREGS 8 /* 保持寄存器地址映射 */ #define MB_COLOR_RED 0 #define MB_COLOR_GREEN 2 #define MB_COLOR_BLUE 4 #define MB_COLOR_YELLOW 7 #define MB_COLOR_CYAN 9 #define MB_COLOR_MAGENTA 10 /* 输入寄存器地址映射 */ #define MB_COLOR_ORANGE 200 #define MB_COLOR_PURPLE 201 /*------------------------------ 全局变量定义 -------------------------- */ USHORT usSRegHoldBufSTD[S_REG_HOLDING_STD_NREGS];//实际存保持寄存器数据的数组 USHORT usSRegInBufVer[S_REG_INPUT_VERSION_NREGS];//实际存输入寄存器数据的数组 /*------------------------------ 枚举定义 ------------------------------ */ /*------------------------------ 静态函数声明 -------------------------- */ /*------------------------------ 类型定义 ------------------------------ */ /*------------------------------ 结构体定义 ---------------------------- */ /*------------------------------ 联合体定义 ---------------------------- */ /*------------------------------ 联合体定义 ---------------------------- */ /*------------------------------ 外部函数声明 -------------------------- */ /*------------------------------ 静态函数 ------------------------------ */ /*------------------------------ 外部函数 ------------------------------ */ /****************************************************************************** 函数名称 eMBRegHoldingCB 函数版本 1.00 功能说明 保持寄存器读写回调功能码 03/06/16 输入参数 pucRegBuffer - 数据缓冲区指针 usAddress - 起始寄存器地址 usNRegs - 寄存器数量 eMode - MB_REG_READ 读 / MB_REG_WRITE 写 输出参数 无 返回值 MB_ENOERR 成功MB_ENOREG 地址非法 其它说明 ******************************************************************************/ eMBErrorCode eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode ) { eMBErrorCode eStatus MB_ENOERR; int iRegIndex; //地址越界检查 if( ( usAddress S_REG_HOLDING_STD_NREGS ) || ( usAddress usNRegs S_REG_HOLDING_STD_NREGS ) ) { return MB_ENOREG; } iRegIndex ( int )usAddress; switch ( eMode ) { /* 主站读把数组里的值搬到 pucRegBuffer*/ case MB_REG_READ: while( usNRegs 0 ) { *pucRegBuffer ( UCHAR )( usSRegHoldBufSTD[iRegIndex] 8 ); *pucRegBuffer ( UCHAR )( usSRegHoldBufSTD[iRegIndex] 0xFF ); iRegIndex; usNRegs--; } break; /* 主站写把 pucRegBuffer 里的值搬到数组缓冲区 */ case MB_REG_WRITE: while( usNRegs 0 ) { usSRegHoldBufSTD[iRegIndex] *pucRegBuffer 8; usSRegHoldBufSTD[iRegIndex] | *pucRegBuffer; iRegIndex; usNRegs--; } break; } return eStatus; } /****************************************************************************** 函数名称 eMBRegInputCB 函数版本 1.00 功能说明 输入寄存器读回调功能码 04 输入参数 pucRegBuffer - 数据缓冲区指针 usAddress - 起始寄存器地址 usNRegs - 寄存器数量 输出参数 无 返回值 MB_ENOERR 成功MB_ENOREG 地址非法 其它说明 ******************************************************************************/ eMBErrorCode eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) { eMBErrorCode eStatus MB_ENOERR; int iRegIndex; /* 地址越界检查 */ if( ( usAddress S_REG_INPUT_VERSION_NREGS ) || ( usAddress usNRegs S_REG_INPUT_VERSION_NREGS ) ) { return MB_ENOREG; } iRegIndex ( int )usAddress; //把 pucRegBuffer 里的值搬到数组缓冲区 while( usNRegs 0 ) { *pucRegBuffer ( UCHAR )( usSRegInBufVer[iRegIndex] 8 ); *pucRegBuffer ( UCHAR )( usSRegInBufVer[iRegIndex] 0xFF ); iRegIndex; usNRegs--; } return eStatus; } /****************************************************************************** 函数名称 eMBRegCoilsCB 函数版本 1.00 功能说明 线圈读写回调功能码 01/05/15暂未使用 输入参数 pucRegBuffer - 数据缓冲区指针 usAddress - 起始线圈地址 usNCoils - 线圈数量 eMode - MB_REG_READ 读 / MB_REG_WRITE 写 输出参数 无 返回值 MB_ENOERR 成功 其它说明 ******************************************************************************/ eMBErrorCode eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils, eMBRegisterMode eMode ) { return MB_ENOERR; } /****************************************************************************** 函数名称 eMBRegDiscreteCB 函数版本 1.00 功能说明 离散输入读回调功能码 02暂未使用 输入参数 pucRegBuffer - 数据缓冲区指针 usAddress - 起始离散输入地址 usNDiscrete - 离散输入数量 输出参数 无 返回值 MB_ENOERR 成功 其它说明 ******************************************************************************/ eMBErrorCode eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete ) { return MB_ENOERR; }7. 应用层适配驱动层编写好了现在写应用层我在freertos多开一个任务void ModbusTask(void *pvParameters) { eMBInit(MB_RTU, 1, 0, 115200, MB_PAR_NONE, 1); eMBEnable(); while(1) { ( void )eMBPoll(); vTaskDelay(10); } }ok测试一下[15:58:15.361]发→◇01 03 00 01 00 01 D5 CA [15:58:15.373]收←◆01 03 02 00 00 B8 44 [15:58:15.529]发→◇01 03 00 01 00 01 D5 CA [15:58:15.535]收←◆01 03 02 00 00 B8 44 [15:58:15.849]发→◇01 03 00 01 00 01 D5 CA [15:58:15.866]收←◆01 03 02 00 00 B8 44 [15:58:16.001]发→◇01 03 00 01 00 01 D5 CA [15:58:16.009]收←◆01 03 02 00 00 B8 44 [15:58:16.121]发→◇01 03 00 01 00 01 D5 CA [15:58:16.128]收←◆01 03 02 00 00 B8 44
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。