华为LiteOS在STM32F103ZET6上的移植实战:从零到跑通多任务
2026/9/9 10:58:17 网站建设 项目流程

简介:面向嵌入式与物联网开发者,针对STM32F103ZET6硬件平台,提供华为LiteOS移植的完整工程包,解决在Cortex-M3内核微控制器上高效运行LiteOS的关键问题,适合需要掌握RTOS移植流程或快速搭建低功耗物联网应用的中高级嵌入式工程师使用。压缩包共506个文件,以C语言源码与头文件、编译中间文件、工程配置文件和说明文档等为主,包含野火标准库基础例程,整体大小约8.49MB,已有1840人学习下载。资源不仅包含可直接编译的移植源码,还覆盖内核参数配置、中断服务适配、外设初始化、LED任务创建、时钟源选择与启动调试等完整移植要点,配合串口调试与排错思路,可帮助理解LiteOS任务调度和内存管理机制;同时提供信号量、消息队列等复杂功能的扩展思路,适合作为开发基础和工程参考。 最近要给一个基于STM32F103ZET6的设备选嵌入式操作系统,需求很明确:轻量、免费、能跑多任务和消息队列,后面还可能要对接物联网平台。FreeRTOS我熟,但这回更倾向华为生态,最后翻牌的是华为LiteOS。网上讲FreeRTOS移植的文章一抓一大把,LiteOS在F103上的完整移植教程却没那么多,而且版本差异大,照着老文章容易卡壳。这篇就记录我从零开始的LiteOS移植过程,包括源码选型、内核配置、中断适配、真机调试踩坑,帮你避开我走过的弯路。已经在用STM32标准库、想从裸机或FreeRTOS过渡到LiteOS的开发者,可以直接参考我的路线。

1. 移植前先把内核和硬件都摸个底

1.1 LiteOS到底是怎么一款系统

华为LiteOS是面向物联网终端的轻量级实时操作系统,内核代码精炼,支持任务、信号量、互斥锁、消息队列、软定时器,还有事件、内存管理等组件。它和OpenHarmony的LiteOS-M在实现思路上有不少相通之处,你在Gitee或GitHub上拉到的LiteOS仓库,核心目录基本都是kernel、arch、components、targets这个结构。与FreeRTOS相比,LiteOS不只是一个内核,它还围绕物联网场景提供shell、文件系统、联网协议等组件,方便和华为云IoT生态对接。缺点也很明显:版本迭代快,文档跟着版本走,网上教程参差不齐,新手直接上手最新代码容易被各种宏定义搞晕。我的建议是第一轮移植别追求新,挑一个官方targets里有相近芯片例程的分支,先把内核跑通再说。

1.2 ZET6这块板子跑LiteOS够不够

STM32F103ZET6属于F1系列里的高配,72MHz主频、512KB Flash、64KB SRAM、144引脚,外设丰富。LiteOS内核本身非常小,一个最小可调度内核编译出来ROM往往只有几KB到十几KB,RAM占用也才几KB,跟裸机开几个外设的差异不大。所以从资源占用看,ZET6跑LiteOS绰绰有余,512KB Flash对后续接LVGL、协议栈、文件系统也都是友好配置。唯一需要注意的是Cortex-M3没有FPU,官方一些demo会使用浮点类型做运算,搬到F1上要么改用定点,要么接受软浮点的性能开销。比如智能小车场景里做PID,直接用float在F1上仍可接受,但尽量避免在高频中断里做大量浮点运算。

2. 环境准备和源码选型

2.1 开发环境怎么配

我使用的是MDK5加ST-Link,配合STM32标准外设库(StdPeriph)。为什么不直接用HAL?因为LiteOS内核移植和HAL关系不大,标准库生成的裸机工程更干净,容易看清中断、时钟这些底层逻辑。如果你习惯寄存器开发也没有问题,只要能保证“LED能点、串口能打印”这个最小工程稳定即可。调试器方面,ST-Link/V2几十块钱,配合MDK的调试功能足够用。另外串口助手必须备好,后面移植成功的标志很大程度靠串口输出判断,别等出了问题才临时找工具。我习惯用串口打印代替在线调试,因为RTOS跑起来后单步调试经常会遇到“一暂停就进HardFault”的尴尬,串口输出反而更可靠。

