FreeRTOS 在 AVR 平台上的 MPLAB X 演示工程:Blinky / Minimal / Full 三档 Demo 完全指南
2026/9/16 12:10:03 网站建设 项目流程

FreeRTOS 在 AVR 平台上的 MPLAB X 演示工程:Blinky / Minimal / Full 三档 Demo 完全指南

【免费下载链接】FreeRTOS'Classic' FreeRTOS distribution. Started as Git clone of FreeRTOS SourceForge SVN repo. Submodules the kernel.项目地址: https://gitcode.com/GitHub_Trending/fr/FreeRTOS

本篇技术指南聚焦于 FreeRTOS 官方仓库中面向 AVR 单片机(AVR128DA48 Curiosity Nano 及 AVR-Dx / megaAVR-0 系列)的 MPLAB X(XC8 编译器)演示工程,系统讲解AVR_ATMega4809_MPLAB.X工程内通过一个宏即可切换的三套示例程序(Blinky、Minimal、Full)的架构、任务组成、check 任务与 LED 反馈机制、串口配置与快速上手指南。读完本文,你将能够独立搭建工具链、在 Curiosity Nano 板上构建并运行任一 Demo,并能依据源码定位每个示例背后的 FreeRTOS 内核机制(队列、信号量、任务通知、递归互斥量、软件定时器等)。

工程概览:面向 AVR128DA48 的 MPLAB X 演示工程

本演示工程位于 FreeRTOS/Demo/AVR_ATMega4809_MPLAB.X 目录,是一个使用MPLAB® X IDE + XC8 编译器的裸机 FreeRTOS 示例项目,目标硬件为AVR128DA48 Curiosity Nano 开发板(搭载 AVR128DA48 微控制器:128 KB Flash、16 KB SRAM、512 字节 EEPROM)。仓库 ReadMe 同时将 readme.md 的参考资料指向了 Atmega4809 / Atmega4808 / Atmega3208 / Atmega3209 等 AVR 器件页面,表明该工程同样适用于这一系列 AVR-Dx 与 megaAVR-0 器件。

工程的最大特点是一个工程、三套示例:通过修改 main.c 中的mainSELECTED_APPLICATION宏,即可在三套演示程序之间切换,每一套示例对应一个独立的main-***demo_name***.c源文件:

宏取值演示名称入口源文件定位
0Blinky Demomain_blinky.c面向初学者的最小示例,仅两个任务 + 一个队列
1Minimal Demomain_minimal.c任务数较多但复杂度仍较低的功能演示
2Full Demomain_full.c覆盖 FreeRTOS 大量特性的综合演示与测试

入口 main() 如何选择 Demo

从 main.c 源码可以看到三套示例的调度逻辑:

#define mainSELECTED_APPLICATION 0 #if ( mainSELECTED_APPLICATION == 0 ) extern void main_blinky( void ); extern void init_blinky( void ); #elif ( mainSELECTED_APPLICATION == 1 ) extern void main_minimal( void ); extern void init_minimal( void ); #elif ( mainSELECTED_APPLICATION == 2 ) extern void main_full( void ); extern void init_full( void ); #else #error Invalid mainSELECTED_APPLICATION setting. ... #endif int main( void ) { prvSetupHardware(); #if ( mainSELECTED_APPLICATION == 0 ) main_blinky(); #elif ( mainSELECTED_APPLICATION == 1 ) main_minimal(); #elif ( mainSELECTED_APPLICATION == 2 ) main_full(); #endif return 0; }

每套示例都有两个入口:init_***demo_name***()(硬件初始化)与main_***demo_name***()(创建任务并启动调度器)。main()首先通过prvSetupHardware()完成底层准备——调用portDISABLE_INTERRUPTS()确保调度器启动前没有中断打断内核状态的建立(注释明确说明:调度器启动时中断会自动使能),随后执行CLK_init()完成时钟配置,最后再调用对应 Demo 的init_***demo_name***()。如果宏取值为非法值(非 0/1/2),编译期会直接报错提示。

时钟与内核配置(FreeRTOSConfig.h 与 clk_config.h)

