四万字长文爽学RTOS,爽拿十个工作Offer

前言

深入学习 FreeRTOS 不仅能让你掌握实时系统的核心概念,还能为你提供进入物联网、机器人和工业控制等前沿嵌入式领域的能力。包学会,学爽,学完就有offer。

基本问题

什么是 RTOS

答:即实时操作系统(Real Time Operating System),一类为有严格时间约束的嵌入式系统设计的精简操作系统。

更具体地说,RTOS 是以在确定时间内响应外部事件为基本原则设计的操作系统,即外部事件发生后,对应的服务例程/应用需要在确定的时间内完成响应,不能因为调度机制等操作系统的原因发生时序违例。

为什么要使用 RTOS

首先要明确一点,操作系统并非什么嵌入式开发的银弹,只不过是一种特殊的开发范式,在机器上以一系列待调用的库存在。这意味着,嵌入式系统开发的优良与否并不以是否采用操作系统为评价指标。

相应的,编写优质的实时嵌入式软件并不一定需要RTOS。但当应用规模扩大或复杂度增加时,RTOS提各种原因显现其价值。但这些也并非绝对标准,而是经验之谈。和所有技术选型一样,只有合适的技术路径,没有唯一最好的实现教条。

以下这些可以算是选择使用 RTOS 的理由:

理由 1 抽象

操作系统可以视为对硬件的抽象, 把处理单元抽象为进程/任务,把磁盘/存储器抽象为文件系统;用内存管理、驱动管理、网络管理等模块分别完成对内存、外设和网络的抽象。抽象意味着屏蔽,带来便捷。

可以说,你使用操作系统时感受到的一切好处几乎都是抽象带来的,而直接可见的好处至少有以下这:

  1. 模块化程度提升。将应用组织为多个自主运行的任务可实现更高效的模块化。任务应是低耦合高内聚的功能单元,内部保持顺序执行逻辑。例如无需将函数拆分为微型状态机来防止执行时间过长。

  2. 时序信息抽象化。实时调度器本质上是一段允许开发者定义应用时序特性的代码——它使得应用代码更简洁、更紧凑(从而更易理解)。

  3. 可维护性/可扩展性。将时序信息从代码中剥离后,软件模块间的相互依赖性降低,从而提升可维护性和可扩展性。如果优先级设置合理,某个模块的修改不会影响其它模块的时序。

  4. 更清晰的接口。明确定义的任务间通信接口有助于系统设计和团队协作开发。简化测试流程(某些场景下)无需添加可能改变被测模块行为的检测代码,即可对任务接口进行全面测试。

  5. 代码复用性增强。更高的模块化程度和更低的模块依赖性促进了跨项目代码复用。任务机制本身也支持项目内的代码复用,例如处理TCP/IP栈连接时,可为每个连接动态创建相同的任务实例。

理由 2 提效

FreeRTOS允许任务阻塞在事件上(无论是时间事件还是外部事件),这意味着当没有待处理事件时,系统不会浪费资源进行轮询或定时器检查,从而显著降低处理器占用率——代码仅在需要时执行。

理由 3 降耗

FreeRTOS可便捷测量处理器负载:当空闲任务运行时即表示处理器无其他任务可执行。该机制同时提供了一种自动进入低功耗模式的简单方案。

理由 4 便捷

  1. 中断处理。将中断触发的处理过程延迟到任务级执行,可极大缩短中断服务程序本身。这实际上就是帮助我们搭好了前后台,直接填充处理例程即可。

  2. 混合需求。便捷实现周期性、连续性和事件驱动型处理的混合调度。结合中断与任务优先级配置,能同时满足硬实时和软实时需求。这些当然都能在裸机上实现,但使用 RTOS 更方便,毕竟除了实现细节,还有很多时序逻辑需要考量。屏蔽一些细节更便于我们专注于业务开发。

  3. 外设控制优化。RTOS 提供有效的互斥机制,可实现外设访问的序列化。或者说,提供的互斥工具包方便多任务开发。


选择的原因

主观

  1. 目标明确​​:你需要的是​​确定性​​和​​实时性​​,而不是庞大的生态。
  2. ​​资源敏感​​:你关注的是在有限资源(MCU)上高效运行。
  3. 控制底层​​:你希望直接与硬件打交道,理解系统从零到一的过程。

客观

  1. 行业霸主地位​​:在微控制器领域,FreeRTOS 是无可争议的王者,拥有最庞大的用户群和生态系统。它被集成在几乎所有主流芯片厂商的 SDK 中(ST, NXP, TI, Microchip, Espressif…),这意味着你学到的技能几乎通用。
  2. 就业市场的硬通货​​:精通 FreeRTOS 和实时编程是嵌入式软件工程师,尤其是 Firmware 工程师非常核心的技能要求,市场需求量大且稳定。
  3. 通往物联网的钥匙​​:由 Amazon 主导的 ​​FreeRTOS Kernel​​ 和 ​​AWS IoT​​ 生态的深度整合,让它成为物联网设备开发的首选平台之一。学习它意味着你同时掌握了设备端和云连接的核心技能。

有效学习路径

1.理解​​实时操作系统核心概念​​

  • 任务调度
  • 队列
  • 信号量
  • 互斥量
  • 内存管理

这些知识是通用的,不管是 FreeRTOS,还是其他 RTOS(如 Zephyr, RT-Thread)都是一通百通,所以和上面说的一样,你需要的是​​确定性​​和​​实时性​​,而不是庞大的生态,理解确定性和实时性的设计和实现才是关键。

所以不要急于写代码。先理解几个最核心的概念,尝试回答以下问题:

  1. ​任务​​:什么是任务?任务的状态(就绪、运行、阻塞、挂起)如何转换?

任务的原语是 task,在多数嵌入式场景中任务、线程(thread)和进程(process)是没有明确边界的,因为并没有把内存等资源分隔开使用。

所以你可以把设备上包括 FreeRTOS 在内的整个软件看作一个应用进程,其中各个单独的模块看作线程,这些线程也就对应着任务。这样说似乎很绕,正面理解就是一个设备上的应用可以分解为多个小任务,这些任务可以用单独的线程实现。

举例说来,一个音频频谱显示设备,需要采集声信号、处理信号和显示信号。那么整个软件至少可以分解为三个任务——一个负责取ADC输出的数字信号;一个负责对信号做降采样、数字滤波、频谱计算等处理;一个负责把频谱数据转换成显示图像刷新到屏幕上。这在FreeRTOS 等 RTOS 中就可以创建三个 Task, 每个 Task 都是一个封闭的循环(也可以理解为独立的小程序),只负责自己被分配的工作,他们之间可以通过一些锁、信号量之类的东西通信,以便按照一定的时序协作,避免资源争用等问题*

尽管多核 SoC 已经遍地都是,可以用非常低廉的价格买到。但对于消费电子或者其他一些功耗/成本敏感的产品中,普遍使用的仍是单核的 MCU/MPU,这意味着这些产品上同一时刻只能运行一个程序/线程。如果你接受了上面说的,把 Task 理解为独立的小程序,或者你直接把它理解为线程,就能理解这些处理器上的核心是分时复用的。在直接编程为一个大循环的情况下,各个阶段的任务顺序执行,可以说整个程序都一直处在运行状态。而在RTOS多任务的编程范式下,软件分解为多个子任务,有一个任务在运行,其他任务就没在运行。所以任务的状态可以分为 running 和 not running 两种。

既然处理核心是分时复用的,那么就会有运行的任务停下来,没在运行的任务被选中开始执行。也就是会有任务的状态在 running 和 not running的状态之间切换的情况。
在这里插入图片描述
完整的状态转换如下图所示,not running状态可分为挂起(suspend)、就绪(ready)和阻塞(blocked)三种子状态

在这里插入图片描述

挂起的任务没法被选中开始执行,除非先通过调用vTaskResume退出挂起状态,不过大多数应用都不使用这个状态。一般只在无条件暂停、手动控制或紧急处理时才会用到挂起状态。

就绪指的是当前没有运行但也没有被阻塞或者挂起的状态。

进入阻塞态分为两种情况——一种是因为暂时的(temporal)事件,比如任务主动调用 delay 暂停一个确定的时间,超时之后就会切回到就绪态。另一种是因为需要等待另一个任务或者中断引发的同步事件,比如等另一个任务让某个数据可用。第二种情况虽然说起来抽象,但实际也很常见,处理数据的任务肯定是要等采集数据的任务告知当帧数据采集完毕才能开始处理的。而显示图像的任务肯定是要等处理数据的任务处理完毕才能开始更新显示帧的。

  • 调度器​​:抢占式调度和时间片轮转调度有什么区别?

前面我们说操作系统用调度器把处理单元抽象为线程

非抢占式调度

工作方式​​:一旦一个进程开始运行,它就会一直运行下去,直到它​​主动​​做出以下行为:

1.自愿让出CPU。
2.运行结束

​​问题​​:如果一个进程执行一个非常长的计算任务(比如一个无限循环),它就会一直霸占CPU,导致其他所有进程都无法运行,系统响应速度极慢。早期的系统(如Windows 3.x)基本是这种模式,一旦一个程序无响应,整个系统可能就卡死了。

抢占式调度

​​工作方式​​:操作系统​​可以主动​​中断当前正在运行的进程,强行剥夺其CPU使用权,并将其重新放回就绪队列,然后选择另一个进程运行。

​​如何实现?​​ 主要依靠​​定时器中断​​。操作系统设置一个硬件定时器,每隔一段时间(例如10ms)就产生一次中断。中断处理程序由操作系统控制,它会在处理完设备请求后,​​触发调度器​​。调度器检查当前进程是否已经运行了“足够长”的时间,如果是,就执行上下文切换,换另一个进程运行。

​​优点​​:保证了系统的响应性。即使有一个进程陷入死循环,GUI界面、后台服务等仍然能够获得CPU时间片,从而保持可用。​​现代通用操作系统(Windows, Linux, macOS)都是抢占式的。​

时间片轮转调度

时间片轮转其实是​​抢占式调度的一种具体实现算法​​。

给每个就绪进程分配一个固定的、最大连续运行时间,这个时间称为​​时间片​​。例如 100ms。
所有就绪进程被组织成一个​​FIFO(先进先出)队列​​。调度器总是选择队列头的进程来执行。
如果该进程在时间片内运行结束或阻塞(如等待I/O),则它主动释放CPU,调度器再选择下一个队首进程。如果该进程在时间片用尽时仍在运行(这是关键),定时器中断会触发,调度器会强制剥夺其CPU,将它放到就绪队列的队尾​​,然后选择队首的新进程运行。

时间片+抢占

操作系统会为每个进程分配两个属性:优先级​​和时间片。

高优先级进程​​可以​​抢占​​低优先级进程的CPU时间,​​无论其时间片是否用完​​。这保证了紧急任务(如用户交互、硬件响应)能得到即时处理。​​同等优先级​​的进程之间,则采用​​时间片轮转​​的方式进行调度。每个进程运行一个时间片后,被抢占并排到队列末尾,让下一个同优先级的进程运行。这保证了公平性,防止同级进程饿死。

工作流程

1.一个低优先级进程A正在运行​​。

2.一个高优先级进程B变为就绪状态​​(例如,用户移动了鼠标)。

3.操作系统立即​​抢占​​当前进程A(即使它的时间片还没用完!)。

4.让高优先级的进程B开始运行。

5.进程B会一直运行,直到:

a.被另一个​​更高优先级​​的进程抢占。

b.主动放弃CPU(如等待I/O)。

​​c.时间片用尽​​(这时,如果存在其他同优先级的就绪进程,就会发生轮转;如果没有,它可能会继续运行)。

优先级抢占​​是纵向的​调度,决定哪个进程能“插队”。​​时间片轮转​​是​横向的​调度,决定同等级别的进程如何“轮流坐庄”。

  • ​​队列​​:为什么任务间通信不直接用全局变量而要用队列?

简单直接的回答是:

​​使用队列而不是全局变量,是为了实现安全、可靠、可预测的“任务间通信”,避免由“竞态条件”和“数据共享”引发的各种难以调试的灾难性错误。​​

安全性:避免竞态条件

全局变量是一个“共享资源”。如果两个任务(例如,一个在中断中,一个在主循环中)同时对这个变量进行“读-写-改”操作,结果将是不可预测的。

比如任务A想对一个全局计数器 count进行加一操作 (count = count + 1)。这个操作在汇编层面通常是三步:

1.从内存读count到寄存器。

2.寄存器值加一。

3.将新值写回count内存。*

如果任务A刚执行完步骤1(读取了值,比如是5),此时一个高优先级的任务B中断了它,并对count完成了完整的加一操作(count变成了6)。当任务A恢复执行时,它会在步骤2用之前读到的旧值(5)加一,得到6,然后在步骤3再次把6写入count。结果​本来两次正确的加一操作应该得到7,但因为这种​​交叉执行​​,最终结果却是6。数据就被破坏了。

​​而队列本身就是一个​​线程安全​​的机制。其内部实现了“互斥锁”或“临界区”保护。当一个任务(发送者)在向队列写入数据时,整个“写入操作”是原子的,不会被其他任务打断。接收者亦然。这从根本上杜绝了竞态条件的发生,保证了数据的完整性。

解耦与同步:生产者-消费者模型

队列天然地实现了优秀的软件设计模式——生产者-消费者模型。

生产者和消费者之间是​​强耦合​​的。生产者不知道消费者何时来读取数据,消费者也不知道生产者何时更新了数据。消费者必须不断地轮询(polling)全局变量,这极大地浪费了CPU资源。或者需要复杂的信号量、事件标志等机制来同步,增加了程序的复杂性。

如果使用队列,生产者只管向队列尾发送数据,消费者只管从队列头提取数据。它们不需要知道彼此的存在和状态,只需要操作队列这个中间件即可。这大大降低了系统模块间的耦合度。

队列自带阻塞机制。如果消费者试图从​​空队列​​中读取数据,它可以选择进入阻塞状态,让出CPU,直到有数据到来才被唤醒。同样,如果生产者向​​满队列​​发送数据,它也可以阻塞,直到队列有空间。这是一种非常高效的事件驱动型同步,CPU不会被浪费在无用的轮询上。

数据传递与所有权转移

如果使用全局变量,数据是共享的,没有所有权的概念。多个任务可能同时认为自己拥有该数据并试图修改它,导致混乱。如果传递的是指针,风险更高(任务A刚把指针写入全局变量,任务B可能还没来得及读,任务A又把指针指向了别的地方)。

*​​队列的解决方案​​:队列操作的本质是​​数据的复制或所有权的转移​​。

​​对于值传递​​:当生产者发送数据时,数据会被复制到队列中。此后,生产者可以随意修改自己的原始数据副本,而不会影响已经进入队列的数据。

​​对于指针传递​​:虽然传递的是指针(例如为了传递大数据块),但通过队列的机制,可以清晰地约定数据的所有权从生产者转移到了消费者。生产者发送后就不再使用该数据块,消费者在取出后负责处理它。这种模式化的工作流避免了混乱。

系统可扩展性与可维护性

​​随着系统变得越来越复杂,全局变量会散落在代码的各个角落。添加一个新任务可能会影响到许多其他任务,因为大家共享着许多“隐藏”的全局状态。调试一个由全局变量引起的bug如同大海捞针,因为你需要在所有可能访问该变量的地方设置断点。

​​而使用队列,通信通道是明确且集中的。所有交互都通过定义良好的队列接口(xQueueCreate, xQueueSend, xQueueReceive)进行。代码更容易理解、调试和维护。添加新的生产者或消费者通常只需要让它们访问同一个已有的队列即可,对现有代码影响极小。

  • ​​信号量与互斥量​​:它们的区别是什么?什么是优先级反转?如何用优先级继承解决?

信号量、互斥量以及优先级反转这个经典问题是理解实时操作系统(RTOS)内核原理的关键。

而优先级反转是RTOS中一个致命的实时性问题,而互斥量通过实现​​优先级继承​​(或另一种方法:优先级天花板)特性解决这个问题。这也是为什么在​​保护临界区、访问共享资源时,应该优先使用互斥量而不是二进制信号量​。

信号量与互斥量的区别

尽管信号量和互斥量在实现上看起来很相似(都有give和take操作),但它们的​​设计目的和用途​​有本质区别。

​​信号量是计数器形式的,用于发信号、同步​​、管理​​多个​​同类资源,没有所有权​​概念。任何任务都可以释放(give)信号量,无论谁获取的;而互斥量是二值形式,用于管理单个临界资源、确保一次只有一个任务能访问共享资源 (如全局变量、外设),需要注意处理优先级反转问题。

什么是优先级反转?

优先级反转是​​高优先级任务被迫等待低优先级任务​​,这种情况破坏了RTOS的优先级调度原则,自然是不被希望出现的。

​​经典的发生过程(假设三个任务:H高, M中, L低):​​

1.低优先级任务L​​开始运行,并获取了一个​​互斥锁​​(Mutex),进入临界区。

2.在L还在临界区内时,​​高优先级任务H​​就绪,抢占了L的CPU,开始运行。

3.​​任务H​​也试图获取​​同一个互斥锁​​。但锁正被L持有,因此H会被阻塞(挂起),等待L释放锁。

4.此时,CPU理应回去继续执行​​任务L​​(持有锁的任务),以便让它尽快执行完并释放锁。

5.然而​​,此时​​中优先级任务M​​就绪了。由于M的优先级高于L但低于H,它会​​抢占任务L​​的CPU并开始运行!

6.​​​​任务M​​(中优先级)正在运行,而​​任务H​​(高优先级)却在阻塞状态,苦苦等待​​任务L​​(低优先级)释放锁。但​​任务L​​根本无法运行,因为它被M抢占着。
​​> 最终结果​​:中优先级任务M​​无意中阻塞了​​高优先级任务H。整个系统的优先级调度被打乱,高优先级任务H的实时性得不到保证,仿佛它的优先级比M和L都低。

如何用优先级继承解决【优先级反转问题】?

优先级继承是解决优先级反转问题最常用的算法。​​核心思想​​是当一个高优先级任务因为请求一个互斥量而被阻塞时,​​持有该互斥量的低优先级任务会临时提升到与这个阻塞它的高优先级任务相同的优先级​​。直到该低优先级任务释放互斥量后,它的优先级再恢复为原值。

​​让我们用优先级继承重演上面的悲剧,将其变为喜剧:​​

1.任务L​​(低优先级)运行,获取互斥锁。

2.​任务H​​(高优先级)就绪,抢占L,运行。

3.​任务H​​请求互斥锁,发现被L持有,于是​​H被阻塞​​。​​关键一步发生:系统发现H在等L的锁,于是将L的优先级临时提升到与H相同。​​

4.现在,​​任务L​​(已临时拥有H的高优先级)得以继续运行,因为它现在是系统中就绪的​​最高优先级任务​​,它不会被中优先级任务M抢占。

5.​​任务L​​尽快执行完临界区代码,​​释放互斥锁​​。在释放锁的瞬间,​​系统自动将其优先级恢复为原来的低优先级​​。

6.锁已被释放,​​一直在等待的任务H​​立即获取到锁,并开始运行(因为它现在是最高优先级任务)。

7.之后,​​任务M​​才有机会运行。

通过这个机制,​​中优先级任务M的干扰被消除了​​。低优先级任务L“借用”高优先级,快速地执行完了临界区,从而让高优先级任务H能够尽快获得资源并继续执行。这有效降低了高优先级任务被阻塞的最长时间。

2. 通读官方文档

​FreeRTOS.org​​ 是最好、最权威的学习资源。它的 ​​API 参考​​和《FreeRTOS 编程指南》写得非常清晰,附有大量示例。这是遇到问题都应该首先查阅的地方。

3. 上开发板做实验​

理论必须结合实践。选择一款流行的、资料丰富的 MCU 开发板,例如:

  • STM32 Nucleo/F-Discovery​​(基于 ARM Cortex-M,生态极好)
  • ​​ESP32-DevKitC​​(内置 Wi-Fi & 蓝牙,物联网神器)

这些板子的厂商通常都提供了集成 FreeRTOS 的示例项目,是极好的起点。
当然也不必过分纠结选什么板子,刚开始借助 QEMU 仿真就是完全可以的,不必要的钱一分都不花。

能做的实验很多,以下几个可以优先尝试,以更好理解基础概念:

  1. ​​任务管理​​:创建多个不同优先级的任务,观察调度器如何工作。
  2. ​​队列​​:在两个任务之间通过队列传递数据。尝试不同的阻塞时间。
  3. ​​信号量​​:用二进制信号量实现任务同步(如模拟一个“事件”)。
  4. ​互斥量​​:保护一个共享资源(如串口打印),并故意制造优先级反转的场景,观察系统行为,然后用互斥量修复它。
  5. ​​软件定时器​​:创建周期性和一次性的定时器回调。
先把环境搭起来

这里不以实际的板卡为目标搭建实验环境,而选择 GCC+QEMU 仿真。使用 QEMU 的好处在于咱们不用关心硬件的问题,聚焦软件本身的功能就行,心智负担比较低。而且只要没有绝对性能测试的需求,QEMU 和真实的板卡也没有什么区别。

安装必要工具
主要是安装交叉编译器 gcc 和 仿真软件 qemu、构建工具 make 和 版本管理工具 git。
这里针对 RISC-V 的处理器开展学习,
qemu 安装支持 riscv64 的版本 qemu-system-riscv64
对应 gcc 需要 gcc-riscv64-unknown-elf,这应该也支持 32 位的目标处理器
假设使用的操作系统是Ubuntu/Debian,用下面的命令安装上述软件。

sudo apt install qemu-system-riscv32 gcc-riscv64-unknown-elf make git

获取示例代码

git clone https://github.com/FreeRTOS/FreeRTOS.git
SRCS = main.c main_blinky.c riscv-virt.c ns16550.c \
	$(DEMO_SOURCE_DIR)/EventGroupsDemo.c \
	$(DEMO_SOURCE_DIR)/TaskNotify.c \
	$(DEMO_SOURCE_DIR)/TimerDemo.c \
	$(DEMO_SOURCE_DIR)/blocktim.c \
	$(DEMO_SOURCE_DIR)/dynamic.c \
	$(DEMO_SOURCE_DIR)/recmutex.c \
	$(RTOS_SOURCE_DIR)/event_groups.c \
	$(RTOS_SOURCE_DIR)/list.c \
	$(RTOS_SOURCE_DIR)/queue.c \
	$(RTOS_SOURCE_DIR)/stream_buffer.c \
	$(RTOS_SOURCE_DIR)/tasks.c \
	$(RTOS_SOURCE_DIR)/timers.c \
	$(RTOS_SOURCE_DIR)/portable/MemMang/heap_4.c \
	$(RTOS_SOURCE_DIR)/portable/GCC/RISC-V/port.c

ASMS = start.S vector.S\
	$(RTOS_SOURCE_DIR)/portable/GCC/RISC-V/portASM.S

移植
进到 RISC-V-Qemu-virt_GCC 对应的 Demo 目录下,能看到 Makefile, 内容表明需要 FreeRTOS/Source/ 目录中的一些文件,如下面摘录的 Makefile 内容所示

RTOS_SOURCE_DIR = $(abspath ../../Source)

CPPFLAGS = \
	-D__riscv_float_abi_soft \
	-DportasmHANDLE_INTERRUPT=handle_trap \
	-I . -I ../Common/include \
	-I $(RTOS_SOURCE_DIR)/include \
	-I $(RTOS_SOURCE_DIR)/portable/GCC/RISC-V \
	-I $(RTOS_SOURCE_DIR)/portable/GCC/RISC-V/chip_specific_extensions/RV32I_CLINT_no_extensions

但是这时候 Source/ 目录是空的,需要自行添加,为了方便,这里提供一个 git 的 ptach 文件:RISC-V-Qemu-virt_GCC.patch

编译运行
,直接make

cd FreeRTOS/FreeRTOS/Demo/RISC-V-Qemu-virt_GCC
make    # 编译项目

然后产生的 build/ 里有一个编译输出的 RTOSDemo.axf,以之作为 kernel 文件进行 qemu 仿真,命令如下:

qemu-system-riscv32 -machine virt -nographic -kernel build/RTOSDemo.axf -bios none -smp 1 -serial mon:stdio  

退出QEMU
Ctrl + A 然后按 X

4. 深入与进阶

4.1 内存管理​​

任务:研究 FreeRTOS 的 heap_1 到 heap_5 几种内存分配策略的区别和适用场景。

在开始讨论具体的发呢配策略之前,我们来综合讨论三个密切相关的问题:C程序的编译链接阶段、栈(Stack)与堆(Heap)的区别,以及标准库的 malloc() 和 free() 函数。通过理解它们的联系,可以更深入地掌握C/C++程序的内存管理机制,而不是局限于某个 RTOS 的实现特质。

1. C程序的编译和链接阶段

一个C程序从源代码到可执行文件需要经历以下阶段:

(1) 预处理(Preprocessing)

输入:.c 源文件(如 main.c)。

输出:预处理后的 .i 文件(文本文件)。

主要操作:

  1. 处理 #include(头文件展开)。
  2. 处理 #define(宏替换)。
  3. 处理 #ifdef、#ifndef(条件编译)。
  4. 删除注释。

示例:

  • gcc -E main.c -o main.i

(2) 编译(Compilation)

输入:预处理后的 .i 文件。

输出:汇编代码 .s 文件(文本文件)。

主要操作:

  1. 语法分析、语义分析
  2. 生成与目标CPU架构相关的汇编代码。

示例:

  • gcc -S main.i -o main.s

(3) 汇编(Assembly)

输入:.s 汇编文件。

输出:目标文件 .o(二进制机器码,但未链接)。

主要操作:

  1. 将汇编代码翻译成机器指令(.o 文件)。

  2. 但此时函数调用(如 printf)的地址还未确定(需要链接)。

示例:
gcc -c main.s -o main.o

(4) 链接(Linking)

输入:多个 .o 目标文件 + 库文件(如 libc.a)> 。

