stm32Hal库移植freemodbus,modbusRTU功能实现

基本借鉴来自:

https://blog.csdn.net/qq_33954661/article/details/151179820

鉴于网上很多文章都写得不清不楚或者就是动不动就收费,这很恶心,就这么点移植步骤还要神神秘秘的,有辱斯文,有的阅读让读者很不舒服,本人经过反复推敲,看了很多文章之后,再根据自己的理解移植了freemodbus,如果在移植过程中有什么问题欢迎留言评论,大家一起进步,共勉。(如果移植过程中哪里有问题,没提到的可以评论区留言,看到我会回复 并修改这个文章)

一、需要准备的东西

1、stm32F407IGT6开发板(本移植是基于HAL库的,只要是stm32平台应该都适用)
2、freemodbus-v1.6 软件包(网上有很多这个可以搜索就能下载)
3、modbus调试工具 modbus Poll
4、开发工具 MDK5,以及STM32CubeMX。

如果是标准库的道友可以看正点原子的论坛,里面有一个前辈已经移植好并且成功了,我也是参阅他的文章做的部分移植链接在这里http://www.openedv.com/forum.php?mod=viewthread&tid=297921&highlight=freemodbus

二、开发步骤

1.使用stm32cubeMX生成代码
步骤如下

1、配置时钟源

2、配置烧录引脚

3、配置定时器2

该定时器用于接收modbusRTU的3.5个字节判断是否超时,目标:每个计数周期为 50μs,以匹配 FreeModbus 的 50μs 单位超时机制。

记得使能中断,多亏网友的提示,忘记需要使能中断,不然会导致modbus通讯超时

4、主频时钟配置

5、串口配置,这里使用的是串口3 用其他串口也可以,在移植freemodbus的时候修改底层配置函数即可。

6、生成代码,记得勾上生成头文件那,主要是方便查看代码以及添加头文件等

7、将下载好的软件包放到工程中,方便移植


8、在core文件夹中新建modbus文件夹


9在modbus文件夹中分别将freemodbus中用到的文件添加进去 不建议全部加进去,因为不美观显得工程很杂乱,而且在工程添加的时候容易出错。



最终像这样在这里插入图片描述
10、打开MDK工程将之前放到目录下的文件统统添加进去(这里不多讲怎么添加,最终结果如下图)

添加PATH

至此,文件移植完毕=======================================================================
现在开始修改底层文件

1、主要最关键的四个文件是

我们一个一个的来修改
首先是 portevent.c文件中(几乎不需要怎么修改)

/* * FreeModbus Libary: BARE Port * Copyright (C) 2006 Christian Walter <wolti@sil.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$ */ /* ----------------------- Modbus includes ----------------------------------*/ #include "mb.h" #include "mbport.h" /* ----------------------- Variables ----------------------------------------*/ static eMBEventType eQueuedEvent; static BOOL xEventInQueue; /* ----------------------- Start implementation -----------------------------*/ BOOL xMBPortEventInit( void ) { xEventInQueue = FALSE; return TRUE; } BOOL xMBPortEventPost( eMBEventType eEvent ) { xEventInQueue = TRUE; eQueuedEvent = eEvent; return TRUE; } BOOL xMBPortEventGet( eMBEventType * eEvent ) { BOOL xEventHappened = FALSE; if( xEventInQueue ) { *eEvent = eQueuedEvent; xEventInQueue = FALSE; xEventHappened = TRUE; } return xEventHappened; }

重点来了 修改port serial.c文件的修改 (网上看到的大多是标准库的移植,有的函数名字不一样,用的时候要注意)

特别是发送和接收函数,不能用HAL库自带的收发函数(想认真看的就看一下不看就跳过)

