恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

uC/OS-II移植STM32F103C8T6:从零跑通首个实时内核实例

  • 首页
  • 资讯中心
  • /
  • uC/OS-II移植STM32F103C8T6:从零跑通首个实时内核实例

相关资讯

AI-Gomoku实战:Minimax+Alpha-Beta打造可部署五子棋AI 2026/10/13 15:56:55
多智能体+行业工作台:AI团队协作与业务流程落地的工程实践 2026/10/13 15:56:55
PERT图核心逻辑与PERT Chart EXPERT实战:三点估算、关键路径与避坑指南 2026/10/13 15:56:55

最新资讯

STM32F103C8T6驱动MPU6050:卡尔曼滤波姿态解算实战
Java类加载器深度探秘:从双亲委派到自定义加载实践
REA:正则提取助手,命令行批量文本清洗与日志处理工具
C++继承与多态从机制到实践:虚函数、虚继承与设计陷阱
知识工作者必备:用插件矩阵搭建统一信息工作流
梯度下降法Matlab程序及利用Excel做详细说明:从一行公式到能跑通的代码

今日推荐

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析
CAN总线错误处理机制深度解析:从位错误到Bus Off的硬件级调试

本周热门

UE动画修改实战:从资产编辑到重定向与蒙太奇驱动
统计随机数生成器攻击下的KLJN安全密钥交换协议Matlab仿真
政务API安全治理:资产测绘、低代码编排与行标对标实践

本月精选

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)

uC/OS-II移植STM32F103C8T6:从零跑通首个实时内核实例

