尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

STM32移植FreeRTOS实战:从CubeMX配置到多任务调度详解

STM32移植FreeRTOS实战:从CubeMX配置到多任务调度详解 1. 项目概述为什么要在STM32上跑FreeRTOS如果你正在玩STM32从点灯、串口打印玩到需要同时处理按键、屏幕刷新和网络通信很快就会发现裸机编程那套while(1)加状态机的路子有点不够用了。这时候一个实时操作系统RTOS就成了刚需。在嵌入式RTOS领域FreeRTOS凭借其开源、免费、体量小、社区活跃的特点几乎是STM32开发者的首选。它能让你的单片机像电脑一样“同时”运行多个任务管理起来井井有条。所谓“移植”就是把FreeRTOS这颗“大脑”安装到你的STM32芯片上让它能正确识别硬件比如系统时钟、中断并顺利启动和调度任务。这个过程听起来有点底层但实际操作起来借助成熟的开发工具如STM32CubeMX和清晰的步骤新手也能在半小时内搞定。这篇文章我就以一个STM32F103C8T6也就是常说的“蓝桥杯”或“最小系统板”核心芯片为例手把手带你走一遍FreeRTOS移植到STM32的完整流程并拆解其中每一个关键步骤背后的原理和容易踩的坑。无论你是用Keil MDK还是STM32CubeIDE思路都是相通的。2. 移植前的核心准备工具与工程解析在动手写代码之前准备工作做得好能省去后面一大堆莫名其妙的错误。这里主要分三块硬件目标确认、软件工具链准备和源码获取。2.1 硬件目标与开发环境确认首先明确你的硬件平台。我以STM32F103C8T6为例它基于ARM Cortex-M3内核这是FreeRTOS官方明确支持的内核架构所以移植基础很好。你需要确认自己使用的开发板或核心板型号以及其主频对于F103通常是72MHz。这关系到后续系统时钟节拍SysTick的配置。开发环境我主要介绍两种最主流的Keil MDK-ARM (uVision5)经典商业软件在国内使用广泛。你需要确保安装了对应你芯片型号的Device Family PackDFP。STM32CubeIDE意法半导体官方的免费集成开发环境基于Eclipse和GCC集成了STM32CubeMX图形化配置工具对新手非常友好也是本文推荐的方式。无论哪种环境STM32CubeMX这个工具都至关重要。它是一个图形化的引脚和中间件配置工具可以自动生成芯片初始化代码HAL库或LL库并且内置了FreeRTOS的中间件支持能极大简化我们的移植工作。请务必从ST官网下载并安装它。2.2 FreeRTOS源码获取与目录结构解读接下来是获取FreeRTOS源码。最稳妥的方式是去FreeRTOS的官网或GitHub仓库下载最新稳定版本。解压后你会看到一堆文件夹对于移植来说我们主要关心以下核心部分FreeRTOS/Source/这是核心源码目录。include/所有头文件都在这里定义了API、数据类型和内核数据结构。根目录下的.c文件如tasks.c,queue.c,list.c,timers.c等这是内核的核心实现。移植时这些文件通常需要全部添加到你的工程中。portable/这是移植的关键所在这个目录包含了针对不同编译器和处理器内核的移植层代码。portable/[Compiler]/比如portable/GCC/用于CubeIDE、portable/RVDS/用于Keil ARMCC。我们需要的“端口”文件就在这里。portable/MemMang/内存管理实现。里面有heap_1.c到heap_5.c等几个文件代表了不同的动态内存分配策略。对于刚入门使用heap_4.c是最通用和推荐的选择它支持内存碎片整理。理解这个结构很重要内核核心代码tasks.c等是平台无关的而portable下的文件则是连接内核与具体硬件编译器、CPU的桥梁。我们的移植工作很大一部分就是正确配置和使用这个“桥梁”。2.3 工程框架的建立思路在开始用CubeMX生成代码前心里要对工程结构有个谱。一个典型的移植后工程目录建议如下YourProject/ ├── Core/ │ ├── Inc/ // 用户头文件 │ ├── Src/ // 用户源文件main.c在这里 │ └── Startup/ // 启动文件通常由CubeMX生成 ├── Drivers/ │ ├── CMSIS/ // Cortex微控制器软件接口标准 │ └── STM32F1xx_HAL_Driver/ // HAL库文件 ├── Middlewares/ │ └── Third_Party/ │ └── FreeRTOS/ │ ├── Source/ // FreeRTOS核心源码从官方拷贝过来 │ │ ├── include │ │ ├── portable/[Compiler]/[Arch]/ │ │ └── ... │ └── License/ // 许可证文件 └── [IDE Project Files] // 如 .ioc, .uvprojx, .project 等这个结构清晰地将芯片驱动、中间件和用户代码分离便于管理。接下来我们就用STM32CubeMX来搭建这个框架的骨架。3. 使用STM32CubeMX进行图形化配置与工程生成这是最直观、最高效的一步能自动化完成80%的移植基础工作。3.1 芯片选型与基础外设配置新建工程打开STM32CubeMX点击“New Project”在芯片选择器中输入你的型号例如STM32F103C8然后选中具体的STM32F103C8Tx点击“Start Project”。配置系统核心SYS在“Pinout Configuration”标签页左侧找到“System Core” - “SYS”。Debug根据你的调试器选择。如果使用ST-Link进行SWD调试这里选择Serial Wire。这一步非常重要否则可能无法调试。配置时钟RCC找到“System Core” - “RCC”。High Speed Clock (HSE)选择Crystal/Ceramic Resonator。这表示你使用外部高速晶振通常是8MHz这是保证系统时钟准确的基础。配置时钟树Clock Configuration点击顶部的“Clock Configuration”标签页。这里的目标是将系统时钟SYSCLK配置到芯片允许的最高频率对于F103是72MHz因为FreeRTOS的系统节拍器SysTick依赖于系统时钟更高的主频意味着更精细的时间片调度。通常路径是HSE (8MHz) - PLL输入 - 设置PLL倍频因子x9- PLL输出 (72MHz) - 作为SYSCLK。将HCLKAHB总线时钟设置为72MHzAPB1低速外设总线时钟最高36MHzAPB2高速外设总线时钟设为72MHz。注意时钟配置必须参考你的开发板原理图确认外部晶振频率。3.2 启用与配置FreeRTOS中间件这是核心步骤。在左侧的“Middleware”分类下找到“FREERTOS”。启用FreeRTOS将界面中的“Mode”从Disabled切换到CMSIS-V2。强烈建议使用CMSIS-V2接口这是ARM为RTOS定义的标准化API封装层比原生FreeRTOS API更规范且与STM32Cube生态结合更好。配置参数CMSIS_V2模式大部分配置已优化我们主要关注几个关键参数。TICK_RATE_HZ系统节拍频率。默认是1000Hz即1ms一个节拍。这个值决定了时间片调度和软件定时器的精度。1000Hz是一个通用值精度和开销平衡得很好。对于低功耗应用可以降低到100Hz。TOTAL_HEAP_SIZEFreeRTOS内核和任务堆栈使用的总堆大小。默认是3072字节3KB。对于STM32F103C8T6仅有20KB RAM这个值需要谨慎评估如果创建多个任务这个值很可能不够。建议初期可以设置为40964KB或61446KB后续根据任务实际情况调整。USE_PREEMPTION是否使用抢占式调度。务必保持Enabled这是RTOS的核心优势允许高优先级任务抢占低优先级任务。MAX_PRIORITIES最大优先级数。默认是56对于小型应用完全够用保持默认即可。MINIMAL_STACK_SIZE最小任务栈大小字。默认是128字对于32位系统就是128*4512字节。这是创建任务时栈大小的一个参考基线实际每个任务的栈需要根据其函数调用深度、局部变量大小单独估算并设置通常要比这个大。注意堆Heap大小的设置是移植初期最容易导致系统崩溃HardFault的原因之一。如果设置太小创建任务或队列时可能因分配不到内存而失败。一个简单的调试方法是在main.c的MX_FREERTOS_Init函数里创建任务后调用osThreadGetStackSpaceCMSIS-V2 API或查看uxTaskGetStackHighWaterMark原生API的返回值来监控栈空间使用情况确保有足够的余量比如20%以上。3.3 生成工程代码项目管理Project Manager点击顶部的“Project Manager”标签页。Project Name Location设置你的工程名和存储路径。Toolchain / IDE选择你使用的IDE例如MDK-ARM V5Keil或STM32CubeIDE。在“Code Generator”部分有一个关键选项Generate peripheral initialization as a pair of ‘.c/.h’ files per peripheral。建议勾选此项。这会将每个外设的初始化代码生成独立的文件使工程结构更清晰。生成代码点击右上角的“GENERATE CODE”。CubeMX会根据你的配置生成完整的初始化代码、HAL库驱动、FreeRTOS适配层以及一个基本的main.c和freertos.c框架。4. 工程整合与FreeRTOS源码移植生成了工程骨架后我们需要将完整的FreeRTOS源码整合进来并配置编译环境。4.1 源码文件的拷贝与工程添加拷贝源码在你的项目目录下例如Middlewares/Third_Party/创建FreeRTOS文件夹。将之前下载的FreeRTOS源码包中的FreeRTOS/Source目录下的所有内容除了演示项目Demo文件夹拷贝到这个新建的FreeRTOS文件夹下。最终结构应类似于前面提到的目录树。在IDE中添加文件组和包含路径Keil MDK在Project窗口右键“Target 1”选择“Add Group…”创建名为FreeRTOS_CORE和FreeRTOS_PORTABLE的组。将FreeRTOS/Source根目录下的所有.c文件tasks.c,queue.c,list.c,timers.c,event_groups.c,stream_buffer.c添加到FreeRTOS_CORE组。将FreeRTOS/Source/portable/[Compiler]/ARM_CM3对于F103是CM3内核下的port.c文件添加到FreeRTOS_PORTABLE组。将FreeRTOS/Source/portable/MemMang下的heap_4.c推荐也添加到FreeRTOS_PORTABLE组。点击魔术棒图标Options for Target在“C/C”选项卡的“Include Paths”中添加以下路径../Middlewares/Third_Party/FreeRTOS/Source/include../Middlewares/Third_Party/FreeRTOS/Source/portable/[Compiler]/ARM_CM3STM32CubeIDE在Project Explorer中右键项目 -New-Folder创建FreeRTOS如果不存在。右键FreeRTOS文件夹 -Import-General-File System浏览到你的FreeRTOS源码目录选择需要添加的文件夹include,portable/GCC/ARM_CM3,portable/MemMang和核心.c文件导入到项目中相应位置。IDE通常会自动识别源文件。右键项目 -Properties-C/C Build-Settings-Tool Settings-MCU GCC Compiler-Include paths(-I)添加与Keil类似的包含路径。4.2 关键文件解析与适配port.c 与 FreeRTOSConfig.hport.c位于portable/[Compiler]/ARM_CM3/下。这个文件是移植层的核心它用汇编和C语言实现了与Cortex-M3内核相关的关键操作任务上下文切换PendSV_Handler中断服务程序用于在任务间切换时保存和恢复CPU寄存器上下文。系统节拍器SysTick_Handler中断服务程序提供RTOS的心跳。CubeMX生成的代码通常会重写这个中断将其与FreeRTOS的xPortSysTickHandler关联。启动第一个任务prvStartFirstTask函数通常用汇编编写用于从启动调度器后跳转到第一个任务的入口。开关中断的宏portENTER_CRITICAL()和portEXIT_CRITICAL()用于实现临界区保护。对于使用CubeMX生成的项目这个文件通常已经自动集成并配置好了我们一般不需要修改它。FreeRTOSConfig.h这是用户配置FreeRTOS的“总开关”文件至关重要CubeMX会在你项目的Core/Inc目录下生成这个文件。所有之前在CubeMX图形界面中配置的参数如configTICK_RATE_HZ,configTOTAL_HEAP_SIZE最终都体现在这个头文件的宏定义里。你可以直接打开这个文件查看和修改配置。重点检查项configUSE_PREEMPTION应为1。configUSE_TICKLESS_IDLE低功耗模式初学者可以先设为0。configCPU_CLOCK_HZ必须正确设置为你的系统时钟频率如72000000。configTICK_RATE_HZ检查是否为1000。configTOTAL_HEAP_SIZE确认大小是否足够。configMINIMAL_STACK_SIZE确认基准栈大小。configMAX_PRIORITIES优先级数量。一个常见问题如果使用了CMSIS-V2接口CubeMX生成的FreeRTOSConfig.h中会包含#include “cmsis_os2.h”并且很多配置是通过#define覆盖默认值的方式进行的。请确保这个文件被正确包含在编译链中。5. 编写与创建第一个多任务应用环境搭建好了现在来点实际的——创建两个简单的任务让它们交替运行验证移植是否成功。5.1 理解CubeMX生成的任务框架打开CubeMX生成的Core/Src/freertos.c文件或者直接在main.c中调用的MX_FREERTOS_Init函数。你会发现CubeMX已经为你创建了一个默认任务StartDefaultTask的框架。但为了理解本质我们不妨自己从头创建。在main.c中通常流程是这样的int main(void) { HAL_Init(); // 初始化HAL库 SystemClock_Config(); // 配置系统时钟 MX_GPIO_Init(); // 初始化GPIO等外设 MX_USART1_UART_Init(); // 初始化串口用于打印 MX_FREERTOS_Init(); // FreeRTOS初始化创建任务、队列等 osKernelStart(); // 启动FreeRTOS内核调度器从此CPU控制权交给RTOS while (1) { } // 理论上永远不会执行到这里 }osKernelStart()是CMSIS-V2的API对应原生FreeRTOS的vTaskStartScheduler()。一旦调用调度器就开始工作。5.2 创建自定义任务函数我们在main.c的/* USER CODE BEGIN PFP */区域定义两个任务函数/* Private function prototypes -----------------------------------------------*/ void StartTask01(void *argument); void StartTask02(void *argument);然后在/* USER CODE END PFP */之后实现它们void StartTask01(void *argument) { /* 任务初始化代码可以写在这里 */ for(;;) { HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); // 假设你配置了一个LED引脚叫LED osDelay(500); // 延迟500毫秒osDelay是CMSIS-V2的延时函数 // 注意在FreeRTOS任务中不要使用HAL_Delay因为它基于SysTick会被RTOS接管。 } } void StartTask02(void *argument) { /* 任务初始化代码可以写在这里 */ for(;;) { printf(Task02 is running...\r\n); // 假设已重定向printf到串口 osDelay(1000); // 延迟1000毫秒 } }关键点任务函数通常是一个无限循环for(;;)或while(1)。使用osDelay()进行延时这个函数会主动让出CPU给其他就绪任务是实现协作的基础。HAL_Delay()是忙等待会阻塞整个CPU在RTOS中禁止在任务里使用。5.3 在FreeRTOS初始化中创建任务找到Core/Src/freertos.c中的void MX_FREERTOS_Init(void)函数。在/* USER CODE BEGIN Init */和/* USER CODE END Init */之间或者直接在该函数末尾添加任务创建代码void MX_FREERTOS_Init(void) { /* 这里可能已有CubeMX生成的默认任务创建代码我们可以保留或删除 */ /* USER CODE BEGIN Init */ osThreadId_t taskHandle01, taskHandle02; // 任务句柄可用于后续控制任务 // 定义任务属性 const osThreadAttr_t task01_attributes { .name Task01, // 任务名字调试时有用 .stack_size 128 * 4, // 栈大小单位是字节。128字 * 4字节/字 512字节 .priority (osPriority_t) osPriorityNormal, // 优先级 }; const osThreadAttr_t task02_attributes { .name Task02, .stack_size 128 * 4, .priority (osPriority_t) osPriorityNormal, }; // 创建任务 taskHandle01 osThreadNew(StartTask01, NULL, task01_attributes); taskHandle02 osThreadNew(StartTask02, NULL, task02_attributes); if (taskHandle01 NULL || taskHandle02 NULL) { // 任务创建失败可能是堆内存不足这里可以添加错误处理比如点亮错误灯 Error_Handler(); } /* USER CODE END Init */ }这里使用的是CMSIS-V2的osThreadNewAPI。参数分别是任务函数指针、传递给任务的参数本例为NULL、任务属性结构体。osPriorityNormal是一个定义的常量通常对应一个中间优先级。5.4 编译、下载与现象验证编译工程确保没有错误和警告。常见的警告可能来自未使用的变量可以暂时忽略。连接硬件用ST-Link或J-Link连接开发板和电脑。下载程序点击IDE中的下载/调试按钮。观察现象如果连接了LED应该看到LED以1Hz的频率闪烁任务1控制。如果连接了串口助手波特率与代码中配置一致如115200应该看到每秒打印一次“Task02 is running...”。如果两个现象都有并且互不影响LED闪烁和串口打印节奏稳定恭喜你FreeRTOS已经在你的STM32上成功运行起来了两个任务正在被调度器公平地因为优先级相同调度执行。6. 移植深度解析与高级配置要点成功点亮第一个灯只是开始。要让FreeRTOS在项目中稳定可靠地运行还需要理解一些深层次配置和原理。6.1 系统时钟节拍SysTick与中断优先级配置FreeRTOS需要一个稳定的时基来驱动任务延时、时间片轮转和软件定时器。它默认使用Cortex-M内核的SysTick定时器。SysTick中断优先级在Cortex-M中中断优先级数值越小优先级越高。SysTick中断的优先级必须设置为最低优先级之一即数值较大以确保它不会阻塞其他硬件外设中断如串口、定时器。在FreeRTOSConfig.h中通过configKERNEL_INTERRUPT_PRIORITY来设置。对于CM3/CM4通常设置为15最低优先级。CubeMX通常会自动配置好。PendSV中断优先级用于上下文切换其优先级必须设置为最低通常与SysTick相同或更低。通过configPENDSV_INTERRUPT_PRIORITY设置。SVC中断优先级用于启动调度器优先级设置需参考手册通常也设为较低。一个黄金法则在FreeRTOS中所有内核管理的中断SysTick, PendSV, SVC优先级必须低于或等于configMAX_SYSCALL_INTERRUPT_PRIORITY或configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY。而你的应用程序中断如USART、TIM的优先级必须高于这个值。这样才能安全地在中断服务程序ISR中调用“FromISR”结尾的FreeRTOS API如xQueueSendFromISR。CubeMX在配置中断优先级时会帮你区分“内核可管理中断”和“用户中断”。6.2 内存管理方案Heap选择与优化前面我们选择了heap_4.c它是最通用的方案。了解不同方案有助于你在特定场景下优化heap_1.c只分配不释放。简单无碎片确定性好。适用于任务和内核对象在初始化时创建后永不删除的场景。heap_2.c支持分配和释放但使用最佳匹配算法会产生碎片。已不推荐使用。heap_3.c简单包装了标准的malloc()和free()。需要编译器提供堆实现。heap_4.c支持分配和释放使用首次适应算法并包含合并相邻空闲块的功能能有效减少碎片。是大多数项目的推荐选择。heap_5.c在heap_4基础上允许堆内存分布在多个不连续的内存区域。适用于有外部RAM或内存分区的复杂系统。堆大小估算实战如何确定configTOTAL_HEAP_SIZE一个粗略的估算方法是每个任务的栈空间在创建任务时指定如512字节。每个任务的控制块TCB大小约100字节。每个队列、信号量、互斥量等内核对象占用的内存。内核自身需要的一些管理开销。例如创建两个任务栈各512字节TCB各100字节再创建一个队列预留一些内核开销。初步可以估算为(512100)*2 200(队列等) 500(内核开销) ≈ 2000字节。但这只是静态估算。最可靠的方法是在调试时调用xPortGetFreeHeapSize()函数查看剩余堆大小确保在系统运行一段时间后仍有足够的空闲内存比如总堆的25%以上。6.3 任务栈空间分配与溢出检测任务栈溢出是RTOS调试中最头疼的问题之一会导致数据损坏、系统崩溃等随机性错误。栈大小设置在osThreadNew中指定的stack_size。设置太小会溢出设置太大会浪费宝贵的RAM。没有绝对标准需要测试。一个函数调用层次深、局部变量多的任务需要更大的栈。可以从一个较大的值如1024字开始运行所有可能的功能路径然后通过下面方法检查实际使用量。栈溢出检测方法FreeRTOS内置检测方法1在FreeRTOSConfig.h中将configCHECK_FOR_STACK_OVERFLOW设置为1或2。FreeRTOS会在任务切换时检查栈指针是否越界。如果检测到溢出会触发vApplicationStackOverflowHook钩子函数你可以在其中打印错误信息或复位系统。这是最推荐的方法。高水位线法方法2在任务运行时调用uxTaskGetStackHighWaterMark()原生API或osThreadGetStackSpace()CMSIS-V2。这个函数返回任务自创建以来栈空间历史最小剩余量以字为单位。如果这个值很小比如小于10就说明栈空间设置得太紧张了。你可以在调试阶段定期打印这个值来优化栈大小。7. 常见问题排查与调试技巧实录即使按照步骤操作也难免会遇到问题。这里记录几个我踩过的坑和解决方法。7.1 编译链接错误汇总错误现象可能原因解决方案undefined reference tovPortSVCHandler‘xPortPendSVHandler‘xPortSysTickHandler‘移植层文件port.c未添加到工程或包含路径错误。检查port.c和heap_x.c是否已正确添加到工程并确认FreeRTOSConfig.h和portable目录的包含路径已添加。osKernelStart或vTaskStartScheduler之后程序卡死或跑飞1. 堆内存configTOTAL_HEAP_SIZE不足创建任务失败。2. SysTick中断配置错误如时钟源、频率。3. 中断优先级配置冲突。1. 增大堆大小或在osThreadNew后检查返回值是否为NULL。2. 检查SystemClock_Config()是否正确HAL_SYSTICK_Config()是否被正确调用。3. 检查FreeRTOSConfig.h中关于中断优先级的宏定义并确保应用中断优先级高于configMAX_SYSCALL_INTERRUPT_PRIORITY。任务创建成功但调度器启动后只有一个任务运行任务优先级设置错误所有任务优先级相同且没有调用能引起调度的API如osDelay。确保任务函数内有osDelay、osThreadYield或等待信号量/队列等能让出CPU的操作。或者给任务设置不同的优先级。使用printf打印乱码或导致系统卡死1. 串口未正确初始化。2. 在中断或临界区内调用了printf它可能不是线程安全的。3.printf重定向函数如_write实现有误或使用了不安全的HAL库函数如HAL_UART_Transmit阻塞式发送。1. 检查串口配置和接线。2. 避免在中断服务程序或portENTER_CRITICAL区域内打印。3. 在RTOS中建议将printf重定向到一个线程安全的队列中由一个专用的“日志任务”负责发送或者使用DMA传输。7.2 运行时问题与调试手段系统运行不稳定偶尔HardFault栈溢出启用configCHECK_FOR_STACK_OVERFLOW并实现钩子函数。堆溢出/内存踩踏检查数组越界、指针非法访问。可以尝试将heap_4.c换成heap_1.c如果不删除任务来排除内存释放带来的问题。中断服务程序ISR中使用了非FromISR版本的API在ISR中必须使用xQueueSendFromISR而不是xQueueSend。性能分析工具仅限专业版FreeRTOS或第三方工具Tracealyzer一个强大的可视化追踪工具可以图形化显示任务调度、中断、资源使用情况是分析复杂系统行为的利器。SystemViewSEGGER公司提供的免费工具配合J-Link可以实时记录和可视化RTOS事件对于调试调度问题非常有帮助。使用串口打印调试信息创建一个优先级较低的后台日志任务接收来自其他任务或中断通过队列发送的日志消息然后统一打印。这样可以避免打印操作阻塞高优先级任务或中断。在FreeRTOSConfig.h中启用configUSE_TRACE_FACILITY和configUSE_STATS_FORMATTING_FUNCTIONS然后可以调用vTaskList()或vTaskGetRunTimeStats()来获取任务状态和CPU占用率并通过串口打印是性能分析的宝贵数据。移植FreeRTOS到STM32初次接触可能会觉得步骤繁琐但一旦理解了其脉络——芯片时钟初始化、内核端口适配、内存堆管理、任务创建与调度——就会发现它是一套非常模块化、逻辑清晰的流程。利用好STM32CubeMX这个利器可以规避大量底层细节错误。真正考验功力的地方在于后续的任务划分、优先级设计、资源互斥锁、信号量、队列管理以及系统稳定性优化。当你看到几个任务在你的小小单片机上和谐共处、各司其职时那种成就感正是嵌入式开发的乐趣所在。先从让两个LED以不同频率闪烁开始慢慢尝试加入按键扫描、传感器数据读取、屏幕UI刷新等任务你会逐渐体会到RTOS带来的结构化编程的便利。
返回列表