2.2 源码拉取和目录识别

拉LiteOS源码有两种思路:一种直接clone整个仓库,体积大但完整;另一种只下载release分支或tag,相对稳定。打开仓库后先别急着全编,先看targets目录,找找有没有Cortex-M3/M4内核的开发板例程。官方一般会提供STM32F429、STM32F407这类芯片的target工程,里面包含了内核代码的组织方式、配置文件位置以及arch层汇编文件。我是这样操作的:把targets里某个相近芯片的工程复制作为底盘,然后把板级驱动部分删掉,替换成自己ZET6的外设初始化代码。这样比凭空创建目录树要高效得多,也能避免漏掉某个必要文件。第一次移植时,我建议不要打开某个“精简专区”就开始对照,尽量以官方targets工程为准,因为LiteOS不同分支的目录变化确实不小。

3. 移植实操:一次跑通的完整路径

3.1 先把裸机工程跑起来

这一步容易被人省略,我建议不要省。基于标准外设库新建一个最小工程,把系统时钟配成72MHz,串口1初始化,LED引脚初始化,printf重定向到串口,然后烧进去确认能正常输出。为什么非要做这一步?因为后面加入RTOS代码后,所有问题都从“外设有没有问题”和“内核有没有问题”两个方向排查。如果裸机部分都没验证过,一开始就把LiteOS加进来,一旦不工作,排查面会非常广。这个最小工程后面会作为整个移植的宿主。我实际测试时,裸机printf输出正常后,才开始动LiteOS相关文件,整体定位问题的速度明显快很多。

3.2 把LiteOS核心文件加进工程

从官方相近targets工程里,把内核相关源文件加进MDK工程。通常需要包括:

  • arch层:针对Cortex-M3的汇编启动切换文件、调度器相关文件;
  • kernel层:任务管理、软件定时器、信号量、互斥锁、消息队列等实现;
  • lib层:内核依赖的基础库实现。

头文件路径也要同步添加,主要是los_config.h所在目录、arch相关目录、kernel目录。加文件时注意,官方target工程里可能会有与开发板强相关的板级驱动,这些不要复制。我的习惯是新建一个“LiteOS”分组,把添加的文件按arch、kernel、lib归类放好,后面查代码、看编译错误都方便。如果编译报错说找不到某头文件,先把include路径补全;如果报函数重复定义,重点看是不是把官方targets里的main也复制进来了。这一步看起来枯燥,但文件组织越清晰,后面和版本差异斗争时越省心。

3.3 内核配置:los_config.h是关键

每个版本的内核配置文件名字可能不同,有的是los_config.h,有的是target_config.h,本质都是内核裁剪的总开关。以下是我这一版用到的主要配置项,可以参考对照自己的源码:

配置宏作用备注
LOSCFG_BASE_CORE内核基础功能总开关关掉就没有任务调度了
LOSCFG_BASE_CORE_TICK_HW_TIME硬件时钟源相关配合SysTick使用
LOSCFG_BASE_CORE_TICK_HW_INT使能tick中断需要配置SysTick周期
LOSCFG_BASE_CORE_TIMESLICE时间片轮转多个同优先级任务调度
LOSCFG_BASE_CORE_TASK任务管理必须开启
LOSCFG_BASE_IPC_SEM信号量按需开启
LOSCFG_BASE_IPC_MUX互斥锁按需开启
LOSCFG_BASE_IPC_QUEUE消息队列按需开启

配置原则很简单:用到的开,用不到的关。很多教程喜欢把所有功能全开,结果编译出来体积大,还可能在中断逻辑上出现意料之外的优先级问题。第一次移植只开任务管理、时间片、软件定时器即可,等跑通了再加其他组件。另外要注意有些宏之间存在依赖关系,比如开了消息队列大概率需要同时开启内存管理相关配置,具体看编译报错,缺哪个补哪个。

3.4 main函数启动流程

LiteOS的启动顺序很固定,和FreeRTOS在main里各种初始化是同一个套路,不同点在于API名字。以下代码是基于我用的这一版内核,新版本API名字可能有改动,但整体结构保持一致:

#include "los_config.h" #include "los_printf.h" static UINT32 TaskA_Entry(UINT32 arg) { (void)arg; while (1) { printf("Task A running...\n"); LOS_TaskDelay(1000); } return LOS_OK; } static UINT32 TaskB_Entry(UINT32 arg) { while (1) { printf("Task B running...\n"); LOS_TaskDelay(2000); } return LOS_OK; } int main(void) { UINT32 ret; TSK_INIT_PARAM_S taskParam; BSP_Init(); // 时钟、串口、LED等硬件初始化 ret = LOS_KernelInit(); if (ret != LOS_OK) { printf("LOS_KernelInit failed: 0x%x\n", ret); return -1; } memset(&taskParam, 0, sizeof(TSK_INIT_PARAM_S)); taskParam.pfnTaskEntry = TaskA_Entry; taskParam.uwStackSize = 1024 * 4; taskParam.pcName = "TaskA"; taskParam.usTaskPrio = 5; ret = LOS_TaskCreate(&g_TaskAHandle, &taskParam); // TaskB创建类似,优先级可以设为6 // 具体字段名以你的源码版本为准 LOS_StartToRun(); // 新版本可能叫LOS_Start,启动调度后不返回 return 0; }

代码里有几个关键点。LOS_KernelInit必须最先调用,它负责初始化内核对象;用户任务必须在调度启动前创建完;LOS_StartToRun之后主函数就回不来了,和FreeRTOS的vTaskStartScheduler一样。如果有任务创建失败,先检查任务栈大小和任务优先级是否合法,LiteOS对优先级范围有要求,不能随手写一个超范围的数字。任务栈建议先给大一点,比如4KB,等跑通了再按实际情况压减。

3.5 SysTick和PendSV怎么处理

这部分是整个移植最核心的地方。Cortex-M3上LiteOS和FreeRTOS一样,用SysTick提供系统节拍,用PendSV做任务切换。具体要处理三件事。

第一,SysTick中断函数。启动文件startup_stm32f10x_hd.s里的中断向量表通常已经放好了SysTick_Handler,我们需要在C文件里实现它,并调用LiteOS的tick处理函数。不同版本的函数名可能不一样,可能是osTickHandler或LOS_TickHandler,直接在内核源码里搜tick就能找到。调用之后LiteOS才能实现延时、时间片轮转这些功能。

第二,PendSV优先级设成最低。典型写法是在内核初始化时由arch层完成,但如果你的工程没有自动设置,需要在代码里显式加一行:

NVIC_SetPriority(PendSV_IRQn, 0xFF); NVIC_SetPriority(SysTick_IRQn, 0xFF);

为什么PendSV要最低?想想任务切换的时机,如果在某个外设中断正在处理时来了一个更高优先级的中断触发任务切换,就可能在中断服务程序内部切换上下文,导致现场保存不完整。PendSV是可悬起异常,只有其它异常都不执行时才真正处理,相当于把切换推迟到安全时机。这一行设置错误,最典型的故障现象就是任务不定期乱跳甚至HardFault。

第三,注意启动文件里的异常函数名要和LiteOS汇编里保持一致。比如LiteOS的调度汇编文件里也许用的就是PendSV_Handler这个名字,那启动文件就不用改;如果名字对不上,编译时不会报错,但运行时PendSV一触发就会进HardFault,相当隐蔽。我的排查方法是直接在启动文件里搜索PendSV、SysTick,确认向量表名字和内核汇编文件导出的符号一致。

SysTick周期的计算也顺手说一下。STM32F103的SysTick时钟可以选择HCLK或HCLK/8,通常配成HCLK/8,也就是72/8=9MHz。要得到1ms的系统Tick,把重装载值配成9000-1即可。如果直接用72MHz,则是72000-1,对24位计数器来说都没压力。在配置内核的tick周期时,这个数值要和配置文件里的tick定义一致,否则系统时间会比实际快或慢,延时全都不准。

4. 真机验证和踩坑排查

4.1 跑两个任务验证调度

移植完成后第一件事,不是写业务代码,先跑两个最简单的任务验证调度正常工作。参考上面3.4的代码,一个任务每1秒打印并翻转LED,另一个任务每2秒打印。烧录后理想输出是:

Task A running... Task B running... Task A running... Task A running... Task B running...

如果能看到这个输出,说明至少三件事是对的:定时器中断正常、任务创建正常、上下文切换正常。反过来,如果卡在第一个任务里出不来,优先怀疑时间片或SysTick没起作用;如果连printf都没有,先查串口和时钟,再查内核初始化是否失败。注意一个细节:LED翻转放到两个不同优先级的任务里,比单纯串口打印更能直观判断实时性。任务优先级不同,高优先级的输出会明显更频繁,这也是验证优先级抢占是否生效的好办法。

4.2 真机上的常见问题和排查速查表

这次移植和帮朋友调试过程中遇到的高频问题比较多,我整理成了表格,方便直接对照排查。

故障现象常见原因排查方向
HardFault任务栈溢出、栈未对齐、PendSV名字与汇编不一致加大任务栈并检查栈对齐;看汇编断点位置
任务不切换,只有一个任务死循环SysTick没配置或未调用tick处理函数确认SysTick_Handler里有LiteOS tick入口
printf不输出或卡死半主机模式未关闭勾选MicroLIB或重定向fputc并处理半主机
串口乱码时钟、波特率不匹配确认SystemInit把时钟配到72MHz,再核对波特率
编译宏冲突工程里已有INLINE、assert之类定义搜索重复宏定义,统一保留一份
偶发异常或中断卡死外设中断优先级高于PendSV,中断里调用LOS接口检查NVIC分组和优先级设置

这些坑基本每个刚接触LiteOS的人都会遇到,尤其HardFault。我调试时一度怀疑是源码问题,后来发现是自己任务栈开小了。LiteOS任务栈虽然最小没有硬件上限,但任务里如果用了printf做格式化输出,栈开销会明显变大,建议默认先给1KB以上,第一次跑通后再慢慢压。另外如果printf卡死,多半是MDK的微库没有开启,或者标准库的半主机模式没处理干净,这个问题和LiteOS本身无关,裸机上就会遇到,但很多人在裸机阶段没踩过,反而在移植RTOS后被坑了一次。

4.3 内核跑通之后还能往哪延伸

如果说裸机是单车道,LiteOS跑通后就是带红绿灯的十字路口,任务、消息、同步机制都能用了。接下来常见的延伸方向有这几个:

  • 开启shell组件,在串口上注册命令查看当前任务运行状态,排障效率高很多;
  • 用消息队列把传感器采集任务和处理任务解耦,比如按键采集、数据处理两个任务各干各的;
  • 在ZET6这种大Flash芯片上,接一块RGB屏可以移植LVGL做图形界面,LiteOS里开一个GUI任务负责刷新即可;
  • 智能小车方向很典型:车控任务、超声避障任务、遥控接收任务各一个线程,配合信号量做互斥和同步,比裸机状态机直观;
  • 串口协议类应用也不少见,比如Modbus从站,LiteOS里用一个任务持续处理串口报文,再用消息队列把解析结果送给控制任务,这套结构配合ZET6完全跑得动。

外围设备接入联网模块以后,也能在LiteOS之上做远程控制或Web配置页面,用户在浏览器里调整参数,设备端实时响应。这个方向很适合做物联网网关类产品,也是LiteOS相对FreeRTOS更有吸引力的地方。

整个移植跑通之后,我的最大感受是:LiteOS移植难不在“加文件”,而在于理解操作系统和硬件中断之间的协作关系。FreeRTOS照着教程抄也能跑,但LiteOS因为版本杂,逼着你去读源码,反而把调度、PendSV、SysTick这些底层机制弄得更清楚了。如果你也是第一次接触,我强烈建议不要直接拿别人整理好的“一键移植包”,自己从标准库工程开始,一个文件一个文件地加,一个坑一个坑地踩。跑通过一次之后,再回来看任何RTOS的移植文档都会觉得通透。最后提醒一句,生产项目用LiteOS之前一定要确认版本维护状态和许可,搭配好组件再做选型,别等产品定型了才发现某个组件不再维护。

本文还有配套的精品资源,点击获取

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

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

立即咨询