1. 为什么MRAM在工业嵌入式场景里越来越受关注
1.1 从EEPROM和Flash的痛点说起
做过工业设备的人大概都有过这种经历:设备在现场跑了半年,突然某天断电重启后参数全丢了,或者日志文件出现几个字节的乱码。排查半天,最后发现是EEPROM的擦写寿命到了,或者Flash在掉电瞬间正在写数据,导致整个扇区数据损坏。这类问题在实验室里很难复现,但到了现场就是批量事故。
传统的非易失存储方案主要有两条路:EEPROM和NOR Flash。EEPROM的优势是字节级可寻址、擦写次数高(通常100万次左右),但容量小、速度慢、功耗也不低。NOR Flash容量大、成本低,但擦写次数只有10万次左右,而且写入前必须先擦除整个扇区,擦除时间长,掉电保护逻辑复杂。NAND Flash就更不用说了,坏块管理和ECC校验能把软件复杂度拉高一个量级。
MRAM(磁阻随机存取存储器)的出现,恰好卡在了一个很舒服的位置。它兼具SRAM的读写速度和Flash的非易失特性,擦写次数可以达到10的14次方以上,写入不需要先擦除,字节级可寻址,掉电数据保持时间超过20年。对于工业设备里那些需要频繁记录运行参数、故障日志、累计计数的场景,MRAM几乎是目前最省心的选择。
1.2 MR25H40CDF这颗芯片的定位
MR25H40CDF是Everspin公司推出的一款4Mbit(512KB)容量的SPI接口MRAM芯片。4Mbit这个容量在工业参数存储场景里非常够用——你可以存几千条故障记录,或者几百组设备配置参数,还能留出空间做数据备份。
它的SPI接口支持最高40MHz时钟,模式0和模式3都兼容,这意味着STM32的硬件SPI可以直接对接,不需要额外的电平转换或时序调整。工作电压范围2.7V到3.6V,工业级温度范围-40°C到+85°C,和STM32F746VG的工业级规格完全匹配。
封装方面,MR25H40CDF提供8引脚SOIC和DFN两种封装。SOIC封装手工焊接难度低,适合打样和小批量;DFN封装体积小,适合空间受限的板卡设计。选型的时候要根据你的PCB空间和焊接工艺来定。
1.3 STM32F746VG作为主控的适配性分析
STM32F746VG是ST的Cortex-M7系列高性能MCU,主频216MHz,带1MB Flash和320KB RAM,外设资源丰富。用它来驱动MR25H40CDF,主要看中几点:
第一,它的SPI外设支持最高50MHz时钟,驱动MR25H40CDF的40MHz上限绰绰有余。第二,F746VG有多个SPI接口,可以分配一个专用的SPI给MRAM,避免和其他外设争抢总线。第三,F746VG的HAL库对SPI的DMA支持很完善,批量读写MRAM时可以用DMA搬运数据,CPU占用率极低。
另外,F746VG的供电也是3.3V,和MR25H40CDF可以直接对接,不需要额外的电平转换芯片。这一点在PCB布局上省了不少事。
注意:MR25H40CDF的VDD和VDDQ是分开的,如果只做3.3V单电源设计,把两个引脚都接到3.3V即可。但如果你的系统里有1.8V的逻辑电平,VDDQ可以单独接1.8V,实现电平匹配。
2. 硬件连接与SPI时序关键细节
2.1 引脚连接方案
MR25H40CDF的引脚定义很标准,和常见的SPI Flash基本一致:
| 引脚编号 | 引脚名称 | 功能说明 | 连接目标 |
|---|---|---|---|
| 1 | CS | 片选,低有效 | STM32 GPIO或SPI_NSS |
| 2 | SO | 数据输出(MISO) | STM32 SPI_MISO |
| 3 | WP | 写保护,低有效 | 接VDD或GPIO控制 |
| 4 | VSS | 地 | GND |
| 5 | SI | 数据输入(MOSI) | STM32 SPI_MOSI |
| 6 | SCK | 时钟 | STM32 SPI_SCK |
| 7 | HOLD | 保持,低有效 | 接VDD或GPIO控制 |
| 8 | VDD | 电源 | 3.3V |
WP和HOLD这两个引脚,如果不需要硬件写保护和总线暂停功能,直接上拉到VDD就行。但我在实际项目里建议把WP用GPIO控制,这样可以在固件升级或参数恢复时临时解除写保护,增加一层软件之外的安全保障。
2.2 硬件片选与软件片选的取舍
STM32的SPI外设支持硬件NSS和软件片选两种模式。硬件NSS的问题是,当SPI配置为主模式时,NSS引脚需要保持高电平,一旦拉低就会触发模式错误。而且硬件NSS在多从机场景下需要额外的逻辑电路做片选译码。
我的建议是:直接用软件片选。把MR25H40CDF的CS接到一个普通GPIO上,在SPI传输前后手动拉低和拉高。这样做的好处是时序完全可控,不会出现硬件NSS那种意外触发的问题。代价是每次传输多两条GPIO操作指令,但对于MRAM这种高速器件来说,这点开销可以忽略。
具体操作上,CS拉低之后要等至少5ns才能开始SCK翻转,CS拉高之前要确保最后一个SCK边沿已经完成。STM32的GPIO翻转速度在100MHz以上,这个时序余量很充足。
2.3 SPI模式选择与时钟配置
MR25H40CDF支持SPI模式0(CPOL=0,CPHA=0)和模式3(CPOL=1,CPHA=1)。两种模式的区别在于空闲时钟电平和数据采样边沿。
模式0:SCK空闲为低,数据在SCK上升沿采样,下降沿变化。 模式3:SCK空闲为高,数据在SCK上升沿采样,下降沿变化。
我一般选模式0,因为这是大多数SPI器件的默认模式,调试的时候用逻辑分析仪抓波形也更容易对照。STM32CubeMX里配置SPI时,把CPOL设为Low,CPHA设为1Edge,就是模式0。
时钟频率方面,MR25H40CDF的最高时钟是40MHz。STM32F746VG的SPI2和SPI3挂在APB1上,最高时钟是54MHz;SPI1挂在APB2上,最高108MHz。分频之后,SPI1可以轻松输出40MHz以下的任意频率。我通常设成20MHz,留出足够的时序余量,同时读写速度也完全够用。
提示:如果你用DMA传输,SPI时钟可以拉到40MHz。但如果是中断方式逐字节传输,建议降到10MHz以下,否则中断响应延迟可能导致CS提前拉高,传输出错。
2.4 PCB布局的注意事项
SPI总线在高速运行时,PCB布局对信号完整性的影响很大。几条经验:
SCK走线尽量短,避免和其他信号线平行走长距离。如果SCK和MOSI/MISO必须平行走线,中间加地线隔离。CS走线可以稍长,但不要和SCK靠得太近,避免串扰导致误触发。
去耦电容方面,MR25H40CDF的VDD引脚旁边放一个100nF的陶瓷电容,再并一个1uF的钽电容。这两个电容要尽量靠近芯片引脚,走线短而粗。我见过不少板子因为去耦电容放得太远,高速读写时出现随机位错误。
如果MRAM和MCU之间的距离超过10cm,建议在SCK和CS上串一个22Ω到33Ω的电阻,做阻抗匹配,减少反射。
3. STM32CubeMX配置与底层驱动实现
3.1 CubeMX中的SPI参数设置
打开STM32CubeMX,选好STM32F746VG之后,找到SPI1或SPI2,按下面的参数配置:
- Mode:Full-Duplex Master
- Hardware NSS Signal:Disable
- Frame Format:Motorola
- Data Size:8 Bits
- First Bit:MSB First
- Prescaler:根据时钟树计算,目标20MHz
- Clock Polarity:Low
- Clock Phase:1 Edge
- CRC Calculation:Disabled
- NSS Pulse Mode:Disabled
- TI Mode:Disabled
Prescaler的计算方法:假设SPI1挂在APB2上,时钟108MHz,目标20MHz,分频系数就是108/20=5.4,取最近的2的幂次分频,即8分频,实际时钟13.5MHz。如果目标40MHz,108/40=2.7,取4分频,实际27MHz。要精确到40MHz,可以把APB2时钟调到80MHz,然后2分频。
GPIO方面,CS引脚配置为GPIO_Output,初始电平设为High。SCK、MISO、MOSI配置为Alternate Function,Pull设为No Pull,Speed设为Very High。
3.2 MRAM指令集与操作时序
MR25H40CDF的指令集和标准SPI Flash类似,但有几个关键区别:
| 指令名称 | 指令码 | 功能说明 | 地址字节数 |
|---|---|---|---|
| WREN | 0x06 | 写使能 | 0 |
| WRDI | 0x04 | 写禁止 | 0 |
| RDSR | 0x05 | 读状态寄存器 | 0 |
| WRSR | 0x01 | 写状态寄存器 | 0 |
| READ | 0x03 | 读数据 | 3 |
| WRITE | 0x02 | 写数据 | 3 |
和Flash最大的区别是:MRAM的WRITE指令不需要先擦除。你直接发WRITE指令,跟3字节地址,然后连续写入数据就行。写入过程中不需要等待擦除周期,每个字节写入时间就是SPI时钟周期。
状态寄存器的bit0是WIP(Write In Progress)位。MRAM的写入速度极快,通常SPI传输完成后WIP就已经清零了。但为了保险,写完最后一个字节后还是读一下状态寄存器,确认WIP为0再拉高CS。
3.3 底层驱动代码实现
先定义CS引脚的操作宏:
#define MRAM_CS_LOW() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET) #define MRAM_CS_HIGH() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET)写使能函数:
void MRAM_WriteEnable(void) { uint8_t cmd = 0x06; MRAM_CS_LOW(); HAL_SPI_Transmit(&hspi1, &cmd, 1, 100); MRAM_CS_HIGH(); }读状态寄存器:
uint8_t MRAM_ReadStatus(void) { uint8_t cmd = 0x05; uint8_t status = 0; MRAM_CS_LOW(); HAL_SPI_Transmit(&hspi1, &cmd, 1, 100); HAL_SPI_Receive(&hspi1, &status, 1, 100); MRAM_CS_HIGH(); return status; }写数据函数:
void MRAM_Write(uint32_t addr, uint8_t *data, uint16_t len) { uint8_t cmd[4]; cmd[0] = 0x02; cmd[1] = (addr >> 16) & 0xFF; cmd[2] = (addr >> 8) & 0xFF; cmd[3] = addr & 0xFF; MRAM_WriteEnable(); MRAM_CS_LOW(); HAL_SPI_Transmit(&hspi1, cmd, 4, 100); HAL_SPI_Transmit(&hspi1, data, len, 1000); MRAM_CS_HIGH(); while (MRAM_ReadStatus() & 0x01); }读数据函数:
void MRAM_Read(uint32_t addr, uint8_t *data, uint16_t len) { uint8_t cmd[4]; cmd[0] = 0x03; cmd[1] = (addr >> 16) & 0xFF; cmd[2] = (addr >> 8) & 0xFF; cmd[3] = addr & 0xFF; MRAM_CS_LOW(); HAL_SPI_Transmit(&hspi1, cmd, 4, 100); HAL_SPI_Receive(&hspi1, data, len, 1000); MRAM_CS_HIGH(); }这几个函数是最基础的轮询版本。实际项目里我会把读写函数改成DMA版本,特别是批量读写的时候,DMA可以省下大量CPU时间。
3.4 DMA传输配置要点
用DMA做SPI传输,CubeMX里要在SPI的DMA Settings标签页添加两个通道:SPI1_TX和SPI1_RX。优先级都设为Medium,数据宽度Byte,模式Normal。
DMA版本的写函数:
void MRAM_Write_DMA(uint32_t addr, uint8_t *data, uint16_t len) { uint8_t cmd[4]; cmd[0] = 0x02; cmd[1] = (addr >> 16) & 0xFF; cmd[2] = (addr >> 8) & 0xFF; cmd[3] = addr & 0xFF; MRAM_WriteEnable(); MRAM_CS_LOW(); HAL_SPI_Transmit_DMA(&hspi1, cmd, 4); while (hspi1.State != HAL_SPI_STATE_READY); HAL_SPI_Transmit_DMA(&hspi1, data, len); while (hspi1.State != HAL_SPI_STATE_READY); MRAM_CS_HIGH(); }这里有个细节:DMA传输完成之后,SPI的状态不会立刻变成READY,要等最后一个字节完全移出移位寄存器。HAL库的HAL_SPI_TxRxCpltCallback回调里会处理这个状态转换。如果你在回调外轮询,要加一点延时或者检查BSY标志位。
注意:DMA传输期间不能拉高CS,否则最后一个字节可能丢失。一定要等HAL_SPI_STATE_READY之后再操作CS。
4. 数据存储架构与掉电保护策略
4.1 参数分区设计
512KB的MRAM空间,如果只是存几个参数,那就太浪费了。我一般会做分区管理,把整个空间划分成几个逻辑区域:
| 分区名称 | 起始地址 | 大小 | 用途 |
|---|---|---|---|
| 系统配置区 | 0x00000 | 4KB | 设备ID、校准参数、通信配置 |
| 运行参数区 | 0x01000 | 12KB | 实时运行参数、累计计数 |
| 故障日志区 | 0x04000 | 128KB | 循环记录故障事件 |
| 数据备份区 | 0x24000 | 256KB | 关键数据的双备份 |
| 预留扩展区 | 0x64000 | 128KB | 固件升级缓存、临时数据 |
系统配置区用双备份加CRC校验的方式存储。每次写入时,先写备份区,再写主区,读取时对比两个区域的CRC,哪个有效用哪个。这样即使写入过程中掉电,至少有一个区域的数据是完整的。
故障日志区用循环队列的方式管理。每一条日志包含时间戳、故障码、故障值,固定长度32字节。写满之后从头覆盖,同时维护一个写指针,记录当前写到哪个位置。
4.2 掉电检测与紧急保存
工业设备最怕的就是掉电瞬间正在写关键数据。MRAM虽然写入速度快,但如果电源电压跌落到2.7V以下,写入可能不完整。所以掉电检测电路是必须的。
我的做法是在电源输入端加一个电压比较器,设定阈值为3.0V。当电压低于3.0V时,比较器输出触发STM32的外部中断。中断服务程序里立刻把关键数据写入MRAM的紧急保存区。
从检测到掉电到电源完全跌落到2.7V以下,中间有大约几毫秒的时间。这取决于你的电源滤波电容容量。假设负载电流100mA,电容1000uF,从3.3V跌到2.7V的时间是:
t = C × ΔV / I = 1000uF × 0.6V / 100mA = 6ms
6ms足够STM32写几百个字节到MRAM了。MRAM的写入速度是SPI时钟决定的,20MHz时钟下,写一个字节大约0.4us,加上指令开销,写100字节不到50us。
提示:紧急保存的数据要尽量精简,只存最关键的几个参数。不要试图在掉电中断里保存大量数据,时间不够。
4.3 数据完整性校验方案
MRAM本身的数据保持能力很强,但SPI传输过程中可能因为电磁干扰出现位翻转。所以软件层面还是要做校验。
我通常用CRC16校验,每块数据后面跟2字节CRC。读取时先算CRC,和存储的CRC对比,不一致就说明数据损坏,从备份区恢复。
CRC16的实现可以用查表法,速度快。STM32F746VG有硬件CRC外设,但那个是CRC32,而且多项式固定,不太适合做小数据块的校验。软件查表法更灵活。
static const uint16_t crc16_table[256] = { ... }; uint16_t CRC16_Calc(uint8_t *data, uint16_t len) { uint16_t crc = 0xFFFF; while (len--) { crc = (crc << 8) ^ crc16_table[((crc >> 8) ^ *data++) & 0xFF]; } return crc; }对于故障日志这种追加写入的场景,每条日志单独算CRC,读取时逐条校验。对于系统配置这种整体读写的场景,整块算一个CRC。
5. 实际项目中的性能测试与优化
5.1 读写速度实测数据
我在STM32F746VG平台上,SPI时钟设到40MHz,用DMA方式做了读写测试。测试数据是连续1KB的随机数,读写各1000次取平均值。
| 操作类型 | 传输方式 | 平均耗时 | 等效速率 |
|---|---|---|---|
| 单字节写 | 轮询 | 2.1us | 476KB/s |
| 1KB连续写 | DMA | 218us | 4.7MB/s |
| 单字节读 | 轮询 | 1.8us | 555KB/s |
| 1KB连续读 | DMA | 205us | 5.0MB/s |
| 100字节写 | 轮询 | 42us | 2.4MB/s |
单字节操作的开销主要在CS拉低拉高和指令字节上。连续传输时,指令开销被摊薄,速率就上去了。所以如果你的应用需要频繁写小数据,可以考虑攒一批再写,减少CS切换次数。
5.2 功耗优化技巧
MRAM的待机电流很低,典型值只有几微安。但SPI接口在高速翻转时会有动态功耗。如果你的设备是电池供电,有几个省电技巧:
第一,不操作MRAM的时候,把CS拉高,SCK保持低电平。SCK翻转会带来额外的功耗。
第二,降低SPI时钟。20MHz和40MHz的功耗差别不大,但10MHz以下会有明显下降。如果对速度要求不高,用5MHz就够了。
第三,用DMA传输代替轮询。DMA传输完成后CPU可以进入低功耗模式,等DMA中断唤醒。轮询方式CPU一直在跑,功耗下不来。
实测下来,STM32F746VG在运行模式下,SPI以20MHz读写MRAM时,整体功耗大约在80mA左右。如果把SPI降到5MHz,功耗可以降到65mA。差距主要来自GPIO翻转和SPI外设的动态功耗。
5.3 长时间运行的稳定性验证
工业设备要求7×24小时运行,存储器的稳定性至关重要。我做过一个连续写入测试:每秒钟写一次故障日志,每次32字节,连续跑了30天。
30天总共写入约260万次,MRAM的擦写寿命是10的14次方,这个测试量连零头都不到。测试结束后读取所有日志,CRC校验全部通过,没有出现数据丢失或损坏。
温度方面,我在-40°C和+85°C各做了1000次读写循环,数据保持正常。MR25H40CDF的工业级温度规格是靠谱的,但要注意PCB上的其他元件是否也能扛住这个温度范围。
注意:高温环境下MRAM的写入电流会略微增加,如果电源设计余量不足,可能出现写入失败。建议在+85°C环境下测试时,用示波器观察VDD引脚,确保电压不低于2.7V。
6. 常见问题排查与避坑经验
6.1 读写失败问题速查表
| 现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| 读回全0xFF | CS未正确拉低 | 示波器看CS波形 | 检查GPIO配置和接线 |
| 读回全0x00 | MISO未连接 | 检查MISO走线 | 重新焊接或换线 |
| 随机位错误 | SPI时钟太快 | 降低时钟测试 | 降到10MHz以下 |
| 写入后读回不变 | WREN未发送 | 抓SPI波形 | 确认WREN指令在WRITE之前 |
| 状态寄存器WIP一直为1 | 电源电压不足 | 测VDD引脚电压 | 检查去耦电容和电源 |
| 高温下写入失败 | 写入电流不足 | 高温箱测试 | 增加电源余量 |
6.2 我踩过的几个坑
第一个坑:CS拉高太早。早期版本我在HAL_SPI_Transmit返回后立刻拉高CS,结果最后一个字节偶尔丢失。后来查手册发现,HAL_SPI_Transmit返回时,数据只是写进了DR寄存器,还没完全移出。要等TXE和BSY标志都清零才能拉高CS。用DMA方式就没这个问题,因为DMA完成回调触发时,BSY已经清零了。
第二个坑:WP引脚悬空。有一批板子WP引脚没接,悬空状态下MRAM偶尔进入写保护模式,写入失败。后来把WP直接上拉到VDD,问题消失。WP和HOLD这两个引脚千万不要悬空,一定要有明确的电平。
第三个坑:SPI模式配错。有一次用模式3配置,读出来的数据全是乱的。后来改成模式0就正常了。MR25H40CDF虽然支持模式3,但STM32的SPI在模式3下,第一个时钟边沿的采样时机和MRAM的预期有细微偏差。模式0更稳妥。
第四个坑:DMA传输和CS操作的顺序。DMA传输完成中断里拉高CS,但如果中断优先级太低,被其他中断打断,CS就会延迟拉高,导致多传一个字节。解决办法是把DMA中断优先级设到最高,或者在DMA传输完成后用轮询方式等BSY清零再拉高CS。
6.3 调试工具与技巧
调试SPI接口,逻辑分析仪是必备的。我用的是Saleae Logic 8,采样率100MHz,抓40MHz的SPI波形绰绰有余。抓波形的时候,把CS、SCK、MOSI、MISO四路都接上,一眼就能看出时序问题。
如果没有逻辑分析仪,可以用STM32的SPI回环模式做自测。把MOSI和MISO短接,发送数据看接收是否一致。这能验证SPI外设配置是否正确,但验证不了MRAM的时序。
还有一个技巧:在MRAM_Read和MRAM_Write函数里加计数器,统计读写次数和错误次数。长时间运行后看统计值,能发现偶发性的问题。我在一个项目里就是靠这个计数器发现,每运行几万次就会出现一次写入失败,最后定位到是电源纹波导致的。
7. 扩展应用与项目实战建议
7.1 多设备共享SPI总线的处理
一个STM32F746VG有多个SPI接口,但如果你的系统里SPI设备比较多,可能需要共享总线。MRAM、Flash、显示屏、传感器都挂在同一个SPI上。
共享总线的关键是CS管理。每个设备有独立的CS引脚,STM32在访问某个设备时,只拉低对应的CS,其他CS保持高电平。SPI的时钟模式要兼容所有设备,如果某个设备只支持模式0,那总线就统一用模式0。
速度方面,取所有设备支持的最低时钟。比如MRAM支持40MHz,但显示屏只支持10MHz,那总线时钟就设10MHz。访问MRAM的时候可以临时提高时钟,但切换时钟需要重新配置SPI外设,有开销。如果MRAM的访问频率不高,统一用低速也没问题。
7.2 固件升级中的数据缓存
STM32F746VG的Flash是1MB,如果固件比较大,升级时需要外部存储做缓存。MRAM的512KB空间可以划出一部分做固件缓存。
升级流程:新固件通过通信接口(UART、CAN、以太网)接收,先写入MRAM的固件缓存区。全部接收完成后,校验CRC,确认无误再搬运到STM32的内部Flash。这样即使升级过程中掉电,MRAM里的固件缓存还在,重新上电后可以继续升级,不会变砖。
搬运的时候要注意,STM32的Flash写入需要先擦除。擦除是按扇区操作的,F746VG的扇区大小是128KB或256KB。搬运之前要先算好固件占几个扇区,把对应的扇区擦除。
7.3 工业数据记录仪的设计思路
如果你要做一台工业数据记录仪,MRAM可以作为核心存储介质。架构大概是:传感器数据通过ADC采集,STM32做预处理和打包,然后写入MRAM。MRAM的循环日志区记录带时间戳的数据点,系统配置区存设备参数。
数据回读可以通过USB、以太网或串口。STM32F746VG自带USB和以太网MAC,实现起来很方便。回读的时候按时间范围检索,从MRAM里读出对应的日志记录,打包发送给上位机。
这种设计的好处是:没有文件系统,不需要磨损均衡算法,掉电安全,读写速度快。缺点是容量有限,512KB只能存大约16000条32字节的记录。如果数据量更大,可以考虑用MRAM做缓存,定期转存到大容量存储介质。
7.4 选型对比:MRAM vs FRAM vs NVSRAM
| 特性 | MRAM | FRAM | NVSRAM |
|---|---|---|---|
| 擦写次数 | 10^14 | 10^12 | 无限 |
| 写入速度 | 快 | 快 | 最快 |
| 容量 | 最大16Mbit | 最大4Mbit | 最大4Mbit |
| 功耗 | 低 | 极低 | 高 |
| 成本 | 中 | 高 | 高 |
| 掉电保持 | 20年 | 10年 | 需电池 |
FRAM的功耗比MRAM更低,但容量小、成本高。NVSRAM速度最快,但需要电池维持数据,电池没电数据就丢了。MRAM在容量、速度、功耗、成本之间取得了比较好的平衡,适合大多数工业嵌入式场景。
如果你的应用对功耗极其敏感,比如无线传感器节点,FRAM可能更合适。如果对速度要求极高,比如高速数据采集,NVSRAM是首选。但综合来看,MRAM的适用面最广。
8. 写在最后的一些个人体会
MR25H40CDF这颗芯片我用在好几个项目里了,从环境监测设备到工业控制器,表现一直很稳定。它的SPI接口简单直接,不需要复杂的初始化流程,上电就能读写。相比Flash,省掉了擦除等待和磨损均衡的代码,固件体积也小了不少。
STM32F746VG的HAL库对SPI的支持很完善,CubeMX配置好之后,底层驱动基本不用怎么调。但HAL库的抽象层比较厚,有些细节被隐藏了,比如CS的时序控制。如果你对时序要求严格,建议直接操作寄存器,或者用LL库。
最后分享一个小技巧:在MRAM的最后一个扇区里存一份“出厂参数”的副本。设备恢复出厂设置的时候,直接从MRAM里读这份副本,不需要重新计算或从外部获取。这个副本在产线烧录的时候写入,之后一直不动。用户参数区怎么改都不影响它,相当于一个只读的基准。
这个方案我在实际项目里用了两年多,现场返修率几乎为零。存储这块只要选对了器件,软件逻辑做扎实,后面基本不用操心。