news 2026/10/4 7:03:29

STM32与MRAM的工业存储改造:告别Flash扇区磨损

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32与MRAM的工业存储改造:告别Flash扇区磨损

前阵子调试一台工业设备,客户报了个挺揪心的故障:设备正常跑着没毛病,但只要一断电重启,几个关键运行参数就被“还原”了。查到最后,问题出在板子上那颗 SPI NOR Flash 扛不住频繁写入,某个扇区被磨穿了。后来我把存储芯片换成了 Everspin 的 MR25H40CDF,配合手头这颗 STM32F412ZG 重新设计了存储方案,故障终于彻底消失。这篇就完整聊聊这次改造的来龙去脉,从选型逻辑、硬件搭线、驱动代码,到工业现场真正要命的掉电数据保护,一次性讲透,给正在做嵌入式存储设计的朋友一个可以直接抄作业的参考。

先说结论:在工业设备这类“要频繁存储、要断电不丢、要长期可靠”的场景里,串行 MRAM(磁阻随机存取存储器)天生就是比 SPI NOR Flash 和 I2C EEPROM 更合适的材料。MR25H40CDF 是 4Mbit 容量、SPI 接口的串行 MRAM,SOIC-8 封装,读写机制和存储单元跟普通 Flash 完全不同;STM32F412ZG 则是 ST 家的高性能 Cortex-M4F 芯片,1MB Flash、256KB RAM,外设接口非常丰富,两者搭配起来做工业级的数据记录和参数存储非常顺手。这篇文章不光是给一份能用的驱动代码,更想把背后那些“为什么选它”“为什么这么接线”“为什么掉电还能保住数据”的逻辑讲明白,对刚入行做嵌入式的朋友来说,这部分才是真正的价值所在。

1. 为什么要换掉 Flash 和 EEPROM:三种非易失存储的底层差异

很多嵌入式工程师一提到“断电保存数据”,第一反应就是 STM32 内部 Flash 或者外挂的 25 系列 SPI NOR Flash。说实话,在我把 MRAM 用进工业项目之前,我也是这么干的。但工业现场和消费电子最大的区别在于,很多数据不是偶尔存一次,而是每次运行状态变化都要存,一天下来可能是几万甚至几十万次的写入操作。这种情况下,Flash 和 EEPROM 的“命”根本不够用。

1.1 MRAM 到底牛在哪里

MRAM 的存储单元不是靠电荷(Flash、EEPROM 都是电荷存储),而是靠磁性隧道结(MTJ,Magnetic Tunnel Junction)的磁阻效应。你可以把它想象成一个微观的磁性开关:有一层固定方向的磁层,还有一层自由磁层。写入数据时,只要在位线写电流的控制下改变自由磁层的磁化方向,隧道结的电阻就会在高阻态和低阻态之间切换,对应逻辑 1 和逻辑 0。这个写入过程是物理层面的“磁翻转”,不涉及电荷注入、不涉及隧穿氧化层的老化,因此它没有“擦除寿命”这个概念,理论上可以无限次写入。

这一点是 Flash 完全比不了的。SPI NOR Flash 写入前必须先擦除,擦除的基本单位是扇区,通常是 4KB;而且每个扇区的擦写次数一般只有十万次量级,到了后期氧化层劣化,数据保持能力都会下降。EEPROM 字节可擦写,但寿命通常标称一百万次,听着不少,可一旦程序里有 bug 导致某个地址被反复写,很快就废了。

1.2 三兄弟对比:选型不再是玄学

我把三种常见的非易失存储放在一起比过,差距相当直观:

维度MR25H40CDF(串行 MRAM)普通 SPI NOR FlashI2C EEPROM
容量4Mbit(512KB)常见 1Mbit~64Mbit常见 2Kbit~1Mbit
接口SPI,最高 40MHzSPI,性能差异大I2C,通常 1MHz 以内
写操作粒度字节写,直接覆盖必须先擦除扇区再写页字节写
是否需擦除不需要必须擦除不需要
写寿命无限十万次/扇区量级一百万次/字节量级
写速度字节级,纳秒级内部完成受擦除拖累,擦一个扇区要几十毫秒到几百毫秒几毫秒到十几毫秒
数据保持20 年以上典型 20 年(擦写多会劣化)典型 50 年
抗辐射/抗干扰磁存储,对电离辐射不敏感电荷存储,辐照会丢数据电荷存储,辐照会丢数据