在 FreeModbus 中,ucRTUBuf 是协议栈用于缓存接收帧的缓冲区,正常情况下应存储一整帧的完整数据(从地址字节到 CRC 校验字节)。如果每次只能收到最后一个字节,通常是由于帧接收逻辑没有正确累积字节,导致前序字节被覆盖或未被正常存储。具体原因可从以下几个方面排查: 1. 接收中断处理不完整,仅最后一个字节触发了协议栈缓存 FreeModbus 依赖每接收一个字节就触发一次 pxMBFrameCBByteReceived 回调,并通过 xMBPortSerialGetByte 读取字节后存入 ucRTUBuf。如果前序字节的中断未被正确处理,会导致只有最后一个字节被缓存。 可能原因: 串口接收中断被意外禁用:例如在中断处理中,未正确重新使能接收中断(如 __HAL_UART_ENABLE_IT(&huart3, UART_IT_RXNE)),导致仅第一个字节触发中断,后续字节无法触发,直到最后一个字节 “侥幸” 触发中断。 中断优先级过低:其他高优先级中断占用过多时间,导致前序字节的中断被忽略,仅最后一个字节的中断被响应。 排查方法: 在 pxMBFrameCBByteReceived 回调中添加打印(或通过调试器观察),确认是否每个字节都触发了该回调。若只有最后一个字节触发,则说明中断处理存在漏触发问题。 2. ucRTUBuf 缓冲区索引未正确递增,前序字节被覆盖 FreeModbus 内部通过一个索引变量(如 ucRTUBufPos)记录当前接收字节在 ucRTUBuf 中的位置,每接收一个字节,索引递增一次。如果索引未正确递增(或被意外重置),会导致新字节始终覆盖 ucRTUBuf 的第一个位置,最终只保留最后一个字节。 可能原因: 协议栈状态机异常:例如在接收过程中,由于帧格式错误(如 CRC 校验失败),协议栈提前重置了 ucRTUBufPos 索引,导致后续字节从起始位置重新存储,仅最后一个字节被保留。 自定义代码干扰:若在用户代码中意外修改了 ucRTUBufPos 或 ucRTUBuf 的地址,会破坏缓冲区的累积逻辑。 排查方法: 通过调试器跟踪 ucRTUBufPos 的值,观察每接收一个字节后是否递增(正常应从 0→1→2…)。若索引始终为 0 或被重置,则说明状态机或索引被异常干扰。 3. T35 超时定时器配置错误,帧提前结束 Modbus RTU 通过T35 超时(3.5 个字符时间)判断一帧结束。如果定时器超时时间过短,会导致协议栈在接收完整帧前就判定 “帧结束”,并重置接收缓冲区,此时若后续还有字节到达,会被当作新帧的第一个字节,最终只保留最后一个字节。 可能原因: 定时器超时时间计算错误:例如波特率为 9600 时,1 个字符时间为 11 位(1 位起始 + 8 位数据 + 1 位停止 + 1 位校验),即 11/9600 ≈ 1.146ms,T35 应为 3.5×1.146≈4.01ms。若配置为 1ms,会导致提前超时。 定时器未正确重启:每接收一个字节后,协议栈需重启 T35 定时器(确保超时从 “最后一个字节接收完成” 开始计算)。若未重启,会导致中间字节触发超时,提前结束帧。 排查方法: 检查 xMBPortTimersInit 中定时器的超时时间是否正确(根据波特率计算),并确认 pxMBFrameCBByteReceived 回调中是否触发了定时器重启(FreeModbus 默认会在接收字节后重启定时器)。 4. xMBPortSerialGetByte 函数读取错误,仅最后一个字节被正确获取 若 xMBPortSerialGetByte 函数读取字节的时机错误(如未及时读取导致数据被覆盖),会导致前序字节无效,仅最后一个字节被正确存入缓冲区。 可能原因: 读取寄存器延迟:xMBPortSerialGetByte 未在 pxMBFrameCBByteReceived 触发后立即读取 DR 寄存器,导致新字节覆盖了 DR 中的旧数据(UART 接收新字节时会自动覆盖 DR)。 函数实现错误:例如仍在使用 HAL_UART_Receive 等阻塞函数,导致前序字节读取超时,仅最后一个字节被成功读取。 排查方法: 在 xMBPortSerialGetByte 中添加打印(或调试观察),确认每次调用是否正确读取到当前字节(而非 0xFF 或固定值)。若前序字节为无效值,说明读取逻辑存在问题。 总结排查步骤 确认每个接收字节都触发了 pxMBFrameCBByteReceived 回调(中断未漏触发); 跟踪 ucRTUBufPos 索引,确认其随字节接收递增(未被重置); 验证 T35 定时器超时时间是否正确(符合当前波特率的 3.5 个字符时间); 检查 xMBPortSerialGetByte 函数是否每次都正确读取 DR 寄存器中的当前字节。 通过以上步骤,通常能定位到 “仅最后一个字节被缓存” 的具体原因,核心是确保每个字节都被正确接收、读取并按顺序存入 ucRTUBuf,且帧结束判断(T35 超时)符合协议规范。
/* * FreeModbus Libary: BARE Port * Copyright (C) 2006 Christian Walter <wolti@sil.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 "port.h" //uesr include start #include "main.h" #include "usart.h" //uesr include end /* ----------------------- Modbus includes ----------------------------------*/ #include "mb.h" #include "mbport.h" /* ----------------------- static functions ---------------------------------*/ //static void prvvUARTTxReadyISR( void ); //static void prvvUARTRxISR( void ); /* ----------------------- Start implementation -----------------------------*/ void vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable ) { /* If xRXEnable enable serial receive interrupts. If xTxENable enable * transmitter empty interrupts. */ if(xRxEnable)//接收使能 { // 使能RXNE(接收非空)中断 __HAL_UART_ENABLE_IT(&huart3, UART_IT_RXNE); } else//接收失能 { // 禁用RXNE中断 __HAL_UART_DISABLE_IT(&huart3, UART_IT_RXNE); } if(xTxEnable)//发送使能 { // 使能TXE(接收非空)中断 __HAL_UART_ENABLE_IT(&huart3, UART_IT_TXE); } else { // 禁用TXE中断 __HAL_UART_DISABLE_IT(&huart3, UART_IT_TXE); } } BOOL xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity ) { (void)ucPORT; //不修改串口号 (void)ucDataBits; //不修改数据位长度 (void)eParity; //不修改检验格式 huart3.Instance = USART3; huart3.Init.BaudRate = ulBaudRate; huart3.Init.WordLength = UART_WORDLENGTH_8B; huart3.Init.StopBits = UART_STOPBITS_1; huart3.Init.Parity = UART_PARITY_NONE; huart3.Init.Mode = UART_MODE_TX_RX; huart3.Init.HwFlowCtl = UART_HWCONTROL_NONE; huart3.Init.OverSampling = UART_OVERSAMPLING_16; if (HAL_UART_Init(&huart3) != HAL_OK) { Error_Handler(); } return TRUE; } BOOL xMBPortSerialPutByte( CHAR ucByte ) { /* Put a byte in the UARTs transmit buffer. This function is called * by the protocol stack if pxMBFrameCBTransmitterEmpty( ) has been * called. */ /* 等待发送缓冲区为空(TXE标志置位) */ while (__HAL_UART_GET_FLAG(&huart3, UART_FLAG_TXE) == RESET) { // 超时处理(可选,避免死等) // 若长时间未置位,可返回FALSE表示发送失败 } /* 直接写入发送寄存器,触发发送 */ huart3.Instance->DR = (uint8_t)ucByte; return TRUE; } BOOL xMBPortSerialGetByte( CHAR *pucByte ) { /* Return the byte in the UARTs receive buffer. This function is called * by the protocol stack after pxMBFrameCBByteReceived( ) has been called. */ *pucByte = (CHAR)(huart3.Instance->DR & 0x00FF); // HAL_UART_Receive(&huart3,(uint8_t *)pucByte,1,100); 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( ); }