输出:最终的可执行文件(如 a.out)。

主要操作:

  1. 符号解析:找到所有未定义的函数/变量(如 > printf)。

  2. 重定位:调整代码和数据的内存地址,使它们能> 正确运行。

  3. 静态链接:将库代码直接合并到可执行文件(如 > libc.a)。

  4. 动态链接:运行时加载共享库(如 libc.so)。

示例:
gcc main.o -o a.out

2. 栈(Stack)与堆(Heap)的区别

程序运行时,内存主要分为以下几个区域:

代码区(Text):存放编译后的机器指令。

静态/全局区(BSS/Data):存放全局变量、静态变量。

栈(Stack):存放局部变量、函数调用信息。

堆(Heap):动态分配的内存(由 malloc 管理)。

下面是对堆和栈的特性的对比:

特性 栈(Stack) 堆(Heap)
分配方式 自动(编译器管理) 手动(malloc/free)
分配速度 极快(只需移动栈指针) 较慢(需查找合适内存块)
内存大小 较小(通常几MB) 较大(受系统内存限制)
生命周期 函数结束时自动释放 需手动 free()
碎片化 无(LIFO结构) 可能产生碎片
访问方式 直接(CPU缓存友好) 间接(指针访问)
典型用途 局部变量、函数调用 动态数据结构(链表、树等)

示例:

void foo()
{
  int x = 10;          // x 在栈上分配
  int *p = (int*)malloc(sizeof(int));  // p 指向堆内存
  *p = 20;
  free(p);             // 必须手动释放堆内存
}                        
// 栈上的变量 x 自动释放(栈回收)

3. 标准库的 malloc() 和 free() 函数

(1) malloc(size_t size)

功能:从堆(Heap)中分配 size 字节的内存。

返回值:

a. 成功:返回指向分配内存的指针(void*,需类型> 转换)。

b. 失败:返回 NULL(如堆内存不足)。

示例:

int *arr = (int*) malloc (10 * sizeof(int));  // 分配 10 个 int 的空间
if (arr == NULL) {
  perror("malloc failed");
  exit(1);
}

(2) free(void *ptr)

功能:释放由 malloc 分配的内存。

注意事项:

  1. 只能释放 malloc 返回的指针,否则可能导致未定义行为(UB)。

  2. 释放后应将指针置为 NULL,避免“悬空指针”(Dangling Pointer)。

示例:

  free(arr);
  arr = NULL;  // 避免误用

(3) 底层实现

malloc 和 free 的底层通常由以下两种方式实现:

  1. 系统调用(如 brk 或 mmap):
    • brk:调整堆的结束地址(适用于小内存分配)。

    • mmap:直接映射一块虚拟内存(适用于大内存分配)。

  2. 内存管理算法:
    • 首次适应(First Fit):找到第一个足够大的空闲块。

    • 最佳适应(Best Fit):找到最小的足够大的空闲块。

    • 最差适应(Worst Fit):找到最大的空闲块(减少碎片)。

    • FreeRTOS 的 heap_4 使用 最佳适应 + 内存合并。


三者的联系

  1. 编译链接阶段决定了内存布局:
    • 编译器决定代码、全局变量、栈的初始位置。

    • 链接器决定库函数(如 malloc)的地址。

  2. 栈和堆的分配方式不同:
    • 栈由编译器自动管理,适合生命周期短的数据。

    • 堆由 malloc/free 手动管理,适合动态大小的数据。

  3. malloc/free 依赖堆内存:
    • malloc 从堆中分配内存,free 将其归还。

    • 堆的大小受限于系统内存,而栈通常较小(如 8MB)。

  4. 内存碎片问题:
    • 栈无碎片(LIFO)。

    • 堆可能因频繁 malloc/free 产生碎片(需合并或智能算法)。

总结

编译链接 预处理 → 编译 → 汇编 → 链接,决定内存布局

栈(Stack) 自动分配/释放,速度快,适合局部变量

堆(Heap) 手动 malloc/free,灵活但可能碎片化

malloc 从堆分配内存,需检查 NULL。free 释放内存,避免内存泄漏

理解这些概念后,可以更高效地编写内存安全的C程序,并优化动态内存管理(如选择 FreeRTOS 的 heap_4 减少碎片)。


4.2​调试技巧​​

1.学习使用 ​​FreeRTOS 的跟踪功能​​
跟踪可以实现任务调度序列可视化,是分析复杂系统问题的利器。

a. 跟踪视图

b. 关于如何在不散焦的情况下缩放轨迹的演示图

c. CPU 负载图

d. 任务(和中断)之间的通信路径图

e. 内核对象历史记录图

f. 内核对象使用历史记录图

g. 用户信号绘图


2. 学会利用 ​​栈溢出检测​​ 功能

栈溢出检测功能是使用 FreeRTOS(乃至任何嵌入式系统)时一个至关重要且必须掌握的安全机制。

在 FreeRTOS 中,每个任务都有自己独立的任务栈。这个栈用于存储局部变量、函数调用地址、中断上下文等。

新手在创建任务时,往往低估了该任务实际所需的栈空间大小。给一个任务分配了 100 * sizeof(StackType_t) 的栈,但任务执行时可能因为一个复杂的函数调用或一个大型局部数组(比如 char buffer[256]),瞬间就用完了这 100 个字,并继续向栈外的内存区域(可能是其他任务的数据或堆)写入。

而且这种溢出不会立即触发硬件错误,它会静默地破坏其他关键内存数据。导致的结果极其难以调试,结果可能是:

  • 系统运行几天后突然死机。
  • 某个无关的任务行为异常。
  • 程序跑飞。

这种 bug 被称为“海森堡Bug”(观察它时行为会改变),对于缺乏相关经验的人来说,定位难度是比较高的。

故而绝对不能凭猜测来分配栈大小,必须借助工具进行测量和验证。栈溢出检测就是最直接的工具。

FreeRTOS 提供的两种栈溢出检测方法,在 FreeRTOSConfig.h 中提供了一个配置项 configCHECK_FOR_STACK_OVERFLOW 来启用此功能。对应有两个检测时机(模式)可选:

方法 1: configCHECK_FOR_STACK_OVERFLOW == 1

原理:在任务切换时,检查当前任务栈的栈指针是否已经指向了合法栈空间之外。如果指向了外面,说明已经发生了溢出。

优点:检测开销非常小,因为它只是在任务切换的上下文保存之后做一个简单的指针值比较。

缺点:无法检测到“栈内破坏”。比如任务栈分配了100字,你写入了101字。第101字破坏了其他数据,但栈指针可能还在第100字的位置(尚未移动到101之外),这种情况下方法1检测不到。


方法 2: configCHECK_FOR_STACK_OVERFLOW == 2 (推荐)

原理:在任务创建时,用特定的已知模式(例如 0xA5A5A5A5)填充整个任务栈空间。在任务切换时,检查任务栈最后(高地址方向)的 16(可配置) 个字节是否还是这个魔数。如果这些魔数被改写了,说明任务已经使用了非常接近栈尾的空间,极有可能已经发生或即将发生溢出。

优点:可以检测到“栈内破坏”,能在溢出实际发生前就发出警告,更加安全可靠。

缺点:检测开销比方法1稍大,因为它需要遍历并检查一段内存。


建议:在开发阶段,强烈使用 方法 2 ( == 2)。

如何配置和使用堆栈溢出检测(实战步骤)

  • 第 1 步:启用并配置检测功能

在你的 FreeRTOSConfig.h 文件中,添加或修改以下配置:

/* FreeRTOSConfig.h */

/* 启用栈溢出检测,并使用方法2 */
#define configCHECK_FOR_STACK_OVERFLOW  2

/* 确保钩子函数也被启用 */
#define configUSE_MALLOC_FAILED_HOOK    1
#define configUSE_IDLE_HOOK             0
#define configUSE_TICK_HOOK             0
#define configUSE_DAEMON_TASK_STARTUP_HOOK 0

栈溢出检测的回调函数是通过 vApplicationStackOverflowHook 实现的,你需要实现它。

  • 第 2 步:实现栈溢出钩子函数(Hook Function)

在你的项目中(通常是 main.c),实现这个函数。当检测到溢出时,FreeRTOS 内核会自动调用它。

/* main.c */

#include “FreeRTOS.h”

void vApplicationStackOverflowHook( TaskHandle_t xTask, char *pcTaskName )
{
  (void) xTask; // 抑制未使用变量的警告

  /* 在这里,用你最直接的方式告警! */
  printf("!!!致命错误!!!栈溢出发生在任务: %s\r\n", pcTaskName);

  /* 对于嵌入式系统,通常需要死循环并伴随看门狗复位,或让LED疯狂闪烁 */
  for(;;);
  // 或者直接触发复位
  // NVIC_SystemReset();
}
  • 第 3 步:编译、运行和调试
  1. 编译程序并下载到目标板。
  2. 运行程序。
  3. 如果发生栈溢出,MCU 会执行到你定义的 vApplicationStackOverflowHook 函数中。
  4. 通过串口打印的消息(printf)或者调试器(IDE 会停在这个函数的死循环里),你可以精确地知道是哪个任务(pcTaskName)导致了溢出。
  • 第 4 步:解决问题并优化

一旦发现某个任务溢出,你有两个选择:

  1. 增加该任务的栈大小:在 xTaskCreate 函数中,增大 usStackDepth 参数的值。这是最直接的解决方法。
  2. 优化任务函数:减少函数调用层级、避免在栈上分配大型数组(如 char bigArray[500]),将其改为静态(static)或动态(malloc)分配。

如何科学地确定栈大小?

  1. 使用 FreeRTOS 自带功能:运行程序,使用 uxTaskGetStackHighWaterMark() 函数来查询任务的“历史最小剩余栈空间”(高水位线)。这个值接近0,说明栈空间分配得很紧张;为0,说明已经溢出了。你应该在系统稳定运行后,检查所有任务的这个值,并据此调整栈大小,留出 10%-20% 的安全余量。

  2. 使用IDE调试工具:像STM32CubeIDE、SEGGER SystemView 等工具可以图形化地显示任务栈的使用情况。

总结:新手最佳实践

  1. 开发阶段:在 FreeRTOSConfig.h 中始终设置 #define configCHECK_FOR_STACK_OVERFLOW 2。
  2. 必须实现 vApplicationStackOverflowHook 函数,并用它来快速定位错误任务。
  3. 不要猜栈大小。使用 uxTaskGetStackHighWaterMark() 来测量和优化,为每个任务分配合适的栈空间。
  4. 发布阶段:如果你的产品对性能极度敏感,并且已经充分测试了栈空间,可以考虑关闭此功能(设为0)以节省极少的CPU开销。但对于大多数产品,建议保留。

掌握并习惯性使用这个功能,能为你节省无数小时的调试时间,是从FreeRTOS新手迈向熟练的重要一步。

4.3中断处理​​

任务:掌握在 FreeRTOS 中处理中断的最佳实践,如使用 xxxFromISR版本的 API。

这里以计算 CPU 利用率,评估系统负载这个性能分析需求为例说明。这不仅仅是学会用几个 API,更是关于如何构建一个健壮、高效且响应迅速的嵌入式系统。

解决具体问题前先构建一个清晰的理论框架,然后再深入到性能分析实践。


第一部分:理论核心 —— 在 FreeRTOS 中处理中断的最佳实践

1. 为什么需要 FromISR 版本的 API?

这是一个根本性的安全问题。普通任务(Task)和中断服务例程(ISR)的运行环境完全不同。

  • 任务:运行在 FreeRTOS 的调度器管理之下。它们可以被挂起、恢复、切换。在执行 API 时,调度器可能会进行上下文切换。

  • 中断:硬件触发,抢占任何正在执行的任务。它的执行环境是不确定的,调度器被挂起,任何可能导致任务切换或阻塞的操作(如获取互斥锁、任务通知等待)在中断中都是危险且不被允许的。

因此,FreeRTOS 提供了两套 API:

a. xQueueSend():用于在任务中向队列发送数据。

b. xQueueSendFromISR():用于在中断中向队列发送数据。

FromISR 版本的 API 被设计为:

  • 不可阻塞:绝不会等待(例如,队列已满时直接返回错误码,而不是阻塞等待空间)。

  • 中断安全:其内部实现会短暂地处理中断屏蔽或使用临界区,以保证在 SMP 多核系统或高优先级中断嵌套下的数据安全。

1. 关键 FromISR API 及其使用

最常用的 FromISR API 涉及任务间通信和同步:

  1. 队列(Queue)

xQueueSendToBackFromISR()

xQueueSendToFrontFromISR()

xQueueReceiveFromISR()

这些APIs用于在 ISR 中快速地向任务传递数据或事件。这是中断与任务解耦的最常用方法。

  1. 信号量(Semaphore)

xSemaphoreGiveFromISR()

常用于释放一个计数信号量,告知某个任务中断已发生。任务可以阻塞等待这个信号量,但中断只能快速“给出”。

  1. 任务通知(Task Notification)

xTaskNotifyFromISR()

xTaskNotifyGiveFromISR()

这是效率最高的中断到任务通信机制。它直接通知一个特定的任务,避免了遍历队列或操作信号量的开销,速度极快。在可能的情况下,应优先考虑使用任务通知。

  1. 软件定时器(Software Timer)

xTimerStartFromISR(), xTimerStopFromISR()

用于在中断中启动或停止一个软件定时器回调。

2. “延迟处理”(Deferred Interrupt Processing)模式

这是中断处理的核心设计模式。核心思想是:ISR 只做最紧急的工作,冗长的处理交给高优先级的任务。

ISR(快进快出):

1.  清除中断标志。
2.  可能的话,读取硬件数据到缓冲区。
3.  使用 xQueueSendToBackFromISR() 或 xTaskNotifyFromISR() 等方式,通知一个等待处理的任务。
4.  可能的话,使能其他中断(如果需要)。
5.  快速退出。

任务(延迟处理任务,或称守护任务):

1.  通常具有很高的优先级(可能仅次于定时器服务任务)。
2.  大部分时间阻塞在 xQueueReceive() 或 ulTaskNotifyTake() 等 API 上,等待 ISR 的通知。
3.  一旦被唤醒,执行所有耗时的处理逻辑,如数据解析、计算、控制逻辑等。

这种模式极大地减少了中断关闭的总时间,提高了系统对其它中断的响应能力。

3. portYIELD_FROM_ISR() 与上下文切换

FromISR 系列的 API 通常有一个参数 pxHigherPriorityTaskWoken。这是一个指向 BaseType_t 的指针。

作用:如果本次 API 调用唤醒了一个任务,并且这个任务的优先级高于当前被中断的任务的优先级,那么这个参数会被设置为 pdTRUE。

使用:在 ISR 退出前,你需要检查这个值。

BaseType_t xHigherPriorityTaskWoken = pdFALSE;

// 在ISR中发送消息
xQueueSendToBackFromISR(xQueue, &data, &xHigherPriorityTaskWoken);

// 检查是否需要上下文切换
if(xHigherPriorityTaskWoken != pdFALSE) {
    // 请求在退出ISR后进行一次上下文切换,让更高优先级的任务立刻运行
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}

portYIELD_FROM_ISR() 是一个宏,它实际上会设置一个寄存器,使得中断退出后不是返回被中断的任务,而是直接切换到就绪的最高优先级任务。

综上,中断安全有两种实现方式:

最佳实践一:在中断服务例程中,必须且只能使用带有 FromISR 后缀的 FreeRTOS API。

最佳实践二:总是检查 pxHigherPriorityTaskWoken 参数,并在必要时调用 portYIELD_FROM_ISR() 以保证系统的实时性。


第二部分:性能分析 —— CPU 利用率与系统负载评估

仅仅让系统跑起来是不够的,我们还需要知道它跑得多“吃力”。主要指标就是 CPU 利用率。

1. 什么是 CPU 利用率?

CPU 利用率直观上就是 CPU 用于执行有效工作(而非空闲循环)的时间百分比。

在 FreeRTOS 中,IDLE 任务(空闲任务)总是在后台运行,其优先级最低。当没有任何用户任务需要运行时,调度器就会运行 IDLE 任务。

因此,CPU 利用率可以估算为:
CPU Utilization ≈ 100% - (IDLE任务运行时间 / 总运行时间) * 100%

2. 计算和测量负载的方法

FreeRTOS 提供了两种主要的方法:


方法一:运行时统计功能(configUSE_STATS_FORMATTING_FUNCTIONS)

这是最强大和准确的方法。需要在 FreeRTOSConfig.h 中开启相关配置:

#define configUSE_STATS_FORMATTING_FUNCTIONS  1
#define configGENERATE_RUN_TIME_STATS         1

你还需要实现两个宏:

  • portCONFIGURE_TIMER_FOR_RUN_TIME_STATS():初始化一个高精度的定时器(比系统心跳定时器 configTICK_RATE_HZ 精度高10-100倍)。

  • portGET_RUN_TIME_COUNTER_VALUE():获取这个高精度定时器的当前计数值。

工作原理:FreeRTOS 会在每个任务切换时,记录当前任务占用了多少“运行时计数器”的滴答数。通过分析这些数据,就能计算出每个任务以及空闲任务所占用的时间比例。

使用方法:在您的应用程序中,可以调用 vTaskGetRunTimeStats() 函数,它会将一个详细统计信息填充到一个字符缓冲区中。
void vTaskGetRunTimeStats( char *pcWriteBuffer );

输出结果类似于:

Task Name Abs Time % Time
IDLE 1234567 78.5%
Task1 200000 12.7%
Task2 100000 6.4%
ISR Stack 34567 2.2%

从这里你可以清晰地看到:

  • CPU 利用率:100% - 78.5% = 21.5%

  • 每个任务的负载:Task1 占了 12.7% 的 CPU 时间。

  • 中断负载:虽然中断处理本身的时间难以直接统计,但中断中调用的 FromISR API 所唤醒的高优先级任务所花费的时间会被记录。


方法二:空闲任务钩子函数(Idle Task Hook)

你可以在 FreeRTOSConfig.h 中开启 configUSE_IDLE_HOOK,然后实现 void vApplicationIdleHook( void ) 函数。

这个函数会在空闲任务内循环调用。你可以在一个高精度定时器里记录这个函数被执行的次数。

计算方法如下:

  1. 在固定时间窗口 T 内,假设系统滴答总次数为 S_total。
  2. 在空闲钩子函数中计数,得到空闲执行的次数 S_idle。
  3. CPU Utilization = (1 - S_idle / S_total) * 100%

这种方法比运行时统计功能简单,但精度较低,且无法区分各个任务的负载。

3. 评估系统负载

计算出 CPU 利用率后,可以参考下面分划分进行评估。

  • 低利用率(如 < 30%):系统非常轻松,有大量空闲资源。可以考虑降低主频以省电,或者增加更多功能。

  • 中等利用率(如 30% - 70%):这是比较理想的状态。系统有足够的空闲带宽来处理突发的中断和事件,响应性好。

  • 高利用率(如 > 80%):危险区域! 系统几乎没有缓冲空间。任何意外的负载增加(如更频繁的中断)都可能导致任务无法按时完成,出现响应延迟甚至死机。此时需要优化代码、提高主频或削减功能。

  • 接近 100%:系统已经过载,设计上存在严重问题。

中断相关总结

  1. 设计中断:遵循“延迟处理”模式。ISR 中使用 FromISR API 快速通知任务,繁重工作交给任务处理。
  2. 启用统计:在 FreeRTOSConfig.h 中配置并启用运行时统计功能。这是分析系统性能的“眼睛”。
  3. 测量基线:在系统正常运行时,打印出 vTaskGetRunTimeStats() 的结果,获取当前的 CPU 利用率和各任务负载。
  4. 压力测试:模拟系统最繁忙的情况(例如,最高频率的中断、最大的数据流量),再次测量 CPU 利用率。确保它不会长期超过 80% 的红线。
  5. 优化迭代:如果利用率过高,分析是哪个任务或中断贡献了主要负载,并针对性地进行优化(算法优化、使用更高效的 API 如任务通知、必要时提高主频)。

掌握 FromISR API 的正确使用、学会分析 CPU 利用率,你能从“让代码运行”上升到“让代码高效、可靠地运行”的层次。这是嵌入式高手必备的技能。

5. 项目驱动学习

​​用小项目来整合所有知识​。

DEMO 1

核心功能:
• 创建两个相同优先级的任务(Task1和Task2)
• 每个任务循环执行:打印任务信息 → 执行空循环延迟

关键特点:

  1. 调度配置:使用抢占式调度(configUSE_PREEMPTION=1)
  2. 优先级相同:两个任务均为优先级1,采用时间片轮转调度
  3. 基础实现:使用空循环实现延迟(后续会改进为系统延时函数)
  4. Windows环境:为Windows端口实现的FreeRTOS示例

执行流程:

  1. 初始化两个任务
  2. 启动调度器
  3. 两个任务交替运行(由于优先级相同)
  4. 通过空循环延迟模拟任务处理

这是一个典型的RTOS多任务基础示例,展示了任务创建、调度和基本任务执行模式。


/* FreeRTOS.org includes. */
#include "FreeRTOS.h"
#include "task.h"

/* Demo includes. */
#include "supporting_functions.h"

/* Used as a loop counter to create a very crude delay. */
#define mainDELAY_LOOP_COUNT    ( 0xffffff )

/* The task functions. */
void vTask1( void * pvParameters );
void vTask2( void * pvParameters );

/*-----------------------------------------------------------*/

int main( void )
{
    /* Create one of the two tasks. */
    xTaskCreate( vTask1,   /* Pointer to the function that implements the task. */
                 "Task 1", /* Text name for the task.  This is to facilitate debugging only. */
                 1000,     /* Stack depth - most small microcontrollers will use much less stack than this. */
                 NULL,     /* We are not using the task parameter. */
                 1,        /* This task will run at priority 1. */
                 NULL );   /* We are not using the task handle. */

    /* Create the other task in exactly the same way. */
    xTaskCreate( vTask2, "Task 2", 1000, NULL, 1, NULL );

    /* Start the scheduler to start the tasks executing. */
    vTaskStartScheduler();

    /* The following line should never be reached because vTaskStartScheduler()
    *  will only return if there was not enough FreeRTOS heap memory available to
    *  create the Idle and (if configured) Timer tasks.  Heap management, and
    *  techniques for trapping heap exhaustion, are described in the book text. */
    for( ; ; )
    {
    }

    return 0;
}
/*-----------------------------------------------------------*/

void vTask1( void * pvParameters )
{
    const char * pcTaskName = "Task 1 is running\r\n";
    volatile uint32_t ul;

    /* As per most tasks, this task is implemented in an infinite loop. */
    for( ; ; )
    {
        /* Print out the name of this task. */
        vPrintString( pcTaskName );

        /* Delay for a period. */
        for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ )
        {
            /* This loop is just a very crude delay implementation.  There is
             * nothing to do in here.  Later exercises will replace this crude
             * loop with a proper delay/sleep function. */
        }
    }
}
/*-----------------------------------------------------------*/

void vTask2( void * pvParameters )
{
    const char * pcTaskName = "Task 2 is running\r\n";
    volatile uint32_t ul;

    /* As per most tasks, this task is implemented in an infinite loop. */
    for( ; ; )
    {
        /* Print out the name of this task. */
        vPrintString( pcTaskName );

        /* Delay for a period. */
        for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ )
        {
            /* This loop is just a very crude delay implementation.  There is
             * nothing to do in here.  Later exercises will replace this crude
             * loop with a proper delay/sleep function. */
        }
    }
}

DEMO 2

下面一个优化后的FreeRTOS双任务演示程序,主要改进和特点:

核心改进:
• 单一任务函数:使用vTaskFunction一个函数实现两个任务,通过参数区分不同任务行为

• 参数化设计:通过pvParameters传递不同的显示字符串(“Task 1 is running” / “Task 2 is running”)

程序结构:

  1. 创建两个任务实例:使用相同的任务函数vTaskFunction但传入不同参数
  2. 相同优先级:两个任务均为优先级1,采用时间片轮转调度
  3. 参数处理:任务函数通过类型转换将void指针转换为字符串指针
  4. 相同执行逻辑:打印参数传递的字符串 → 空循环延迟

优点:
• 代码复用:减少重复代码,提高可维护性

• 灵活性:可通过不同参数创建多个功能相似的任务实例

• 内存效率:共享相同的代码段

这仍然是一个基础示例,使用空循环实现延迟(后续会改用FreeRTOS的vTaskDelay()等系统延时函数)。

/* FreeRTOS.org includes. */
#include "FreeRTOS.h"
#include "task.h"

/* Demo includes. */
#include "supporting_functions.h"

/* Used as a loop counter to create a very crude delay. */
#define mainDELAY_LOOP_COUNT    ( 0xffffff )

/* The task function. */
void vTaskFunction( void * pvParameters );

/* Define the strings that will be passed in as the task parameters.  These are
 * defined const and off the stack to ensure they remain valid when the tasks are
 * executing. */
const char * pcTextForTask1 = "Task 1 is running\r\n";
const char * pcTextForTask2 = "Task 2 is running\r\n";

/*-----------------------------------------------------------*/

int main( void )
{
    /* Create one of the two tasks. */
    xTaskCreate( vTaskFunction,             /* Pointer to the function that implements the task. */
                 "Task 1",                  /* Text name for the task.  This is to facilitate debugging only. */
                 1000,                      /* Stack depth - most small microcontrollers will use much less stack than this. */
                 ( void * ) pcTextForTask1, /* Pass the text to be printed in as the task parameter. */
                 1,                         /* This task will run at priority 1. */
                 NULL );                    /* We are not using the task handle. */

    /* Create the other task in exactly the same way.  Note this time that we
     * are creating the SAME task, but passing in a different parameter.  We are
     * creating two instances of a single task implementation. */
    xTaskCreate( vTaskFunction, "Task 2", 1000, ( void * ) pcTextForTask2, 1, NULL );

    /* Start the scheduler to start the tasks executing. */
    vTaskStartScheduler();

    /* The following line should never be reached because vTaskStartScheduler()
    *  will only return if there was not enough FreeRTOS heap memory available to
    *  create the Idle and (if configured) Timer tasks.  Heap management, and
    *  techniques for trapping heap exhaustion, are described in the book text. */
    for( ; ; )
    {
    }

    return 0;
}
/*-----------------------------------------------------------*/

