一、前言 & 本期学习目标

前面三期我们完成了嵌入式 C 全套语法、指针、链表、FIFO、多文件模块化工程,已经能规范编写单片机驱动与业务代码。

但传统裸机大循环存在明显短板:

  1. 所有代码串行执行,响应慢、实时性差;
  2. 按键、串口、传感器等事件轮询检测,CPU 资源浪费严重;
  3. 复杂项目功能耦合严重,功能越多逻辑越乱。

单片机两大核心解决方案:硬件中断 + 实时操作系统 FreeRTOS。中断用来紧急事件快速响应,RTOS 用来多任务并行调度,二者是嵌入式开发的分水岭,也是汽车电子、工业控制、物联网 ECU 必备技能。

本期结合前面 volatile、位运算、函数、多文件知识点,从裸机中断→软件定时器→FreeRTOS 实战逐步递进,所有代码、写法、规范完全对标真实项目。

本期学习目标

  1. 理解中断基本概念、中断优先级、中断标志位,掌握嵌入式中断编码规范;
  2. 学会中断专属关键字 volatile 实战用法,解决中断优化 BUG;
  3. 实现裸机软件定时器,不占用硬件定时器实现延时与周期任务;
  4. 掌握 FreeRTOS 核心基础:任务、延时、消息队列;
  5. 实战案例:中断按键 + 多任务 LED / 串口,裸机与 RTOS 两套方案对比;
  6. 掌握中断 + RTOS 联合开发的避坑要点、代码规范。

前置基础:嵌入式 C 语法、位运算、volatile、函数、模块化工程


第一部分:嵌入式核心 —— 中断(Interrupt)

1.1 什么是中断?

通俗理解:CPU 正在正常执行主循环代码,突然收到硬件紧急信号,暂停当前任务,跳转去处理紧急事件,处理完毕后回到原位置继续运行。

典型应用场景:

  • 按键按下(外部中断)
  • 串口 / CAN 总线数据接收
  • 定时器定时时间到
  • 传感器数据触发

三大核心要素(单片机通用):

  1. 中断使能:打开对应中断开关
  2. 中断标志位:硬件置 1,表示中断触发(必须手动清零)
  3. 中断服务函数:中断触发后执行的代码

1.2 中断编码铁律(嵌入式必守)

  1. 中断函数执行时间一定要短:只做标记、置标志位,复杂逻辑丢到主循环 / 任务里执行;
  2. 中断共享变量必须加 volatile,防止编译器优化;
  3. 中断标志位触发后手动清零,否则重复进入中断;
  4. 中断函数不能使用耗时函数、延时、printf 长打印;
  5. 禁止在中断里调用会阻塞的函数。

1.3 裸机外部中断实战(按键中断模拟)

结合前面位运算、volatile 知识点,模拟单片机外部中断框架,标准工程写法:

#include <stdio.h>

// 嵌入式标准类型
typedef unsigned char u8;
typedef unsigned int  u32;

// 中断标志位:中断与主循环共享变量,必须加 volatile
volatile u8 key_int_flag = 0;

// 模拟中断服务函数(真实单片机固定命名,无返回值、无参数)
void EXTI_Key_IRQHandler(void)
{
    // 1. 判断中断标志位(硬件自动置1)
    // 模拟:按键中断触发
    key_int_flag = 1;  

    // 2. 手动清除中断标志位(重中之重)
    // 此处省略硬件寄存器清零代码
}

int main(void)
{
    while(1)
    {
        // 主循环轮询标志位,复杂业务放这里
        if(key_int_flag == 1)
        {
            printf("按键中断触发,执行对应业务\r\n");
            key_int_flag = 0; // 软件清零标志位
        }

        // 其他常规任务
    }
    return 0;
}

代码解析

  • volatile u8 key_int_flag:全局标志位,中断与主循环共享,必须加 volatile
  • 中断服务函数:仅置标志位,逻辑极简;
  • 主循环检测标志位,执行实际功能,保证中断快速退出。

