调试key LCD rs485都正常运行,修复了bug,但是DMA接收的问题没有解决

This commit is contained in:
2026-01-26 15:48:15 +08:00
parent 2b93c00a16
commit f9f0016f69
14 changed files with 1600 additions and 3523 deletions

View File

@@ -12,6 +12,7 @@
******************************************************************************/
#include "rs485.h"
#include "160160D.h"
/* ============================================================================
* USART 与引脚宏定义
@@ -37,7 +38,6 @@
#define RS485_RX_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)
#define BAUDRATE (700000) /**< 波特率 */
/* ============================================================================
* 全局变量定义
* ============================================================================ */
@@ -45,7 +45,6 @@ RS485_REGISTER_TYPE RS485REG = {RESET, {0}, {0}}; /**< RS485 收发寄存器,
UART_HandleTypeDef rs485_handle; /**< HAL UART 句柄 */
DMA_HandleTypeDef hdma_rs485_rx; /**< HAL DMA 接收句柄DMA1 Channel6 */
DMA_HandleTypeDef hdma_rs485_tx; /**< HAL DMA 发送句柄(未使用,保留) */
/* ============================================================================
* 初始化函数
@@ -65,61 +64,201 @@ DMA_HandleTypeDef hdma_rs485_tx; /**< HAL DMA 发送句柄(未使用,保留
*/
void RS485_DMA_init(void)
{
/* 使能时钟 */
RS485_TX_GPIO_CLK_ENABLE();
RS485_RX_GPIO_CLK_ENABLE();
RS485_ENABLE_GPIO_CLK_ENABLE();
RS485_UX_CLK_ENABLE();
/* ========================================================================
* 第一步:使能相关外设时钟
* ========================================================================
* 在 STM32 中,所有外设在使用前必须先使能其时钟,以降低功耗。
* 这里需要使能:
* - GPIOA 时钟:用于 TX(PA2)、RX(PA3)、DE(PA1) 引脚
* - USART2 时钟:用于串口通信功能
* - DMA1 时钟:用于 DMA 数据传输功能(必须!)
*/
RS485_TX_GPIO_CLK_ENABLE(); /* 使能 GPIOA 时钟TX 引脚 PA2 */
RS485_RX_GPIO_CLK_ENABLE(); /* 使能 GPIOA 时钟RX 引脚 PA3 */
RS485_ENABLE_GPIO_CLK_ENABLE(); /* 使能 GPIOA 时钟DE 引脚 PA1 */
RS485_UX_CLK_ENABLE(); /* 使能 USART2 时钟 */
__HAL_RCC_DMA1_CLK_ENABLE(); /* 使能 DMA1 时钟(必须!否则 DMA 无法工作) */
/* GPIO 初始化 */
GPIO_InitTypeDef gpio_init_struct = {0};
gpio_init_struct.Pin = RS485_TX_GPIO_PIN;
gpio_init_struct.Mode = GPIO_MODE_AF_PP;
gpio_init_struct.Pull = GPIO_PULLUP;
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;
/* ========================================================================
* 第二步:配置 GPIO 引脚功能
* ========================================================================
* RS485 需要三个 GPIO 引脚:
* 1. TX 引脚PA2发送数据配置为复用推挽输出
* 2. RX 引脚PA3接收数据配置为复用输入
* 3. DE 引脚PA1收发方向控制配置为普通推挽输出
*/
GPIO_InitTypeDef gpio_init_struct = {0}; /* 初始化 GPIO 配置结构体 */
/* 配置 TX 引脚PA2为复用推挽输出模式 */
gpio_init_struct.Pin = RS485_TX_GPIO_PIN; /* 选择 PA2 引脚 */
gpio_init_struct.Mode = GPIO_MODE_AF_PP; /* 复用推挽输出USART2_TX 功能 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉:提高输出驱动能力,抗干扰 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速:支持高波特率通信 */
HAL_GPIO_Init(RS485_TX_GPIO_PORT, &gpio_init_struct);
gpio_init_struct.Pin = RS485_RX_GPIO_PIN;
gpio_init_struct.Mode = GPIO_MODE_AF_INPUT;
/* 配置 RX 引脚PA3为复用输入模式 */
gpio_init_struct.Pin = RS485_RX_GPIO_PIN; /* 选择 PA3 引脚 */
gpio_init_struct.Mode = GPIO_MODE_AF_INPUT; /* 复用输入USART2_RX 功能 */
/* 注意复用输入模式下Pull 和 Speed 参数通常被忽略,但保持一致性 */
HAL_GPIO_Init(RS485_RX_GPIO_PORT, &gpio_init_struct);
gpio_init_struct.Pin = RS485_ENABLE_GPIO_PIN;
gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP;
/* 配置 DE 引脚PA1为普通推挽输出模式 */
gpio_init_struct.Pin = RS485_ENABLE_GPIO_PIN; /* 选择 PA1 引脚 */
gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP; /* 推挽输出:用于控制 RS485 收发方向 */
/* DE 引脚控制逻辑:
* - 低电平RESET使能发送模式允许数据从 TX 发送到总线
* - 高电平SET使能接收模式允许从总线接收数据到 RX
*/
HAL_GPIO_Init(RS485_ENABLE_GPIO_PORT, &gpio_init_struct);
/* DMA 接收配置 */
hdma_rs485_rx.Instance = DMA1_Channel6;
hdma_rs485_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_rs485_rx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_rs485_rx.Init.MemInc = DMA_MINC_ENABLE;
hdma_rs485_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_rs485_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_rs485_rx.Init.Mode = DMA_NORMAL;
hdma_rs485_rx.Init.Priority = DMA_PRIORITY_VERY_HIGH;
HAL_DMA_Init(&hdma_rs485_rx);
/* ========================================================================
* 第三步:配置 DMA 接收通道
* ========================================================================
* DMADirect Memory Access用于在 USART 接收数据时自动将数据从
* 外设寄存器传输到内存缓冲区,无需 CPU 干预,提高效率。
*
* STM32F103 中USART2_RX 对应 DMA1_Channel6
*
* 时钟配置分析(系统时钟 72MHz
* - AHB 时钟72MHzDMA 在 AHB 总线上)
* - APB1 时钟36MHzUSART2 在 APB1 总线上)
* - 波特率700000 bps
* - 每个字节时间:约 14.3 微秒10 位1 起始 + 8 数据 + 1 停止)
*
* DMA 传输速度要求:
* - DMA 需要从 APB1 外设USART2读取数据到 AHB 内存
* - 需要通过 AHB-APB1 桥,可能有 1-2 个时钟周期延迟
* - 理论上 DMA 时钟 72MHz 足够快,但总线仲裁可能导致延迟
*
* 溢出错误可能原因:
* 1. 总线竞争CPU 访问 APB1 外设时与 DMA 竞争总线
* 2. AHB-APB1 桥延迟DMA 访问 APB1 外设需要经过桥接器
* 3. DMA 优先级:虽然设置为最高,但总线仲裁是轮询的
* 4. 接收速度过快3208 字节连续接收DMA 可能来不及处理
*/
hdma_rs485_rx.Instance = DMA1_Channel6; /* 使用 DMA1 通道 6 */
hdma_rs485_rx.Init.Direction = DMA_PERIPH_TO_MEMORY; /* 传输方向:外设到内存 */
hdma_rs485_rx.Init.PeriphInc = DMA_PINC_DISABLE; /* 外设地址不递增USART 数据寄存器地址固定 */
hdma_rs485_rx.Init.MemInc = DMA_MINC_ENABLE; /* 内存地址递增:数据依次存入缓冲区 */
hdma_rs485_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; /* 外设数据宽度字节8位 */
hdma_rs485_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; /* 内存数据宽度字节8位 */
hdma_rs485_rx.Init.Mode = DMA_NORMAL; /* 普通模式:传输完成后停止,需重新启动 */
hdma_rs485_rx.Init.Priority = DMA_PRIORITY_VERY_HIGH; /* 最高优先级:确保数据及时传输,避免丢失 */
HAL_DMA_Init(&hdma_rs485_rx); /* 初始化 DMA 通道 */
/* 将 DMA 句柄关联到 UART 句柄,使 HAL 库能够自动管理 DMA 传输 */
__HAL_LINKDMA(&rs485_handle, hdmarx, hdma_rs485_rx);
HAL_NVIC_EnableIRQ(DMA1_Channel6_IRQn);
HAL_NVIC_SetPriority(DMA1_Channel6_IRQn, 0, 0);
/* 使能 DMA 中断,用于接收完成等事件的通知 */
HAL_NVIC_EnableIRQ(DMA1_Channel6_IRQn); /* 使能 DMA1_Channel6 中断 */
HAL_NVIC_SetPriority(DMA1_Channel6_IRQn, 0, 0); /* 设置中断优先级为最高0,0 */
/* UART 初始化 */
rs485_handle.Instance = RS485_UX;
rs485_handle.Init.BaudRate = BAUDRATE;
rs485_handle.Init.WordLength = UART_WORDLENGTH_8B;
rs485_handle.Init.StopBits = UART_STOPBITS_1;
rs485_handle.Init.Parity = UART_PARITY_NONE;
rs485_handle.Init.HwFlowCtl = UART_HWCONTROL_NONE;
rs485_handle.Init.Mode = UART_MODE_TX_RX;
rs485_handle.Init.OverSampling = UART_OVERSAMPLING_16;
HAL_UART_Init(&rs485_handle);
/* ========================================================================
* 第四步:配置 UART 串口参数
* ========================================================================
* 配置 USART2 的通信参数,包括波特率、数据位、停止位、校验位等
*/
rs485_handle.Instance = RS485_UX; /* 使用 USART2 */
rs485_handle.Init.BaudRate = BAUDRATE; /* 波特率700000 bps高速通信 */
rs485_handle.Init.WordLength = UART_WORDLENGTH_8B; /* 数据位8 位 */
rs485_handle.Init.StopBits = UART_STOPBITS_1; /* 停止位1 位 */
rs485_handle.Init.Parity = UART_PARITY_NONE; /* 校验位:无校验 */
rs485_handle.Init.HwFlowCtl = UART_HWCONTROL_NONE; /* 硬件流控RS485 不需要 RTS/CTS */
rs485_handle.Init.Mode = UART_MODE_TX_RX; /* 工作模式:同时支持发送和接收 */
rs485_handle.Init.OverSampling = UART_OVERSAMPLING_16; /* 过采样16 倍STM32F103 仅支持 16 倍过采样) */
HAL_UART_Init(&rs485_handle); /* 初始化 UART应用上述配置 */
HAL_NVIC_EnableIRQ(RS485_UX_IRQn);
HAL_NVIC_SetPriority(RS485_UX_IRQn, 1, 0);
/* 手动精确设置 BRR 寄存器以获得准确的 700000 波特率
* 计算PCLK1 = 36MHz, 16倍过采样
* USARTDIV = 36000000 / (16 * 700000) = 3.2142857...
* BRR = (整数部分 << 4) | 小数部分
*/
/* 启动 DMA 接收 */
RS485_RECEIVE_ENABLE();
HAL_UARTEx_ReceiveToIdle_DMA(&rs485_handle, (uint8_t *)RS485REG.DR, UART_RX_LEN);
/* 使能 USART2 中断,用于处理接收完成、错误等事件 */
HAL_NVIC_EnableIRQ(RS485_UX_IRQn); /* 使能 USART2 中断 */
HAL_NVIC_SetPriority(RS485_UX_IRQn, 1, 0); /* 设置中断优先级为 1低于 DMA 中断) */
/* ========================================================================
* 第五步:启动 DMA 接收
* ========================================================================
* 将 RS485 切换到接收模式,并启动 ReceiveToIdle DMA 接收
*
* ReceiveToIdle 模式特点:
* - 当检测到总线空闲IDLE自动触发接收完成回调
* - 适用于接收不定长数据帧(如 Modbus 协议)
* - 无需预先知道数据长度,总线空闲即表示一帧数据接收完成
*/
RS485_RECEIVE_ENABLE(); /* DE 引脚置高,切换到接收模式 */
// HAL_UARTEx_ReceiveToIdle_DMA(&rs485_handle, /* UART 句柄 */
// (uint8_t *)RS485REG.DR, /* 接收缓冲区RS485REG.DR3208 字节) */
// UART_RX_LEN); /* 最大接收长度3208 字节 */
HAL_UARTEx_ReceiveToIdle_IT(&rs485_handle, /* UART 句柄 */
(uint8_t *)RS485REG.DR, /* 接收缓冲区RS485REG.DR3208 字节) */
UART_RX_LEN); /* 最大接收长度3208 字节 */
/* 注意:接收完成后,会在 HAL_UARTEx_RxEventCallback 回调函数中
* 自动重新启动接收,实现连续接收 */
}
/**
* @brief RS485 反初始化DMA 接收模式)
* @note 按照与初始化相反的顺序清理资源:
* 1. 停止 DMA 接收
* 2. 禁用中断
* 3. 反初始化 DMA
* 4. 反初始化 UART
* 5. 反初始化 GPIO可选
* 注意:时钟通常不禁用,因为可能被其他外设使用
* @retval 无
*/
void RS485_DMA_DeInit(void)
{
/* ========================================================================
* 第一步:停止 DMA 接收
* ========================================================================
* 必须先停止正在进行的 DMA 传输,避免数据损坏
*/
HAL_UART_DMAStop(&rs485_handle); /* 停止 UART DMA 接收 */
/* ========================================================================
* 第二步:禁用中断
* ========================================================================
* 禁用 USART2 和 DMA1_Channel6 的中断,避免在反初始化过程中触发中断
*/
HAL_NVIC_DisableIRQ(RS485_UX_IRQn); /* 禁用 USART2 中断 */
HAL_NVIC_DisableIRQ(DMA1_Channel6_IRQn); /* 禁用 DMA1_Channel6 中断 */
/* ========================================================================
* 第三步:反初始化 DMA
* ========================================================================
* 断开 DMA 与 UART 的关联,然后反初始化 DMA 通道
* 注意HAL 库没有提供 __HAL_UNLINK_DMA 宏,需要手动将指针设为 NULL
*/
rs485_handle.hdmarx = NULL; /* 断开 DMA 与 UART 的关联 */
HAL_DMA_DeInit(&hdma_rs485_rx); /* 反初始化 DMA 通道 */
/* ========================================================================
* 第四步:反初始化 UART
* ========================================================================
* 反初始化 USART2这会调用 HAL_UART_MspDeInit 来清理底层硬件
*/
HAL_UART_DeInit(&rs485_handle); /* 反初始化 UART */
/* ========================================================================
* 第五步:反初始化 GPIO可选
* ========================================================================
* 将 GPIO 引脚恢复为默认状态(模拟输入,高阻态)
* 注意:如果这些引脚可能被其他外设使用,可以跳过此步骤
*/
HAL_GPIO_DeInit(RS485_TX_GPIO_PORT, RS485_TX_GPIO_PIN); /* 反初始化 TX 引脚PA2 */
HAL_GPIO_DeInit(RS485_RX_GPIO_PORT, RS485_RX_GPIO_PIN); /* 反初始化 RX 引脚PA3 */
HAL_GPIO_DeInit(RS485_ENABLE_GPIO_PORT, RS485_ENABLE_GPIO_PIN); /* 反初始化 DE 引脚PA1 */
/* 注意:时钟通常不禁用,因为:
* - GPIOA 时钟可能被其他外设使用
* - USART2 时钟可能被其他功能使用
* - DMA1 时钟可能被其他 DMA 通道使用
* 如果需要完全禁用时钟以节省功耗,可以在确认没有其他外设使用时禁用
*/
}
/**
@@ -127,46 +266,96 @@ void RS485_DMA_init(void)
* @note 配置 GPIO、UART使能 RXNE 中断。不启用 DMA。
* 用于错误恢复或接收短帧。USART 时钟源需已配置。
* @retval 无
*
* @details 中断接收模式与 DMA 模式的区别:
* - 中断模式每接收一个字节触发一次中断CPU 需要逐字节处理
* - DMA 模式:自动将数据从 USART 传输到内存CPU 负担小
* 中断模式适用于:
* - DMA 故障时的错误恢复
* - 接收短帧(数据量小,中断开销可接受)
* - 调试和测试场景
*/
void RS485_init(void)
{
/* 开启时钟 */
RS485_TX_GPIO_CLK_ENABLE(); /* 使能串口TX脚时钟 */
RS485_RX_GPIO_CLK_ENABLE(); /* 使能串口RX脚时钟 */
RS485_ENABLE_GPIO_CLK_ENABLE(); /* 使能串口RX脚时钟 */
RS485_UX_CLK_ENABLE(); /* 使能串口时钟 */
/* ========================================================================
* 第一步:使能相关外设时钟
* ========================================================================
* 与 DMA 模式相同,需要使能 GPIOA 和 USART2 的时钟
*/
RS485_TX_GPIO_CLK_ENABLE(); /* 使能 GPIOA 时钟TX 引脚 PA2 */
RS485_RX_GPIO_CLK_ENABLE(); /* 使能 GPIOA 时钟RX 引脚 PA3 */
RS485_ENABLE_GPIO_CLK_ENABLE(); /* 使能 GPIOA 时钟DE 引脚 PA1 */
RS485_UX_CLK_ENABLE(); /* 使能 USART2 时钟 */
/* GPIO 初始化 */
GPIO_InitTypeDef gpio_init_struct = {0};
gpio_init_struct.Pin = RS485_TX_GPIO_PIN;
gpio_init_struct.Mode = GPIO_MODE_AF_PP;
gpio_init_struct.Pull = GPIO_PULLUP;
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;
/* ========================================================================
* 第二步:配置 GPIO 引脚功能
* ========================================================================
* GPIO 配置与 DMA 模式完全相同:
* - TX(PA2):复用推挽输出,用于发送数据
* - RX(PA3):复用输入,用于接收数据
* - DE(PA1):普通推挽输出,用于控制收发方向
*/
GPIO_InitTypeDef gpio_init_struct = {0}; /* 初始化 GPIO 配置结构体 */
/* 配置 TX 引脚PA2为复用推挽输出模式 */
gpio_init_struct.Pin = RS485_TX_GPIO_PIN; /* 选择 PA2 引脚 */
gpio_init_struct.Mode = GPIO_MODE_AF_PP; /* 复用推挽输出USART2_TX 功能 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉:提高输出驱动能力 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速:支持高波特率 */
HAL_GPIO_Init(RS485_TX_GPIO_PORT, &gpio_init_struct);
gpio_init_struct.Pin = RS485_RX_GPIO_PIN;
gpio_init_struct.Mode = GPIO_MODE_AF_INPUT;
/* 配置 RX 引脚PA3为复用输入模式 */
gpio_init_struct.Pin = RS485_RX_GPIO_PIN; /* 选择 PA3 引脚 */
gpio_init_struct.Mode = GPIO_MODE_AF_INPUT; /* 复用输入USART2_RX 功能 */
HAL_GPIO_Init(RS485_RX_GPIO_PORT, &gpio_init_struct);
gpio_init_struct.Pin = RS485_ENABLE_GPIO_PIN;
gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP;
/* 配置 DE 引脚PA1为普通推挽输出模式 */
gpio_init_struct.Pin = RS485_ENABLE_GPIO_PIN; /* 选择 PA1 引脚 */
gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP; /* 推挽输出:控制 RS485 收发方向 */
HAL_GPIO_Init(RS485_ENABLE_GPIO_PORT, &gpio_init_struct);
/* UART 初始化 */
rs485_handle.Instance = RS485_UX;
rs485_handle.Init.BaudRate = BAUDRATE;
rs485_handle.Init.WordLength = UART_WORDLENGTH_8B;
rs485_handle.Init.StopBits = UART_STOPBITS_1;
rs485_handle.Init.Parity = UART_PARITY_NONE;
rs485_handle.Init.HwFlowCtl = UART_HWCONTROL_NONE;
rs485_handle.Init.Mode = UART_MODE_TX_RX;
rs485_handle.Init.OverSampling = UART_OVERSAMPLING_16;
HAL_UART_Init(&rs485_handle);
/* ========================================================================
* 第三步:配置 UART 串口参数
* ========================================================================
* UART 参数配置与 DMA 模式完全相同:
* - 波特率700000 bps
* - 数据格式8 位数据位1 位停止位,无校验位
* - 无硬件流控
* - 16 倍过采样
*/
rs485_handle.Instance = RS485_UX; /* 使用 USART2 */
rs485_handle.Init.BaudRate = BAUDRATE; /* 波特率700000 bps */
rs485_handle.Init.WordLength = UART_WORDLENGTH_8B; /* 数据位8 位 */
rs485_handle.Init.StopBits = UART_STOPBITS_1; /* 停止位1 位 */
rs485_handle.Init.Parity = UART_PARITY_NONE; /* 校验位:无校验 */
rs485_handle.Init.HwFlowCtl = UART_HWCONTROL_NONE; /* 硬件流控:无 */
rs485_handle.Init.Mode = UART_MODE_TX_RX; /* 工作模式:发送和接收 */
rs485_handle.Init.OverSampling = UART_OVERSAMPLING_16; /* 过采样16 倍 */
HAL_UART_Init(&rs485_handle); /* 初始化 UART */
__HAL_UART_ENABLE_IT(&rs485_handle, UART_IT_RXNE);
HAL_NVIC_EnableIRQ(RS485_UX_IRQn);
HAL_NVIC_SetPriority(RS485_UX_IRQn, 1, 0);
RS485_RECEIVE_ENABLE();
/* ========================================================================
* 第四步使能接收中断RXNE
* ========================================================================
* RXNEReceive Not Empty中断
* - 当 USART 接收数据寄存器RDR中有新数据时触发
* - 每接收一个字节触发一次中断
* - 在中断服务函数中需要手动读取数据并存入缓冲区
*
* 注意:此模式不使用 DMA需要 CPU 在中断中逐字节处理数据
*/
__HAL_UART_ENABLE_IT(&rs485_handle, UART_IT_RXNE); /* 使能 RXNE接收数据寄存器非空中断 */
HAL_NVIC_EnableIRQ(RS485_UX_IRQn); /* 使能 USART2 中断 */
HAL_NVIC_SetPriority(RS485_UX_IRQn, 1, 0); /* 设置中断优先级为 1 */
/* ========================================================================
* 第五步:切换到接收模式
* ========================================================================
* 将 DE 引脚置高,使 RS485 处于接收状态,准备接收数据
*/
RS485_RECEIVE_ENABLE(); /* DE 引脚置高,切换到接收模式 */
/* 注意:中断模式下,需要在 USART2_IRQHandler 中断服务函数中
* 手动调用 HAL_UART_IRQHandler并在回调函数中处理接收到的数据 */
}
/* ============================================================================
@@ -178,14 +367,70 @@ void RS485_init(void)
* @param huart UART 句柄
* @param Size 本帧接收到的字节数
* @note 当为 RS485 所用 UART 时:置位 NewMessageFlag并重新启动 ReceiveToIdle DMA。
*
* @details HAL 库在调用此回调前已经完成以下操作:
* - DMA 状态已设置为 READY在 DMA IRQ 处理中)
* - UART 状态已设置为 READY在 UART_DMAReceiveCplt 中)
* - DMA 请求已禁用(清除 DMAR 位)
* - 相关中断已禁用
* 因此可以直接调用 HAL_UARTEx_ReceiveToIdle_DMA 重新启动接收。
*
* @warning 重要:当接收数据长度 = 缓冲区大小3208字节
* - DMA 传输完成中断触发,而不是 IDLE 中断
* - 如果发生溢出错误OREHAL 库会中止 DMA 并重置 ReceptionType 为 STANDARD
* - 这会导致回调函数不会被调用
* - 解决方案:添加错误回调函数来处理这种情况
*
* @retval 无
*/
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
if (huart->Instance == RS485_UX)
{
/* 调试信息:显示接收到的字节数 */
// HZ12AndChar_Printf(0,32, (uint8_t*)"RxEvent:", RESET);
// IntValue_Printf(32,32, Size, RESET);
/* 设置新消息标志,通知主程序处理数据 */
RS485REG.NewMessageFlag = SET;
HAL_UARTEx_ReceiveToIdle_DMA(&rs485_handle, (uint8_t *)RS485REG.DR, UART_RX_LEN);
/* 重新启动 IT 接收*/
HAL_UARTEx_ReceiveToIdle_IT(&rs485_handle, /* UART 句柄 */
(uint8_t *)RS485REG.DR, /* 接收缓冲区RS485REG.DR3208 字节) */
UART_RX_LEN); /* 最大接收长度3208 字节 */
}
}
/**
* @brief UART 错误回调函数
* @param huart UART 句柄
* @note 当发生 UART 错误如溢出、帧错误等HAL 库会调用此函数
*
* @details 重要当接收3208字节时如果发生溢出错误OREHAL 库会:
* 1. 中止 DMA 传输
* 2. 重置 ReceptionType 为 STANDARD
* 3. 调用错误回调函数(而不是 RxEventCallback
* 因此,需要在错误回调中处理这种情况,并重新启动接收
*
* @details 错误代码说明:
* - HAL_UART_ERROR_NONE (0x00): 无错误
* - HAL_UART_ERROR_PE (0x01): 奇偶校验错误
* - HAL_UART_ERROR_NE (0x02): 噪声错误(信号干扰、波特率不匹配等)
* - HAL_UART_ERROR_FE (0x04): 帧错误(停止位检测失败)
* - HAL_UART_ERROR_ORE (0x08): 溢出错误数据接收过快DMA 来不及处理)
* - HAL_UART_ERROR_DMA (0x10): DMA 传输错误
*
* @retval 无
*/
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == RS485_UX)
{
// /* 调试信息:显示错误代码 */
// HZ12AndChar_Printf(0,64, (uint8_t*)"Error:", RESET);
// IntValue_Printf(32,64, huart->ErrorCode, RESET);
//HAL_UARTEx_ReceiveToIdle_DMA(&rs485_handle, (uint8_t *)RS485REG.DR, UART_RX_LEN);
}
}