void vTaskFunction( void * pvParameters )
{
    char * pcTaskName;
    volatile uint32_t ul;

    /* The string to print out is passed in via the parameter.  Cast this to a
     * character pointer. */
    pcTaskName = ( char * ) pvParameters;

    /* As per most tasks, this task is implemented in an infinite loop. */
    for( ; ; )
    {
        /* Print out the name of this task. */
        vPrintString( pcTaskName );

        /* Delay for a period. */
        for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ )
        {
            /* This loop is just a very crude delay implementation.  There is
             * nothing to do in here.  Later exercises will replace this crude
             * loop with a proper delay/sleep function. */
        }
    }
}
DEMO 3
  1. 创建两个任务
    • 共用同一个任务函数 vTaskFunction,但通过参数区分行为(分别打印 “Task 1 is running” 和 “Task 2 is running”)。

    • Task 2 优先级(2)高于 Task 1(1),演示 抢占式调度。

  2. 任务行为
    • 每个任务循环执行:打印自身标识 → 空循环延迟(粗糙实现,仅用于演示)。

  3. 启动调度器
    • vTaskStartScheduler() 启动内核调度,高优先级任务可抢占低优先级任务。

关键点:
• 展示 FreeRTOS 多任务创建、参数传递和优先级调度机制。

/* FreeRTOS.org includes. */
#include "FreeRTOS.h"
#include "task.h"

/* Demo includes. */
#include "supporting_functions.h"

/* Used as a loop counter to create a very crude delay. */
#define mainDELAY_LOOP_COUNT    ( 0xffffff )

/* The task function. */
void vTaskFunction( void * pvParameters );

/* Define the strings that will be passed in as the task parameters.  These are
 * defined const and off the stack to ensure they remain valid when the tasks are
 * executing. */
const char * pcTextForTask1 = "Task 1 is running\r\n";
const char * pcTextForTask2 = "Task 2 is running\r\n";

/*-----------------------------------------------------------*/

int main( void )
{
    /* Create the first task at priority 1... */
    xTaskCreate( vTaskFunction, "Task 1", 1000, ( void * ) pcTextForTask1, 1, NULL );

    /* ... and the second task at priority 2.  The priority is the second to
     * last parameter. */
    xTaskCreate( vTaskFunction, "Task 2", 1000, ( void * ) pcTextForTask2, 2, NULL );

    /* Start the scheduler to start the tasks executing. */
    vTaskStartScheduler();

    /* The following line should never be reached because vTaskStartScheduler()
    *  will only return if there was not enough FreeRTOS heap memory available to
    *  create the Idle and (if configured) Timer tasks.  Heap management, and
    *  techniques for trapping heap exhaustion, are described in the book text. */
    for( ; ; )
    {
    }

    return 0;
}
/*-----------------------------------------------------------*/

void vTaskFunction( void * pvParameters )
{
    char * pcTaskName;
    volatile uint32_t ul;

    /* The string to print out is passed in via the parameter.  Cast this to a
     * character pointer. */
    pcTaskName = ( char * ) pvParameters;

    /* As per most tasks, this task is implemented in an infinite loop. */
    for( ; ; )
    {
        /* Print out the name of this task. */
        vPrintString( pcTaskName );

        /* Delay for a period. */
        for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ )
        {
            /* This loop is just a very crude delay implementation.  There is
             * nothing to do in here.  Later exercises will replace this crude
             * loop with a proper delay/sleep function. */
        }
    }
}

DEMO 4

改进版的FreeRTOS多任务演示程序,核心改进和功能:

主要改进

用系统延时替代空循环延迟:
• 使用 vTaskDelay(pdMS_TO_TICKS(250)) 替代了之前的空循环延迟
• 延时250毫秒,更加精确和高效

核心功能

  1. 创建两个不同优先级的任务
    • Task 1:优先级1
    • Task 2:优先级2(更高优先级)

  2. 任务行为
    • 每个任务循环执行:打印标识信息 → 阻塞延时250ms
    • 使用相同的任务函数,通过参数区分显示内容

  3. 调度机制
    • 高优先级任务(Task 2)可以抢占低优先级任务(Task 1)
    • 使用阻塞式延时,任务在延时时主动让出CPU,提高系统效率

关键优势

• 资源友好:任务在延时时进入阻塞状态,释放CPU给其他任务
• 时间精确:使用系统时钟,延时更加准确
• 节能高效:避免了CPU空转浪费资源

这是一个更接近实际应用的FreeRTOS多任务编程范例。


/* FreeRTOS.org includes. */
#include "FreeRTOS.h"
#include "task.h"

/* Demo includes. */
#include "supporting_functions.h"

/* The task function. */
void vTaskFunction( void * pvParameters );

/* Define the strings that will be passed in as the task parameters.  These are
 * defined const and off the stack to ensure they remain valid when the tasks are
 * executing. */
const char * pcTextForTask1 = "Task 1 is running\r\n";
const char * pcTextForTask2 = "Task 2 is running\r\n";

/*-----------------------------------------------------------*/

int main( void )
{
    /* Create the first task at priority 1... */
    xTaskCreate( vTaskFunction, "Task 1", 1000, ( void * ) pcTextForTask1, 1, NULL );

    /* ... and the second task at priority 2.  The priority is the second to
     * last parameter. */
    xTaskCreate( vTaskFunction, "Task 2", 1000, ( void * ) pcTextForTask2, 2, NULL );

    /* Start the scheduler to start the tasks executing. */
    vTaskStartScheduler();

    /* The following line should never be reached because vTaskStartScheduler()
    *  will only return if there was not enough FreeRTOS heap memory available to
    *  create the Idle and (if configured) Timer tasks.  Heap management, and
    *  techniques for trapping heap exhaustion, are described in the book text. */
    for( ; ; )
    {
    }

    return 0;
}
/*-----------------------------------------------------------*/

void vTaskFunction( void * pvParameters )
{
    char * pcTaskName;
    const TickType_t xDelay250ms = pdMS_TO_TICKS( 250UL );

    /* The string to print out is passed in via the parameter.  Cast this to a
     * character pointer. */
    pcTaskName = ( char * ) pvParameters;

    /* As per most tasks, this task is implemented in an infinite loop. */
    for( ; ; )
    {
        /* Print out the name of this task. */
        vPrintString( pcTaskName );

        /* Delay for a period.  This time a call to vTaskDelay() is used which
         * places the task into the Blocked state until the delay period has
         * expired.  The parameter takes a time specified in 'ticks', and the
         * pdMS_TO_TICKS() macro is used (where the xDelay250ms constant is
         * declared) to convert 250 milliseconds into an equivalent time in
         * ticks. */
        vTaskDelay( xDelay250ms );
    }
}

DEMO 5

使用精确周期性延时的FreeRTOS多任务演示程序,核心功能:

主要改进

用周期性延时替代简单延时:
• 使用 vTaskDelayUntil() 替代 vTaskDelay()
• 实现精确的250毫秒周期执行

核心功能

  1. 创建两个不同优先级的任务
    • Task 1:优先级1
    • Task 2:优先级2(更高优先级)

  2. 精确周期性执行
    • 每个任务以固定250ms间隔循环执行
    • 使用 vTaskDelayUntil(&xLastWakeTime, xDelay250ms) 保证执行周期准确性

  3. 时间管理机制
    • xLastWakeTime 记录任务上次唤醒时间
    • 系统自动计算下一次唤醒时间,补偿任务执行时间偏差

关键优势

• 时间精确:避免任务执行时间累积误差,保证严格的时间周期
• 稳定性好:适合需要精确时间间隔的应用(如数据采集、控制循环)
• 资源高效:仍然保持阻塞状态,不浪费CPU资源

这是一个面向实时周期性任务的高级FreeRTOS编程范例,比简单延时更加精确和可靠。


/* FreeRTOS.org includes. */
#include "FreeRTOS.h"
#include "task.h"

/* Demo includes. */
#include "supporting_functions.h"

/* The task function. */
void vTaskFunction( void * pvParameters );

/* Define the strings that will be passed in as the task parameters.  These are
 * defined const and off the stack to ensure they remain valid when the tasks are
 * executing. */
const char * pcTextForTask1 = "Task 1 is running\r\n";
const char * pcTextForTask2 = "Task 2 is running\r\n";

/*-----------------------------------------------------------*/

int main( void )
{
    /* Create the first task at priority 1... */
    xTaskCreate( vTaskFunction, "Task 1", 1000, ( void * ) pcTextForTask1, 1, NULL );

    /* ... and the second task at priority 2.  The priority is the second to
     * last parameter. */
    xTaskCreate( vTaskFunction, "Task 2", 1000, ( void * ) pcTextForTask2, 2, NULL );

    /* Start the scheduler to start the tasks executing. */
    vTaskStartScheduler();

    /* The following line should never be reached because vTaskStartScheduler()
    *  will only return if there was not enough FreeRTOS heap memory available to
    *  create the Idle and (if configured) Timer tasks.  Heap management, and
    *  techniques for trapping heap exhaustion, are described in the book text. */
    for( ; ; )
    {
    }

    return 0;
}
/*-----------------------------------------------------------*/

void vTaskFunction( void * pvParameters )
{
    char * pcTaskName;
    TickType_t xLastWakeTime;
    const TickType_t xDelay250ms = pdMS_TO_TICKS( 250UL );

    /* The string to print out is passed in via the parameter.  Cast this to a
     * character pointer. */
    pcTaskName = ( char * ) pvParameters;

    /* The xLastWakeTime variable needs to be initialized with the current tick
     * count.  Note that this is the only time we access this variable.  From this
     * point on xLastWakeTime is managed automatically by the vTaskDelayUntil()
     * API function. */
    xLastWakeTime = xTaskGetTickCount();

    /* As per most tasks, this task is implemented in an infinite loop. */
    for( ; ; )
    {
        /* Print out the name of this task. */
        vPrintString( pcTaskName );

        /* We want this task to execute exactly every 250 milliseconds.  As per
         * the vTaskDelay() function, time is measured in ticks, and the
         * pdMS_TO_TICKS() macro is used to convert this to milliseconds.
         * xLastWakeTime is automatically updated within vTaskDelayUntil() so does not
         * have to be updated by this task code. */
        vTaskDelayUntil( &xLastWakeTime, xDelay250ms );
    }
}

DEMO 6

混合型任务调度演示程序,展示了FreeRTOS中不同任务类型的协同工作:

核心功能

  1. 三种任务类型混合

• 两个连续处理任务(优先级1):vContinuousProcessingTask

• 不断循环打印信息,从不阻塞或延迟

• 类似"忙等待"任务,持续占用CPU

• 一个周期性任务(优先级2):vPeriodicTask

• 每3毫秒精确周期执行

• 使用vTaskDelayUntil()保证时间精度

  1. 优先级调度演示

• 高优先级周期性任务(2)可以抢占低优先级连续任务(1)

• 但当周期性任务阻塞时(延时3ms),连续任务继续执行

  1. 时间关键型任务设计

• 周期性任务使用精确延时,确保严格的时间间隔

• 连续任务展示非阻塞式任务行为

关键特点

• 实时性演示:展示高优先级周期性任务如何保证时间精度

• 资源竞争:连续任务持续占用CPU,周期性任务通过抢占获得执行权

• 混合负载:模拟实际系统中既有时间关键任务,又有后台处理任务的场景

这个程序演示了FreeRTOS在混合任务负载下的调度行为,特别是如何通过优先级机制保证时间关键任务的实时性。


/* FreeRTOS.org includes. */
#include "FreeRTOS.h"
#include "task.h"

/* Demo includes. */
#include "supporting_functions.h"

/* The task functions. */
void vContinuousProcessingTask( void * pvParameters );
void vPeriodicTask( void * pvParameters );

/* Define the strings that will be passed in as the task parameters.  These are
 * defined const and off the stack to ensure they remain valid when the tasks are
 * executing. */
const char * pcTextForTask1 = "Continuous task 1 running\r\n";
const char * pcTextForTask2 = "Continuous task 2 running\r\n";
const char * pcTextForPeriodicTask = "Periodic task is running\r\n";

/*-----------------------------------------------------------*/

int main( void )
{
    /* Create two instances of the continuous processing task, both at priority	1. */
    xTaskCreate( vContinuousProcessingTask, "Task 1", 1000, ( void * ) pcTextForTask1, 1, NULL );
    xTaskCreate( vContinuousProcessingTask, "Task 2", 1000, ( void * ) pcTextForTask2, 1, NULL );

    /* Create one instance of the periodic task at priority 2. */
    xTaskCreate( vPeriodicTask, "Task 3", 1000, ( void * ) pcTextForPeriodicTask, 2, NULL );

    /* Start the scheduler to start the tasks executing. */
    vTaskStartScheduler();

    /* The following line should never be reached because vTaskStartScheduler()
    *  will only return if there was not enough FreeRTOS heap memory available to
    *  create the Idle and (if configured) Timer tasks.  Heap management, and
    *  techniques for trapping heap exhaustion, are described in the book text. */
    for( ; ; )
    {
    }

    return 0;
}
/*-----------------------------------------------------------*/

void vContinuousProcessingTask( void * pvParameters )
{
    char * pcTaskName;

    /* The string to print out is passed in via the parameter.  Cast this to a
     * character pointer. */
    pcTaskName = ( char * ) pvParameters;

    /* As per most tasks, this task is implemented in an infinite loop. */
    for( ; ; )
    {
        /* Print out the name of this task.  This task just does this repeatedly
         * without ever blocking or delaying. */
        vPrintString( pcTaskName );
    }
}
/*-----------------------------------------------------------*/

void vPeriodicTask( void * pvParameters )
{
    TickType_t xLastWakeTime;
    const TickType_t xDelay5ms = pdMS_TO_TICKS( 3UL );

    /* The xLastWakeTime variable needs to be initialized with the current tick
     * count.  Note that this is the only time we access this variable.  From this
     * point on xLastWakeTime is managed automatically by the vTaskDelayUntil()
     * API function. */
    xLastWakeTime = xTaskGetTickCount();

    /* As per most tasks, this task is implemented in an infinite loop. */
    for( ; ; )
    {
        /* Print out the name of this task. */
        vPrintString( "Periodic task is running\r\n" );

        /* We want this task to execute exactly every 10 milliseconds. */
        vTaskDelayUntil( &xLastWakeTime, xDelay5ms );
    }
}

DEMO 7

多任务调度和空闲任务钩子函数的使用。

核心功能

  1. 多任务创建与调度
    • 创建了两个任务(Task 1 和 Task 2)

    • 两个任务共享相同的任务函数 vTaskFunction,但通过参数传递不同的字符串

    • 任务优先级不同(Task 1: 优先级1,Task 2: 优先级2)

  2. 任务执行逻辑
    • 每个任务在无限循环中:

    ◦ 打印任务名称和空闲循环计数

    ◦ 延迟 250ms(使用 vTaskDelay 进入阻塞状态)

  3. 空闲任务钩子函数
    • vApplicationIdleHook() 在系统空闲时自动调用

    • 每次空闲时递增 ulIdleCycleCount 计数器

关键机制

• 任务调度:FreeRTOS 内核根据优先级调度两个任务

• 阻塞延迟:vTaskDelay() 让出 CPU 时间,允许其他任务运行

• 空闲任务监控:通过钩子函数监控系统空闲时间

• 参数传递:通过任务创建参数区分不同任务的执行内容

运行效果

程序会交替显示:


Task 1 is running [计数]
Task 2 is running [计数]

其中计数值反映了系统空闲时间的多少,优先级较高的 Task 2 会获得更多的执行机会。


/* FreeRTOS.org includes. */
#include "FreeRTOS.h"
#include "task.h"

/* Demo includes. */
#include "supporting_functions.h"

/* The task function. */
void vTaskFunction( void * pvParameters );

/* A variable that is incremented by the idle task hook function. */
static uint32_t ulIdleCycleCount = 0UL;

/* Define the strings that will be passed in as the task parameters.  These are
 * defined const and off the stack to ensure they remain valid when the tasks are
 * executing. */
const char * pcTextForTask1 = "Task 1 is running\r\n";
const char * pcTextForTask2 = "Task 2 is running\r\n";

/*-----------------------------------------------------------*/

int main( void )
{
    /* Create the first task at priority 1... */
    xTaskCreate( vTaskFunction, "Task 1", 1000, ( void * ) pcTextForTask1, 1, NULL );

    /* ... and the second task at priority 2.  The priority is the second to
     * last parameter. */
    xTaskCreate( vTaskFunction, "Task 2", 1000, ( void * ) pcTextForTask2, 2, NULL );

    /* Start the scheduler to start the tasks executing. */
    vTaskStartScheduler();

    /* The following line should never be reached because vTaskStartScheduler()
    *  will only return if there was not enough FreeRTOS heap memory available to
    *  create the Idle and (if configured) Timer tasks.  Heap management, and
    *  techniques for trapping heap exhaustion, are described in the book text. */
    for( ; ; )
    {
    }

    return 0;
}
/*-----------------------------------------------------------*/

void vTaskFunction( void * pvParameters )
{
    char * pcTaskName;
    const TickType_t xDelay250ms = pdMS_TO_TICKS( 250UL );

    /* The string to print out is passed in via the parameter.  Cast this to a
     * character pointer. */
    pcTaskName = ( char * ) pvParameters;

    /* As per most tasks, this task is implemented in an infinite loop. */
    for( ; ; )
    {
        /* Print out the name of this task AND the number of times ulIdleCycleCount
         * has been incremented. */
        vPrintStringAndNumber( pcTaskName, ulIdleCycleCount );

        /* Delay for a period.  This time we use a call to vTaskDelay() which
         * puts the task into the Blocked state until the delay period has expired.
         * The delay period is specified in 'ticks'. */
        vTaskDelay( xDelay250ms );
    }
}
/*-----------------------------------------------------------*/

/* Idle hook functions MUST be called vApplicationIdleHook(), take no parameters,
 * and return void. */
void vApplicationIdleHook( void )
{
    /* This hook function does nothing but increment a counter. */
    ulIdleCycleCount++;
}

DEMO 8

任务优先级动态调整。

核心功能分析

  1. 任务创建与初始优先级

• Task1:优先级2(较高)

• Task2:优先级1(较低)

  1. 优先级动态调整机制

• Task1运行时将Task2的优先级提高到3(高于自身)

• Task2运行时将自身优先级降低到-1(低于Task1)

  1. 调度行为

  2. 初始状态:Task1先运行(优先级高)

  3. Task1提升Task2优先级后,Task2立即抢占执行

  4. Task2降低自身优先级后,Task1重新获得执行权

  5. 循环往复,形成任务交替执行模式

关键FreeRTOS API使用

• xTaskCreate() - 创建任务

• uxTaskPriorityGet() - 获取任务优先级

• vTaskPrioritySet() - 设置任务优先级

• vTaskStartScheduler() - 启动任务调度器

技术特点

• 演示了优先级继承模式

• 展示了实时系统中动态优先级调整的实际应用

• 说明了高优先级任务如何主动让出CPU给低优先级任务

这种模式在实时系统中很常见,特别是在需要处理高优先级事件但又不希望长期阻塞低优先级任务的场景中。


/* FreeRTOS.org includes. */
#include "FreeRTOS.h"
#include "task.h"

/* Demo includes. */
#include "supporting_functions.h"

/* The two task functions. */
void vTask1( void * pvParameters );
void vTask2( void * pvParameters );

/* Used to hold the handle of Task2. */
TaskHandle_t xTask2Handle;

/*-----------------------------------------------------------*/

int main( void )
{
    /* Create the first task at priority 2.  This time the task parameter is
     * not used and is set to NULL.  The task handle is also not used so likewise
     * is also set to NULL. */
    xTaskCreate( vTask1, "Task 1", 1000, NULL, 2, NULL );
    /* The task is created at priority 2 ^. */

    /* Create the second task at priority 1 - which is lower than the priority
     * given to Task1.  Again the task parameter is not used so is set to NULL -
     * BUT this time we want to obtain a handle to the task so pass in the address
     * of the xTask2Handle variable. */
    xTaskCreate( vTask2, "Task 2", 1000, NULL, 1, &xTask2Handle );
    /* The task handle is the last parameter ^^^^^^^^^^^^^ */

    /* Start the scheduler to start the tasks executing. */
    vTaskStartScheduler();

    /* The following line should never be reached because vTaskStartScheduler()
    *  will only return if there was not enough FreeRTOS heap memory available to
    *  create the Idle and (if configured) Timer tasks.  Heap management, and
    *  techniques for trapping heap exhaustion, are described in the book text. */
    for( ; ; )
    {
    }

    return 0;
}
/*-----------------------------------------------------------*/

void vTask1( void * pvParameters )
{
    UBaseType_t uxPriority;

    /* This task will always run before Task2 as it has the higher priority.
     * Neither Task1 nor Task2 ever block so both will always be in either the
     * Running or the Ready state.
     *
     * Query the priority at which this task is running - passing in NULL means
     * "return our own priority". */
    uxPriority = uxTaskPriorityGet( NULL );

    for( ; ; )
    {
        /* Print out the name of this task. */
        vPrintString( "Task1 is running\r\n" );

        /* Setting the Task2 priority above the Task1 priority will cause
         * Task2 to immediately start running (as then Task2 will have the higher
         * priority of the    two created tasks). */
        vPrintString( "About to raise the Task2 priority\r\n" );
        vTaskPrioritySet( xTask2Handle, ( uxPriority + 1 ) );

        /* Task1 will only run when it has a priority higher than Task2.
         * Therefore, for this task to reach this point Task2 must already have
         * executed and set its priority back down to 0. */
    }
}

/*-----------------------------------------------------------*/

void vTask2( void * pvParameters )
{
    UBaseType_t uxPriority;

    /* Task1 will always run before this task as Task1 has the higher priority.
     * Neither Task1 nor Task2 ever block so will always be in either the
     * Running or the Ready state.
     *
     * Query the priority at which this task is running - passing in NULL means
     * "return our own priority". */
    uxPriority = uxTaskPriorityGet( NULL );

    for( ; ; )
    {
        /* For this task to reach this point Task1 must have already run and
         * set the priority of this task higher than its own.
         *
         * Print out the name of this task. */
        vPrintString( "Task2 is running\r\n" );

        /* Set our priority back down to its original value.  Passing in NULL
         * as the task handle means "change our own priority".  Setting the
         * priority below that of Task1 will cause Task1 to immediately start
         * running again. */
        vPrintString( "About to lower the Task2 priority\r\n" );
        vTaskPrioritySet( NULL, ( uxPriority - 2 ) );
    }
}
/*-----------------------------------------------------------*/

DEMO 9

动态创建任务

核心功能

  1. 动态任务创建与删除

• vTask1 在运行时动态创建 vTask2(通过 xTaskCreate)

• vTask2 在执行后立即删除自身(通过 vTaskDelete)

• 演示了任务可以运行时创建,而不仅限于启动时静态创建

  1. 优先级调度

• vTask1 优先级为 1

• vTask2 优先级为 2(更高优先级)

• 当 vTask2 创建后,由于优先级更高,立即抢占 vTask1 执行

• vTask2 删除自身后,控制权返回给 vTask1

执行流程

  1. main() 创建优先级1的 Task1
  2. Task1 运行,打印消息后创建优先级2的 Task2
  3. 调度器立即切换到更高优先级的 Task2
  4. Task2 打印消息后删除自己
  5. 控制权返回给 Task1,延迟100ms
  6. 循环重复此过程

关键特性展示

• 任务句柄的使用:通过 xTask2Handle 跟踪和管理任务

• 优先级抢占:高优先级任务立即执行

• 动态内存管理:任务运行时创建和销毁

• 时间延迟:使用 vTaskDelay 进行精确的时间控制

这个示例完美演示了 FreeRTOS 实时调度器的动态任务管理和优先级调度机制。


/* FreeRTOS.org includes. */
#include "FreeRTOS.h"
#include "task.h"

/* Demo includes. */
#include "supporting_functions.h"

/* The two task functions. */
void vTask1( void * pvParameters );
void vTask2( void * pvParameters );

/* Used to hold the handle of Task2. */
TaskHandle_t xTask2Handle;

/*-----------------------------------------------------------*/

int main( void )
{
    /* Create the first task at priority 1.  This time the task parameter is
     * not used and is set to NULL.  The task handle is also not used so likewise
     * is also set to NULL. */
    xTaskCreate( vTask1, "Task 1", 1000, NULL, 1, NULL );
    /* The task is created at priority 1 ^. */

    /* Start the scheduler to start the tasks executing. */
    vTaskStartScheduler();

    /* The following line should never be reached because vTaskStartScheduler()
    *  will only return if there was not enough FreeRTOS heap memory available to
    *  create the Idle and (if configured) Timer tasks.  Heap management, and
    *  techniques for trapping heap exhaustion, are described in the book text. */
    for( ; ; )
    {
    }

    return 0;
}
/*-----------------------------------------------------------*/

void vTask1( void * pvParameters )
{
    const TickType_t xDelay100ms = pdMS_TO_TICKS( 100UL );

    for( ; ; )
    {
        /* Print out the name of this task. */
        vPrintString( "Task1 is running\r\n" );

        /* Create task 2 at a higher priority.  Again the task parameter is not
         * used so is set to NULL - BUT this time we want to obtain a handle to the
         * task so pass in the address of the xTask2Handle variable. */
        xTaskCreate( vTask2, "Task 2", 1000, NULL, 2, &xTask2Handle );
        /* The task handle is the last parameter ^^^^^^^^^^^^^ */

        /* Task2 has/had the higher priority, so for Task1 to reach here Task2
         * must have already executed and deleted itself.  Delay for 100
         * milliseconds. */
        vTaskDelay( xDelay100ms );
    }
}

