第二篇、CubeMX | FreeModbus 从机 DMA接收串口数据(无OS,裸机移植)
文章目录
一、写在前面
1.1 文章回顾
该文章对【第一篇、基于CubeMX完成FreeModbus从机移植(无OS,裸机移植)】的所提出的代码弊端进行优化的方法实现
1.2 FreeModbus从机 数据接收优化版例程下载
1. 芯片:STM32F103C8T6
2. 串口引脚:USART1_TX(PB6) 、USART1_RX(PB7)
3. RS485控制引脚:DIR1(PB8)
4. 采用DMA+接收空闲中断的串口数据的接收(中断量最少、效率高)
5. 使用STM32CubeMX生成的Hal库程序
6. 未使用RTOS
1.3 更新
博主精力有限,以下更新可能没有添加到下载资源中,若下载了资源,请自行对照修改!!!
1.3.1 使用DMA接受广播帧时,导致MODBUS异常
问题:由于modbus接收到广播帧以后,不需要回复任何数据,由于DMA状态重置在发送状态进行,因此没有重置DMA,导致异常
解决办法:在eMBErrorCode eMBPoll( void )的EV_EXECUTE状态中增加串口重启函数
BOOL xMBPortSerialRestart(void)
{
HAL_UART_DMAStop(&SLAVE_PORT);
vMBPortSerialEnable(TRUE, FALSE);
return TRUE;
}
1.3.2 使用DMA接收到异常数据,如不满足modbus任何条件的数据,导致MDOBUS异常
问题: 使用DMA接收到异常数据,如不满足modbus任何条件的数据,导致MDOBUS异常,这个原因是由于eMBErrorCode eMBPoll( void )没有处理帧异常时的措施
解决办法:在eMBErrorCode eMBPoll( void )的EV_FRAME_RECEIVED状态中增加串口重启函数
eMBErrorCode
eMBPoll( void )
{
static UCHAR *ucMBFrame;
static UCHAR ucRcvAddress;
static UCHAR ucFunctionCode;
static USHORT usLength;
static eMBException eException;
int i;
eMBErrorCode eStatus = MB_ENOERR;
eMBEventType eEvent;
/* Check if the protocol stack is ready. */
if( eMBState != STATE_ENABLED )
{
return MB_EILLSTATE;
}
/* Check if there is a event available. If not return control to caller.
* Otherwise we will handle the event. */
if( xMBPortEventGet( &eEvent ) == TRUE )
{
switch ( eEvent )
{
case EV_READY:
break;
case EV_FRAME_RECEIVED:
eStatus = peMBFrameReceiveCur( &ucRcvAddress, &ucMBFrame, &usLength );
if( eStatus == MB_ENOERR )
{
/* Check if the frame is for us. If not ignore the frame. */
if( ( ucRcvAddress == ucMBAddress ) || ( ucRcvAddress == MB_ADDRESS_BROADCAST ) )
{
( void )xMBPortEventPost( EV_EXECUTE );
}
}
if(eStatus == MB_EIO)
{
printf("接收到错误帧\n");
xMBPortSerialRestart();
}
break;
case EV_EXECUTE:
ucFunctionCode = ucMBFrame[MB_PDU_FUNC_OFF];
eException = MB_EX_ILLEGAL_FUNCTION;
for( i = 0; i < MB_FUNC_HANDLERS_MAX; i++ )
{
/* No more function handlers registered. Abort. */
if( xFuncHandlers[i].ucFunctionCode == 0 )
{
break;
}
else if( xFuncHandlers[i].ucFunctionCode == ucFunctionCode )
{
eException = xFuncHandlers[i].pxHandler( ucMBFrame, &usLength );
break;
}
}
/* If the request was not sent to the broadcast address we
* return a reply. */
if( ucRcvAddress != MB_ADDRESS_BROADCAST )
{
if( eException != MB_EX_NONE )
{
/* An exception occured. Build an error frame. */
usLength = 0;
ucMBFrame[usLength++] = ( UCHAR )( ucFunctionCode | MB_FUNC_ERROR );
ucMBFrame[usLength++] = eException;
}
if( ( eMBCurrentMode == MB_ASCII ) && MB_ASCII_TIMEOUT_WAIT_BEFORE_SEND_MS )
{
vMBPortTimersDelay( MB_ASCII_TIMEOUT_WAIT_BEFORE_SEND_MS );
}
eStatus = peMBFrameSendCur( ucMBAddress, ucMBFrame, usLength );
}else
{
printf("接收广播帧\n");
xMBPortSerialRestart();
}
break;
case EV_FRAME_SENT:
break;
}
}
return MB_ENOERR;
}
1.3.3 (20260513更新)使用xMBPortSerialPutByte( CHAR ucByte )进行数据发送时,发生断帧现象
现象描述:当主站对T3.5字符间隔要求严格时,由于原发送方式是单字符中断发送,有时候可能上一个字符与下一个字符的发送间隔会超过T3.5,导致主机认为从机帧发送接收。从而引发失败。
解决办法:在xMBRTUTransmitFSM中修改发送方式为HAL_UART_Transmit
BOOL
xMBRTUTransmitFSM( void )
{
BOOL xNeedPoll = FALSE; // 标记是否需要轮询
switch ( eSndState )
{
/* 如果发送器处于空闲状态,我们不应该收到发送器事件。 */
case STATE_TX_IDLE:
/* 启用接收器/禁用发送器。 */
vMBPortSerialEnable( TRUE, FALSE ); // 使能串口接收,禁用串口发送
break;
case STATE_TX_XMIT:
/* 检查我们是否完成发送。 */
ENTER_CRITICAL_SECTION( ); // 进入临界区
HAL_UART_Transmit(&huart1,pucSndBufferCur,usSndBufferCount,0xff);
usSndBufferCount=0;
EXIT_CRITICAL_SECTION( ); // 退出临界区
xNeedPoll = xMBPortEventPost( EV_FRAME_SENT ); // 发送帧发送完成事件
vMBPortSerialEnable( TRUE, FALSE ); // 使能串口接收,禁用串口发送
eSndState = STATE_TX_IDLE; // 设置发送器状态为空闲状态
break;
}
return xNeedPoll; // 返回是否需要轮询的标记
}
二、生成CubeMX工程
在【第一篇、基于CubeMX完成FreeModbus从机移植(无OS,裸机移植)】工程基础上,进行以下改动
1、使能DMA

