/******************************************************************************
 * @file        rs485.c
 * @brief       rs485串口配置，主要配置两种模式 中断接收模式 和 DMA 接收模式
 * @details      
 *   1. DMA模式是主要工作模式
 *   2. 中断模式用于错误恢复和接收第一个短帧
 *   3. 中断处理完后自动切换回DMA模式
 *   4. 中断接收主要用于异常情况处理
 * @author      阜阳师范大学物电学院
 * @version     V0.01
 * @date        2026.1.24
 * @note        通信协议：Modbus RTU
 *              通信接口：RS485
 *              主站地址：0x01
 ******************************************************************************/

#include "rs485.h"

#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)  /* 时钟使能 */

#define RS485_SEND_ENABLE()                 do{HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET);}while(0)
#define RS485_RECEIVE_ENABLE()              do{HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET);}while(0)

#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)   /* PA口时钟使能 */

#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)   /* PA口时钟使能 */

#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)   /* PA口时钟使能 */

RS485_REGISTER_TYPE RS485REG = {RESET, {0}, {0}};     /* 零初始化定义 */

// HAL库UART句柄
UART_HandleTypeDef rs485_handle;  /* UART句柄 */
// HAL库DMA句柄（接收）
DMA_HandleTypeDef hdma_rs485_rx;
// HAL库DMA句柄（发送）
DMA_HandleTypeDef hdma_rs485_tx;


#define BAUDRATE                            (700000)

/**
 * @brief       串口X初始化函数
 * @param       baudrate: 波特率, 根据自己需要设置波特率值
 * @note        注意: 必须设置正确的时钟源, 否则串口波特率就会设置异常.
 *              这里的USART的时钟源在sys_stm32_clock_init()函数中已经设置过了.
 * @retval      无
 */
void RS485_DMA_init()
{
    /* 开启时钟 */
    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;          /* IO速度设置为高速 */
    HAL_GPIO_Init(RS485_TX_GPIO_PORT, &gpio_init_struct);
            
    gpio_init_struct.Pin = RS485_RX_GPIO_PIN;               /* 串口RX脚 模式设置 */
    gpio_init_struct.Mode = GPIO_MODE_AF_INPUT;    
    HAL_GPIO_Init(RS485_RX_GPIO_PORT, &gpio_init_struct);   /* 串口RX脚 必须设置成输入模式 */
    
    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);                                 /*初始化DMA 接收*/
    
    /* 关联 DMA 到 UART（重要！）*/
    __HAL_LINKDMA(&rs485_handle, hdmarx, hdma_rs485_rx);
    
    /* 使能DMA传输完成中断 */
    HAL_NVIC_EnableIRQ(DMA1_Channel6_IRQn);
    HAL_NVIC_SetPriority(DMA1_Channel6_IRQn, 0, 0);
    
    /* UART 初始化设置 */
    rs485_handle.Instance = RS485_UX;                                       /* USART_UX */
    rs485_handle.Init.BaudRate = BAUDRATE;                                  /* 波特率 */
    rs485_handle.Init.WordLength = UART_WORDLENGTH_8B;                      /* 字长为8位数据格式 */
    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 */
    
    HAL_NVIC_EnableIRQ(RS485_UX_IRQn);                                     /* 使能USART中断通道 */
    HAL_NVIC_SetPriority(RS485_UX_IRQn, 1, 0);                             /* 组2，抢占优先级1，子优先级0 */

    /* 启动DMA接收 */
    RS485_RECEIVE_ENABLE();                                              
    HAL_UARTEx_ReceiveToIdle_DMA(&rs485_handle, (uint8_t *)RS485REG.DR, UART_RX_LEN);
}
/**
 * @brief       串口X初始化函数
 * @note        注意: 必须设置正确的时钟源, 否则串口波特率就会设置异常.
 *     
 * @retval      无
 */
void RS485_init()
{
    /* 开启时钟 */
    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;
    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;          /* IO速度设置为高速 */
    HAL_GPIO_Init(RS485_TX_GPIO_PORT, &gpio_init_struct);
            
    gpio_init_struct.Pin = RS485_RX_GPIO_PIN;               /* 串口RX脚 模式设置 */
    gpio_init_struct.Mode = GPIO_MODE_AF_INPUT;    
    HAL_GPIO_Init(RS485_RX_GPIO_PORT, &gpio_init_struct);   /* 串口RX脚 必须设置成输入模式 */
    
    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;                                       /* USART_UX */
    rs485_handle.Init.BaudRate = BAUDRATE;                                  /* 波特率 */
    rs485_handle.Init.WordLength = UART_WORDLENGTH_8B;                      /* 字长为8位数据格式 */
    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;                               /* 收发模式 */
    HAL_UART_Init(&rs485_handle);                                           /* 使能UART */
    
    /* 该函数会开启接收中断：标志位UART_IT_RXNE，并且设置接收缓冲以及接收缓冲接收最大数据量 */
    __HAL_UART_ENABLE_IT(&rs485_handle, UART_IT_RXNE); 
    HAL_NVIC_EnableIRQ(RS485_UX_IRQn);                      /* 使能USART中断通道 */
    HAL_NVIC_SetPriority(RS485_UX_IRQn, 1, 0);              /* 组2，抢占优先级1，子优先级0 */
    RS485_RECEIVE_ENABLE();
}

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       485串口中断服务函数
 * @param       无
 * @retval      无
 */
void RS485_UX_IRQHandler(void)
{
    HAL_UART_IRQHandler(&rs485_handle);
}

/*******************************************************************************
* FunctionName   : DMA1_Channel6_IRQHandler()
* Description    : 串口接收中断
* EntryParameter : none
* ReturnValue    : none
********************************************************************************/
void DMA1_Channel6_IRQHandler(void)
{
     HAL_DMA_IRQHandler(&hdma_rs485_rx);
}
/***********************************************************************
* FunctionName   : RS485_SendBuff()
* Description    : 485发送数组
* EntryParameter : *ptr:待发送的字符串, len:待发送的数据个数
* ReturnValue    : ptrLen:发送的数据个数 
**************************************************************************/ 
void RS485_SendBuff(uint8_t *ptr, uint32_t len)
{
    RS485_SEND_ENABLE();
    
    HAL_UART_Transmit(&rs485_handle, ptr,len, 1000);
    
    RS485_RECEIVE_ENABLE();
}