/*-----------------------------------------------------------*/

void vTask2( void * pvParameters )
{
    /* Task2 does nothing but delete itself.  To do this it could call vTaskDelete()
     * using a NULL parameter, but instead and purely for demonstration purposes it
     * instead calls vTaskDelete() with its own task handle. */
    vPrintString( "Task2 is running and about to delete itself\r\n" );
    vTaskDelete( xTask2Handle );
}
/*-----------------------------------------------------------*/

DEMO 10 任务间的通信与同步

FreeRTOS 中队列(Queue)的核心功能:任务间的通信与同步。

核心功能概述

这是一个典型的生产者-消费者模型,其中:
• 生产者 (vSenderTask):两个发送任务不断向队列中写入数据(一个写100,一个写200)。

• 消费者 (vReceiverTask):一个接收任务从队列中读取并处理这些数据。

队列在其中扮演了缓冲区和同步机制的角色,解耦了生产者和消费者,使它们可以以不同的速率运行。

核心功能详细解析

  1. 队列的创建 (main 函数)

xQueue = xQueueCreate( 5, sizeof( int32_t ) );

• 功能:创建了一个能容纳 5 个元素的队列,每个元素是一个 int32_t(4字节整数)。

• 意义:队列是全局的 (xQueue),因此可以被所有任务访问,成为了任务间共享数据的通信通道。

  1. 数据的发送 - 生产者 (vSenderTask 函数)

xStatus = xQueueSendToBack( xQueue, &lValueToSend, 0 );

• 功能:将数据 (lValueToSend, 即 100 或 200) 发送到队列的尾部。

• 关键参数 0:xTicksToWait 设置为 0,意味着如果队列已满,发送函数会立即返回错误码 errQUEUE_FULL,而不会阻塞等待。这是因为代码逻辑认为有两个发送者不断发送,而接收者优先级更高会及时取走数据,队列理论上不应该满。这是一种非阻塞的发送方式。

  1. 数据的接收 - 消费者 (vReceiverTask 函数)

xStatus = xQueueReceive( xQueue, &lReceivedValue, xTicksToWait );

• 功能:从队列的头部接收一个数据,并将其存入 lReceivedValue 变量。

• 关键参数 xTicksToWait:这里设置为等待 100 毫秒 (pdMS_TO_TICKS( 100UL ))。这意味着:

◦   如果队列不为空:立即取出数据,返回 pdPASS。

◦   如果队列为空:任务将进入阻塞态 (Blocked State),最多等待 100ms 让数据到来。

◦   如果在超时前数据到来:任务立即被唤醒,取出数据,返回 pdPASS。

◦   如果超时后队列仍为空:函数返回 errQUEUE_EMPTY。

• 意义:这是一种带超时的阻塞接收方式。它避免了接收任务在队列为空时持续空转(忙等待),从而节省了CPU资源。接收任务的优先级 (2) 高于发送任务 (1),确保了它一旦就绪(例如收到数据),就能立即抢占CPU进行处理,实现了高效的响应。

  1. 队列状态的检查

if( uxQueueMessagesWaiting( xQueue ) != 0 )
{
vPrintString( “Queue should have been empty!\r\n” );
}

• 功能:uxQueueMessagesWaiting 返回队列中当前存在的消息数量。

• 代码逻辑:接收任务在每次读取前检查队列。因为它优先级最高,理论上应该在发送任务(优先级低)没能向队列写入新数据之前就被唤醒并取走数据,所以队列应该总是空的。如果检查发现队列不为空,则打印错误信息。这是一个简单的断言机制,用于验证设计预期。

总结:核心功能

  1. 任务间通信:通过一个共享的队列,两个发送任务可以将整数数据安全地传递给一个接收任务。

  2. 数据缓冲:长度为5的队列可以暂存数据,平滑生产者和消费者之间的速度差异。

  3. 任务同步:
    ◦ 发送任务使用非阻塞方式写入。

    ◦ 接收任务使用带超时的阻塞方式读取,高效地等待数据。

  4. 优先级调度演示:高优先级的接收任务会优先运行。一旦数据到达队列,接收任务就会从阻塞态中解除,并抢占低优先级的发送任务,及时处理数据。这体现了FreeRTOS基于优先级的抢占式调度机制。

简单来说,这个程序的核心功能就是演示了如何用FreeRTOS的队列,让多个任务安全、高效地传递数据,并协调它们之间的执行节奏。


/* FreeRTOS.org includes. */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"

/* Demo includes. */
#include "supporting_functions.h"


/* The tasks to be created.  Two instances are created of the sender task while
 * only a single instance is created of the receiver task. */
static void vSenderTask( void * pvParameters );
static void vReceiverTask( void * pvParameters );

/*-----------------------------------------------------------*/

/* Declare a variable of type QueueHandle_t.  This is used to store the queue
 * that is accessed by all three tasks. */
QueueHandle_t xQueue;


int main( void )
{
    /* The queue is created to hold a maximum of 5 long values. */
    xQueue = xQueueCreate( 5, sizeof( int32_t ) );

    if( xQueue != NULL )
    {
        /* Create two instances of the task that will write to the queue.  The
         * parameter is used to pass the value that the task should write to the queue,
         * so one task will continuously write 100 to the queue while the other task
         * will continuously write 200 to the queue.  Both tasks are created at
         * priority 1. */
        xTaskCreate( vSenderTask, "Sender1", 1000, ( void * ) 100, 1, NULL );
        xTaskCreate( vSenderTask, "Sender2", 1000, ( void * ) 200, 1, NULL );

        /* Create the task that will read from the queue.  The task is created with
         * priority 2, so above the priority of the sender tasks. */
        xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 2, NULL );

        /* Start the scheduler so the created tasks start executing. */
        vTaskStartScheduler();
    }
    else
    {
        /* The queue could not be created. */
    }

    /* The following line should never be reached because vTaskStartScheduler()
    *  will only return if there was not enough FreeRTOS heap memory available to
    *  create the Idle and (if configured) Timer tasks.  Heap management, and
    *  techniques for trapping heap exhaustion, are described in the book text. */
    for( ; ; )
    {
    }

    return 0;
}
/*-----------------------------------------------------------*/

static void vSenderTask( void * pvParameters )
{
    int32_t lValueToSend;
    BaseType_t xStatus;

    /* Two instances are created of this task so the value that is sent to the
     * queue is passed in via the task parameter rather than be hard coded.  This way
     * each instance can use a different value.  Cast the parameter to the required
     * type. */
    lValueToSend = ( int32_t ) pvParameters;

    /* As per most tasks, this task is implemented within an infinite loop. */
    for( ; ; )
    {
        /* The first parameter is the queue to which data is being sent.  The
         * queue was created before the scheduler was started, so before this task
         * started to execute.
         *
         * The second parameter is the address of the data to be sent.
         *
         * The third parameter is the Block time � the time the task should be kept
         * in the Blocked state to wait for space to become available on the queue
         * should the queue already be full.  In this case we don�t specify a block
         * time because there should always be space in the queue. */
        xStatus = xQueueSendToBack( xQueue, &lValueToSend, 0 );

        if( xStatus != pdPASS )
        {
            /* We could not write to the queue because it was full � this must
             * be an error as the queue should never contain more than one item! */
            vPrintString( "Could not send to the queue.\r\n" );
        }
    }
}
/*-----------------------------------------------------------*/

static void vReceiverTask( void * pvParameters )
{
/* Declare the variable that will hold the values received from the queue. */
    int32_t lReceivedValue;
    BaseType_t xStatus;
    const TickType_t xTicksToWait = pdMS_TO_TICKS( 100UL );

    /* This task is also defined within an infinite loop. */
    for( ; ; )
    {
        /* As this task unblocks immediately that data is written to the queue this
         * call should always find the queue empty. */
        if( uxQueueMessagesWaiting( xQueue ) != 0 )
        {
            vPrintString( "Queue should have been empty!\r\n" );
        }

        /* The first parameter is the queue from which data is to be received.  The
         * queue is created before the scheduler is started, and therefore before this
         * task runs for the first time.
         *
         * The second parameter is the buffer into which the received data will be
         * placed.  In this case the buffer is simply the address of a variable that
         * has the required size to hold the received data.
         *
         * the last parameter is the block time � the maximum amount of time that the
         * task should remain in the Blocked state to wait for data to be available should
         * the queue already be empty. */
        xStatus = xQueueReceive( xQueue, &lReceivedValue, xTicksToWait );

        if( xStatus == pdPASS )
        {
            /* Data was successfully received from the queue, print out the received
             * value. */
            vPrintStringAndNumber( "Received = ", lReceivedValue );
        }
        else
        {
            /* We did not receive anything from the queue even after waiting for 100ms.
             * This must be an error as the sending tasks are free running and will be
             * continuously writing to the queue. */
            vPrintString( "Could not receive from the queue.\r\n" );
        }
    }
}

DEMO 11 任务间数据传输

这段代码展示了FreeRTOS中队列(Queue)的核心功能实现,主要演示了多任务间通过队列进行数据通信的机制。

核心功能分析:

  1. 队列创建与配置

xQueue = xQueueCreate(3, sizeof(Data_t));

• 创建了一个最多能容纳3个Data_t结构体的队列

• 队列作为全局变量,被所有任务共享访问

  1. 数据结构设计

typedef struct {
uint8_t ucValue; // 数据值
DataSource_t eDataSource; // 数据来源标识
} Data_t;

• 使用结构体封装数据,包含数据值和来源标识

• 支持区分不同发送者的数据

  1. 多生产者-单消费者模式

• 生产者任务(2个发送者):

• 优先级2,高于消费者

• 持续向队列发送数据

• 使用100ms阻塞时间等待队列空间

• 消费者任务(1个接收者):

• 优先级1,低于生产者

• 从队列接收并处理数据

• 无阻塞等待(队列常满)

  1. 队列操作机制

• 发送操作 (xQueueSendToBack):

• 队列满时任务进入阻塞状态

• 等待接收者取出数据释放空间

• 接收操作 (xQueueReceive):

• 立即取出数据(不阻塞)

• 处理数据并识别来源

  1. 任务调度策略

• 高优先级发送者优先执行,填满队列

• 当发送者因队列满而阻塞时,低优先级接收者获得执行机会

• 接收者取出数据后,发送者解除阻塞继续发送

  1. 错误检测机制

• 检查队列操作返回值 (pdPASS/错误)

• 验证队列状态 (uxQueueMessagesWaiting)

• 输出错误信息便于调试

核心功能总结:

这段代码实现了FreeRTOS中典型的任务间通信机制,通过队列实现了:
• 数据的安全传递(线程安全)

• 任务间的同步与协调

• 优先级驱动的调度

• 阻塞机制处理资源竞争

这种模式非常适合实现生产者-消费者场景,确保数据在任务间高效、安全地传递。


/* FreeRTOS.org includes. */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"

/* Demo includes. */
#include "supporting_functions.h"

/* The tasks to be created.  Two instances are created of the sender task while
 * only a single instance is created of the receiver task. */
static void vSenderTask( void * pvParameters );
static void vReceiverTask( void * pvParameters );

/*-----------------------------------------------------------*/

/* Declare a variable of type QueueHandle_t.  This is used to store the queue
 * that is accessed by all three tasks. */
QueueHandle_t xQueue;

typedef enum
{
    eSender1,
    eSender2
} DataSource_t;

/* Define the structure type that will be passed on the queue. */
typedef struct
{
    uint8_t ucValue;
    DataSource_t eDataSource;
} Data_t;

/* Declare two variables of type Data_t that will be passed on the queue. */
static const Data_t xStructsToSend[ 2 ] =
{
    { 100, eSender1 }, /* Used by Sender1. */
    { 200, eSender2 } /* Used by Sender2. */
};

int main( void )
{
    /* The queue is created to hold a maximum of 3 structures of type Data_t. */
    xQueue = xQueueCreate( 3, sizeof( Data_t ) );

    if( xQueue != NULL )
    {
        /* Create two instances of the task that will write to the queue.  The
         * parameter is used to pass the structure that the task should write to the
         * queue, so one task will continuously send xStructsToSend[ 0 ] to the queue
         * while the other task will continuously send xStructsToSend[ 1 ].  Both
         * tasks are created at priority 2, which is above the priority of the receiver. */
        xTaskCreate( vSenderTask, "Sender1", 1000, ( void * ) &( xStructsToSend[ 0 ] ), 2, NULL );
        xTaskCreate( vSenderTask, "Sender2", 1000, ( void * ) &( xStructsToSend[ 1 ] ), 2, NULL );

        /* Create the task that will read from the queue.  The task is created with
         * priority 1, so below the priority of the sender tasks. */
        xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 1, NULL );

        /* Start the scheduler so the created tasks start executing. */
        vTaskStartScheduler();
    }
    else
    {
        /* The queue could not be created. */
    }

    /* The following line should never be reached because vTaskStartScheduler()
    *  will only return if there was not enough FreeRTOS heap memory available to
    *  create the Idle and (if configured) Timer tasks.  Heap management, and
    *  techniques for trapping heap exhaustion, are described in the book text. */
    for( ; ; )
    {
    }

    return 0;
}
/*-----------------------------------------------------------*/

static void vSenderTask( void * pvParameters )
{
    BaseType_t xStatus;
    const TickType_t xTicksToWait = pdMS_TO_TICKS( 100UL );

    /* As per most tasks, this task is implemented within an infinite loop. */
    for( ; ; )
    {
        /* The first parameter is the queue to which data is being sent.  The
         * queue was created before the scheduler was started, so before this task
         * started to execute.
         *
         * The second parameter is the address of the structure being sent.  The
         * address is passed in as the task parameter.
         *
         * The third parameter is the Block time - the time the task should be kept
         * in the Blocked state to wait for space to become available on the queue
         * should the queue already be full.  A block time is specified as the queue
         * will become full.  Items will only be removed from the queue when both
         * sending tasks are in the Blocked state.. */
        xStatus = xQueueSendToBack( xQueue, pvParameters, xTicksToWait );

        if( xStatus != pdPASS )
        {
            /* We could not write to the queue because it was full - this must
             * be an error as the receiving task should make space in the queue
             * as soon as both sending tasks are in the Blocked state. */
            vPrintString( "Could not send to the queue.\r\n" );
        }
    }
}
/*-----------------------------------------------------------*/

static void vReceiverTask( void * pvParameters )
{
/* Declare the structure that will hold the values received from the queue. */
    Data_t xReceivedStructure;
    BaseType_t xStatus;

    /* This task is also defined within an infinite loop. */
    for( ; ; )
    {
        /* As this task only runs when the sending tasks are in the Blocked state,
         * and the sending tasks only block when the queue is full, this task should
         * always find the queue to be full.  3 is the queue length. */
        if( uxQueueMessagesWaiting( xQueue ) != 3 )
        {
            vPrintString( "Queue should have been full!\r\n" );
        }

        /* The first parameter is the queue from which data is to be received.  The
         * queue is created before the scheduler is started, and therefore before this
         * task runs for the first time.
         *
         * The second parameter is the buffer into which the received data will be
         * placed.  In this case the buffer is simply the address of a variable that
         * has the required size to hold the received structure.
         *
         * The last parameter is the block time - the maximum amount of time that the
         * task should remain in the Blocked state to wait for data to be available
         * should the queue already be empty.  A block time is not necessary as this
         * task will only run when the queue is full so data will always be available. */
        xStatus = xQueueReceive( xQueue, &xReceivedStructure, 0 );

        if( xStatus == pdPASS )
        {
            /* Data was successfully received from the queue, print out the received
             * value and the source of the value. */
            if( xReceivedStructure.eDataSource == eSender1 )
            {
                vPrintStringAndNumber( "From Sender 1 = ", xReceivedStructure.ucValue );
            }
            else
            {
                vPrintStringAndNumber( "From Sender 2 = ", xReceivedStructure.ucValue );
            }
        }
        else
        {
            /* We did not receive anything from the queue.  This must be an error
             * as this task should only run when the queue is full. */
            vPrintString( "Could not receive from the queue.\r\n" );
        }
    }
}

DEMO 12 队列集

这段代码展示了 FreeRTOS 中队列集(Queue Set)的核心功能,这是一个高级的同步机制,允许单个任务同时等待多个队列或信号量。

核心功能

  1. 队列集(Queue Set)的创建与管理

xQueueSet = xQueueCreateSet(1 * 2); // 创建队列集
xQueueAddToSet(xQueue1, xQueueSet); // 将队列1加入集合
xQueueAddToSet(xQueue2, xQueueSet); // 将队列2加入集合

• 功能:创建一个可以监控多个队列的集合

• 容量:队列集容量为2(1个项 × 2个队列),表示最多可同时处理2个队列的就绪事件

  1. 多队列同步等待机制

xQueueThatContainsData = (QueueHandle_t)xQueueSelectFromSet(xQueueSet, portMAX_DELAY);

• 核心功能:单个接收任务可以同时等待两个队列

• 阻塞机制:任务阻塞在队列集上,直到任意一个队列中有数据可用

• 返回值:返回包含数据的队列句柄,让接收者知道数据来自哪个队列

  1. 生产者-消费者模式

• 生产者:

• vSenderTask1:每100ms向xQueue1发送消息

• vSenderTask2:每200ms向xQueue2发送消息

• 优先级为1(较低)

• 消费者:

• vReceiverTask:从队列集接收数据,优先级为2(较高)

• 使用xQueueSelectFromSet同时监听两个队列

  1. 优先级驱动的调度

• 接收任务优先级高于发送任务

• 当发送任务写入队列后,立即被高优先级的接收任务抢占

• 确保数据及时处理,队列保持空闲状态

  1. 非阻塞操作优化

xQueueSend(xQueue1, &pcMessage, 0); // 非阻塞发送
xQueueReceive(xQueueThatContainsData, &pcReceivedString, 0); // 非阻塞接收

• 由于优先级安排,队列操作不需要阻塞等待

• 发送者知道接收者会立即处理数据

• 接收者知道数据已经可用

技术特点

  1. 高效的多路复用:单个任务处理多个数据源,减少任务数量
  2. 事件驱动:接收任务只在数据到达时被唤醒,节省CPU资源
  3. 来源识别:通过返回的队列句柄区分数据来源
  4. 资源优化:避免了为每个队列创建独立接收任务的开销

应用场景

这种模式非常适合需要:
• 监控多个数据源的任务

• 处理来自不同设备的异步事件

• 实现高效的I/O多路复用

• 减少系统任务数量的场景

队列集功能使得FreeRTOS能够实现类似select/poll的多路I/O监控机制,在嵌入式系统中非常实用。


/* FreeRTOS.org includes. */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"

/* Demo includes. */
#include "supporting_functions.h"

/* The three sender task. */
void vSenderTask1( void * pvParameters );
void vSenderTask2( void * pvParameters );

/* The receiver task.  The receiver blocks on the queue set to received data
 * from both sender task. */
void vReceiverTask( void * pvParameters );

/*-----------------------------------------------------------*/

/* Declare two variables of type QueueHandle_t.  Both queues are added to the
 * same queue set. */
static QueueHandle_t xQueue1 = NULL, xQueue2 = NULL;

/* Declare a variable of type QueueSetHandle_t.  This is the queue set to which
 * the two queues are added. */
static QueueSetHandle_t xQueueSet = NULL;

int main( void )
{
    /* Create the two queues.  Each queue sends character pointers.  The
     * priority of the receiving task is above the priority of the sending tasks so
     * the queues will never have more than one item in them at any one time. */
    xQueue1 = xQueueCreate( 1, sizeof( char * ) );
    xQueue2 = xQueueCreate( 1, sizeof( char * ) );

    /* Create the queue set.  There are two queues both of which can contain
     * 1 item, so the maximum number of queue handle the queue set will ever have
     * to hold is 2 (1 item multiplied by 2 sets). */
    xQueueSet = xQueueCreateSet( 1 * 2 );

    /* Add the two queues to the set. */
    xQueueAddToSet( xQueue1, xQueueSet );
    xQueueAddToSet( xQueue2, xQueueSet );

    /* Create the tasks that send to the queues. */
    xTaskCreate( vSenderTask1, "Sender1", 1000, NULL, 1, NULL );
    xTaskCreate( vSenderTask2, "Sender2", 1000, NULL, 1, NULL );

    /* Create the receiver task. */
    xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 2, NULL );

    /* Start the scheduler so the created tasks start executing. */
    vTaskStartScheduler();

    /* The following line should never be reached because vTaskStartScheduler()
    *  will only return if there was not enough FreeRTOS heap memory available to
    *  create the Idle and (if configured) Timer tasks.  Heap management, and
    *  techniques for trapping heap exhaustion, are described in the book text. */
    for( ; ; )
    {
    }

    return 0;
}
/*-----------------------------------------------------------*/

static void vSenderTask1( void * pvParameters )
{
    const TickType_t xBlockTime = pdMS_TO_TICKS( 100 );
    const char * const pcMessage = "Message from vSenderTask1\r\n";

    /* As per most tasks, this task is implemented within an infinite loop. */
    for( ; ; )
    {
        /* Block for 100ms. */
        vTaskDelay( xBlockTime );

        /* Send this task's string to xQueue1. It is not necessary to use a
         * block time, even though the queue can only hold one item.  This is
         * because the priority of the task that reads from the queue is higher
         * than the priority of this task; as soon as this task writes to the queue
         * it will be pre-empted by the task that reads from the queue, so the
         * queue will already be empty again by the time the call to xQueueSend()
         * returns.  The block time is set to 0. */
        xQueueSend( xQueue1, &pcMessage, 0 );
    }
}
/*-----------------------------------------------------------*/

static void vSenderTask2( void * pvParameters )
{
    const TickType_t xBlockTime = pdMS_TO_TICKS( 200 );
    const char * const pcMessage = "Message from vSenderTask2\r\n";

    /* As per most tasks, this task is implemented within an infinite loop. */
    for( ; ; )
    {
        /* Block for 200ms. */
        vTaskDelay( xBlockTime );

        /* Send this task's string to xQueue1. It is not necessary to use a
         * block time, even though the queue can only hold one item.  This is
         * because the priority of the task that reads from the queue is higher
         * than the priority of this task; as soon as this task writes to the queue
         * it will be pre-empted by the task that reads from the queue, so the
         * queue will already be empty again by the time the call to xQueueSend()
         * returns.  The block time is set to 0. */
        xQueueSend( xQueue2, &pcMessage, 0 );
    }
}
/*-----------------------------------------------------------*/

static void vReceiverTask( void * pvParameters )
{
    QueueHandle_t xQueueThatContainsData;
    char * pcReceivedString;

    /* As per most tasks, this task is implemented within an infinite loop. */
    for( ; ; )
    {
        /* Block on the queue set to wait for one of the queues in the set to
         * contain data.  Cast the QueueSetMemberHandle_t values returned from
         * xQueueSelectFromSet() to a QueueHandle_t as it is known that all the
         * items in the set are queues (as opposed to semaphores, which can also be
         * members of a queue set). */
        xQueueThatContainsData = ( QueueHandle_t ) xQueueSelectFromSet( xQueueSet, portMAX_DELAY );

        /* An indefinite block time was used when reading from the set so
         * xQueueSelectFromSet() will not have returned unless one of the queues in
         * the set contained data, and xQueueThatContansData must be valid.  Read
         * from the queue.  It is not necessary to specify a block time because it
         * is known that the queue contains data.  The block time is set to 0. */
        xQueueReceive( xQueueThatContainsData, &pcReceivedString, 0 );

        /* Print the string received from the queue. */
        vPrintString( pcReceivedString );
    }
}

DEMO 13 软件定时器

这段代码展示了 FreeRTOS 中软件定时器(Software Timers)的核心功能,演示了两种不同类型的定时器及其回调机制。

核心功能

  1. 软件定时器的创建

xOneShotTimer = xTimerCreate(“OneShot”, mainONE_SHOT_TIMER_PERIOD, pdFALSE, 0, prvOneShotTimerCallback);
xAutoReloadTimer = xTimerCreate(“AutoReload”, mainAUTO_RELOAD_TIMER_PERIOD, pdTRUE, 0, prvAutoReloadTimerCallback);

• 功能:创建两种不同类型的定时器

• 参数说明:

• 名称标识符(调试用)

• 定时周期(3333ms 和 500ms)

• 自动重载标志(pdFALSE=单次,pdTRUE=自动重载)

• ID(可用于区分定时器)

• 回调函数指针

  1. 两种定时器类型

• 单次定时器(One-Shot Timer):

• 周期:3333ms(约3.3秒)

• 执行一次后停止

• 需要手动重新启动才能再次执行

• 自动重载定时器(Auto-Reload Timer):

• 周期:500ms(0.5秒)

• 周期性地自动重启

• 持续执行直到被显式停止

  1. 定时器启动机制

xTimerStart(xOneShotTimer, 0);
xTimerStart(xAutoReloadTimer, 0);

• 功能:启动定时器

• 阻塞时间:0表示非阻塞调用

• 底层机制:通过定时器命令队列与定时器服务任务通信

  1. 回调函数执行

static void prvOneShotTimerCallback(TimerHandle_t xTimer) {
xTimeNow = xTaskGetTickCount();
vPrintStringAndNumber(“One-shot timer callback executing”, xTimeNow);
}

• 功能:定时器到期时自动调用的函数

• 执行上下文:在定时器服务任务中执行

• 时间戳:使用xTaskGetTickCount()获取当前时间

  1. 定时器服务任务

• 隐式创建:首次创建定时器时自动创建

• 优先级:在FreeRTOSConfig.h中配置

• 职责:管理所有定时器的到期检查和回调执行

技术特点

  1. 异步执行模型

• 回调函数在定时器服务任务上下文中执行

• 不影响当前运行的任务

• 适合执行非实时性操作

  1. 时间精度

• 基于系统tick计数,精度受tick频率影响

• 3333ms和500ms周期演示了不同时间尺度的定时

  1. 资源管理

• 共享定时器服务任务,减少系统开销

