【嵌入式开发学习】第9天:DMA 传输 + FreeRTOS 入门(提升系统效率与多任务管理)

核心目标:掌握 DMA(直接内存访问)的 “无 CPU 干预传输” 特性,解决串口 / I2C 传输占用 CPU 的问题;入门 FreeRTOS 实时操作系统,实现 “温湿度采集 + LED 闪烁 + 串口指令响应” 多任务并行,让系统从 “单任务循环” 升级为 “多任务协同”—— 这是嵌入式系统从 “简单控制” 到 “复杂应用” 的关键提升。

一、DMA 原理:嵌入式的 “数据搬运工”(30 分钟)

DMA(Direct Memory Access,直接内存访问)是 STM32 的核心外设,作用是 “在不占用 CPU 的情况下,直接在外设(如串口、I2C)和内存之间传输数据”,相当于专门的 “数据搬运工”,解放 CPU 去处理更重要的任务。

1. 核心痛点:传统传输的 CPU 占用问题

之前用HAL_UART_Transmit(阻塞发送)或中断发送时,CPU 需要参与数据搬运:

  • 阻塞发送:CPU 等待数据发送完成,期间无法做其他事;
  • 中断发送:每次发送 1 字节 / 多字节都要触发中断,CPU 频繁响应中断,效率低;
  • DMA 传输:CPU 只需初始化 “搬运任务”(如 “把内存缓冲区的 100 字节通过串口发送”),之后 DMA 自动完成传输,CPU 可并行处理其他任务(如传感器采集、LED 控制)。
2. DMA 核心概念(新手必懂)
  • 传输方向:外设→内存(如 I2C 接收传感器数据到内存)、内存→外设(如内存数据通过串口发送);
  • 通道:每个外设对应专属 DMA 通道(如 USART1_TX 对应 DMA1_Channel4,USART1_RX 对应 DMA1_Channel5,需查 STM32 datasheet 确认);
  • 传输模式:正常模式(传输一次后停止)、循环模式(反复传输指定数据);
  • 触发源:由外设事件触发 DMA 传输(如串口发送数据寄存器空时,触发 DMA 送数据)。
3. HAL 库 DMA 核心函数(重点记)
  • 串口 DMA 发送:HAL_UART_Transmit_DMA(&huart1, 发送缓冲区, 数据长度);
  • 串口 DMA 接收:HAL_UART_Receive_DMA(&huart1, 接收缓冲区, 数据长度);
  • DMA 传输完成回调:HAL_UART_TxCpltCallback(发送完成)、HAL_UART_RxCpltCallback(接收完成);
  • 关键:DMA 传输无需 CPU 干预,回调函数仅用于通知 CPU “传输完成”(如更新缓冲区数据)。

二、实操 1:DMA 串口发送(解放 CPU,实现高效数据上传)

目标:用 DMA 替代传统串口发送,实现温湿度数据 1 秒上传(定时器触发),同时主循环中执行 LED 闪烁(验证 CPU 未被占用)。

步骤 1:CubeIDE 配置(DMA + 串口 + 定时器 + GPIO)
  1. 新建工程 “DMA_UART_Send”,选择 STM32F103C8T6;
  2. 配置串口 USART1:异步模式,波特率 9600,PA9=TX,PA10=RX;
  3. 启用 DMA 发送:
    • 打开 USART1 配置页,点击 “DMA Settings”→“Add”,添加 TX 方向 DMA;
    • 配置 DMA 参数:Channel 选 “DMA1_Channel4”(USART1_TX 对应通道)、Direction 选 “Memory to Peripheral”(内存→外设)、Mode 选 “Normal”(正常模式);
  4. 配置定时器 TIM3:1 秒中断一次(PSC=7199,ARR=9999),使能中断;
  5. 配置 LED(PC13=GPIO_Output);
  6. 生成初始化代码。
步骤 2:编写 DMA 发送逻辑
  1. 定义全局变量(main.c的 “USER CODE BEGIN 2” 区域):

c

/* USER CODE BEGIN 2 */
uint32_t timer_count = 0;
float temp = 25.5, humi = 45.2;  // 模拟温湿度数据
uint8_t send_buf[50];  // DMA发送缓冲区
/* USER CODE END 2 */
  1. 主函数初始化(启动定时器和 LED 初始状态):

c

/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start_IT(&htim3);
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET);  // LED初始熄灭
/* USER CODE END 2 */
  1. 定时器中断回调(触发 DMA 发送温湿度数据):