看到这个表,很多朋友就明白了:MRAM 就是拿“掉电保存”的需求,硬生生做到了接近 SRAM 的使用体验。它在工业设备里的典型位置包括:运动控制系统的当前位置/速度参数、变频器的运行日志、仪表的历史曲线数据、医疗设备的关键配置,以及需要突然断电瞬间把重要状态“抢存”下来的那类场景。STM32F412ZG 在这个组合里的角色也很明确:它的 SPI 外设数量多、支持 DMA、时钟频率足够高,还有掉电检测(PVD)中断可以配合做紧急保存,145 个 IO 让给存储单元留片选、留中断引脚都绰绰有余。

2. 硬件连接:STM32F412ZG 与 MR25H40CDF 的搭线工程

硬件这部分看起来简单,不就是电源、地、四根 SPI 线吗?其实有几个细节决定系统稳不稳。我实际改板子的时候在这些点上吃过亏,逐个说清楚。

2.1 引脚分配与连接表

MR25H40CDF 是标准的 SOIC-8 封装,引脚功能相对固定。以我这次用的 STM32F412ZG 为例,我把它挂在 SPI1 上,片选用 PB12。完整的连接关系如下:

MR25H40CDF 引脚功能连接到 STM32F412ZG说明
1 CS片选输入,低有效PB12(或任意 GPIO)软件片选,速度要求不高
2 SO串行数据输出SPI1_MISO(PA6)芯片输出,MCU 输入
3 WP写保护,低有效VCC直接拉高,关闭硬件写保护
4 VSS地GND必须共地,避免浮地干扰
5 SI串行数据输入SPI1_MOSI(PA7)芯片输入,MCU 输出
6 SCK串行时钟SPI1_SCK(PA5)时钟源来自 MCU
7 HOLD暂停通信,低有效VCC直接拉高,不允许悬空
8 VCC电源3.3V必须加 100nF 去耦电容

需要强调一下 WP 和 HOLD 这两个脚。很多朋友第一次用 MRAM,按着 SPI Flash 的惯性思维,觉得这两个脚用不到就不接,这是大坑。WP 如果不固定电平,内部保护电路可能处于不确定状态,导致写入命令被悄悄忽略,你读出来全是旧数据还查半天;HOLD 一旦受外界干扰被拉低,芯片会暂停通信,SCK 上过来的字节全部作废。稳妥的做法是两个脚都直接接 VCC,一了百了。

2.2 电源与去耦:别小看这个 100nF

MRAM 写入瞬间内部电流脉冲比普通 Flash 要大,如果 VCC 引脚旁边没有去耦电容,电压跌落可能直接导致写时序失败。我在原设计里 VCC 和 GND 之间只放了一颗 10µF 钽电容,结果用示波器一抓,写入瞬间 VCC 跌了近 300mV,后来加上一颗 100nF 陶瓷电容贴近引脚放,纹波立刻降到 50mV 以内。陶瓷电容要尽量靠近芯片引脚放置,走线先过电容再到 VCC,不要为了布线方便把电容甩到板子另一面。

另外 STM32F412ZG 的 IO 输出电压一般是 3.3V,和 MR25H40CDF 的电源电平完全匹配,不需要额外电平转换。如果哪天你换成了 5V 供电的 MCU,SI 和 SCK 线上必须加电阻分压或电平转换芯片,因为 MRAM 的输入引脚不是 5V 容忍型。

2.3 片选与 SPI 模式选择的门道

SPI 挂在总线上时,片选最好用软件控制。CubeMX 里 NSS 选择“Software NSS”,然后拿一个普通 GPIO 去接 CS 引脚。为什么不用硬件 NSS?因为硬件 NSS 在某些微控制器上会受 SPI 配置变化的影响,master mode 下如果配置不当,CS 会出现不期望的抖动,可能把 MRAM 正在进行的内部操作打断。软件片选虽然每次通信多一条拉高拉低的指令,但控制逻辑完全透明,排查问题时也舒服。

关于 SPI 模式,MR25H40CDF 的时序表和大多数 SPI NOR Flash 一样,支持 Mode 0(CPOL=0,CPHA=0)和 Mode 3(CPOL=1,CPHA=1),也就是 SCK 空闲电平为低或高都可以,只要保证数据在合适的边沿被采样。实际项目中我统一用 Mode 0,也就是常规的“时钟空闲为低、第一个沿采样输入”。这样即使总线上还挂着别的 SPI 器件,大家也能友好共存。

