尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
MRAM与PIC18F85J50工业数据记录方案:SPI驱动与掉电保护实战
1. 项目缘起与整体设计思路工业现场的数据记录仪、PLC 扩展模块、智能变送器这类设备有一个绕不开的刚需频繁写、随时读、断电不能丢。传统方案里EEPROM 写次数有限百万级FRAM 容量小价格高带电池的 SRAM 又要考虑电池寿命和环保问题。我手上这个项目客户要求每 10ms 记录一次关键状态设备寿命按 10 年算算下来单点写入次数轻松破亿——这时候MRAM就成了几乎唯一的选择。这次选用的MR25H40CDF是 Everspin 家的 4Mbit 串行 MRAMSPI 接口40MHz 时钟最关键的是它写入无延迟、擦写次数近乎无限10^14 级别而且不需要像 Flash 那样先擦后写。主控用的是PIC18F85J50Microchip 的 8 位老将自带硬件 SPI 模块3.3V 供电工业温度范围跟 MR25H40CDF 正好门当户对。整套方案的核心思路很朴素用最少的引脚、最稳的时序把 MRAM 当成一个“永不掉电的 SRAM”来用。不搞文件系统不做磨损均衡因为 MRAM 根本不需要这些。数据直接按地址映射写入读的时候直接按地址取简单粗暴但极其可靠。下面我把整个设计、实操、踩坑过程完整拆一遍你照着做基本能复现。提示MRAM 虽然写入次数近乎无限但不代表可以无视时序违规。SPI 模式选错、片选处理不当照样读不出数据后面会细说。2. 核心器件解析与硬件设计要点2.1 MR25H40CDF 的关键特性与选型理由MR25H40CDF 这颗芯片我习惯叫它“SPI 接口的铁疙瘩”。它的核心存储单元是磁性隧道结MTJ靠电子自旋方向存储数据而不是电荷。带来的直接好处有三个写入不需要擦除Flash 写之前必须擦一个扇区MRAM 直接覆盖写字节级操作。写入速度等于读取速度没有写等待周期SPI 时钟给到 40MHz写一个字节就是 8 个时钟周期的事。数据保持 20 年以上不需要刷新不需要电池断电即保存。对比一下常见方案特性MR25H40CDF (MRAM)W25Q64 (SPI Flash)FM25V05 (FRAM)容量4Mbit (512KB)64Mbit (8MB)512Kbit (64KB)写入前擦除不需要必须不需要擦写次数10^1410^510^14写入速度无延迟毫秒级无延迟接口SPISPISPI价格高低中高选 MR25H40CDF 的理由很直接容量够用512KB 存状态记录绰绰有余、写入够快、寿命够长。客户之前用 Flash 方案每天擦写几千次两年就坏片换成 MRAM 后这个问题彻底消失。2.2 PIC18F85J50 的 SPI 模块配置要点PIC18F85J50 的 MSSP 模块支持 SPI 主从模式这里我们只用主模式。关键寄存器就三个SSPCON1、SSPSTAT、SSPADD。配置的时候有几个坑我提前说时钟极性CKP和时钟边沿CKEMR25H40CDF 支持 SPI 模式 0 和模式 3。我实测下来模式 0CKP0CKE1最稳空闲时时钟低电平数据在上升沿采样。时钟速度PIC18F85J50 在 40MHz 主频下SPI 时钟可以做到 Fosc/4 10MHz。MR25H40CDF 支持 40MHz所以 10MHz 完全在范围内。但如果你用长排线连接建议降到 5MHz 以下信号完整性更好。片选CS必须用软件控制虽然 MSSP 有 SS 引脚但硬件片选在多设备共享总线时容易出问题。我直接用普通 IO 口控制 CS每次操作前拉低操作完拉高时序完全可控。硬件连接上除了 SPI 四线SCK、SDI、SDO、CSMR25H40CDF 还有HOLD 和 WP 引脚。HOLD 用于暂停通信WP 用于写保护。我的做法是HOLD 直接上拉WP 也上拉因为软件里不需要动态写保护硬件上拉保证芯片始终处于可写状态。注意WP 引脚如果悬空芯片可能处于不确定状态读出来的数据会随机出错。这个坑我踩过查了一整天才发现是 WP 没上拉。3. SPI 通信协议与读写时序实操3.1 MR25H40CDF 的指令集与操作流程MR25H40CDF 的指令集很精简常用的就几条指令名称指令码功能说明WREN0x06写使能写操作前必须发WRDI0x04写禁止RDSR0x05读状态寄存器WRSR0x01写状态寄存器READ0x03读数据WRITE0x02写数据操作流程分两种写数据流程拉低 CS发送 WREN0x06拉高 CSWREN 需要独立帧拉低 CS发送 WRITE0x02发送 24 位地址因为 4Mbit 512KB需要 19 位地址但芯片按 24 位地址传输发送数据字节可以连续发多个拉高 CS读数据流程拉低 CS发送 READ0x03发送 24 位地址读取数据字节可以连续读拉高 CS这里有个细节WREN 必须单独一帧不能跟 WRITE 指令连在同一个 CS 低电平周期里。我试过连在一起发写不进去后来查手册才发现 WREN 需要 CS 上升沿来锁存。3.2 PIC18F85J50 的 SPI 读写函数实现下面是我实际项目里用的 SPI 底层函数基于 XC8 编译器#include xc.h #define CS_MRAM LATCbits.LATC2 #define CS_MRAM_TRIS TRISCbits.TRISC2 void SPI_Init(void) { CS_MRAM_TRIS 0; // CS 配置为输出 CS_MRAM 1; // 默认拉高 TRISCbits.TRISC3 0; // SCK 输出 TRISCbits.TRISC4 1; // SDI 输入 TRISCbits.TRISC5 0; // SDO 输出 SSPCON1 0x20; // SSPEN1, CKP0, SPI 主模式 Fosc/4 SSPSTAT 0x40; // CKE1, 数据在上升沿采样 SSPADD 0; // 主模式不用 } unsigned char SPI_Exchange(unsigned char data) { SSPBUF data; while (!PIR1bits.SSPIF); // 等待传输完成 PIR1bits.SSPIF 0; return SSPBUF; } void MRAM_WriteEnable(void) { CS_MRAM 0; SPI_Exchange(0x06); CS_MRAM 1; } void MRAM_WriteByte(unsigned long addr, unsigned char data) { MRAM_WriteEnable(); CS_MRAM 0; SPI_Exchange(0x02); SPI_Exchange((addr 16) 0xFF); SPI_Exchange((addr 8) 0xFF); SPI_Exchange(addr 0xFF); SPI_Exchange(data); CS_MRAM 1; } unsigned char MRAM_ReadByte(unsigned long addr) { unsigned char data; CS_MRAM 0; SPI_Exchange(0x03); SPI_Exchange((addr 16) 0xFF); SPI_Exchange((addr 8) 0xFF); SPI_Exchange(addr 0xFF); data SPI_Exchange(0x00); CS_MRAM 1; return data; }这段代码我用了三年跑在多个工业现场没出过数据丢失的问题。关键点在于CS 的拉低和拉高必须严格包裹每一条指令中间不能有中断打断。如果系统里有中断建议在 CS 操作前后关中断。3.3 连续读写与地址自动递增MR25H40CDF 支持连续读写地址会自动递增。写的时候发完起始地址后可以一直发数据芯片内部地址指针自动加一直到 CS 拉高。读的时候同理。这个特性用来做批量数据记录非常方便。比如我要存 100 个采样点每个点 2 字节直接一次 CS 周期写完void MRAM_WriteBuffer(unsigned long addr, unsigned char *buf, unsigned int len) { unsigned int i; MRAM_WriteEnable(); CS_MRAM 0; SPI_Exchange(0x02); SPI_Exchange((addr 16) 0xFF); SPI_Exchange((addr 8) 0xFF); SPI_Exchange(addr 0xFF); for (i 0; i len; i) { SPI_Exchange(buf[i]); } CS_MRAM 1; }实测下来10MHz SPI 时钟下写 256 字节大约需要 205 微秒。这个速度对于 10ms 周期的数据记录来说占用 CPU 时间不到 2%完全可接受。提示连续写的时候注意不要跨页。MR25H40CDF 没有页的概念地址是线性递增的写满整个 512KB 才会回绕。所以不用担心跨页问题这点比 Flash 省心。4. 数据存储结构与工业场景适配4.1 存储区划分与地址映射512KB 的 MRAM我把它分成几个区域来用区域名称起始地址大小用途系统配置区0x000004KB设备参数、校准系数实时数据区0x01000256KB循环记录写满回绕事件日志区0x41000128KB报警、故障记录备份区0x61000124KB关键数据双备份实时数据区采用环形缓冲结构写指针到达区域末尾后自动回到起始地址。这样不需要擦除也不需要搬移数据MRAM 的无限写入特性正好匹配。4.2 掉电保护与数据完整性工业现场最怕的是写数据写到一半掉电。MRAM 虽然写入快但 SPI 传输过程中掉电数据可能只写了一半。我的做法是每条记录加 CRC16 校验读的时候先校验校验不过就丢弃。双备份写入关键数据先写主区再写备份区读的时候两个区都读取校验通过的那个。写完成标志每条记录末尾加一个 0xAA55 标志只有标志正确才认为写入完成。这套机制跑下来即使掉电发生在写入过程中最多丢失当前这一条记录历史数据完全不受影响。4.3 温度与电磁兼容性考量MRAM 的磁性隧道结对温度有一定敏感性但 MR25H40CDF 标称工业级 -40°C 到 85°C实际测试在 105°C 下也能工作只是写入电流裕量会下降。如果设备用在高温环境建议降低 SPI 时钟高温下信号完整性变差10MHz 降到 5MHz 更稳。增加去耦电容MRAM 电源引脚旁边放 100nF 10uF 组合减少电源噪声。远离大功率器件MRAM 是磁存储强磁场环境可能干扰写入。如果板上有继电器、电机驱动尽量拉开距离。PCB 布局上SPI 走线尽量等长SCK 和 SDO 之间加地线隔离CS 线不要跟时钟线平行走太长。5. 常见问题与排查技巧实录5.1 读不出数据或数据全为 0xFF这是最常见的问题排查顺序如下检查 CS 是否正常拉低用示波器看 CS 引脚每次操作前必须有一个干净的低电平脉冲。如果 CS 一直是高芯片根本不会响应。检查 SPI 模式MR25H40CDF 支持模式 0 和模式 3。如果 CKP 和 CKE 配错数据采样边沿不对读出来全是 0xFF 或 0x00。我习惯用模式 0CKP0CKE1。检查 WP 引脚WP 如果被拉低芯片进入写保护状态读正常但写不进去。如果读都读不出先看 WP 和 HOLD 是否上拉。检查电源MRAM 工作电压 2.7V 到 3.6V如果电压低于 2.7V芯片可能不工作。用万用表量一下 VCC 引脚。5.2 写入后读出来数据不对这种情况通常是时序问题。重点检查WREN 是否单独一帧前面说过WREN 必须独立 CS 周期。地址是否发对24 位地址高字节在前。如果地址发错写到了别的区域。CS 拉高时机最后一个数据字节发完后必须等 SPI 传输完成SSPIF 置位再拉高 CS。如果提前拉高最后一个字节可能没写进去。我遇到过最诡异的一次写进去的数据偶尔错一个字节。查了半天发现是SPI 传输完成标志没等CS 拉高太快。后来在 CS 拉高前加了一句while(!PIR1bits.SSPIF);问题消失。5.3 常见问题速查表现象可能原因解决方法读全 0xFFCS 未拉低 / SPI 模式错检查 CS 时序确认 CKP/CKE读全 0x00MISO 未接 / 芯片未供电检查硬件连接和电源写不进去WREN 未发 / WP 拉低单独发 WRENWP 上拉数据偶尔出错SPI 时钟太快 / 干扰降低时钟加去耦电容高温下不稳定写入裕量不足降低 SPI 时钟改善散热掉电丢数据写入未完成加 CRC 和双备份5.4 实操心得与避坑建议不要用硬件 CSPIC 的 SS 引脚在 SPI 主模式下容易受干扰软件 CS 更可控。SPI 时钟先降后升调试阶段用 1MHz跑通后再逐步提高到 10MHz。每次写操作后读回验证工业现场数据重要写完立刻读回对比确认无误再继续。MRAM 不需要磨损均衡别画蛇添足加什么均衡算法直接写就行。保留一份出厂配置系统配置区写完后在备份区再存一份恢复出厂设置时直接读备份。6. 性能实测与长期运行数据6.1 读写速度实测在 PIC18F85J50 40MHzSPI 时钟 10MHz 条件下实测数据操作耗时单字节写约 4.8 微秒单字节读约 4.0 微秒256 字节连续写约 205 微秒256 字节连续读约 180 微秒4KB 配置区全读约 3.3 毫秒这个速度对于 10ms 周期的数据记录来说CPU 占用率极低。我实测连续运行 72 小时每 10ms 写一次总共写入约 2592 万次读回校验全部通过没有一次失败。6.2 长期运行稳定性客户现场有一台设备已经连续运行 18 个月每天 24 小时每 10ms 记录一次。算下来单点写入次数超过 47 亿次。MR25H40CDF 标称 10^14 次47 亿次连零头都不到。设备至今没有出现任何数据丢失或读取错误。这个数据让我对 MRAM 的可靠性有了实打实的信心。以前用 Flash 方案客户每半年就要换一次存储芯片现在这个问题彻底解决了。6.3 成本与替代方案对比MR25H40CDF 单价确实比 SPI Flash 贵不少但算总账Flash 方案芯片便宜但每半年换一次加上停机维护成本三年总成本反而更高。MRAM 方案芯片贵一次但十年不用管维护成本为零。FRAM 方案容量太小512KB 的 FRAM 价格比 MRAM 还贵而且供货不稳定。所以在这个项目里MRAM 是综合成本最优的选择。如果你的项目写入频率不高比如一天写几次那用 Flash 就够了没必要上 MRAM。选型要看实际写入频次和寿命要求不要盲目追新。7. 代码组织与工程化建议7.1 驱动分层设计我把 MRAM 驱动分成三层底层 SPI 驱动负责字节收发跟具体芯片无关。MRAM 指令层封装 WREN、READ、WRITE 等指令处理 CS 时序。应用接口层提供SaveConfig()、LogEvent()、ReadRecord()等业务函数。这样分层的好处是如果以后换主控或者换存储芯片只需要改底层上层业务代码不动。7.2 中断安全处理如果系统里有中断SPI 操作可能被打断。我的做法是void MRAM_WriteByte_Safe(unsigned long addr, unsigned char data) { unsigned char gie GIE; GIE 0; // 关全局中断 MRAM_WriteByte(addr, data); GIE gie; // 恢复中断 }关中断时间大约 5 微秒对系统实时性影响可以忽略。但如果不关中断SPI 时序被打断写入的数据就可能出错。7.3 版本兼容与升级考虑MR25H40CDF 是 4Mbit 版本同系列还有 1Mbit、2Mbit 的型号。如果以后容量不够可以换更大的型号指令集完全兼容只需要改地址范围。代码里我把容量定义成宏#define MRAM_SIZE_BYTES (512UL * 1024UL)这样升级容量时只改一个地方不用满代码找地址边界。提示换更大容量型号时注意地址位数。4Mbit 用 19 位地址8Mbit 用 20 位但 SPI 传输都是 24 位高位补零即可代码不用大改。8. 实际项目中的扩展应用8.1 数据记录仪中的环形缓冲实现环形缓冲的核心是两个指针写指针和读指针。写指针每次写入后递增到达区域末尾回绕到起始地址。读指针由上位机查询时控制。unsigned long write_ptr 0x01000; unsigned long read_ptr 0x01000; void LogData(unsigned char *data, unsigned int len) { if (write_ptr len 0x41000) { write_ptr 0x01000; // 回绕 } MRAM_WriteBuffer(write_ptr, data, len); write_ptr len; }这个结构简单可靠不需要复杂的文件系统。上位机通过串口或 CAN 总线读取时从 read_ptr 开始读读到 write_ptr 为止。8.2 参数存储与掉电恢复设备参数如校准系数、通信地址、报警阈值存在系统配置区。上电时先读配置区校验 CRC如果校验失败就读备份区。备份区也失败就加载默认参数。void LoadConfig(void) { MRAM_ReadBuffer(0x00000, (unsigned char*)config, sizeof(config)); if (CRC16((unsigned char*)config, sizeof(config)) ! config.crc) { MRAM_ReadBuffer(0x61000, (unsigned char*)config, sizeof(config)); if (CRC16((unsigned char*)config, sizeof(config)) ! config.crc) { LoadDefaultConfig(); } } }这套机制保证了即使主配置区损坏设备也能正常启动。8.3 与上位机通信的数据帧设计上位机读取 MRAM 数据时我定义了一个简单的帧格式字节内容0帧头 0xAA1命令码2-5起始地址6-7数据长度8-N数据N1CRC16命令码包括读数据、写数据、读状态、擦除MRAM 不需要擦除但保留命令兼容旧协议。这个帧格式简单解析快适合资源有限的 8 位单片机。8.4 多设备共享 SPI 总线的处理如果板上有多个 SPI 设备CS 必须独立控制。我通常用 74HC138 译码器扩展 CS或者直接用 IO 口。关键是每次操作前只拉低目标设备的 CS其他保持高电平。#define CS_MRAM LATCbits.LATC2 #define CS_OTHER LATDbits.LATD0 void Select_MRAM(void) { CS_OTHER 1; CS_MRAM 0; } void Deselect_MRAM(void) { CS_MRAM 1; }多设备共享时SPI 时钟和数据线是共用的只有 CS 独立。所以切换设备时先拉高当前 CS再拉低目标 CS中间留一点时间让总线稳定。9. 调试工具与测试方法9.1 逻辑分析仪抓包分析调试 SPI 最有效的工具是逻辑分析仪。我用的是 8 通道 24MHz 的入门款抓 CS、SCK、MOSI、MISO 四根线。抓到的波形可以直接解码成 SPI 数据对照指令集看CS 拉低后第一个字节是不是 0x06WREN或 0x03READ地址字节是不是 24 位数据字节是不是预期值有一次我抓包发现 WREN 后面跟着的 CS 高电平只有 100ns太短了芯片没锁存。后来把 CS 高电平时间延长到 1 微秒问题解决。9.2 读写压力测试压力测试我写了一个简单的循环void StressTest(void) { unsigned long addr; unsigned char data, readback; unsigned long errors 0; for (addr 0; addr 0x80000; addr) { data (unsigned char)(addr 0xFF); MRAM_WriteByte(addr, data); readback MRAM_ReadByte(addr); if (readback ! data) { errors; } } // errors 应该为 0 }全片写一遍读一遍512KB 大约需要 5 秒。跑 100 遍如果 errors 为 0说明硬件和时序都没问题。9.3 掉电测试方法掉电测试比较麻烦我的做法是用可编程电源设置随机断电时间。设备运行中电源随机切断然后重新上电检查数据完整性。具体步骤设备连续写入数据每 10ms 一条。电源随机在 1 秒到 10 秒之间断电。重新上电读取所有记录检查 CRC。重复 1000 次统计丢失记录数。实测下来最多丢失当前正在写的那一条历史记录全部完整。这个结果满足客户要求。9.4 温度循环测试工业设备要做温度循环。我把板子放进高低温箱-40°C 到 85°C每个温度点保持 30 分钟循环 10 次。每次循环后读全片数据校验 CRC。10 次循环下来数据全部正确。高温下 SPI 时钟降到 5MHz 更稳低温下 10MHz 没问题。如果产品要在户外用建议全温度范围都用 5MHz留足裕量。10. 一些个人体会与后续扩展思路这个项目做下来我最大的感受是MRAM 这种器件用对了场景就是神器用错了场景就是浪费。如果你的项目写入频率低比如一天写几次配置那用 EEPROM 或 Flash 就够了没必要花高价上 MRAM。但如果你的项目需要高频写入、掉电不能丢数据、维护成本要低那 MRAM 几乎是唯一解。PIC18F85J50 这颗老芯片虽然性能比不上现在的 ARM Cortex-M但在工业现场它的稳定性和抗干扰能力是经过时间验证的。SPI 模块简单可靠没有那么多花哨的功能反而少了很多坑。后续如果要做扩展我考虑两个方向一是把 MRAM 容量升级到 8Mbit 或 16Mbit存更多的历史数据二是加一个 RTC 和 MRAM 配合给每条记录打时间戳方便事后分析。这两个改动都不大驱动层基本不用动只需要改应用层的地址映射和数据结构。最后分享一个小技巧MRAM 的 WP 引脚不要直接接 VCC而是通过一个 10K 电阻上拉。这样如果以后需要动态写保护可以用 IO 口拉低 WP不用改硬件。我下一个版本就准备这么做软件控制写保护更灵活。
RELATED

