FreeRTOS ATMega4809 IAR 演示项目实战指南:Blinky / Minimal / Full 三档示例解析与源码级运行原理
【免费下载链接】FreeRTOS'Classic' FreeRTOS distribution. Started as Git clone of FreeRTOS SourceForge SVN repo. Submodules the kernel.项目地址: https://gitcode.com/GitHub_Trending/fr/FreeRTOS
导读
本文以 FreeRTOS/Demo/AVR_ATMega4809_IAR/readme.md 为主体,系统讲解面向 Microchip ATMega4809 Curiosity Nano 开发板的 IAR EWAVR 演示工程(48 KB Flash、6 KB SRAM、256 B EEPROM)。该工程通过 main.c 中一个宏即可在三个演示间切换,分别覆盖"入门队列通信""多任务组合与自检""FreeRTOS 全特性压测"三个层次。读完本文,你将掌握每个演示的任务构成、check 任务的自检与 LED 反馈机制、串口控制台参数、时钟与 FreeRTOS 内核配置,以及从源码层面理解演示背后 FreeRTOS 的调度、队列、信号量与任务通知实现。
工程总览:一个宏切换三种演示
该目录是一个完整的 IAR Embedded Workbench for AVR(EWAVR)工程,包含 RTOSDemo.eww 工作区及配套的.ewp/.ewd/.ewt工程、调试与 trace 文件。三个演示各自拥有独立的main-***.c文件:
| 演示 | 宏值 | 主文件 | 定位 |
|---|---|---|---|
| Blinky Demo | 0 | main_blinky.c | 面向初学者的入门示例 |
| Minimal Demo | 1 | main_minimal.c | 多任务组合的自检示例 |
| Full Demo | 2 | main_full.c | FreeRTOS 特性全面演示 |
切换方式是在 main.c 中修改:
#define mainSELECTED_APPLICATION 0 /* 0 = Blinky, 1 = Minimal, 2 = Full */main.c 依据宏值分别extern对应演示的main_xxx()与init_xxx(),并在#else分支通过#error Invalid mainSELECTED_APPLICATION setting.拦截非法取值。程序入口流程为:
int main( void ) { prvSetupHardware(); /* 关中断 -> CLK_init() -> init_xxx() */ main_blinky(); /* 或 main_minimal() / main_full() */ return 0; }硬件初始化阶段(main.c)先调用portDISABLE_INTERRUPTS()确保调度器启动前没有中断打断初始化流程(中断会在调度器启动时自动使能),随后由 clk_config.h 的CLK_init()完成时钟配置,最后调用各演示的init_xxx()初始化 LED、UART 等外设。
Blinky Demo:队列驱动的经典入门示例
任务与交互模型
Blinky 演示只创建一个队列和两个任务,全部逻辑集中在 main_blinky.c:
- 队列发送任务
prvQueueSendTask("TX"):以 200 ms 周期向队列发送预定义值100UL; - 队列接收任务
prvQueueReceiveTask("Rx"):阻塞等待队列消息,收到后校验是否为期望值,若是则翻转板载 LED(PF5 引脚)。
两个任务的优先级定义(main_blinky.c)为:
#define mainQUEUE_RECEIVE_TASK_PRIORITY ( tskIDLE_PRIORITY + 2 ) #define mainQUEUE_SEND_TASK_PRIORITY ( tskIDLE_PRIORITY + 1 )发送周期与队列容量(main_blinky.c):
#define mainQUEUE_SEND_FREQUENCY_MS ( 200 / portTICK_PERIOD_MS ) #define mainQUEUE_LENGTH ( 1 )源码级运行原理
队列在main_blinky()中创建(main_blinky.c):
xQueue = xQueueCreate( mainQUEUE_LENGTH, sizeof( uint32_t ) );队列容量为 1,因为接收任务会及时取走消息,发送任务写队列时队列通常为空,因此xQueueSend( xQueue, &ulValueToSend, 0U )使用 0 阻塞时间即可成功(main_blinky.c)。
两个关键内核行为:
- 精确周期发送:发送任务用
vTaskDelayUntil( &xNextWakeTime, mainQUEUE_SEND_FREQUENCY_MS )(main_blinky.c)实现固定频率唤醒。与vTaskDelay不同,vTaskDelayUntil以绝对时间点为基准,不会因任务调度抖动累积漂移。 - 无限期阻塞接收:接收任务用
xQueueReceive( xQueue, &ulReceivedValue, portMAX_DELAY )(main_blinky.c)等待数据,进入 Blocked 态直至队列有消息。这正是 FreeRTOSConfig.h 中INCLUDE_vTaskSuspend 1所要求的支持。
LED 控制直接操作寄存器(main_blinky.c):
void init_blinky( void ) { PORTF.DIRSET = PIN5_bm; /* 将 PF5 配置为输出 */ }接收任务校验成功后通过PORTF.OUTTGL |= PIN5_bm翻转 LED(main_blinky.c)。因此,若一切正常,LED 会以约 200 ms 的频率闪烁——这正是队列消息 200 ms 到达一次的直接体现。
Minimal Demo:多任务协作与 check 自检机制
任务构成
Minimal 演示在 main_minimal.c 中组合了多种标准演示任务,覆盖不同类型的 FreeRTOS 功能验证:
| 任务类型 | 源文件 | 验证内容 |
|---|---|---|
| 整数运算任务 | Integer.c(Common 目录) | 32 位整数运算与寄存器使用 |
| 寄存器校验任务 | regtest.c | 上下文切换时寄存器现场保护 |
| 轮询队列任务 | PollQ.c(Common 目录) | 队列轮询读写 |
| 串口通信任务 | serial/serial.c | 中断驱动串口收发 |
| check 任务 | main_minimal.c 内实现 | 周期巡检上述任务运行状态 |
创建代码(main_minimal.c):
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 );其中串口测试波特率定义为mainCOM_TEST_BAUD_RATE = 9600(main_minimal.c),LED 编号与巡检周期为mainCHECK_TASK_LED = 5、mainCHECK_PERIOD = 1000 / portTICK_PERIOD_MS(约 1 秒)。init_minimal()将 PB0(Rx)与 PB1(Tx)配置为 UART 引脚并调用vParTestInitialise()(main_minimal.c)。
此外,Minimal 演示还包含两个细节:
- EEPROM 复位计数:
prvIncrementResetCount()在 EEPROM 地址0x1400处自增复位计数(main_minimal.c),用于检测程序是否发生意外复位; - 协程演示:
vStartFlashCoRoutines()创建 3 个 LED 闪烁协程(main_minimal.c),并通过空闲钩子vApplicationIdleHook()中的vCoRoutineSchedule()驱动协程调度(main_minimal.c)。
check 任务自检逻辑
check 任务每次巡检调用各任务暴露的xAre***StillRunning()查询函数(main_minimal.c),任一返回非pdTRUE即置位xErrorHasOccurred,此后 LED 不再翻转:
if( xAreIntegerMathsTaskStillRunning() != pdTRUE ) xErrorHasOccurred = pdTRUE; if( xAreComTestTasksStillRunning() != pdTRUE ) xErrorHasOccurred = pdTRUE; if( xArePollingQueuesStillRunning() != pdTRUE ) xErrorHasOccurred = pdTRUE; if( xAreRegTestTasksStillRunning() != pdTRUE ) xErrorHasOccurred = pdTRUE; if( xErrorHasOccurred == pdFALSE ) { vParTestToggleLED( mainCHECK_TASK_LED ); }运行判据:LED 约每 1 秒翻转一次表示所有任务正常;LED 停止翻转说明至少一个任务检测到错误。check 任务本身还会执行一次ulDummyVariable *= 3的 32 位运算,以辅助"锻炼"整数任务所依赖的寄存器路径(main_minimal.c)。
串口控制台配置
要观察串口通信任务的输出,串口终端需配置为:
| 参数 | 值 |
|---|---|
| 波特率 | 9600 |
| 数据位 | 8 |
| 校验 | 无 |
| 停止位 | 1 |
| 流控 | 无 |
底层串口驱动见 serial/serial.c,采用 USART3 中断驱动:xSerialPutChar()将字符压入发送队列并使能 DRE 中断(serial.c),USART3_RXC_handler/USART3_DRE_handler两个中断服务程序通过xQueueSendFromISR/xQueueReceiveFromISR与任务层交换数据,并在唤醒高优先级任务时调用portYIELD_FROM_ISR()触发上下文切换(serial.c)。波特率寄存器由宏USART_BAUD_RATE(BAUD_RATE)依据configCPU_CLOCK_HZ计算得出(serial.c)。
Full Demo:FreeRTOS 特性全面压测
任务构成
Full 演示是综合性的功能与压力测试,覆盖直接任务通知、队列、信号量、递归信号量、软件定时器等特性,任务创建见 main_full.c:
| 任务类型 | 源文件 | 验证内容 |
|---|---|---|
| 寄存器校验任务 | regtest.c | 上下文切换寄存器保护 |
| 信号量任务 | Semtest.c(Common 目录) | 二值/计数信号量 |
| 任务通知任务 | TaskNotify.c(Common 目录) | 直接任务到任务通知 |
| 递归信号量任务 | RecMutex.c(Common 目录) | 递归互斥量 |
| check 任务 | main_full.c 内实现 | 周期巡检上述任务 |
vStartSemaphoreTasks( mainSEM_TEST_PRIORITY ); vStartTaskNotifyTask(); vStartRegTestTasks(); vStartRecursiveMutexTasks(); xTaskCreate( prvCheckTask, "Check", configMINIMAL_STACK_SIZE, NULL, mainCHECK_TASK_PRIORITY, NULL ); vTaskStartScheduler();check 任务优先级设为configMAX_PRIORITIES - 1(main_full.c),即最高优先级;巡检周期为3000 / portTICK_PERIOD_MS约 3 秒(main_full.c)。
check 任务与错误位掩码
Full 演示的 check 任务用位掩码ulErrorFound分别记录各任务组的故障(main_full.c):
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 ); /* LED 编号 5 */ }运行判据:LED 约每 3 秒翻转一次表示全部正常;一旦任何任务出错,LED 永久停止翻转。该设计让开发者无需串口或调试器也能通过肉眼判断系统是否健康。
Tick 钩子驱动任务通知
Full 演示额外实现了vApplicationTickHook()(main_full.c),在每个 tick 中断中调用xNotifyTaskFromISR(),用于从 ISR 上下文向任务通知测试任务发送通知,验证"任务通知 + ISR"组合路径。该钩子的启用依赖于 FreeRTOSConfig.h 中configUSE_TICK_HOOK 1。
硬件与时钟配置:FreeRTOSConfig.h 与 clk_config.h
内核关键配置
FreeRTOSConfig.h 是移植配置核心,以下几点值得注意:
TCB 类型冲突处理(FreeRTOSConfig.h):AVR 的 Timer Counter B 外设类型TCB_t与 FreeRTOS 任务控制块类型同名,此工程通过宏重定义规避冲突:
#define TCB_t avrTCB_t #include <ioavr.h> #undef TCB_tTick 定时器选择(FreeRTOSConfig.h):configUSE_TIMER_INSTANCE选择用于系统 tick 的定时器实例,0~3 对应 TCB0~TCB3,4 对应 RTC;同时configCALL_STACK_SIZE 30指定调用栈深度。
时钟与调度(FreeRTOSConfig.h):
| 配置项 | 值 | 含义 |
|---|---|---|
configUSE_PREEMPTION | 1 | 抢占式调度 |
configCPU_CLOCK_HZ | 10000000 (10 MHz) | CPU 时钟频率 |
configTICK_RATE_HZ | 1000 | tick 频率(1 ms) |
configMAX_PRIORITIES | 4 | 最大优先级数 |
configMINIMAL_STACK_SIZE | 110 | 最小任务栈(字) |
configMAX_TASK_NAME_LEN | 8 | 任务名最大长度 |
configUSE_16_BIT_TICKS | 1 | 16 位 tick 计数 |
configCPU_CLOCK_HZ可选值有 20 MHz、10 MHz、5 MHz、2 MHz(FreeRTOSConfig.h),选择其他频率需自行修改时钟配置。
内存管理(FreeRTOSConfig.h):configSUPPORT_DYNAMIC_ALLOCATION 1、configTOTAL_HEAP_SIZE 0x1000(4 KB 堆),配合configAPPLICATION_ALLOCATED_HEAP 0使用默认静态堆数组。
钩子函数(FreeRTOSConfig.h):configUSE_IDLE_HOOK 1、configUSE_TICK_HOOK 1,而configCHECK_FOR_STACK_OVERFLOW 0、configUSE_MALLOC_FAILED_HOOK 0。若需栈溢出检测,可在 main.c 中取消vApplicationStackOverflowHook注释并将configCHECK_FOR_STACK_OVERFLOW置 1;若需内存分配失败检测,则取消vApplicationMallocFailedHook注释并将configUSE_MALLOC_FAILED_HOOK置 1。
协程与软件定时器(FreeRTOSConfig.h):configUSE_CO_ROUTINES 1、configMAX_CO_ROUTINE_PRIORITIES 2;configUSE_TIMERS 1,定时器任务优先级configMAX_PRIORITIES - 1,定时器队列长度 5,定时器任务栈深度configMINIMAL_STACK_SIZE * 2。
功能裁剪(FreeRTOSConfig.h):通过INCLUDE_xxx宏精确控制可链接的 API,例如INCLUDE_vTaskSuspend 1(供portMAX_DELAY无限期阻塞使用)、INCLUDE_vTaskDelayUntil 1、INCLUDE_vTaskPrioritySet 1,同时关闭了vTaskDelete、xTaskAbortDelay等以节省 6 KB SRAM 中宝贵的 RAM。
毫秒换算宏(FreeRTOSConfig.h):
#define pdMS_TO_TICKS(xTimeInMs) ((TickType_t)(((uint32_t)(xTimeInMs) * (uint32_t)configTICK_RATE_HZ) / (uint32_t)1000))时钟初始化实现
clk_config.h 依据configCPU_CLOCK_HZ通过条件编译生成CLK_init():时钟源统一选择内部 20 MHz 振荡器(CLKCTRL_CLKSEL_OSC20M_gc),再通过MCLKCTRLB的分频字段得到目标频率,例如 10 MHz 对应CLKCTRL_PDIV_2X_gc | CLKCTRL_PEN_bm(2 分频)。写时钟控制寄存器必须通过ccp_write_io()(来自 protected_io/ccp.h,实现见 protected_io.S)完成"受保护写"序列,否则写操作无效——这是 megaAVR 0 系列对关键寄存器的一贯保护机制。非法频率会触发#error编译期报错。
LED 抽象层:ParTest 的实现
ParTest.c 提供 LED 操作抽象,是 check 任务可视反馈的基础。初始化将 PORTF 全部 8 位配置为输出并清零(ParTest.c):
void vParTestInitialise( void ) { PORTF.DIRSET = partstALL_BITS_OUTPUT; PORTF.OUTCLR = partstALL_OUTPUTS_OFF; }vParTestSetLED()/vParTestToggleLED()支持 LED 编号 0~7(partstMAX_OUTPUT_LED),且对寄存器操作包裹了vTaskSuspendAll()/xTaskResumeAll()临界区(ParTest.c),避免多任务并发访问 GPIO 寄存器造成竞争。这也解释了为什么各演示的 check 任务选择 LED 编号 5——该编号在partstMAX_OUTPUT_LED范围内且与 Curiosity Nano 板载 LED(PF5)对应。
上下文切换验证:regtest 的汇编级自检
regtest.c 同时被 Minimal 与 Full 演示使用,是验证 AVR 移植正确性的关键。其原理极具针对性:两个同优先级任务("Reg1"/"Reg2")分别用内联汇编把不同已知值写入 r0~r30 全部通用寄存器(regtest.c),然后调用taskYIELD()让出 CPU 触发上下文切换,恢复执行后用CPI/CPSE逐寄存器比对(regtest.c),任何寄存器值被破坏都会将全局标志xRegTestError置非零,进而被 check 任务通过xAreRegTestTasksStillRunning()捕获。
这个测试对 8 位 AVR 移植尤为关键:由于寄存器窗口窄,上下文切换必须完整保存/恢复全部工作寄存器;两任务使用不同数值(Reg1 用 5~35,Reg2 用 1~31),可同时检测"寄存器恢复错位"与"值混淆"两类问题。
快速上手:构建与运行步骤
按 readme 的 Quick start 指引,在 ATMega4809 Curiosity Nano 上运行本演示需要:
- 安装IAR Embedded Workbench for AVR;
- 打开当前目录下的RTOSDemo.eww工作区文件(RTOSDemo.eww);
- 按上文说明修改 main.c 中
#define mainSELECTED_APPLICATION选择目标演示(0/1/2); - 编译并调试下载到开发板。
运行结果判读:
- Blinky(0):LED 约 200 ms 翻转一次(PF5);
- Minimal(1):LED 约 1 s 翻转一次表示自检通过;同时可通过波特率 9600、8N1 的串口观察控制台输出;
- Full(2):LED 约 3 s 翻转一次表示全部特性任务运行正常。
无论哪个演示,LED 停止翻转都意味着 check 任务发现了错误——这是这套示例工程最有价值的工程实践:以极低成本的外设反馈实现系统级健康监控。
结语
AVR_ATMega4809_IAR 演示工程体现了 FreeRTOS 经典示例的层次化设计:Blinky 让初学者在最小代码量内理解队列与任务;Minimal 将整数运算、寄存器校验、轮询队列、串口通信与协程组合在一起,配合 check 任务形成可观测的自检系统;Full 则覆盖信号量、递归互斥量、任务通知与软件定时器等内核特性,用于移植验证与性能摸底。三个示例共享同一套 FreeRTOSConfig.h 配置,可通过一个宏自由切换,是学习 FreeRTOS 在 8 位 AVR 平台上移植与应用的理想起点。
【免费下载链接】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),仅供参考