1.4 中断高频坑点

  1. 共享变量不加 volatile → 编译器优化,主循环检测不到标志位;
  2. 中断函数写过长代码 → 系统卡顿、丢失中断、程序跑飞;
  3. 忘记清除中断标志位 → 一直重复进中断;
  4. 中断内使用延时、大循环、串口打印 → 实时性严重下降。

第二部分:裸机软件定时器(无需硬件 Timer)

很多场景不需要高精度硬件定时,使用软件定时器即可实现周期任务、延时任务,裸机项目使用极广,基于系统滴答计数器实现。

2.1 实现原理

  1. 定义全局系统滴答计数器,定时自增;
  2. 任务记录上次执行时间
  3. 当前时间 - 上次时间 ≥ 定时周期 → 执行任务,并刷新时间戳。

2.2 完整软件定时器代码(裸机通用)

#include <stdio.h>

typedef unsigned char u8;
typedef unsigned int  u32;

// 系统滴答计数器(1ms自增一次,模拟SysTick)
volatile u32 sys_tick = 0;

// 任务时间戳
u32 led_tick = 0;
#define LED_PERIOD 500  // 500ms翻转一次

// 模拟1ms中断(SysTick滴答中断)
void SysTick_IRQHandler(void)
{
    sys_tick++;
}

int main(void)
{
    while(1)
    {
        // 周期翻转LED
        if(sys_tick - led_tick >= LED_PERIOD)
        {
            printf("LED 状态翻转\r\n");
            led_tick = sys_tick; // 刷新时间戳
        }

        // 可叠加多个不同周期任务
    }
    return 0;
}

优势

  • 不占用硬件定时器资源;
  • 可同时创建多路周期任务;
  • 裸机大循环架构下最优定时方案。

第三部分:FreeRTOS 实时操作系统入门(工业主流 RTOS)

裸机架构在功能复杂、多并发任务场景下难以维护,FreeRTOS 是目前单片机、汽车电子、物联网使用最广的轻量级实时操作系统,免费、精简、移植简单。

3.1 核心概念:任务(Task)

FreeRTOS 把每一个独立功能拆分成独立任务,系统自动调度,宏观上实现 “并行运行”。

  • 每个任务拥有独立栈空间;
  • 系统按照优先级调度,高优先级任务优先执行;
  • vTaskDelay() 延时函数:主动让出 CPU,提升系统利用率。

3.2 基础 API 说明(入门必记)

// 1. 任务句柄(任务ID)
TaskHandle_t Task1_Handle;

// 2. 创建任务
BaseType_t xTaskCreate(
    任务函数,    // 任务入口
    任务名称,    // 字符串
    栈大小,      // 栈深度
    传入参数,
    优先级,      // 数字越大优先级越高
    任务句柄
);

// 3. 任务延时(单位:系统节拍,常用1ms/节拍)
void vTaskDelay(TickType_t xTicksToDelay);

3.3 实战 1:多任务运行(LED 任务 + 打印任务)

两套独立任务并行执行,标准 FreeRTOS 入门模板:

#include "FreeRTOS.h"
#include "task.h"
#include <stdio.h>

// 任务句柄
TaskHandle_t LedTask_Handle;
TaskHandle_t PrintTask_Handle;

// LED任务:500ms翻转一次
void Led_Task(void *pvParameters)
{
    while(1)
    {
        printf("LED 切换状态\r\n");
        vTaskDelay(pdMS_TO_TICKS(500)); // 延时500ms
    }
}