porttimer.c文件的修改

/* * FreeModbus Libary: BARE Port * Copyright (C) 2006 Christian Walter <wolti@sil.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 "tim.h" #include "main.h" /* ----------------------- Modbus includes ----------------------------------*/ #include "mb.h" #include "mbport.h" /* ----------------------- static functions ---------------------------------*/ static void prvvTIMERExpiredISR( void ); /* ----------------------- Start implementation -----------------------------*/ BOOL xMBPortTimersInit( USHORT usTim1Timerout50us ) { /* USER CODE BEGIN TIM2_Init 0 */ /* USER CODE END TIM2_Init 0 */ TIM_ClockConfigTypeDef sClockSourceConfig = {0}; TIM_MasterConfigTypeDef sMasterConfig = {0}; /* USER CODE BEGIN TIM2_Init 1 */ /* USER CODE END TIM2_Init 1 */ htim2.Instance = TIM2; htim2.Init.Prescaler = 8400-1; htim2.Init.CounterMode = TIM_COUNTERMODE_UP; htim2.Init.Period = usTim1Timerout50us-1; htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; if (HAL_TIM_Base_Init(&htim2) != HAL_OK) { Error_Handler(); } sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL; if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK) { Error_Handler(); } sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK) { Error_Handler(); } // 清除TIM3的溢出(更新)中断标志位 __HAL_TIM_CLEAR_IT(&htim2, TIM_IT_UPDATE); // 关闭TIM3的溢出(更新)中断(失能中断) __HAL_TIM_DISABLE_IT(&htim2, TIM_IT_UPDATE); // // 禁用TIM3(停止定时器计数) // HAL_TIM_Base_Stop(&htim2); return TRUE; } inline void vMBPortTimersEnable( ) { /* Enable the timer with the timeout passed to xMBPortTimersInit( ) */ // /* 假设已定义定时器句柄 htim2(对应 TIM2),需在外部声明 */ // extern TIM_HandleTypeDef htim2; /* 清除定时器更新中断标志位(对应 TIM_ClearITPendingBit) */ __HAL_TIM_CLEAR_IT(&htim2, TIM_IT_UPDATE); /* 使能定时器更新中断(对应 TIM_ITConfig) */ __HAL_TIM_ENABLE_IT(&htim2, TIM_IT_UPDATE); /* 重置定时器计数器值为 0(对应 TIM_SetCounter) */ __HAL_TIM_SET_COUNTER(&htim2, 0x0000); /* 启动定时器(对应 TIM_Cmd(ENABLE)) */ HAL_TIM_Base_Start(&htim2); } inline void vMBPortTimersDisable( ) { /* Disable any pending timers. */ /* 清除定时器更新中断标志位(对应 TIM_ClearITPendingBit) */ __HAL_TIM_CLEAR_IT(&htim2, TIM_IT_UPDATE); /* 使能定时器更新中断(对应 TIM_ITConfig) */ __HAL_TIM_ENABLE_IT(&htim2, TIM_IT_UPDATE); /* 重置定时器计数器值为 0(对应 TIM_SetCounter) */ __HAL_TIM_SET_COUNTER(&htim2, 0x0000); /* 停止定时器(对应 TIM_Cmd(ENABLE)) */ HAL_TIM_Base_Stop(&htim2); } /* Create an ISR which is called whenever the timer has expired. This function * must then call pxMBPortCBTimerExpired( ) to notify the protocol stack that * the timer has expired. */ static void prvvTIMERExpiredISR( void ) { ( void )pxMBPortCBTimerExpired( ); } void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { // 确认是目标定时器(TIM2)的更新中断 if (htim == &htim2) { // 清除TIM2的溢出(更新)中断标志位 __HAL_TIM_CLEAR_IT(&htim2, TIM_IT_UPDATE); prvvTIMERExpiredISR(); } }

