前言
STM32CubeMX USART–dma数据接收【1】
一、CUBEMX配置
不用勾选usart的全局中断。
二、代码
1.main.c
进行dma的初始化
HAL_UART_Receive_DMA(&huart4, rx_buffer_usart4, 6);//打开串口接收中断用于接收和发送陀螺仪的数据
2.接收函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == UART4)
{
// 检查帧头和帧尾
if (rx_buffer_usart4[0] == 0xAA && rx_buffer_usart4[5] == 0xCC)
{
uint32_t float_data_as_int = (rx_buffer_usart4[1] << 24) | (rx_buffer_usart4[2] << 16) | (rx_buffer_usart4[3] << 8) | rx_buffer_usart4[4];
float data;
memcpy(&data, &float_data_as_int, sizeof(float));
// 在这里处理解析得到的 float 数据
Yaw_angle = data;
}
else
{
// 如果帧头和帧尾不匹配,可以在这里添加错误处理逻辑
}
// 重新启动 DMA 接收
HAL_UART_Receive_DMA(&huart4, rx_buffer_usart4, 6);//由于开启的是循环模式所以这个可以写
}
}
DMA额外讲解
DMA(Direct Memory Access)是一种硬件机制,允许外设(如 USART)直接与内存之间传输数据,而无需 CPU的干预。这样可以减轻 CPU 负担,提高数据传输的效率。
在使用 DMA 的情况下,当 USART 收到数据时,数据会自动通过 DMA 传输到内存缓冲区。在配置 DMA时,您可以选择数据传输的模式:普通模式和循环模式。
普通模式:当 DMA 接收到指定数量的数据后,DMA 传输会停止。在这种模式下,一旦 DMA 缓冲区被填满,DMA传输会自动停止,并触发中断。在回调函数中,您需要手动重新启动 DMA 接收。
循环模式:在循环模式下,DMA 在传输指定数量的数据后,会自动从头开始接收新的数据。当 DMA 缓冲区被填满时,将触发中断,但 DMA会自动继续接收新数据。
在您提供的代码中,由于您使用了循环模式(DMA_CIRCULAR
),所以每次缓冲区满(接收到指定数量的数据)时,DMA 会触发中断并自动继续接收。在HAL_UART_RxCpltCallback
函数中,您可以处理接收到的数据,然后重新启动 DMA 接收(在循环模式下,实际上不需要重新启动,因为 DMA 已经自动继续接收了)。
总之,使用 DMA 循环模式时,每次接收到指定数量的数据时,都会触发中断,并在回调函数中处理接收到的数据。
1示例:
在使用 DMA 循环模式时,有可能出现您提到的这种情况。如果数据流中发生了丢失或错误,导致帧头和帧尾不再与预期的缓冲区索引对齐,那么就会发生这种情况。为了解决这个问题,您可以在回调函数中实现一个简单的帧同步机制,以确保始终正确地处理帧头和帧尾。
uint8_t frame_sync = 0;
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
if (huart->Instance == USART2) {
for (uint8_t i = 0; i < RX_BUFFER_SIZE_USART2 - 1; i++) {
if (rx_buffer_usart2[i] == 0xAA && rx_buffer_usart2[i + 5] == 0xCC) {
frame_sync = 1;
uint32_t float_data_as_int = (rx_buffer_usart2[i + 1] << 24) | (rx_buffer_usart2[i + 2] << 16) | (rx_buffer_usart2[i + 3] << 8) | rx_buffer_usart2[i + 4];
float data;
memcpy(&data, &float_data_as_int, sizeof(float));
// 在这里处理解析得到的 float 数据
Yaw_angle = data;
break;
}
}
if (!frame_sync) {
// 如果没有找到帧头和帧尾,可以在这里添加错误处理逻辑
} else {
frame_sync = 0;
}
// 重新启动 DMA 接收
HAL_UART_Receive_DMA(&huart2, rx_buffer_usart2, RX_BUFFER_SIZE_USART2);
}
}
在这个示例中,我们遍历整个缓冲区,寻找帧头和帧尾的正确位置。一旦找到正确的帧头和帧尾,我们就会解析其中的数据,并在下次回调中重置 frame_sync 标志。这样,即使数据不完全对齐,我们仍然可以找到正确的帧头和帧尾并处理数据。
需要注意的是,这个方法可能会降低性能,因为我们需要遍历整个缓冲区。但是,对于低速数据传输和较小的缓冲区来说,这种方法通常是可行的。您可以根据实际应用场景进行优化。
2示例:
确实有一种更优的方法来解决这个问题,那就是使用 DMA 的"半传输完成"(Half-Transfer Complete)中断。这样,当 DMA 完成一半的传输时,即缓冲区的前一半已经填充完毕,就会触发一个中断。然后,当 DMA 完成整个传输时,即缓冲区的后一半已经填充完毕,又会触发一个中断。这样,我们可以在每次中断中处理一半的缓冲区,从而保证不会丢失数据。
下面是如何使用 “半传输完成” 中断的示例:
- 首先,将缓冲区大小加倍(以便为半传输完成中断留出空间):
#define RX_BUFFER_SIZE_USART2 12
uint8_t rx_buffer_usart2[RX_BUFFER_SIZE_USART2];
- 在初始化 DMA 时,启用半传输完成中断:
// 启用半传输完成中断
__HAL_DMA_ENABLE_IT(&hdma_usart2_rx, DMA_IT_HT);
- 修改回调函数以处理半传输完成中断:
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
if (huart->Instance == USART2) {
process_usart2_buffer(rx_buffer_usart2, 6);
}
}
void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart) {
if (huart->Instance == USART2) {
process_usart2_buffer(rx_buffer_usart2 + 6, 6);
}
}
void process_usart2_buffer(uint8_t *buffer, uint16_t size) {
// 检查帧头和帧尾
if (buffer[0] == 0xAA && buffer[5] == 0xCC) {
uint32_t float_data_as_int = (buffer[1] << 24) | (buffer[2] << 16) | (buffer[3] << 8) | buffer[4];
float data;
memcpy(&data, &float_data_as_int, sizeof(float));
// 在这里处理解析得到的 float 数据
Yaw_angle = data;
} else {
// 如果帧头和帧尾不匹配,可以在这里添加错误处理逻辑
uint8_t found_header = 0;
for (int i = 1; i < size; ++i) {
if (buffer[i] == 0xAA) {
found_header = 1;
memmove(buffer, buffer + i, size - i);
break;
}
}
if (found_header) {
// 重新启动 DMA 接收,但缩短接收数据的长度
HAL_UART_AbortReceive(&huart2);
HAL_UART_Receive_DMA(&huart2, buffer + (size - 1), 1);
} else {
// 帧头未找到,可以记录错误或执行其他操作
}
}
}
这个 process_usart2_buffer 函数会检查给定缓冲区的帧头和帧尾,如果找到,则处理数据。如果帧头和帧尾不匹配,它将尝试在缓冲区中找到下一个帧头,并调整 DMA 接收以适应新的帧头位置。这样,即使发生帧丢失,你的代码也可以尽快恢复到正确的帧同步状态。