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源文件:
| 宏取值 | 演示名称 | 入口源文件 | 定位 |
|---|---|---|---|
0 | Blinky Demo | main_blinky.c | 面向初学者的最小示例,仅两个任务 + 一个队列 |
1 | Minimal Demo | main_minimal.c | 任务数较多但复杂度仍较低的功能演示 |
2 | Full Demo | main_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_HZ为1000(即 1 kHz 系统节拍);configMAX_PRIORITIES为4,configMINIMAL_STACK_SIZE为110字(AVR 平台为 8 位 MCU,栈以字节为单位考量);configUSE_16_BIT_TICKS为1(AVR 8 位平台上 TickType_t 使用 16 位,节省 RAM);- 内存管理采用动态分配(
configSUPPORT_DYNAMIC_ALLOCATION 1),堆大小configTOTAL_HEAP_SIZE为0x1000(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(); }该示例还包含两个值得注意的细节:
- EEPROM 复位计数:
prvIncrementResetCount()在启动时从地址0x1400读取复位计数并加 1 写回,用于检测 CPU 是否发生意外复位; - 协程(Co-routine)演示:创建 3 个 LED 闪烁协程(
vStartFlashCoRoutines),并且空闲钩子vApplicationIdleHook()中调用vCoRoutineSchedule()驱动协程调度(对应的内核配置configUSE_CO_ROUTINES 1、configMAX_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 平台上运行本演示工程,按以下步骤操作:
- 安装MPLAB® X IDE(参考资料建议 5.40 或更新版本);
- 安装MPLAB® XC8编译器(参考资料建议 2.20 或更新版本);
- 在 MPLAB X IDE 中打开工程文件:
FreeRTOS/Demo/AVR_ATMega4809_MPLAB.X目录下的工程(工程目录包含由 MPLAB X 自动管理的 Makefile,实际构建逻辑由nbproject/Makefile-impl.mk与Makefile-variables.mk提供,支持build、clean、clobber、all等标准目标); - 按上文说明,在 main.c 中通过
#define mainSELECTED_APPLICATION选择期望的 Demo(0 = Blinky,1 = Minimal,2 = Full); - 构建(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 0、configUSE_MALLOC_FAILED_HOOK 0),但代码已就绪,按需打开即可在开发期获得故障定位能力。
小结
AVR_ATMega4809_MPLAB.X演示工程以"一个工程、三档 Demo"的方式,为 AVR 平台的 FreeRTOS 入门到进阶提供了循序渐进的路径:
- Blinky(
mainSELECTED_APPLICATION 0):任务 + 队列的最小范例,验证工具链与调度器是否正常工作; - Minimal(
mainSELECTED_APPLICATION 1):整数运算、寄存器测试、轮询队列、串口通信、协程与 check 任务的组合演示,验证上下文切换与多种内核对象; - Full(
mainSELECTED_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),仅供参考