3. 驱动代码实现:从 CubeMX 到读写函数

硬件搭好只是万里长征第一步,真正决定项目能否跑起来的是底层驱动。MR25H40CDF 的指令集很像精简版的 NOR Flash,没有扇区擦除、整片擦除这些恼人的命令,所以驱动写起来非常清爽。下面这套代码我在 STM32F412ZG 上用 HAL 库实测通过,可以直接复制到工程里改改用。

3.1 STM32CubeMX 配置要点

在 CubeMX 里把 SPI1 配成“Full-Duplex Master”,预分频可以先设 16,得到大概 5.4MHz 的 SCK(F412 的 SPI1 挂在 APB2 上,最快能到 50MHz 左右,但不建议摸顶跑)。MR25H40CDF 标称最高 40MHz,那是不是直接配最高就行了?不是的。工业环境里总线走线难免有杂散电容和干扰,跑满速一旦出现时序劣化,排查起来极痛苦。我一般先按 10~20MHz 调通功能,最后再根据量产板实测结果决定要不要提速。

参数配置清单:

  • 模式:Full-Duplex Master
  • 数据传输大小:8 位
  • 时钟极性/相位:Low / 1Edge(Mode 0)
  • NSS:Software
  • 先发送:MSB First
  • 预分频:16(SCK ≈ 5.4MHz)起步,后面再降

GPIO 方面,把 PB12 配置为 GPIO Output,初始输出高电平。这个初始状态很重要,如果 MCU 上电瞬间 CS 默认是低电平,而 SPI 线又处于高阻或不确定状态,MRAM 可能误收到垃圾命令,把状态寄存器改乱。

3.2 关键驱动代码

下面是一个完整的、可直接落地的驱动逻辑。它只封装了最核心的四个操作:命令收发、写使能、读状态、读数组、写数组。

/* SPI 收发单字节封装 */ static uint8_t MR25_SPI_Byte(SPI_HandleTypeDef *hspi, uint8_t byte) { uint8_t rxbuf = 0; HAL_SPI_TransmitReceive(hspi, &byte, &rxbuf, 1, HAL_MAX_DELAY); return rxbuf; } /* 片选操作 */ #define MR25_CS_LOW() HAL_GPIO_WritePin(MRAM_CS_GPIO_Port, MRAM_CS_Pin, GPIO_PIN_RESET) #define MR25_CS_HIGH() HAL_GPIO_WritePin(MRAM_CS_GPIO_Port, MRAM_CS_Pin, GPIO_PIN_SET) /* 写使能:所有写命令前必须执行 */ void MR25_WriteEnable(SPI_HandleTypeDef *hspi) { MR25_CS_LOW(); MR25_SPI_Byte(hspi, 0x06); /* WREN */ MR25_CS_HIGH(); } /* 读状态寄存器 */ uint8_t MR25_ReadStatus(SPI_HandleTypeDef *hspi) { uint8_t status = 0; MR25_CS_LOW(); MR25_SPI_Byte(hspi, 0x05); /* RDSR */ status = MR25_SPI_Byte(hspi, 0x00); MR25_CS_HIGH(); return status; } /* 从指定地址连续读取 len 字节 */ int MR25_ReadBytes(SPI_HandleTypeDef *hspi, uint32_t addr, uint8_t *pBuf, uint32_t len) { if (addr + len > MR25_CAPACITY_BYTES) { return -1; } MR25_CS_LOW(); MR25_SPI_Byte(hspi, 0x03); /* READ Array */ MR25_SPI_Byte(hspi, (addr >> 16) & 0xFF); MR25_SPI_Byte(hspi, (addr >> 8) & 0xFF); MR25_SPI_Byte(hspi, addr & 0xFF); for (uint32_t i = 0; i < len; i++) { pBuf[i] = MR25_SPI_Byte(hspi, 0x00); } MR25_CS_HIGH(); return 0; } /* 从指定地址连续写入 len 字节 */ int MR25_WriteBytes(SPI_HandleTypeDef *hspi, uint32_t addr, const uint8_t *pData, uint32_t len) { if (addr + len > MR25_CAPACITY_BYTES) { return -1; } /* 写使能 */ MR25_WriteEnable(hspi); /* 可选:回读状态确认 WEL 已置位 */ uint8_t status = MR25_ReadStatus(hspi); if ((status & 0x02) == 0) { return -2; /* 写使能没生效,必须停下排查 */ } MR25_CS_LOW(); MR25_SPI_Byte(hspi, 0x02); /* WRITE Array */ MR25_SPI_Byte(hspi, (addr >> 16) & 0xFF); MR25_SPI_Byte(hspi, (addr >> 8) & 0xFF); MR25_SPI_Byte(hspi, addr & 0xFF); for (uint32_t i = 0; i < len; i++) { MR25_SPI_Byte(hspi, pData[i]); } MR25_CS_HIGH(); /* MRAM 写操作在 CS 拉高后瞬间完成,不需要轮询 WIP */ return 0; }