c

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
  if (htim->Instance == TIM3) {
    // 1. 格式化温湿度数据到缓冲区
    sprintf((char*)send_buf, "DMA发送:温度=%.1f℃,湿度=%.1f%%RH\r\n", temp, humi);
    // 2. DMA串口发送(无需CPU等待,直接返回)
    HAL_UART_Transmit_DMA(&huart1, send_buf, strlen((char*)send_buf));
    // 3. 模拟数据更新
    temp += 0.1;
    if (temp > 30.0) temp = 25.0;
  }
}
  1. 主循环(LED 闪烁,验证 CPU 空闲):

c

while (1)
{
  /* 主循环执行LED闪烁,不受DMA发送影响 */
  HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
  HAL_Delay(300);  // 300ms翻转一次,LED快速闪烁
  /* USER CODE END WHILE */
}
步骤 3:下载测试
  1. 串口助手配置 9600 波特率,打开串口;
  2. 现象:串口每秒收到一条 DMA 发送的数据,同时 LED 快速闪烁 —— 证明 DMA 传输未占用 CPU,CPU 可并行处理多个任务。

三、FreeRTOS 入门:嵌入式多任务管理(40 分钟)

FreeRTOS 是开源实时操作系统(RTOS),核心作用是 “管理多个任务,让 CPU 按优先级轮流执行”,解决传统 “单任务循环” 中 “一个任务阻塞,整个系统卡住” 的问题。

1. FreeRTOS 核心概念(新手必懂)
  • 任务:最小执行单元(如 “温湿度采集任务”“LED 闪烁任务”“串口指令处理任务”),每个任务有独立的栈空间和优先级;
  • 任务调度:FreeRTOS 的调度器按任务优先级,让高优先级任务优先执行,低优先级任务在高优先级任务空闲时执行;
  • 任务状态:就绪(等待执行)、运行(正在执行)、阻塞(等待事件,如延时、信号量);
  • 核心 API(HAL 库适配版):
    • 创建任务:xTaskCreate(任务函数, 任务名, 栈大小, 参数, 优先级, 任务句柄);
    • 启动调度器:vTaskStartScheduler();(启动后,CPU 由 FreeRTOS 管理);
    • 任务延时:vTaskDelay(延时 ticks);(ticks 是系统节拍,默认 1ms/tick)。
2. CubeIDE 配置 FreeRTOS
  1. 新建工程 “FreeRTOS_Demo”,选择 STM32F103C8T6;
  2. 启用 FreeRTOS:左侧 “Middleware→FreeRTOS”,选择 “CMSIS_V1”(新手友好);
  3. 配置任务(可视化创建,无需手动写 API):
    • 点击 “Tasks and Queues”→“Add”,创建 3 个任务:
      1. 任务 1:TempHumi_Task(温湿度采集,优先级 2);
      2. 任务 2:LED_Blink_Task(LED 闪烁,优先级 1);
      3. 任务 3:UART_Process_Task(串口指令处理,优先级 3);
  4. 配置串口 USART1(9600 波特率)、GPIO(PC13=LED);
  5. 生成初始化代码(CubeIDE 自动生成 FreeRTOS 底层代码和任务框架)。

四、实操 2:FreeRTOS 多任务并行(3 个任务协同工作)

目标:实现 3 个任务并行执行,验证 “高优先级任务优先响应,低优先级任务后台运行”。

步骤 1:编写各任务逻辑(在main.c的 “USER CODE BEGIN 5” 区域)

FreeRTOS 任务函数是 “无限循环 + 阻塞延时” 的结构(避免 CPU 空转):

  1. 温湿度采集任务(优先级 2,1 秒采集一次):

c

/* USER CODE BEGIN 5 */
// 温湿度采集任务
void TempHumi_Task(void const * argument)
{
  float temp = 25.0, humi = 45.0;
  uint8_t send_buf[50];
  for(;;)  // 任务无限循环
  {
    // 模拟采集+格式化数据
    sprintf((char*)send_buf, "多任务:温度=%.1f℃,湿度=%.1f%%RH\r\n", temp, humi);
    // 串口发送(用之前的DMA或阻塞发送均可)
    HAL_UART_Transmit(&huart1, send_buf, strlen((char*)send_buf), 100);
    // 任务延时1000ms(阻塞状态,CPU可执行其他任务)
    vTaskDelay(1000);
    // 模拟数据更新
    temp += 0.2;
    if (temp > 30.0) temp = 25.0;
  }
}

