核心目标:掌握 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)
- 新建工程 “DMA_UART_Send”,选择 STM32F103C8T6;
- 配置串口 USART1:异步模式,波特率 9600,PA9=TX,PA10=RX;
- 启用 DMA 发送:
- 打开 USART1 配置页,点击 “DMA Settings”→“Add”,添加 TX 方向 DMA;
- 配置 DMA 参数:Channel 选 “DMA1_Channel4”(USART1_TX 对应通道)、Direction 选 “Memory to Peripheral”(内存→外设)、Mode 选 “Normal”(正常模式);
- 配置定时器 TIM3:1 秒中断一次(PSC=7199,ARR=9999),使能中断;
- 配置 LED(PC13=GPIO_Output);
- 生成初始化代码。
步骤 2:编写 DMA 发送逻辑
- 定义全局变量(
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 */
- 主函数初始化(启动定时器和 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 */
- 定时器中断回调(触发 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;
}
}
- 主循环(LED 闪烁,验证 CPU 空闲):
c
while (1)
{
/* 主循环执行LED闪烁,不受DMA发送影响 */
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
HAL_Delay(300); // 300ms翻转一次,LED快速闪烁
/* USER CODE END WHILE */
}
步骤 3:下载测试
- 串口助手配置 9600 波特率,打开串口;
- 现象:串口每秒收到一条 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
- 新建工程 “FreeRTOS_Demo”,选择 STM32F103C8T6;
- 启用 FreeRTOS:左侧 “Middleware→FreeRTOS”,选择 “CMSIS_V1”(新手友好);
- 配置任务(可视化创建,无需手动写 API):
- 点击 “Tasks and Queues”→“Add”,创建 3 个任务:
- 任务 1:TempHumi_Task(温湿度采集,优先级 2);
- 任务 2:LED_Blink_Task(LED 闪烁,优先级 1);
- 任务 3:UART_Process_Task(串口指令处理,优先级 3);
- 点击 “Tasks and Queues”→“Add”,创建 3 个任务:
- 配置串口 USART1(9600 波特率)、GPIO(PC13=LED);
- 生成初始化代码(CubeIDE 自动生成 FreeRTOS 底层代码和任务框架)。
四、实操 2:FreeRTOS 多任务并行(3 个任务协同工作)
目标:实现 3 个任务并行执行,验证 “高优先级任务优先响应,低优先级任务后台运行”。
步骤 1:编写各任务逻辑(在main.c的 “USER CODE BEGIN 5” 区域)
FreeRTOS 任务函数是 “无限循环 + 阻塞延时” 的结构(避免 CPU 空转):
- 温湿度采集任务(优先级 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:下载测试(多任务并行验证)
- 串口助手配置 9600 波特率,打开串口;
- 现象:
- LED 每 500ms 闪烁一次(LED_Blink_Task);
- 串口每 1 秒收到一条温湿度数据(TempHumi_Task);
- 发送 “ON” 指令,LED 立即点亮并收到响应(UART_Process_Task 优先级最高,实时响应);
- 发送 “OFF” 指令,LED 立即熄灭 ——3 个任务并行执行,互不干扰。
五、第九天必掌握的 3 个关键知识点(10 分钟自测)
- DMA 核心价值:无 CPU 干预传输,解放 CPU,提升串口 / I2C 等外设的传输效率;
- FreeRTOS 核心逻辑:任务是无限循环 + 阻塞延时,调度器按优先级分配 CPU 资源,高优先级任务优先执行;
- 多任务优势:解决单任务循环的阻塞问题(如串口接收等待不会卡住 LED 闪烁),适合复杂系统。
总结
今天掌握了嵌入式系统 “效率提升” 和 “复杂任务管理” 的两大核心工具:DMA 解决了 “数据传输占用 CPU” 的痛点,FreeRTOS 实现了 “多任务并行”,让系统能同时处理采集、控制、通信等多个任务 —— 这正是实际嵌入式产品(如智能硬件、工业控制器)的核心架构。

1408

被折叠的 条评论
为什么被折叠?



