ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Keil C51/C251/ARM多版本共存与MDK5安装调试指南

Keil C51/C251/ARM多版本共存与MDK5安装调试指南 1. Keil 这几条产品线先把谁是谁捋清楚用了十来年 Keil我发现新手最容易犯的错不是写不出代码而是压根没搞明白自己装的到底是哪一个 Keil。同事问我Keil 装哪个版本我第一句反问永远是你要编的是 8051、C251 还是 Cortex-M这三个答案对应的安装包、编译器、授权方式完全不是一回事。把这件事理顺了后面所谓的版本兼容共存其实就只是个目录规划问题没那么玄乎。标题里的\C51\C251\ARM其实就是 Keil 家族内部的三条产品线装完之后在安装目录下正好是三个平级的文件夹。C51 面向传统 8051 内核单片机C251 面向 251 内核那批工控与老平台器件ARM 就是我们常说的 MDK-ARM覆盖 Cortex-M0/M0/M3/M4/M7 以及部分 Cortex-R。它们共用同一个 IDE 外壳 uVision但底下的编译器、器件库、调试驱动各走各的。很多人以为装了 Keil5 就能编 STM32 又能编 C51这句话只对了一半——外壳确实是同一个但工具链必须各自到位缺一个都会在编译时报找不到编译器。1.1 C51、C251、ARM 三条线各管一摊C51 这条线是 Keil 最老的产品编译器叫 C51最新常见版本是 V9.60 左右。它生成的代码针对 8051 的有限资源做了大量优化比如 data/idata/xdata 的存储类型区分、可重入函数的模拟栈、中断函数的interrupt关键字语法这些都是标准 C 里没有的东西也是 C51 独占的语法糖。你在 C51 工程里写的void timer0_isr() interrupt 1这种写法换到 MDK-ARM 里直接就是语法错误。反过来说MDK 里到处用的__attribute__、分散加载.sct文件在 C51 里也完全不存在。两条线的工程文件格式虽然都叫.uvproj或.uvprojx但内部的工具链配置字段是不通用的硬拿一个工程的配置去套另一个结果就是一堆莫名其妙的目标选项报错。C251 这条线更小众主要服务 251 内核器件比如早年一些工控卡、特定型号的控制器。它的编译器是 C251语法上介于 C51 和标准 C 之间同样有存储类型和中断关键字的概念。现在还在用 C251 的人基本都是在维护十几年前的老平台新项目极少会选这条线。但正因为老这类工程往往绑死了特定版本的编译器换个版本就可能编译不过所以存量项目在机器上保留一个独立、原封不动的 C251 环境比升级更划算。ARM 这条线就是 MDK现在主流是 uVision5 外壳配 MDK 5.3x 版本。它和 C51 最大的区别在于器件支持是通过 Pack 机制动态扩展的你装完 MDK 本体可能只有一个空壳必须再装对应厂商的 DFPDevice Family Pack才能在新建工程时看到具体芯片型号。这个机制很灵活但也带来一个新手常见困惑明明装了 MDK为什么新建工程里搜不到 STM32F103答案通常就是包没装。1.2 uVision 外壳和编译器是两码事把这一点想透共存问题就解决了一半。uVision 是 IDE 外壳负责编辑器、工程管理、调试界面、下载算法调度编译器是后端工具链负责把 C 代码变成机器码。C51 装完之后你用的是一个 uVision 外壳加一套 C51 编译器MDK 装完之后是另一个或同一个uVision 外壳加一套 ARM 编译器。它们之间的耦合点只有一个文件安装目录下的TOOLS.INI。这个文件登记了当前外壳能识别到哪些工具链、各自的路径和版本号。外壳启动时会读它读到[C51]段就认为这台机器上有 C51读到[ARM]段就认为有 ARM。这也解释了一个现象有些人装了 MDK 之后打开 uVision菜单里居然还能看到 C51 的器件选项或者反过来。原因就是TOOLS.INI里两段都在外壳把两套工具链都挂上了。这种天然共存看起来很美但它非常依赖安装顺序和版本搭配。如果你先用新的 MDK 覆盖安装了外壳再用一个很老的 C51 安装包往同一个目录里塞老安装包自带的旧版 uVision 可执行文件可能把新的覆盖掉结果就是 MDK 打不开了。这个坑我当年踩过一次重装花了一下午。1.3 版本选择对照表我把三条线常见的情况整理成一张表装机之前对着看一眼能省不少事。产品线目标内核常见外壳版本编译器典型使用场景Keil C518051 系列uVision V5C51 V9.60C51 V9.60传统 8 位单片机、老设备维护Keil C251251 系列uVision V5C251 V5.60老工控平台、特定控制器MDK-ARMCortex-M/RuVision V5.3xAC5 / AC6STM32、GD32、瑞萨 RA 等MDK-ARM旧ARM7/ARM9uVision V4MDK 4.7xAC5 早期版本极老工程维护表里能看出来uVision5 是当前唯一还在更新的外壳C51 和 MDK 都挂在它下面所以Keil5 兼容 C51 和 STM32这种说法指的就是同一个 uVision5 外壳同时挂载 C51 和 ARM 两套工具链。C251 因为受众太窄官方资料也少基本属于能跑就别动它的状态。至于 uVision4官方早已停止维护新机器上除非要维护老工程否则没有理由再装。选版本的原则其实很简单新项目跟着厂商推荐走老项目跟着原工程走。新项目用最新稳定版 MDK能拿到最新的器件包和编译器优化老项目如果要改代码最好把原来的工具链版本也一起搬过来因为编译器版本变化带来的行为差异往往比重写一段代码还费时间。2. 多版本共存的安装思路目录到底怎么分网上关于 keil5 兼容 c51 和 stm32 安装 的教程一抓一大把但相当一部分只告诉你先装这个再装那个不讲为什么。照着做确实能成可一旦出问题就完全不知道从哪查。我自己的习惯是先把原理讲清楚再动手这样后面任何异常都有排查方向。核心逻辑就是上一节说的共存的关键在于TOOLS.INI是否同时登记了多套工具链而安装顺序和安装目录决定了这个文件最终的形态。所以共存方案本质上只有两种一是全部塞进同一个目录让安装程序自动合并TOOLS.INI二是各装各的目录互不干扰靠手动切换快捷方式或手动合并配置。两种都可行差别在于风险和便利性。2.1 TOOLS.INI 才是决定能不能共存的关键先看一眼这个文件的真实长相心里有个数。装完之后它在安装根目录下用记事本打开内容大致长这样[UV2] ORGANIZATIONYourCompany NAMEYourName EMAIL BOOK0HLP\RELEASE_NOTES.HTM(Release Notes,GEN) [C51] PATHC:\Keil_v5\C51\ VERSIONV9.60 BOOK0HLP\RELEASE_NOTES.HTM(Release Notes,GEN) [ARM] PATHC:\Keil_v5\ARM\ VERSIONV5.38 BOOK0HLP\RELEASE_NOTES.HTM(Release Notes,GEN)[C51]和[ARM]两个段同时存在外壳启动时就会同时挂载两套工具链你在新建工程时就能在器件库里同时看到 8051 和 Cortex-M 的器件。如果只有[ARM]一段那这台机器上的 uVision 就是纯 MDK编不了 C51。有人遇到过装了 C51 但器件列表里看不到 8051八成就是TOOLS.INI里[C51]段被覆盖掉了或者 PATH 指向了一个不存在的目录。这里有个容易被忽视的细节PATH是绝对路径。如果你把整个 Keil 安装目录从 C 盘挪到 D 盘或者在绿色版环境里换了盘符TOOLS.INI里的路径就失效了外壳会认为工具链不存在。这种情况手工改一下路径就能救回来不用重装。我自己的做法是装完之后先备份一份TOOLS.INI后面不管出什么幺蛾子把它复制回去基本都能恢复。2.2 两种共存方案与我的取舍方案一同目录合并安装。最常见的做法是先把 C51 装到C:\Keil_v5再把 MDK 装到同一个目录C:\Keil_v5。安装程序检测到已有 uVision 外壳会尝试合并而不是覆盖。好处是只有一个快捷方式点开之后 C51 和 ARM 工程都能开切换零成本。风险在于安装顺序敏感——通常建议先装老的、再装新的让新版外壳覆盖旧版外壳而不是反过来如果顺序反了老安装包里的旧版可执行文件可能破坏新版环境。方案二分目录独立安装。我目前用的就是这个。C51 装到C:\Keil_C51MDK 装到C:\Keil_MDK两个目录井水不犯河水各自有独立的 uVision 和TOOLS.INI。好处是极其稳定任何一个环境的升级、卸载都不会波及另一个老工程的编译器版本可以永久冻结。代价是桌面会有两个快捷方式打开工程前要先想一下该用哪个以及如果想让某一个外壳同时挂两套工具链需要手动改TOOLS.INI。这两种方案我都长期用过。早期图省事选方案一直到有一次为了升级 C51 版本覆盖式安装把 MDK 的器件包路径搞乱了排查了半天才定位到是TOOLS.INI被改。从那以后新机器我一律分目录装多花五分钟省掉后面一堆不确定性。除非你的机器盘符紧张或者特别在意快捷方式数量否则我更推荐方案二。2.3 实操C51 与 MDK5 装到一台机器上下面这套流程是我现在给新机器装环境的固定动作按顺序走基本不会出问题。第一步准备两套安装源。C51 安装包通常是c51v960a.exe这类命名MDK 是MDK5xx.exe。两者都从官方渠道获取不要从来源不明的网盘拿原因后面在授权章节会讲。安装前先关掉杀毒软件的实时防护Keil 安装过程要写驱动和注册表项部分安全软件会拦截导致装完少文件。第二步装 C51。双击安装包路径改成C:\Keil_C51一路下一步。安装程序会问序列号或选择评估版这里选评估版先装完后面统一处理授权。装完后打开 uVision 确认一下新建工程时器件库里应该能看到 Atmel、STC、Nuvoton 等 8051 厂商的器件。第三步装 MDK。路径改成C:\Keil_MDK这个必须和 C51 目录区分开。安装过程中会弹窗安装 ULINK、CMSIS-DAP 等调试驱动全部允许。装完后同样打开确认此时器件库里应该是空的或者只有很少几个这是正常现象因为 Pack 还没装。第四步装芯片包。MDK 的包有两种装法直接双击厂商官网下载的.pack文件或者打开 uVision 里的 Pack Installer 在线搜索安装。前者适合离线环境后者适合网络通畅的情况。国内网络在线装包经常卡住我的做法是提前把常用包下载好本地双击安装又快又稳。第五步验证双环境。分别用两个快捷方式打开各建一个空白工程C51 那边选一个 8051 器件MDK 那边选一个 Cortex-M 器件都编译一次。能过就说明环境是活的。这一步别省我见过装完看着正常一编译才报找不到编译器的案例。提示安装过程中如果被提示检测到已安装的 uVision是否覆盖务必先确认这个已有环境是不是你还要用的。覆盖是不可逆的。第六步备份配置。把两个目录下的TOOLS.INI各复制一份出来重命名为TOOLS_C51.bak、TOOLS_MDK.bak放在一起。以后环境出问题这是最快的恢复手段。另外 MDK 的器件包建议单独归档重装系统时能省掉大量下载时间。3. 芯片包、器件支持与老工程的迁移很多人对 Keil 的印象停留在装个软件就能用这在 MDK 上是行不通的。MDK 从 V5 开始引入 Pack 机制把器件支持从 IDE 里彻底剥离出来变成可以独立安装、独立升级的组件。这个设计思路跟现代包管理器是一个路子好处是 IDE 本体可以保持精简厂商更新器件支持不用等新版本 IDE代价是新手必须多学一个概念否则会在找不到芯片这个坎上卡很久。3.1 Pack 机制与 DFP 的来龙去脉Pack 分几类最常用的是 DFPDevice Family Pack也就是器件家族包里面包含芯片的寄存器定义、启动文件、外设库、Flash 下载算法。以 STM32 为例Keil.STM32F1xx_DFP这个包里面就有 STM32F103 全系列的器件描述和下载算法。除此之外还有 CMSIS 核心包现在通常已经内置、中间件包RTOS、TCP/IP、文件系统这些以及板级支持包 BSP。Pack 安装之后放在哪里不同版本位置不一样早期在安装目录\ARM\PACK下后来改到用户目录C:\Users\你的用户名\AppData\Local\Arm\Packs下。这个变化带来一个很实际的困扰你以为装在了 D 盘的 Keil 目录里重装系统时只备份了 Keil 目录结果 Pack 全丢了。我现在的习惯是打开 Pack Installer用里面的导入导出功能生成一个包索引文件重装后按索引一次性恢复比重装一个个包省事得多。注意Pack 的版本不是越新越好。有些厂商新版本包会改外设库 API老工程升级包之后直接编译不过。维护老工程时把原来能用的 Pack 版本记下来别随手更新。3.2 老工程迁移到 MDK5 的三个坑手里有基于 MDK4 或者更早版本的老工程想搬到 MDK5 上继续开发这是很常见的需求。我经手过不少这类迁移总结下来主要有三个坑。第一个坑是器件支持方式变了。老工程用的是 legacy device器件信息直接内置在 IDE 里新环境只有 Pack 里的器件。迁过来之后工程能打开但目标器件是灰的编译报找不到器件。解决办法是在工程选项里重新选一次器件让它绑定到 Pack 里的对应型号。如果这个型号在新 Pack 里已经被合并或改名就只能找对应的替代型号注意核对 Flash 和 RAM 大小是否一致。第二个坑是启动文件和库的差异。老工程用的启动文件可能是startup_stm32f10x_hd.s新 Pack 里对应的是startup_stm32f103xe.s中断向量表的名称和数量可能不同。直接用新文件替换如果代码里引用了旧的中断处理函数名链接阶段就会报未定义符号。我的处理方式是先把新启动文件的向量表列出来跟工程里的中断函数逐个对照改名对齐别偷懒。第三个坑是编译器版本切换。MDK4 时代用的是 AC5ARM Compiler 5MDK5 后期默认变成 AC6两者对语法的要求不一样。老代码里常见的__align(4)、__weak、#pragma arm section这些 AC5 特有的写法AC6 全都认不出来会报一堆语法错误。/* AC5 写法AC6 下会报错 */ __align(4) uint8_t buffer[64]; /* AC6 需要改成标准写法 */ _Alignas(4) uint8_t buffer[64]; /* 或者用 CMSIS 提供的宏 */ __ALIGNED(4) uint8_t buffer[64];迁移的时候如果不想动代码最简单的办法是在工程选项里把编译器切回 AC5。但 AC5 从 MDK 5.37 开始就不再随安装包附带需要单独安装这一点后面细说。3.3 瑞萨 RA 这类第三方器件包的搭建除了 STM32 这种大热门不少项目会用到瑞萨、芯科、NXP 等厂商的器件。以瑞萨 RA 系列为例在 Keil 下开发需要装Renesas.RA_DFP这个包装完之后器件库里才能选到具体的 RA 型号。但光装包还不够RA 生态的代码生成主要靠 RASC 这个配置工具工程一般是在 RASC 里创建、配置外设、生成代码然后导出成 Keil 工程。这里有个容易踩的细节RASC 导出 Keil 工程时会指定一个 MDK 版本和 Pack 版本范围如果本机装的版本跟它期望的不一致导出的工程可能打不开或者提示缺包。我的处理方式是先用 RASC 生成一次看它要求哪些包、什么版本然后按需安装而不是先把所有包都装上。另外不同厂商的调试器支持也不一样瑞萨的板子可以用板载调试器或者 J-Link工程选项里的调试驱动要对应选对选错了会提示连接不上目标。4. 编译工具链AC5、AC6 与 C51 授权工具链这块是很多讨论里最容易混淆的部分因为Keil 版本这个词有时候指 IDE 版本有时候指编译器版本混着说就会越说越乱。我习惯把它们拆开IDE 是 uVision编译器是 AC5、AC6、C51、C251。你升级 IDE 不代表编译器会跟着换反过来也一样两者是可以独立搭配的。4.1 AC5 与 AC6 的取舍AC5 是 ARM 自家的老牌编译器从 ARM7 时代一路用过来成熟度极高很多长期维护的工业代码都是在它下面编译出来的。AC6 则是基于 LLVM/Clang 架构重新做的编译速度明显更快生成的代码在部分场景下体积更小、性能更好而且对 C99、C11 标准支持更完整。对比项AC5AC6底层架构ARM 自研基于 LLVM/Clang编译速度较慢明显更快代码体积一般部分场景更小标准支持C90 为主部分 C99C99/C11 完整内联汇编语法自有语法GCC 风格老工程兼容好需要改造怎么选我的经验是新项目一律用 AC6享受更好的优化和更快的编译存量老项目如果代码量大、改造风险高就继续用 AC5别为了追新去动一个跑了五年的稳定工程。真正需要评估的是那种不大不小、要长期演进的项目这种可以花时间做一次 AC5 到 AC6 的迁移一次投入长期受益。迁移时重点检查三处内联汇编、编译器特有的关键字与 pragma、以及对未定义行为的依赖比如有符号整数溢出、变量未初始化就使用。AC6 对未定义行为更严格原来碰巧能跑的代码可能就编不过了。4.2 Arm Compiler 5.06 update 7 为什么还有人专门找Arm Compiler 5.06 update 7 (build 960)这个版本号在搜索里出现频率很高原因是它基本是 AC5 系列的收官版本很多老工程的官方文档、厂商例程都以它为基准。而从 MDK 5.37 开始官方安装包里不再包含 AC5只带 AC6。这就导致一个很尴尬的局面拿到一个老工程里面写着需使用 AC5 编译但装完新版 MDK 发现根本没有 AC5 可选。解决办法是从官方渠道单独下载 Arm Compiler 5 的独立安装包装完之后在 uVision 的工程选项里手动指定编译器路径。装完之后要验证一下在工程选项的 Target 页面把编译器切到 AC5然后编译一次看输出的 Build Output 里显示的版本号是不是你要的那个 build 号。版本号对不上有时候会带来细微的代码行为差异尤其是涉及浮点运算和优化器的部分这一点在精密控制类项目里值得较真。提示AC5 与 AC6 可以在同一台机器上共存装在不同目录通过工程选项切换。切换是工程级的不会互相影响。相关的一个提醒编译器版本升级后务必重新跑一遍完整的测试用例尤其是涉及浮点、中断时序、看门狗喂狗周期的部分。我曾经遇到过一次编译器升级导致某个中断服务函数执行时间变长刚好卡在临界点上跑到现场偶发异常查了很久才定位到是编译器优化策略变化。4.3 评估版限制与授权说明这一段必须说清楚因为它是很多新手最困惑的地方。Keil 的 C51 评估版有一个众所周知的限制生成的代码不能超过 2KB。这就是搜索里那个keil5 c51 的 2k 限制怎么解除的来源。这个限制的正式解除方式只有一个——通过官方渠道获取正式授权。我不打算也不会在文章里提供任何绕过授权的做法因为那既不合法也不安全从非官方渠道拿到的所谓工具往往捆绑了恶意代码为了省一点钱把开发环境甚至整台机器的安全搭进去完全不划算。MDK 这边的情况不同MDK-Lite 版本对 Cortex-M 器件有 32KB 代码限制具体限制随版本有变化超过就要正式授权。对于学习和评估用途评估版完全够用对于商业项目正规采购授权是必须的这既是法律要求也是对自己项目负责。C51 的 2KB 限制在实战中怎么绕过去合理的做法是把代码模块化评估阶段只编译核心功能模块验证逻辑正确性等到需要完整烧录时再使用正式授权。另一个思路是评估阶段先用 C251 或者 ARM 平台验证算法逻辑最后再移植回 8051因为算法本身跟平台无关只有外设操作需要平台适配。这算是一种变通但仅适用于评估阶段正式产品该买授权还是得买。5. 调试环节的实战排错写代码十分钟调 bug 两小时这是嵌入式开发的常态。Keil 的调试功能其实相当强只是很多功能藏得比较深不看文档很难发现。这一节我把这些年最常遇到的几个调试问题整理出来都是反复被问到、也确实有明确解法的。5.1 Watch 窗口看不到结构体成员怎么办这个问题被问到的频率极高描述通常是我想在调试时看一个结构体变量的内容但 Watch 窗口里只显示一个变量名展开看不到成员或者显示optimized out。原因有三类按可能性从高到低排。第一类是编译器优化。这是最常见的原因。当优化等级开到 -O1 及以上时编译器会把只在一个地方使用的变量直接内联掉Python 调试器自然找不到它。你在 Watch 里看到的optimized out或者not in scope就是编译器在告诉你这个变量在我生成的代码里已经不存在了。解决办法有两个把优化等级临时降到 -O0 重新编译或者在变量定义前加volatile告诉编译器每次都要从内存读写、不许优化掉。/* 调试期临时加 volatile定位完可以去掉 */ volatile struct sensor_data g_sensor; /* AC6 下也可以用这个属性强制保留 */ __attribute__((used)) static struct config_t g_cfg;第二类是变量作用域。局部变量在函数返回之后就不存在了如果你是在函数外部打断点去看一个局部变量那当然看不到。这种情况要把断点打在变量还在作用域内的位置或者干脆把变量提成全局静态变量。第三类是 Watch 窗口的显示设置。Keil 的 Watch 窗口对指针和结构体有默认的显示规则有时候它能显示但显示得不直观。可以在 Watch 窗口里手动输入表达式比如g_sensor.temperature直接看单个成员也可以展开左侧的加号逐层查看。对于数组把表达式写成buffer,10这种形式可以指定只显示前 10 个元素避免刷屏。对于指向结构体的指针输入*ptr展开才有效只写ptr只会显示地址。实操心得定位复杂 bug 时我习惯先降优化等级到 -O0 跑一遍确认逻辑正确再逐步把优化等级升回去每次升一级都重新验证。这样既能定位问题又能确认优化等级不会引入新的问题。5.2 常见错误速查表下面这张表是我这些年攒下来的涵盖链接、编译、下载三类错误遇到红字报错可以先对一下。报错关键词大概率原因处理方向L6218E: Undefined symbol函数或变量没定义 / 没加进工程检查源文件是否添加到工程检查拼写L6406E: No space in execution regions代码或数据超出芯片容量检查分散加载文件看是不是 RAM 分配过大L107: ADDRESS SPACE OVERFLOWC51data/idata 空间不够把大数组移到 xdata或用compact模式W15: MULTIPLE CALL TO SEGMENTC51函数被主循环和中断同时调用加reentrant声明或做互斥保护Cannot access target/No target connected调试器连接异常检查复位方式、SWD 引脚、供电Error: Flash Download failed下载算法不匹配在工程的 Flash Download 页重选算法Cannot open source input file头文件路径没配检查 Include PathsDevice not found新建工程器件包未安装装对应 DFP这张表里的每一条我都实际遇到过。举一个印象最深的例子L6406E这个报错第一次遇见时以为是芯片容量选错了折腾半天才发现是分散加载文件里有一段把外部 SRAM 的地址范围写大了一倍链接器算了半天发现放不下。这类问题的排查顺序应该是先看器件选型对不对再看分散加载或链接脚本最后才怀疑代码体积。5.3 中断、Boot 与 APP 的调试经验带 Boot 和 APP 分区的项目调试起来会比单片程序麻烦因为涉及跳转和中断向量。C51 平台上的做法跟 ARM 差别很大值得单独说一下。ARM 平台有 VTOR 寄存器APP 启动后把中断向量表重定位到自己的起始地址就行一行SCB-VTOR APP_ADDR;搞定。C51 没有这个机制中断向量表固定在代码空间的低地址外部中断 0 在 0x0003定时器 0 在 0x000B 等Boot 和 APP 没法各自维护一套。实际工程里常见的做法是Boot 里保留中断入口中断发生时通过函数指针跳到 APP 注册的处理函数APP 启动时把自己的中断处理函数地址写进这个指针。跳转前必须关总中断跳转后再开否则中途来的中断会跳到还没准备好的向量上直接跑飞。/* Boot 侧中转中断处理实际逻辑由 APP 注册 */ void (*app_timer0_isr)(void) 0; void timer0_isr(void) interrupt 1 { if (app_timer0_isr ! 0) { app_timer0_isr(); } }调试这种结构时最有效的手段是打日志而不是单步。在 Boot 和 APP 的关键节点各加一个串口输出把跳转地址、栈指针、时钟配置状态打出来比在调试器里一步步跟要快得多。另外要注意 APP 的链接起始地址必须和 Boot 里的跳转地址一致这两个地方对不上是最常见的跳转过去就死机的原因。5.4 FreeRTOS 移植时最容易卡住的地方在 STM32F103C8T6 这种资源紧张的小容量芯片上移植 FreeRTOS是很多人学习 RTOS 的起点。移植本身不难就是几个文件加中断处理但有几个点特别容易卡住。堆空间设置。FreeRTOSConfig.h里的configTOTAL_HEAP_SIZE决定能创建多少任务和队列。F103C8T6 只有 20KB RAM这个值设大了链接就报内存不够设小了运行时pvPortMalloc返回空、任务创建失败。我的建议是从 6KB 起步根据实际创建的任务数量逐步调整用xPortGetFreeHeapSize()在运行时打印剩余堆空间一眼就能看出该调大还是调小。中断优先级配置。这是新手最容易翻车的地方。Cortex-M3 的优先级数值越小优先级越高而 FreeRTOS 要求所有调用 RTOS API 的中断优先级必须低于configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY设定的门槛数值上要大于等于这个门槛。SysTick 和 PendSV 的中断优先级必须设成最低15。如果设错表现是偶发死机或者断言失败非常难查。配置的时候记得把 NVIC 优先级分组设成 4 位全抢占也就是NVIC_PriorityGroup_4否则优先级门槛的判断会失准。堆方案选择。FreeRTOS 提供 heap_1 到 heap_5 五种内存管理方案。小项目用 heap_4 就行它支持内存释放、能合并相邻空闲块不会像 heap_2 那样产生严重碎片。如果芯片有外部 SDRAM 或者分散的 RAM 区域才需要考虑 heap_5。移植完成之后我习惯做几个简单的压力测试创建删除任务循环几十次看堆空间是否稳定不下降开一个高优先级任务死循环看低优先级任务是否还能被调度到故意写一个数组越界看是不是能触发configASSERT。这几个测试能覆盖大部分移植配置问题。6. 几个容易被忽略的经验写到这里主体内容基本讲完了。最后再分享三块零散但很实用的经验都是那种文档里不写、但实际会用上的东西。6.1 安装包的归档与恢复策略嵌入式开发环境的麻烦在于重装成本高IDE、编译器、器件包、调试驱动一层套一层少一个都跑不起来。我现在的做法是维护一个开发环境归档目录里面放这些东西Keil 各产品线的安装包、常用的 DFP 包、Arm Compiler 5 独立安装包、调试器驱动以及每个环境的TOOLS.INI备份。整个目录加起来几个 GB放在移动硬盘里。恢复的时候按顺序来先装 IDE再装编译器附加包再装器件包最后改配置。装完把备份的TOOLS.INI覆盖回去省掉重新配置器件路径的步骤。这套流程我大概走过四五次包括换电脑、重装系统、帮同事搭环境每次从零到能用不超过一小时。提示器件包建议按厂商建子目录归档比如Packs\ST、Packs\Renesas并且在文件名里带上版本号。厂商更新包之后会覆盖同名文件不带版本号将来分不清哪个是哪个。6.2 C51 与 ARM 的功耗到底怎么比搜索里有个问题问c51 单片机与 arm5 的功耗对比如何这个问题本身问得有点偏。原因在于内核架构不直接决定功耗决定功耗的是芯片的工艺、主频、外设配置和休眠策略。同一个 Cortex-M0 内核不同厂商做出来的芯片运行功耗可能差好几倍。不过从趋势上还是能说几句。传统 8051 内核主频通常在 12 到 35MHz 之间1T 模式下单周期指令运行电流在毫安量级Cortex-M 系列主频高得多Cortex-M4 跑到 100MHz 以上很常见但先进工艺下的动态功耗控制得更好单位 MHz 的能效往往更高。真正的差距在休眠能力上现代 Cortex-M 芯片普遍支持多级睡眠Sleep、Stop、StandbyStop 模式下电流可以低到几微安靠 RTC 或外部中断唤醒老一代 8051 的掉电模式虽然也省电但唤醒方式和支持的外设没这么丰富。所以选型时别只看内核要看具体型号的数据手册重点看三张表运行模式下的电流与主频曲线、低功耗模式下的电流、唤醒时间。如果一个项目对功耗极其敏感正确做法是做一轮实测——用电流表配合不同的工作模式跑一遍看实际电流曲线这比看任何宣传数据都靠谱。6.3 版本升级的节奏该怎么把握最后聊一个策略问题Keil 的版本要不要跟着升级。我的原则是分环境对待。个人学习和新项目可以跟着升。新版本带来更好的编译器优化、更全的器件支持、修掉的 bug升完重新编译验证一遍风险可控。升级前把旧的TOOLS.INI和器件包都备份好出问题能退回去。已经交付、在现场跑着的项目别动。现场设备的稳定性优先级最高为了用上新特性去升级工具链换来一个现场偶发故障这个账怎么算都不划算。这类项目的环境应该在开发机上冻结甚至单独留一台机器或者一个虚拟机专跑。还有一个折中做法用虚拟机隔离。给每个大项目建一个虚拟机镜像里面装这个项目专属的工具链版本镜像归档保存。这样既不影响新项目用新版本老项目也能随时恢复到它自己的环境。我现在手里几个长期维护的老项目就是这么管的虽然镜像占空间但省下来的排查时间远超这点硬盘成本。我个人在实际操作中的体会是Keil 这套工具本身的坑并不算多绝大多数诡异问题追到底都是环境问题——装了几个版本、哪个版本在管事、路径指到哪里、包装没装全。把TOOLS.INI这一个文件理解透把安装目录规划清楚把安装包和器件包归档好剩下的事情就顺了。真正花时间的从来不是软件本身而是搞不清自己到底在用哪一套环境。
返回列表