示例对系统时钟有明确的限定与配置。在 FreeRTOSConfig.h 中:

  • configCPU_CLOCK_HZ默认为10000000(10 MHz),注释明确说明可选的时钟频率为20 MHz、10 MHz、5 MHz、2 MHz四种,其他频率需要自行修改clock_config.h
  • configTICK_RATE_HZ1000(即 1 kHz 系统节拍);
  • configMAX_PRIORITIES4configMINIMAL_STACK_SIZE110字(AVR 平台为 8 位 MCU,栈以字节为单位考量);
  • configUSE_16_BIT_TICKS1(AVR 8 位平台上 TickType_t 使用 16 位,节省 RAM);
  • 内存管理采用动态分配(configSUPPORT_DYNAMIC_ALLOCATION 1),堆大小configTOTAL_HEAP_SIZE0x1000(4 KB);
  • 软件定时器相关:configUSE_TIMERS 1、定时器队列长度5、定时器任务栈configMINIMAL_STACK_SIZE * 2
  • 头文件顶部还有一个 AVR 平台特有的处理:为避免 FreeRTOS 任务控制块类型TCB_t与 AVR 的 Timer Counter B 类型TCB_t冲突,先将TCB_t重定义为avrTCB_t,包含<avr/io.h>后再#undef恢复。

对应的 clk_config.h 使用_PROTECTED_WRITE()(受保护寄存器写入)操作CLKCTRL.MCLKCTRLA/CLKCTRL.MCLKCTRLB寄存器,将时钟源选为内部 20 MHz 振荡器,再通过预分频器得到目标频率:20 MHz 不分频(PDIV关闭)、10 MHz 二分频(CLKCTRL_PDIV_2X_gc | CLKCTRL_PEN_bm)、5 MHz 四分频、2 MHz 十分频。

值得注意的还有定时器实例的选择:configUSE_TIMER_INSTANCE 0表示使用TCB0作为 FreeRTOS 系统节拍定时器(注释给出了实例与取值的对应表:TCB0=0、TCB1=1、TCB2=2、TCB3=3、RTC=4)。

Blinky Demo:两个任务与一个队列的最小内核演示

#define mainSELECTED_APPLICATION 0

Blinky Demo 专为初学者设计,是理解 FreeRTOS 任务与队列机制的最佳起点。整个示例只创建两个任务和一个队列,全部代码集中在 main_blinky.c 单个 C 文件中。

任务角色与数据流

  • 队列发送任务prvQueueSendTask,优先级tskIDLE_PRIORITY + 1):每隔200 ms向队列写入一个预定义值100
  • 队列接收任务prvQueueReceiveTask,优先级tskIDLE_PRIORITY + 2):阻塞等待队列中的消息,收到后校验值是否为预期的100,若匹配则翻转板载 LED(引脚 PF5)。

两个任务通过队列解耦,接收方以portMAX_DELAY无限期阻塞在xQueueReceive()上,只有发送方写入数据时才会被唤醒——因此 LED 恰好以200 ms 为周期翻转

源码关键点解读

#define mainQUEUE_RECEIVE_TASK_PRIORITY ( tskIDLE_PRIORITY + 2 ) #define mainQUEUE_SEND_TASK_PRIORITY ( tskIDLE_PRIORITY + 1 ) #define mainQUEUE_SEND_FREQUENCY_MS ( 200 / portTICK_PERIOD_MS ) #define mainQUEUE_LENGTH ( 1 ) /* 队列深度为 1 */ static QueueHandle_t xQueue = NULL; void main_blinky( void ) { xQueue = xQueueCreate( mainQUEUE_LENGTH, sizeof( uint32_t ) ); if( xQueue != NULL ) { xTaskCreate( prvQueueReceiveTask, "Rx", configMINIMAL_STACK_SIZE, NULL, mainQUEUE_RECEIVE_TASK_PRIORITY, NULL ); xTaskCreate( prvQueueSendTask, "TX", configMINIMAL_STACK_SIZE, NULL, mainQUEUE_SEND_TASK_PRIORITY, NULL ); vTaskStartScheduler(); } for( ; ; ) { } /* 正常情况下调度器启动后不会执行到这里 */ }
  • 队列深度设为1是有意为之:接收任务会及时取走数据,发送任务写入时队列总是空的,因此发送时使用0阻塞时间也不会失败;
  • 发送任务使用vTaskDelayUntil()(周期性延时,而非相对延时),配合xNextWakeTime = xTaskGetTickCount()的初始化,可以避免节拍漂移、实现精确的 200 ms 周期;
  • LED 翻转直接操作 AVR 寄存器PORTF.OUTTGL |= PIN5_bm,硬件初始化init_blinky()中将PORTF.DIRSET = PIN5_bm(PF5 设为输出)——这与工程通用的 LED 驱动 ParTest.c 使用的 PORTF 端口一致。