• 通过命令队列进行线程安全的定时器操作

  1. 应用场景

• 单次定时器:延时操作、超时检测、一次性事件

• 自动重载定时器:周期性任务、心跳检测、数据采样

执行流程

  1. 创建并启动两个定时器
  2. 自动重载定时器每500ms触发一次回调
  3. 单次定时器在3333ms后触发一次回调并停止
  4. 回调函数输出执行时间信息

这个示例展示了 FreeRTOS 软件定时器的完整生命周期管理,包括创建、启动、回调执行等核心功能。


/* FreeRTOS.org includes. */
#include "FreeRTOS.h"
#include "task.h"
#include "timers.h"

/* Demo includes. */
#include "supporting_functions.h"

/* The periods assigned to the one-shot and auto-reload timers respectively. */
#define mainONE_SHOT_TIMER_PERIOD       ( pdMS_TO_TICKS( 3333UL ) )
#define mainAUTO_RELOAD_TIMER_PERIOD    ( pdMS_TO_TICKS( 500UL ) )

/*-----------------------------------------------------------*/

/*
 * The callback functions used by the one-shot and auto-reload timers
 * respectively.
 */
static void prvOneShotTimerCallback( TimerHandle_t xTimer );
static void prvAutoReloadTimerCallback( TimerHandle_t xTimer );

/*-----------------------------------------------------------*/

int main( void )
{
    TimerHandle_t xAutoReloadTimer, xOneShotTimer;
    BaseType_t xTimer1Started, xTimer2Started;

    /* Create the one shot software timer, storing the handle to the created
     * software timer in xOneShotTimer. */
    xOneShotTimer = xTimerCreate( "OneShot",                 /* Text name for the software timer - not used by FreeRTOS. */
                                  mainONE_SHOT_TIMER_PERIOD, /* The software timer's period in ticks. */
                                  pdFALSE,                   /* Setting uxAutoRealod to pdFALSE creates a one-shot software timer. */
                                  0,                         /* This example does not use the timer id. */
                                  prvOneShotTimerCallback ); /* The callback function to be used by the software timer being created. */

    /* Create the auto-reload software timer, storing the handle to the created
     * software timer in xAutoReloadTimer. */
    xAutoReloadTimer = xTimerCreate( "AutoReload",                 /* Text name for the software timer - not used by FreeRTOS. */
                                     mainAUTO_RELOAD_TIMER_PERIOD, /* The software timer's period in ticks. */
                                     pdTRUE,                       /* Set uxAutoRealod to pdTRUE to create an auto-reload software timer. */
                                     0,                            /* This example does not use the timer id. */
                                     prvAutoReloadTimerCallback ); /* The callback function to be used by the software timer being created. */

    /* Check the timers were created. */
    if( ( xOneShotTimer != NULL ) && ( xAutoReloadTimer != NULL ) )
    {
        /* Start the software timers, using a block time of 0 (no block time).
         * The scheduler has not been started yet so any block time specified here
         * would be ignored anyway. */
        xTimer1Started = xTimerStart( xOneShotTimer, 0 );
        xTimer2Started = xTimerStart( xAutoReloadTimer, 0 );

        /* The implementation of xTimerStart() uses the timer command queue, and
         * xTimerStart() will fail if the timer command queue gets full.  The timer
         * service task does not get created until the scheduler is started, so all
         * commands sent to the command queue will stay in the queue until after
         * the scheduler has been started.  Check both calls to xTimerStart()
         * passed. */
        if( ( xTimer1Started == pdPASS ) && ( xTimer2Started == pdPASS ) )
        {
            /* Start the scheduler. */
            vTaskStartScheduler();
        }
    }

    /* If the scheduler was started then the following line should never be
     * reached because vTaskStartScheduler() will only return if there was not
     * enough FreeRTOS heap memory available to create the Idle and (if configured)
     * Timer tasks.  Heap management, and techniques for trapping heap exhaustion,
     * are described in the book text. */
    for( ; ; )
    {
    }

    return 0;
}
/*-----------------------------------------------------------*/

static void prvOneShotTimerCallback( TimerHandle_t xTimer )
{
    static TickType_t xTimeNow;

    /* Obtain the current tick count. */
    xTimeNow = xTaskGetTickCount();

    /* Output a string to show the time at which the callback was executed. */
    vPrintStringAndNumber( "One-shot timer callback executing", xTimeNow );
}
/*-----------------------------------------------------------*/

static void prvAutoReloadTimerCallback( TimerHandle_t xTimer )
{
    static TickType_t xTimeNow;

    /* Obtain the current tick count. */
    xTimeNow = xTaskGetTickCount();

    /* Output a string to show the time at which the callback was executed. */
    vPrintStringAndNumber( "Auto-reload timer callback executing", xTimeNow );
}
/*-----------------------------------------------------------*/

DEMO 14 动态定时器

这段代码展示了 FreeRTOS 中软件定时器(Software Timers)的核心功能,特别强调了定时器标识符(Timer ID)的使用和动态定时器管理。

核心功能

  1. 定时器标识符(Timer ID)的高级用法

ulExecutionCount = (uint32_t)pvTimerGetTimerID(xTimer);
ulExecutionCount++;
vTimerSetTimerID(xTimer, (void *)ulExecutionCount);

• 功能:使用定时器ID作为执行计数器

• 机制:

• 通过pvTimerGetTimerID()获取当前计数值

• 递增计数值

• 通过vTimerSetTimerID()更新ID值

• 优势:避免了使用全局变量,实现了数据封装

  1. 共享回调函数与定时器识别

if(xTimer == xOneShotTimer) {
// 单次定时器处理
} else {
// 自动重载定时器处理
}

• 功能:单个回调函数处理多个定时器

• 实现:通过比较定时器句柄区分不同的定时器源

• 优势:代码复用,减少回调函数数量

  1. 动态定时器控制

if(ulExecutionCount == 5) {
xTimerStop(xTimer, 0); // 执行5次后停止自动重载定时器
}

• 功能:运行时控制定时器行为

• 应用:基于执行次数动态停止定时器

• 场景:实现有限次数的周期性操作

  1. 两种定时器类型的行为差异

• 单次定时器(One-Shot):

• 周期:3333ms

• 执行一次后自动停止

• 需要手动重启才能再次执行

• 自动重载定时器(Auto-Reload):

• 周期:500ms

• 持续周期性执行

• 可以通过xTimerStop()手动停止

  1. 定时器服务任务上下文

// 在定时器回调中执行
xTimerStop(xTimer, 0); // 使用0阻塞时间

• 执行环境:回调函数在定时器服务任务(daemon task)中运行

• 限制:不能调用可能导致服务任务阻塞的函数

• 要求:必须使用非阻塞操作(阻塞时间为0)

技术特点

  1. 数据封装模式

• 使用Timer ID存储定时器特定数据

• 避免全局变量污染

• 实现更好的模块化和封装性

  1. 条件性定时器管理

• 基于执行次数动态调整定时器行为

• 演示了运行时定时器状态控制

  1. 执行统计功能

• 自动记录每个定时器的执行次数

• 为调试和监控提供有价值的信息

  1. 资源优化

• 共享回调函数减少代码体积

• 使用Timer ID替代多个全局变量

执行流程

  1. 两个定时器同时启动(单次3333ms,自动重载500ms)
  2. 自动重载定时器每500ms触发一次回调,计数递增
  3. 当自动重载定时器执行5次后自动停止
  4. 单次定时器在3333ms后触发一次回调并自动停止

这个示例展示了 FreeRTOS 软件定时器的高级用法,特别是如何利用 Timer ID 进行状态管理和数据存储,以及如何在运行时动态控制定时器行为。


/* FreeRTOS.org includes. */
#include "FreeRTOS.h"
#include "task.h"
#include "timers.h"

/* Demo includes. */
#include "supporting_functions.h"

/* The periods assigned to the one-shot and auto-reload timers respectively. */
#define mainONE_SHOT_TIMER_PERIOD       ( pdMS_TO_TICKS( 3333UL ) )
#define mainAUTO_RELOAD_TIMER_PERIOD    ( pdMS_TO_TICKS( 500UL ) )

/*-----------------------------------------------------------*/

/*
 * The callback function that is used by both timers.
 */
static void prvTimerCallback( TimerHandle_t xTimer );

/*-----------------------------------------------------------*/

/* The timer handles are used inside the callback function so this time are
 * given file scope. */
static TimerHandle_t xAutoReloadTimer, xOneShotTimer;

int main( void )
{
    BaseType_t xTimer1Started, xTimer2Started;

    /* Create the one shot timer, storing the handle to the created timer in
     * xOneShotTimer. */
    xOneShotTimer = xTimerCreate( "OneShot",                 /* Text name for the timer - not used by FreeRTOS. */
                                  mainONE_SHOT_TIMER_PERIOD, /* The timer's period in ticks. */
                                  pdFALSE,                   /* Set uxAutoRealod to pdFALSE to create a one-shot timer. */
                                  0,                         /* The timer ID is initialised to 0. */
                                  prvTimerCallback );        /* The callback function to be used by the timer being created. */

    /* Create the auto-reload, storing the handle to the created timer in
     * xAutoReloadTimer. */
    xAutoReloadTimer = xTimerCreate( "AutoReload",                 /* Text name for the timer - not used by FreeRTOS. */
                                     mainAUTO_RELOAD_TIMER_PERIOD, /* The timer's period in ticks. */
                                     pdTRUE,                       /* Set uxAutoRealod to pdTRUE to create an auto-reload timer. */
                                     0,                            /* The timer ID is initialised to 0. */
                                     prvTimerCallback );           /* The callback function to be used by the timer being created. */

    /* Check the timers were created. */
    if( ( xOneShotTimer != NULL ) && ( xAutoReloadTimer != NULL ) )
    {
        /* Start the timers, using a block time of 0 (no block time).  The
         * scheduler has not been started yet so any block time specified here
         * would be ignored anyway. */
        xTimer1Started = xTimerStart( xOneShotTimer, 0 );
        xTimer2Started = xTimerStart( xAutoReloadTimer, 0 );

        /* The implementation of xTimerStart() uses the timer command queue, and
         * xTimerStart() will fail if the timer command queue gets full.  The timer
         * service task does not get created until the scheduler is started, so all
         * commands sent to the command queue will stay in the queue until after
         * the scheduler has been started.  Check both calls to xTimerStart()
         * passed. */
        if( ( xTimer1Started == pdPASS ) && ( xTimer2Started == pdPASS ) )
        {
            /* Start the scheduler. */
            vTaskStartScheduler();
        }
    }

    /* If the scheduler was started then the following line should never be
     * reached because vTaskStartScheduler() will only return if there was not
     * enough FreeRTOS heap memory available to create the Idle and (if configured)
     * Timer tasks.  Heap management, and techniques for trapping heap exhaustion,
     * are described in the book text. */
    for( ; ; )
    {
    }

    return 0;
}
/*-----------------------------------------------------------*/

static void prvTimerCallback( TimerHandle_t xTimer )
{
    TickType_t xTimeNow;
    uint32_t ulExecutionCount;

    /* The count of the number of times this software timer has expired is
     * stored in the timer's ID.  Obtain the ID, increment it, then save it as the
     * new ID value.  The ID is a void pointer, so is cast to a uint32_t. */
    ulExecutionCount = ( uint32_t ) pvTimerGetTimerID( xTimer );
    ulExecutionCount++;
    vTimerSetTimerID( xTimer, ( void * ) ulExecutionCount );

    /* Obtain the current tick count. */
    xTimeNow = xTaskGetTickCount();

    /* The handle of the one-shot timer was stored in xOneShotTimer when the
     * timer was created.  Compare the handle passed into this function with
     * xOneShotTimer to determine if it was the one-shot or auto-reload timer that
     * expired, then output a string to show the time at which the callback was
     * executed. */
    if( xTimer == xOneShotTimer )
    {
        vPrintStringAndNumber( "One-shot timer callback executing", xTimeNow );
    }
    else
    {
        /* xTimer did not equal xOneShotTimer, so it must be the auto-reload
         * timer that expired. */
        vPrintStringAndNumber( "Auto-reload timer callback executing", xTimeNow );

        if( ulExecutionCount == 5 )
        {
            /* Stop the auto-reload timer after it has executed 5 times.  This
             * callback function executes in the context of the RTOS daemon task so
             * must not call any functions that might place the daemon task into
             * the Blocked state.  Therefore a block time of 0 is used. */
            xTimerStop( xTimer, 0 );
        }
    }
}
/*-----------------------------------------------------------*/

DEMO 15 超时触发

这段代码展示了 FreeRTOS 中软件定时器(Software Timers)的一个典型应用场景:实现背光超时关闭功能。这是一个非常实用的嵌入式系统功能。

核心功能

  1. 背光超时管理机制

xBacklightTimer = xTimerCreate(“Backlight”, mainBACKLIGHT_TIMER_PERIOD, pdFALSE, 0, prvBacklightTimerCallback);

• 功能:创建单次定时器(5秒超时)

• 用途:监控用户无操作时间,超时后自动关闭背光

• 类型:单次定时器(pdFALSE),超时后需要手动重启

  1. 按键事件处理与定时器重置

xTimerReset(xBacklightTimer, xShortDelay);

• 功能:每次按键时重置定时器

• 效果:重新开始5秒倒计时

• 应用:用户有操作时保持背光开启

  1. 状态管理

static BaseType_t xSimulatedBacklightOn = pdFALSE;

• 功能:模拟背光状态(开/关)

• 实际应用:在真实硬件中会控制GPIO或PWM输出

  1. 键盘轮询任务

static void vKeyHitTask(void *pvParameters)

• 功能:模拟中断驱动的按键检测

• 实现:使用_kbhit()和_getch()轮询键盘

• 延迟:50ms轮询间隔,避免CPU过载

工作流程

正常操作流程:

  1. 初始状态:背光关闭,定时器运行中
  2. 用户按键:背光开启,定时器重置
  3. 持续操作:每次按键重置5秒定时器
  4. 无操作超时:5秒后定时器回调关闭背光

具体行为:

• 背光关闭时按键:开启背光 + 启动定时器

• 背光开启时按键:重置定时器(保持背光开启)

• 定时器到期:自动关闭背光

技术特点

  1. 单次定时器应用

• 适合超时检测场景

• 需要外部事件(按键)来重新启动

• 节省系统资源

  1. 状态机设计

• 清晰的背光状态管理

• 条件逻辑处理不同状态下的按键事件

  1. 实际硬件适配

• 代码注释说明了真实硬件中的实现方式

• 中断处理(xTimerResetFromISR)

• 硬件控制(GPIO/PWM)

  1. 资源优化

• 低优先级轮询任务(tskIDLE_PRIORITY)

• 适当的轮询频率(50ms)

应用场景

这个模式广泛应用于:
• 嵌入式设备:手机、平板、工控设备的背光管理

• 家电控制:微波炉、洗衣机的面板背光控制

• 工业设备:HMI人机界面的屏幕保护

• 汽车电子:车载娱乐系统的背光管理

这是一个典型的用户交互超时管理实现,展示了软件定时器在现实应用中的实用价值。


/* Standard includes. */
#include <conio.h>

/* FreeRTOS.org includes. */
#include "FreeRTOS.h"
#include "task.h"
#include "timers.h"

/* Demo includes. */
#include "supporting_functions.h"

/* The periods assigned to the one-shot timer. */
#define mainBACKLIGHT_TIMER_PERIOD    ( pdMS_TO_TICKS( 5000UL ) )

/*-----------------------------------------------------------*/

/*
 * The callback function used by the timer.
 */
static void prvBacklightTimerCallback( TimerHandle_t xTimer );

/*
 * A real application, running on a real target, would probably read button
 * pushes in an interrupt.  That allows the application to be event driven, and
 * prevents CPU time being wasted by polling for key presses when no keys have
 * been pressed.  It is not practical to use real interrupts when using the
 * FreeRTOS Windows port, so the vKeyHitTask() task is created to provide the
 * key reading functionality by simply polling the keyboard.
 */
static void vKeyHitTask( void * pvParameters );

/*-----------------------------------------------------------*/

/* This example does not have a real backlight to turn on and off, so the
 * following variable is used to just hold the state of the backlight. */
static BaseType_t xSimulatedBacklightOn = pdFALSE;

/* The software timer used to turn the backlight off. */
static TimerHandle_t xBacklightTimer = NULL;

/*-----------------------------------------------------------*/

int main( void )
{
    /* The backlight is off at the start. */
    xSimulatedBacklightOn = pdFALSE;

    /* Create the one shot timer, storing the handle to the created timer in
     * xOneShotTimer. */
    xBacklightTimer = xTimerCreate( "Backlight",                 /* Text name for the timer - not used by FreeRTOS. */
                                    mainBACKLIGHT_TIMER_PERIOD,  /* The timer's period in ticks. */
                                    pdFALSE,                     /* Set uxAutoRealod to pdFALSE to create a one-shot timer. */
                                    0,                           /* The timer ID is not used in this example. */
                                    prvBacklightTimerCallback ); /* The callback function to be used by the timer being created. */

    /* A real application, running on a real target, would probably read button
     * pushes in an interrupt.  That allows the application to be event driven, and
     * prevents CPU time being wasted by polling for key presses when no keys have
     * been pressed.  It is not practical to use real interrupts when using the
     * FreeRTOS Windows port, so the vKeyHitTask() task is created to instead
     * provide the	key reading functionality by simply polling the keyboard. */
    xTaskCreate( vKeyHitTask, "Key poll", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY, NULL );

    /* Start the timer. */
    xTimerStart( xBacklightTimer, 0 );

    /* Start the scheduler. */
    vTaskStartScheduler();

    /* As in previous examples, vTaskStartScheduler() shoudl not return so the
     * following lines should not be reached. */
    for( ; ; )
    {
    }

    return 0;
}
/*-----------------------------------------------------------*/

static void prvBacklightTimerCallback( TimerHandle_t xTimer )
{
    TickType_t xTimeNow = xTaskGetTickCount();

    /* The backlight timer expired, turn the backlight off. */
    xSimulatedBacklightOn = pdFALSE;

    /* Print the time at which the backlight was turned off. */
    vPrintStringAndNumber( "Timer expired, turning backlight OFF at time\t", xTimeNow );
}
/*-----------------------------------------------------------*/

static void vKeyHitTask( void * pvParameters )
{
    const TickType_t xShortDelay = pdMS_TO_TICKS( 50 );
    extern BaseType_t xKeyPressesStopApplication;
    TickType_t xTimeNow;

    /* This example uses key presses, so prevent key presses being used to end
     * the application. */
    xKeyPressesStopApplication = pdFALSE;

    vPrintString( "Press a key to turn the backlight on.\r\n" );

    /* A real application, running on a real target, would probably read button
     * pushes in an interrupt.  That allows the application to be event driven, and
     * prevents CPU time being wasted by polling for key presses when no keys have
     * been pressed.  It is not practical to use real interrupts when using the
     * FreeRTOS Windows port, so this task is created to instead provide the key
     * reading functionality by simply polling the keyboard. */
    for( ; ; )
    {
        /* Has a key been pressed? */
        if( _kbhit() != 0 )
        {
            /* Record the time at which the key press was noted. */
            xTimeNow = xTaskGetTickCount();

            /* A key has been pressed. */
            if( xSimulatedBacklightOn == pdFALSE )
            {
                /* The backlight was off so turn it on and print the time at
                 * which it was turned on. */
                xSimulatedBacklightOn = pdTRUE;
                vPrintStringAndNumber( "Key pressed, turning backlight ON at time\t", xTimeNow );
            }
            else
            {
                /* The backlight was already on so print a message to say the
                 * backlight is about to be reset and the time at which it was
                 * reset. */
                vPrintStringAndNumber( "Key pressed, resetting software timer at time\t", xTimeNow );
            }

            /* Reset the software timer.  If the backlight was previously off
             * this call will start the timer.  If the backlight was previously on
             * this call will restart the timer.  A real application will probably
             * read key presses in an interrupt.  If this function was an interrupt
             * service routine then xTimerResetFromISR() must be used instead of
             * xTimerReset(). */
            xTimerReset( xBacklightTimer, xShortDelay );

            /* Read and discard the key that was pressed. */
            ( void ) _getch();
        }

        /* Don't poll too quickly. */
        vTaskDelay( xShortDelay );
    }
}
/*-----------------------------------------------------------*/

DEMO 16 中断-任务同步

这段代码展示了 FreeRTOS 中中断服务程序(ISR)与任务间同步的核心功能,特别是使用二进制信号量(Binary Semaphore)来实现中断与任务之间的通信。

核心功能

  1. 中断与任务的同步机制

xBinarySemaphore = xSemaphoreCreateBinary();

• 功能:创建二进制信号量用于中断与任务间的同步

• 作用:作为事件标志,通知任务中断已发生

  1. 中断服务程序(ISR)的实现