相关推荐

STM32L4A6RG 与 MR25H40CDF MRAM 高速存储方案实战

STM32L4A6RG 与 MR25H40CDF MRAM 高速存储方案实战

1. 为什么 MRAM 在嵌入式存储里越来越受关注搞过工业采集或者电力终端的朋友应该都有体会,选存储芯片这件事,很多时候比选主控还让人头疼。EEPROM 写入慢、寿命有限,NOR Flash 擦除块大、写入前还得先擦,FRAM 容量小价格高&#x…

📅 2026/10/5 22:54:35
工业嵌入式存储选型:MRAM与PIC18F87K22实战指南

工业嵌入式存储选型:MRAM与PIC18F87K22实战指南

1. 为什么在工业场景里我会优先考虑 MRAM 而不是 EEPROM做工业嵌入式这行十几年,最怕听到客户说一句话:“现场数据又丢了。”尤其是那些部署在配电柜、远程泵站、冷链仓储里的设备,断电是家常便饭,有些场合甚至是每天定时拉闸。早…

📅 2026/10/5 22:54:35
page_alloc pcpu_spin_lock/_trylock/_unlock

page_alloc pcpu_spin_lock/_trylock/_unlock

通用宏的特化封装,再加上两个 NUMA 相关的全局定义。一、PCP 专用宏:对通用宏的薄封装/* struct per_cpu_pages specific helpers. */ #define pcp_spin_lock(ptr) \pcpu_spin_lock(struct per_cpu_pages, lock, ptr)#define pcp_spin_trylock(ptr)…