Minimal Demo:多任务功能演示与 check 任务

#define mainSELECTED_APPLICATION 1

Minimal Demo 的任务数量明显多于 Blinky,但整体复杂度仍然较低。全部功能集中在 main_minimal.c 中,它创建了以下任务:

  • 整数运算任务Integer.c):反复执行一组整型运算并将结果与已知期望值比对,用于验证上下文切换机制是否完好(实现见 FreeRTOS/Demo/Common/Minimal/integer.c,运算常量与期望答案定义在文件头部);
  • 寄存器测试任务Regtest.c):创建两个测试任务,用内联汇编给大量寄存器写入已知值,然后调用taskYIELD()强制切换,恢复运行后逐一检查寄存器值是否被破坏——这是对上下文切换正确性的直接压力测试,完整实现见 regtest.c,一旦发现异常即将xRegTestError置为非零;
  • 轮询队列任务PollQ.c):以轮询方式测试队列操作;
  • 串口通信任务Serial.c):测试基于中断驱动的串口收发;
  • check 任务(本文件内的vErrorChecks):周期性地检查上述任务是否仍在无错误运行。

从 main_minimal.c 可以看到任务创建顺序:

void main_minimal( void ) { prvIncrementResetCount(); /* EEPROM 复位计数 */ vStartIntegerMathTasks( tskIDLE_PRIORITY ); vAltStartComTestTasks( mainCOM_TEST_PRIORITY, mainCOM_TEST_BAUD_RATE, mainCOM_TEST_LED ); vStartPolledQueueTasks( mainQUEUE_POLL_PRIORITY ); vStartRegTestTasks(); xTaskCreate( vErrorChecks, "Check", configMINIMAL_STACK_SIZE, NULL, mainCHECK_TASK_PRIORITY, NULL ); vStartFlashCoRoutines( mainNUM_FLASH_COROUTINES ); /* 3 个闪烁协程 */ vTaskStartScheduler(); }

该示例还包含两个值得注意的细节:

  1. EEPROM 复位计数prvIncrementResetCount()在启动时从地址0x1400读取复位计数并加 1 写回,用于检测 CPU 是否发生意外复位;
  2. 协程(Co-routine)演示:创建 3 个 LED 闪烁协程(vStartFlashCoRoutines),并且空闲钩子vApplicationIdleHook()中调用vCoRoutineSchedule()驱动协程调度(对应的内核配置configUSE_CO_ROUTINES 1configMAX_CO_ROUTINE_PRIORITIES 2)。

check 任务与 LED 状态反馈

check 任务(优先级tskIDLE_PRIORITY + 3)是这套示例的"健康监测员":它每隔约1 秒mainCHECK_PERIOD = 1000 / portTICK_PERIOD_MS)调用一次prvCheckOtherTasksAreStillRunning(),依次查询整数运算、串口通信、轮询队列、寄存器测试四类任务的状态查询函数(xAreIntegerMathsTaskStillRunning()xAreComTestTasksStillRunning()xArePollingQueuesStillRunning()xAreRegTestTasksStillRunning()),任何一项返回异常都会将xErrorHasOccurred置位并永远不再翻转 LED

因此板载 LED 的翻转行为就是系统状态的指示灯:

  • LED 约每秒翻转一次:check 任务未发现任何问题,所有任务运行正常;
  • LED 停止翻转:check 任务在某一个或多个任务中发现了错误。

串口控制台参数

若需查看串口通信任务的输出,串口(USART)应按如下参数配置:

参数取值
波特率9600
数据位8 bit
校验位none
停止位1 bit
流控none