static uint32_t ulExampleInterruptHandler(void)
{
xSemaphoreGiveFromISR(xBinarySemaphore, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}

• 关键API:xSemaphoreGiveFromISR() - 在ISR中释放信号量

• 上下文切换:portYIELD_FROM_ISR() - 必要时触发任务切换

• ISR最佳实践:保持中断处理时间最短

  1. 任务等待中断事件

xSemaphoreTake(xBinarySemaphore, portMAX_DELAY);

• 功能:任务阻塞等待信号量,直到中断发生

• 阻塞方式:无限期等待(portMAX_DELAY)

• 效率:任务在等待时不消耗CPU时间

  1. 中断模拟机制

vPortGenerateSimulatedInterrupt(mainINTERRUPT_NUMBER);

• 用途:在Windows模拟环境中生成软件中断

• 周期性:每500ms产生一次中断

• 实际应用:在真实硬件中由外部事件触发

工作流程

执行顺序:

  1. 周期性任务:每500ms模拟产生一次中断
  2. 中断处理程序:立即释放二进制信号量
  3. 处理任务:从阻塞状态唤醒,处理中断事件
  4. 循环重复:处理完成后继续等待下一个中断

优先级安排:

• 处理任务:优先级3(高)- 确保及时处理中断

• 周期性任务:优先级1(低)- 仅负责触发中断

• 中断服务程序:最高优先级(硬件级别)

技术特点

  1. 中断延迟处理模式

• ISR快速响应:仅进行必要的硬件操作和信号量释放

• 任务处理复杂逻辑:在实际任务中完成耗时处理

• 符合RTOS最佳实践:保持ISR执行时间最短

  1. 线程安全通信

• 信号量操作是原子性的

• 无需额外的保护机制

• 确保数据一致性

  1. 资源效率

• 任务在等待时不消耗CPU

• 中断处理时间极短

• 系统响应性高

实际应用场景

这种模式广泛应用于:
• 外设中断处理:串口数据接收、GPIO中断

• 定时器中断:周期性数据采集

• 通信接口:SPI、I2C传输完成中断

• 传感器中断:数据就绪通知

这是一个典型的中断处理架构,展示了如何在 FreeRTOS 中正确实现中断服务程序与任务间的协同工作。


/* FreeRTOS.org includes. */
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"

/* Demo includes. */
#include "supporting_functions.h"

/* The number of the simulated interrupt used in this example.  Numbers 0 to 2
 * are used by the FreeRTOS Windows port itself, so 3 is the first number available
 * to the application. */
#define mainINTERRUPT_NUMBER    3

/* The tasks to be created. */
static void vHandlerTask( void * pvParameters );
static void vPeriodicTask( void * pvParameters );

/* The service routine for the (simulated) interrupt.  This is the interrupt
 * that the task will be synchronized with. */
static uint32_t ulExampleInterruptHandler( void );

/*-----------------------------------------------------------*/

/* Declare a variable of type SemaphoreHandle_t.  This is used to reference the
 * semaphore that is used to synchronize a task with an interrupt. */
SemaphoreHandle_t xBinarySemaphore;

int main( void )
{
    /* Before a semaphore is used it must be explicitly created.  In this
     * example	a binary semaphore is created. */
    xBinarySemaphore = xSemaphoreCreateBinary();

    /* Check the semaphore was created successfully. */
    if( xBinarySemaphore != NULL )
    {
        /* Create the 'handler' task, which is the task to which interrupt
         * processing is deferred, and so is the task that will be synchronized
         * with the interrupt.  The handler task is created with a high priority to
         * ensure it runs immediately after the interrupt exits.  In this case a
         * priority of 3 is chosen. */
        xTaskCreate( vHandlerTask, "Handler", 1000, NULL, 3, NULL );

        /* Create the task that will periodically generate a software interrupt.
         * This is created with a priority below the handler task to ensure it will
         * get preempted each time the handler task exits the Blocked state. */
        xTaskCreate( vPeriodicTask, "Periodic", 1000, NULL, 1, NULL );

        /* Install the handler for the software interrupt.  The syntax necessary
         * to do this is dependent on the FreeRTOS port being used.  The syntax
         * shown here can only be used with the FreeRTOS Windows port, where such
         * interrupts are only simulated. */
        vPortSetInterruptHandler( mainINTERRUPT_NUMBER, ulExampleInterruptHandler );

        /* Start the scheduler so the created tasks start executing. */
        vTaskStartScheduler();
    }

    /* The following line should never be reached because vTaskStartScheduler()
    *  will only return if there was not enough FreeRTOS heap memory available to
    *  create the Idle and (if configured) Timer tasks.  Heap management, and
    *  techniques for trapping heap exhaustion, are described in the book text. */
    for( ; ; )
    {
    }

    return 0;
}
/*-----------------------------------------------------------*/

static void vHandlerTask( void * pvParameters )
{
    /* As per most tasks, this task is implemented within an infinite loop. */
    for( ; ; )
    {
        /* Use the semaphore to wait for the event.  The semaphore was created
         * before the scheduler was started so before this task ran for the first
         * time.  The task blocks indefinitely meaning this function call will only
         * return once the semaphore has been successfully obtained - so there is
         * no need to check the returned value. */
        xSemaphoreTake( xBinarySemaphore, portMAX_DELAY );

        /* To get here the event must have occurred.  Process the event (in this
         * case just print out a message). */
        vPrintString( "Handler task - Processing event.\r\n" );
    }
}
/*-----------------------------------------------------------*/

static void vPeriodicTask( void * pvParameters )
{
    const TickType_t xDelay500ms = pdMS_TO_TICKS( 500UL );

    /* As per most tasks, this task is implemented within an infinite loop. */
    for( ; ; )
    {
        /* This task is just used to 'simulate' an interrupt.  This is done by
         * periodically generating a simulated software interrupt.  Block until it
         * is time to generate the software interrupt again. */
        vTaskDelay( xDelay500ms );

        /* Generate the interrupt, printing a message both before and after
         * the interrupt has been generated so the sequence of execution is evident
         * from the output.
         *
         * The syntax used to generate a software interrupt is dependent on the
         * FreeRTOS port being used.  The syntax used below can only be used with
         * the FreeRTOS Windows port, in which such interrupts are only
         * simulated. */
        vPrintString( "Periodic task - About to generate an interrupt.\r\n" );
        vPortGenerateSimulatedInterrupt( mainINTERRUPT_NUMBER );
        vPrintString( "Periodic task - Interrupt generated.\r\n\r\n\r\n" );
    }
}
/*-----------------------------------------------------------*/

static uint32_t ulExampleInterruptHandler( void )
{
    BaseType_t xHigherPriorityTaskWoken;

    /* The xHigherPriorityTaskWoken parameter must be initialized to pdFALSE as
     * it will get set to pdTRUE inside the interrupt safe API function if a
     * context switch is required. */
    xHigherPriorityTaskWoken = pdFALSE;

    /* 'Give' the semaphore to unblock the task. */
    xSemaphoreGiveFromISR( xBinarySemaphore, &xHigherPriorityTaskWoken );

    /* Pass the xHigherPriorityTaskWoken value into portYIELD_FROM_ISR().  If
     * xHigherPriorityTaskWoken was set to pdTRUE inside xSemaphoreGiveFromISR()
     * then calling portYIELD_FROM_ISR() will request a context switch.  If
     * xHigherPriorityTaskWoken is still pdFALSE then calling
     * portYIELD_FROM_ISR() will have no effect.  The implementation of
     * portYIELD_FROM_ISR() used by the Windows port includes a return statement,
     * which is why this function does not explicitly return a value. */
    portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}

DEMO 17

这段代码展示了FreeRTOS实时操作系统中中断与任务同步的核心功能实现。以下是其核心功能的详细说明:

核心功能概述

代码演示了如何使用计数信号量(Counting Semaphore)实现中断服务程序(ISR)与任务之间的同步机制,这是一种典型的生产者-消费者模式。

主要组件功能

  1. 计数信号量 (xCountingSemaphore)

• 类型: SemaphoreHandle_t

• 配置: 最大计数值10,初始值0

• 作用: 作为中断事件与任务之间的同步桥梁

  1. 中断处理任务 (vHandlerTask)

• 优先级: 3(较高优先级)

• 功能: 等待信号量并处理中断事件

• 行为: 无限循环中阻塞等待信号量,获取后执行事件处理

  1. 周期性任务 (vPeriodicTask)

• 优先级: 1(较低优先级)

• 功能: 模拟中断发生

• 行为: 每500ms生成一次模拟中断

  1. 中断服务程序 (ulExampleInterruptHandler)

• 功能: 实际的中断处理逻辑

• 特性: 使用中断安全API (xSemaphoreGiveFromISR)

• 行为: 每次中断产生时释放3次信号量

工作流程

  1. 初始化: 创建计数信号量(初始值为0)
  2. 任务创建: 创建高优先级的中断处理任务和低优先级的周期性任务
  3. 中断安装: 注册中断处理程序
  4. 周期性中断: 每500ms周期性任务触发模拟中断
  5. 中断处理: 中断处理程序释放3个信号量
  6. 任务响应: 中断处理任务每次获取1个信号量并处理事件

关键技术特点

• 中断延迟处理: 将耗时中断处理推迟到任务中执行

• 优先级保障: 高优先级任务确保及时响应中断

• 事件计数: 计数信号量确保多次中断事件不会丢失

• 线程安全: 使用中断安全API进行任务间通信

实际应用场景

这种模式适用于:
• 处理硬件中断事件

• 实现事件驱动的系统架构

• 需要保证中断响应及时性的实时系统

• 处理突发性高频事件的场景

代码展示了FreeRTOS中典型的中断-任务同步机制,是实时系统中处理外部事件的经典模式。


/* FreeRTOS.org includes. */
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"

/* Demo includes. */
#include "supporting_functions.h"


/* The number of the simulated interrupt used in this example.  Numbers 0 to 2
 * are used by the FreeRTOS Windows port itself, so 3 is the first number available
 * to the application. */
#define mainINTERRUPT_NUMBER    3

/* The tasks to be created. */
static void vHandlerTask( void * pvParameters );
static void vPeriodicTask( void * pvParameters );

/* The service routine for the (simulated) interrupt.  This is the interrupt
 * that the task will be synchronized with. */
static uint32_t ulExampleInterruptHandler( void );

/*-----------------------------------------------------------*/

/* Declare a variable of type SemaphoreHandle_t.  This is used to reference the
 * semaphore that is used to synchronize a task with an interrupt. */
SemaphoreHandle_t xCountingSemaphore;


int main( void )
{
    /* Before a semaphore is used it must be explicitly created.  In this
     * example a counting semaphore is created.  The semaphore is created to have a
     * maximum count value of 10, and an initial count value of 0. */
    xCountingSemaphore = xSemaphoreCreateCounting( 10, 0 );

    /* Check the semaphore was created successfully. */
    if( xCountingSemaphore != NULL )
    {
        /* Create the 'handler' task, which is the task to which interrupt
         * processing is deferred, and so is the task that will be synchronized
         * with the interrupt.  The handler task is created with a high priority to
         * ensure it runs immediately after the interrupt exits.  In this case a
         * priority of 3 is chosen. */
        xTaskCreate( vHandlerTask, "Handler", 1000, NULL, 3, NULL );

        /* Create the task that will periodically generate a software interrupt.
         * This is created with a priority below the handler task to ensure it will
         * get preempted each time the handler task exits the Blocked state. */
        xTaskCreate( vPeriodicTask, "Periodic", 1000, NULL, 1, NULL );

        /* Install the handler for the software interrupt.  The syntax necessary
         * to do this is dependent on the FreeRTOS port being used.  The syntax
         * shown here can only be used with the FreeRTOS Windows port, where such
         * interrupts are only simulated. */
        vPortSetInterruptHandler( mainINTERRUPT_NUMBER, ulExampleInterruptHandler );

        /* Start the scheduler so the created tasks start executing. */
        vTaskStartScheduler();
    }

    /* The following line should never be reached because vTaskStartScheduler()
    *  will only return if there was not enough FreeRTOS heap memory available to
    *  create the Idle and (if configured) Timer tasks.  Heap management, and
    *  techniques for trapping heap exhaustion, are described in the book text. */
    for( ; ; )
    {
    }

    return 0;
}
/*-----------------------------------------------------------*/

static void vHandlerTask( void * pvParameters )
{
    /* As per most tasks, this task is implemented within an infinite loop. */
    for( ; ; )
    {
        /* Use the semaphore to wait for the event.  The semaphore was created
         * before the scheduler was started so before this task ran for the first
         * time.  The task blocks indefinitely meaning this function call will only
         * return once the semaphore has been successfully obtained - so there is
         * no need to check the returned value. */
        xSemaphoreTake( xCountingSemaphore, portMAX_DELAY );

        /* To get here the event must have occurred.  Process the event (in this
         * case just print out a message). */
        vPrintString( "Handler task - Processing event.\r\n" );
    }
}
/*-----------------------------------------------------------*/

static void vPeriodicTask( void * pvParameters )
{
    const TickType_t xDelay500ms = pdMS_TO_TICKS( 500UL );

    /* As per most tasks, this task is implemented within an infinite loop. */
    for( ; ; )
    {
        /* This task is just used to 'simulate' an interrupt.  This is done by
         * periodically generating a simulated software interrupt.  Block until it
         * is time to generate the software interrupt again. */
        vTaskDelay( xDelay500ms );

        /* Generate the interrupt, printing a message both before and after
         * the interrupt has been generated so the sequence of execution is evident
         * from the output.
         *
         * The syntax used to generate a software interrupt is dependent on the
         * FreeRTOS port being used.  The syntax used below can only be used with
         * the FreeRTOS Windows port, in which such interrupts are only
         * simulated. */
        vPrintString( "Periodic task - About to generate an interrupt.\r\n" );
        vPortGenerateSimulatedInterrupt( mainINTERRUPT_NUMBER );
        vPrintString( "Periodic task - Interrupt generated.\r\n\r\n\r\n" );
    }
}
/*-----------------------------------------------------------*/

static uint32_t ulExampleInterruptHandler( void )
{
    BaseType_t xHigherPriorityTaskWoken;

    /* The xHigherPriorityTaskWoken parameter must be initialized to pdFALSE as
     * it will get set to pdTRUE inside the interrupt safe API function if a
     * context switch is required. */
    xHigherPriorityTaskWoken = pdFALSE;

    /* 'Give' the semaphore multiple times.  The first will unblock the deferred
     * interrupt handling task, the following 'gives' are to demonstrate that the
     * semaphore latches the events to allow the handler task to process them in
     * turn without events getting lost.  This simulates multiple interrupts being
     * processed by the processor, even though in this case the events are
     * simulated within a single interrupt occurrence.*/
    xSemaphoreGiveFromISR( xCountingSemaphore, &xHigherPriorityTaskWoken );
    xSemaphoreGiveFromISR( xCountingSemaphore, &xHigherPriorityTaskWoken );
    xSemaphoreGiveFromISR( xCountingSemaphore, &xHigherPriorityTaskWoken );

    /* Pass the xHigherPriorityTaskWoken value into portYIELD_FROM_ISR().  If
     * xHigherPriorityTaskWoken was set to pdTRUE inside xSemaphoreGiveFromISR()
     * then calling portYIELD_FROM_ISR() will request a context switch.  If
     * xHigherPriorityTaskWoken is still pdFALSE then calling
     * portYIELD_FROM_ISR() will have no effect.  The implementation of
     * portYIELD_FROM_ISR() used by the Windows port includes a return statement,
     * which is why this function does not explicitly return a value. */
    portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}

DEMO 18

这段代码展示了FreeRTOS实时操作系统中一个核心功能:中断延迟处理(Deferred Interrupt Processing)。

核心功能

实现了中断服务程序(ISR)与任务之间的安全通信和同步,通过将中断处理分为两部分:

  1. 即时处理部分(在ISR中执行)
    • 快速响应硬件中断

    • 执行最小必要的处理

    • 调度延迟处理函数

  2. 延迟处理部分(在守护任务中执行)
    • 执行耗时或复杂的处理

    • 可以使用FreeRTOS API

    • 避免在ISR中执行阻塞操作

关键组件

  1. 中断处理机制

static uint32_t ulExampleInterruptHandler(void)
{
// ISR中快速调度延迟处理
xTimerPendFunctionCallFromISR(vDeferredHandlingFunction, NULL, ulParameterValue, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}

  1. 延迟处理函数

static void vDeferredHandlingFunction(void *pvParameter1, uint32_t ulParameter2)
{
// 在守护任务上下文中安全执行复杂处理
vPrintStringAndNumber("Handler function - Processing event ", ulParameter2);
}

  1. 中断模拟

// 周期性任务模拟硬件中断
vPortGenerateSimulatedInterrupt(mainINTERRUPT_NUMBER);

技术特点

  1. 优先级控制:周期性任务优先级低于守护任务,确保及时处理
  2. 线程安全:通过守护任务避免在ISR中使用非线程安全API
  3. 资源管理:最小化ISR执行时间,提高系统响应性
  4. 参数传递:通过ulParameter2传递中断计数信息

实际应用场景

这种模式适用于:
• 网络数据包处理

• 传感器数据采集

• 外设通信(UART、SPI、I2C)

• 任何需要在中段上下文外进行复杂处理的场景

这是FreeRTOS中处理硬件中断的标准且重要的模式,确保了实时系统的稳定性和响应性。


/* FreeRTOS.org includes. */
#include "FreeRTOS.h"
#include "task.h"
#include "timers.h"

/* Demo includes. */
#include "supporting_functions.h"


/* The number of the simulated interrupt used in this example.  Numbers 0 to 2
 * are used by the FreeRTOS Windows port itself, so 3 is the first number available
 * to the application. */
#define mainINTERRUPT_NUMBER    3

/* The task to be created. */
static void vPeriodicTask( void * pvParameters );

/* The function that performs the deferred interrupt processing.  This function
 * is executed in the context of the daemon task. */
static void vDeferredHandlingFunction( void * pvParameter1,
                                       uint32_t ulParameter2 );

/* The service routine for the (simulated) interrupt.  This is the interrupt
 * that the task will be synchronized with. */
static uint32_t ulExampleInterruptHandler( void );

/*-----------------------------------------------------------*/

int main( void )
{
/* The task that generates the software interrupt is created at a priority
 * below the priority of the daemon task.  The priority of the daemon task is
 * set by the configTIMER_TASK_PRIORITY compile time configuration constant in
 * FreeRTOSConfig.h. */
    const UBaseType_t ulPeriodicTaskPriority = configTIMER_TASK_PRIORITY - 1;

    /* Create the task that will periodically generate a software interrupt. */
    xTaskCreate( vPeriodicTask, "Periodic", 1000, NULL, ulPeriodicTaskPriority, NULL );

    /* Install the handler for the software interrupt.  The syntax necessary
     * to do this is dependent on the FreeRTOS port being used.  The syntax
     * shown here can only be used with the FreeRTOS Windows port, where such
     * interrupts are only simulated. */
    vPortSetInterruptHandler( mainINTERRUPT_NUMBER, ulExampleInterruptHandler );

    /* Start the scheduler so the created task starts executing. */
    vTaskStartScheduler();

    /* The following line should never be reached because vTaskStartScheduler()
    *  will only return if there was not enough FreeRTOS heap memory available to
    *  create the Idle and (if configured) Timer tasks.  Heap management, and
    *  techniques for trapping heap exhaustion, are described in the book text. */
    for( ; ; )
    {
    }

    return 0;
}
/*-----------------------------------------------------------*/

static void vPeriodicTask( void * pvParameters )
{
    const TickType_t xDelay500ms = pdMS_TO_TICKS( 500UL );

    /* As per most tasks, this task is implemented within an infinite loop. */
    for( ; ; )
    {
        /* This task is just used to 'simulate' an interrupt.  This is done by
         * periodically generating a simulated software interrupt.  Block until it
         * is time to generate the software interrupt again. */
        vTaskDelay( xDelay500ms );

        /* Generate the interrupt, printing a message both before and after
         * the interrupt has been generated so the sequence of execution is evident
         * from the output.
         *
         * The syntax used to generate a software interrupt is dependent on the
         * FreeRTOS port being used.  The syntax used below can only be used with
         * the FreeRTOS Windows port, in which such interrupts are only
         * simulated. */
        vPrintString( "Periodic task - About to generate an interrupt.\r\n" );
        vPortGenerateSimulatedInterrupt( mainINTERRUPT_NUMBER );
        vPrintString( "Periodic task - Interrupt generated.\r\n\r\n\r\n" );
    }
}
/*-----------------------------------------------------------*/

static uint32_t ulExampleInterruptHandler( void )
{
    static uint32_t ulParameterValue = 0;
    BaseType_t xHigherPriorityTaskWoken;

    /* The xHigherPriorityTaskWoken parameter must be initialized to pdFALSE as
     * it will get set to pdTRUE inside the interrupt safe API function if a
     * context switch is required. */
    xHigherPriorityTaskWoken = pdFALSE;

    /* Send a pointer to the interrupt's deferred handling function to the
     * daemon task.  The deferred handling function's pvParameter1 parameter is not
     * used so just set to NULL.  The deferred handling function's ulParameter2
     * parameter is used to pass a number that is incremented by one each time this
     * interrupt occurs. */
    xTimerPendFunctionCallFromISR( vDeferredHandlingFunction, NULL, ulParameterValue, &xHigherPriorityTaskWoken );
    ulParameterValue++;

    /* Pass the xHigherPriorityTaskWoken value into portYIELD_FROM_ISR().  If
     * xHigherPriorityTaskWoken was set to pdTRUE inside
     * xTimerPendFunctionCallFromISR()	then calling portYIELD_FROM_ISR() will
     * request a context switch.  If xHigherPriorityTaskWoken is still pdFALSE then
     * calling	portYIELD_FROM_ISR() will have no effect.  The implementation of
     * portYIELD_FROM_ISR() used by the Windows port includes a return statement,
     * which is why this function does not explicitly return a value. */
    portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
/*-----------------------------------------------------------*/

static void vDeferredHandlingFunction( void * pvParameter1,
                                       uint32_t ulParameter2 )
{
    /* Remove the compiler warning indicating that pvParameter1 is not used, as
     * pvParameter1 is not used in this example. */
    ( void ) pvParameter1;

    /* Process the event - in this case just print out a message and the value
     * of ulParameter2. */
    vPrintStringAndNumber( "Handler function - Processing event ", ulParameter2 );
}

DEMO 19

这个 FreeRTOS 示例程序的核心功能是演示如何在中断服务程序(ISR)和任务之间使用队列进行通信,具体实现了以下功能:

核心功能概述

  1. 双队列通信机制:
    • xIntegerQueue:用于从任务向中断传递整数数据

    • xStringQueue:用于从中断向任务传递字符串数据

  2. 任务间协作:
    • vIntegerGenerator 任务:周期性生成整数并通过队列发送给中断

    • vStringPrinter 任务:接收并打印从中断发送过来的字符串

  3. 中断处理:
    • ulExampleInterruptHandler:中断服务程序,处理来自任务的整数数据,转换为相应的字符串

工作流程

  1. 数据生成:vIntegerGenerator 任务每 200ms 生成 5 个连续整数并发送到 xIntegerQueue

  2. 中断触发:任务通过 vPortGenerateSimulatedInterrupt() 触发软件中断

  3. 中断处理:中断服务程序从 xIntegerQueue 读取所有可用整数,将其转换为对应的字符串(取最后两位作为索引)

  4. 数据输出:中断将转换后的字符串发送到 xStringQueue,由 vStringPrinter 任务接收并打印

关键技术点

• 中断安全的队列操作:使用 xQueueSendToBackFromISR() 和 xQueueReceiveFromISR() 在中断中安全操作队列

• 任务同步:通过 portYIELD_FROM_ISR() 实现中断到任务的高效切换

• 优先级管理:字符串打印任务具有更高优先级,确保及时输出结果

这个示例展示了 FreeRTOS 中典型的中断-任务通信模式,是嵌入式实时系统中常见的数据处理架构。


/* FreeRTOS.org includes. */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"

/* Demo includes. */
#include "supporting_functions.h"

/* The number of the simulated interrupt used in this example.  Numbers 0 to 2
 * are used by the FreeRTOS Windows port itself, so 3 is the first number available
 * to the application. */
#define mainINTERRUPT_NUMBER    3

/* The tasks to be created. */
static void vIntegerGenerator( void * pvParameters );
static void vStringPrinter( void * pvParameters );

/* The service routine for the (simulated) interrupt.  This is the interrupt
 * that the task will be synchronized with. */
static uint32_t ulExampleInterruptHandler( void );

/*-----------------------------------------------------------*/

/* Declare two variables of type QueueHandle_t.  One queue will be read from
 * within an ISR, the other will be written to from within an ISR. */
QueueHandle_t xIntegerQueue, xStringQueue;

int main( void )
{
    /* Before a queue can be used it must first be created.  Create both queues
     * used by this example.  One queue can hold variables of type uint32_t,
     * the other queue can hold variables of type char*.  Both queues can hold a
     * maximum of 10 items.  A real application should check the return values to
     * ensure the queues have been successfully created. */
    xIntegerQueue = xQueueCreate( 10, sizeof( uint32_t ) );
    xStringQueue = xQueueCreate( 10, sizeof( char * ) );

    /* Create the task that uses a queue to pass integers to the interrupt
     * service	routine.  The task is created at priority 1. */
    xTaskCreate( vIntegerGenerator, "IntGen", 1000, NULL, 1, NULL );

    /* Create the task that prints out the strings sent to it from the interrupt
     * service routine.  The task is created at the higher priority of 2. */
    xTaskCreate( vStringPrinter, "String", 1000, NULL, 2, NULL );

    /* Install the handler for the software interrupt.  The syntax necessary
     * to do this is dependent on the FreeRTOS port being used.  The syntax
     * shown here can only be used with the FreeRTOS Windows port, where such
     * interrupts are only simulated. */
    vPortSetInterruptHandler( mainINTERRUPT_NUMBER, ulExampleInterruptHandler );

    /* Start the scheduler so the created tasks start executing. */
    vTaskStartScheduler();

    /* The following line should never be reached because vTaskStartScheduler()
    *  will only return if there was not enough FreeRTOS heap memory available to
    *  create the Idle and (if configured) Timer tasks.  Heap management, and
    *  techniques for trapping heap exhaustion, are described in the book text. */
    for( ; ; )
    {
    }

    return 0;
}
/*-----------------------------------------------------------*/

static void vIntegerGenerator( void * pvParameters )
{
    TickType_t xLastExecutionTime;
    const TickType_t xDelay200ms = pdMS_TO_TICKS( 200UL ), xDontBlock = 0;
    uint32_t ulValueToSend = 0;
    BaseType_t i;

    /* Initialize the variable used by the call to vTaskDelayUntil(). */
    xLastExecutionTime = xTaskGetTickCount();

    for( ; ; )
    {
        /* This is a periodic task.  Block until it is time to run again.
         * The task will execute every 200ms. */
        vTaskDelayUntil( &xLastExecutionTime, xDelay200ms );

        /* Send five numbers to the queue, each value one higher than the
         * previous value.  The numbers are read from the queue by the interrupt
         * service routine.  The interrupt	service routine always empties the
         * queue, so this task is guaranteed to be able to write all five values
         * without needing to specify a block time. */
        for( i = 0; i < 5; i++ )
        {
            xQueueSendToBack( xIntegerQueue, &ulValueToSend, xDontBlock );
            ulValueToSend++;
        }

        /* Generate the interrupt so the interrupt service routine can read the
         * values from the queue. The syntax used to generate a software interrupt
         * is dependent on the FreeRTOS port being used.  The syntax used below can
         * only be used with the FreeRTOS Windows port, in which such interrupts
         * are only simulated.*/
        vPrintString( "Generator task - About to generate an interrupt.\r\n" );
        vPortGenerateSimulatedInterrupt( mainINTERRUPT_NUMBER );
        vPrintString( "Generator task - Interrupt generated.\r\n\r\n\r\n" );
    }
}
/*-----------------------------------------------------------*/

static void vStringPrinter( void * pvParameters )
{
    char * pcString;

    for( ; ; )
    {
        /* Block on the queue to wait for data to arrive. */
        xQueueReceive( xStringQueue, &pcString, portMAX_DELAY );

        /* Print out the received string. */
        vPrintString( pcString );
    }
}
/*-----------------------------------------------------------*/

static uint32_t ulExampleInterruptHandler( void )
{
    BaseType_t xHigherPriorityTaskWoken;
    uint32_t ulReceivedNumber;

/* The strings are declared static const to ensure they are not allocated on the
 * interrupt service routine's stack, and exist even when the interrupt service
 * routine is not executing. */
    static const char * pcStrings[] =
    {
        "String 0\r\n",
        "String 1\r\n",
        "String 2\r\n",
        "String 3\r\n"
    };

    /* As always, xHigherPriorityTaskWoken is initialized to pdFALSE to be able
     * to detect it getting set to pdTRUE inside an interrupt safe API function. */
    xHigherPriorityTaskWoken = pdFALSE;

    /* Read from the queue until the queue is empty. */
    while( xQueueReceiveFromISR( xIntegerQueue, &ulReceivedNumber, &xHigherPriorityTaskWoken ) != errQUEUE_EMPTY )
    {
        /* Truncate the received value to the last two bits (values 0 to 3
         * inc.), then use the truncated value as an index into the pcStrings[]
         * array to select a string (char *) to send on the other queue. */
        ulReceivedNumber &= 0x03;
        xQueueSendToBackFromISR( xStringQueue, &pcStrings[ ulReceivedNumber ], &xHigherPriorityTaskWoken );
    }

    /* If receiving from xIntegerQueue caused a task to leave the Blocked state,
     * and if the priority of the task that left the Blocked state is higher than
     * the priority of the task in the Running state, then xHigherPriorityTaskWoken
     * will have been set to pdTRUE inside xQueueReceiveFromISR().
     *
     * If sending to xStringQueue caused a task to leave the Blocked state, and
     * if the priority of the task that left the Blocked state is higher than the
     * priority of the task in the Running state, then xHigherPriorityTaskWoken
     * will have been set to pdTRUE inside xQueueSendFromISR().
     *
     * xHigherPriorityTaskWoken is used as the parameter to portYIELD_FROM_ISR().
     * If xHigherPriorityTaskWoken equals pdTRUE then calling portYIELD_FROM_ISR()
     * will request a context switch.  If xHigherPriorityTaskWoken is still pdFALSE
     * then calling portYIELD_FROM_ISR() will have no effect.
     *
     * The implementation of portYIELD_FROM_ISR() used by the Windows port includes
     * a return statement, which is why this function does not explicitly return a
     * value. */
    portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}

DEMO 20

这段代码展示了FreeRTOS中互斥信号量(Mutex)的核心功能:实现对共享资源(这里是标准输出stdout)的互斥访问。

核心功能详解

  1. 互斥保护共享资源

• 问题:两个任务(Print1和Print2)都需要访问stdout,如果不加保护,它们的输出会相互交错,导致输出混乱

• 解决方案:使用互斥信号量确保任何时候只有一个任务能访问stdout

  1. 关键实现机制

互斥信号量创建:
xMutex = xSemaphoreCreateMutex(); // 创建互斥信号量

获取互斥锁:
xSemaphoreTake(xMutex, portMAX_DELAY); // 获取互斥锁,无限期等待

释放互斥锁:
xSemaphoreGive(xMutex); // 释放互斥锁

  1. 保护区域

xSemaphoreTake(xMutex, portMAX_DELAY);
{
printf(“%s”, pcString); // 受保护的代码区域
fflush(stdout);
}
xSemaphoreGive(xMutex);

  1. 任务设计

• 两个不同优先级的任务:Task1(优先级1)和Task2(优先级2)

• 随机延迟:使用rand() % xMaxBlockTimeTicks模拟任务执行时间的不确定性

• 减速延迟:xSlowDownDelay确保输出不会滚动太快

执行流程

  1. 高优先级任务(Task2)先运行,获取互斥锁
  2. Task2完成输出后释放互斥锁
  3. 低优先级任务(Task1)获取互斥锁并输出
  4. 过程中确保stdout访问的原子性,避免输出交错

关键技术点

• 优先级继承:FreeRTOS的互斥信号量支持优先级继承,防止优先级反转

• 阻塞机制:任务在等待互斥锁时会进入阻塞状态,让出CPU给其他任务

• 资源安全访问:确保共享资源的线程安全访问

这个示例完美演示了如何在多任务环境中使用互斥信号量来保护共享资源,是嵌入式实时系统中资源管理的经典模式。


/* Standard includes. */
#include <stdio.h>
#include <conio.h>

/* FreeRTOS.org includes. */
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"

/* Demo includes. */
#include "supporting_functions.h"

/* The task to be created.  Two instances of this task are created. */
static void prvPrintTask( void * pvParameters );

/* The function that uses a mutex to control access to standard out. */
static void prvNewPrintString( const char * pcString );

/*-----------------------------------------------------------*/

/* Declare a variable of type SemaphoreHandle_t.  This is used to reference the
 * mutex type semaphore that is used to ensure mutual exclusive access to stdout. */
SemaphoreHandle_t xMutex;

/* The tasks block for a pseudo random time between 0 and xMaxBlockTime ticks. */
const TickType_t xMaxBlockTimeTicks = 0x20;

int main( void )
{
    /* Before a semaphore is used it must be explicitly created.  In this example
     * a mutex type semaphore is created. */
    xMutex = xSemaphoreCreateMutex();

    /* Check the semaphore was created successfully. */
    if( xMutex != NULL )
    {
        /* Create two instances of the tasks that attempt to write stdout.  The
         * string they attempt to write is passed into the task as the task's
         * parameter.  The tasks are created at different priorities so some
         * pre-emption will occur. */
        xTaskCreate( prvPrintTask, "Print1", 1000, "Task 1 ******************************************\r\n", 1, NULL );
        xTaskCreate( prvPrintTask, "Print2", 1000, "Task 2 ------------------------------------------\r\n", 2, NULL );

        /* Start the scheduler so the created tasks start executing. */
        vTaskStartScheduler();
    }

    /* The following line should never be reached because vTaskStartScheduler()
    *  will only return if there was not enough FreeRTOS heap memory available to
    *  create the Idle and (if configured) Timer tasks.  Heap management, and
    *  techniques for trapping heap exhaustion, are described in the book text. */
    for( ; ; )
    {
    }

    return 0;
}
/*-----------------------------------------------------------*/

static void prvNewPrintString( const char * pcString )
{
    /* The semaphore is created before the scheduler is started so already
     * exists by the time this task executes.
     *
     * Attempt to take the semaphore, blocking indefinitely if the mutex is not
     * available immediately.  The call to xSemaphoreTake() will only return when
     * the semaphore has been successfully obtained so there is no need to check the
     * return value.  If any other delay period was used then the code must check
     * that xSemaphoreTake() returns pdTRUE before accessing the resource (in this
     * case standard out. */
    xSemaphoreTake( xMutex, portMAX_DELAY );
    {
        /* The following line will only execute once the semaphore has been
         * successfully obtained - so standard out can be accessed freely. */
        printf( "%s", pcString );
        fflush( stdout );
    }
    xSemaphoreGive( xMutex );

    /* Allow any key to stop the application running.  A real application that
     * actually used the key value should protect access to the keyboard too.  A
     * real application is very unlikely to have more than one task processing
     * key presses though! */
    if( _kbhit() != 0 )
    {
        vTaskEndScheduler();
    }
}
/*-----------------------------------------------------------*/

static void prvPrintTask( void * pvParameters )
{
    char * pcStringToPrint;
    const TickType_t xSlowDownDelay = pdMS_TO_TICKS( 5UL );

    /* Two instances of this task are created.  The string printed by the task
     * is passed into the task using the task's parameter.  The parameter is cast
     * to the required type. */
    pcStringToPrint = ( char * ) pvParameters;

    for( ; ; )
    {
        /* Print out the string using the newly defined function. */
        prvNewPrintString( pcStringToPrint );

        /* Wait a pseudo random time.  Note that rand() is not necessarily
         * re-entrant, but in this case it does not really matter as the code does
         * not care what value is returned.  In a more secure application a version
         * of rand() that is known to be re-entrant should be used - or calls to
         * rand() should be protected using a critical section. */
        vTaskDelay( rand() % xMaxBlockTimeTicks );

        /* Just to ensure the scrolling is not too fast! */
        vTaskDelay( xSlowDownDelay );
    }
}

DEMO 21

这个FreeRTOS示例程序的核心功能是通过队列实现串口输出的互斥访问,确保在多任务环境中对标准输出(如串口)的安全访问。

核心功能详细说明:

  1. 互斥访问标准输出

• 问题:在多任务环境中,多个任务同时访问标准输出(如printf)会导致输出内容混乱

• 解决方案:使用"门卫任务"模式,创建一个专门的任务(prvStdioGatekeeperTask)负责所有输出操作

  1. 实现机制

• 消息队列:创建了一个队列(xPrintQueue)用于传递要输出的字符串

• 生产者任务:两个prvPrintTask任务作为生产者,将要输出的消息发送到队列

• 消费者任务:prvStdioGatekeeperTask作为消费者,从队列接收消息并执行实际输出

  1. 关键组件

• 队列通信:使用xQueueSendToBack()发送消息,xQueueReceive()接收消息

• 中断处理:通过vApplicationTickHook()在定时器中断中也能安全发送输出消息

• 优先级管理:不同任务有不同优先级,但输出操作通过队列序列化

  1. 工作流程

  2. 打印任务将要输出的字符串指针放入队列

  3. 门卫任务从队列中取出字符串指针

  4. 门卫任务使用printf实际输出字符串

  5. 这个过程确保了即使有多个任务和中断试图输出,实际输出操作也是串行化的

这种设计模式是嵌入式系统中常见的资源管理方式,特别适用于共享资源(如串口、显示屏等)的安全访问。


/* Standard includes. */
#include <stdio.h>
#include <conio.h>

/* FreeRTOS.org includes. */
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"

/* Demo includes. */
#include "supporting_functions.h"

/* The task that sends messages to the stdio gatekeeper.  Two instances of this
 * task are created. */
static void prvPrintTask( void * pvParameters );

/* The gatekeeper task itself. */
static void prvStdioGatekeeperTask( void * pvParameters );

/* Define the strings that the tasks and interrupt will print out via the
 * gatekeeper. */
static const char * pcStringsToPrint[] =
{
    "Task 1 ****************************************************\r\n",
    "Task 2 ----------------------------------------------------\r\n",
    "Message printed from the tick hook interrupt ##############\r\n"
};

/*-----------------------------------------------------------*/

/* Declare a variable of type QueueHandle_t.  This is used to send messages from
 * the print tasks to the gatekeeper task. */
static QueueHandle_t xPrintQueue;

/* The tasks block for a pseudo random time between 0 and xMaxBlockTime ticks. */
const TickType_t xMaxBlockTimeTicks = 0x20;

int main( void )
{
    /* Before a queue is used it must be explicitly created.  The queue is created
     * to hold a maximum of 5 character pointers. */
    xPrintQueue = xQueueCreate( 5, sizeof( char * ) );

    /* Check the queue was created successfully. */
    if( xPrintQueue != NULL )
    {
        /* Create two instances of the tasks that send messages to the gatekeeper.
         * The	index to the string they attempt to write is passed in as the task
         * parameter (4th parameter to xTaskCreate()).  The tasks are created at
         * different priorities so some pre-emption will occur. */
        xTaskCreate( prvPrintTask, "Print1", 1000, ( void * ) 0, 1, NULL );
        xTaskCreate( prvPrintTask, "Print2", 1000, ( void * ) 1, 2, NULL );

        /* Create the gatekeeper task.  This is the only task that is permitted
         * to access standard out. */
        xTaskCreate( prvStdioGatekeeperTask, "Gatekeeper", 1000, NULL, 0, NULL );

        /* Start the scheduler so the created tasks start executing. */
        vTaskStartScheduler();
    }

    /* The following line should never be reached because vTaskStartScheduler()
    *  will only return if there was not enough FreeRTOS heap memory available to
    *  create the Idle and (if configured) Timer tasks.  Heap management, and
    *  techniques for trapping heap exhaustion, are described in the book text. */
    for( ; ; )
    {
    }

    return 0;
}
/*-----------------------------------------------------------*/

static void prvStdioGatekeeperTask( void * pvParameters )
{
    char * pcMessageToPrint;

    /* This is the only task that is allowed to write to the terminal output.
     * Any other task wanting to write to the output does not access the terminal
     * directly, but instead sends the output to this task.  As only one task
     * writes to standard out there are no mutual exclusion or serialization issues
     * to consider within this task itself. */
    for( ; ; )
    {
        /* Wait for a message to arrive. */
        xQueueReceive( xPrintQueue, &pcMessageToPrint, portMAX_DELAY );

        /* There is no need to check the return	value as the task will block
         * indefinitely and only run again when a message has arrived.  When the
         * next line is executed there will be a message to be output. */
        printf( "%s", pcMessageToPrint );
        fflush( stdout );

        /* Now simply go back to wait for the next message. */
    }
}
/*-----------------------------------------------------------*/

void vApplicationTickHook( void )
{
    static int iCount = 0;

    /* Print out a message every 200 ticks.  The message is not written out
     * directly, but sent to the gatekeeper task. */
    iCount++;

    if( iCount >= 200 )
    {
        /* In this case the last parameter (xHigherPriorityTaskWoken) is not
         * actually used and is set to NULL. */
        xQueueSendToFrontFromISR( xPrintQueue, &( pcStringsToPrint[ 2 ] ), NULL );

        /* Reset the count ready to print out the string again in 200 ticks
         * time. */
        iCount = 0;
    }
}
/*-----------------------------------------------------------*/

static void prvPrintTask( void * pvParameters )
{
    int iIndexToString;

    /* Two instances of this task are created so the index to the string the task
     * will send to the gatekeeper task is passed in the task parameter.  Cast this
     * to the required type. */
    iIndexToString = ( int ) pvParameters;

    for( ; ; )
    {
        /* Print out the string, not directly but by passing the string to the
         * gatekeeper task on the queue.  The queue is created before the scheduler is
         * started so will already exist by the time this task executes.  A block time
         * is not specified as there should always be space in the queue. */
        xQueueSendToBack( xPrintQueue, &( pcStringsToPrint[ iIndexToString ] ), 0 );

        /* Wait a pseudo random time.  Note that rand() is not necessarily
         * re-entrant, but in this case it does not really matter as the code does
         * not care what value is returned.  In a more secure application a version
         * of rand() that is known to be re-entrant should be used - or calls to
         * rand() should be protected using a critical section. */
        vTaskDelay( ( rand() % xMaxBlockTimeTicks ) );
    }
}
/*-----------------------------------------------------------*/



/* In other examples this function is implemented within the
 * supporting_functions.c source file - but that source file is not included in
 * this example as to include it would result in multiple definitions of the tick
 * hook function. */
void vAssertCalled( uint32_t ulLine,
                    const char * const pcFile )
{
/* The following two variables are just to ensure the parameters are not
*  optimised away and therefore unavailable when viewed in the debugger. */
    volatile uint32_t ulLineNumber = ulLine, ulSetNonZeroInDebuggerToReturn = 0;
    volatile const char * const pcFileName = pcFile;

    taskENTER_CRITICAL();
    {
        while( ulSetNonZeroInDebuggerToReturn == 0 )
        {
            /* If you want to set out of this function in the debugger to see
             * the	assert() location then set ulSetNonZeroInDebuggerToReturn to a
             * non-zero value. */
        }
    }
    taskEXIT_CRITICAL();

    /* Remove the potential for compiler warnings issued because the variables
     * are set but not subsequently referenced. */
    ( void ) pcFileName;
    ( void ) ulLineNumber;
}
/*-----------------------------------------------------------*/

/* In other examples this function is implemented within the
 * supporting_functions.c source file - but that source file is not included in
 * this example as to include it would result in multiple definitions of the tick
 * hook function. */
void vApplicationMallocFailedHook( void )
{
    /* vApplicationMallocFailedHook() will only be called if
     * configUSE_MALLOC_FAILED_HOOK is set to 1 in FreeRTOSConfig.h.  It is a hook
     * function that will get called if a call to pvPortMalloc() fails.
     * pvPortMalloc() is called internally by the kernel whenever a task, queue,
     * timer, event group, or semaphore is created.  It is also called by various
     * parts of the demo application.  If heap_1.c, heap_2.c or heap_4.c are used,
     * then the size of the heap available to pvPortMalloc() is defined by
     * configTOTAL_HEAP_SIZE in FreeRTOSConfig.h, and the xPortGetFreeHeapSize()
     * API function can be used to query the size of free heap space that remains.
     * More information is provided in the book text. */
    vAssertCalled( __LINE__, __FILE__ );
}
/*-----------------------------------------------------------*/

DEMO 22

该代码的核心功能是演示FreeRTOS事件组(Event Group)的跨任务和中断服务程序(ISR)同步机制。具体来说,它展示了如何:

  1. 使用事件组进行任务间同步:一个任务设置事件位,另一个任务等待并响应这些事件位。
  2. 在ISR中安全地设置事件位:通过一个模拟的中断,演示了如何在中断服务程序中与事件组交互,这是FreeRTOS中一个关键的实时操作模式。
  3. 使用守护任务(Daemon Task)进行ISR中的非实时操作:遵循“快进快出”的中断处理原则,将耗时的操作(如打印)推迟到守护任务中执行。

核心功能组件详解:

组件 角色与功能 关键行为

xEventGroup 同步中心:一个全局的事件组对象,用作任务和ISR之间的通信媒介。 包含三个事件位(bit 0, 1, 2),分别由不同的实体设置。

vEventBitSettingTask 任务设置者:一个FreeRTOS任务,周期性地设置事件位。 每200ms交替设置 mainFIRST_TASK_BIT (bit 0) 和 mainSECOND_TASK_BIT (bit 1)。

vIntegerGenerator 中断触发者:一个周期性的FreeRTOS任务,模拟硬件中断的发生。 每500ms调用 vPortGenerateSimulatedInterrupt 来触发一个软件中断。

ulEventBitSettingISR 中断服务程序:响应模拟中断的函数。这是核心之一。 1. 推迟打印:通过 xTimerPendFunctionCallFromISR 将打印操作发送到守护任务。
2. 设置事件位:通过 xEventGroupSetBitsFromISR 安全地设置 mainISR_BIT (bit 2)。
3. 请求上下文切换:根据情况调用 portYIELD_FROM_ISR。

vEventBitReadingTask 事件消费者:一个高优先级任务,等待并处理所有事件位。 使用 xEventGroupWaitBits 阻塞等待三个事件位中的任意一个被设置。一旦满足条件,它会检查是哪个位被设置并打印相应信息,然后清除所有位。

vPrintStringFromDaemonTask 守护任务函数:由定时器服务任务(Daemon Task)调用的函数。 执行在ISR中无法安全进行的操作——在这里是打印接收到的字符串。

核心工作机制流程:

  1. 初始化:main 函数创建事件组、三个任务并安装中断处理程序。

  2. 循环开始:
    ◦ vEventBitSettingTask 运行,设置 bit 0 和 bit 1。

    ◦ vIntegerGenerator 每500ms触发一次模拟中断。

  3. 中断发生:
    ◦ 中断触发 ulEventBitSettingISR。

    ◦ ISR 不直接打印,而是将一个打印请求(包含消息字符串)挂起(Pend) 到守护任务的队列中。

    ◦ ISR 调用 xEventGroupSetBitsFromISR 来设置 bit 2。这个函数通过向定时器命令队列发送消息来工作,是线程安全的。

    ◦ 如果设置事件位的操作唤醒了更高优先级的任务(如守护任务或位读取任务),ISR会通过 portYIELD_FROM_ISR 立即请求一次上下文切换,确保系统能及时响应。

  4. 守护任务执行:守护任务(通常是prvTimerTask)随后运行,从它的队列中取出打印请求并调用 vPrintStringFromDaemonTask 来实际输出消息。

  5. 事件处理:
    ◦ 高优先级的 vEventBitReadingTask 一直在 xEventGroupWaitBits 上阻塞。

    ◦ 当 bit 0, 1, 2 中的任何一个被设置时,该任务被解除阻塞。

    ◦ 它检查事件组的值,打印出哪些位被设置了,然后因为设置了 pdTRUE 参数,它在退出时会自动清除它等待的所有位(bit 0, 1, 2)。

总结:

这个Demo的核心功能是一个完整的、生产级别的FreeRTOS同步范例。它不仅仅展示了事件组的基本用法,更重要的是演示了如何在中断环境这种苛刻的实时场景中,通过…FromISR API和守护任务机制,安全、高效地与RTOS对象(如事件组)进行交互,同时保持系统的实时性和可靠性。这是编写健壮嵌入式实时应用程序的关键技术。


/* FreeRTOS.org includes. */
#include "FreeRTOS.h"
#include "task.h"
#include "event_groups.h"
#include "timers.h" /* For the xTimerPendFunctionCallFromISR() function. */

/* Demo includes. */
#include "supporting_functions.h"

/* The number of the simulated interrupt used in this example.  Numbers 0 to 2
 * are used by the FreeRTOS Windows port itself, so 3 is the first number available
 * to the application. */
#define mainINTERRUPT_NUMBER    3

/* Definitions for the event bits in the event group. */
#define mainFIRST_TASK_BIT      ( 1UL << 0UL ) /* Event bit 0, which is set by a task. */
#define mainSECOND_TASK_BIT     ( 1UL << 1UL ) /* Event bit 1, which is set by a task. */
#define mainISR_BIT             ( 1UL << 2UL ) /* Event bit 2, which is set by an ISR. */

/* The tasks to be created. */
static void vIntegerGenerator( void * pvParameters );
static void vEventBitSettingTask( void * pvParameters );
static void vEventBitReadingTask( void * pvParameters );

/* A function that can be deferred to run in the RTOS daemon task.  The function
 * prints out the string passed to it using the pvParameter1 parameter. */
void vPrintStringFromDaemonTask( void * pvParameter1,
                                 uint32_t ulParameter2 );

/* The service routine for the (simulated) interrupt.  This is the interrupt
 * that sets an event bit in the event group. */
static uint32_t ulEventBitSettingISR( void );

/*-----------------------------------------------------------*/

/* Declare the event group in which bits are set from both a task and an ISR. */
EventGroupHandle_t xEventGroup;

int main( void )
{
    /* Before an event group can be used it must first be created. */
    xEventGroup = xEventGroupCreate();

    /* Create the task that sets event bits in the event group. */
    xTaskCreate( vEventBitSettingTask, "BitSetter", 1000, NULL, 1, NULL );

    /* Create the task that waits for event bits to get set in the event
     * group. */
    xTaskCreate( vEventBitReadingTask, "BitReader", 1000, NULL, 2, NULL );

    /* Create the task that is used to periodically generate a software
     * interrupt. */
    xTaskCreate( vIntegerGenerator, "IntGen", 1000, NULL, 3, NULL );

    /* Install the handler for the software interrupt.  The syntax necessary
     * to do this is dependent on the FreeRTOS port being used.  The syntax
     * shown here can only be used with the FreeRTOS Windows port, where such
     * interrupts are only simulated. */
    vPortSetInterruptHandler( mainINTERRUPT_NUMBER, ulEventBitSettingISR );

    /* Start the scheduler so the created tasks start executing. */
    vTaskStartScheduler();

    /* The following line should never be reached because vTaskStartScheduler()
    *  will only return if there was not enough FreeRTOS heap memory available to
    *  create the Idle and (if configured) Timer tasks.  Heap management, and
    *  techniques for trapping heap exhaustion, are described in the book text. */
    for( ; ; )
    {
    }

    return 0;
}
/*-----------------------------------------------------------*/

static void vEventBitSettingTask( void * pvParameters )
{
    const TickType_t xDelay200ms = pdMS_TO_TICKS( 200UL ), xDontBlock = 0;

    for( ; ; )
    {
        /* Delay for a short while before starting the next loop. */
        vTaskDelay( xDelay200ms );

        /* Print out a message to say event bit 0 is about to be set by the
         * task, then set event bit 0. */
        vPrintString( "Bit setting task -\t about to set bit 0.\r\n" );
        xEventGroupSetBits( xEventGroup, mainFIRST_TASK_BIT );

        /* Delay for a short while before setting the other bit set within this
         * task. */
        vTaskDelay( xDelay200ms );

        /* Print out a message to say event bit 1 is about to be set by the
         * task, then set event bit 1. */
        vPrintString( "Bit setting task -\t about to set bit 1.\r\n" );
        xEventGroupSetBits( xEventGroup, mainSECOND_TASK_BIT );
    }
}
/*-----------------------------------------------------------*/

static uint32_t ulEventBitSettingISR( void )
{
    BaseType_t xHigherPriorityTaskWoken;

/* The string is not printed within the interrupt service, but is instead
 * sent to the RTOS daemon task for printing.  It is therefore declared static to
 * ensure the compiler does not allocate the string on the stack of the ISR (as the
 * ISR's stack frame will not exist when the string is printed from the daemon
 * task. */
    static const char * pcString = "Bit setting ISR -\t about to set bit 2.\r\n";

    /* As always, xHigherPriorityTaskWoken is initialized to pdFALSE. */
    xHigherPriorityTaskWoken = pdFALSE;

    /* Print out a message to say bit 2 is about to be set.  Messages cannot be
     * printed from an ISR, so defer the actual output to the RTOS daemon task by
     * pending a function call to run in the context of the RTOS daemon task. */
    xTimerPendFunctionCallFromISR( vPrintStringFromDaemonTask, ( void * ) pcString, 0, &xHigherPriorityTaskWoken );

    /* Set bit 2 in the event group. */
    xEventGroupSetBitsFromISR( xEventGroup, mainISR_BIT, &xHigherPriorityTaskWoken );

    /* xEventGroupSetBitsFromISR() writes to the timer command queue.  If
     * writing to the timer command queue results in the RTOS daemon task leaving
     * the Blocked state, and if the priority of the RTOS daemon task is higher
     * than the priority of the currently executing task (the task this interrupt
     * interrupted) then xHigherPriorityTaskWoken will have been set to pdTRUE
     * inside xEventGroupSetBitsFromISR().
     *
     * xHigherPriorityTaskWoken is used as the parameter to portYIELD_FROM_ISR().
     * If xHigherPriorityTaskWoken equals pdTRUE then calling portYIELD_FROM_ISR()
     * will request a context switch.  If xHigherPriorityTaskWoken is still pdFALSE
     * then calling portYIELD_FROM_ISR() will have no effect.
     *
     * The implementation of portYIELD_FROM_ISR() used by the Windows port includes
     * a return statement, which is why this function does not explicitly return a
     * value. */
    portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
/*-----------------------------------------------------------*/

static void vEventBitReadingTask( void * pvParameters )
{
    const EventBits_t xBitsToWaitFor = ( mainFIRST_TASK_BIT | mainSECOND_TASK_BIT | mainISR_BIT );
    EventBits_t xEventGroupValue;

    for( ; ; )
    {
        /* Block to wait for event bits to become set within the event group. */
        xEventGroupValue = xEventGroupWaitBits( /* The event group to read. */
            xEventGroup,

            /* Bits to test. */
            xBitsToWaitFor,

            /* Clear bits on exit if the
            *  unblock condition is met. */
            pdTRUE,

            /* Don't wait for all bits. */
            pdFALSE,

            /* Don't time out. */
            portMAX_DELAY );

        /* Print a message for each bit that was set. */
        if( ( xEventGroupValue & mainFIRST_TASK_BIT ) != 0 )
        {
            vPrintString( "Bit reading task -\t event bit 0 was set\r\n" );
        }

        if( ( xEventGroupValue & mainSECOND_TASK_BIT ) != 0 )
        {
            vPrintString( "Bit reading task -\t event bit 1 was set\r\n" );
        }

        if( ( xEventGroupValue & mainISR_BIT ) != 0 )
        {
            vPrintString( "Bit reading task -\t event bit 2 was set\r\n" );
        }

        vPrintString( "\r\n" );
    }
}
/*-----------------------------------------------------------*/

void vPrintStringFromDaemonTask( void * pvParameter1,
                                 uint32_t ulParameter2 )
{
    /* The string to print is passed into this function using the pvParameter1
     * parameter. */
    vPrintString( ( const char * ) pvParameter1 );
}
/*-----------------------------------------------------------*/

static void vIntegerGenerator( void * pvParameters )
{
    TickType_t xLastExecutionTime;
    const TickType_t xDelay500ms = pdMS_TO_TICKS( 500UL );

    /* Initialize the variable used by the call to vTaskDelayUntil(). */
    xLastExecutionTime = xTaskGetTickCount();

    for( ; ; )
    {
        /* This is a periodic task.  Block until it is time to run again.
         * The task will execute every 500ms. */
        vTaskDelayUntil( &xLastExecutionTime, xDelay500ms );

        /* Generate the interrupt that will set a bit in the event group. */
        vPortGenerateSimulatedInterrupt( mainINTERRUPT_NUMBER );
    }
}

DEMO 23

这段代码的核心功能是使用FreeRTOS的事件组(Event Group)实现多任务同步,具体来说是一个三任务同步屏障(Synchronization Barrier)。

核心功能详解:

  1. 目标:确保三个任务(Task 1, Task 2, Task 3)都到达各自的同步点后,才能一起继续向下执行。

  2. 实现机制:
    ◦ 事件组 (Event Group):xEventGroup 是一个全局对象,用作同步的中心枢纽。它是一个可以设置和等待多个二进制事件标志(bits)的机制。

    ◦ 事件位 (Event Bits):每个任务分配一个唯一的事件位来标识自己:

    ▪   mainFIRST_TASK_BIT (bit 0) -> Task 1
    
    ▪   mainSECOND_TASK_BIT (bit 1) -> Task 2
    
    ▪   mainTHIRD_TASK_BIT (bit 2) -> Task 3
    

    ◦ 关键API:xEventGroupSync() 是实现同步的核心函数。它的作用是“原子性地”完成以下操作:

    1.  将自己任务对应的比特位(uxThisTasksSyncBit)在事件组中置位(表示“我已到达”)。
    2.  等待事件组中指定的所有比特位(uxAllSyncBits,即 bit0 bit1
    

bit2)都被置位(表示“所有人都已到达”)。
3. 当所有位都被置位后,该函数会清除这些等待的比特位(为下一次同步做准备),然后所有等待于此的任务同时被解除阻塞,继续执行。

  1. 工作流程:
    1. 初始化:在 main 函数中创建事件组和一个随机数种子,然后创建三个相同的任务 vSyncingTask,并通过参数告诉每个任务它对应的是哪个事件位。

    2. 任务执行:
      ▪ 每个任务首先会随机延迟一段时间 (vTaskDelay( xDelayTime )),模拟执行不同时长的准备工作,确保它们不会同时到达同步点。

      ▪ 延迟结束后,任务打印信息表示“到达同步点”(“reached sync point”)。

      ▪ 随后调用 xEventGroupSync(),设置自己的标志位并等待所有三个标志位。

      ▪ 最先到达的任务会在 xEventGroupSync() 中阻塞,等待后来的任务。

      ▪ 当最后一个任务到达并设置了自己的标志位时,所有三个任务的 xEventGroupSync() 调用将同时完成。

    3. 同步完成:
      ▪ 所有任务从 xEventGroupSync() 函数返回,打印信息表示“退出同步点”(“exited sync point”)。

      ▪ 然后每个任务再次进入 for 循环,开始下一次的“工作 -> 延迟 -> 同步”过程。

代码流程总结表

步骤 角色/组件 动作 目的/结果

1 main() 函数 创建事件组 xEventGroup 提供同步所需的共享对象

2 main() 函数 创建三个 vSyncingTask 实例 启动三个需要同步的任务

3 vSyncingTask (每个任务) 随机延迟 vTaskDelay(…) 模拟不确定的任务执行时间,防止同时到达

4 vSyncingTask (任务N) 打印 “reached sync point” 指示该任务已到达同步点

5 vSyncingTask (任务N) 调用 xEventGroupSync(…) 原子性地:1) 设置自己的事件位;2) 等待所有位被设置

6 内核/事件组 前两个任务在 xEventGroupSync 中阻塞 等待第三个任务

7 内核/事件组 第三个任务调用 xEventGroupSync 设置自己的位后,发现所有位都已置位

8 内核/事件组 清除事件组中的同步位,并唤醒所有等待的任务 同步完成,所有任务同时被释放

9 vSyncingTask (所有任务) 从 xEventGroupSync 返回,打印 “exited sync point” 指示任务已通过同步点

10 vSyncingTask (所有任务) 循环回到步骤3 开始下一次同步周期

总结

这个例程的核心是演示了FreeRTOS中事件组的同步屏障功能。它完美解决了多个任务需要在一个特定点相互等待,直到所有成员都就绪后才能继续执行的并发同步问题。伪随机延迟的加入使得演示效果更加明显,模拟了真实场景中任务执行时间的不确定性。


/* Standard includes - used to seed the random number generator. */
#include <time.h>

/* FreeRTOS.org includes. */
#include "FreeRTOS.h"
#include "task.h"
#include "event_groups.h"

/* Demo includes. */
#include "supporting_functions.h"

/* Definitions for the event bits in the event group. */
#define mainFIRST_TASK_BIT     ( 1UL << 0UL ) /* Event bit 0, which is set by the first task. */
#define mainSECOND_TASK_BIT    ( 1UL << 1UL ) /* Event bit 1, which is set by the second task. */
#define mainTHIRD_TASK_BIT     ( 1UL << 2UL ) /* Event bit 2, which is set by the third task. */

/* Pseudo random number generation functions - implemented in this file as the
 * MSVC rand() function has unexpected consequences. */
static uint32_t prvRand( void );
static void prvSRand( uint32_t ulSeed );

/* Three instances of this task are created. */
static void vSyncingTask( void * pvParameters );

/*-----------------------------------------------------------*/

/* Use by the pseudo random number generator. */
static uint32_t ulNextRand;

/* Declare the event group used to synchronize the three tasks. */
EventGroupHandle_t xEventGroup;

int main( void )
{
    /* The tasks created in this example block for a random time.  The block
     * time is generated using rand() - seed the random number generator. */
    prvSRand( ( uint32_t ) time( NULL ) );

    /* Before an event group can be used it must first be created. */
    xEventGroup = xEventGroupCreate();

    /* Create three instances of the task.  Each task is given a different name,
     * which is later printed out to give a visual indication of which task is
     * executing.  The event bit to use when the task reaches its synchronization
     * point is passed into the task using the task parameter. */
    xTaskCreate( vSyncingTask, "Task 1", 1000, ( void * ) mainFIRST_TASK_BIT, 1, NULL );
    xTaskCreate( vSyncingTask, "Task 2", 1000, ( void * ) mainSECOND_TASK_BIT, 1, NULL );
    xTaskCreate( vSyncingTask, "Task 3", 1000, ( void * ) mainTHIRD_TASK_BIT, 1, NULL );

    /* Start the scheduler so the created tasks start executing. */
    vTaskStartScheduler();

    /* The following line should never be reached because vTaskStartScheduler()
    *  will only return if there was not enough FreeRTOS heap memory available to
    *  create the Idle and (if configured) Timer tasks.  Heap management, and
    *  techniques for trapping heap exhaustion, are described in the book text. */
    for( ; ; )
    {
    }

    return 0;
}
/*-----------------------------------------------------------*/

static void vSyncingTask( void * pvParameters )
{
    const EventBits_t uxAllSyncBits = ( mainFIRST_TASK_BIT | mainSECOND_TASK_BIT | mainTHIRD_TASK_BIT );
    const TickType_t xMaxDelay = pdMS_TO_TICKS( 4000UL );
    const TickType_t xMinDelay = pdMS_TO_TICKS( 200UL );
    TickType_t xDelayTime;
    EventBits_t uxThisTasksSyncBit;

    /* Three instances of this task are created - each task uses a different
     * event bit in the synchronization.  The event bit to use by this task
     * instance is passed into the task using the task's parameter.  Store it in
     * the uxThisTasksSyncBit variable. */
    uxThisTasksSyncBit = ( EventBits_t ) pvParameters;

    for( ; ; )
    {
        /* Simulate this task taking some time to perform an action by delaying
         * for a pseudo random time.  This prevents all three instances of this
         * task from reaching the synchronization point at the same time, and
         * allows the example's behavior to be observed more easily. */
        xDelayTime = ( prvRand() % xMaxDelay ) + xMinDelay;
        vTaskDelay( xDelayTime );

        /* Print out a message to show this task has reached its synchronization
         * point.  pcTaskGetTaskName() is an API function that returns the name
         * assigned to the task when the task was created. */
        vPrintTwoStrings( pcTaskGetTaskName( NULL ), "reached sync point" );

        /* Wait for all the tasks to have reached their respective
         * synchronization points. */
        xEventGroupSync( /* The event group used to synchronize. */
            xEventGroup,

            /* The bit set by this task to indicate it has reached
             * the synchronization point. */
            uxThisTasksSyncBit,

            /* The bits to wait for, one bit for each task taking
             * part in the synchronization. */
            uxAllSyncBits,

            /* Wait indefinitely for all three tasks to reach the
             * synchronization point. */
            portMAX_DELAY );

        /* Print out a message to show this task has passed its synchronization
         * point.  As an indefinite delay was used the following line will only be
         * reached after all the tasks reached their respective synchronization
         * points. */
        vPrintTwoStrings( pcTaskGetTaskName( NULL ), "exited sync point" );
    }
}
/*-----------------------------------------------------------*/

static uint32_t prvRand( void )
{
    const uint32_t ulMultiplier = 0x015a4e35UL, ulIncrement = 1UL;
    uint32_t ulReturn;

    /* Utility function to generate a pseudo random number as the MSVC rand()
     * function has unexpected consequences. */
    taskENTER_CRITICAL();
    ulNextRand = ( ulMultiplier * ulNextRand ) + ulIncrement;
    ulReturn = ( ulNextRand >> 16UL ) & 0x7fffUL;
    taskEXIT_CRITICAL();
    return ulReturn;
}
/*-----------------------------------------------------------*/

static void prvSRand( uint32_t ulSeed )
{
    /* Utility function to seed the pseudo random number generator. */
    ulNextRand = ulSeed;
}
/*-----------------------------------------------------------*/

DEMO 24

这段代码展示了FreeRTOS中中断处理与任务同步的核心功能实现。以下是其主要功能的详细分析:

核心功能概述

代码实现了一个典型的中断处理模式,其中:
• 中断服务程序(ISR)处理硬件中断

• 高优先级任务处理实际的中断相关逻辑

• 周期性任务模拟中断的发生

关键组件功能

  1. 中断处理程序 (ulExampleInterruptHandler)

static uint32_t ulExampleInterruptHandler(void)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;

// 向处理任务发送通知
vTaskNotifyGiveFromISR(xHandlerTask, &xHigherPriorityTaskWoken);

// 如果需要,请求上下文切换
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);

}