发布时间:2026/10/13 16:01:55
uC/OS-II移植STM32F103C8T6:从零跑通首个实时内核实例 简介这是一份为嵌入式开发者准备的uCOSII在STM32F103C8T6上的完整移植范例工程面向需要掌握RTOS移植方法或正在基于Cortex-M3平台做实时系统开发的人群可解决内核适配、中断管理、任务调度与内存分配等关键问题。压缩包共278个文件以C源文件、头文件、汇编启动文件为主并附有Keil工程配置文件、编译生成的hex烧录文件、map地址映射文件以及PDF/DOC说明文档整体约6.5MB目录结构清晰便于对照查阅。目前已有1197人学习下载经历过STM32与uCOSII入门者的实际验证具备一定参考价值。工程可导入Keil直接编译烧录通过两个LED不同频率的闪烁直观演示多任务抢占式切换源码涉及NVIC中断优先级配置、启动代码时钟初始化、任务创建/挂起/恢复、堆栈分配与同步机制等实现细节读者不但能复现一个可运行的系统还可从中沉淀RTOS移植的完整流程、常见编译错误处理与调试排错思路。1. 为什么还在折腾uCOSII一块C8T6能跑的实时内核没那么神秘「uCOSII移植STM32F103C8T6范例工程」在嵌入式圈子里几乎是日经贴一颗64KB Flash、20KB RAM的Cortex-M3小容量芯片要跑起uC/OS-II这套经典实时内核。它解决的问题不是「能不能用RTOS」而是「在C8T6这种资源受限的芯片上如何把移植这件事做得干净、可复现、不翻车」。uC/OS-II的优势在于源码清晰、裁剪灵活整个内核核心代码不过几千行非常适合拿来当第一个被彻底读懂的RTOS。这篇笔记面向两类读者刚接触RTOS、想在C8T6上跑通第一个多任务工程的开发者以及已经会跑但想回头核对移植文件细节的熟手。下面这套移植思路和范例工程是我反复搭过多次后沉淀下来的版本。2. 移植前先弄清底细uCOSII的移植面与C8T6的硬件边界2.1 uCOSII的移植只需要动三个文件uC/OS-II的源码结构分两部分与处理器无关的通用内核代码以及与处理器相关的移植层。通用代码在Source目录下包括os_core.c、os_task.c、os_time.c、os_sem.c、os_mbox.c、os_q.c、os_mem.c、os_flag.c、os_mutex.c这些文件在任何芯片上都是同一份不需要修改。真正需要动手的只有移植层三件套OS_CPU.H、OS_CPU_C.C、OS_CPU_A.ASM。文件职责改动重点OS_CPU.H数据类型定义、临界区方法、堆栈方向、OS_TASK_SW宏让uC/OS-II的类型与Cortex-M3的32位字长相匹配OS_CPU_C.C任务堆栈初始化函数、五个钩子函数OSTaskStkInit是核心其余钩子可留空实现OS_CPU_A.ASM启动第一个任务、任务级切换、中断级切换OSStartHighRdy、PendSV_Handler、OSCtxSw、OSIntCtxSw这三件套的职责一句话概括OS_CPU.H告诉内核“这颗芯片的字是多宽、栈怎么长、临界区怎么关中断”OS_CPU_C.C负责在创建任务时把任务栈摆成内核期望的样子OS_CPU_A.ASM负责在任务切换时真正把寄存器现场存下来、再把新任务的现场恢复回去。理解了这个分工后面改代码就不会乱。2.2 Cortex-M3让移植省掉了大部分汇编uC/OS-II的早期版本是为8位、16位处理器设计的移植工作里有大量汇编要处理现场保护、栈帧切换。但Cortex-M3内核自带硬件压栈和尾链中断机制把任务切换这件最敏感的活儿简化了一大半。任务被中断打断时硬件自动把xPSR、PC、LR、R12、R3、R2、R1、R0这8个寄存器压到当前栈上异常返回时硬件再从栈上自动弹出。这意味着PendSV_Handler里只需要软件保存和恢复R4到R11这8个通用寄存器剩下的交给硬件。还有一个关键设计Cortex-M3线程模式可以用MSP也可以用PSP。uC/OS-II的通常做法是内核启动后让所有任务运行在PSP上中断异常处理使用MSP。这样每个任务各自持有独立栈中断栈固定由MSP承担互不污染。移植时需要在OSStartHighRdy里把CONTROL寄存器的bit1置1让线程模式切到PSP并在异常返回时使用EXC_RETURN值0xFFFFFFFD告诉CPU「我从线程模式PSP的现场返回」。这一套机制理解透了再看官方移植代码就不会觉得那是玄学。2.3 C8T6的资源边界64KB Flash与20KB RAM怎么分账STM32F103C8T6的Flash容量64KB、SRAM容量20KB放在今天看确实紧张但uC/OS-II本身就是为这种资源设计的。启用基础任务管理、信号量、消息队列、统计任务这几个模块时编译产物通常在20KB到35KB之间Flash余量充足。RAM这边要精打细算每个任务栈、每个TCB、内核对象都要吃RAMC8T6的20KB要按预算分配。以范例工程为例2个用户任务每个任务栈128字也就是512字节两个栈共1KB加上起始任务的栈、空闲任务栈总栈开销大约2.5KB。uC/OS-II的TCB在2.91版本里大约占用60到80字节10个任务也就不到1KB。这样算下来跑一个10任务规模的小系统RAM占用在5KB以内C8T6完全兜得住。如果任务栈开到256字甚至更大就要小心了20KB RAM经不起几个大手笔栈同时存在。3. 逐文件动手移植OS_CPU.H、OS_CPU_C.C、OS_CPU_A.ASM的改写要点3.1 OS_CPU.H数据类型、临界区方法与堆栈方向打开OS_CPU.H核心要改的是类型定义、临界区方法和OS_TASK_SW宏。Cortex-M3是32位内核字长和指针都是32位所以OS_STK和OS_CPU_SR必须定义成unsigned int。临界区方法这里固定选方法3也就是用PRIMASK来关中断这是Cortex-M3上最可靠的做法。typedef unsigned char BOOLEAN; typedef unsigned char INT8U; typedef signed char INT8S; typedef unsigned short INT16U; typedef signed short INT16S; typedef unsigned int INT32U; typedef signed int INT32S; typedef float FP32; typedef double FP64; typedef unsigned int OS_STK; /* 栈单元宽度32位 */ typedef unsigned int OS_CPU_SR; /* 保存PRIMASK用必须是32位 */ #define OS_CRITICAL_METHOD 3 /* 方法3CPSID i/CPSIE i */ #define OS_STK_GROWTH 1 /* Cortex-M3栈向下增长 */这段代码的逻辑说明OS_STK是uC/OS-II里任务栈的基本单位一个OS_STK占4字节所以声明OS_STK TaskStk[128]时实际栈空间是512字节。OS_CPU_SR关联临界区方法3进入临界区时把PRIMASK值保存到这个变量里退出时再恢复保证了临界区嵌套不会出错。OS_STK_GROWTH设为1表示栈从高地址向低地址增长创建任务传入栈顶地址时取的是数组最后一个元素的地址。OS_TASK_SW宏定义成触发PendSV异常#define OS_TASK_SW() OSCtxSw()OSCtxSw在汇编文件里实现实际动作是往中断控制寄存器ICSR的bit28写1挂起PendSV。任务级切换通过PendSV完成而不是直接在这里做现场切换这是Cortex-M3移植的标准姿势。3.2 OS_CPU_C.COSTaskStkInit与五个钩子函数OS_CPU_C.C里最核心的函数是OSTaskStkInit。它做的事是当OSTaskCreate创建任务时把任务栈初始化成「仿佛这个任务刚被PendSV打断过」的样子。这样第一个任务启动时PendSV_Handler恢复现场的逻辑可以复用不需要另写一套启动代码。OS_STK *OSTaskStkInit(OS_TASK_PTR task, void *p_arg, OS_STK *ptos, OS_STK *pbot) { OS_STK *stk; (void)pbot; stk ptos; /* 栈顶高地址 */ /* 异常返回帧8个字由硬件弹栈 */ *(--stk) (OS_STK)0x01000000u; /* xPSRbit24置1表示Thumb模式 */ *(--stk) (OS_STK)task; /* PC任务入口地址 */ *(--stk) (OS_STK)0xFFFFFFFEu; /* LR填非法值任务意外return会触发fault */ *(--stk) (OS_STK)0x12121212u; /* R12 */ *(--stk) (OS_STK)0x03030303u; /* R3 */ *(--stk) (OS_STK)0x02020202u; /* R2 */ *(--stk) (OS_STK)0x01010101u; /* R1 */ *(--stk) (OS_STK)p_arg; /* R0任务参数 */ /* 其余寄存器由PendSV_Handler软件恢复 */ *(--stk) (OS_STK)0x11111111u; /* R11 */ *(--stk) (OS_STK)0x10101010u; /* R10 */ *(--stk) (OS_STK)0x09090909u; /* R9 */ *(--stk) (OS_STK)0x08080808u; /* R8 */ *(--stk) (OS_STK)0x07070707u; /* R7 */ *(--stk) (OS_STK)0x06060606u; /* R6 */ *(--stk) (OS_STK)0x05050505u; /* R5 */ *(--stk) (OS_STK)0x04040404u; /* R4 */ return (stk); /* 返回新的栈顶指针 */ }这段代码的关键点在于栈帧顺序必须和PendSV_Handler的恢复顺序严格一致。异常返回帧在最上面也就是最后被压入硬件弹出时先从高地址取xPSR、PC、LR、R12、R3、R2、R1、R0。R4到R11由软件恢复所以放在栈的低地址段。返回值stk指向R4所在的位置这个值会存入TCB的OSTCBStkPtr。钩子函数OSTaskSwHook、OSTaskCreateHook、OSTaskDelHook、OSTaskStatHook、OSTimeTickHook在这个范例里都写成空函数。如果调试时需要统计任务切换次数或追踪任务生命周期往这些钩子里插代码就行不需要改内核。3.3 OS_CPU_A.ASMOSStartHighRdy与PendSV_Handler这部分是整个移植的核心。我直接给一份在MDK-ARM环境下可用的汇编代码对应uC/OS-II 2.91版本的移植层AREA OS_CPU_A, CODE, READONLY THUMB EXTERN OSRunning EXTERN OSTCBCurPtr EXTERN OSTCBHighRdyPtr EXTERN OSPrioCur EXTERN OSPrioHighRdy EXTERN OSTaskSwHook NVIC_INT_CTRL EQU 0xE000ED04 NVIC_SYSPRI14 EQU 0xE000ED22 NVIC_SYSPRI15 EQU 0xE000ED23 NVIC_PENDSV_PRI EQU 0xFF NVIC_PENDSVSET EQU 0x10000000 EXPORT OSStartHighRdy EXPORT OSCtxSw EXPORT OSIntCtxSw EXPORT PendSV_Handler OSStartHighRdy ; 设置PendSV和SysTick优先级为最低 LDR R0, NVIC_SYSPRI14 LDR R1, NVIC_PENDSV_PRI STRB R1, [R0] LDR R0, NVIC_SYSPRI15 LDR R1, NVIC_PENDSV_PRI STRB R1, [R0] ; 线程模式切换到PSP MOVS R0, #0 MSR PSP, R0 MRS R0, CONTROL ORR R0, R0, #2 MSR CONTROL, R0 ISB ; 标记内核已运行 LDR R0, OSRunning MOVS R1, #1 STRB R1, [R0] ; 触发PendSV准备进入第一个任务 LDR R0, NVIC_INT_CTRL LDR R1, NVIC_PENDSVSET STR R1, [R0] ; 开中断 CPSIE I OSStartHang B OSStartHang OSCtxSw LDR R0, NVIC_INT_CTRL LDR R1, NVIC_PENDSVSET STR R1, [R0] BX LR OSIntCtxSw LDR R0, NVIC_INT_CTRL LDR R1, NVIC_PENDSVSET STR R1, [R0] BX LR PendSV_Handler CPSID I MRS R0, PSP SUBS R0, R0, #0x20 STR R4, [R0] STR R5, [R0, #0x04] STR R6, [R0, #0x08] STR R7, [R0, #0x0C] STR R8, [R0, #0x10] STR R9, [R0, #0x14] STR R10, [R0, #0x18] STR R11, [R0, #0x1C] LDR R1, OSTCBCurPtr LDR R1, [R1] STR R0, [R1] LDR R0, OSTaskSwHook BLX R0 LDR R0, OSTCBHighRdyPtr LDR R1, [R0] LDR R2, OSTCBCurPtr STR R1, [R2] LDR R0, OSPrioHighRdy LDRB R1, [R0] LDR R0, OSPrioCur STRB R1, [R0] LDR R1, [R1] LDR R1, [R1] MSR PSP, R1 LDR R4, [R1] LDR R5, [R1, #0x04] LDR R6, [R1, #0x08] LDR R7, [R1, #0x0C] LDR R8, [R1, #0x10] LDR R9, [R1, #0x14] LDR R10, [R1, #0x18] LDR R11, [R1, #0x1C] CPSIE I BX LR END这段汇编的逻辑说明OSStartHighRdy把PendSV和SysTick都设为最低优先级这保证任务切换期间不会被系统节拍打断。接着把PSP清零并切换CONTROL寄存器使用PSP再触发PendSV。PendSV产生后进入PendSV_Handler此时硬件已经帮我们把R0到R3、R12、LR、PC、xPSR压到了PSP指向的任务栈上软件只需再压R4到R11。保存完当前任务的SP后切换TCB指针和优先级变量再从新任务的栈里恢复R4到R11最后BX LR由硬件完成异常返回第一个任务就这样跑起来了。OSCtxSw和OSIntCtxSw都采用了触发PendSV的写法。任务级切换时触发PendSV后如果当前没有更高优先级中断活动PendSV马上执行中断级切换时PendSV优先级低于当前中断会等当前中断返回后再执行中断现场由硬件丢弃不会污染任务栈。这两种写法合在一起避免了官方早期版本里OSIntCtxSw要手工修正栈指针的麻烦。Q为什么OSIntCtxSw不用像老版本那样调整SP因为中断返回到线程模式后硬件会把MSP上的中断帧丢弃PSP仍在被打断任务自己的栈上PendSV保存R4到R11完全基于PSP不关心MSP上有多少残留数据。3.4 链接与编译配置让启动文件的弱符号被替换MDK工程里启动文件startup_stm32f103xb.s中声明了PendSV_Handler为WEAK弱符号而我们汇编文件里EXPORT了同名的强符号。链接时强符号覆盖弱符号中断向量表自动指向我们的PendSV_Handler启动文件不需要修改。但有一件事必须做对汇编文件加入工程后要在MDK里确认它的文件类型被识别为汇编源文件编译器用armasm处理而不是被当成C文件。文件后缀用.s或.asm都可以只要工程设置里Assembler选项正常。还有一个链接参数注意点C8T6的Flash是64KBRAM是20KB在MDK的Target选项卡里要把IROM1大小设为0x10000IRAM1大小设为0x5000。如果沿用默认的512KB Flash配置链接器虽然也能工作但生成的烧录文件和芯片实际容量不匹配后面用ST-Link烧录时可能报地址越界。4. 搭一个能跑的最小范例工程从main.c到LED闪烁的完整配置4.1 范例工程的目录结构与SysTick时钟节拍配置范例工程按下面这种结构组织层次清楚方便以后往自己的项目里搬project/ ├─ ucos/ │ ├─ source/ // 内核源码os_core.c等 │ ├─ ports/ // 移植三件套 │ │ ├─ os_cpu.h │ │ ├─ os_cpu_c.c │ │ └─ os_cpu_a.s │ └─ config/ │ ├─ os_cfg.h │ └─ includes.h ├─ bsp/ │ ├─ bsp.c // 时钟、GPIO、串口、SysTick初始化 │ └─ bsp.h ├─ app/ │ └─ main.c └─ startup/ // start_stm32f103xb.s时钟节拍是RTOS的心跳。uC/OS-II用OSTimeTick来推进系统时间典型配置是1ms一个节拍也就是OS_TICKS_PER_SEC设为1000。如果任务对延时精度要求不高也可以设为10010ms一个节拍能减少中断开销。在os_cfg.h里配置#define OS_TICKS_PER_SEC 1000u #define OS_MAX_TASKS 10u #define OS_LOWEST_PRIO 12u #define OS_TASK_STAT_EN 1uOS_MAX_TASKS开了10加上系统自带的空闲任务和统计任务实际可用用户任务数是8个左右。OS_LOWEST_PRIO必须大于等于OS_MAX_TASKS而且优先级数字越大优先级越低0是最高优先级。统计任务使能后系统会自动创建统计任务来监测CPU使用率后面验证移植是否成功全靠它。4.2 创建两个任务并启动uC/OS-IImain.c的写法有固定套路先OSInit初始化内核再创建起始任务最后OSStart启动调度器。起始任务里做硬件初始化和创建用户任务。#include includes.h #include bsp.h static OS_STK TaskStartStk[128]; static OS_STK Task1Stk[128]; static OS_STK Task2Stk[128]; static void TaskStart(void *p_arg); static void Task1(void *p_arg); static void Task2(void *p_arg); int main(void) { OSInit(); OSTaskCreate(TaskStart, (void *)0, TaskStartStk[127], 4); OSStart(); return 0; /* 不会执行到这里 */ } static void TaskStart(void *p_arg) { (void)p_arg; OS_INT16U err; BSP_Init(); /* 时钟、GPIO、串口、SysTick */ OSTaskCreate(Task1, (void *)0, Task1Stk[127], 5); OSTaskCreate(Task2, (void *)0, Task2Stk[127], 6); OSTaskDel(OS_PRIO_SELF); /* 起始任务自我删除让出CPU */ }任务栈参数传的是数组最后一个元素的地址不是首地址。因为Cortex-M3栈向下增长栈顶在高地址TaskStartStk[127]才是真正的初始栈顶。任务优先级4、5、6的安排有意错开起始任务优先级最高它创建完另外两个任务后自我删除这样Task1和Task2不会出现「创建完没人调度」的尴尬。OSStart启动后调度器从优先级最高的就绪任务开始运行。4.3 任务里该写什么LED与串口的最小业务代码Task1和Task2是最能说明RTOS价值的两个示例一个闪烁LED一个打印串口日志通过延时让出CPU观察两个任务交替运行。static void Task1(void *p_arg) { (void)p_arg; while (1) { BSP_LED_Toggle(); OSTimeDly(500); /* 延时500个节拍也就是500ms */ } } static void Task2(void *p_arg) { (void)p_arg; while (1) { BSP_UART_SendString(Task2 running\r\n); OSTimeDly(1000); } }这里的关键点是OSTimeDly。uC/OS-II是非抢占式轮转和优先级抢占结合的内核一个任务如果不主动等待它会独占CPU直到更高优先级任务就绪。所以每个任务循环里都必须有阻塞点OSTimeDly是最简单的一种。另外要有意识地记录OSTimeDly的参数单位是节拍数OS_TICKS_PER_SEC设为1000时OSTimeDly(500)就是0.5秒。仔细看Task1和Task2的循环没有任何「握手」它们靠调度器各自独立运行。这就是RTOS带来的最直观变化裸机里要用状态机或者中断标志才能实现的并发逻辑在uC/OS-II里拆成两个独立的while(1)循环即可。4.4 编译烧录后的预期现象与运行检查编译前检查三件事一是os_cpu_a.s必须正确加入工程并且被armasm编译二是os_cfg.h里OS_MAX_TASKS和OS_LOWEST_PRIO配置合理三是链接脚本或MDK Target设置里Flash与RAM容量填对。编译通过后用ST-Link连接C8T6的SWD口烧录打开串口助手看输出。预期现象是LED以1Hz频率翻转同时串口每1秒打印一行Task2 running打印间隔稳定不抖动。如果LED闪了但串口没有输出先查串口初始化代码和波特率配置不要怀疑RTOS。如果两个现象都没有多半是系统根本没跑起来回到第5章的避坑清单逐项排查。建议把BSP_Init里SysTick初始化的返回值打印出来确认配置成功后再OSStart。5. uCOSII移植避坑实战5个会让范例工程翻车的经典问题5.1 卡在OSStart第一个任务为什么起不来现象程序运行到OSStart后直接跑进OSStartHang死循环任务一个都没执行。LED不闪串口不打印用调试器看PC停在OSStartHang。原因PendSV异常没有被正确触发或没有被正确响应。常见的具体原因有三个一是OSStartHighRdy里没有把PendSV优先级设为最低PendSV被某个更高优先级异常抢占后无法执行二是触发PendSV的ICSR写入方式不对写成了字节写而非字写三是启动文件里PendSV_Handler弱符号没有被强符号覆盖中断向量表仍然指向一个空的WEAK处理函数。解决单步跟踪OSStartHighRdy确认执行完STR写ICSR后再执行CPSIE I下一步应该跳进PendSV_Handler。如果没跳进去检查向量表里PendSV_Handler地址是不是指向我们汇编文件里的那个实现。方法是在MDK的调试界面里直接看中断向量表偏移0x3C处的值。另外确认汇编里NVIC_PENDSVSET用的是0x10000000这个32位值STR是全字写入。5.2 HardFault高频触发优先检查栈对齐和任务栈大小现象系统刚跑起来时正常运行几秒或者一调用复杂函数就进HardFault_Handler有时任务切换瞬间就挂。原因Cortex-M3要求栈指针8字节对齐如果OSTaskStkInit返回的栈顶地址没有对齐到8字节边界硬件压栈时就会触发总线错误。另一个常见原因是任务栈开得太小任务里的局部变量、函数调用层级一深栈就溢出到了未分配区域。解决声明任务栈时用__align(8)修饰确保数组首地址8字节对齐__align(8) static OS_STK Task1Stk[128];同时把任务栈从128字加到256字试试如果HardFault消失说明是栈容量问题再用下一章的OSTaskStkChk去精确测量实际峰值使用量。HardFault出现时优先看Fault状态寄存器里的BFAR和CFSR能区分是栈溢出还是对齐问题。5.3 任务切换不动延时与阻塞才是调度触发器现象两个任务都创建成功了但只有优先级高的那个在跑低优先级任务永远不执行或者整个系统看起来像死机。原因大多数情况下不是移植错了而是任务里没有让出CPU的调用。uC/OS-II的调度是基于优先级的抢占式调度但任务必须进入等待状态调度器才会选择下一个就绪任务。如果任务循环里没有OSTimeDly、OSSemPend等阻塞调用就绪列表里永远只有当前任务低优先级任务没有机会运行。解决检查每个任务的while(1)循环里是否至少有一个阻塞点。范例代码里Task1和Task2都有OSTimeDly这是最低要求。如果任务里有轮询标志位的死等逻辑改成用信号量或事件标志组来唤醒既省CPU又符合RTOS的用法。5.4 汇编文件编译报错MDK与IAR的汇编风格不能混现象编译时os_cpu_a.s报一堆语法错误比如EXPORT不认识、THUMB伪指令错误、END未定义或者链接时找不到OSStartHighRdy符号。原因网上流传的uC/OS-II移植工程有一部分是IAR EWARM的版本汇编语法用的是IAR风格比如EXPORT写成EXPORT OSStartHighRdy段声明用RSEG文件后缀是.s90或.asm。MDK-ARM的armasm使用ARM风格语法段声明是AREA伪指令是THUMB两个编译器语法不互通。解决统一用MDK的armasm语法重写整个汇编文件就是我第3章给的那份代码。特别注意文件结尾必须有END每个EXTERN和EXPORT符号名后面没有冒号。如果你从别处抄的工程是GCC风格又是另一种写法比如用.syntax unified和.thumb这种GNU汇编语法同样不能直接拿给armasm用。判断一个汇编文件能不能用先看第一行的AREA关键字在不在。5.5 中断执行OSIntExit后系统乱跳SysTick与HAL时基的冲突现象使用了STM32标准外设库或HAL库的工程移植uC/OS-II后SysTick_Handler被重写成调用OSTimeTick结果程序里的HAL_Delay、HAL_GetTick或者标准外设库的延时函数全部失效系统行为异常。原因HAL库和标准外设库都把SysTick当成自己的时基中断服务函数里维护一个全局tick计数。我们移植uC/OS-II时接管了SysTick_Handler原来的自增计数逻辑被覆盖所有依赖这个计数的延时函数自然失效。解决两个选择。一是范例工程坚持用标准外设库SysTick完全交给uC/OS-II应用中只用OSTimeDly做延时不碰库的延时接口。二是工程必须用HAL库时把HAL的时基从SysTick改到TIM6或TIM7让两个系统各用各的定时器。改法是在HAL_InitTick里把HAL_InitTick的函数体换成sConfig.Period 1000; HAL_TIM_Base_Init(...)这种方式同时SysTick_Handler里只保留OSTimeTick。6. 验证移植是否成功的最后一公里CPU利用率与栈检查移植跑通LED之后别急着收工还有一步值得做的验证用uC/OS-II的统计任务确认内核真的在正常工作而不只是「碰巧能闪灯」。在os_cfg.h里使能OS_TASK_STAT_EN并在第一个用户任务里调用OSStatInit统计任务就会周期性计算CPU使用率。再配合OSTaskStkChk检查每个任务栈的峰值用量就能彻底确认移植质量。static OS_STK TaskReportStk[128]; static void TaskReport(void *p_arg) { OS_STK_DATA stk_data; (void)p_arg; OSStatInit(); /* 先让统计任务跑起来获得空闲任务基准值 */ while (1) { OSTimeDly(2000); /* 等2秒让统计任务积累数据 */ BSP_UART_SendString(CPU Usage: ); BSP_UART_SendNumber(OSCPUUsage); /* 单位是百分数 */ BSP_UART_SendString(%\r\n); OSTaskStkChk(5, stk_data); BSP_UART_SendString(Task1 stack free: ); BSP_UART_SendNumber(stk_data.OSFree); BSP_UART_SendString( used: ); BSP_UART_SendNumber(stk_data.OSUsed); BSP_UART_SendString(\r\n); } }这段代码把OSCPUUsage这个全局变量通过串口发出去正常情况下空闲时会接近100任务繁忙时会降下来。如果OSCPUUsage恒为0先确认OSStatInit有没有被调用以及统计任务是否真的在运行。OSTaskStkChk需要任务创建时指定了栈底地址参数也就是OSTaskCreate的最后一个参数传了栈底否则检查结果不可信。用这两个指标一个验证调度器在持续工作一个验证栈容量设计是否合理。我自己第一次在C8T6上做这套移植就是在OSStartHighRdy里翻车了一整天PendSV的优先级写成了0x00而不是0xFF第一个任务永远起不来。后来养成一个习惯拿到任何一份移植代码先检查三个配置——OS_CRITICAL_METHOD是不是3、OS_STK_GROWTH是不是1、PendSV优先级是不是0xFF这三点对了系统基本就活了一半。希望你这次移植也一次跑通希望帮到你。本文还有配套的精品资源点击获取

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号