对应源码中的mainCOM_TEST_BAUD_RATE定义为9600。底层串口驱动位于 serial/serial.c,是一个基于USART3的中断驱动驱动:

  • 波特率通过公式USART_BAUD_RATE(BAUD_RATE) = configCPU_CLOCK_HZ * 64 / (16 * BAUD_RATE)换算后写入USART3.BAUD
  • 接收完成中断USART3_RXC_vect中通过xQueueSendFromISR()把字符投递到接收队列,并在有更高优先级任务被唤醒时调用portYIELD_FROM_ISR()触发上下文切换;
  • 发送采用"数据寄存器空"中断(USART3_DRE_vect)驱动:任务调用xSerialPutChar()将字符放入发送队列并开中断,ISR 逐字符取出发送,队列空时关闭中断;
  • 硬件初始化init_minimal()中将 PB0 配置为 Tx、PB1 配置为 Rx(PORTB.DIR方向设置)。

Full Demo:FreeRTOS 特性的综合演示与测试

#define mainSELECTED_APPLICATION 2

Full Demo 是对 FreeRTOS 大量特性的综合演示与测试,覆盖任务间直接通知(direct task to task notifications)、队列、信号量、递归互斥量、软件定时器等机制。从 main_full.c 可以看到它创建的任务:

void main_full( void ) { vStartSemaphoreTasks( mainSEM_TEST_PRIORITY ); /* 信号量测试任务 */ vStartTaskNotifyTask(); /* 任务通知测试任务 */ vStartRegTestTasks(); /* 寄存器上下文切换测试 */ vStartRecursiveMutexTasks(); /* 递归互斥量测试任务 */ xTaskCreate( prvCheckTask, "Check", configMINIMAL_STACK_SIZE, NULL, mainCHECK_TASK_PRIORITY, NULL ); vTaskStartScheduler(); }

各测试任务的完整实现位于 FreeRTOS 仓库的公共演示代码中:

  • 信号量测试:Semtest.c(队列信号量/互斥量相关);
  • 任务通知测试:FreeRTOS/Demo/Common/Minimal/TaskNotify.c(测试任务通知的位操作、数值累加、通知等待等行为,涉及ULONG_MAX环绕、挂起/恢复等边界场景);
  • 寄存器测试:与 Minimal Demo 共用 regtest.c;
  • 递归互斥量测试:Recmutex.c(对应配置configUSE_RECURSIVE_MUTEXES 1,其任务栈大小宏recmuRECURSIVE_MUTEX_TEST_TASK_STACK_SIZE定义为configMINIMAL_STACK_SIZE * 2)。

check 任务与 LED 状态反馈

与 Minimal Demo 的机制相同,Full Demo 也使用check 任务(优先级为configMAX_PRIORITIES - 1,即最高优先级)周期巡检所有任务。区别在于巡检周期更长(约3 秒mainCHECK_PERIOD = 3000 / portTICK_PERIOD_MS),且错误记录采用位标志方式——每类任务对应一个独立的位:

if( xAreSemaphoreTasksStillRunning() != pdTRUE ) ulErrorFound |= 1UL << 0UL; if( xAreTaskNotificationTasksStillRunning() != pdTRUE ) ulErrorFound |= 1UL << 1UL; if( xAreRegTestTasksStillRunning() != pdTRUE ) ulErrorFound |= 1UL << 2UL; if( xAreRecursiveMutexTasksStillRunning() != pdTRUE ) ulErrorFound |= 1UL << 3UL; if( ulErrorFound == pdFALSE ) vParTestToggleLED( mainCHECK_TASK_LED ); /* LED5 */

LED 反馈规则:

  • LED 约每 3 秒翻转一次:check 任务未发现任何问题;
  • LED 停止翻转:check 任务在某个或多个任务中发现了错误。

中断与任务通知的联动

Full Demo 还有一个值得注意的设计:在 main_full.c 中实现了vApplicationTickHook()(tick 钩子,由内核在每个系统节拍中断中调用),其中调用xNotifyTaskFromISR()——这正是演示"从 ISR 向任务发送任务通知"这一机制的入口,让任务通知测试能够在中断上下文中被驱动,验证configUSE_TICK_HOOK 1配置下的 ISR 通知路径。