这里有几个习惯和写 Flash 完全不同,值得单独说。第一,MRAM 写完后不需要查 WIP 位,也不需要等待 3~5ms 的页面编程时间,CS 拉高即写完成,所以我代码里直接返回 0,没有任何忙等待。第二,写使能和实际写命令之间,CS 必须经历一次完整的“拉低—发命令—拉高”过程,这是一个独立命令,不能把 WREN 和 WRITE 塞在同一个 CS 低电平周期里做,否则 MRAM 不会理你。第三,写入不需要先擦除,所以哪怕你要写一个字节,直接把数据填到目标地址即可,整个流程只有两个命令。

3.3 READ/WRITE 指令时序解读

细细拆一下 WRITE 命令的时序:CS 拉低后,主机先发 0x02,再发 24 位地址(高字节在前),然后连续发出数据字节。MR25H40CDF 是 4Mbit 容量,地址范围是 0x00000 到 0x7FFFF,三字节地址中最高字节的有效位只有 bit7,也就是地址最大值不超过 0x7FFFF。很多驱动出错就是因为没做地址越界检查,写越界后数据落到别的地址或者干脆出错。

READ 命令和 WRITE 命令结构几乎一样,只是命令码换成 0x03。它支持地址自增连续读,也就是发完地址之后,主机只要持续给 SCK,芯片就会一个字节一个字节把数据从 SO 吐出来,直到 CS 拉高。这个特性对读取日志、整块上传配置数据非常有用,一次命令就能批量搬完。

我实际测过,用 STM32F412ZG + ST-Link 虚拟串口做回环,SCK 配 20MHz,读写 512KB 全片数据,大约 300ms 出头,比同类容量的 SPI NOR Flash 慢一点点——但注意,这个时间主要是受 SPI 时钟频率限制,MRAM 本身写入没有擦除那种几十毫秒的卡顿,所以在小数据频繁写入的场景里,它的实际吞吐能力和一致性反而更好。

4. 工业现场数据存储方案设计:掉电安全与数据校验

驱动写完,读写都通了,这还只是“能用”。真正让工业客户点头的方案,必须解决掉电安全、数据完整性和写入策略这三个问题。MR25H40CDF 给了我们很好的底层能力,但上层设计不合理,照样丢数据。

4.1 掉电日志记录的关键点

工业设备最常见的需求是“日志记录”:记录开机时间、报警代码、运行参数、当前计数值。普通 Flash 方案里,日志必须做成均衡磨损的环形链表,每写一页都要先擦除一个扇区,如果指针管理有 bug,很容易把日志写乱。MRAM 方案里完全不用考虑磨损均衡,因为反复写同一地址不会伤害存储单元。

那是不是可以随便写?也不是。真正的风险来自“写到一半掉电”。假设你在写一条 16 字节的日志,CS 拉低后刚发出地址,电流就断了,那么这条日志里可能只有几个字节是新的、剩下是旧的,读出来就是一条撕裂的数据。解决办法是给每一条日志加“前导标志 + 长度 + CRC”,读的时候发现标志不对或者 CRC 校验不过,就判定这条日志无效,从下一条有效位置继续。

我在项目里还做过更稳妥的双备份策略:同一个关键数据在地址 A 和地址 B 各存一份,写的时候先写 A 再写 B,读的时候先读 A,若 A 校验失败则回退读 B。MRAM 写入速度快,双写一次也就几十个微秒,代价完全可接受。这样即使掉电时刻正好卡在两次写入之间,A 损坏了 B 往往还是完好的,最多丢一次更新,绝不会把设备参数全清空。

