FreeRTOS ATMega4809 IAR 演示项目实战指南:Blinky / Minimal / Full 三档示例解析与源码级运行原理
2026/9/16 22:22:45 网站建设 项目流程

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 Demo0main_blinky.c面向初学者的入门示例
Minimal Demo1main_minimal.c多任务组合的自检示例
Full Demo2main_full.cFreeRTOS 特性全面演示

切换方式是在 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)。

两个关键内核行为:

  1. 精确周期发送:发送任务用vTaskDelayUntil( &xNextWakeTime, mainQUEUE_SEND_FREQUENCY_MS )(main_blinky.c)实现固定频率唤醒。与vTaskDelay不同,vTaskDelayUntil以绝对时间点为基准,不会因任务调度抖动累积漂移。
  2. 无限期阻塞接收:接收任务用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 = 5mainCHECK_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_t

Tick 定时器选择(FreeRTOSConfig.h):configUSE_TIMER_INSTANCE选择用于系统 tick 的定时器实例,0~3 对应 TCB0~TCB3,4 对应 RTC;同时configCALL_STACK_SIZE 30指定调用栈深度。

时钟与调度(FreeRTOSConfig.h):

配置项含义
configUSE_PREEMPTION1抢占式调度
configCPU_CLOCK_HZ10000000 (10 MHz)CPU 时钟频率
configTICK_RATE_HZ1000tick 频率(1 ms)
configMAX_PRIORITIES4最大优先级数
configMINIMAL_STACK_SIZE110最小任务栈(字)
configMAX_TASK_NAME_LEN8任务名最大长度
configUSE_16_BIT_TICKS116 位 tick 计数

configCPU_CLOCK_HZ可选值有 20 MHz、10 MHz、5 MHz、2 MHz(FreeRTOSConfig.h),选择其他频率需自行修改时钟配置。

内存管理(FreeRTOSConfig.h):configSUPPORT_DYNAMIC_ALLOCATION 1configTOTAL_HEAP_SIZE 0x1000(4 KB 堆),配合configAPPLICATION_ALLOCATED_HEAP 0使用默认静态堆数组。

钩子函数(FreeRTOSConfig.h):configUSE_IDLE_HOOK 1configUSE_TICK_HOOK 1,而configCHECK_FOR_STACK_OVERFLOW 0configUSE_MALLOC_FAILED_HOOK 0。若需栈溢出检测,可在 main.c 中取消vApplicationStackOverflowHook注释并将configCHECK_FOR_STACK_OVERFLOW置 1;若需内存分配失败检测,则取消vApplicationMallocFailedHook注释并将configUSE_MALLOC_FAILED_HOOK置 1。

协程与软件定时器(FreeRTOSConfig.h):configUSE_CO_ROUTINES 1configMAX_CO_ROUTINE_PRIORITIES 2configUSE_TIMERS 1,定时器任务优先级configMAX_PRIORITIES - 1,定时器队列长度 5,定时器任务栈深度configMINIMAL_STACK_SIZE * 2

功能裁剪(FreeRTOSConfig.h):通过INCLUDE_xxx宏精确控制可链接的 API,例如INCLUDE_vTaskSuspend 1(供portMAX_DELAY无限期阻塞使用)、INCLUDE_vTaskDelayUntil 1INCLUDE_vTaskPrioritySet 1,同时关闭了vTaskDeletexTaskAbortDelay等以节省 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 上运行本演示需要:

  1. 安装IAR Embedded Workbench for AVR
  2. 打开当前目录下的RTOSDemo.eww工作区文件(RTOSDemo.eww);
  3. 按上文说明修改 main.c 中#define mainSELECTED_APPLICATION选择目标演示(0/1/2);
  4. 编译并调试下载到开发板。

运行结果判读:

  • 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),仅供参考

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

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

立即咨询