调试key LCD rs485都正常运行,修复了bug,但是DMA接收的问题没有解决
This commit is contained in:
@@ -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 接收通道
|
||||
* ========================================================================
|
||||
* DMA(Direct Memory Access)用于在 USART 接收数据时自动将数据从
|
||||
* 外设寄存器传输到内存缓冲区,无需 CPU 干预,提高效率。
|
||||
*
|
||||
* STM32F103 中,USART2_RX 对应 DMA1_Channel6
|
||||
*
|
||||
* 时钟配置分析(系统时钟 72MHz):
|
||||
* - AHB 时钟:72MHz(DMA 在 AHB 总线上)
|
||||
* - APB1 时钟:36MHz(USART2 在 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.DR(3208 字节) */
|
||||
// UART_RX_LEN); /* 最大接收长度:3208 字节 */
|
||||
HAL_UARTEx_ReceiveToIdle_IT(&rs485_handle, /* UART 句柄 */
|
||||
(uint8_t *)RS485REG.DR, /* 接收缓冲区:RS485REG.DR(3208 字节) */
|
||||
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)
|
||||
* ========================================================================
|
||||
* RXNE(Receive 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 中断
|
||||
* - 如果发生溢出错误(ORE),HAL 库会中止 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.DR(3208 字节) */
|
||||
UART_RX_LEN); /* 最大接收长度:3208 字节 */
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief UART 错误回调函数
|
||||
* @param huart UART 句柄
|
||||
* @note 当发生 UART 错误(如溢出、帧错误等)时,HAL 库会调用此函数
|
||||
*
|
||||
* @details 重要:当接收3208字节时,如果发生溢出错误(ORE),HAL 库会:
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user