4.2 利用 STM32F412ZG 的 PVD 做紧急保存

这里必须提一下 STM32F412ZG 一个非常实用但很多人不用的外设:可编程电压检测器(PVD)。工业电源掉电不是瞬间降到 0V,而是一个有斜坡的过程,比如 24V 直流电源断开后,板级 3.3V 会以毫秒级的时间跌落到欠压点。我们可以把 PVD 阈值设在 2.9V 左右,当 3.3V 跌到阈值以下,MCU 进入 PVD 中断。这时候利用掉电前的“最后窗口期”,快速把最重要的几十字节参数写入 MRAM,就能保证即使在毫无预警的断电场景下,关键数据也是最新的。

具体操作上,PVD 中断里只做一件事:调用 MR25_WriteBytes 把全局状态结构体推进 MRAM。由于 MRAM 写入不需要擦除、不需要长等待,几十字节在掉电窗口内完全可以写完。要是换成 NOR Flash,掉电中断里又要擦除又要编程,还没写完,电压早就跌没了,这也是 MRAM 在“掉电守护”场景里不可替代的原因之一。

4.3 写次数与寿命的正确认知

MRAM 标称无限写寿命,这是指“写入本身不限定次数”,但从系统层面看,总线的可靠性、程序逻辑的 bug 都可能引入单调重复写同一个地址。以 MR25H40CDF 内部写周期约 100ns 计算,就算你 1MHz 频率不间断写入同一个地址,连续跑一年也才写入约 3 万亿次,离任何已知失效机理都远得很。所以选用 MRAM 后,我建议把设计重心从“省擦写次数”转移到“数据实时性”上来——大胆高频写,但要坚持做 CRC 校验和版本号管理。

另外要注意,MR25H40CDF 和所有串行存储器一样,状态寄存器里有 WPEN、BP1、BP0 这几个保护位。如果你不小心把状态寄存器配置成了写保护模式,后续所有写入命令都会被拒绝。驱动初始化时最好主动把状态寄存器写成 0x00,彻底打开全部地址的写入权限,避免上电默认状态不确定带来的妖蛾子。

5. 常见问题与调试实录:我在实际项目中踩过的坑

再好的方案,到了实测阶段总会冒出一些意想不到的问题。我把这次改造中真实遇到的现象、排查过程和最终解决办法整理出来,基本覆盖同类存储方案 80% 的坑。

5.1 意外断电后,明明写过却读不到新数据

排查思路先看写使能。我遇到过一版固件,MR25_WriteEnable 发完 WREN 后 CS 没有拉高就马上接着发 WRITE,导致芯片把 0x06 和 0x02 当成同一段数据流,WREN 根本没生效。驱动里我特意加了回读状态寄存器、检查 WEL 位的步骤,一旦发现 WEL 没有置位立刻返回错误码,调试阶段能快速暴露问题。另外,WP 引脚如果处于悬空或者受干扰拉低,也会导致写入被硬件封锁,接线时直接把 WP 接到 VCC 能规避这个问题。

5.2 数据和 0xFF 交替出现,或者某些地址读出来全是 0xFF

这个大概率是 SPI 模式或者时序问题。MR25H40CDF 虽然支持 Mode 0 和 Mode 3,但如果 CubeMX 配置成 Mode 1(CPOL=0,CPHA=1),时钟采样沿就和芯片要求对不上,写入数据时看起来发送了,实际芯片收到的全是无效电平。遇到这种症状,先用逻辑分析仪抓一下 SCK 空闲电平、采样沿和数据线上的时序,确认是不是模式配错。还有一种常见情况是 SCK 频率过高时劣化了建立保持时间,尤其飞线调试时导线长、分布电容大,降到 5MHz 再量一遍数据。

5.3 写入一次成功,第二次开始数据全是乱的

有一个我一开始完全没想道的坑:地址越界。MR25H40CDF 容量 512KB,地址范围 0x00000~0x7FFFF,而 24 位地址里高字节的部分位是无效的。如果驱动把地址计算错了,比如把地址计算成 0x8xxxx,芯片行为会不确定,可能落到物理映射的意外位置,读回来当然是乱数据。驱动里必须加addr + len > 0x80000的越界检查,发现越界第一时间报错而不是默默截断。

5.4 怎么确认芯片和接线没问题:一个 10 分钟的置信测试

