STM32F105使用UASRT串口 DMA接收

我的需求
1 使用串口接收一款姿态传感器,这款传感器默认可以25Hz反馈姿态数据,我需要用串口来接收这个姿态获取数据
2 使用串口发送指令,清零当前姿态数据,用于复位

看到这里建议了解一下DMA接收和中断接收的区别!!!
后续我会在补充这个知识点。
这篇文章不错
https://blog.csdn.net/xuanjianqiang/article/details/123083590
他说到的乒乓缓存,如果你确定你的2次接收不会在你拷贝数据的时候,可以不做;也可以遇到问题再做,接收速度不快的情况下,乒乓也可以不写,但是如果出了相关问题,你要有判断、解决问题的能力。

我的硬件
STM32F105开发板
IMU406 瑞芬科技的姿态传感器

我的IDE
STM32CUBEMX
VSCODE

核心思路
在串口空闲中断,获取DMA数据,解锁互斥量,在接收线程拷贝数据
也可以使用队列

注意
利用RX接收中断,去处理DMA数据。因为DMA只管接收,是不是接收一条完整数据它是无法识别的,所以需要利用RX中断来辅助判断,
需要注意 DMA是环形接收的,不及时处理会被新数据覆盖
这里的几个中断,默认是使用串口空闲中断来作为一个通信完成的,如果你分配的DMA区域比接收数据大几倍,正常不会触发DMA满中断的,这个用于特殊情况再使用。
DMA是只管在缓存区依次写入的,所以在一次接收之后,需要重置写入位置。
如果出现了在处理数据期间有数据接收,就要考虑乒乓接收了。

步骤
1 在CUBEMX配置串口,设置DMA
我使用了USART2,只有USART才可以使用DMA。串口模式为异步,没有硬件流控制,串口波特率115200, 8 1 N。
image

启用DMA接收,方向为 外设->内存 ,优先级自己看着选, 模式为循环 Circular 必选,否则需要自己在代码启动接收。这里要记住你的DMA通道,我的是DMA1 CHANNEL6
image

这个DMA中断应该会默认打勾! 全局中断也要打勾
image

注意确认IO是否正确,我的是PA2 PA3,注意是否需要上拉下拉(单线串口是需要的,否则接收不到数据或者会损坏串口),优先级自己定义
image

到这里CUBEMX配置结束,可以生成代码了

生成代码之后还需要
1 启用DMA接收,我是在main.c里处理的添加以下代码,注意是硬件初始化完之后才可以启用,我放在了最后(FreeRTOS启动之前)
`
// 启动串口2DMA接收
HAL_USART_Receive_DMA(&huart2, (uint8_t*)g_Uart1RxBuffer, sizeof(g_Uart1RxBuffer)); // 这一行是添加的

/* USER CODE END 2 */

/* Init scheduler */
osKernelInitialize();

/* Call init function for freertos objects (in cmsis_os2.c) */
MX_FREERTOS_Init();

/* Start scheduler */
osKernelStart();
`

2 定义变量 最好定义到你的imu文件里,按功能存放
`
uint8_t g_imu406_buf[128]; // 接收到的数据缓存
uint32_t g_imu406_buf_len; // 接收到的数据长度
uint8_t g_imuStatus = 0; // 状态标记

QueueHandle_t xUart2RxSema; // 串口2 接收队列
`

3 初始化 主要任务是启用串口中断、启动DMA接收
`
// 启动串口接收中断 空闲中断
__HAL_UART_ENABLE_IT(&huart2, UART_IT_IDLE);

xUart2RxSema = osSemaphoreNew(1, 1, NULL);   // 创建信号量

// 启动串口DMA接收
if (HAL_UART_Receive_DMA(&huart2, (uint8_t *)g_imu406_buf, sizeof(g_imu406_buf_len)) != HAL_OK)
// if (false)
{
    printf("E 启动串口2 DMA接收失败\r\n");
}

`

