ARTICLE DETAIL

资讯详情

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

S32K344 Flash驱动实战:扇区解锁、任意字节写入与C40_Ip CRC校验

S32K344 Flash驱动实战:扇区解锁、任意字节写入与C40_Ip CRC校验 做S32K344项目这段时间Flash驱动是我花时间最多的一块。别看官方LLD里把Flash_Ip_Program和Flash_Ip_EraseSector封装得挺好看真到了现场要解锁扇区、实现任意字节写入、再配合C40_Ip做数据校验问题一个接一个。我把自己从踩坑到跑通的完整过程整理出来希望能给后面接手的兄弟省点时间。本文不是翻译参考手册而是把那些手册里不会明说、但实际调板子一定会撞上的细节摊开来讲。先交代一下背景我用的芯片是S32K344Cortex-M7内核主频160MHzSDK是基于NXP的RTDReal-Time Drivers版本Flash相关的驱动文件是Flash_Ip.c/hCRC校验用的是C40_Ip.c/h。开发环境是S32 Design Studio 劳特巴赫调试器。项目里需要做Bootloader的APP升级功能这就要求在应用运行时把新的固件写入PFlash同时对写入的数据做完整性校验这就是C40_Ip参与进来的原因。如果你也正在被“为什么程序进不了Flash”“为什么擦除总是报错”“为什么CRC算出来跟工具对不上”这些问题折磨那这篇文章应该能帮上忙。我尽量按我实际调试的顺序来写而不是按手册章节来读起来会更有现场感。1. 先搞明白S32K344的Flash到底怎么工作1.1 FMC控制器与Flash_Ip驱动的关系很多人上来就调Flash_Ip_Program完全不看底层结果出错时一头雾水。S32K344的Flash控制器叫FMCFlash Memory Controller它才是真正执行擦除和编程动作的硬件单元。你写的Flash_Ip_xxx只是RTD封装给你的软件接口最终都是通过配置FMC寄存器来触发操作的。FMC的编程和擦除命令走的是“命令寄存器序列”模式你先把要执行的命令码、目标地址、数据长度填入对应的寄存器然后置位启动位FMC就会去操作Flash阵列。操作完成后状态寄存器里会有对应的完成标志或错误标志。我在调试时习惯直接看FMC的几个关键寄存器快速定位问题寄存器/标志作用我踩过的坑命令完成标志表示命令是否执行完毕忘了等待完成标志直接读数据读到的是旧值访问错误标志地址对齐、命令序列非法等地址没有按8字节对齐触发ACCERR保护违规标志目标区域被保护或上锁扇区没解锁执行擦除直接报FPVIOL校验错误标志编程后校验失败或ECC错误写入前未擦除或擦除不彻底RTD的Flash_Ip_Init里会让你填一堆配置项比如Flash_Ip_FlashTypeConfig、Flash_Ip_FlashSize等。它要做的事就是告诉驱动你的Flash布局比如PFlash多大、分了多少个块、扇区大小是多少。这些配置如果跟实际芯片不符最典型的症状就是擦除时报错或者擦除的扇区边界不对。我建议你在初始化之后把配置结构体里各个字段打印出来或者用调试器填入Watch窗口核对一遍。别嫌麻烦很多时候莫名其妙的问题就是这里写错导致后面的定位浪费一整天。1.2 为什么“标准驱动”不能直接写任意字节官方LLD里的Flash_Ip_Program看起来好像什么都能写但它有一个硬性前提目标地址和写入长度都必须是“编程粒度”的整数倍。S32K344的PFlash编程粒度是8字节也就是说Flash_Ip_Program(FLASH_IP_PFLASH, dest, data, 8)这种写法才是合法的写3字节、写5字节基本等着报错。为什么硬件要这么设计因为Flash编程本质上是“电荷注入”的过程硬件一次操作就会锁存一整条编程线对应的数据宽度。就像打印一张A4纸打印机一次处理一整行墨点你不能只让它打印半行。一次写8字节是硬件层面最合理的效率平衡这也是几乎D系列MCUS32K1、S32K3、MPC57xx共通的特性。那“任意字节写入”怎么实现两条路要么写驱动时用“读-改-写”的方式要么利用硬件上的一些特殊命令。我推荐先用读-改-写因为它逻辑最简单、最不容易出错后面我会详细讲实现。1.3 C40_Ip在这里到底是什么角色先泼一盆冷水C40_Ip不是一个独立的Flash控制器它是S32K344里用于CRC计算的一个硬件IP模块。它的本职工作是对一段数据进行循环冗余校验算出校验值。那它跟Flash驱动有什么关系答案在Bootloader场景里非常直接你在APP运行时往Flash里写固件写入完成后不能拍脑袋说“写成功了”得把写入的数据读出来算一遍CRC跟上位机下发的固件CRC比对。如果一致才能把这块数据标记为“有效”。C40_Ip就是干这个算CRC的活儿的。你可能想说“我用软件轮询也能算CRC啊”没错但C40_Ip的优势是硬件计算不占CPU、速度快而且它本身是S32K3系列时钟域里一个稳定可靠的IP。之前做S32K146时我偶尔用软件查表法算CRC32数据量一大CPU占用直接飙上去升级10KB固件时主循环都要被拖垮。换到S32K344上我全部走C40_IpCPU占用低了一个数量级。2. 扇区解锁没有它擦写一开始就会失败2.1 我遇到的“擦不掉”现场项目第一次联调时我在Bootloader里写了一个很简单的功能上电后把APP区擦掉然后往里面写新程序。结果一执行擦除日志立刻弹出一条错误码FLASH_IP_ERR_FAILED。进一步看FMC状态寄存器的错误标志位发现是FPVIOLFlash Protection Violation也就是“你访问了一个受保护的区域”。当时我第一反应是地址写错了把DFlash的地址当成了PFlash来擦。仔细核对地址没错。然后是怀疑Flash驱动配置问题改来改去还是不行。最后才意识到S32K344的Flash上电后默认是有保护的你不主动解除保护PFlash的绝大多数区域都是不可擦、不可写的这就是“扇区封锁”的含义。所以“扇区解锁”并不是说你要一个一个扇区去做解锁动作其实质是去配置FMC的保护寄存器把对应Flash块block的读保护、写保护、擦除保护去掉。S32K344的PFlash并不是逐字节管理保护的它按块来管理——每个块包含若干扇区保护位配置一次就作用于整个块。就像你住的小区保安不是每个房间门口都站一个人而是整栋楼设一个门禁你买了这栋楼的权限才能进去。2.2 解锁的完整代码路径我用的是RTD提供的Flash驱动接口实现解锁不需要直接操作FMC寄存器但也有绕不开的坑。RTD里跟保护相关的接口有Flash_Ip_SetProtection这样一类函数。它接收一个“保护配置”结构体你告诉它要设置哪类FlashPFlash还是DFlash、从哪个地址开始、覆盖多大范围、最后是保护还是取消保护。下面这段是我在实际工程里能跑通的代码用途是解锁整个PFlash的编程保护#include Flash_Ip.h #include C40_Ip.h static void App_SetPFlashUnprotected(void) { Flash_Ip_ProtectionConfigType config; config.FlashType FLASH_IP_PFLASH; config.Address 0x100000u; /* PFlash起始地址 */ config.Size 0x180000u; /* S32K344 PFlash为1.5MB这里按实际容量调整 */ config.Protection FLASH_IP_PROT_NONE; /* 取消所有保护 */ config.Unprotect TRUE; /* 执行解除保护动作 */ if (Flash_Ip_SetProtection(config) ! STATUS_SUCCESS) { /* 这里最好接一个错误上报我在调试阶段是直接断点 */ while (1); } }注意看这个结构体的两个关键字段Protection和Unprotect。我一开始踩的坑是只填了Unprotect TRUE没有把Protection字段设置成FLASH_IP_PROT_NONE结果函数内部逻辑判断保护类型时认为“我要保护的还是默认的读保护”于是它把读保护也去掉了但写保护依然保留擦除照样报错。还有一个隐藏的坑是S32K344的PFlash保护寄存器在某种模式下是“一次可编程”的。如果你在工程里配置了某些安全生命周期状态保护位被OTP化One-Time Programmable一次性可编程那么软件里怎么调Flash_Ip_SetProtection都不可能解开。碰到这种情况只能用调试器或HSE相关工具先把芯片切回允许解锁的生命周期状态。这个属于环境问题量产时基本不会遇到但折腾过的人都知道那种“明明代码没错就是不行”的绝望。/* 实际调用的前置初始化确保Flash驱动已就绪 */ void Flash_UnlockAndInit(void) { Flash_Ip_InitConfigType flashInitCfg; flashInitCfg.FlashSize 0x180000u; /* PFlash总大小 */ flashInitCfg.SectorSize 0x2000u; /* 8KB扇区 */ flashInitCfg.PageSize 8u; /* 编程粒度为8字节 */ (void)Flash_Ip_Init(flashInitCfg); App_SetPFlashUnprotected(); }上面这段只是最小解锁流程。在真实项目里我还会把DFlash也做同样的处理因为升级时往往需要把一些配置参数或者回滚标志放在DFlash区域它同样有保护位。2.3 排查工具与错误码速查如果你在解锁过程中也遇到奇奇怪怪的错误别急着改代码。先接上调试器暂停程序看FMC的状态寄存器一般错误标志会直接告诉你原因。下面是我整理的速查表按我遇到的概率排序错误现象可能原因解决方向FLASH_IP_ERR_FAILED状态寄存器的FPVIOL置位目标地址所在Block被保护调Flash_Ip_SetProtection取消保护FLASH_IP_ERR_FAILED状态寄存器的ACCERR置位地址未按8字节对齐或命令序列不符合规范检查地址和长度是否为8的倍数擦除后读取数据不全为0xFF扇区大小配置错误只擦了一半核对Flash_Ip_InitConfigType中的SectorSize写入后立即读取数据正确重启后数据丢失编程后没有等待命令完成标志就返回或写入了未擦除区域确认编程前先擦除并等待命令完成解锁函数返回成功但擦除仍然报保护违规保护位已被OTP锁死用调试器/Lauterbach检查当前生命周期状态必要时恢复工厂设置3. 任意字节写入的实现读-改-写3.1 核心思路与算法设计既然硬件一次只能写8字节而我们的需求是“给什么地址、什么长度都能写”那办法就是对“边界做拼凑”。把这个过程拆开就是三件事第一判断目标地址是否8字节对齐。如果目标地址本来就是8的倍数且长度也是8的倍数那好办直接分成若干个8字节块调用Flash_Ip_Program即可。第二如果目标地址不是8字节对齐或者长度不足8字节那就需要先把目标地址所在的8字节区域从Flash里读出来放到RAM里的临时缓冲区在这个缓冲区里修改你要改的字节比如只改第3个字节改完之后再把整个8字节写回Flash。这一步叫做“读-改-写”。第三如果一次写入跨了多个8字节边界比如从地址0x100005开始写8个字节那么前3个字节落在0x100000-0x100007这个8字节单元里后5个字节落在0x100008-0x10000F这个8字节单元里必须分别对两个8字节单元各做一次读-改-写。一个很容易忽略的前提是Flash编程前必须先擦除。读-改-写也一样如果目标扇区没有先擦除你读回来的数据里已经包含了旧数据改完写回去新旧数据叠加在一起结果肯定不对。读-改-写在Flash行业里是个特别经典的做法但有几个公司级项目里严禁场景要特别小心比如你的目标扇区正在存运行代码而你跑到RAM里去执行写Flash逻辑那没问题但如果你一边从Flash取指一边写同一个Flash块很可能触发总线异常。S32K344虽然支持在Flash执行代码的同时进行编程但如果你写过复杂Bootloader就知道最稳妥的方案永远是把Flash驱动代码复制到RAM里执行。3.2 完整代码实现下面这段代码是我在实际项目中使用的版本依赖RTD的Flash_Ip_Program接口和标准memcpy。注意实际工程里要确保srcData指向的缓冲区不是当前正在执行的Flash区域否则复制会得到不确定结果。#include string.h #include Flash_Ip.h #define FLASH_PFLASH_PROGRAM_UNIT (8u) static status_t Flash_WriteUnit(uint32_t addr, const uint8_t *data) { uint8_t tmpBuffer[FLASH_PFLASH_PROGRAM_UNIT]; if ((addr (FLASH_PFLASH_PROGRAM_UNIT - 1u)) ! 0u) { return STATUS_ERROR; } /* 编程前先确认这块区域已擦除可选但强烈建议 */ (void)memcpy(tmpBuffer, (const uint8_t *)addr, FLASH_PFLASH_PROGRAM_UNIT); /* 如果用户传入的数据与当前Flash内容一致可以跳过编程 */ if (memcmp(tmpBuffer, data, FLASH_PFLASH_PROGRAM_UNIT) 0) { return STATUS_SUCCESS; } return Flash_Ip_Program(FLASH_IP_PFLASH, addr, (uint8_t *)data, FLASH_PFLASH_PROGRAM_UNIT); } status_t Flash_WriteBytes(uint32_t destAddr, const uint8_t *srcData, uint32_t length) { uint32_t offset 0u; if (srcData NULL || length 0u) { return STATUS_ERROR; } while (offset length) { uint32_t currentAddr destAddr offset; uint32_t unitStart currentAddr ~(FLASH_PFLASH_PROGRAM_UNIT - 1u); uint32_t offsetInUnit currentAddr (FLASH_PFLASH_PROGRAM_UNIT - 1u); uint32_t chunkLen FLASH_PFLASH_PROGRAM_UNIT - offsetInUnit; if (chunkLen (length - offset)) { chunkLen length - offset; } if (chunkLen FLASH_PFLASH_PROGRAM_UNIT) { /* 正好一个8字节单元直接写 */ status_t st Flash_Ip_Program(FLASH_IP_PFLASH, unitStart, (uint8_t *)srcData[offset], chunkLen); if (st ! STATUS_SUCCESS) { return st; } } else { /* 需要读-改-写 */ uint8_t tmpBuffer[FLASH_PFLASH_PROGRAM_UNIT]; (void)memcpy(tmpBuffer, (const uint8_t *)unitStart, FLASH_PFLASH_PROGRAM_UNIT); (void)memcpy(tmpBuffer[offsetInUnit], srcData[offset], chunkLen); status_t st Flash_Ip_Program(FLASH_IP_PFLASH, unitStart, tmpBuffer, FLASH_PFLASH_PROGRAM_UNIT); if (st ! STATUS_SUCCESS) { return st; } } offset chunkLen; } return STATUS_SUCCESS; }我测试时用的调用方式是这样的uint8_t testData[5] {0x11, 0x22, 0x33, 0x44, 0x55}; status_t st Flash_WriteBytes(0x100003u, testData, 5u);这个调用会做什么目标地址0x100003不对齐所以程序会第一次循环计算unitStart 0x100000offsetInUnit 3chunkLen 5。把0x100000到0x100007这8字节读回来修改偏移3到偏移7这5个字节然后写回0x100000-0x100007。循环结束因为5字节全部处理完。如果你写一个从0x100005开始、长度8字节的数据就会发现循环会执行两次第一次处理0x100005-0x1000073字节第二次处理0x100008-0x10000C5字节分别对两个8字节单元做读-改-写最终实现“任意字节写入”。3.3 边界情况与注意事项第一个要提醒的读-改-写的“读”阶段读出来的数据必须是你预期的“已擦除”状态。如果你没有先擦除扇区里面是上一次的旧数据那么这一轮写的操作实质是在旧数据上做覆盖合并产生的结果大概率不是你想要的。我建议在Flash_WriteBytes之前显式地对目标扇区执行一次Flash_Ip_EraseSector。别把这个操作隐藏得太深方便以后排查问题。第二个容易忽略的Flash_Ip_Program内部是否会帮你做“等待命令完成”处理通过看RTD源码它其实是等待命令完成标志的但如果你的代码配置了中断优先级或者Flash命令执行期间来了一个高优先级中断中断里又去访问Flash比如取中断向量、读Flash里的常量表有可能导致命令执行异常。S32K344芯片手册里明确建议Flash编程期间尽量避免从同一块Flash取指或取数据。最稳妥的做法是把Flash_WriteBytes整个函数放到RAM里执行Flash数据缓冲区也放在RAM里。第三个经验是我血的教训写之前一定要校验输入长度和目标地址不能越界。S32K344的PFlash地址范围是0x100000到0x27FFFF1.5MB如果我传了个0x280000的地址进去接口本身也许不会立刻返回错误但那个区域是保留区或映射到其他外设往那写数据轻则报错重则触发HardFault。所以我在函数开头加了边界检查if ((destAddr 0x100000u) || ((destAddr length) 0x280000u)) { return STATUS_ERROR; }4. C40_Ip避坑指南4.1 先看数据手册别急着照抄例程NXP官方例程里C40_Ip的初始化代码往往来自另一个系列的工程或者来自早期版本SDK。你在S32K344上使用时要特别注意S32K344的C40_Ip在时钟域、寄存器位宽、支持的CRC模式上可能和你之前用过的芯片有所不同。我最初是照着S32K312的一个例程抄的C40_Ip初始化结果编译没问题运行也没报错但算出来的CRC值始终和上位机不一致。后来打开S32K344参考手册里的C40_Ip章节对照寄存器定义才发现我配置的多项式寄存器位序和芯片实际要求不完全一致需要设置CrcReflectIn和CrcReflectOut这两个字段。先看一段正确可用的初始化代码#include C40_Ip.h static C40_Ip_ConfigType s_crcConfig; void Crc_Init(void) { s_crcConfig.CrcMode C40_IP_CRC_MODE_32_BIT; s_crcConfig.CrcPoly 0x04C11DB7u; /* 标准CRC-32多项式 */ s_crcConfig.CrcSeed 0xFFFFFFFFu; /* 初始种子 */ s_crcConfig.CrcResult C40_IP_CRC_RESULT_32_BIT; s_crcConfig.CrcReflectIn TRUE; s_crcConfig.CrcReflectOut TRUE; /* 有些SDK版本还有CrcComplement等字段按实际SDK填充 */ if (C40_Ip_Init(s_crcConfig) ! STATUS_SUCCESS) { /* 初始化失败处理 */ while (1); } }这里最大的坑就是CrcReflectIn和CrcReflectOut。如果你的上位机工具是用标准CRC32比如zlib、WinRAR这些软件常用的CRC32算法算出来的那么反射位必须是TRUE否则两边永远对不上。判断自己有没有配错的最快方法很简单拿一小段固定数据比如字符串“123456789”先用一个可信的在线CRC计算器算出CRC32再在板子上用C40_Ip算同一段数据对比一下。如果一致说明配置正确如果不一致优先调整反射位和初始种子。4.2 数据输入格式与字节序的坑C40_Ip的数据寄存器是32位的。调用C40_Ip_WriteData时你传的是一个32位整型。但如果你要计算的数据是从Flash里读回来的字节流就需要特别注意大小端问题。S32K344是小端芯片内存里0x11223344这个数在地址上排列是44 33 22 11。如果你按字节流把0x11,0x22,0x33,0x44逐个喂给CRC模块和按32位字把0x11223344一次性喂进去两者算出的CRC是完全不同的。我在实际项目中踩过的场景是上位机是按文件字节流算的CRC而我的下位机是从Flash缓冲区按32位字读出来喂给C40_Ip。算出来的值总差那么一点最后发现是字节序反了。解决方法很简单要么统一按字节流喂做成一个循环每次取一个字节放到C40_Ip_WriteData里注意该接口接收的是32位数据传入时要把字节放到最低位要么从Flash读取时用字节指针逐个读出再拼成32位。更推荐的做法是写一个字节流接口底层每次从字节流里取4个字节组成32位数据再调用C40_Ip_WriteData。这样逻辑清晰不容易踩大小端坑。uint32_t Crc_CalcOverBytes(const uint8_t *data, uint32_t len) { C40_Ip_ConfigType cfg; uint32_t wordData 0u; uint32_t i 0u; /* 假设cfg已经配置好这里略 */ (void)C40_Ip_Init(cfg); while (i 4u len) { wordData ((uint32_t)data[i] 24u) | ((uint32_t)data[i1] 16u) | ((uint32_t)data[i2] 8u) | ((uint32_t)data[i3]); (void)C40_Ip_WriteData(wordData); i 4u; } return C40_Ip_GetCrc(); }注意我在代码里故意把字节拼成高位在前Big-endian这和标准CRC算法的输入顺序通常是对应关系。具体你工程里怎么拼以你上位机用的算法为准关键是两边一致。还有一种更隐蔽的情况C40_Ip的CRC计算不会在你读取CRC值时自动清零。如果你分多次调用C40_Ip_WriteData中间去做了别的事回来忘记重新初始化种子算出的CRC就是前面所有数据的累积结果。所以每次开始一段新数据的CRC计算时一定要重新调用C40_Ip_Init或等价地重置种子。4.3 配合Flash写入做写后校验这一节我讲讲怎么把C40_Ip真正接进Flash刷写链路。我的做法分三步。第一步在把固件数据写入Flash之前先在RAM里用C40_Ip计算一遍整个固件包的CRC把这个值保存下来。第二步调用Flash_WriteBytes把数据写入Flash。第三步从Flash地址把数据读回来再次用C40_Ip计算CRC。如果两个CRC值一致说明数据写入没问题如果不一致说明Flash写入过程中某个环节出了问题需要标记升级失败并触发回滚。一个不容忽视的细节是从Flash读数据做CRC计算时要确保FMC没有被配置成“读保护”模式否则读回来的数据全是0xFF。其次Flash读取可能受cache影响刚写完的数据如果没有及时invalidate cache读回来可能是旧值。S32K344的Flash读取路径上有cache调试时这个问题很容易被忽略。我建议在写完Flash后显式做一次cache clean/invalidate操作再读。我实测下来的一段核心流程是这样的status_t Flash_ProgramWithCrc(uint32_t dstAddr, const uint8_t *srcData, uint32_t len) { uint32_t crcBefore, crcAfter; /* 1. 先算源数据CRC */ crcBefore Crc_CalcOverBytes(srcData, len); /* 2. 保证目标扇区已擦除这里省略擦除代码 */ /* 3. 写入 */ status_t st Flash_WriteBytes(dstAddr, srcData, len); if (st ! STATUS_SUCCESS) { return st; } /* 4. 从Flash读回并算CRC */ crcAfter Crc_CalcOverBytes((const uint8_t *)dstAddr, len); if (crcBefore ! crcAfter) { return STATUS_ERROR; } return STATUS_SUCCESS; }有人可能会抬杠写上一步明明已经等着命令完成了为什么还要再算一遍CRC我只能说在Bootloader这种“写坏了就变砖”的场景里多一层校验永远不亏。曾经我遇到过写入时总线被DMA抢占导致数据错乱的情况如果只看状态标志根本查不出来但CRC一比对就立刻暴露了。5. 实测数据与量产建议5.1 我测到的典型性能数据在我们项目的实际运行频率下S32K344的PFlash擦一个8KB扇区耗时大概是20毫秒左右写入8字节大约耗时几百微秒。如果按512字节的App包来算光是擦除加写入大概需要几百毫秒的量级配合上位机通信一次小固件升级大概在1到2秒内完成。这个数据供你参考不同时钟配置下会有偏差。用C40_Ip算4096字节数据的CRC32耗时在微秒级。对比之前S32K146上软件查表法算同样长度数据快了一个数量级。这也是我坚持用C40_Ip而不是自己在RAM里写软件CRC的根本原因。5.2 量产时建议增加的防护逻辑如果你不是做Demo而是准备把这段代码放进真正的量产Bootloader里我建议你至少增加以下几层防护第一Flash写入期间关中断或使用临界区。虽然S32K344的命令序列可以被打断后恢复但在嵌套中断场景下很容易出现时序错乱。我在量产代码里直接把Flash驱动执行放到临界区里代价是擦写期间中断延迟稍微变长但换来的是写入可靠性。第二掉电保护。固件升级最怕写到一半掉电。简单的方案是采用A/B分区加标志位当前使用A分区新固件写入B分区全部写完且CRC校验通过后再把启动标志切换到B分区。这样即使写B分区时掉电下次启动仍然能从A分区正常运行。第三Flash状态寄存器要彻底检查。不要只看一个“命令完成”标志要把访问错误、保护违规、校验错误这些标志全部读一遍一旦有任何异常立刻终止流程并上报。5.3 最后的调试心得这套流程从开始调试到稳定运行我大概用了两周。回头看最耗时间的不是函数本身的实现而是各种“隐性条件”的排查。比如C40_Ip的字节序问题、Flash保护位的一次性特性、FMC命令期间不能访问Flash等等。这些问题没有哪个是看一眼代码就能发现的都是一步一步打印寄存器状态、对照手册才定位到的。如果你也是第一次在S32K344上玩Flash我建议你准备两样东西一是S32K344参考手册的Flash和CRC章节最好打印出来二是劳特巴赫调试器能够直接看FMC和C40_Ip寄存器状态。有了这两个工具排查效率会高很多。我个人在实际操作中的体会是Flash驱动这种看似底层的代码最忌讳“不动脑筋照抄例程”。每调一行代码都问一句“这个寄存器的值为什么会是这样”才能真正掌握它。希望这篇实战记录能帮你少走几步弯路顺利把S32K344的Flash操作跑起来。
返回列表