调驱动前,我习惯先不给芯片发任何“数据内容”,而是做一轮全片固定模式读写,验证总线物理层。依次写入全 0x00、全 0xFF、0xAA、0x55,再读回来对比。如果这四轮都通过,说明 SPI 硬件链路是通的;如果某一轮失败,基本可以锁定是 MISO 路径或电源问题,而不是驱动逻辑问题。这个测试在量产板调试时格外有用,替我省了大量查错时间。

症状可能原因排查方法
数据读回全 FFWP 悬空/拉低、CS 抖动、SPI 模式错量 WP 电平;检查片选;核对 CPOL/CPHA
写入不生效WREN 未独立完成、状态寄存器有写保护回读状态寄存器查 WEL;初始化写 0x00 进状态寄存器
偶发数据错乱SCK 频率过高、走线太长、去耦不足降频到 5MHz;缩短飞线;加 100nF 去耦
掉电后丢最后一条掉电窗口太短、PVD 未配置开启 PVD 中断;MRAM 写入放在宕机前窗口
地址高位数据混乱地址越界、24 位地址赋值错误驱动加边界检查;打印实际发送地址字节

最后再说一个真香小技巧:MR25H40CDF 作为 SPI 设备,可以直接挂在现有 SPI Flash 的同一总线上,通过 CS 片选区分。我在这块板子上就是这么干的,两颗芯片共用 SPI1,一根 CS 拉低操作 MRAM,另一根 CS 拉低操作 Flash,固件升级走 Flash,运行数据走 MRAM,互不干扰。设计存储架构时,这个“双存储、各司其职”的组合方案比把所有需求压在一颗芯片上要灵活得多,值得你在下一个项目里试试。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/4 7:01:46

园区AI巡检告警闭环5大坑:从IM推送到工单联动的工程实践

1. 园区巡检告警闭环&#xff0c;为什么“发出去”不等于“处理完”做园区 AI 巡检系统的团队&#xff0c;十有八九会把注意力压在算法侧&#xff1a;摄像头选型、模型精度、误报率、边缘盒子算力。这些当然重要&#xff0c;但真正让一线运维骂娘的&#xff0c;往往不是“没检测…

作者头像 李华
网站建设 2026/10/4 7:01:27

台积电技术研发实力解析:从先进制程到良率闭环

在半导体行业待久了&#xff0c;你会发现一个很有意思的现象&#xff1a;几乎每家芯片公司都在强调“先进制程”&#xff0c;但真正能把先进制程从流片演示变成大规模出货产品的&#xff0c;绕来绕去总绕不开台积电。手机上用的应用处理器、PC里的GPU、AI服务器上那颗又贵又难买…

作者头像 李华
网站建设 2026/10/4 6:58:22

哈夫曼编码原理与Java实现:从优先队列到文件压缩实战

1. 项目概述与核心思路拆解1.1 哈夫曼编码到底是什么&#xff0c;为什么能压缩这东西说穿了不复杂&#xff0c;本质就是一句话&#xff1a;让出现频率高的字符用更短的二进制编码&#xff0c;让出现频率低的字符用更长的二进制编码&#xff0c;整体算下来总位数变小了&#xff…

作者头像 李华
网站建设 2026/10/4 6:53:06

Unity网络编程面经:从TCP/UDP选型到同步方案与弱网优化

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/4 6:51:58

Open-Shell:一键把 Windows 11 开始菜单改回经典高效布局

说实话&#xff0c;这两年我帮人装电脑&#xff0c;系统装完干的第一件事不是激活&#xff0c;不是装驱动&#xff0c;而是把开始菜单换掉。Windows 11 那个新的开始菜单&#xff0c;很多人真的用不惯&#xff0c;找程序要点开“所有应用”&#xff0c;最近文件的位置还被推荐内…

作者头像 李华
网站建设 2026/10/4 6:46:22

OpenRig:基于Node.js+tmux+Codex+YAML的本地AI推理装备栈

1. OpenRig 是什么&#xff1a;一个被严重误读的开源项目名OpenRig 这个名字最近在技术社区里频繁出现&#xff0c;但绝大多数搜索者其实并不清楚它到底指代什么——它既不是某个新发布的 AI 框架&#xff0c;也不是 Codex 的官方配套工具&#xff0c;更不是 Node.js 的衍生发行…

作者头像 李华