嵌入式硬件CRC原理与应用:以TI CC323x为例的差错校验实战
2026/7/19 7:40:05 网站建设 项目流程

1. 项目概述:从数据完整性到嵌入式实践

在嵌入式系统开发中,数据完整性是系统可靠性的基石。无论是通过无线网络传输一个数据包,还是向Flash存储器写入一段固件,任何一位数据的错误都可能导致功能失效、系统崩溃甚至安全事故。想象一下,你正在通过Wi-Fi更新智能家居设备的固件,如果传输过程中某个字节因干扰而翻转,设备可能就此“变砖”。如何高效、可靠地检测这类错误?循环冗余校验(CRC)技术就是为此而生的。

CRC本质上是一种基于多项式除法的差错检测码。它不像简单的奇偶校验那样只能检测奇数个错误,而是能高概率地捕捉到数据块中任意位置、任意模式的突发性错误。其核心思想是,发送方将待传输的数据视为一个巨大的二进制数,用一个预先约定的“生成多项式”去除它,得到的余数(即CRC校验码)随数据一同发送。接收方用同样的多项式对接收到的数据(含CRC码)再做一次计算,若余数为零(或某个特定值),则认为数据在传输过程中极大概率是完整的。

在资源受限的嵌入式环境中,用软件逐位计算CRC会消耗大量宝贵的CPU周期。因此,现代微控制器(MCU)普遍集成了硬件CRC加速引擎。本文将以德州仪器(TI)的SimpleLink™ CC323x系列无线MCU为例,深入剖析其内置CRC模块的原理、配置方法及实战应用。这款芯片广泛用于物联网设备,其CRC模块支持多种国际标准,并能与AES/DES加密模块协同工作,是构建高可靠性嵌入式系统的绝佳样板。无论你是正在调试通信协议,还是确保固件升级安全,理解并掌握这个硬件模块都将事半功倍。

2. CRC核心原理与算法深度解析

2.1 多项式除法的数学本质

很多人初次接触CRC时,会被“多项式”这个词吓到。其实,我们可以把它理解成一种特殊的“除法”游戏规则。这里的关键在于,所有的运算都是在二进制模2运算下进行的,也就是不考虑进位和借位的异或(XOR)运算。

生成多项式(Generator Polynomial)是CRC算法的核心,它决定了校验的强度和特性。一个多项式如0x1021(CRC-16-CCITT) 或0x04C11DB7(CRC-32-IEEE),其二进制形式中的每一个‘1’都代表多项式的一项。例如,0x1021的二进制是0001 0000 0010 0001,对应的多项式是 ( x^{16} + x^{12} + x^5 + 1 )。计算时,数据帧末尾会先补上多项式位数减一个‘0’(对于16位CRC就是16个0),然后用这个多项式去“除”它。

这个过程更贴切的描述是:数据位流被当作一个多项式的系数,与生成多项式进行模2除法。最终得到的余数,其位数比生成多项式少一位(因为最高次项系数为1),这个余数就是CRC校验码。硬件CRC引擎的本质,就是一个高度优化、能够并行处理的移位寄存器网络,它实时地完成这个多项式除法运算。

2.2 关键参数与变体:为什么有这么多CRC标准?

不同的应用场景催生了不同的CRC标准,主要区别在于几个关键参数:

  1. 生成多项式:如上所述,这是根本区别。例如,CRC32-IEEE 802.3使用0x04C11DB7,而CRC32C (Castagnoli) 使用0x1EDC6F41,后者在存储系统(如SATA, iSCSI)中因更好的错误检测能力而被广泛采用。
  2. 初始值(Initial Value):计算开始前,CRC寄存器的初始值。可以是全0、全1,或一个特定值。这影响了最终结果,发送和接收双方必须一致。
  3. 输入/输出反转(Reflect In/Out):有些协议规定,在计算前要将每个输入字节的比特位顺序反转(如最低有效位LSB先处理),或者在输出最终结果前,将整个CRC值的比特位反转。这通常是为了匹配某些硬件串行传输的位序。
  4. 结果异或值(Final XOR Value):计算完成后,将CRC结果与一个固定值(如0xFFFFFFFF)进行异或。这可以确保全零数据流的CRC码不为零,便于检测。