// LED闪烁任务(优先级1,500ms翻转一次)
void LED_Blink_Task(void const * argument)
{
  for(;;)
  {
    HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
    vTaskDelay(500);  // 延时500ms
  }
}

// 串口指令处理任务(优先级3,最高,实时响应指令)
void UART_Process_Task(void const * argument)
{
  uint8_t recv_buf[10] = {0};
  uint8_t send_buf[50] = {0};
  for(;;)
  {
    // 串口接收指令(阻塞接收,等待电脑发送数据)
    HAL_UART_Receive(&huart1, recv_buf, 3, portMAX_DELAY);  // portMAX_DELAY:无限等待
    // 解析指令
    if (strcmp((char*)recv_buf, "ON\r") == 0) {
      HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);
      sprintf((char*)send_buf, "指令响应:LED已点亮\r\n");
    } else if (strcmp((char*)recv_buf, "OFF\r") == 0) {
      HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET);
      sprintf((char*)send_buf, "指令响应:LED已熄灭\r\n");
    } else {
      sprintf((char*)send_buf, "指令响应:无效指令(支持ON/OFF)\r\n");
    }
    // 发送响应
    HAL_UART_Transmit(&huart1, send_buf, strlen((char*)send_buf), 100);
    // 清空接收缓冲区
    memset(recv_buf, 0, sizeof(recv_buf));
  }
}
/* USER CODE END 5 */
步骤 2:启动 FreeRTOS 调度器(CubeIDE 自动生成,无需手动修改)

主函数中,CubeIDE 已自动添加调度器启动代码:

c

int main(void)
{
  // 初始化硬件(省略自动生成代码)
  MX_FREERTOS_Init();  // 初始化FreeRTOS任务
  vTaskStartScheduler();  // 启动调度器,CPU开始执行任务
  // 启动后不会执行到这里(除非调度器启动失败)
  for(;;)
  {
  }
}
步骤 3:下载测试(多任务并行验证)
  1. 串口助手配置 9600 波特率,打开串口;
  2. 现象:
    • LED 每 500ms 闪烁一次(LED_Blink_Task);
    • 串口每 1 秒收到一条温湿度数据(TempHumi_Task);
    • 发送 “ON” 指令,LED 立即点亮并收到响应(UART_Process_Task 优先级最高,实时响应);
    • 发送 “OFF” 指令,LED 立即熄灭 ——3 个任务并行执行,互不干扰。

五、第九天必掌握的 3 个关键知识点(10 分钟自测)

  1. DMA 核心价值:无 CPU 干预传输,解放 CPU,提升串口 / I2C 等外设的传输效率;
  2. FreeRTOS 核心逻辑:任务是无限循环 + 阻塞延时,调度器按优先级分配 CPU 资源,高优先级任务优先执行;
  3. 多任务优势:解决单任务循环的阻塞问题(如串口接收等待不会卡住 LED 闪烁),适合复杂系统。

总结

今天掌握了嵌入式系统 “效率提升” 和 “复杂任务管理” 的两大核心工具:DMA 解决了 “数据传输占用 CPU” 的痛点,FreeRTOS 实现了 “多任务并行”,让系统能同时处理采集、控制、通信等多个任务 —— 这正是实际嵌入式产品(如智能硬件、工业控制器)的核心架构。

