基于STM32实现RS485通信及DMA、IT回调机制

在嵌入式系统中,RS485是一种常用的通信协议,广泛应用于工业自动化、传感器网络等领域。通过RS485,可以实现多设备的长距离通信。本文将介绍如何在STM32平台上实现RS485通信,特别是如何结合DMA(Direct Memory Access)和中断(Interrupt)机制,以提高数据传输效率,并通过回调函数接口来处理接收到的数据。

项目背景

在本项目中,我们使用STM32的UART接口来实现RS485通信,支持数据的接收和发送。为了提高系统效率,我们结合使用了DMA和中断方式来处理数据接收,避免了CPU的频繁干预,提升了系统的响应速度。

RS485基本实现

RS485通信基于半双工模式,通常使用两根信号线:一根用于发送数据,另一根用于接收数据。在STM32中,我们使用USART外设来进行数据的传输。RS485的特殊性在于,它需要通过控制DE(驱动使能)引脚来切换发送和接收模式。

1. 硬件连接与引脚配置

在STM32的开发板中,我们使用GPIO引脚来控制RS485的方向(发送或接收)。通过控制RS485的DE引脚(例如PA4),可以实现发送模式和接收模式的切换。

#define RS485_DE_GPIO_PORT GPIOA
#define RS485_DE_PIN GPIO_PIN_4

2. RS485通信初始化

首先,我们需要初始化GPIO和USART接口。GPIO用于控制RS485的DE引脚,USART接口用于数据的发送和接收。

void RS485_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    
    // 配置RS485 DE引脚为推挽输出
    GPIO_InitStruct.Pin = RS485_DE_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(RS485_DE_GPIO_PORT, &GPIO_InitStruct);
    
    // 默认设置为接收模式(DE = 0)
    RS485_ReceiveEnable();  
    
    // 使用DMA模式接收数据
    RS485_ReceiveData_DMA(current_rx_buffer, RX_BUFFER_SIZE);
}

3. 发送和接收数据

RS485的通信模式切换依赖于DE引脚的控制。在发送数据时,我们需要将DE引脚设置为高电平,切换到发送模式。在接收数据时,将DE引脚设置为低电平,切换到接收模式。

// RS485 发送模式
void RS485_SendEnable(void)
{
    HAL_GPIO_WritePin(RS485_DE_GPIO_PORT, RS485_DE_PIN, GPIO_PIN_SET);
}

// RS485 接收模式
void RS485_ReceiveEnable(void)
{
    HAL_GPIO_WritePin(RS485_DE_GPIO_PORT, RS485_DE_PIN, GPIO_PIN_RESET);
}

4. 数据传输(使用DMA)

通过DMA来传输数据,可以有效减轻CPU的负担。DMA允许USART直接将数据传输到内存,或者从内存传输到USART外设。

// 使用 DMA 发送数据
void RS485_SendData_DMA(uint8_t* data, uint16_t size)
{
    RS485_SendEnable();  // 切换到发送模式
    HAL_UART_Transmit_DMA(&huart2, data, size);  // 使用DMA发送数据
}

// 使用 DMA 接收数据
void RS485_ReceiveData_DMA(uint8_t* buffer, uint16_t size)
{
    HAL_UARTEx_ReceiveToIdle_DMA(&huart2, buffer, size);  // 使用DMA接收数据
}

双缓冲区实现

在这篇博客中,我将介绍如何在 RS485 数据接收过程中使用双缓冲区来提高数据的接收效率和稳定性。在此实现中,我们使用两个缓冲区交替存储数据,以确保数据接收的连续性,避免丢失数据。

1. 双缓冲区的定义与初始化

首先,我们定义了两个接收缓冲区 rx_buffer1rx_buffer2,以及两个指针 current_rx_buffernext_rx_buffer 来管理当前和下一个缓冲区。