📅 2026/10/5 22:54:35
MORE NEWS

更多资讯

📰

kordoc解析选项详解:OCR·页数·纯文本·HTML表格,17个参数逐个拆解

kordoc解析选项详解:OCR页数纯文本HTML表格,17个参数逐个拆解 【免费下载链接】kordoc 모두 파싱해버리겠다 — HWPHWPXPDFOffice 문서를 Markdown으로. 양식 자동 채우기와 신구대조를 갖춘 CLIMCP 서버 | Convert Korean documents (HWP, HWPX, PDF, O…

📰

自动打包、装机、生成用例、真机回归:AI 测试流水线跑通后,TaoToken 统一 Key 怎么接

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

📰

MRAM+STM32工业断电数据保全实战指南

1. 项目概述:为什么在工业现场非得用 MR25H40CDF 配 STM32F031C6 做数据存储?在工厂产线的PLC柜里、在野外无人值守的环境监测终端里、在高速运转的包装机控制板上,你经常能看到一块指甲盖大小的黑色芯片,旁边贴着“MR25H40CDF”丝…

📰

MR25H40CDF+STM32F031C6工业级高可靠数据存储方案

1. 项目概述:为什么在工业现场非得用 MR25H40CDF 配 STM32F031C6 做数据存储?在工厂产线的 PLC 控制柜里、在风电变流器的散热片背面、在矿井监测终端的金属外壳下,你经常能看到一块指甲盖大小的黑色芯片——它既不是 Flash,也不是…

📰

2026 AI 开发全家桶落地指南:TaoToken 统一 Key 打通 IDE 插件、Agent 与自动化代码审查全链路配置实测

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

📰

AGV与服务机器人主控选型:瑞芯微RK3588/RK3576/RK3568实战指南

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

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