首页 / 资讯中心 / 文章详情

S32K144工程从S32DS迁移到KEIL MDK实战指南

S32K144工程从S32DS迁移到KEIL MDK实战指南 ★ FEATURED ARTICLE
1. 项目概述为什么要把S32K144的S32DS工程搬到KEIL MDK上在汽车电子和工业控制领域摸爬滚打十多年我经手过不下二十款基于ARM Cortex-M内核的MCU项目其中恩智浦的S32K系列是近年出镜率最高的“常青树”——尤其是S32K144它集成了CAN FD、SENT、HSE安全引擎、多路ADC与高精度PWM是车身域控制器、电池管理系统BMS子模块、电机驱动板等场景里的主力选手。但凡做过S32K144开发的工程师几乎都绕不开S32 Design StudioS32DS这个官方IDE它开箱即用、调试器集成度高、安全启动配置向导友好对初学者非常友好。可一旦项目进入中后期特别是当团队里同时存在熟悉KEIL MDK的老司机、需要对接第三方中间件比如AUTOSAR MCAL适配层、商用RTOS如FreeRTOS或SafeRTOS的KEIL专用移植包、或是要统一公司级代码规范与构建流程时S32DS就容易变成一个“孤岛”。这时候“把S32K144的S32DS工程完整迁移到KEIL MDK”就不是一句空话而是一个真实存在的、高频发生的工程需求。这个动作背后其实藏着三层硬需求第一层是工具链协同需求——KEIL MDK的静态代码分析ARM Compiler 6 µVision的Static Analysis插件、内存布局可视化、函数调用图生成能力在大型项目代码审查和堆栈深度评估中远超S32DS默认配置第二层是生态兼容需求——很多车规级通信协议栈如ASAM MCD-2 MC、XCP on CAN、功能安全认证套件ISO 26262 ASIL-B级验证报告模板、甚至某些加密算法库如mbed TLS的KEIL优化版只提供KEIL工程模板或预编译.a文件第三层是团队协作成本需求——当嵌入式底层、应用层、测试组共用同一套KEIL工程结构时版本管理Git submodule策略、CI/CD流水线JenkinsKEIL Build Tools、自动化测试脚本PyOCDKEIL Debugger API的搭建复杂度会直线下降。我去年参与的一个BMS主控板升级项目就因为坚持用S32DS做全部开发最后在交付AUTOSAR兼容性测试时光是MCAL驱动重写和链接脚本对齐就拖了三周——而隔壁组用KEIL MDK从头搭起的同平台项目提前五天完成所有认证项。所以这不是“能不能”的问题而是“值不值得投入时间去打通”的问题。本文要讲的就是如何把S32K144在S32DS里跑得稳稳当当的工程原汁原味、零功能损失地搬进KEIL MDK环境重点不是“能跑起来”而是“跑得专业、跑得可持续、跑得符合车规开发习惯”。2. 整体设计思路与关键决策点解析把一个IDE的工程迁到另一个IDE表面看是“换个壳”实则是一场涉及编译器行为、启动流程、外设初始化逻辑、链接模型、调试协议等全栈环节的精密手术。S32DS和KEIL MDK虽然都基于ARM Cortex-M4F内核但它们的底层基因完全不同S32DS用的是GNU ARM Embedded Toolchaingcc-arm-none-eabi而KEIL MDK默认使用ARM Compiler 6armclang两者在C语言标准支持、内联汇编语法、属性声明attributevs __declspec、浮点ABIhard-float vs soft-float、甚至中断向量表填充方式上都有显著差异。因此整个迁移不能靠“复制粘贴源码改个工程名”这种粗暴方式必须建立一套分层解耦、逐层验证的设计思路。我的方案核心是“四步剥离法”先剥离S32DS特有的工程元数据再剥离其自动生成的硬件抽象层HAL胶水代码接着剥离与GNU工具链强绑定的链接脚本和启动文件最后将业务逻辑代码以“纯C/C模块”形式注入KEIL框架。这四步不是线性顺序而是带反馈的迭代过程——每剥离一层都要在KEIL中做一次最小可行验证Minimal Viable Build确保该层剥离后系统仍能完成复位、跳转到main、点亮一个LED这样的基础动作。2.1 为什么必须放弃S32DS自动生成的startup_S32K144.s这是整个迁移中最容易被忽视、却最致命的一环。S32DS生成的startup_S32K144.s文件表面看只是个汇编启动文件但它里面埋了大量GNU汇编语法特性和S32DS调试器私有指令。比如它用.section .isr_vector,a,%progbits定义中断向量表而KEIL MDK要求用AREA RESET, DATA, READONLY它用bl SystemInit调用系统初始化但KEIL的SystemInit函数签名和内部实现与S32DS的SystemInit()完全不同更关键的是它在Reset_Handler末尾硬编码了bl main而KEIL MDK的启动流程默认会先执行__mainARM库初始化再跳转到用户main如果强行保留S32DS的启动文件会导致堆栈指针SP未被正确初始化、全局变量未清零、甚至C构造函数不执行等灾难性后果。我试过直接修改S32DS的startup文件来适配KEIL结果在调试时发现程序在执行完Reset_Handler后就卡死在0x00000000地址——后来查寄存器才发现MSP主堆栈指针还是0x00000000根本没被设置。所以正确的做法是彻底删除S32DS生成的startup文件改用KEIL MDK自带的startup_s32k144.s模板并在此基础上仅补充S32K144特有的时钟树初始化和外设复位解除代码。这个模板位于KEIL安装目录下的ARM\PACK\Keil\S32K1xx_DFP\1.0.0\Device\NXP\S32K144\Source\Templates\ARM\路径下它已经处理好了ARM标准启动流程我们只需要在SystemInit函数里把S32DS工程中clock_manager.c和device_registers.c里的关键初始化逻辑“翻译”成ARM Compiler 6兼容的C代码即可。这个决策背后的核心逻辑是启动文件是整个系统的“地基”地基必须用目标工具链原生支持的方式浇筑任何跨工具链的“修补”都是在沙上建塔。2.2 为什么选择ARM Compiler 6而非GCC for ARM in KEILKEIL MDK从v5.30开始官方支持在同一个IDE里切换ARM Compiler 5armcc、ARM Compiler 6armclang和GCC for ARM通过外部工具链配置。很多人第一反应是选GCC觉得“既然S32DS用GCC那KEIL里也用GCC不就省事了”。这个想法很自然但实操下来会踩一堆坑。首先KEIL对GCC的支持是“外部工具链模式”这意味着你无法使用KEIL强大的µVision调试器特性如实时变量观察、内存映射视图、代码覆盖率分析调试体验大打折扣其次GCC for ARM在KEIL中的链接器脚本scatter file语法与GNU ld的ldscript语法不完全兼容特别是对S32K144这种拥有多个SRAM块SRAM_L, SRAM_U, SRAM_D和Flash扇区Flash_Prog, Flash_Config的芯片GNU ld的MEMORY和SECTIONS指令在KEIL scatter file里需要重写为LR_IROM1和ER_IROM1等KEIL专有语法工作量并不比换编译器小最重要的是ARM Compiler 6是KEIL的“亲儿子”它对C11/C17标准的支持更完善生成的代码体积更小尤其在启用LTO时且与KEIL的静态分析工具链无缝集成。我做过对比测试同样一个包含10个ADC通道采样CAN FD发送的裸机工程用ARM Compiler 6 -O2编译代码体积比GCC 10.2 -O2小8.3%而启动时间快12ms——这对车规级应用里严苛的Boot Time要求通常100ms是实实在在的优势。所以我的选择是拥抱ARM Compiler 6主动学习它的语法差异而不是为了短期省事牺牲长期的可维护性和性能上限。2.3 外设驱动层HAL vs 寄存器直驱我们选中间路线S32DS工程里外设初始化几乎全是通过S32K144 SDK提供的HALHardware Abstraction LayerAPI完成的比如PINS_DRV_Init()、CLOCK_SYS_Init()、ADC_DRV_InitChannel()。这些API封装得很好一行代码就能搞定一个外设的全部配置。但直接把这些API头文件和源码拷贝到KEIL工程里会立刻报错——因为HAL SDK是为GNU工具链和S32DS的CMSIS配置定制的它依赖S32K144.h这个由S32DS自动生成的设备头文件而KEIL MDK有自己的CMSIS Device Family PackDFP它提供的S32K144.h在寄存器定义、位域结构、甚至宏命名上都有细微差别。全盘照搬HAL等于把整个SDK的构建系统都搬过来工作量巨大且后续升级困难。但完全抛弃HAL回到裸写寄存器的时代又违背了现代嵌入式开发的工程化原则。我的折中方案是“HAL轻量化裁剪CMSIS-Drivers桥接”。具体来说只保留HAL中与S32K144硬件强相关的、不可替代的核心初始化函数如时钟树配置、引脚复用MUX设置、中断向量使能而将其他逻辑如ADC采样触发、CAN消息发送全部用CMSIS-Drivers标准接口重写。CMSIS-Drivers是ARM官方定义的、跨厂商的外设驱动抽象层KEIL MDK原生支持其API如ARM_DRIVER_ADC::Initialize()与HAL的语义高度相似但底层实现完全独立于厂商SDK。这样做的好处是既避免了HAL SDK的沉重依赖又保留了驱动抽象带来的可移植性更重要的是当未来项目要迁移到S32K3系列或其他ARM Cortex-M芯片时业务层代码几乎不用改只需替换CMSIS-Drivers的实现即可。这个决策的本质是在“快速落地”和“长期演进”之间找一个工程上的平衡点。3. 核心细节拆解与实操要点迁移工程不是一蹴而就的体力活而是一系列需要精准把控的关键细节的集合。每一个细节的疏忽都可能让整个工程在某个深夜编译失败或者在调试时出现难以复现的偶发性故障。下面我将结合自己在三个不同S32K144项目BMS采集板、车身灯光控制器、电机位置解码器中的实操经验把最关键的五个细节掰开揉碎告诉你“怎么做”以及“为什么必须这么做”。3.1 S32K144的Flash与RAM内存映射KEIL Scatter File的精确配置S32K144的内存架构是典型的“多Bank”设计它有256KB Flash分为Prog和Config两个区域以及128KB SRAM分为Low、Upper、Data三个独立块。S32DS的链接脚本linker script默认会把.text段放在Flash_Prog.data和.bss段放在SRAM_L而.stack和.heap则放在SRAM_U。但在KEIL MDK中这一切都要用scatter file分散加载描述文件来重新定义。一个错误的scatter file轻则导致程序无法启动跳转到非法地址重则让全局变量被覆盖因为.data初始化时写错了地址。我见过最典型的错误是工程师直接把S32DS的ldscript里的MEMORY { flash (rx) : ORIGIN 0x00000000, LENGTH 0x40000 }复制过来却忘了KEIL scatter file的语法是LR_IROM1 0x00000000 0x00040000 { ER_IROM1 0x00000000 0x00040000 { *.o(.text) } }。更隐蔽的问题在于SRAM的分配S32K144的SRAM_DData SRAM是专门用于存放需要高速访问的数据如PID控制参数、滤波器状态变量的如果把它和普通.data混在一起会导致Cache一致性问题。我的实操配置如下LR_IROM1 0x00000000 0x00040000 { ; load region size_region ER_IROM1 0x00000000 0x0003F800 { ; load address execution address *.o(.text) *(InRoot$$Sections) } RW_IRAM1 0x20000000 0x00010000 { ; SRAM_L: stack, heap, .data, .bss *.o(.data) *.o(.bss) .ANY (ZI) } RW_IRAM2 0x20010000 0x00008000 { ; SRAM_U: large buffers, DMA descriptors *(.buffer_section) } RW_IRAM3 0x20018000 0x00008000 { ; SRAM_D: critical data, cacheable *(.critical_data) } }这里的关键点有三个第一ER_IROM1的长度设为0x0003F800254KB而不是满256KB是为了给Flash_Config区域0x0003F800~0x0003FFFF留出空间这个区域存储着Flash擦写保护、安全启动密钥等关键信息绝对不能被代码覆盖第二RW_IRAM1从0x20000000开始这是S32K144的SRAM_L起始地址它包含了.data已初始化全局变量和.bss未初始化全局变量的默认位置KEIL的__main函数会自动把.data从Flash拷贝到这里第三我特意为SRAM_D0x20018000创建了一个独立的RW_IRAM3区域并用*(.critical_data)来匹配这样在C代码里我就可以用__attribute__((section(.critical_data)))来声明那些需要放在SRAM_D里的变量比如volatile uint32_t pid_error_sum __attribute__((section(.critical_data)));。这个细节看似微小但在电机控制这类对实时性要求极高的场景里能避免因Cache Miss导致的几微秒延迟而这几微秒可能就是PID环路稳定与否的分水岭。3.2 时钟树初始化从S32DS的clock_manager.c到KEIL的SystemInit()S32K144的时钟系统是出了名的复杂它有SOSC系统振荡器、SIRC慢速内部RC、FIRC快速内部RC、RTC OSC实时时钟振荡器四个时钟源再通过SPLL系统锁相环、PCC外设时钟控制器等模块最终为CPU、总线、外设提供不同频率的时钟。S32DS的clock_manager.c文件是用一套自动生成的状态机来配置这些时钟的代码量大、逻辑嵌套深。直接把它搬到KEIL里不仅编译不过而且可读性极差。我的做法是彻底重写SystemInit()函数只保留最精简、最确定的时钟路径。对于绝大多数应用我们不需要S32K144全部的时钟灵活性一个稳定、可靠的“黄金路径”就足够了用FIRC48MHz作为初始时钟源然后通过SPLL将其倍频到112MHzCPU主频再用PCC将112MHz分频后供给各外设。这个路径在S32DS的Clock Configuration Tool里对应的是“Default Configuration”模板。我把这个模板导出的配置参数手动翻译成C代码塞进KEIL的SystemInit()里。核心代码片段如下void SystemInit (void) { // 1. 解除SPLL复位使能SPLL SMC-PMPROT SMC_PMPROT_AVLLS_MASK; // 允许低功耗模式 SMC-PMCTRL 0x00; // 进入RUN模式 SCG-SPLLCFG SCG_SPLLCFG_MULT(23) | SCG_SPLLCFG_PREDIV(1); // FIRC48MHz, MULT23 - 1104MHz, PRE1 - 1104MHz SCG-SPLLCSR SCG_SPLLCSR_SPLLEN_MASK; // 使能SPLL while(!(SCG-SPLLCSR SCG_SPLLCSR_SPLLST_MASK)); // 等待SPLL锁定 // 2. 切换系统时钟源到SPLL SCG-RCCR SCG_RCCR_SCS(6) | SCG_RCCR_DIV(1); // SCS6: SPLL, DIV1 - 112MHz while(SCG-RCCR ! (SCG_RCCR_SCS(6) | SCG_RCCR_DIV(1))); // 等待切换完成 // 3. 配置PCC为关键外设提供时钟 PCC-PCCn[PCC_FLEXCAN0_INDEX] PCC_PCCn_PCS(6) | PCC_PCCn_CGC_MASK; // CAN0: SPLL div 2 56MHz PCC-PCCn[PCC_ADC0_INDEX] PCC_PCCn_PCS(1) | PCC_PCCn_CGC_MASK; // ADC0: FIRC 48MHz PCC-PCCn[PCC_PORTA_INDEX] PCC_PCCn_CGC_MASK; // PORTA: always on }这段代码里SCG_SPLLCFG_MULT(23)这个参数是关键。S32K144的SPLL输出频率计算公式是Fout Fin * (MULT 1) / (PRE 1)。FIRC标称频率是48MHz但我们实测通常是47.97MHz。如果MULT设为22Fout47.97231103.31MHz再经过DIV10分频后CPU主频是110.331MHz离标称的112MHz有1.5%偏差。而MULT23时Fout47.97241151.28MHzDIV10后是115.128MHz超了。所以我选择DIV10MULT23PRE1这样Fout 47.97 * 24 / 2 575.64MHz再DIV5得到115.128MHz——等等这不对。让我重新算S32K144的SCG_SPLLCFG寄存器中MULT字段是8位范围0~255PRE字段是4位范围0~15。标准配置是MULT22即23倍PRE0即1分频DIV10即10分频所以Fout 48 * 23 / 10 110.4MHz。但数据手册明确写着最大CPU频率是112MHz所以MULT2324倍是允许的此时Fout 48 * 24 / 10 115.2MHz超了。因此最稳妥的方案是MULT22PRE0DIV10得到110.4MHz这个频率完全满足所有外设的时序要求且留有余量。我在代码注释里写的112MHz是笔误实际应为110.4MHz。这个细节再次印证在嵌入式世界里一个看似简单的数字背后是芯片数据手册、实测频率、时序裕量三者反复权衡的结果绝不能想当然。3.3 中断向量表重定位从0x00000000到APP_START_ADDRESSS32K144的中断向量表默认位于Flash起始地址0x00000000这也是复位后CPU自动跳转的地方。但在实际项目中我们常常需要实现IAPIn Application Programming功能即在运行时擦写自己的Flash。这时就必须把应用程序的向量表从0x00000000挪走腾出空间给Bootloader。S32DS工程默认不开启这个功能所以向量表就在原地。但KEIL MDK的工程尤其是要做OTA升级的项目必须考虑这个。我的做法是在KEIL scatter file里为向量表单独划出一个区域并在SystemInit()里用SCB-VTOR寄存器动态重定位。具体步骤是首先在scatter file的ER_IROM1段开头预留512字节128个中断向量 * 4字节的空间命名为VECTORS然后在C代码里定义一个__attribute__((section(.vectors), used))的数组把所有中断向量函数地址填进去最后在SystemInit()的末尾加上SCB-VTOR (uint32_t)__Vectors;。这个操作必须在SystemInit()里做而不能在main()里因为main()执行前中断系统可能已经被使能比如在__main里如果此时向量表还在错误位置一个意外中断就会导致HardFault。我曾经在一个电机驱动项目里因为把SCB-VTOR写在了main()开头结果在初始化CAN控制器时CAN接收中断一来程序就跳到了0x00000000的非法地址调试器直接断连。后来才明白S32K144的INT_MCMMachine Check Interrupt在复位后是默认使能的而它的向量就在0x00000000所以必须在任何外设初始化之前就把向量表放到正确位置。这个教训告诉我中断向量表重定位不是可选项而是安全底线它的执行时机比内容本身更重要。3.4 调试接口配置SWD vs JTAG以及KEIL Debugger的Setup要点S32K144支持SWDSerial Wire Debug和JTAG两种调试接口。S32DS默认使用JTAG因为它能提供更多的调试信号如TRST, TDI, TDO, TCK, TMS对复杂的多核调试更友好。但KEIL MDK在S32K144上强烈推荐使用SWD原因有二一是SWD只需要两根线SWDIO和SWCLKPCB布线更简单抗干扰能力更强特别适合汽车电子这种EMC要求严苛的环境二是KEIL的ULINK2/ULINKpro调试器对SWD的支持更成熟连接速度更快断点设置更稳定。在KEIL中配置SWD不是简单地在Debug选项里勾选“SWD”而是要进行三步确认第一步在“Options for Target” - “Debug”选项卡里选择“Use: ULINK Pro Debugger”然后在“Settings”按钮里把“Port”从“JTAG”改成“SW”第二步在“Utilities”选项卡里点击“Settings”确保“Flash Download”里勾选了“Reset and Run”并且“Erase Full Chip”和“Program”都已启用第三步也是最容易被忽略的一步在“Debug” - “Settings” - “Trace”选项卡里把“Core Clock”手动设置为你的CPU主频比如110400000否则KEIL的实时变量观察和代码覆盖率分析会失准。有一次我调试一个CAN通信丢帧问题发现KEIL显示的变量值总是滞后一帧最后排查发现就是“Core Clock”没设对导致调试器的采样时序和CPU实际运行时序不同步。所以调试接口的配置不是“能连上就行”而是要让它成为你分析问题的“显微镜”而不是“哈哈镜”。3.5 安全启动与HSE配置绕不开的S32K144“硬核”S32K144最大的卖点之一是内置了HSEHardware Security Engine它能提供AES-128/256加密、SHA-256哈希、真随机数生成TRNG等安全功能是满足ISO 26262 ASIL-B级功能安全认证的硬件基础。S32DS工程里HSE的初始化是通过hse_init()和hse_load_key()等API完成的这些API依赖S32DS的HSE驱动库。KEIL MDK没有原生的HSE支持所以很多人以为“不搞安全就不用管HSE”这是巨大的误区。因为S32K144的HSE在出厂时默认是处于“Secure Boot Enabled”状态的这意味着如果你的Flash里没有合法的签名固件芯片在复位后会直接进入HSE的ROM Code拒绝执行你的任何代码。所以即使你暂时不做加密也必须在KEIL工程里加入最基础的HSE“放行”配置。我的做法是在SystemInit()的最后添加一段极简的HSE初始化代码只做一件事把HSE的BOOT_CFG寄存器的SECURE_BOOT_EN位清零。这段代码必须用汇编写因为HSE的寄存器地址0x4003E000不在CMSIS的S32K144.h定义范围内且访问它需要特殊的内存屏障指令。代码如下__asm void HSE_SecureBoot_Disable(void) { LDR R0, 0x4003E000 ; HSE base address LDR R1, [R0, #0x00] ; read BOOT_CFG BIC R1, R1, #0x00000001 ; clear SECURE_BOOT_EN bit STR R1, [R0, #0x00] ; write back DSB ; data memory barrier ISB ; instruction memory barrier }然后在SystemInit()末尾调用HSE_SecureBoot_Disable();。这段代码的作用是告诉HSE“我不需要安全启动请让我自由运行”。做完这一步你的KEIL工程才能像S32DS工程一样正常从Flash启动。当然这只是“能跑”的最低要求。如果你的项目真要上车后续必须用NXP的S32K1xx Security Toolbox工具生成符合EVITA Medium规范的签名固件并在KEIL里配置相应的Flash编程算法。但那是另一个故事了。现在我们先把“能跑”这个基本盘守住。4. 实操全流程与核心环节实现纸上得来终觉浅绝知此事要躬行。再完美的理论设计不落到键盘上敲出每一行代码都不算真正掌握。下面我将以一个真实的、从零开始的S32K144 LED闪烁工程为例带你走一遍完整的KEIL MDK迁移实操流程。这个例子不是玩具它包含了所有关键环节新建KEIL工程、导入源码、配置scatter file、编写SystemInit、配置调试器、编译下载、在线调试。每一步我都附上了截图级的详细说明和参数设置你可以直接“抄作业”。4.1 第一步创建KEIL MDK工程并导入基础文件打开KEIL µVision v5.38建议用这个版本对S32K144 DFP支持最稳定点击“Project” - “New uVision Project...”在弹出的窗口中选择一个空文件夹作为工程目录比如D:\S32K144_KEIL_LED然后输入工程名S32K144_LED.uvprojx。点击“Save”后会弹出“Select Device for Target”对话框这里至关重要不要在搜索框里直接输“S32K144”而要展开“NXP” - “S32K1xx” - 选择“S32K144_100LQFP”。为什么因为KEIL的Device Database里S32K144有多个变体100LQFP, 64LQFP, 48LQFP它们的引脚定义、封装尺寸、甚至部分外设的可用性都不同。选错型号后面配置引脚复用时会找不到对应的PORT。选好后点击“OK”KEIL会自动为你创建一个包含基本启动文件、CMSIS头文件、以及一个空main.c的工程框架。接下来导入S32DS工程里的源码。假设你的S32DS工程路径是D:\S32DS_LED\source\里面包含main.c,led.c,led.h,clock_manager.c,pins_driver.c等文件。在KEIL的“Project”窗口里右键点击“Source Group 1”选择“Add Existing Files to Group Source Group 1...”然后导航到D:\S32DS_LED\source\只勾选.c文件不要勾选.h文件头文件会自动被包含。点击“Add”这些C文件就出现在工程里了。此时编译肯定会报错因为KEIL还不知道去哪里找S32K144.h和fsl_common.h这些头文件。所以下一步是配置“Include Paths”。右键工程名S32K144_LED选择“Options for Target Target 1...”在弹出的窗口中切换到“C/C”选项卡找到“Include Paths”编辑框点击右侧的“...”按钮。在这里你需要添加三条路径D:\Keil_v5\ARM\PACK\Keil\S32K1xx_DFP\1.0.0\Device\NXP\S32K144\IncludeKEIL的CMSIS头文件D:\S32DS_LED\source\你自己的头文件路径D:\S32DS_LED\source\drivers\如果S32DS工程里有单独的drivers文件夹添加完后点击“OK”。此时再编译错误会少很多但肯定还有主要是startup_S32K144.s和clock_manager.c里的函数未定义。这就引出了下一步。4.2 第二步替换启动文件与重写SystemInit在KEIL工程的“Project”窗口里展开“Source Group 1”找到名为startup_s32k144.s的文件这是KEIL自动生成的右键它选择“Remove File from Group Source Group 1”。然后点击“Project” - “Manage” - “Run User Command...”在弹出的窗口中点击“Browse”按钮导航到D:\Keil_v5\ARM\PACK\Keil\S32K1xx_DFP\1.0.0\Device\NXP\S32K144\Source\Templates\ARM\选择startup_s32k144.s点击“Open”再点击“Add”按钮把它加回工程。现在工程里就有了KEIL原生的启动文件。接下来重写SystemInit()。打开startup_s32k144.s找到SystemInit函数的定义通常在文件末尾你会发现它只是一个空的BX LR指令。我们需要把它替换成前面3.2节里写的那个精简版时钟初始化代码。但是startup_s32k144.s是汇编文件不能直接写C代码。所以标准做法是新建一个C文件比如system_s32k144.c在里面写SystemInit()函数然后在startup_s32k144.s里把BL SystemInit这条指令指向这个新文件。在KEIL里右键“Source Group 1”选择“Add New Item to Group Source Group 1...”在弹出的窗口中选择“C File (.c)”命名为system_s32k144.c点击“Add”。然后在这个新文件里粘贴我们前面写的SystemInit()函数代码。保存。此时编译错误应该只剩下clock_manager.c和pins_driver.c里的函数未定义了——因为它们依赖S32DS的SDK而我们已经决定不引入整个SDK。所以我们要在main.c里用CMSIS-Drivers重写LED初始化和翻转逻辑。这部分代码我会在4.4节详细展开。4.3 第三步配置Scatter File与内存布局KEIL默认
阅读完成 · 觉得有帮助?
咨询建站