基于STM32实现RS485通信及DMA空闲中断、IT回调机制
首先,我们定义了两个接收缓冲区rx_buffer1和rx_buffer2,以及两个指针和来管理当前和下一个缓冲区。// 第一个接收缓冲区 uint8_t rx_buffer2 [ RX_BUFFER_SIZE ];// 第二个接收缓冲区 uint8_t * current_rx_buffer = rx_buffer1;// 当前使用的接收缓冲区 uint8_t * next_rx_buffer =
基于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_buffer1 和 rx_buffer2,以及两个指针 current_rx_buffer 和 next_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 的开关。
- 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);
- 接收并控制 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);
更多推荐



所有评论(0)