<think>好的,用户的问题是关于在STM32 CubeMX中配置FreeRTOS使用DMA传输的。首先,我需要回忆一下STM32开发的相关知识,特别是CubeMX的配置步骤、FreeRTOS的集成以及DMA的使用方法。 用户提到了引用[1]中的内容,其中提到了在CubeMX中打开FreeRTOS并保持默认配置,新手一般不需要改动。这可能涉及到FreeRTOS的初始配置步骤,但用户现在需要结合DMA进行数据传输,比如串口的空闲中断和DMA接收。所以,我需要将这三个部分结合起来:CubeMX配置、FreeRTOS任务创建和DMA的设置。 首先,配置CubeMX的基础部分,比如时钟树、项目设置等。然后启用FreeRTOS,确保在Middleware中选择正确的版本,并创建任务。这里可能需要设置任务堆栈大小、优先级等参数,但根据引用[1]的提示,新手可以保持默认,但需要确认是否需要调整以适应DMA传输的需求。 接下来是配置DMA,比如配置串口的DMA通道。用户可能使用的是UART,需要启用DMA传输,并设置空闲中断。这部分需要配置NVIC,开启空闲中断,并在代码中编写中断处理函数,这可能涉及到CubeMX生成的HAL库函数的使用,比如HAL_UARTEx_ReceiveToIdle_DMA。 然后,结合FreeRTOS的消息队列,确保DMA传输完成后的数据处理可以在任务间通信。需要创建消息队列,并在DMA完成中断或回调函数中发送消息到队列,任务则接收并处理这些数据。 需要注意的是,HAL库的DMA传输函数可能会在传输完成时调用回调函数,而使用FreeRTOS时,中断服务例程中需要使用FreeRTOS的API,如xQueueSendFromISR,并处理任务切换。 可能存在一些常见问题,比如DMA缓冲区溢出、任务优先级设置不当导致数据丢失,或者中断配置错误导致无法触发。需要提醒用户检查这些点,并确保CubeMX生成的代码正确集成FreeRTOSDMA设置。 最后,生成相关问题,可能涉及更详细的配置步骤、调试方法或性能优化,比如如何调整任务优先级、处理DMA中断冲突,或者使用其他外设的DMA配置。</think>### 如何在STM32 CubeMX中配置FreeRTOS使用DMA传输 #### **步骤1:基础工程配置** 1. 打开STM32CubeMX,选择目标MCU型号(如STM32F4/F7/H7系列)。 2. 配置系统时钟(RCC)和调试接口(SYS),确保时钟树设置正确。 3. 在`Middleware`分类下启用`FREERTOS`,选择CMSIS_V1或CMSIS_V2模式(建议默认CMSIS_V1)[^1]。 #### **步骤2:FreeRTOS任务队列配置** 1. 在FreeRTOS配置界面(`Config Parameters`)中,调整以下参数: - `TOTAL_HEAP_SIZE`:根据DMA缓冲区大小和应用需求调整堆空间(例如4096字节)。 - 在`Tasks and Queues`标签页创建任务,设置任务优先级和堆栈大小(例如默认任务优先级为`osPriorityNormal`)。 2. 创建消息队列(用于DMA数据传输): ```c osMessageQueueId_t dmaQueue; dmaQueue = osMessageQueueNew(10, sizeof(DMA_Data), NULL); //队列容量10,数据块大小为自定义结构体 ``` #### **步骤3:DMA串口配置(以UART空闲中断为例)** 1. 启用USART/UART外设,选择`Asynchronous`模式。 2. 打开DMA设置标签页: - 添加`RX`方向的DMA请求(如`UART1_RX`),配置为循环模式(Circular)或普通模式(Normal)。 - 设置数据宽度(通常为`Byte`),优先级根据需求调整。 3. 启用空闲中断(Idle Interrupt): - 在NVIC设置中勾选`USARTx global interrupt`和`USARTx idle interrupt`。 #### **步骤4:生成代码关键函数添加** 1. 生成代码后,在`main.c`中补充以下内容: - **DMA启动**:在任务中调用HAL库函数启动DMA接收: ```c HAL_UARTEx_ReceiveToIdle_DMA(&huart1, buffer, BUFFER_SIZE); ``` - **空闲中断回调**:在`stm32xx_it.c`中实现空闲中断处理,并通过队列传递数据: ```c void USART1_IRQHandler(void) { if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE)) { __HAL_UART_CLEAR_IDLE_FLAG(&huart1); uint16_t len = BUFFER_SIZE - __HAL_DMA_GET_COUNTER(huart1.hdmarx); osMessageQueuePut(dmaQueue, &len, 0, 0); //将接收长度发送到队列 } HAL_UART_IRQHandler(&huart1); } ``` - **任务处理**:在FreeRTOS任务中读取队列并处理数据: ```c void DataProcessTask(void *argument) { uint16_t dataLen; while (1) { if (osMessageQueueGet(dmaQueue, &dataLen, NULL, osWaitForever) == osOK) { //处理buffer[0]到buffer[dataLen-1]的数据 } } } ``` #### **关键注意事项** 1. **DMAFreeRTOS的线程安全**:在DMA中断中调用FreeRTOS API时,需使用`FromISR`后缀的函数(如`xQueueSendFromISR`)。 2. **缓冲区管理**:建议使用双缓冲区策略,避免数据处理过程中DMA覆盖未处理的数据。 3. **优先级冲突**:确保DMA中断优先级低于FreeRTOS的调度器中断优先级(通过CubeMX的NVIC配置调整)。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值