/******************************************************************************
 * @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"

/* ============================================================================
 * 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） */
DMA_HandleTypeDef hdma_rs485_tx;  /**< HAL DMA 发送句柄（未使用，保留） */

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

/**
 * @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)
{
    /* 使能时钟 */
    RS485_TX_GPIO_CLK_ENABLE();
    RS485_RX_GPIO_CLK_ENABLE();
    RS485_ENABLE_GPIO_CLK_ENABLE();
    RS485_UX_CLK_ENABLE();
    
    /* 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;
    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;
    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;
    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);
    
    __HAL_LINKDMA(&rs485_handle, hdmarx, hdma_rs485_rx);
    
    HAL_NVIC_EnableIRQ(DMA1_Channel6_IRQn);
    HAL_NVIC_SetPriority(DMA1_Channel6_IRQn, 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);
    
    HAL_NVIC_EnableIRQ(RS485_UX_IRQn);
    HAL_NVIC_SetPriority(RS485_UX_IRQn, 1, 0);
    
    /* 启动 DMA 接收 */
    RS485_RECEIVE_ENABLE();
    HAL_UARTEx_ReceiveToIdle_DMA(&rs485_handle, (uint8_t *)RS485REG.DR, UART_RX_LEN);
}

/**
 * @brief  RS485 初始化（中断接收模式）
 * @note   配置 GPIO、UART，使能 RXNE 中断。不启用 DMA。
 *          用于错误恢复或接收短帧。USART 时钟源需已配置。
 * @retval 无
 */
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();                                  /* 使能串口时钟 */
    
    /* 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;
    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;
    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;
    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);
    
    __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();
}

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

/**
 * @brief  UART 接收事件回调（ReceiveToIdle 完成时由 HAL 调用）
 * @param  huart UART 句柄
 * @param  Size  本帧接收到的字节数
 * @note   当为 RS485 所用 UART 时：置位 NewMessageFlag，并重新启动 ReceiveToIdle DMA。
 * @retval 无
 */
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
    if (huart->Instance == RS485_UX)
    {
        RS485REG.NewMessageFlag = SET;
        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();
}

