代码地址
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 <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"
/* ----------------------- 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 <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"
/* ----------------------- 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 <wolti@sil.at>
*
* 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. 运行
好像带连续读取保护,这个我还没看,连续读取会失败
答:由于主频过低,导致使用中断的方式接收数据时,让处理时间稍微耗时,导致下一次数据露采,所以会有通讯失败的情况。
解决办法是提高主频,或者降低波特率。
主站
源码地址: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
发送任务每一秒钟发送一次测试数据