4 在中断添加处理
打开生成的 stm32f1xx_it.c ,找到 USART2_IRQHandler 函数(找自己对应串口硬件的名字),添加自己的处理,
我的操作是 关闭DMA,拷贝数据到队列,打开DMA
记得要清除串口中断状态,否则会反复触发。
`
uint32_t tic1, toc1; // 这个变量是我查看接收间隔的,可以去掉
/**

  • @brief This function handles USART2 global interrupt.
    /
    void USART2_IRQHandler(void)
    {
    /
    USER CODE BEGIN USART2_IRQn 0 */

// if ((__HAL_UART_GET_FLAG(&huart2, UART_FLAG_IDLE) != RESET) || (__HAL_UART_GET_FLAG(&huart2, UART_FLAG_RXNE) != RESET))
if (__HAL_UART_GET_FLAG(&huart2, UART_FLAG_IDLE) != RESET)
{
__HAL_UART_CLEAR_IDLEFLAG(&huart2); // 清除中断标志
// 获取本次接收数据长度
g_imu406_buf_len = sizeof(g_imu406_buf) - __HAL_DMA_GET_COUNTER(huart2.hdmarx);
printf("current tick: %u\n", xTaskGetTickCount());
if(xUart2RxSema!= NULL )
{
Reset_DMA_Address(&hdma_usart2_rx, (uint32_t)g_imu406_buf, sizeof(g_imu406_buf));

  osSemaphoreRelease(xUart2RxSema);
}

}

/* USER CODE END USART2_IRQn 0 /
HAL_UART_IRQHandler(&huart2);
/
USER CODE BEGIN USART2_IRQn 1 */

/* USER CODE END USART2_IRQn 1 */
}
`

5 在对应的接收任务等待信号量,解析
`
void imu406_taskReceive(void *arg)
{
while (true)
{
// 等待接收到数据
if (osSemaphoreAcquire(xUart2RxSema, osWaitForever) == osOK) {

        // printf("I 接收数据长度%d\n", g_imu406_buf_len);
        memcpy(g_imu406_buf2, g_imu406_buf, g_imu406_buf_len);  // 拷贝数据到临时缓存
        g_imu406_buf_len2 = g_imu406_buf_len;  // 临时缓存长度

        // 复位缓存区
        memset(g_imu406_buf, 0, sizeof(g_imu406_buf));
        g_imu406_buf_len = 0;
        
        // 开始解析
        imu406_parse();
    }
}

osThreadExit();

}
`

附完整代码
imu406.h
`

ifndef IMU406_H

define IMU406_H

include <stdint.h>

include <stdio.h>

include <math.h>

include <cmsis_os2.h>

include <stdbool.h>

define IMU406_ADDR 0x68 // I2C address of the IMU406

define IMU406_PUB_MSG_LEN 32 // 传感器发布消息的长度

// 数据类型
/// @brief 加速度计数据
typedef struct {
float roll, pitch, yaw; // 单位:度/s
float accx, accy, accz; // 单位:m/s^2
float gyrox, gyroy, gyroz; // 单位:°/s

} KImu406Data_t;

/// @brief 传感器消息
typedef struct {
uint32_t msgLen; // 消息长度

} KImu406Message_t;

extern uint8_t g_imu406_buf[128]; // 接收到的数据缓存
extern uint32_t g_imu406_buf_len; // 接收到的数据长度
extern uint8_t g_imuStatus; // 状态标记
extern KImu406Data_t g_imu406_data; // 传感器数据

void imu406_init(void);

/// @brief 从缓存获取数据,存储到g_imu406_buf,数量存到g_imu406_buf_len
/// @param
void imu406_read(void);

/// @brief 从g_imu406_buf解析数据,存储到变量
/// @param
void imu406_parse(void);

// bcd转换为float
float imu406_bcd_to_float(uint8_t *fpbcd, uint fbuf_len, uint8_t decimal_places);

/// @brief 接收数据任务
/// @param arg
void imu406_taskReceive(void* arg);

endif //IMU406_H

`