```c
#define RX_BUFFER_SIZE 200

uint8_t rx_buffer1[RX_BUFFER_SIZE];  // 第一个接收缓冲区
uint8_t rx_buffer2[RX_BUFFER_SIZE];  // 第二个接收缓冲区
uint8_t* current_rx_buffer = rx_buffer1;  // 当前使用的接收缓冲区
uint8_t* next_rx_buffer = rx_buffer2;  // 下一个将用于接收数据的缓冲区

在初始化时,我们默认将 current_rx_buffer 指向 rx_buffer1,而 next_rx_buffer 指向 rx_buffer2

2. 接收回调函数

当DMA接收完成时,HAL_UARTEx_RxEventCallback 回调函数会被调用。此时,我们切换 current_rx_buffer 和 next_rx_buffer,并重新启动DMA接收,继续接收数据。

我们定义了一个全局数据处理函数指针 RS485_DataHandler,用于设置外部传入的回调函数。当数据接收完成时,中断回调函数会被触发,并执行回调函数来处理数据。

// 定义数据接收处理接口
typedef void (*RS485_DataHandler)(uint8_t* data, uint16_t size);

// 默认没有设置处理函数
static RS485_DataHandler dataHandler = NULL;

void RS485_SetDataHandler(RS485_DataHandler handler)
{
    dataHandler = handler;  // 设置外部数据处理函数
}

void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
    if (huart == &huart2) {
        // 如果设置了数据处理函数,调用该函数处理接收到的数据
        if (dataHandler != NULL) {
            dataHandler(current_rx_buffer, Size);
        }
        
        // 切换到下一个缓冲区
        uint8_t* temp_buffer = current_rx_buffer;
        current_rx_buffer = next_rx_buffer;
        next_rx_buffer = temp_buffer;
        
        // 重新启动DMA接收,接收数据到下一个缓冲区
        RS485_ReceiveData_DMA(current_rx_buffer, RX_BUFFER_SIZE);
    }
}

2.设置数据处理函数

通过调用 RS485_SetDataHandler,我们可以将外部的处理函数传入。当接收到数据时,系统会自动调用该函数来处理数据。

void RS485_SetDataHandler(RS485_DataHandler handler)
{
    dataHandler = handler;  // 设置外部数据处理函数
}

应用

在本例中,我将结合之前提到的 RS485 基本操作和双缓冲区机制,展示如何通过 RS485 协议控制 LED 的开关。这是一个简单的应用,通过发送特定的指令来控制 LED 的开关。

  1. RS485 初始化与发送
    首先,我们初始化 RS485 通信,并发送两个消息 msg1 和 msg2,这些消息将通过 RS485 发送到接收端。以下是初始化和发送消息的代码:
RS485_Init();  // 初始化 RS485(包括 UART2)

uint8_t msg1[] = "功能测试2发送模式测试\r\n";
uint8_t msg2[] = "功能测试2发送模式测试\r\n";

// 发送数据
RS485_SendData(msg1, sizeof(msg1) - 1);
RS485_SendData(msg2, sizeof(msg2) - 1);
  1. 接收并控制 LED
    我们在接收到数据后判断其内容,根据数据的不同控制 LED 的状态。通过 RS485_ReceiveData 函数接收数据,然后通过条件判断控制 LED 的开关。
uint8_t buffer[1];
RS485_ReceiveData(buffer, 1);

if (buffer[0] == '1') {
    LED_On(&LED1);  // 如果接收到 '1',打开 LED1
} else {
    LED_Off(&LED1); // 如果接收到其他字符,关闭 LED1
}

RS485_SendData("你好\r\n", sizeof("你好\r\n") - 1);  // 发送响应消息
HAL_Delay(1000);

3.数据处理函数:控制 LED

在应用中,我们可以为 RS485 接收数据设置一个回调函数 Control_Light,该函数根据接收到的数据来控制 LED 的开关。例如,当接收到特定数据 00 00 00 11 时,我们开启 LED;接收到 00 00 00 22 时,关闭 LED。

void Control_Light(uint8_t* data, uint16_t size)
{
    RS485_SendData(data, size);  // 发送当前缓冲区的数据

    // 只检查前4个字节
    if (size >= 4) {
        if (data[0] == 0x00 && data[1] == 0x00 && data[2] == 0x00) {
            if (data[3] == 0x11) {
                // 收到 00 00 00 11 时,开灯
                LED_On(&LED1);
            } else if (data[3] == 0x22) {
                // 收到 00 00 00 22 时,关灯
                LED_Off(&LED1);
            }
        }
    }
}

// 设置数据处理函数
RS485_SetDataHandler(Control_Light);
Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