代码地址
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 定时器配置

为了实现一个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
文件中有两处断言需要调整,我不清楚为什么,但是我的不修改会进入断言


E:\STM32CubeIDE\Workspace\FreeMODBUSRTUSlave\modbus\port\portserial.c
代码如下:
/*
* FreeModbus Libary: BARE Port
* Copyright (C) 2006 Christian Walter <[email protected]>
*
* 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 -----------------------------*/
void
vMBPortSerialEnable( 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);
}
}
BOOL
xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{
MX_USART1_UART_Init();
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. */
USART1->DR = 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 = (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_FLAG(&huart1, UART_FLAG_RXNE))
{
__HAL_UART_CLEAR_FLAG(&huart1, UART_FLAG_RXNE);
prvvUARTRxISR();
// __HAL_UART_ENABLE_IT(&huart1, UART_IT_RXNE);
}
if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_TXE))
{
__HAL_UART_CLEAR_FLAG(&huart1, UART_FLAG_TXE);
prvvUARTTxReadyISR();
}
// HAL_UART_IRQHandler(&huart1);
}
E:\STM32CubeIDE\Workspace\FreeMODBUSRTUSlave\modbus\port\porttimer.c
代码如下:
/*
* FreeModbus Libary: BARE Port
* Copyright (C) 2006 Christian Walter <[email protected]>
*
* 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();
}
}
BOOL
xMBPortTimersInit( USHORT usTim1Timerout50us )
{
MX_TIM4_Init();
__HAL_TIM_CLEAR_FLAG(&htim4, TIM_FLAG_UPDATE);
__HAL_TIM_ENABLE_IT(&htim4, TIM_IT_UPDATE);
return TRUE;
}
inline void
vMBPortTimersEnable( )
{
/* Enable the timer with the timeout passed to xMBPortTimersInit( ) */
__HAL_TIM_SET_COUNTER(&htim4, 0);
__HAL_TIM_ENABLE(&htim4);
}
inline void
vMBPortTimersDisable( )
{
/* 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 <[email protected]>
*
* 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 10
uint16_t REG_INPUT_BUF[REG_INPUT_SIZE];
// 十路保持寄存器
#define REG_HOLD_SIZE 10
uint16_t REG_HOLD_BUF[REG_HOLD_SIZE];
// 十路线圈
#define REG_COILS_SIZE 10
uint8_t REG_COILS_BUF[REG_COILS_SIZE] = {1, 1, 1, 1, 0, 0, 0, 0, 1, 1};
// 十路离散量
#define REG_DISC_SIZE 10
uint8_t REG_DISC_BUF[REG_DISC_SIZE] = {1,1,1,1,0,0,0,0,1,1};
/* ----------------------- Start implementation -----------------------------*/
/// CMD4命令处理回调函数
eMBErrorCode
eMBRegInputCB( 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_INPUT_SIZE; usRegIndex++) {
REG_INPUT_BUF[usRegIndex]++;
}
return MB_ENOERR;
}
/// CMD6、3、16命令处理回调函数
eMBErrorCode
eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs,
eMBRegisterMode eMode )
{
USHORT usRegIndex = usAddress - 1;
// 非法检测
if ((usRegIndex + usNRegs) > REG_HOLD_SIZE) {
return MB_ENOREG;
}
// 写寄存器
if (eMode == MB_REG_WRITE) {
while (usNRegs > 0) {
REG_HOLD_BUF[usRegIndex] = (pucRegBuffer[0] << 8) | pucRegBuffer[1];
pucRegBuffer += 2;
usRegIndex++;
usNRegs--;
}
}
// 读寄存器
else {
while (usNRegs > 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命令处理回调函数
eMBErrorCode
eMBRegCoilsCB( 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 < 8) {
REG_COILS_BUF[usRegIndex++] = (ucState >> 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 << ucBits);
}
usNCoils--;
usRegIndex++;
ucBits++;
}
*pucRegBuffer++ = ucState;
ucLoops--;
}
}
return MB_ENOERR;
}
/// CMD2命令处理回调函数
eMBErrorCode
eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
{
USHORT usRegIndex = usAddress - 1;
UCHAR ucBits = 0;
UCHAR ucState = 0;
UCHAR ucLoops = 0;
// 非法检测
if ((usRegIndex + usNDiscrete) > 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
发送任务每一秒钟发送一次测试数据

4. 运行
