/******************************************************************************
 * @file        rs485.c
 * @brief       RS485 串口驱动 - DMA 与中断接收
 * @details    本文件实现 RS485 串口通信驱动，支持两种模式：
 *              1. DMA 模式：主工作模式，使用 ReceiveToIdle 接收不定长帧
 *              2. 中断模式：用于错误恢复或短帧接收，RXNE 逐字节接收
 *              收发通过 DE 引脚（PA1）切换：低电平发送，高电平接收。
 * @author      阜阳师范大学物电学院
 * @version     V0.01
 * @date        2026.1.24
 * @note        USART2，TX=PA2，RX=PA3，DE=PA1；波特率 700000
 ******************************************************************************/

#include "rs485.h"
#include "160160D.h"

/* ============================================================================
 * USART 与引脚宏定义
 * ============================================================================ */
#define RS485_UX                            USART2
#define RS485_UX_IRQn                       USART2_IRQn
#define RS485_UX_IRQHandler                 USART2_IRQHandler
#define RS485_UX_CLK_ENABLE()               do{ __HAL_RCC_USART2_CLK_ENABLE(); }while(0)  /**< USART2 时钟使能 */

#define RS485_SEND_ENABLE()                 do{HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET);}while(0)   /**< DE 低：发送 */
#define RS485_RECEIVE_ENABLE()              do{HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET);}while(0)     /**< DE 高：接收 */

#define RS485_ENABLE_GPIO_PORT              GPIOA
#define RS485_ENABLE_GPIO_PIN               GPIO_PIN_1
#define RS485_ENABLE_GPIO_CLK_ENABLE()      do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)

#define RS485_TX_GPIO_PORT                  GPIOA
#define RS485_TX_GPIO_PIN                   GPIO_PIN_2
#define RS485_TX_GPIO_CLK_ENABLE()          do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)

#define RS485_RX_GPIO_PORT                  GPIOA
#define RS485_RX_GPIO_PIN                   GPIO_PIN_3
#define RS485_RX_GPIO_CLK_ENABLE()          do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)

#define BAUDRATE                            (700000)  /**< 波特率 */
/* ============================================================================
 * 全局变量定义
 * ============================================================================ */
RS485_REGISTER_TYPE RS485REG = {RESET, {0}, {0}};  /**< RS485 收发寄存器，零初始化 */

UART_HandleTypeDef rs485_handle;   /**< HAL UART 句柄 */
DMA_HandleTypeDef hdma_rs485_rx;  /**< HAL DMA 接收句柄（DMA1 Channel6） */

/* ============================================================================
 * 初始化函数
 * ============================================================================ */

/**
 * @brief  RS485 初始化（DMA 接收模式）
 * @note   配置流程：
 *          1. 使能 GPIO、USART 时钟
 *          2. 配置 TX(PA2)、RX(PA3)、DE(PA1)
 *          3. 配置 DMA1 Channel6 接收，关联 UART
 *          4. 使能 DMA、USART 中断
 *          5. 初始化 UART（8N1，无流控，波特率 BAUDRATE）
 *          6. 切换为接收，启动 ReceiveToIdle DMA
 *          USART 时钟源需在 sys_stm32_clock_init 中已配置。
 * @retval 无
 */
void RS485_DMA_init(void)
{
    /* ========================================================================
     * 第一步：使能相关外设时钟
     * ========================================================================
     * 在 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 引脚功能
     * ========================================================================
     * 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);
    
    /* 配置 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);
    
    /* 配置 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 接收通道
     * ========================================================================
     * 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);
    
    /* 使能 DMA 中断，用于接收完成等事件的通知 */
    HAL_NVIC_EnableIRQ(DMA1_Channel6_IRQn);                   /* 使能 DMA1_Channel6 中断 */
    HAL_NVIC_SetPriority(DMA1_Channel6_IRQn, 0, 0);           /* 设置中断优先级为最高（0,0） */
    
    /* ========================================================================
     * 第四步：配置 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，应用上述配置 */
    
    /* 手动精确设置 BRR 寄存器以获得准确的 700000 波特率
     * 计算：PCLK1 = 36MHz, 16倍过采样
     * USARTDIV = 36000000 / (16 * 700000) = 3.2142857...
     * BRR = (整数部分 << 4) | 小数部分
     */
    
    /* 使能 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 通道使用
     * 如果需要完全禁用时钟以节省功耗，可以在确认没有其他外设使用时禁用
     */
}

/**
 * @brief  RS485 初始化（中断接收模式）
 * @note   配置 GPIO、UART，使能 RXNE 中断。不启用 DMA。
 *          用于错误恢复或接收短帧。USART 时钟源需已配置。
 * @retval 无
 * 
 * @details 中断接收模式与 DMA 模式的区别：
 *          - 中断模式：每接收一个字节触发一次中断，CPU 需要逐字节处理
 *          - DMA 模式：自动将数据从 USART 传输到内存，CPU 负担小
 *          中断模式适用于：
 *          - DMA 故障时的错误恢复
 *          - 接收短帧（数据量小，中断开销可接受）
 *          - 调试和测试场景
 */
void RS485_init(void)
{
    /* ========================================================================
     * 第一步：使能相关外设时钟
     * ========================================================================
     * 与 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 配置与 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);
    
    /* 配置 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);
    
    /* 配置 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 串口参数
     * ========================================================================
     * 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 */
    
    /* ========================================================================
     * 第四步：使能接收中断（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，并在回调函数中处理接收到的数据 */
}

/* ============================================================================
 * 回调与中断服务函数
 * ============================================================================ */

/**
 * @brief  UART 接收事件回调（ReceiveToIdle 完成时由 HAL 调用）
 * @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;
        
        /* 重新启动 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);
    }
}

/**
 * @brief  RS485 所用 USART 中断服务函数
 * @note   调用 HAL_UART_IRQHandler 处理 UART 及关联 DMA 中断。
 * @retval 无
 */
void RS485_UX_IRQHandler(void)
{
    HAL_UART_IRQHandler(&rs485_handle);
}

/**
 * @brief  DMA1 Channel6 中断服务函数（RS485 接收 DMA）
 * @note   调用 HAL_DMA_IRQHandler，在 ReceiveToIdle 完成等事件时触发。
 * @retval 无
 */
void DMA1_Channel6_IRQHandler(void)
{
    HAL_DMA_IRQHandler(&hdma_rs485_rx);
}

/* ============================================================================
 * 发送接口
 * ============================================================================ */

/**
 * @brief  RS485 发送数据
 * @param  ptr 待发送数据指针
 * @param  len 待发送字节数
 * @note   切换为发送（DE 低）→ 阻塞发送 → 切换回接收（DE 高）。超时 1000ms。
 * @retval 无
 */
void RS485_SendBuff(uint8_t *ptr, uint32_t len)
{
    RS485_SEND_ENABLE();
    HAL_UART_Transmit(&rs485_handle, ptr, len, 1000);
    RS485_RECEIVE_ENABLE();
}