imu406.c
`

include "imu406.h"

include "usart.h"

// 变量定义
uint8_t g_imu406_buf[128]; // 接收到的数据缓存
uint32_t g_imu406_buf_len; // 接收到的数据长度
uint8_t g_imuStatus = 0; // 状态标记
KImu406Data_t g_imu406_data; // 传感器数据

uint8_t g_imu406_buf2[128]; // 2次缓存,存储用于解析的数据
uint32_t g_imu406_buf_len2; // 接收到的数据长度

osSemaphoreId_t xUart2RxSema; // 串口2 接收队列

void imu406_init(void)
{
// 数据初始化
memset(g_imu406_buf, 0, sizeof(g_imu406_buf));
g_imu406_buf_len = 0;
g_imuStatus = 0;

// 启动串口接收中断 空闲中断
__HAL_UART_ENABLE_IT(&huart2, UART_IT_IDLE);

xUart2RxSema = osSemaphoreNew(1, 1, NULL);   // 创建信号量

// 启动串口DMA接收
if (HAL_UART_Receive_DMA(&huart2, (uint8_t *)g_imu406_buf, sizeof(g_imu406_buf_len)) != HAL_OK)
// if (false)
{
    printf("E 启动串口2 DMA接收失败\r\n");
}
else
{
    printf("I 启动串口2 DMA接收成功\r\n");

    xUart2RxSema = xSemaphoreCreateBinary();  // 创建信号量

    // 启动串口解析
    osThreadAttr_t attr = 
    {
        .name = "imu406_taskReceive",
       .stack_size = 1024,
        .priority = osPriorityNormal,

    };
    osThreadNew(imu406_taskReceive, NULL, &attr);  // 创建任务
}

}

void imu406_read(void)
{
}

void imu406_parse(void)
{
// 检测 如果数据数量小于最小信息长度 报错
if (g_imu406_buf_len2 < IMU406_PUB_MSG_LEN) {
printf("imu406_parse: data len error\n");
return;
}

if(g_imu406_buf2[3] == 0x84)  // 数据类型
{
    // 解析数据
    KImu406Data_t imuData;  // 临时存储
    imuData.roll = imu406_bcd_to_float(&g_imu406_buf2[4], 3, 2);  // 俯仰角
    imuData.pitch = imu406_bcd_to_float(&g_imu406_buf2[7], 3, 2);  // 横滚角
    imuData.yaw = imu406_bcd_to_float(&g_imu406_buf2[10], 3, 2);  // 偏航角
    imuData.accx = imu406_bcd_to_float(&g_imu406_buf2[13], 3, 2);  // 加速度X
    imuData.accy = imu406_bcd_to_float(&g_imu406_buf2[16], 3, 2);  // 加速度Y
    imuData.accz = imu406_bcd_to_float(&g_imu406_buf2[19], 3, 2);  // 加速度Z

    imuData.gyrox = imu406_bcd_to_float(&g_imu406_buf2[22], 3, 2);  // 角速度X
    imuData.gyroy = imu406_bcd_to_float(&g_imu406_buf2[25], 3, 2);  // 角速度Y
    imuData.gyroz = imu406_bcd_to_float(&g_imu406_buf2[28], 3, 2);  // 角速度Z

    memcpy(&g_imu406_data, &imuData, sizeof(KImu406Data_t));

    printf("I 解析数据成功\n");
    printf("roll:%d, pitch:%d, yaw:%d\n", (int)imuData.roll, (int)imuData.pitch, (int)imuData.yaw);
    printf("accx:%d, accy:%d, accz:%d\n", (int)imuData.accx, (int)imuData.accy, (int)imuData.accz);
    printf("gyrox:%d, gyroy:%d, gyroz:%d\n", (int)imuData.gyrox, (int)imuData.gyroy, (int)imuData.gyroz);
    printf("I ------------------------\n");
}
else
{

}

}

/// @brief
/// @param fpbcd
/// @param fbuf_len
/// @param decimal_places
/// @return
float imu406_bcd_to_float(uint8_t *fpbcd, uint fbuf_len, uint8_t decimal_places)
{
if(fbuf_len == 0 || fbuf_len > 10)
{
printf("E imu406_bcd_to_float: fbuf_len is 0 or 10\n");
return 0.0f;
}

/*
    规则为 BCD的最高位为1,是负数,为0 是正数
    一个数据包含3个BCD数,c1 c2 c3, c1高位为正负符号,c1低位 c2 c3 为数据

    本函数可用于大于0长度的数据
*/

float value ;  // 结果值
int bit_len = fbuf_len * 2;  // 总位数
uint32_t* val_bit = (uint32_t*) malloc(bit_len * sizeof(uint32_t));  // 位值数组
float tmp_value = 0.0f;  // 临时值

// 获取每个位
for (size_t i = 0; i < fbuf_len; i++)
{
    val_bit[i * 2] = (fpbcd[i] >> 4);
    val_bit[i * 2 + 1] = (fpbcd[i] & 0x0F);
}

float dir = val_bit[0] & 0x10 ? -1 : 1;  // 方向系数 正负1
for(size_t i = 1; i < bit_len; i++)
{
    tmp_value += (val_bit[i] & 0x0F) * pow(10, bit_len - 1 - i);  // 计算值
}

value = tmp_value * dir / powf(10, decimal_places);  // 结果值

free(val_bit);
return value;  // 结果值

}

void imu406_taskReceive(void *arg)
{
while (true)
{
// 等待接收到数据
if (osSemaphoreAcquire(xUart2RxSema, osWaitForever) == osOK) {

        // printf("I 接收数据长度%d\n", g_imu406_buf_len);
        memcpy(g_imu406_buf2, g_imu406_buf, g_imu406_buf_len);  // 拷贝数据到临时缓存
        g_imu406_buf_len2 = g_imu406_buf_len;  // 临时缓存长度

        // 复位缓存区
        memset(g_imu406_buf, 0, sizeof(g_imu406_buf));
        g_imu406_buf_len = 0;
        
        // 开始解析
        imu406_parse();
    }
}

osThreadExit();

}
`