功能:在ISR中直接通知处理任务,使用vTaskNotifyGiveFromISR()实现高效的任务同步。

  1. 中断处理任务 (vHandlerTask)

static void vHandlerTask(void *pvParameters)
{
for(;😉
{
// 等待来自ISR的通知
ulEventsToProcess = ulTaskNotifyTake(pdTRUE, xMaxExpectedBlockTime);

    // 处理所有待处理的事件
    while(ulEventsToProcess > 0)
    {
        vPrintString("Handler task - Processing event.\r\n");
        ulEventsToProcess--;
    }
}

}

功能:作为中断的延迟处理程序,使用任务通知机制高效地接收和处理中断事件。

  1. 周期性任务 (vPeriodicTask)

static void vPeriodicTask(void *pvParameters)
{
for(;😉
{
vTaskDelay(xInterruptFrequency); // 等待500ms

    // 生成模拟中断
    vPortGenerateSimulatedInterrupt(mainINTERRUPT_NUMBER);
}

}

功能:模拟硬件中断的周期性发生,用于演示目的。

核心技术特点

  1. 任务通知机制:使用ulTaskNotifyTake()和vTaskNotifyGiveFromISR()实现高效的中断到任务通信

  2. 中断延迟处理:将中断处理逻辑从ISR转移到高优先级任务,减少ISR执行时间

  3. 优先级管理:
    • 处理任务优先级为3(较高)

    • 周期性任务优先级为1(较低)

    • 确保处理任务能及时响应中断

  4. 超时处理:处理任务设置了最大预期阻塞时间,包含错误恢复机制

设计模式优势

• 减少ISR复杂度:ISR只做最基本的通知工作

• 提高响应性:高优先级任务确保及时处理

• 资源管理:避免了在ISR中直接处理复杂逻辑

• 可测试性:通过模拟中断便于测试和调试

这种模式是FreeRTOS中处理中断的推荐方式,特别适用于需要处理复杂中断逻辑或需要与多个任务交互的场景。


/* FreeRTOS.org includes. */
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"

/* Demo includes. */
#include "supporting_functions.h"

/* The number of the simulated interrupt used in this example.  Numbers 0 to 2
 * are used by the FreeRTOS Windows port itself, so 3 is the first number available
 * to the application. */
#define mainINTERRUPT_NUMBER    3

/* The tasks to be created. */
static void vHandlerTask( void * pvParameters );
static void vPeriodicTask( void * pvParameters );

/* The service routine for the (simulated) interrupt.  This is the interrupt
 * that the task will be synchronized with. */
static uint32_t ulExampleInterruptHandler( void );

/* The rate at which the periodic task generates software interrupts. */
static const TickType_t xInterruptFrequency = pdMS_TO_TICKS( 500UL );

/* Stores the handle of the task to which interrupt processing is deferred. */
static TaskHandle_t xHandlerTask = NULL;

/*-----------------------------------------------------------*/

int main( void )
{
    /* Create the 'handler' task, which is the task to which interrupt
     * processing is deferred, and so is the task that will be synchronized
     * with the interrupt.  The handler task is created with a high priority to
     * ensure it runs immediately after the interrupt exits.  In this case a
     * priority of 3 is chosen.  The handle of the task is saved for use by the
     * ISR. */
    xTaskCreate( vHandlerTask, "Handler", 1000, NULL, 3, &xHandlerTask );

    /* Create the task that will periodically generate a software interrupt.
     * This is created with a priority below the handler task to ensure it will
     * get preempted each time the handler task exits the Blocked state. */
    xTaskCreate( vPeriodicTask, "Periodic", 1000, NULL, 1, NULL );

    /* Install the handler for the software interrupt.  The syntax necessary
     * to do this is dependent on the FreeRTOS port being used.  The syntax
     * shown here can only be used with the FreeRTOS Windows port, where such
     * interrupts are only simulated. */
    vPortSetInterruptHandler( mainINTERRUPT_NUMBER, ulExampleInterruptHandler );

    /* Start the scheduler so the created tasks start executing. */
    vTaskStartScheduler();

    /* The following line should never be reached because vTaskStartScheduler()
    *  will only return if there was not enough FreeRTOS heap memory available to
    *  create the Idle and (if configured) Timer tasks.  Heap management, and
    *  techniques for trapping heap exhaustion, are described in the book text. */
    for( ; ; )
    {
    }

    return 0;
}
/*-----------------------------------------------------------*/

static void vHandlerTask( void * pvParameters )
{
/* xMaxExpectedBlockTime is set to be a little longer than the maximum expected
 * time between events. */
    const TickType_t xMaxExpectedBlockTime = xInterruptFrequency + pdMS_TO_TICKS( 10 );
    uint32_t ulEventsToProcess;

    /* As per most tasks, this task is implemented within an infinite loop. */
    for( ; ; )
    {
        /* Wait to receive a notification sent directly to this task from the
         * interrupt handler. */
        ulEventsToProcess = ulTaskNotifyTake( pdTRUE, xMaxExpectedBlockTime );

        if( ulEventsToProcess != 0 )
        {
            /* To get here at least one event must have occurred.  Loop here
             * until all the pending events have been processed (in this case, just
             * print out a message for each event). */
            while( ulEventsToProcess > 0 )
            {
                vPrintString( "Handler task - Processing event.\r\n" );
                ulEventsToProcess--;
            }
        }
        else
        {
            /* If this part of the function is reached then an interrupt did not
             * arrive within the expected time, and (in a real application) it may
             * be necessary to perform some error recovery operations. */
        }
    }
}
/*-----------------------------------------------------------*/

static uint32_t ulExampleInterruptHandler( void )
{
    BaseType_t xHigherPriorityTaskWoken;

    /* The xHigherPriorityTaskWoken parameter must be initialized to pdFALSE as
     * it will get set to pdTRUE inside the interrupt safe API function if a
     * context switch is required. */
    xHigherPriorityTaskWoken = pdFALSE;

    /* Send a notification directly to the handler task. */
    vTaskNotifyGiveFromISR( /* The handle of the task to which the notification
                             * is being sent.  The handle was saved when the task
                             * was created. */
        xHandlerTask,

        /* xHigherPriorityTaskWoken is used in the usual
         * way. */
        &xHigherPriorityTaskWoken );

    /* Pass the xHigherPriorityTaskWoken value into portYIELD_FROM_ISR().  If
     * xHigherPriorityTaskWoken was set to pdTRUE inside vTaskNotifyGiveFromISR()
     * then calling portYIELD_FROM_ISR() will request a context switch.  If
     * xHigherPriorityTaskWoken is still pdFALSE then calling
     * portYIELD_FROM_ISR() will have no effect.  The implementation of
     * portYIELD_FROM_ISR() used by the Windows port includes a return statement,
     * which is why this function does not explicitly return a value. */
    portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
/*-----------------------------------------------------------*/

static void vPeriodicTask( void * pvParameters )
{
    /* As per most tasks, this task is implemented within an infinite loop. */
    for( ; ; )
    {
        /* This task is just used to 'simulate' an interrupt.  This is done by
         * periodically generating a simulated software interrupt.  Block until it
         * is time to generate the software interrupt again. */
        vTaskDelay( xInterruptFrequency );

        /* Generate the interrupt, printing a message both before and after
         * the interrupt has been generated so the sequence of execution is evident
         * from the output.
         *
         * The syntax used to generate a software interrupt is dependent on the
         * FreeRTOS port being used.  The syntax used below can only be used with
         * the FreeRTOS Windows port, in which such interrupts are only
         * simulated. */
        vPrintString( "Periodic task - About to generate an interrupt.\r\n" );
        vPortGenerateSimulatedInterrupt( mainINTERRUPT_NUMBER );
        vPrintString( "Periodic task - Interrupt generated.\r\n\r\n\r\n" );
    }
}

DEMO 25

这个FreeRTOS示例程序展示了中断延迟处理(Deferred Interrupt Processing)的核心功能,通过任务通知(Task Notifications)实现高效的中断与任务同步。

核心功能概述

  1. 中断延迟处理模式

• 中断服务程序(ISR)最小化:中断处理程序(ulExampleInterruptHandler)只做最必要的操作,将实际处理工作推迟到高优先级任务(vHandlerTask)中执行

• 减少中断关闭时间:避免在ISR中执行复杂操作,提高系统响应性

  1. 任务通知机制

• 高效同步:使用vTaskNotifyGiveFromISR()和ulTaskNotifyTake()实现中断与任务间的同步

• 事件计数:通过pdFALSE参数保持通知计数,确保不丢失多次中断事件

  1. 双任务架构

• Handler任务(高优先级):专门处理中断事件,确保及时响应

• Periodic任务(低优先级):周期性触发模拟中断,演示功能

关键组件功能

组件 优先级 功能描述

vHandlerTask 3(高) 中断处理任务,等待并处理来自ISR的通知

vPeriodicTask 1(低) 周期性生成模拟中断,触发处理流程

ulExampleInterruptHandler ISR 中断服务程序,发送任务通知

工作流程

  1. 初始化:创建两个任务并设置中断处理程序
  2. 中断触发:Periodic任务每隔500ms生成模拟中断
  3. 中断处理:ISR向Handler任务发送3次通知(增加通知值)
  4. 事件处理:Handler任务接收通知,处理中断事件
  5. 超时检测:Handler任务设置超时机制,检测异常情况

技术特点

• 内存高效:使用任务通知代替二进制信号量,节省内存资源

• 实时性保证:高优先级处理任务确保中断及时响应

• 可靠性设计:包含超时检测机制,可扩展错误恢复功能

• 可移植性:通过宏定义和抽象接口保持跨平台兼容性

这种设计模式特别适合需要处理高频中断或复杂中断处理的嵌入式实时系统,在保持系统响应性的同时确保中断处理的完整性。


/* FreeRTOS.org includes. */
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"

/* Demo includes. */
#include "supporting_functions.h"

/* The number of the simulated interrupt used in this example.  Numbers 0 to 2
 * are used by the FreeRTOS Windows port itself, so 3 is the first number available
 * to the application. */
#define mainINTERRUPT_NUMBER    3

/* The tasks to be created. */
static void vHandlerTask( void * pvParameters );
static void vPeriodicTask( void * pvParameters );

/* The service routine for the (simulated) interrupt.  This is the interrupt
 * that the task will be synchronized with. */
static uint32_t ulExampleInterruptHandler( void );

/* The rate at which the periodic task generates software interrupts. */
static const TickType_t xInterruptFrequency = pdMS_TO_TICKS( 500UL );

/* Stores the handle of the task to which interrupt processing is deferred. */
static TaskHandle_t xHandlerTask = NULL;

/*-----------------------------------------------------------*/

int main( void )
{
    /* Create the 'handler' task, which is the task to which interrupt
     * processing is deferred, and so is the task that will be synchronized
     * with the interrupt.  The handler task is created with a high priority to
     * ensure it runs immediately after the interrupt exits.  In this case a
     * priority of 3 is chosen.  The handle of the task is saved for use by the
     * ISR. */
    xTaskCreate( vHandlerTask, "Handler", 1000, NULL, 3, &xHandlerTask );

    /* Create the task that will periodically generate a software interrupt.
     * This is created with a priority below the handler task to ensure it will
     * get preempted each time the handler task exits the Blocked state. */
    xTaskCreate( vPeriodicTask, "Periodic", 1000, NULL, 1, NULL );

    /* Install the handler for the software interrupt.  The syntax necessary
     * to do this is dependent on the FreeRTOS port being used.  The syntax
     * shown here can only be used with the FreeRTOS Windows port, where such
     * interrupts are only simulated. */
    vPortSetInterruptHandler( mainINTERRUPT_NUMBER, ulExampleInterruptHandler );

    /* Start the scheduler so the created tasks start executing. */
    vTaskStartScheduler();

    /* The following line should never be reached because vTaskStartScheduler()
    *  will only return if there was not enough FreeRTOS heap memory available to
    *  create the Idle and (if configured) Timer tasks.  Heap management, and
    *  techniques for trapping heap exhaustion, are described in the book text. */
    for( ; ; )
    {
    }

    return 0;
}
/*-----------------------------------------------------------*/

static void vHandlerTask( void * pvParameters )
{
/* xMaxExpectedBlockTime is set to be a little longer than the maximum expected
 * time between events. */
    const TickType_t xMaxExpectedBlockTime = xInterruptFrequency + pdMS_TO_TICKS( 10 );

    /* As per most tasks, this task is implemented within an infinite loop. */
    for( ; ; )
    {
        /* Wait to receive a notification sent directly to this task from the
         * interrupt handler.  The xClearCountOnExit parameter is now pdFALSE, so
         * the task's notification will be decremented when ulTaskNotifyTake()
         * returns having received a notification. */
        if( ulTaskNotifyTake( pdFALSE, xMaxExpectedBlockTime ) != 0 )
        {
            /* To get here the event must have occurred.  Process the event (in
             * this case just print out a message). */
            vPrintString( "Handler task - Processing event.\r\n" );
        }
        else
        {
            /* If this part of the function is reached then an interrupt did not
             * arrive within the expected time, and (in a real application) it may
             * be necessary to perform some error recovery operations. */
        }
    }
}
/*-----------------------------------------------------------*/

static uint32_t ulExampleInterruptHandler( void )
{
    BaseType_t xHigherPriorityTaskWoken;

    /* The xHigherPriorityTaskWoken parameter must be initialized to pdFALSE as
     * it will get set to pdTRUE inside the interrupt safe API function if a
     * context switch is required. */
    xHigherPriorityTaskWoken = pdFALSE;

    /* Send a notification to the handler task multiple times.  The first will
     * unblock the task, the following 'gives' are to demonstrate that the
     * receiving task's notification value is being used to latch events - allowing
     * the task to process the events in turn. */
    vTaskNotifyGiveFromISR( xHandlerTask, &xHigherPriorityTaskWoken );
    vTaskNotifyGiveFromISR( xHandlerTask, &xHigherPriorityTaskWoken );
    vTaskNotifyGiveFromISR( xHandlerTask, &xHigherPriorityTaskWoken );

    /* Pass the xHigherPriorityTaskWoken value into portYIELD_FROM_ISR().  If
     * xHigherPriorityTaskWoken was set to pdTRUE inside vTaskNotifyGiveFromISR()
     * then calling portYIELD_FROM_ISR() will request a context switch.  If
     * xHigherPriorityTaskWoken is still pdFALSE then calling
     * portYIELD_FROM_ISR() will have no effect.  The implementation of
     * portYIELD_FROM_ISR() used by the Windows port includes a return statement,
     * which is why this function does not explicitly return a value. */
    portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
/*-----------------------------------------------------------*/

static void vPeriodicTask( void * pvParameters )
{
    /* As per most tasks, this task is implemented within an infinite loop. */
    for( ; ; )
    {
        /* This task is just used to 'simulate' an interrupt.  This is done by
         * periodically generating a simulated software interrupt.  Block until it
         * is time to generate the software interrupt again. */
        vTaskDelay( xInterruptFrequency );

        /* Generate the interrupt, printing a message both before and after
         * the interrupt has been generated so the sequence of execution is evident
         * from the output.
         *
         * The syntax used to generate a software interrupt is dependent on the
         * FreeRTOS port being used.  The syntax used below can only be used with
         * the FreeRTOS Windows port, in which such interrupts are only
         * simulated. */
        vPrintString( "Periodic task - About to generate an interrupt.\r\n" );
        vPortGenerateSimulatedInterrupt( mainINTERRUPT_NUMBER );
        vPrintString( "Periodic task - Interrupt generated.\r\n\r\n\r\n" );
    }
}

学习资源

  1. FreeRTOS 编程指南
  2. ​​Mastering the FreeRTOS™ Real Time Kernel​​(官方出的这本书是手把手教程,极其优秀)
Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