2、取消定时器

三、FreeModbus文件修改
1、portserial.c
1、增加DMA相关代码
2、引入ucRTUBuf和usRcvBufferPos变量
3、新增本机地址变量,用于触发接收空闲中断时,判断地址是否为本机地址,如果不是,直接忽略,
/*
* 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"
/* ----------------------- serial port --------------------------------------*/
extern UART_HandleTypeDef huart1;
extern DMA_HandleTypeDef hdma_usart1_rx; // 新增 DMA 句柄
extern DMA_HandleTypeDef hdma_usart1_tx; // 新增发送 DMA 句柄
#define SLAVE_PORT huart1
#define rs485_dir_recv() HAL_GPIO_WritePin(DIR1_GPIO_Port,DIR1_Pin,GPIO_PIN_RESET)
#define rs485_dir_send() HAL_GPIO_WritePin(DIR1_GPIO_Port,DIR1_Pin,GPIO_PIN_SET)
volatile uint8_t ucLocalSlaveAddress = 1; //本机modbus从地址
#define MB_SER_PDU_SIZE_MAX 256 /*!< Modbus RTU 帧的最大尺寸 */
extern volatile UCHAR ucRTUBuf[MB_SER_PDU_SIZE_MAX]; // 用于存储接收的 Modbus RTU 数据的缓冲区
extern volatile USHORT usRcvBufferPos; // 接收缓冲区中已存储数据的位置
/* ----------------------- static functions ---------------------------------*/
static void prvvUARTTxReadyISR( void );
/* ----------------------- Start implementation -----------------------------*/
void
vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
{
/* If xRXEnable enable serial receive interrupts. If xTxENable enable
* transmitter empty interrupts.
*/
if (xRxEnable)
{
while(__HAL_UART_GET_FLAG(&SLAVE_PORT,UART_FLAG_TC)!=SET) //等待发送结束
{
/* code */
}
rs485_dir_recv();
if (HAL_UART_Receive_DMA(&SLAVE_PORT, (uint8_t *)ucRTUBuf, MB_SER_PDU_SIZE_MAX) != HAL_OK)
{
// 处理错误
}
__HAL_UART_ENABLE_IT(&SLAVE_PORT, UART_IT_IDLE); // 使能空闲中断
}
else
{
rs485_dir_send();
HAL_UART_DMAStop(&SLAVE_PORT); // 停止 DMA 接收
__HAL_UART_DISABLE_IT(&SLAVE_PORT, UART_IT_IDLE); // 禁止空闲中断
}
if (xTxEnable)
{
__HAL_UART_ENABLE_IT(&SLAVE_PORT, UART_IT_TXE); // 使能发送空中断
}
else
{
__HAL_UART_DISABLE_IT(&SLAVE_PORT, UART_IT_TXE); // 禁止发送空中断
}
}
BOOL
xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{
SLAVE_PORT.Instance = USART1;
SLAVE_PORT.Init.BaudRate = ulBaudRate;
SLAVE_PORT.Init.StopBits = UART_STOPBITS_1;
SLAVE_PORT.Init.Mode = UART_MODE_TX_RX;
SLAVE_PORT.Init.HwFlowCtl = UART_HWCONTROL_NONE;
SLAVE_PORT.Init.OverSampling = UART_OVERSAMPLING_16;
switch(eParity)
{
// 奇校验
case MB_PAR_ODD:
SLAVE_PORT.Init.Parity = UART_PARITY_ODD;
SLAVE_PORT.Init.WordLength = UART_WORDLENGTH_9B; // 带奇偶校验数据位为9bits
break;
// 偶校验
case MB_PAR_EVEN:
SLAVE_PORT.Init.Parity = UART_PARITY_EVEN;
SLAVE_PORT.Init.WordLength = UART_WORDLENGTH_9B; // 带奇偶校验数据位为9bits
break;
// 无校验
default:
SLAVE_PORT.Init.Parity = UART_PARITY_NONE;
SLAVE_PORT.Init.WordLength = UART_WORDLENGTH_8B; // 无奇偶校验数据位为8bits
break;
}
// 初始化 DMA
SLAVE_PORT.hdmarx = &hdma_usart1_rx;
hdma_usart1_rx.Instance = DMA1_Channel5; // 根据实际情况修改
hdma_usart1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_usart1_rx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_usart1_rx.Init.MemInc = DMA_MINC_ENABLE;
hdma_usart1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_usart1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_usart1_rx.Init.Mode = DMA_NORMAL;
hdma_usart1_rx.Init.Priority = DMA_PRIORITY_LOW;
if (HAL_DMA_Init(&hdma_usart1_rx) != HAL_OK)
{
return FALSE;
}
__HAL_LINKDMA(&SLAVE_PORT, hdmarx, hdma_usart1_rx);
return HAL_UART_Init(&SLAVE_PORT) == HAL_OK ? TRUE : FALSE;
}
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;
}
/* 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( );
}
/*
* Add your serial port interrupt handler
*
*/
#include <string.h> // 包含 memset 函数所在的头文件
// 定义结构体来记录不同类型中断的次数
typedef struct {
uint32_t idle_irq_count; // 空闲中断次数
uint32_t txe_irq_count; // 发送空中断次数
uint32_t rxne_irq_count; // 接收缓冲区非空中断次数
uint32_t error_irq_count; // 错误中断次数
} UART1_IRQ_Counters;
// 全局变量,用于存储中断计数
UART1_IRQ_Counters uart1_irq_counters = {0, 0, 0, 0};
// 原有的中断处理函数,添加中断计数逻辑
void USART1_IRQHandler(void)
{
if (__HAL_UART_GET_FLAG(&SLAVE_PORT, UART_FLAG_IDLE)) // 空闲中断标记被置位
{
uart1_irq_counters.idle_irq_count++; // 空闲中断次数加 1
__HAL_UART_CLEAR_IDLEFLAG(&SLAVE_PORT); // 清除空闲中断标记
usRcvBufferPos = MB_SER_PDU_SIZE_MAX - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx); // 计算接收到的数据长度
// 将接收到的从机地址存储到指定位置
if (ucRTUBuf[0] == ucLocalSlaveAddress || ucRTUBuf[0] == MB_ADDRESS_BROADCAST)
{
xMBPortEventPost(EV_FRAME_RECEIVED); // 通知协议栈有新帧接收
}
else
{
// 停止 DMA 接收
HAL_UART_DMAStop(&SLAVE_PORT);
vMBPortSerialEnable(TRUE, FALSE);
}
}
else if(__HAL_UART_GET_FLAG(&SLAVE_PORT, UART_FLAG_TXE)) // 发送为空中断标记被置位
{
uart1_irq_counters.txe_irq_count++; // 发送空中断次数加 1
__HAL_UART_CLEAR_FLAG(&SLAVE_PORT, UART_FLAG_TXE); // 清除中断标记
prvvUARTTxReadyISR(); // 通知modbus数据可以发送
}
else if(__HAL_UART_GET_FLAG(&SLAVE_PORT, UART_FLAG_RXNE)) // 接收缓冲区非空中断标记被置位
{
uart1_irq_counters.rxne_irq_count++; // 接收缓冲区非空中断次数加 1
__HAL_UART_CLEAR_FLAG(&SLAVE_PORT, UART_FLAG_RXNE); // 清除中断标记
// 若有需要可添加接收数据处理逻辑
}
else if(__HAL_UART_GET_FLAG(&SLAVE_PORT, UART_FLAG_ORE) || // 溢出错误
__HAL_UART_GET_FLAG(&SLAVE_PORT, UART_FLAG_NE) || // 噪声错误
__HAL_UART_GET_FLAG(&SLAVE_PORT, UART_FLAG_FE) || // 帧错误
__HAL_UART_GET_FLAG(&SLAVE_PORT, UART_FLAG_PE)) // 奇偶校验错误
{
uart1_irq_counters.error_irq_count++; // 错误中断次数加 1
__HAL_UART_CLEAR_FLAG(&SLAVE_PORT, UART_FLAG_ORE | UART_FLAG_NE | UART_FLAG_FE | UART_FLAG_PE); // 清除错误中断标记
// 若有需要可添加错误处理逻辑
}
}
2、mbrtu.c
删除 eMBRTUStart、eMBRTUStop 和 xMBRTUTimerT35Expired 函数中与定时器相关的代码,同时移除 xMBPortTimersInit、vMBPortTimersEnable 和 vMBPortTimersDisable 函数调用。
/*
* FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU.
* Copyright (c) 2006-2018 Christian Walter <cwalter@embedded-solutions.at>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
/* ----------------------- System includes ----------------------------------*/
#include "stdlib.h"
#include "string.h"
/* ----------------------- Platform includes --------------------------------*/
#include "port.h"
/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbrtu.h"
#include "mbframe.h"
#include "mbcrc.h"
#include "mbport.h"
/* ----------------------- Defines ------------------------------------------*/
#define MB_SER_PDU_SIZE_MIN 4 /*!< Modbus RTU 帧的最小尺寸 */
#define MB_SER_PDU_SIZE_MAX 256 /*!< Modbus RTU 帧的最大尺寸 */
#define MB_SER_PDU_SIZE_CRC 2 /*!< PDU 中 CRC 字段的大小 */
#define MB_SER_PDU_ADDR_OFF 0 /*!< 从机地址在串行 PDU 中的偏移量 */
#define MB_SER_PDU_PDU_OFF 1 /*!< Modbus-PDU 在串行 PDU 中的偏移量 */
/* ----------------------- Type definitions ---------------------------------*/
typedef enum
{
STATE_RX_INIT, /*!< 接收器处于初始状态 */
STATE_RX_IDLE, /*!< 接收器处于空闲状态 */
STATE_RX_RCV, /*!< 正在接收帧 */
STATE_RX_ERROR /*!< 如果帧无效 */
} eMBRcvState;
typedef enum
{
STATE_TX_IDLE, /*!< 发送器处于空闲状态 */
STATE_TX_XMIT /*!< 发送器处于传输状态 */
} eMBSndState;
/* ----------------------- Static variables ---------------------------------*/
static volatile eMBSndState eSndState; // 发送器状态,volatile 确保在多线程或中断环境下能正确访问
static volatile eMBRcvState eRcvState; // 接收器状态,volatile 确保在多线程或中断环境下能正确访问
volatile UCHAR ucRTUBuf[MB_SER_PDU_SIZE_MAX]; // 用于存储接收的 Modbus RTU 数据的缓冲区
static volatile UCHAR *pucSndBufferCur; // 指向发送缓冲区当前位置的指针
static volatile USHORT usSndBufferCount; // 发送缓冲区中剩余要发送的字节数
volatile USHORT usRcvBufferPos; // 接收缓冲区中已存储数据的位置
/* ----------------------- Start implementation -----------------------------*/
eMBErrorCode
eMBRTUInit( UCHAR ucSlaveAddress, UCHAR ucPort, ULONG ulBaudRate, eMBParity eParity )
{
eMBErrorCode eStatus = MB_ENOERR;
( void )ucSlaveAddress;
ENTER_CRITICAL_SECTION( );
/* Modbus RTU uses 8 Databits. */
if( xMBPortSerialInit( ucPort, ulBaudRate, 8, eParity ) != TRUE )
{
eStatus = MB_EPORTERR;
}
else
{
}
EXIT_CRITICAL_SECTION( );
return eStatus;
}
void
eMBRTUStart( void )
{
ENTER_CRITICAL_SECTION( ); // 进入临界区
eRcvState = STATE_RX_IDLE; // 设置接收器状态为初始状态
vMBPortSerialEnable( TRUE, FALSE ); // 使能串口接收,禁用串口发送
EXIT_CRITICAL_SECTION( ); // 退出临界区
}
void
eMBRTUStop( void )
{
ENTER_CRITICAL_SECTION( ); // 进入临界区
vMBPortSerialEnable( FALSE, FALSE ); // 禁用串口接收和发送
EXIT_CRITICAL_SECTION( ); // 退出临界区
}
eMBErrorCode
eMBRTUReceive( UCHAR * pucRcvAddress, UCHAR ** pucFrame, USHORT * pusLength )
{
BOOL xFrameReceived = FALSE; // 标记是否接收到有效帧
eMBErrorCode eStatus = MB_ENOERR; // 错误状态码,初始设为无错误
ENTER_CRITICAL_SECTION( ); // 进入临界区
assert( usRcvBufferPos < MB_SER_PDU_SIZE_MAX ); // 断言确保接收缓冲区位置在有效范围内
/* 长度和 CRC 检查 */
if( ( usRcvBufferPos >= MB_SER_PDU_SIZE_MIN )
&& ( usMBCRC16( ( UCHAR * ) ucRTUBuf, usRcvBufferPos ) == 0 ) )
{
/* 保存地址字段。所有帧都传递到上层,
* 是否使用该帧的决策在那里进行。
*/
*pucRcvAddress = ucRTUBuf[MB_SER_PDU_ADDR_OFF]; // 将接收到的从机地址存储到指定位置
/* Modbus-PDU 的总长度是 Modbus 串行线路 PDU 减去
* 地址字段大小和 CRC 校验和。
*/
*pusLength = ( USHORT )( usRcvBufferPos - MB_SER_PDU_PDU_OFF - MB_SER_PDU_SIZE_CRC ); // 计算有效数据长度
/* 将 Modbus PDU 的起始位置返回给调用者。 */
*pucFrame = ( UCHAR * ) & ucRTUBuf[MB_SER_PDU_PDU_OFF]; // 设置指向有效数据的指针
xFrameReceived = TRUE; // 标记接收到有效帧
}
else
{
eStatus = MB_EIO; // 如果帧无效,设置错误状态为输入输出错误
}
EXIT_CRITICAL_SECTION( ); // 退出临界区
return eStatus; // 返回错误状态码
}
eMBErrorCode
eMBRTUSend( UCHAR ucSlaveAddress, const UCHAR * pucFrame, USHORT usLength )
{
eMBErrorCode eStatus = MB_ENOERR; // 错误状态码,初始设为无错误
USHORT usCRC16; // 用于存储 CRC16 校验和
ENTER_CRITICAL_SECTION( ); // 进入临界区
/* 检查接收器是否仍处于空闲状态。如果不是,说明我们处理接收到的帧太慢,
* 并且主机在网络上发送了另一个帧。我们必须中止发送该帧。
*/
if( eRcvState == STATE_RX_IDLE )
{
/* Modbus-PDU 之前的第一个字节是从机地址。 */
pucSndBufferCur = ( UCHAR * ) pucFrame - 1; // 设置发送缓冲区指针指向从机地址位置
usSndBufferCount = 1; // 初始化发送缓冲区计数为 1(从机地址占 1 字节)
/* 现在将 Modbus-PDU 复制到 Modbus 串行线路 PDU 中。 */
pucSndBufferCur[MB_SER_PDU_ADDR_OFF] = ucSlaveAddress; // 将从机地址存储到发送缓冲区
usSndBufferCount += usLength; // 更新发送缓冲区计数为从机地址和有效数据长度之和
/* 计算 Modbus 串行线路 PDU 的 CRC16 校验和。 */
usCRC16 = usMBCRC16( ( UCHAR * ) pucSndBufferCur, usSndBufferCount ); // 计算 CRC16 校验和
ucRTUBuf[usSndBufferCount++] = ( UCHAR )( usCRC16 & 0xFF ); // 将 CRC16 低字节存储到缓冲区
ucRTUBuf[usSndBufferCount++] = ( UCHAR )( usCRC16 >> 8 ); // 将 CRC16 高字节存储到缓冲区
/* 激活发送器。 */
eSndState = STATE_TX_XMIT; // 设置发送器状态为传输状态
vMBPortSerialEnable( FALSE, TRUE ); // 禁用串口接收,使能串口发送
}
else
{
eStatus = MB_EIO; // 如果接收器不处于空闲状态,设置错误状态为输入输出错误
}
EXIT_CRITICAL_SECTION( ); // 退出临界区
return eStatus; // 返回错误状态码
}
BOOL
xMBRTUReceiveFSM( void )
{
BOOL xTaskNeedSwitch = FALSE; // 标记是否需要任务切换
assert( eSndState == STATE_TX_IDLE ); // 断言确保发送器处于空闲状态
switch ( eRcvState )
{
case STATE_RX_INIT:
eRcvState = STATE_RX_IDLE; // 直接切换到空闲状态
break;
/* 在错误状态下,我们等待直到损坏的帧中的所有字符都被传输。 */
case STATE_RX_ERROR:
eRcvState = STATE_RX_IDLE; // 直接切换到空闲状态
break;
case STATE_RX_IDLE:
// 无需处理,等待空闲中断触发接收完成事件
break;
case STATE_RX_RCV:
// 无需处理,等待空闲中断触发接收完成事件
break;
}
return xTaskNeedSwitch; // 返回是否需要任务切换的标记
}
BOOL
xMBRTUTransmitFSM( void )
{
BOOL xNeedPoll = FALSE; // 标记是否需要轮询
assert( eRcvState == STATE_RX_IDLE ); // 断言确保接收器处于空闲状态
switch ( eSndState )
{
/* 如果发送器处于空闲状态,我们不应该收到发送器事件。 */
case STATE_TX_IDLE:
/* 启用接收器/禁用发送器。 */
vMBPortSerialEnable( TRUE, FALSE ); // 使能串口接收,禁用串口发送
break;
case STATE_TX_XMIT:
/* 检查我们是否完成发送。 */
if( usSndBufferCount != 0 )
{
xMBPortSerialPutByte( ( CHAR )*pucSndBufferCur ); // 发送缓冲区中的当前字节
pucSndBufferCur++; /* 发送缓冲区中的下一个字节。 */
usSndBufferCount--; // 减少待发送字节数
}
else
{
xNeedPoll = xMBPortEventPost( EV_FRAME_SENT ); // 发送帧发送完成事件
/* 禁用发送器。这可以防止另一个发送缓冲区为空中断。 */
vMBPortSerialEnable( TRUE, FALSE ); // 使能串口接收,禁用串口发送
eSndState = STATE_TX_IDLE; // 设置发送器状态为空闲状态
}
break;
}
return xNeedPoll; // 返回是否需要轮询的标记
}
3、porttimer.c
从工程中删除该代码。或取消编译
四、测试验证
uart1_irq_counters结构体记录了系统进入中断的次数,可以看到每收到一帧数据,系统进入一次串口空闲中断,大大减少了中断次数。但发送空闲中断进入次数较多,后续将继续优化
五、代码弊端
程序的发送严重依赖TXE中断,这种办法依旧会产生较多中断次数,如果能使用TC中断和DMA,则仅需进入一次中断即可。最终可以达到总线上收发一帧数据最多引起两次中断的效果,是最高效的串口收发方式。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐


所有评论(0)