// 打印任务:1000ms打印一次
void Print_Task(void *pvParameters)
{
    while(1)
    {
        printf("系统正常运行中\r\n");
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

int main(void)
{
    // 创建LED任务
    xTaskCreate(Led_Task, "LedTask", 128, NULL, 2, &LedTask_Handle);
    // 创建打印任务
    xTaskCreate(Print_Task, "PrintTask", 128, NULL, 1, &PrintTask_Handle);

    // 启动任务调度器
    vTaskStartScheduler();

    // 正常不会执行到这里
    while(1);
}

运行效果

两个任务互不阻塞,按照各自周期独立运行,真正实现多任务并发。

3.4 核心通信:消息队列(xQueue)

多任务、中断与任务之间传递数据,消息队列是首选,安全、稳定、无冲突,广泛用于串口 / CAN / 中断数据转发。

消息队列基础实战(中断→任务传数据)

#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include <stdio.h>

QueueHandle_t Key_Queue; // 队列句柄

// 按键任务:读取队列数据
void Key_Process_Task(void *pvParameters)
{
    u8 key_val;
    while(1)
    {
        // 阻塞等待队列数据
        xQueueReceive(Key_Queue, &key_val, portMAX_DELAY);
        printf("收到按键数据:%d\r\n", key_val);
    }
}

// 模拟中断:向队列发送数据
void Key_EXTI_IRQHandler(void)
{
    u8 key_dat = 1;
    // 中断中使用带中断保护的入队函数
    xQueueSendFromISR(Key_Queue, &key_dat, NULL);
}

int main(void)
{
    // 创建队列:长度8,单元素1字节
    Key_Queue = xQueueCreate(8, sizeof(u8));

    // 创建任务
    xTaskCreate(Key_Process_Task, "KeyTask", 128, NULL, 2, NULL);

    vTaskStartScheduler();
    while(1);
}

应用场景

  • 中断接收串口 / CAN 数据 → 投递队列 → 任务解析协议
  • 按键中断 → 队列传值 → 任务执行对应功能
  • 多任务之间数据交互

第四部分:中断 + FreeRTOS 联合开发规范(重点)

4.1 中断内 FreeRTOS API 使用规则

  1. 普通任务 / 队列函数 不能在中断里调用
  2. 中断专用 API 后缀:FromISR,例如:xQueueSendFromISR
  3. 中断代码依旧遵循简短原则,只做数据转发、置标志;
  4. 中断共享全局变量依旧必须加 volatile

4.2 裸机 vs FreeRTOS 选型参考

表格

方案 适用场景 优点 缺点
裸机大循环 + 中断 简单设备、单一功能、低成本 MCU 代码简单、资源占用极小 复杂功能耦合、实时性一般
裸机 + 软件定时器 多路周期任务、中等复杂度项目 无需操作系统、移植方便 并发能力弱
FreeRTOS 多并发、UDS、Bootloader、通信协议、复杂 ECU 实时性强、任务解耦、架构清晰 占用少量 RAM/Flash

4.3 高频坑点汇总

  1. RTOS 任务内不要使用裸机死循环不加延时 → 独占 CPU,其他任务卡死;
  2. 中断误用普通队列 API,必须使用 xxxFromISR 系列;
  3. 任务栈分配过小 → 栈溢出、程序随机死机;
  4. 多任务直接读写全局变量 → 数据错乱,优先使用消息队列
  5. 系统滴答时钟配置错误 → 延时、周期任务全部不准。

第五部分:本篇完整总结

  1. 吃透中断原理与编码规范,掌握标志位、volatile 在中断中的硬性用法;
  2. 学会裸机软件定时器,实现多路周期任务,适配无操作系统项目;
  3. 掌握 FreeRTOS 核心:任务创建、延时、优先级、调度器;
  4. 精通消息队列,实现中断与任务、任务与任务安全数据交互;
  5. 掌握裸机 / RTOS 两套架构选型与联合开发规范,可应对绝大多数嵌入式项目。

至此,从C 语言基础→嵌入式进阶语法→模块化工程→中断 / 定时器 / FreeRTOS 整条学习链路全部打通,你已经具备汽车电子、工业控制、单片机全栈开发的基础能力,可直接开展 ECU、UDS、Bootloader、CAN 总线、嵌入式应用开发。


下期进阶预告(最终高阶专题)

下一期主题:CAN 总线、UDS 诊断与 ECU 程序实战

结合前面所有 C 语言、中断、RTOS、队列知识,落地汽车电子核心:

  1. CAN 基础帧 / 扩展帧 ID、过滤器配置原理与代码;
  2. UDS 诊断常用服务(ECU 复位、读取 ID、会话控制)指令解析;
  3. RTOS + 队列实现 CAN 报文收发、上下位机 UDS 联调;
  4. 常见故障排查:过滤器 ID 匹配、收发异常、报文丢失问题。
Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