mb.c文件不需要修改

port.c文件的修改 具体算法有兴趣的可以自行研究

#include "mb.h" /* ----------------------- Modbus includes ----------------------------------*/ #include "mb.h" #include "mbport.h" #include "main.h" #include "port.h" #include "mbutils.h" /* ----------------------- Defines ------------------------------------------*/ /* ----------------------- Defines ------------------------------------------*/ #define REG_INPUT_START 0x0001 #define REG_INPUT_NREGS 50 #define REG_HOLDING_START 0x0000 #define REG_HOLDING_NREGS 50 #define REG_COILS_START 0x0000 #define REG_COILS_SIZE 50 #define REG_DISCRETE_START 0x0000 #define REG_DISCRETE_SIZE 50 //寄存器数量 /* ----------------------- Static variables ---------------------------------*/ uint16_t usRegInputBuf[REG_INPUT_NREGS] = {0x8001,0x7002,0x6003,0x5004,0x4005,0x3006,0x2007,0x1008}; uint16_t usRegHoldingBuf[REG_HOLDING_NREGS]={0x0810,0x0720,0x0630,0x0540,0x0450,0x0360,0x0270,0x0180}; uint8_t ucRegCoilsBuf[REG_COILS_SIZE]={0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08}; uint8_t ucRegDiscreteBuf[REG_DISCRETE_SIZE]={0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08}; /* ----------------------- Static variables ---------------------------------*/ /* ----------------------- Variables ----------------------------------------*/ int VIC_Temp; /* ----------------------- Start implementation -----------------------------*/ void EnterCriticalSection( ) { __ASM volatile("cpsid i"); } void ExitCriticalSection( ) { __ASM volatile("cpsie i"); } eMBErrorCode eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) { eMBErrorCode eStatus = MB_ENOERR; int iRegIndex; if( ( usAddress >= REG_INPUT_START ) \ && ( usAddress + usNRegs <= REG_INPUT_START + REG_INPUT_NREGS ) ) { iRegIndex = ( int )( usAddress - REG_INPUT_START ); while( usNRegs > 0 ) { *pucRegBuffer++ = ( unsigned char )( usRegInputBuf[iRegIndex] >> 8 ); *pucRegBuffer++ = ( unsigned char )( usRegInputBuf[iRegIndex] & 0xFF ); iRegIndex++; usNRegs--; } } else { eStatus = MB_ENOREG; } return eStatus; } eMBErrorCode eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode ) { eMBErrorCode eStatus = MB_ENOERR; int iRegIndex; // u16 *PRT=(u16*)pucRegBuffer; if( ( (int16_t)usAddress >= REG_HOLDING_START ) && ( usAddress + usNRegs <= REG_HOLDING_START + REG_HOLDING_NREGS ) ) { iRegIndex = ( int )( usAddress - REG_HOLDING_START ); switch ( eMode ) { case MB_REG_READ: while( usNRegs > 0 ) { // *PRT++ = __REV16(usRegHoldingBuf[iRegIndex++]); //数据序转 REV16.W *pucRegBuffer++ = ( unsigned char )( usRegHoldingBuf[iRegIndex] >> 8 ); *pucRegBuffer++ = ( unsigned char )( usRegHoldingBuf[iRegIndex] & 0xFF ); iRegIndex++; usNRegs--; } break; case MB_REG_WRITE: while( usNRegs > 0 ) { // usRegHoldingBuf[iRegIndex++] = __REV16(*PRT++); //数据序转 REV16.W usRegHoldingBuf[iRegIndex] = *pucRegBuffer++ << 8; usRegHoldingBuf[iRegIndex] |= *pucRegBuffer++; iRegIndex++; usNRegs--; } break; } } else { eStatus = MB_ENOREG; } return eStatus; } eMBErrorCode eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils, eMBRegisterMode eMode ) { eMBErrorCode eStatus = MB_ENOERR; //寄存器个数 int16_t iNCoils = ( int16_t )usNCoils; //寄存器偏移量 int16_t usBitOffset; //检查寄存器是否在指定范围内 if( ( (int16_t)usAddress >= REG_COILS_START ) && ( usAddress + usNCoils <= REG_COILS_START + REG_COILS_SIZE ) ) { //计算寄存器偏移量 usBitOffset = ( int16_t )( usAddress - REG_COILS_START ); switch ( eMode ) { //读操作 case MB_REG_READ: while( iNCoils > 0 ) { *pucRegBuffer++ = xMBUtilGetBits( ucRegCoilsBuf, usBitOffset, ( uint8_t )( iNCoils > 8 ? 8 : iNCoils ) ); iNCoils -= 8; usBitOffset += 8; } break; //写操作 case MB_REG_WRITE: while( iNCoils > 0 ) { xMBUtilSetBits( ucRegCoilsBuf, usBitOffset, ( uint8_t )( iNCoils > 8 ? 8 : iNCoils ), *pucRegBuffer++ ); iNCoils -= 8; } break; } } else { eStatus = MB_ENOREG; } return eStatus; } eMBErrorCode eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete ) { eMBErrorCode eStatus = MB_ENOERR; //操作寄存器个数 int16_t iNDiscrete = ( int16_t )usNDiscrete; //偏移量 uint16_t usBitOffset; //判断寄存器时候再制定范围内 if( ( (int16_t)usAddress >= REG_DISCRETE_START ) && ( usAddress + usNDiscrete <= REG_DISCRETE_START + REG_DISCRETE_SIZE ) ) { //获得偏移量 usBitOffset = ( uint16_t )( usAddress - REG_DISCRETE_START ); while( iNDiscrete > 0 ) { *pucRegBuffer++ = xMBUtilGetBits( ucRegDiscreteBuf, usBitOffset, ( uint8_t)( iNDiscrete > 8 ? 8 : iNDiscrete ) ); iNDiscrete -= 8; usBitOffset += 8; } } else { eStatus = MB_ENOREG; } return eStatus; } /*freemodbus作者使用了assert,故增加如下代码,同时 *options for target 对话框,target页面,勾选Use MicroLib */ #ifdef USE_FULL_ASSERT /** * @brief Reports the name of the source file and the source line number * where the assert_param error has occurred. * @param file: pointer to the source file name * @param line: assert_param error line source number * @retval None */ void assert_failed(uint8_t* file, uint32_t line) { /* User can add his own implementation to report the file name and line number, ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */ /* Infinite loop */ while (1) { } } #else void __aeabi_assert(const char * x1, const char * x2, int x3) { (void)x3; } #endif

