/****************************************************************************** * @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(); }