stm32f1xx_it.c中修改的部分
`
// 声明使用的全局变量 在外部
extern SemaphoreHandle_t xUart2RxSema; // 串口6 接收队列

extern uint8_t g_imu406_buf[128]; // 接收到的数据缓存
extern uint32_t g_imu406_buf_len; // 接收到的数据长度

uint32_t tic1, toc1;
/**

  • @brief This function handles USART2 global interrupt.
    /
    void USART2_IRQHandler(void)
    {
    /
    USER CODE BEGIN USART2_IRQn 0 */

// if ((__HAL_UART_GET_FLAG(&huart2, UART_FLAG_IDLE) != RESET) || (__HAL_UART_GET_FLAG(&huart2, UART_FLAG_RXNE) != RESET))
if (__HAL_UART_GET_FLAG(&huart2, UART_FLAG_IDLE) != RESET)
{
__HAL_UART_CLEAR_IDLEFLAG(&huart2); // 清除中断标志
// 获取本次接收数据长度
g_imu406_buf_len = sizeof(g_imu406_buf) - __HAL_DMA_GET_COUNTER(huart2.hdmarx);
printf("current tick: %u\n", xTaskGetTickCount());
if(xUart2RxSema!= NULL )
{
Reset_DMA_Address(&hdma_usart2_rx, (uint32_t)g_imu406_buf, sizeof(g_imu406_buf));

  osSemaphoreRelease(xUart2RxSema);
}

}

/* USER CODE END USART2_IRQn 0 /
HAL_UART_IRQHandler(&huart2);
/
USER CODE BEGIN USART2_IRQn 1 */

/* USER CODE END USART2_IRQn 1 */
}
`

posted @ 2025-07-19 11:13  侠盗一号  阅读(99)  评论(0)    收藏  举报