新建port.h如果没有的话

/* * FreeModbus Libary: BARE Port * Copyright (C) 2006 Christian Walter <wolti@sil.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$ */ #ifndef _PORT_H #define _PORT_H #include <assert.h> #include <inttypes.h> #define INLINE inline #define PR_BEGIN_EXTERN_C extern "C" { #define PR_END_EXTERN_C } #define ENTER_CRITICAL_SECTION( ) EnterCriticalSection() #define EXIT_CRITICAL_SECTION( ) ExitCriticalSection() //#define assert (expr) typedef uint8_t BOOL; typedef unsigned char UCHAR; typedef char CHAR; typedef uint16_t USHORT; typedef int16_t SHORT; typedef uint32_t ULONG; typedef int32_t LONG; void EnterCriticalSection(void); void ExitCriticalSection(void); #ifndef TRUE #define TRUE 1 #endif #ifndef FALSE #define FALSE 0 #endif #endif

串口中断函数的修改 在stm32f4xx_it.c文件中

/** * @brief This function handles USART3 global interrupt. */ void USART3_IRQHandler(void) { /* USER CODE BEGIN USART3_IRQn 0 */ // 在HAL库处理前,优先处理Modbus需要的标志位 if (__HAL_UART_GET_FLAG(&huart3, UART_FLAG_RXNE) != RESET && __HAL_UART_GET_IT_SOURCE(&huart3, UART_IT_RXNE) != RESET) { pxMBFrameCBByteReceived(); // 调用Modbus接收回调 __HAL_UART_CLEAR_FLAG(&huart3, UART_FLAG_RXNE); // 清除标志位 tx_cnt = (tx_cnt + 1) % 1000; // 计数(确保原子操作,简单场景可忽略) } if (__HAL_UART_GET_FLAG(&huart3, UART_FLAG_TXE) != RESET && __HAL_UART_GET_IT_SOURCE(&huart3, UART_IT_TXE) != RESET) { pxMBFrameCBTransmitterEmpty(); // 调用Modbus发送回调 __HAL_UART_CLEAR_FLAG(&huart3, UART_FLAG_TXE); // 清除标志位 rx_cnt = (rx_cnt + 1) % 1000; // 计数 } /* USER CODE END USART3_IRQn 0 */ HAL_UART_IRQHandler(&huart3); /* USER CODE BEGIN USART3_IRQn 1 */ /* USER CODE END USART3_IRQn 1 */ }

至此所有步骤移植,完成,
然后main.c中编写如下代码即可开始测试

/* USER CODE BEGIN Header */ /** ****************************************************************************** * @file : main.c * @brief : Main program body ****************************************************************************** * @attention * * Copyright (c) 2025 STMicroelectronics. * All rights reserved. * * This software is licensed under terms that can be found in the LICENSE file * in the root directory of this software component. * If no LICENSE file comes with this software, it is provided AS-IS. * ****************************************************************************** */ /* USER CODE END Header */ /* Includes ------------------------------------------------------------------*/ #include "main.h" #include "tim.h" #include "usart.h" #include "gpio.h" /* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ #include "mb.h" #include "mbport.h" /* USER CODE END Includes */ /* Private typedef -----------------------------------------------------------*/ /* USER CODE BEGIN PTD */ /* USER CODE END PTD */ /* Private define ------------------------------------------------------------*/ /* USER CODE BEGIN PD */ /* USER CODE END PD */ /* Private macro -------------------------------------------------------------*/ /* USER CODE BEGIN PM */ /* USER CODE END PM */ /* Private variables ---------------------------------------------------------*/ /* USER CODE BEGIN PV */ /* USER CODE END PV */ /* Private function prototypes -----------------------------------------------*/ void SystemClock_Config(void); /* USER CODE BEGIN PFP */ /* USER CODE END PFP */ /* Private user code ---------------------------------------------------------*/ /* USER CODE BEGIN 0 */ /* USER CODE END 0 */ /** * @brief The application entry point. * @retval int */ int main(void) { /* USER CODE BEGIN 1 */ /* USER CODE END 1 */ /* MCU Configuration--------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* USER CODE BEGIN Init */ /* USER CODE END Init */ /* Configure the system clock */ SystemClock_Config(); /* USER CODE BEGIN SysInit */ /* USER CODE END SysInit */ /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_USART3_UART_Init(); MX_TIM4_Init(); MX_TIM2_Init(); /* USER CODE BEGIN 2 */ // HAL_TIM_Base_Start_IT(&htim4); eMBInit( MB_RTU, 0x0A, 0, 9600, MB_PAR_NONE ); eMBEnable( ); /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ HAL_GPIO_TogglePin(RUN_LED_GPIO_Port,RUN_LED_Pin); HAL_Delay(150); ( void )eMBPoll( );//不断查询数据帧 } /* USER CODE END 3 */ } /** * @brief System Clock Configuration * @retval None */ void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; /** Configure the main internal regulator output voltage */ __HAL_RCC_PWR_CLK_ENABLE(); __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1); /** Initializes the RCC Oscillators according to the specified parameters * in the RCC_OscInitTypeDef structure. */ RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI; RCC_OscInitStruct.HSIState = RCC_HSI_ON; RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI; RCC_OscInitStruct.PLL.PLLM = 8; RCC_OscInitStruct.PLL.PLLN = 168; RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2; RCC_OscInitStruct.PLL.PLLQ = 4; if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { Error_Handler(); } /** Initializes the CPU, AHB and APB buses clocks */ RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK) { Error_Handler(); } } /* USER CODE BEGIN 4 */ /* USER CODE END 4 */ /** * @brief This function is executed in case of error occurrence. * @retval None */ void Error_Handler(void) { /* USER CODE BEGIN Error_Handler_Debug */ /* User can add his own implementation to report the HAL error return state */ __disable_irq(); while (1) { } /* USER CODE END Error_Handler_Debug */ } #ifdef USE_FULL_ASSERT /** * @brief Reports the name of the source file and the source line number * where the assert_param error has occurred. * @param file: pointer to the source file name * @param line: assert_param error line source number * @retval None */ void assert_failed(uint8_t *file, uint32_t line) { /* USER CODE BEGIN 6 */ /* User can add his own implementation to report the file name and line number, ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */ /* USER CODE END 6 */ } #endif /* USE_FULL_ASSERT */

打开在线仿真,以及modbus调试工具
测试结果如下图所示

modbus调试软件正常无报错,且数据同步正常

基本借鉴来自:

https://blog.csdn.net/qq_33954661/article/details/151179820

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/1136073.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

玩转线材端子机PLC一拖二方案

线材端子机程序&#xff0c;主机加从机一拖二&#xff0c;不用通信指令&#xff0c;共用公共区寄存器&#xff0c;威纶屏加松下fpxh60ct plc&#xff0c;最多可以控制12轴搞工控的老铁们应该都懂&#xff0c;遇到多轴控制项目最怕通信延迟和程序复杂度。最近刚整完一个线材端子…

Halcon联合C#贴片机程序:四轴运动控制,使用雷赛驱动卡,程序带注释,直接使用减少开发周期

Halcon联合C#贴片机程序&#xff0c;带运动控制部分&#xff0c;四轴运动使用 国内性价比很高的雷赛驱动卡&#xff0c;非常方便&#xff0c;程序带注释&#xff0c;懂一点C#和Halcon的改一下可以直接使用&#xff0c;减少开发周期。 自带软件加密源程序。在工业自动化开发中&a…

电子电路中的负反馈机制:全面讲解与应用

负反馈&#xff1a;让电路“自我纠正”的智慧你有没有想过&#xff0c;为什么你的耳机能清晰还原音乐中的每一个音符&#xff1f;为什么工业传感器能在嘈杂的工厂里准确读出微弱的温度变化&#xff1f;这些看似理所当然的背后&#xff0c;藏着一个模拟电路中最古老却最强大的设…

基于SpringBoot的流浪动物救助系统(源码+lw+部署文档+讲解等)

课题介绍 本课题聚焦流浪动物救助规范化与社会化协同需求&#xff0c;设计并实现一套基于Spring Boot框架的流浪动物救助系统&#xff0c;旨在破解传统流浪动物救助中信息分散、救助资源调配低效、救助流程不透明、领养与救助衔接不畅等痛点问题&#xff0c;精准匹配救助人员便…

LeetCode热题--1143. 最长公共子序列--中等

题目 给定两个字符串 text1 和 text2&#xff0c;返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 &#xff0c;返回 0 。 一个字符串的 子序列 是指这样一个新的字符串&#xff1a;它是由原字符串在不改变字符的相对顺序的情况下删除某些字符&#xff08;…

西门子博图PID仿真对象库,可以模拟现场温度,阀门等实物对象,训练PID调节,省去买设备

西门子博图PID仿真对象库&#xff0c;可以模拟现场温度&#xff0c;阀门等实物对象&#xff0c;训练PID调节&#xff0c;省去买设备&#xff0c;选1500硬件组态支持模拟器运行&#xff0c;就是在没有任何硬件的情况下非常接近现场设备属性&#xff0c;调PID&#xff0c;支持自动…

比亚迪逆风突围:2025年销量飙升62%,海狮7热销单月冲破3千!

最新数据显示&#xff0c;2025年中国新能源汽车巨头比亚迪在日本市场实现了令人瞩目的逆袭&#xff0c;全年销量增长62%&#xff0c;达到3870辆。这一成绩不仅打破了日本电动汽车市场的增长僵局&#xff0c;也凸显了比亚迪在全球市场布局中的战略韧性。一、 市场环境&#xff1…

基于DELM深度极限学习机的回归预测MATLAB代码教程——代码清晰、注释详尽、可读取EXCE...

基于DELM深度极限学习机的回归预测MATLAB代码 代码注释清楚。 main为主程序&#xff0c;可以读取EXCEL数据&#xff0c;使用换自己数据集。 很方便&#xff0c;初学者容易上手。最近在折腾回归预测模型&#xff0c;发现DELM&#xff08;深度极限学习机&#xff09;用起来还挺…

三菱Q系列PLC ,QD77MS16走总线控制伺服项目,实际应用的 程序结构清晰明了,通俗易懂...

三菱Q系列PLC ,QD77MS16走总线控制伺服项目&#xff0c;实际应用的 程序结构清晰明了&#xff0c;通俗易懂&#xff0c;8个伺服&#xff0c;PLC程序有完整的注释&#xff0c;有伺服设定参数&#xff0c;三菱触摸屏程序&#xff0c;电气BOM &#xff0c;电气I/O表&#xff0c;完…

AD画PCB通俗解释:什么是PCB封装?

从“画几个焊盘”到量产可靠&#xff1a;深入理解AD中PCB封装的真正意义你有没有遇到过这样的情况——原理图画得一丝不苟&#xff0c;网络连接清清楚楚&#xff0c;结果一导入PCB&#xff0c;元件飞得到处都是&#xff1f;或者更糟&#xff1a;板子打回来后发现某个芯片根本焊…

2026开战:AI眼镜“百镜大战”打响,国内厂商领衔掀起“神仙打架”!

2026年1月7日&#xff0c;全球最大的消费电子展CES在美国拉斯维加斯盛大开幕。本届展会中国AI眼镜赛道可谓是“神仙打架”&#xff0c;头部大厂齐聚&#xff0c;抢眼的硬件层出不穷&#xff0c;27家中国AI眼镜、VR、AR厂商联手组团&#xff0c;点燃了现场观众的热情。作为记者&…

手把手玩转昆仑通泰触摸屏与V20变频器USS通讯

昆仑通泰触摸屏与v20变频器uss通讯&#xff0c;控制变频器 1&#xff0c;通过触摸屏与变频器uss通讯 2&#xff0c;通过触摸屏读取变频器电压&#xff0c;电流&#xff0c;频率 3&#xff0c;通过触摸屏设定变频器正反转&#xff0c;设定频率&#xff0c;加速&#xff0c;减速时…

x64dbg调试多线程程序注意事项

用x64dbg调试多线程程序&#xff1f;别让线程“乱跑”毁了你的分析你有没有遇到过这种情况&#xff1a;在x64dbg里设了个断点&#xff0c;结果一运行&#xff0c;程序频繁中断——不是你想调试的那个线程触发的&#xff0c;而是某个后台心跳线程、日志刷新线程或者GUI重绘线程不…

【Linux】PVE系统创建规范的VM模版

基于 Ubuntu Cloud Image 在 Proxmox VE 上创建一个 可用于制作模板的虚拟机。 注意&#xff01;如果未将 local-lvm 和 local 分区合并&#xff0c; --scsi0 参数的值应该是 local-lvm Proxmox VE Linux 模板创建规范&#xff08;Ubuntu 22.04 Cloud Image 示例&#xff09; 1…

H5U的一个比较完整的程序框架. PLC还是性价比挺高,特别是对于伺服的总线。 主打的伺服控制...

H5U的一个比较完整的程序框架. PLC还是性价比挺高&#xff0c;特别是对于伺服的总线。 主打的伺服控制是ETHERCAT总线 程序写的条理分明&#xff0c;清晰易懂&#xff0c;注释清楚&#xff0c;对于初次使用汇川的总线控制有很好的参考价值&#xff0c;。 气缸的控制宝库伸出、…

基于SpringBoot的旅游出行指南系统(源码+lw+部署文档+讲解等)

课题介绍本课题聚焦旅游出行场景下精准指南服务与信息整合需求&#xff0c;设计并实现一套基于Spring Boot框架的旅游出行指南系统&#xff0c;旨在破解传统旅游出行中攻略信息分散、目的地信息不对称、行程规划低效、特色资源难挖掘等痛点问题&#xff0c;精准匹配游客便捷获取…

机器学习中的逻辑回归

什么是逻辑回归&#xff1f;想象一下&#xff0c;你在玩一个游戏&#xff1a;根据一些线索&#xff0c;猜一个人是“猫派”还是“狗派”。机器学习里的逻辑回归&#xff08;Logistic Regression&#xff09;就是这样一个“猜分类”的算法。它不是用来预测连续的数字&#xff08…

Elasticsearch JVM堆内存使用图解说明

Elasticsearch JVM堆内存使用图解说明 一次查询背后的“内存战争” 你有没有遇到过这样的场景&#xff1a;集群刚上线时响应飞快&#xff0c;但随着数据量增长&#xff0c;查询延迟逐渐升高&#xff0c;偶尔还出现节点失联&#xff1f;监控图表上&#xff0c;JVM堆内存使用率…

一文说清AUTOSAR架构结构:核心要点全梳理

深入AUTOSAR架构&#xff1a;从分层设计到工程落地的全链路解析 汽车电子系统正在经历一场静默却深刻的变革。十年前&#xff0c;一辆车的ECU&#xff08;电子控制单元&#xff09;数量不过十几个&#xff1b;如今&#xff0c;高端车型的ECU已超过100个&#xff0c;软件代码量逼…

基于SpringBoot的旅游分享点评网系统(源码+lw+部署文档+讲解等)

课题介绍本课题聚焦旅游场景下用户分享互动与真实点评需求&#xff0c;设计并实现一套基于Spring Boot框架的旅游分享点评网系统&#xff0c;旨在破解传统旅游信息获取中真实体验缺失、用户互动不足、优质攻略传播不畅、点评信息分散等痛点问题&#xff0c;精准匹配游客获取真实…