以CC323x支持的四种形式为例:

  • CRC16-CCITT (0x1021):初始值常为0xFFFF,输入输出不反转。用于X.25, HDLC, XMODEM等老式协议。
  • CRC16-IBM (0x8005):初始值常为0x0000,输入输出反转。用于Modbus, USB数据包等。
  • CRC32-IEEE (0x04C11DB7):初始值0xFFFFFFFF,输入输出反转,结果异或0xFFFFFFFF。这是以太网(IEEE 802.3)、ZIP、PNG等领域的霸主。
  • CRC32C (0x1EDC6F41):初始值0xFFFFFFFF,输入输出反转,结果异或0xFFFFFFFF。因其在SCSI、SCTP、EXT4等系统中的优异性能而成为新宠。

注意:选择哪种CRC标准,完全取决于你要对接的协议或系统规范。在嵌入式开发中,最常见的“坑”就是参数配置不匹配,导致本地计算的CRC值与对方不匹配。务必仔细查阅相关协议文档。

2.3 硬件加速的优势:为何要交给硬件?

软件实现CRC通常采用查表法,虽然比直接计算快,但仍需CPU参与每次数据的搬运和查表操作。硬件CRC模块的优势是颠覆性的:

  • 单周期计算:如CC323x的CRC引擎,在数据写入输入寄存器后,一个时钟周期内即可更新结果。这对于高速数据流(如Wi-Fi MAC层帧校验)至关重要。
  • 零CPU开销:CPU只需写入数据、读取结果,计算本身不占用CPU指令周期,解放出来的算力可以处理更复杂的应用逻辑。
  • 低功耗:专用硬件电路比CPU执行软件算法功耗低得多,对电池供电的物联网设备意义重大。
  • 支持流式数据:可以连续不断地向引擎喂入数据(字或字节),适合DMA配合,实现后台无感校验。

3. CC323x CRC引擎功能详解与寄存器映射

CC323x的CRC模块是一个高度可配置的协处理器,我们结合其寄存器手册,将其能力拆解开来理解。

3.1 核心功能寄存器剖析

模块通过四个主要寄存器进行控制,基地址通常为0x4403 0000(属于加密子系统)。

1. CRC控制寄存器 (CRCCTRL, Offset 0xC00)这是大脑,所有关键配置都在这里。

  • TYPE[3:0]:选择算法。0x0(0x8005),0x1(0x1021),0x2(0x4C11DB7),0x3(0x1EDC6F41),分别对应前述四种标准。还有一个0x8模式用于TCP校验和(一种简单的加法校验和)。
  • INIT[1:0]:初始化控制。00使用SEED寄存器的值;10初始化为全0;11初始化为全1。这直接对应了不同CRC标准的初始值需求。
  • SIZE:数据输入大小。0为32位字模式,1为8位字节模式。在字节模式下,向数据输入寄存器写入时,只有最低字节被用于计算。
  • ENDIAN[1:0]:字节序配置。这是嵌入式开发中极易出错的地方。它控制一个32位字内部的字节顺序如何被送入CRC计算电路。假设你有一个32位数据0x12345678在内存中按小端序存放(即低地址存0x78),那么:
    • 00:不变,即{0x12, 0x34, 0x56, 0x78}被处理。
    • 01:半字内字节交换,即{0x34, 0x12, 0x78, 0x56}
    • 10:半字交换,即{0x56, 0x78, 0x12, 0x34}
    • 11:半字内字节交换且半字交换,即{0x78, 0x56, 0x34, 0x12}。 你必须根据你的数据在内存中的实际布局和协议要求的处理顺序来配置此字段。
  • BR:输入位反转。置1时,在计算前,每个输入字节的比特位(bit)会被反转(MSB变LSB)。这对应了那些需要“输入反转”的CRC标准。
  • OBR:输出位反转。置1时,在最终输出前,将CRC结果的每个字节的比特位反转。
  • RESINV:结果取反��置1时,将最终CRC结果按位取反(与0xFFFFFFFF异或)。这对应了“结果异或值”参数。

2. CRC种子/上下文寄存器 (CRCSEED, Offset 0xC10)这是一个多功能寄存器。在计算开始前,如果INIT字段设置为00,你需要将初始值写入这里。在计算过程中以及计算完成后,这个寄存器会实时更新,存放当前最新的CRC中间结果或最终结果。你可以随时读取它来获取进度。