View File

@@ -20,7 +20,7 @@
/* ============================================================================
* 宏定义
* ============================================================================ */
#define UART_RX_LEN (3208) /**< 接收缓冲区长度(字节),与 Modbus 数据帧长度一致 */
#define UART_RX_LEN (4208) /**< 接收缓冲区长度(字节),与 Modbus 数据帧长度一致 */
#define UART_TX_LEN (8) /**< 发送缓冲区长度(字节) */
/* ============================================================================
@@ -39,7 +39,7 @@ typedef struct
} RS485_REGISTER_TYPE;
extern RS485_REGISTER_TYPE RS485REG; /**< 全局 RS485 收发寄存器 */
extern UART_HandleTypeDef rs485_handle;
/* ============================================================================
* 函数声明
* ============================================================================ */
@@ -51,6 +51,13 @@ extern RS485_REGISTER_TYPE RS485REG; /**< 全局 RS485 收发寄存器 */
*/
void RS485_DMA_init(void);
/**
* @brief RS485 反初始化DMA 接收模式)
* @note 按照与初始化相反的顺序清理资源:停止 DMA、禁用中断、反初始化 DMA/UART/GPIO。
* @retval 无
*/
void RS485_DMA_DeInit(void);
/**
* @brief RS485 初始化(中断接收模式)
* @note 配置 GPIO、UART使能 RXNE 中断接收。用于异常恢复或短帧接收。