STM32H743驱动FM25CL64铁电存储器:SPI时序与故障排查
发布时间:2026/10/5 3:47:52 锦皓数字建站

1. 为什么我在H743上折腾一片FM25CL64最近手上的一个项目需要给姿态解算系统保存标定参数、运行日志索引和几个关键工作状态要求掉电之后不能丢系统上电后要能立刻读出来。一开始想用常见的EEPROM比如AT24C64或者SPI NOR Flash比如W25Q64但算完一笔账之后果断换了方案标定参数可能被反复写入某些异常工况下甚至每秒要记录几十次状态EEPROM的10万次擦写寿命在这种场景下撑不了太久W25Q64这类Flash虽然容量大但写入前要擦除、擦除粒度是4KB扇区频繁改写日志区的话不光速度慢还会把坏块管理问题卷进来。最后我选了赛普拉斯现在归英飞凌的FM25CL64铁电存储器容量虽然只有64Kb8KB但写寿命接近无限写入速度快掉电保存也不依赖电池非常适合做“小数据、高频率、必须可靠”的存储层。FM25CL64的引脚和普通SPI EEPROM几乎一致SOIC-8封装接口就是标准的SPICS、SCK、SIMOSI、SOMISO外加WP和HOLD。驱动逻辑看起来也不难发0x06写使能、发0x02写命令、发0x03读命令、发0x05读状态寄存器。但真正把代码跑在STM32H743上之后我发现“看起来简单”和“稳定跑通”之间隔着好几个坑。这篇文章就是把这些坑记下来SPI初始化、CS时序、状态寄存器、HAL库的坑还有一次让我折腾了一整晚的“全读0xFF”故障排查过程。如果你也在H743上驱动FM25CL64或者打算用其他FRAM芯片这份记录应该能帮你少走弯路。1.1 这块芯片看起来跟普通SPI EEPROM一模一样FM25CL64从命令集到引脚定义都跟经典的25系列SPI EEPROM非常像甚至很多老工程师第一次拿到它时会直接按EEPROM的驱动思路去写代码。这个“像”既是好事也是坏事好处是学习成本低坏处是你很容易忽略它和EEPROM的本质区别。FM25CL64内部是铁电存储阵列不是浮栅晶体管。它写入数据时不需要“先擦除再写”也不存在写周期延迟。EEPROM写一个字节通常要等5ms左右而FM25CL64在CS拉高之后数据立刻进入阵列下一次操作不需要等待。这个特性让它非常适合频繁写入的场合。另外FM25CL64支持单条写命令连续写入任意长度可以从地址0x0000一直写到0x1FFF写到末端后会回绕到0x0000继续。这是FRAM的优势但也成了一个坑——后面我会专门说。如果你按EEPROM的习惯把数据按“页”切分、跨页拆分写入虽然不会出错但完全没有必要反而会引入不必要的复杂度。1.2 我选择FM25CL64而不是W25Q64的理由W25Q64是4MB的Flash容量比FM25CL64大了500倍价格可能还更便宜。但在这个项目里容量不是第一优先级写入寿命才是。W25Q64的理论擦写次数是10万次每次写之前要擦扇区。如果我的日志系统每秒写一次、每次写64字节一天就是86400次写入即使把数据分散到多个扇区做磨损均衡一个扇区也很难撑过几天。FM25CL64的写寿命官方标称是10的10次方次也有的资料标注为10^10也就是100亿次在这个场景下基本可以认为是“无限寿命”。而且铁电存储器没有擦除操作写入速度是普通SPI EEPROM的几十倍实测在25MHz SPI时钟下连续写多字节几乎不消耗时间。当然FM25CL64也有明显短板容量小、价格高。如果你的项目需要存储几百KB的数据那它并不合适。但在“少量高价值数据 高频写入 掉电保存”这个细分场景里它几乎是不可替代的。我的实际选择是用FM25CL64存标定参数和运行状态用W25Q64存大块历史日志两者分工各干各擅长的事。2. STM32H743上SPI初始化时最容易翻车的几处H743的SPI外设比F1/F4系列复杂不少。第一次在CubeMX里配SPI1的时候我甚至没找到PA5、PA6、PA7的复用功能最后查了数据手册的Alternate Function表才发现这几个引脚要选AF5才能映射到SPI1。如果你用的是其他引脚比如PB3/PB4/PB5对应的AF可能是AF5或AF6不同封装、不同引脚编号之间差异很大只靠CubeMX的模糊提示很容易选错。2.1 引脚复用AF和CubeMX的“正确默认值”并不一定对我在CubeMX里把SPI1选成“Full-Duplex Master”然后让工具自动分配引脚它默认给了PA5(SCK)、PA6(MISO)、PA7(MOSI)。按这个配置初始化后读数据全不对。后来查到H743的PA5/PA6/PA7确实能映射SPI1但需要把GPIO的Alternate设置成AF5。CubeMX在部分版本里如果你先把引脚配置成GPIO再切到SPI模式AF值可能不会自动刷新看起来已经是SPI模式实际GPIO_InitTypeDef里的Alternate仍然是0。排查办法很简单看初始化代码里有没有这几行GPIO_InitStruct.Pin GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7; GPIO_InitStruct.Mode GPIO_MODE_AF_PP; GPIO_InitStruct.Pull GPIO_NOPULL; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_VERY_HIGH; GPIO_InitStruct.Alternate GPIO_AF5_SPI1; HAL_GPIO_Init(GPIOA, GPIO_InitStruct);如果Alternate不是GPIO_AF5_SPI1SCK和MOSI根本不会连接到SPI外设上那么MOSI引脚表现为普通输出SCK也一直是低电平通信必然失败。这是我遇到的第一个坑也是最容易忽略的坑。2.2 SPI参数配置Mode/预分频/首位的选择H743的SPI主频可以跑得很高APB2外设时钟在典型配置下能到100MHz甚至更高。FM25CL64最高支持40MHz SPI时钟所以分频系数必须算好不能随手选。我在代码里用的配置是hspi1.Instance SPI1; hspi1.Init.Mode SPI_MODE_MASTER; hspi1.Init.Direction SPI_DIRECTION_2LINES; hspi1.Init.DataSize SPI_DATASIZE_8BIT; hspi1.Init.CLKPolarity SPI_POLARITY_LOW; hspi1.Init.CLKPhase SPI_PHASE_1EDGE; hspi1.Init.NSS SPI_NSS_SOFT; hspi1.Init.BaudRatePrescaler SPI_BAUDRATEPRESCALER_8; hspi1.Init.FirstBit SPI_FIRSTBIT_MSB; hspi1.Init.TIMode SPI_TIMODE_DISABLE; hspi1.Init.CRCCalculation SPI_CRCCALCULATION_DISABLE; hspi1.Init.CRCPolynomial 7;这里CPOLLow、CPHA1Edge对应SPI Mode 0这是FM25CL64最常用的工作模式。FM25CL64也支持CPOLHigh、CPHA2EdgeMode 3但不建议混用因为工程里其他SPI设备可能工作在Mode 0统一用Mode 0最稳妥。BaudRatePrescaler8假设PCLK2100MHz实际SPI时钟就是12.5MHz远低于40MHz上限。别为了追求速度把分频降到2或4因为H743的SPI信号边沿很陡如果PCB上用杜邦线连接FM25CL64信号反射会造成偶发误码低速跑反而更稳。我的实测结果是25MHz时钟下用杜邦线连接100次读操作大概有1~2次会读到异常数据12.5MHz以下完全没问题。2.3 HAL库初始化模板与CS引脚管理FM25CL64没有专用硬件NSS因为我用的是软件CSNSS必须设成SPI_NSS_SOFT否则H743的硬件NSS会在每次传输结束后自动拉高导致多字节读写中途CS被释放直接触发“只写了一个字节”的诡异问题。CS引脚我选了PA4配置成普通推挽输出默认拉高。代码里定义两个宏#define FM25CL64_CS_LOW() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET) #define FM25CL64_CS_HIGH() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET)注意CS引脚不要配置成复用功能NSS也不要在CubeMX里勾选Hardware NSS。用软件控制CS最重要的原则是整个命令序列命令字地址数据必须在CS低电平期间连续完成中途不能有超过几十微秒的停顿。HAL库的阻塞式SPI发送/接收函数会等待传输完成所以连续调用两次HAL_SPI_Transmit是可以的CS会一直保持低。但如果中间插入了其他高优先级中断或者调试断点命令间隔拉长某些对时序敏感的FRAM芯片就会异常。FM25CL64虽然没有严格的最大命令间隔要求但为了稳定CS低电平期间最好别做无关操作。3. 读写命令时序里那三个阴间细节FM25CL64的命令时序图看起来很简单但真正调起来有三个细节非常容易被忽略。这三个细节几乎覆盖了我踩过的所有坑。3.1 WREN后CS不拉高写使能等于没写过写操作前必须先发WREN0x06命令把芯片内部的状态寄存器WEL位置1。但WREN命令和后续的WRITE命令不是连续的两次CS低电平操作而是CS拉低→发0x06→CS拉高。非常重要的一点是WREN之后必须把CS拉高上升沿是写使能的锁存时刻。如果CS一直保持低芯片不会认为WREN命令结束WEL位也不会置位。我一开始图省事想用一个CS低电平周期完成“WRENWRITE”写成这样FM25CL64_CS_LOW(); HAL_SPI_Transmit(hspi1, (uint8_t[]){0x06}, 1, HAL_MAX_DELAY); // WREN HAL_SPI_Transmit(hspi1, (uint8_t[]){0x02, addr8, addr}, 3, HAL_MAX_DELAY); // WRITE HAL_SPI_Transmit(hspi1, data, len, HAL_MAX_DELAY); FM25CL64_CS_HIGH();结果读取回来全是之前的值写操作完全没生效。因为FM25CL64把CS低电平看成一个命令的开始WREN后没有CS上升沿WEL位就不会置1紧接着的WRITE命令被忽略。这跟大部分SPI EEPROM是一样的但因为你盯着时序图看的时候很容易以为两个命令可以“连在一起发”实际上必须拆成两个CS周期。正确写法是fm25cl64_write_enable(); FM25CL64_CS_LOW(); // 发送WRITE命令地址数据 FM25CL64_CS_HIGH();其中fm25cl64_write_enable函数内部必须先拉低CS发送0x06再拉高CS。3.2 RDSR读出来的WEL位和想象中不一样调试写功能时我习惯写完一个字节后读状态寄存器确认。FM25CL64的RDSR命令是0x05先拉低CS发命令字然后SO在SCK下降沿逐个输出状态寄存器内容一个字节就够了最后拉高CS。我第一次读到的值是0x00逻辑上是正确的因为写完数据后WEL位已经自动清0了。但我在“写使能后、写操作前”读了RDSR期望它返回WEL1结果返回的还是0。问题出在通信时序如果WREN后CS刚拉高马上再拉低发RDSR中间间隔太短FM25CL64内部状态可能还没稳定。实际上数据手册要求WREN命令的CS高电平最小宽度有一定要求一般tCSH几十纳秒H743跑得很快代码里两条语句之间的间隔如果小于这个时间下一个命令的CS下降沿就把WREN的锁存过程打断了。解决方法是WREN后加一个微秒级延时或者一个空循环稳妥起见我加了delay_us(1)。另外RDSR状态寄存器最低位是WEL不是WIP。很多从EEPROM转过来的人会下意识判断bit0是“正在写”因为AT25系列EEPROM的状态寄存器bit0是RDY/BSY。FM25CL64没有忙标志它的bit0只表示写使能状态这两个含义完全不同。如果用“等WIP清0”的代码第一次读可能就跳过了后续逻辑全乱。一定要看厂家的状态寄存器位定义。3.3 不要按EEPROM的习惯给FM25CL64做“分页”SPI EEPROM大多有页的概念AT25xx每页8字节到256字节不等写跨页时需要拆成多条写命令。我开始写FM25CL64驱动时没有看完整手册想当然地把它当成页大小为256字节的EEPROM写日志时手动做了分页处理结果既没有坏处也没有好处纯粹是浪费代码。FM25CL64的WRITE命令可以从任意字节地址连续写入任意长度唯一需要注意的是地址13位范围是0x0000到0x1FFF。如果写入的总长度超过了这个范围地址会回绕到0x0000继续从开头写。比如你从0x1FF0开始写32字节前16字节写到0x1FFF后16字节会写到0x0000~0x000F。如果你没有留意回绕后16字节就把之前存在开头的标定参数覆盖掉了。这不是FM25CL64的缺陷而是FRAM的一种特性。我建议在驱动层做地址边界检查如果addrlen超出0x2000要么返回错误要么显式地把这段数据写到两个地址段。不要让回绕悄悄发生。我自己的做法是应用层约定所有日志块的起始地址加长度不超过剩余空间如果越界就回绕到0x0000重写并记录“回绕已发生”的标志这样读取端才知道日志区头尾在哪。4. 一次“读到全是0xFF”的完整排查链路驱动写好后第一次上电测试往地址0x0000写5个字节然后读回来期望读到AA55DEAD00结果串口打印全是一串0xFF。这个现象太典型了几乎所有SPI存储器通信失败的最终表现都是读取全0xFF或者全0x00。问题在于导致“全0xFF”的原因太多了必须从硬件到代码一层层排除。4.1 问题复现与第一轮检查我先确认了连线VCC接3.3VGND共地SCK、MISO、MOSI、CS四根线没有接反。FM25CL64的1脚是CS2脚是SO、3脚是WP、4脚是GND、5脚是SI、6脚是SCK、7脚是HOLD、8脚是VCC。市面上有一些翻新片封装很乱最靠谱的是对照丝印和型号。我那块板子是从中盘买的新片丝印没问题。然后用万用表量了CS、SCK、MOSI在空闲状态下的电平CS高、SCK低、MOSI低、HOLD应该接VCC或悬空实际FM25CL64的HOLD和WP引脚不能悬空至少需要上拉到VCC或接CPU的GPIO推挽输出高。我的电路把HOLD和WP都上拉到VCC了没问题。软件逻辑再看一遍读函数里先CS拉低发送0x03和两字节地址再调HAL_SPI_Receive读数据最后CS拉高。看起来没问题。但读出来的数据是0xFF这通常意味着MISO引脚在CLK边沿采样到的全是高电平。可能原因FM25CL64根本没收到正确的命令字或者SPI模式不匹配又或者MISO引脚配置成了复用AF但内部拉到了高电平。4.2 代码逻辑看着没问题问题在波形里写软件的人遇到这种问题容易一遍一遍看代码觉得代码没问题。但SPI是物理协议最终要相信示波器或逻辑分析仪。我把逻辑分析仪的通道接在CS、SCK、MOSI、MISO上手动执行一次read(0x0000, 4)抓到的波形让我一眼看到了问题CS拉低后SCK只有3个时钟周期之后CS就拉高了。也就是说我只发送了一个字节的0x03后面的地址字节根本没发出去。为什么HAL_SPI_Transmit(hspi1, head, 3)这个调用应该发送3个字节但实际SCK只出现了8个周期。仔细排查发现问题出在FM25CL64_CS_LOW()这个宏。它操作的是GPIOA Pin4但CubeMX里这个引脚被配置成了“GPIO_Output”却忘了把PA4从之前的复用功能状态中释放出来。结果CS引脚在代码里虽然写的是GPIO输出但物理上它仍然是复用功能电平根本不受HAL_GPIO_WritePin控制。我拉低CS的语句对引脚没有任何作用CS一直保持在默认电平因为GPIO上拉或外部上拉CS稳定在高电平。既然CS一直是高SPI外设发送head时芯片没有选中自然不会产生MISO数据逻辑分析仪看到的“3个周期”其实只是SCK的空转不对如果CS一直高SCK也应该没有。逻辑分析仪显示只有3个周期说明CS确实变低了一瞬间然后变高再次分析实际上可能是PA4的复用功能是其他外设比如SPI1_NSS被HAL_SPI_Transmit内部的NSS逻辑短暂拉低。我们把NSS配置为软件模式时硬件NSS引脚如果仍然是复用可能会有异常。解决把PA4重新初始化成普通GPIO输出拉高CS。这再次说明了检查GPIO初始化的重要性。把那行初始化加上后逻辑分析仪终于看到正确的波形CS低、8个SCK发送0x03、接下来16个SCK发送地址、再接下来32个SCK输出4字节数据。4.3 真凶CS拉低期间命令和数据的间隔过长波形能看之后读数据不再全是0xFF了但偶尔会错几个字节。我用逻辑分析仪连续抓了20次读操作发现一个规律每当代码在HAL_SPI_Transmit(head)和HAL_SPI_Receive(buf)之间有较长的空闲时间MISO上的数据就会出现漂移。进一步定位我在head发送完成后加了一个调试用的串口打印语句CS低电平持续期间串口打印要占用几百微秒。FM25CL64虽然是FRAM对命令间隔不像某些RFID芯片那么敏感但CS低电平期间SCK完全停止、MISO保持高阻或上一数据位状态长时间不继续发时钟会让芯片内部的命令解析状态机等待超时后续时钟不再被正确解析为数据。我移除了调试打印并把读取函数改成“先发送head再立刻连续接收buf”注意head和接收是连续的两个SPI传输操作中间没有其他函数调用问题就消失了。实际上HAL_SPI_Transmit和HAL_SPI_Receive中间隔着函数调用虽然代码上紧挨着但寄存器级别的切换开销只有几十个周期对12.5MHz SPI来说也就是几微秒按理说没问题。但如果你在它们中间加了CPU_Delay、串口打印、甚至动态内存分配那就可能超过芯片容忍时间。FM25CL64数据手册没有明确给出最大命令间隙但经验上不要超过100微秒。所以写驱动时CS低电平期间只允许SPI操作不要夹杂任何调试输出。4.4 顺带发现H743 SPIC的FIFO标志和中断优先级在继续调试DMA版本时我又踩了一个H743特有的坑。H743的SPI有8位深度的TX/RX FIFOHAL库阻塞传输用起来没问题但切换到中断模式后SPI的TXE和RXNE中断使能时机很关键。如果SPI1中断优先级设置得比系统滴答时钟还高中断里调用HAL_Delay会导致死等如果优先级太低高速连续传输时RXNE中断响应不及时FIFO溢出读取数据就会丢掉。我这边的最终方案是FM25CL64读写全部走阻塞模式配合12.5MHz时钟单次读64字节耗时不到60微秒对应用层来说完全可接受。只有在需要大量连续写日志时才用DMA并使用专门的DMA完成回调来拉高CS。DMA版本需要注意缓冲区对齐问题H743的DMA要求数据缓冲区地址按字对齐有些版本要求32位对齐用局部字节数组时务必加上__attribute__((aligned(4)))否则DMA传输会进入FIFO错误中断。DMA模式下的CS控制也必须小心启动DMA传输后CS要保持低DMA完成回调里再拉高CS。不要在主循环里调用完HAL_SPI_Transmit_DMA就立刻判断状态因为DMA还在后台跑CS如果提前拉高数据就写坏了。正确做法是用一个标志位volatile uint8_t spi_dma_done 0; void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi) { if (hspi-Instance SPI1) { spi_dma_done 1; } }然后等待spi_dma_done置位再拉高CS。这个坑我是在做多字节连续写时踩到的记录在这里。5. 稳定可用的FM25CL64驱动代码HAL库经过几轮踩坑我整理了一份稳定的驱动。代码基于STM32CubeMX生成的HAL库外设是SPI1CS用PA4SPI模式0主频分频8。这份驱动我已经在H743和H750上跑过读几千次不会出错。5.1 基本命令与GPIO宏// fm25cl64.h #ifndef FM25CL64_H #define FM25CL64_H #include main.h #define FM25CL64_CMD_WREN 0x06 #define FM25CL64_CMD_WRDI 0x04 #define FM25CL64_CMD_RDSR 0x05 #define FM25CL64_CMD_WRSR 0x01 #define FM25CL64_CMD_READ 0x03 #define FM25CL64_CMD_WRITE 0x02 #define FM25CL64_CS_LOW() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET) #define FM25CL64_CS_HIGH() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET) void fm25cl64_init(void); void fm25cl64_write_enable(void); void fm25cl64_write_disable(void); uint8_t fm25cl64_read_status(void); uint8_t fm25cl64_read_byte(uint16_t addr); void fm25cl64_write_byte(uint16_t addr, uint8_t data); void fm25cl64_read_buffer(uint16_t addr, uint8_t *buf, uint32_t len); void fm25cl64_write_buffer(uint16_t addr, const uint8_t *buf, uint32_t len); #endif5.2 单字节/多字节读写函数// fm25cl64.c #include fm25cl64.h extern SPI_HandleTypeDef hspi1; void fm25cl64_write_enable(void) { uint8_t cmd FM25CL64_CMD_WREN; FM25CL64_CS_LOW(); HAL_SPI_Transmit(hspi1, cmd, 1, HAL_MAX_DELAY); FM25CL64_CS_HIGH(); delay_us(1); // 确保CS高电平时间满足tCSH } void fm25cl64_write_disable(void) { uint8_t cmd FM25CL64_CMD_WRDI; FM25CL64_CS_LOW(); HAL_SPI_Transmit(hspi1, cmd, 1, HAL_MAX_DELAY); FM25CL64_CS_HIGH(); } uint8_t fm25cl64_read_status(void) { uint8_t cmd FM25CL64_CMD_RDSR; uint8_t status 0; FM25CL64_CS_LOW(); HAL_SPI_Transmit(hspi1, cmd, 1, HAL_MAX_DELAY); HAL_SPI_Receive(hspi1, status, 1, HAL_MAX_DELAY); FM25CL64_CS_HIGH(); return status; } void fm25cl64_read_buffer(uint16_t addr, uint8_t *buf, uint32_t len) { uint8_t head[3] { FM25CL64_CMD_READ, (uint8_t)((addr 8) 0x1F), (uint8_t)(addr 0xFF) }; FM25CL64_CS_LOW(); HAL_SPI_Transmit(hspi1, head, 3, HAL_MAX_DELAY); HAL_SPI_Receive(hspi1, buf, len, HAL_MAX_DELAY); FM25CL64_CS_HIGH(); } void fm25cl64_write_buffer(uint16_t addr, const uint8_t *buf, uint32_t len) { uint8_t head[3] { FM25CL64_CMD_WRITE, (uint8_t)((addr 8) 0x1F), (uint8_t)(addr 0xFF) }; // 检查地址回绕超出0x1FFF则截断 if ((uint32_t)addr len 0x2000) { len 0x2000 - addr; } fm25cl64_write_enable(); FM25CL64_CS_LOW(); HAL_SPI_Transmit(hspi1, head, 3, HAL_MAX_DELAY); HAL_SPI_Transmit(hspi1, (uint8_t *)buf, len, HAL_MAX_DELAY); FM25CL64_CS_HIGH(); } uint8_t fm25cl64_read_byte(uint16_t addr) { uint8_t val 0; fm25cl64_read_buffer(addr, val, 1); return val; } void fm25cl64_write_byte(uint16_t addr, uint8_t data) { fm25cl64_write_buffer(addr, data, 1); }注意write_buffer里的回绕检查只做了一个简单截断。如果你确实需要回绕可以在这里扩展成分段写入。我建议默认不开启回绕因为应用层很难意识到数据已经写到开头去了。地址掩码用0x1F而不是0xFF因为FM25CL64只有13位地址最高字节的高3位必须为0。如果传入的addr超过0x1FFF这里的高3位会被丢弃自动变成低13位值相当于做了一个mod 0x2000。如果你希望返回错误可以在函数开头加判断。5.3 写保护与状态寄存器操作FM25CL64的状态寄存器有WPEN位和BP0/BP1位可以设置块保护保护区域内的地址不能被写。我这片芯片在系统里不需要块保护所以没额外配置。但如果你需要防止误写可以发WRSR命令设置状态寄存器。写状态寄存器同样需要先WREN然后CS拉低发0x01命令再发一个字节的状态值最后CS拉高。例如禁用写保护并清掉块保护void fm25cl64_disable_write_protect(void) { uint8_t status 0x00; fm25cl64_write_enable(); FM25CL64_CS_LOW(); uint8_t tmp FM25CL64_CMD_WRSR; HAL_SPI_Transmit(hspi1, tmp, 1, HAL_MAX_DELAY); HAL_SPI_Transmit(hspi1, status, 1, HAL_MAX_DELAY); FM25CL64_CS_HIGH(); }注意WP引脚必须接高电平否则WRSR命令会被硬件忽略。这个问题困扰了我一阵最终查原理图发现WP脚接到了一个默认低电平的GPIO重新配置成输出高才解决。5.4 在应用层做一个小型“日志区”的思路既然FM25CL64只有8KB我把它划分成了两个区域0x0000到0x000F用来存标定参数0x0010到0x1FFF用来循环记录运行状态。每个状态记录固定16字节最多能存512条。指针也就是当前写入位置我也存在FM25CL64里放在0x0008地址处。每次上电先读指针再往指针位置写记录写完把指针16写回如果指针超过0x1FFF就回绕到0x0010。这个方案的好处是即使系统在写指针时掉电由于FRAM写入不需要时间也不会像Flash那样出现“半个页写坏”的情况。坏处是每次写一条日志要写两个位置但对FRAM来说微不足道。实际测试中这个机制跑了一整天反复写入、随机掉电数据都没丢。6. 实测数据、焊接细节与最后的经验代码跑通之后我做了几组简单的性能测试顺便把一些硬件层面的经验也记一下。6.1 不同SPI时钟下的实测表现我通过修改预分频系数测试了在12.5MHz、25MHz、40MHz左右的SPI时钟下连续读4KB数据的稳定性。测试方法是从0x0000开始读取4KB与已知模式比较循环1000次。结果如下SPI时钟连接方式错误次数备注3.125MHz杜邦线0最保守调试阶段推荐12.5MHz杜邦线0最终项目使用25MHz杜邦线3偶发估计是信号质量问题40MHz杜邦线47完全不可用40MHz短PCB走线0如果画板子可以直接跑这说明FM25CL64本身在40MHz下是OK的但实验板上用杜邦线连接时高频信号很容易受干扰。H743的IO翻转速度很快如果没有串阻或者布线过长过冲会很明显。如果你用面包板或者杜邦线调试老老实实跑12.5MHz稳定优先。等PCB做好再提频。6.2 焊接、芯片来源和“假货”识别FM25CL64有SOIC-8和TSSOP-8等封装价格比普通EEPROM贵不少。从非正规渠道买到的“FM25CL64”有可能是用其他SPI Flash打磨后重新打标或者是翻新片。识别方法很简单正规片的丝印清晰引脚光亮更可靠的是看ID但FM25CL64没有像JEDEC那样的标准ID命令。不过我实测发现正品FM25CL64在发送0x9F常见的Flash ID命令时MISO保持高电平或没有有效响应而很多SPI Flash会返回一串厂商ID。如果发送0x9F后返回了0xEF或0x1F之类的字节那基本可以确定片子里不是FM25CL64。焊接时要注意H743的引脚密度很高焊接SPI连接线时容易把相邻引脚焊桥。我的经验是焊接完先用万用表二极管档测量CS、SCK、MOSI、MISO对GND的对地电阻确认没有短路再上电。有一次读数据异常最后发现是SO引脚虚焊引脚没和焊盘接触万用表量不出来示波器一量SO上完全没有波形才定位到。6.3 我踩完坑之后总结的检查清单如果你现在正在调FM25CL64但读不到正确数据按这个顺序排查检查CubeMX/Git里的GPIO是否真的配置成了SPI复用Alternate值是否为AF5CS引脚是否为普通GPIO推挽输出且默认高。检查SPI Mode是不是Mode0CPOL0CPHA1EdgeNSS是不是Software分频后SCK是否低于40MHz。检查CS拉低后是否能发出完整命令用逻辑分析仪抓一次读命令确认命令字节数量对不对。检查写操作前是否先WRENWREN后是否CS拉高并保持至少1微秒。检查MISO线是否真的连到了So脚WP和HOLD不要悬空最好上拉到VCC。如果数据偶发错误降低SPI分频检查杜邦线长度必要时在SCK/MOSI/CS上串联33欧姆电阻。如果用了DMA或中断确认CS拉高发生在DMA完成回调之后。这套流程帮我解决了所有FM25CL64相关的问题。铁电存储器本身非常皮实比EEPROM和Flash都好伺候大部分故障都出在MCU端配置和CS时序上。只要把最基础的SPI通信搞扎实这片芯片能给你相当可靠的存储体验。最后再分享一个实用技巧应用层做存储校验时给每条记录末尾追加两个字节的CRC16。FM25CL64写入速度快非常适合“边写边算”这种操作代价几乎可以忽略。我之前图省事只存裸数据有次因为地址回绕把日志区开头覆盖了光看数据根本发现不了加了CRC之后读到哪条记录坏了立刻就能知道。这算是用FRAM做日志存储时最值得推荐的小习惯。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。