STM32 MODBUS-RTU主从站库移植_stm32 modbus rtu
代码地址
STM32MODBUSRTU: stm32上的modbus工程
从站
FreeModbus是一个开源的Modbus通信协议栈实现。它允许开发者在各种平台上轻松地实现Modbus通信功能,包括串口和以太网。FreeMODBUS提供了用于从设备和主站通信的功能,支持Modbus RTU和Modbus TCP协议。在工业控制和自动化领域广泛应用。
FreeModBus可通过官方网站下载:FreeMODBUS
参考文章:FreeModbus RTU 从机Hal库裸机移植避坑指南 - Atul-8 - 博客园
1. STM32CubeMX 配置流程
我假设你已经学会使用stm32cubeMX点灯了;
1.1下载模式配置
1.2 定时器配置
上图为修正图,需要配置为向下计数,ARR写入的值才是计数值。
为了实现一个1750us的超时计时定时器,其中计算过程如下,使用内部时钟8M,预分配399+1,及400/8m=50us,计数周期为34+1,也就是35*50us=1750us
1.3 串口配置
1.4 中断配置
关闭自动生成中断函数,因为需要在freemodbus源码里添加这两个函数。
2.库文件导入
2.1 .c文件汇总
2.2 .h文件汇总
2.3 demo文件选择
3. 移植流程
ok 完成上述步骤后, 你就可以开始正式的移植工作了:
主要需要移植的地方为: portserial.c && porttimer.c && demo.c
E:\\STM32CubeIDE\\Workspace\\FreeMODBUSRTUSlave\\modbus\\include\\mbconfig.h
文件中将ASCII关闭
E:\\STM32CubeIDE\\Workspace\\FreeMODBUSRTUSlave\\modbus\\rtu\\mbrtu.c
文件中有两处断言需要调整,我不清楚为什么,但是我的不修改会进入断言
这里的问题找到原因了,是因为TXE标志无法被清楚,当第一次收到接收中断后会进入中断函数,中断函数中判断了TXE标志,导致也进入了发送流程,导致eRcvState被置位。这个问题可以通过修改中断函数避免,后面会展示中断函数。
E:\\STM32CubeIDE\\Workspace\\FreeMODBUSRTUSlave\\modbus\\port\\portserial.c
代码如下:
/* * FreeModbus Libary: BARE Port * Copyright (C) 2006 Christian Walter * * 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\"/* ----------------------- Modbus includes ----------------------------------*/#include \"mb.h\"#include \"mbport.h\"#include \"main.h\"#include \"usart.h\"/* ----------------------- static functions ---------------------------------*/static void prvvUARTTxReadyISR( void );static void prvvUARTRxISR( void );extern UART_HandleTypeDef huart1;/* ----------------------- Start implementation -----------------------------*/voidvMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable ){ /* If xRXEnable enable serial receive interrupts. If xTxENable enable * transmitter empty interrupts. */ if (xRxEnable) { __HAL_UART_ENABLE_IT(&huart1, UART_IT_RXNE); } else { __HAL_UART_DISABLE_IT(&huart1, UART_IT_RXNE); } if (xTxEnable) { __HAL_UART_ENABLE_IT(&huart1, UART_IT_TXE); } else { __HAL_UART_DISABLE_IT(&huart1, UART_IT_TXE); }}BOOLxMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity ){ MX_USART1_UART_Init();return TRUE;}BOOLxMBPortSerialPutByte( CHAR ucByte ){ /* Put a byte in the UARTs transmit buffer. This function is called * by the protocol stack if pxMBFrameCBTransmitterEmpty( ) has been * called. */ USART1->DR = ucByte; return TRUE;}BOOLxMBPortSerialGetByte( CHAR * pucByte ){ /* Return the byte in the UARTs receive buffer. This function is called * by the protocol stack after pxMBFrameCBByteReceived( ) has been called. */ *pucByte = (USART1->DR & (uint16_t)0x00FF); 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 USART1_IRQHandler(void){ if((__HAL_UART_GET_IT_SOURCE(&huart1, UART_IT_RXNE)) && (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_RXNE))) { __HAL_UART_CLEAR_FLAG(&huart1, UART_FLAG_RXNE); prvvUARTRxISR(); } if((__HAL_UART_GET_IT_SOURCE(&huart1, UART_IT_TXE)) && (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_TXE))) { __HAL_UART_CLEAR_FLAG(&huart1, UART_FLAG_TXE); prvvUARTTxReadyISR(); }}
在USART1_IRQHandler函数中,不仅仅要判断标志位,还要判断中断位,因为TXE标志始终为1,导致每次进入中断都会执行TXE逻辑,所以增加了中断位的判断。
E:\\STM32CubeIDE\\Workspace\\FreeMODBUSRTUSlave\\modbus\\port\\porttimer.c
代码如下:
/* * FreeModbus Libary: BARE Port * Copyright (C) 2006 Christian Walter * * 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\"/* ----------------------- Modbus includes ----------------------------------*/#include \"mb.h\"#include \"mbport.h\"#include \"main.h\"/* ----------------------- static functions ---------------------------------*/static void prvvTIMERExpiredISR( void );extern TIM_HandleTypeDef htim4;/* ----------------------- Start implementation -----------------------------*/static void MX_TIM4_Init(void){ TIM_ClockConfigTypeDef sClockSourceConfig = {0}; TIM_MasterConfigTypeDef sMasterConfig = {0}; htim4.Instance = TIM4; htim4.Init.Prescaler = 399; htim4.Init.CounterMode = TIM_COUNTERMODE_UP; htim4.Init.Period = 34; htim4.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; htim4.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; if (HAL_TIM_Base_Init(&htim4) != HAL_OK) { Error_Handler(); } sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL; if (HAL_TIM_ConfigClockSource(&htim4, &sClockSourceConfig) != HAL_OK) { Error_Handler(); } sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; if (HAL_TIMEx_MasterConfigSynchronization(&htim4, &sMasterConfig) != HAL_OK) { Error_Handler(); }}BOOLxMBPortTimersInit( USHORT usTim1Timerout50us ){ MX_TIM4_Init();__HAL_TIM_CLEAR_FLAG(&htim4, TIM_FLAG_UPDATE); __HAL_TIM_ENABLE_IT(&htim4, TIM_IT_UPDATE); return TRUE;}inline voidvMBPortTimersEnable( ){ /* Enable the timer with the timeout passed to xMBPortTimersInit( ) */ __HAL_TIM_SET_COUNTER(&htim4, 0); __HAL_TIM_ENABLE(&htim4);}inline voidvMBPortTimersDisable( ){ /* Disable any pending timers. */ __HAL_TIM_DISABLE(&htim4);}/* 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 TIM4_IRQHandler(void){ if(__HAL_TIM_GET_FLAG(&htim4, TIM_FLAG_UPDATE)) { __HAL_TIM_CLEAR_FLAG(&htim4, TIM_FLAG_UPDATE); prvvTIMERExpiredISR(); }}
E:\\STM32CubeIDE\\Workspace\\FreeMODBUSRTUSlave\\modbus\\demo.c
代码如下:这里是主要modbus栈需要维护的数据
/* * FreeModbus Libary: BARE Demo Application * Copyright (C) 2006 Christian Walter * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; 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\"/* ----------------------- Defines ------------------------------------------*/#define REG_INPUT_START 1000#define REG_INPUT_NREGS 4/* ----------------------- Static variables ---------------------------------*/static USHORT usRegInputStart = REG_INPUT_START;static USHORT usRegInputBuf[REG_INPUT_NREGS];// 十路输入寄存器#define REG_INPUT_SIZE 10uint16_t REG_INPUT_BUF[REG_INPUT_SIZE];// 十路保持寄存器#define REG_HOLD_SIZE 10uint16_t REG_HOLD_BUF[REG_HOLD_SIZE];// 十路线圈#define REG_COILS_SIZE 10uint8_t REG_COILS_BUF[REG_COILS_SIZE] = {1, 1, 1, 1, 0, 0, 0, 0, 1, 1};// 十路离散量#define REG_DISC_SIZE 10uint8_t REG_DISC_BUF[REG_DISC_SIZE] = {1,1,1,1,0,0,0,0,1,1};/* ----------------------- Start implementation -----------------------------*//// CMD4命令处理回调函数eMBErrorCodeeMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ){ USHORT usRegIndex = usAddress - 1; // 非法检测 if ((usRegIndex + usNRegs) > REG_INPUT_SIZE) { return MB_ENOREG; } // 循环读取 while (usNRegs > 0) { *pucRegBuffer++ = (unsigned char)(REG_INPUT_BUF[usRegIndex] >> 8); *pucRegBuffer++ = (unsigned char)(REG_INPUT_BUF[usRegIndex] & 0xFF); usRegIndex++; usNRegs--; } // 模拟输入寄存器被改变 for (usRegIndex = 0; usRegIndex REG_HOLD_SIZE) { return MB_ENOREG; } // 写寄存器 if (eMode == MB_REG_WRITE) { while (usNRegs > 0) { REG_HOLD_BUF[usRegIndex] = (pucRegBuffer[0] < 0) { *pucRegBuffer++ = (unsigned char)(REG_HOLD_BUF[usRegIndex] >> 8); *pucRegBuffer++ = (unsigned char)(REG_HOLD_BUF[usRegIndex] & 0xFF); usRegIndex++; usNRegs--; } } return MB_ENOERR;}/// CMD1、5、15命令处理回调函数eMBErrorCodeeMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils, eMBRegisterMode eMode ){ USHORT usRegIndex = usAddress - 1; UCHAR ucBits = 0; UCHAR ucState = 0; UCHAR ucLoops = 0; // 非法检测 if ((usRegIndex + usNCoils) > REG_COILS_SIZE) { return MB_ENOREG; } if (eMode == MB_REG_WRITE) { ucLoops = (usNCoils - 1) / 8 + 1; while (ucLoops != 0) { ucState = *pucRegBuffer++; ucBits = 0; while (usNCoils != 0 && ucBits > ucBits) & 0X01; usNCoils--; ucBits++; } ucLoops--; } } else { ucLoops = (usNCoils - 1) / 8 + 1; while (ucLoops != 0) { ucState = 0; ucBits = 0; while (usNCoils != 0 && ucBits < 8) { if (REG_COILS_BUF[usRegIndex]) { ucState |= (1 < REG_DISC_SIZE) { return MB_ENOREG; } ucLoops = (usNDiscrete - 1) / 8 + 1; while (ucLoops != 0) { ucState = 0; ucBits = 0; while (usNDiscrete != 0 && ucBits < 8) { if (REG_DISC_BUF[usRegIndex]) { ucState |= (1 << ucBits); } usNDiscrete--; usRegIndex++; ucBits++; } *pucRegBuffer++ = ucState; ucLoops--; } // 模拟离散量输入被改变 for (usRegIndex = 0; usRegIndex < REG_DISC_SIZE; usRegIndex++) { REG_DISC_BUF[usRegIndex] = !REG_DISC_BUF[usRegIndex]; } return MB_ENOERR;}
E:\\STM32CubeIDE\\Workspace\\FreeMODBUSRTUSlave\\Core\\Src\\freertos.c
如下修改:
/* Private includes ----------------------------------------------------------*//* USER CODE BEGIN Includes */#include \"mb.h\"#include \"mbport.h\"/* USER CODE END Includes */
/* USER CODE BEGIN Header_StartDefaultTask *//** * @brief Function implementing the defaultTask thread. * @param argument: Not used * @retval None *//* USER CODE END Header_StartDefaultTask */void StartDefaultTask(void *argument){ /* USER CODE BEGIN StartDefaultTask */ /* Infinite loop */ eMBInit(MB_RTU, 0x01, 0, 115200, MB_PAR_NONE); eMBEnable(); for(;;) { osDelay(1); eMBPoll(); } /* USER CODE END StartDefaultTask */}
4. 运行
好像带连续读取保护,这个我还没看,连续读取会失败
答:由于主频过低,导致使用中断的方式接收数据时,让处理时间稍微耗时,导致下一次数据露采,所以会有通讯失败的情况。
解决办法是提高主频,或者降低波特率。
主站
开源一套MODBUS主机代码(带讲解分析) – 电子创客营
源码地址:https://github.com/Derrick45/modbus-host
1. STM32CubeMX 配置流程
1.1配置系统模式
1.2配置定时器3
这个需要和port中代码保持一致
因为系统主频配置为64MHz
1.3配置串口1
1.4打开串口1和tim3中断
1.5 取消默认生成中断函数
因为后面代码需要自己实现
1.6 freeRTOS创建两个任务
一个默认任务,处理poll函数,一个任务做发送
1.7 配置系统时钟为64M
1.8 生成独立文件
2. 库文件导入
把源码文件放到modbus文件夹中
环境配置
3.移植流程
MODBUSRTUMaster\\Core\\Src\\freertos.c
添加头文件
任务逻辑实现,默认任务处理poll
发送任务每一秒钟发送一次测试数据