快速上手指南

要在 AVR128DA48 Curiosity Nano 平台上运行本演示工程,按以下步骤操作:

  1. 安装MPLAB® X IDE(参考资料建议 5.40 或更新版本);
  2. 安装MPLAB® XC8编译器(参考资料建议 2.20 或更新版本);
  3. 在 MPLAB X IDE 中打开工程文件:FreeRTOS/Demo/AVR_ATMega4809_MPLAB.X目录下的工程(工程目录包含由 MPLAB X 自动管理的 Makefile,实际构建逻辑由nbproject/Makefile-impl.mkMakefile-variables.mk提供,支持buildcleanclobberall等标准目标);
  4. 按上文说明,在 main.c 中通过#define mainSELECTED_APPLICATION选择期望的 Demo(0 = Blinky,1 = Minimal,2 = Full);
  5. 构建(build)并将固件烧录/调试(debug)到目标板。

版本与硬件要求(来自原文档 References)

运行本工程需要以下环境与硬件:

  • MPLAB® X IDE 5.40 或更新版本
  • MPLAB® XC8 2.20 或更新版本
  • 目标器件为 AVR128DA48(Curiosity Nano 板载),参考资料同时覆盖 Atmega4809、Atmega4808、Atmega3208、Atmega3209 等 AVR 器件页面,可按需选用同一系列的开发板。

说明:工程目录名沿用了AVR_ATMega4809,而 ReadMe 正文的目标硬件为 AVR128DA48 Curiosity Nano(128 KB Flash / 16 KB SRAM / 512 B EEPROM),参考资料亦同时列出 megaAVR-0 系列器件,使用时请以实际板卡型号为准,并参照 FreeRTOS 官网对应 RTOS port 文档页完成目标硬件搭建、固件下载与运行。

调试钩子:栈溢出与内存分配失败处理

main.c 中还预置了两个可选的调试钩子,方便开发阶段排查问题:

  • vApplicationStackOverflowHook:栈溢出时被内核调用(需要将FreeRTOSConfig.h中的configCHECK_FOR_STACK_OVERFLOW设为1)。钩子进入临界区后死循环等待,调试器可将局部变量ulSetToNonZeroInDebuggerToContinue改为非零值以继续执行;
  • vApplicationMallocFailedHook:动态内存分配失败时被调用(需要configUSE_MALLOC_FAILED_HOOK设为1),处理方式同上。

这两个钩子当前在 FreeRTOSConfig.h 中对应配置均为关闭状态(configCHECK_FOR_STACK_OVERFLOW 0configUSE_MALLOC_FAILED_HOOK 0),但代码已就绪,按需打开即可在开发期获得故障定位能力。

小结

AVR_ATMega4809_MPLAB.X演示工程以"一个工程、三档 Demo"的方式,为 AVR 平台的 FreeRTOS 入门到进阶提供了循序渐进的路径:

  • BlinkymainSELECTED_APPLICATION 0):任务 + 队列的最小范例,验证工具链与调度器是否正常工作;
  • MinimalmainSELECTED_APPLICATION 1):整数运算、寄存器测试、轮询队列、串口通信、协程与 check 任务的组合演示,验证上下文切换与多种内核对象;
  • FullmainSELECTED_APPLICATION 2):信号量、任务通知、递归互斥量等特性的综合压力测试。

三套 Demo 共用同一套check 任务 + LED 翻转的健康反馈机制(Minimal 每 1 秒、Full 每 3 秒),通过 LED 是否持续翻转即可直观判断系统是否运行无误,是理解 FreeRTOS 任务调度、内核对象与 AVR 移植层配合的优秀参考实现。更完整的上下文切换压力测试(寄存器级验证)可继续研读 regtest.c,内核任务通知的深度测试则见 FreeRTOS/Demo/Common/Minimal/TaskNotify.c。

【免费下载链接】FreeRTOS'Classic' FreeRTOS distribution. Started as Git clone of FreeRTOS SourceForge SVN repo. Submodules the kernel.项目地址: https://gitcode.com/GitHub_Trending/fr/FreeRTOS

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询