3. CRC数据输入寄存器 (CRCDIN, Offset 0xC14)这是数据喂入口。你将要计算CRC的数据流,按照配置的SIZE(字或字节)和ENDIAN顺序,依次写入这个寄存器的DATAIN字段。每写入一次,CRCSEED寄存器中的值就会立即被更新。

4. CRC后处理结果寄存器 (CRCRSLTPP, Offset 0xC18)这是一个只读寄存器。当CRC计算完成,并且你配置了后处理选项(如OBR或RESINV)后,最终的结果会出现在这里。如果你没有启用任何后处理,那么CRCSEED和CRCRSLTPP的值应该是一样的。

3.2 数据流与字节序的实战理解

手册中关于数据流喂入的例子非常经典,值得仔细推敲。假设你有一串字节数据D0, D1, D2, D3, D4, D5...存储在内存的连续地址中。

  • 在字节模式(SIZE=1)下:你需要将每个字节放入一个32位字的最低字节(LSB),然后写入CRCDIN。即写入序列为:0x000000D0,0x000000D1,0x000000D2...
  • 在字模式(SIZE=0)下:你需要每4个字节组成一个32位字,然后写入。但关键在于这4个字节在字中的排列顺序,这由ENDIAN配置和内存中的字节序共同决定。

假设内存是小端序(Little-Endian),地址从低到高存放D0, D1, D2, D3。那么CPU读取一个32位字,得到的值是D3<<24 | D2<<16 | D1<<8 | D0。如果你希望CRC引擎按D0, D1, D2, D3的顺序处理,就需要设置ENDIAN为11(字节和半字都交换),这样硬件会在内部将D3, D2, D1, D0重排为D0, D1, D2, D3

实操心得:字节序问题在跨平台、跨协议通信时是“头号杀手”。我的调试方法是:先用一个已知的短数据(如字符串"123456789")和标准的CRC计算工具(如在线CRC计算器)算出结果。然后在代码中,将数据按我认为的方式写入CRC引擎,读取结果进行比对。如果不匹配,不要盲目尝试,而是系统地遍历ENDIAN和BR的组合(总共也没几种),总能找到正确的配置。记录下这个配置,它就是你这个项目中的“黄金法则”。

4. 嵌入式系统中的CRC配置与驱动实现

理解了原理和寄存器,我们来看如何在真实的嵌入式C语言项目中,编写一个可靠、易用的CRC驱动。

4.1 硬件初始化与基础配置流程

首先,需要启用CRC模块所在的加密子系统的时钟。在CC323x中,这通过设置CRYPTOCLKEN寄存器的相应位完成。

// 1. 启用CRC模块时钟 (假设CRYPTOCLKEN寄存器地址为0x440250B8) volatile uint32_t *crypto_clken = (volatile uint32_t *)0x440250B8; *crypto_clken |= (1 << 0); // 设置R0位,启用时钟 // 2. 定义CRC寄存器基址 #define CRC_BASE 0x44030000 typedef struct { volatile uint32_t CRCCTRL; // 0x00 volatile uint32_t reserved[3]; // 0x04-0x0C volatile uint32_t CRCSEED; // 0x10 volatile uint32_t CRCDIN; // 0x14 volatile uint32_t CRCRSLTPP; // 0x18 } CRC_Registers; CRC_Registers *crc = (CRC_Registers *)CRC_BASE; // 3. 配置CRCCTRL寄存器 uint32_t ctrl_config = 0; ctrl_config |= (2 << 0); // TYPE = 2, 选择CRC32-IEEE 0x04C11DB7 ctrl_config |= (3 << 13); // INIT = 3, 初始化为全1 (0xFFFFFFFF) ctrl_config |= (0 << 12); // SIZE = 0, 字模式 ctrl_config |= (0 << 4); // ENDIAN = 0, 根据实际情况调整 ctrl_config |= (0 << 7); // BR = 0, 输入不反转 ctrl_config |= (0 << 8); // OBR = 0, 输出不反转 ctrl_config |= (0 << 9); // RESINV = 0, 结果不取反 crc->CRCCTRL = ctrl_config; // 4. 如果INIT设置为0,则需要手动写入种子值 // crc->CRCSEED = 0xFFFFFFFF; // 例如CRC32的常见初始值

4.2 数据计算函数封装

一个好的驱动应该提供易于调用的接口。下面是一个计算数据块CRC32的函数示例:

/** * @brief 计算一块数据的CRC32校验值 (使用IEEE 802.3多项式) * @param data: 指向数据缓冲区的指针 * @param len: 数据长度(以字节为单位) * @return: 计算得到的32位CRC值 */ uint32_t calculate_crc32(const uint8_t *data, uint32_t len) { uint32_t i; const uint32_t *word_ptr; uint32_t words_remaining; uint8_t bytes_remaining; // 确保CRC模块已初始化(略去初始化代码) // 重置CRC种子为初始值(通过重新配置INIT或直接写寄存器) crc->CRCCTRL = (crc->CRCCTRL & ~(3 << 13)) | (3 << 13); // 强制INIT=3,全1 // 首先,以字(4字节)为单位处理数据,效率最高 word_ptr = (const uint32_t *)data; words_remaining = len / 4; for (i = 0; i < words_remaining; i++) { // 注意:这里直接写入字,假设数据已经是内存中的原生格式。 // 如果协议要求的字节序与内存序不同,需要在此处或通过ENDIAN配置进行调整。 crc->CRCDIN = word_ptr[i]; } // 处理剩余的不是4整数倍的字节 bytes_remaining = len % 4; if (bytes_remaining > 0) { const uint8_t *byte_ptr = (const uint8_t *)(&word_ptr[words_remaining]); // 临时切换到字节模式?不,更简单的方法:将剩余字节组合成一个字写入。 // 但需注意内存访问对齐和填充值。安全做法是逐个字节写入。 // 为了效率,我们可以临时组合。 uint32_t last_word = 0; for (i = 0; i < bytes_remaining; i++) { last_word |= ((uint32_t)byte_ptr[i]) << (i * 8); } crc->CRCDIN = last_word; // 写入组合后的字 // 注意:如果数据总长度不是4的倍数,最后这个“字”的有效部分只有前几个字节。 // CRC引擎会根据SIZE=0(字模式)处理整个32位。这通常没问题,因为多余的填充字节(0)不影响前面有效字节的CRC结果。 // 更严谨的做法是在写入前,将SIZE切换为字节模式,然后逐个写入剩余字节。 } // 读取最终结果(从CRCSEED或CRCRSLTPP读取,取决于后处理配置) // 假设我们未启用OBR和RESINV,且需要标准的CRC32输出(与0xFFFFFFFF异或) // 标准CRC32的输出需要:结果取反(与0xFFFFFFFF异或) return ~(crc->CRCSEED); // 注意:如果配置了RESINV,这里就不需要再取反了。 }

4.3 与DMA协同工作的高效模式

在需要处理大量数据(如网络数据包、Flash编程验证)时,让CPU逐个字节/字地写CRC引擎是巨大的浪费。此时,DMA(直接内存访问)是绝配。

思路:将CRC引擎的数据输入寄存器(CRCDIN)配置为DMA的外设目标地址。DMA控制器可以自动将内存中的数据块搬运到CRC引擎,全程无需CPU干预。

// 伪代码示例:配置DMA从内存搬运数据到CRC->CRCDIN void setup_dma_for_crc(uint32_t src_addr, uint32_t data_size_bytes) { // 1. 配置DMA通道源地址 = src_addr // 2. 配置DMA通道目标地址 = (uint32_t)&(crc->CRCDIN) // 3. 配置传输数据量 = data_size_bytes / 4 (如果字模式) // 4. 配置源地址递增,目标地址固定(外设寄存器) // 5. 启动DMA传输 // 6. (可选) 使能DMA传输完成中断,在中断中读取CRC结果 }

注意事项:使用DMA时,必须确保数据对齐和字节序与CRC引擎的配置(SIZE, ENDIAN)完全匹配。例如,如果CRC配置为字模式,DMA每次传输也必须是32位(4字节)。如果数据源不是4字节对齐的,可能需要CPU先处理开头几个字节,或者使用支持非对齐访问的DMA控制器。

5. 在嵌入式系统中的应用场景与实战案例

CRC在嵌入式系统中无处不在,下面结合CC323x的特性,看几个典型应用。

5.1 应用场景一:无线网络协议栈中的数据帧校验

在CC323x的Wi-Fi协议栈中,MAC层帧和某些管理帧的完整性由硬件CRC自动保障。但应用层开发者也可能需要为自己定义的上层协议(例如通过TCP/UDP传输的私有数据包)添加CRC校验。

实战步骤

  1. 发送端
    • 在组包时,预留出CRC字段(例如4字节)。
    • 调用calculate_crc32()函数,计算整个数据包(不包括CRC字段本身)的CRC值。
    • 将计算出的CRC值填入预留字段。
    • 通过Socket API发送数据包。
  2. 接收端
    • 收到数据包后,提取出数据部分和发送端填入的CRC值。
    • 对数据部分(不包括接收到的CRC字段)再次调用calculate_crc32()
    • 将计算得到的新CRC值与接收到的CRC值进行比较。
    • 如果相等,数据包有效;如果不相等,则说明传输过程中发生了错误,应丢弃该包或请求重传。

优势:利用硬件CRC,即使对长达数百字节的数据包进行校验,开销也微乎其微,确保了无线通信的可靠性。

5.2 应用场景二:Flash存储器编程与验证

嵌入式系统的固件通常存储在Flash中。在编程(烧录)过程中,或系统启动时,验证Flash内容的完整性至关重要。CC323x的Flash编程接口(FMA, FMD, FMC寄存器)本身不包含自动CRC校验,但我们可以利用CRC引擎来实现。

实战案例:固件升级镜像校验假设我们通过OTA(空中升级)下载了一个新的固件镜像到外部Flash或网络缓冲区。在将其编程到主Flash前,必须验证其完整性。

  1. 计算镜像CRC:将接收到的整个固件镜像(或每个独立的段)作为数据源,使用CRC32C(因其在存储系统中更优)计算其校验和。
  2. 比对期望值:固件发布时,构建系统会生成一个标准的CRC值,并通常将其附加在镜像文件的末尾或头部的元数据中。将计算出的CRC与这个预存的期望值进行比对。
  3. 编程与二次验证:即使下载校验通过,在编程到内部Flash后,最好再读取出来计算一次CRC,确保编程过程没有出错。这可以利用CC323x的Flash读取功能和CRC引擎快速完成。
// 伪代码:验证Flash中一段数据的CRC bool verify_flash_crc(uint32_t flash_start_addr, uint32_t length, uint32_t expected_crc) { uint32_t calculated_crc; uint32_t temp_buffer[256]; // 局部缓冲区 uint32_t words_to_read; // 配置CRC引擎为CRC32C,初始值等 setup_crc_engine(CRC_TYPE_32C, INIT_ALL_ONES, ...); words_to_read = length / 4; for(uint32_t i = 0; i < words_to_read; i += 256) { uint32_t chunk_size = (words_to_read - i) > 256 ? 256 : (words_to_read - i); // 从Flash读取数据到缓冲区 (假设有flash_read函数) flash_read(flash_start_addr + i*4, temp_buffer, chunk_size); // 将缓冲区数据喂入CRC引擎 (可以优化为DMA) for(uint32_t j = 0; j < chunk_size; j++) { crc->CRCDIN = temp_buffer[j]; } } // 处理可能的剩余字节(略) calculated_crc = crc->CRCRSLTPP; // 读取后处理结果 return (calculated_crc == expected_crc); }

5.3 应用场景三:与安全模块(AES/DES)协同工作

CC323x的CRC模块与AES/DES加密模块同属加密子系统。这种架构允许在数据加解密流水线中无缝集成完整性校验。

一个典型的安全数据流

  1. 明文数据 ->CRC引擎计算原始校验和1。
  2. 明文数据 + 校验和1 ->AES加密引擎进行加密。
  3. 加密后的数据通过信道传输。
  4. 接收端收到数据 ->AES解密引擎进行解密。
  5. 解密出的数据(应包含明文和校验和1)->CRC引擎重新计算校验和2。
  6. 比较校验和1与校验和2。如果一致,则证明数据在传输过程中既未被篡改(完整性),也成功解密(机密性)。

这种“加密+CRC”的组合,为物联网设备间的安全通信提供了双重保障。硬件级的集成使得这一系列操作可以在极短的时间内完成,满足实时性要求。

6. 调试技巧、常见问题与避坑指南

即使理解了所有原理,实际调试CRC时依然会遇到各种问题。以下是我从多个项目中总结出的经验。

6.1 问题排查清单:CRC值不匹配

当你的代码计算的CRC值与标准工具或对方设备计算的值不匹配时,请按以下清单逐一排查:

问题类别可能原因检查点与解决方法
多项式/算法错误选错了CRC标准。确认协议要求的CRC类型(CRC16-CCITT, CRC32等)。核对CRCCTRL.TYPE字段配置。
初始值错误初始种子配置错误。检查CRCCTRL.INIT字段。如果是自定义种子,确认写入CRCSEED寄存器的值是否正确,且是否在写入数据完成。
数据输入错误1. 数据本身有误。
2. 数据长度不对。
3. 包含了不该计算的部分。
1. 用十六进制查看器确认发送/计算的数据缓冲区内容。
2. 确认长度参数是否包含了填充字节或结束符。
3. 明确CRC计算范围:是从帧头开始,还是从某个特定字段之后?是否包含CRC字段本身(通常不包含)?
字节序/位序错误1. 字节顺序(Endianness)错误。
2. 位反转(Bit Reflection)配置错误。
这是最常见的问题!
1. 系统性地测试CRCCTRL.ENDIAN的所有4种模式。
2. 测试CRCCTRL.BR(输入反转)和OBR(输出反转)的组合。记住,CRC32-IEEE通常需要输入和输出都反转。
后处理错误结果未进行最终的异或操作。检查CRCCTRL.RESINV位。对于CRC32-IEEE,最终结果需要与0xFFFFFFFF异或。如果RESINV置1,则硬件已做;如果RESINV为0,则需在软件中手动取反 (~result)。
寄存器访问时序在CRC引擎忙时写入数据或读取结果。虽然CC323x的CRC是单周期计算,但在连续写入大量数据时,仍需确保写入操作完成。通常连续写入即可,但读取最终结果前可加入短暂延时或检查状态位(如果提供)。
数据对齐与填充在字模式下,数据长度不是4的倍数,末尾处理不当。如4.2节所述,对于末尾零头,要么切换为字节模式处理,要么组合成字但清楚填充字节的影响。建议使用字节模式处理零头最安全。

6.2 调试方法:隔离与比对

  1. 使用已知向量测试:找一组标准测试数据(如ASCII字符串 "123456789")和其公认的CRC结果。用你的驱动计算这组数据,与标准结果比对。这是验证基础配置是否正确的黄金标准。
  2. 分步调试
    • 第一步:只计算一个字节(如0x00),检查结果。很多CRC算法对全0数据的CRC有特定值。
    • 第二步:计算一个短字符串。
    • 第三步:再计算一个长数据块。
    • 通过分步,可以定位问题是出在开头、中间还是结尾的处理上。
  3. 逻辑分析仪/调试器观察:如果条件允许,用调试器实时查看写入CRCDIN寄存器的值,以��CRCSEED寄存器的变化。确保写入的数据序列完全符合你的预期。
  4. 模拟对方行为:如果是在通信中,尝试用PC上的脚本(如Python的binascii.crc32)或串口工具计算CRC,与设备端结果比对。确保两端算法参数完全一致。

6.3 性能优化要点

  • 优先使用字模式:32位字模式比8位字节模式吞吐量高得多。尽量将数据组织成4字节对齐的块进行处理。
  • 启用DMA:对于大数据块,DMA是性能倍增器。将CRC引擎配置为DMA的外设,让数据搬运脱离CPU。
  • 合理选择多项式:在满足协议要求的前提下,了解不同多项式的性能。CRC32C在部分硬件上有更优化的实现。
  • 避免频繁重置:如果连续计算多个数据块,且它们需要相同的初始值(如全0),不必在每次计算后都重置种子。可以在计算完一个块后直接读取结果,然后为下一个块写入新的数据,CRC引擎会基于上一个结果继续计算(即链式计算),这在某些流式处理中很有用。

6.4 一个真实的“坑”:Flash验证中的地址对齐

在一次固件验证项目中,我们计算整个Flash区域的CRC。代码工作正常,直到有一次更新后,验证总是失败。排查后发现,Flash的起始地址是0x01000000,我们的代码从该地址开始,按字读取并计算CRC。问题出在:Flash的某些区域(如配置区)可能不允许按字读取,或者存在未使用的间隙。当DMA或CPU尝试访问这些区域时,会引发硬件错误或读取到错误数据。

解决方案:在计算Flash CRC前,必须明确知晓Flash的有效区域布局。只对存储了有效程序/数据的扇区(Sector)进行计算,跳过保留区、配置区等。可以参考链接器脚本(Linker Script)中定义的段(Section)地址和长度。

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

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

立即咨询