STM32双板SPI主从通信实战:从接线到数据不丢的完整指南
简介这是一份面向嵌入式初学者与STM32开发者的SPI双机通信例程聚焦两块STM32板子之间通过SPI协议完成主从数据交互的完整实现并附带液晶显示相关代码帮助读者理解同步串行通信在实际项目中的落地方式。资源包共60个文件约450KB包含c与h源码、uvproj工程文件、map与lst编译列表、hex烧录文件及readme说明等覆盖从工程配置到编译输出的完整链路代码中配有详细注解便于对照寄存器配置理解每一步操作。内容涉及SPI工作模式选择、CPOL与CPHA极性相位配置、CR1/CR2/SR/DR等关键寄存器使用以及GPIO初始化、数据收发、错误处理与液晶屏命令数据传输等环节。目前已有3086人学习下载适合希望从零掌握STM32 SPI主从通信、并借助注解快速上手调试的读者参考实践。1. 两块板子之间的 STM32 SPI 通信从「接线能通」到「数据不丢」的完整落地手头有两块 STM32 开发板想把它们用 SPI 连起来跑通通信这个需求在工控、传感器组网、双核协作场景里非常常见。但很多人第一次上手就翻车主机发数据从机收不到或者收到一堆 0xFF示波器上看波形又「好像没问题」。问题往往不在 SPI 本身而在片选时序、时钟极性、DMA 配置和中断优先级这些细节上。这篇笔记按「先讲清 SPI 主从到底怎么握手再给一套能直接复现的双板例程最后把踩过的坑摊开」的顺序写目标读者是已经会点灯、会串口打印、但还没把 SPI 主从跑稳的嵌入式开发者。整套方案基于常见 STM32F1/F4 系列用 HAL 库实现主机用查询或 DMA 发送从机用中断接收代码可以直接搬到你的工程里改引脚就能跑。2. SPI 主从通信的底层逻辑与硬件选型2.1 为什么 SPI 主从不能「随便接四根线就通」SPI 是全双工同步串行总线四根线分别是 SCK、MOSI、MISO、CS。很多人以为只要把主机的 MOSI 接从机的 MOSI、MISO 接 MISO 就行这是第一个翻车点。正确接法是主机 MOSI 接从机 MOSI主机输出、从机输入主机 MISO 接从机 MISO主机输入、从机输出SCK 对 SCKCS 由主机控制拉低选中从机。如果两块板子都配置成主机或者都配成从机通信必然失败。更隐蔽的问题是时钟极性 CPOL 和时钟相位 CPHA。SPI 有四种模式主机和从机必须完全一致否则采样点错位收到的数据就是乱的。常见做法是主机用模式 0CPOL0CPHA0从机也配模式 0。如果你在 CubeMX 里配了主机模式 0、从机模式 3示波器上看 SCK 有波形但数据就是错的这种玄学问题排查起来很费时间。还有一个容易被忽略的点从机的 NSS 引脚。如果用硬件 NSS从机在 CS 拉低时自动使能如果用软件 NSS从机需要手动在接收前把 NSS 内部拉低。HAL 库里从机模式通常建议把 NSS 配成「硬件输入」或者直接用软件管理具体看你的芯片手册。我一般会在从机初始化后先调一次HAL_SPI_Receive_IT让从机处于等待接收状态否则主机发数据时从机还没准备好第一帧必丢。2.2 双板 SPI 的引脚分配与时钟参数怎么定以两块 STM32F103C8T6 为例主机用 SPI1从机用 SPI1引脚分配如下表。注意从机的 SCK 和 MOSI 是输入MISO 是输出CS 是输入。信号主机引脚从机引脚方向SCKPA5PA5主机输出从机输入MISOPA6PA6主机输入从机输出MOSIPA7PA7主机输出从机输入CSPA4PA4主机输出从机输入时钟参数方面SPI 波特率预分频值决定 SCK 频率。主机如果跑 72MHz预分频 8 就是 9MHz预分频 16 就是 4.5MHz。从机不需要设波特率它跟随主机时钟。但要注意从机的 SPI 时钟不能超过芯片手册规定的最大值F1 系列 SPI 最高 18MHzF4 系列可以到 42MHz 以上。实际调试时建议先把主机波特率降到 1MHz 左右确认通信正常后再往上加这样能排除时序余量不足导致的偶发错误。数据帧格式默认 8 位也可以配 16 位。如果传的是结构体或浮点数建议统一按字节流处理在应用层做打包和解包不要依赖 SPI 的 16 位帧否则大小端和对齐问题会让你很头疼。2.3 主机查询发送与从机中断接收的职责划分SPI 是主机驱动的总线从机不能主动发起传输。所以典型架构是主机定时或事件触发发送从机一直处于接收就绪状态。主机可以用查询方式发送代码简单但会阻塞 CPU也可以用 DMA 发送适合大数据块。从机必须用中断或 DMA 接收因为从机不知道主机什么时候发数据查询方式会漏帧。我一般这样分工主机用HAL_SPI_Transmit查询发送短命令用HAL_SPI_Transmit_DMA发长数据包从机用HAL_SPI_Receive_IT逐字节接收收满一帧后在回调里置标志位主循环处理。这样从机不会丢数据主机也不会被长数据阻塞。3. 手把手搭建双板 SPI 通信例程3.1 主机端初始化与发送代码主机端 CubeMX 配置SPI1 模式选 Full-Duplex Master硬件 NSS 选 Disable预分频先设 16CPOL LowCPHA 1 Edge。生成代码后在 main 里加发送逻辑。// 主机发送缓冲区前两字节是帧头后面是数据 uint8_t tx_buf[8] {0xAA, 0x55, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06}; // 手动控制 CS 引脚PA4 配成 GPIO 输出 void SPI_CS_Low(void) { HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); } void SPI_CS_High(void) { HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); } // 查询方式发送一帧 void SPI_SendFrame(void) { SPI_CS_Low(); HAL_SPI_Transmit(hspi1, tx_buf, sizeof(tx_buf), 100); // 超时 100ms SPI_CS_High(); }逻辑说明CS 拉低选中从机HAL_SPI_Transmit会依次把 tx_buf 里的 8 个字节通过 MOSI 发出去同时 SCK 输出 8 个时钟脉冲。超时参数 100 是毫秒防止总线异常时死等。发送完拉高 CS从机收到帧结束信号。注意HAL_SPI_Transmit是阻塞式如果主机主循环里还有其他任务建议改用 DMA 版本。参数说明sizeof(tx_buf)是发送字节数必须是 uint8_t 数组。超时时间根据你的波特率算8 字节在 1MHz 下不到 100us100ms 足够。如果从机没接好这里会返回 HAL_TIMEOUT可以在返回值里判断。3.2 从机端中断接收与回调处理从机 CubeMX 配置SPI1 模式选 Full-Duplex SlaveNSS 选 Hardware Input 或者 SoftwareCPOL LowCPHA 1 Edge和主机一致。生成代码后在 main 里启动接收中断。// 从机接收缓冲区 uint8_t rx_buf[8]; volatile uint8_t rx_flag 0; // 接收完成标志 // 在 main 初始化后调用启动第一次接收 void SPI_Slave_StartReceive(void) { HAL_SPI_Receive_IT(hspi1, rx_buf, sizeof(rx_buf)); } // 接收完成回调HAL 库自动调用 void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi) { if (hspi-Instance SPI1) { rx_flag 1; // 置标志主循环处理 // 重新启动接收准备下一帧 HAL_SPI_Receive_IT(hspi1, rx_buf, sizeof(rx_buf)); } } // 主循环里检查标志 void Main_Loop(void) { if (rx_flag) { rx_flag 0; // 处理 rx_buf 里的数据比如校验帧头 if (rx_buf[0] 0xAA rx_buf[1] 0x55) { // 有效帧解析后续数据 } } }逻辑说明从机调用HAL_SPI_Receive_IT后SPI 外设进入接收状态每来一个 SCK 脉冲就移入一位收满 8 字节后触发回调。回调里置标志并重新启动接收保证连续帧不丢。主循环检测标志后处理数据处理完清标志。注意回调里不要做耗时操作否则会阻塞下一次接收。参数说明HAL_SPI_Receive_IT的第三个参数是接收字节数必须和主机发送字节数一致。如果主机发 8 字节、从机收 4 字节从机会在收满 4 字节后触发回调剩下的 4 字节丢失。所以双方要约定好帧长度或者用变长协议在帧头里带长度字段。3.3 用逻辑分析仪验证时序的四个关键点代码跑起来后如果通信不正常先别改代码用逻辑分析仪抓 SCK、MOSI、CS 三根线。重点看四个点第一CS 拉低到第一个 SCK 上升沿之间有没有足够建立时间一般要大于 50ns第二SCK 空闲电平是不是和 CPOL 配置一致模式 0 空闲低第三MOSI 数据在 SCK 上升沿是否稳定如果数据跳变和时钟沿重合说明 CPHA 配错了第四CS 拉高前最后一个 SCK 沿之后数据是否保持足够时间。我遇到过一种情况主机发送 8 字节逻辑分析仪上 SCK 只有 7 个脉冲最后一字节没发出去。原因是HAL_SPI_Transmit超时设太短发送到第 7 字节时超时返回了。把超时从 1ms 改成 100ms 就好了。所以超时参数不要抠太紧留足余量。4. 避坑指南双板 SPI 调试中最容易翻车的五个问题4.1 从机收到全 0xFF 或全 0x00现象主机发送正常从机 rx_buf 里全是 0xFF 或 0x00帧头校验永远不过。原因全 0xFF 通常是从机 MISO 线悬空或从机没使能SPI 外设读到的都是高电平全 0x00 是从机时钟没接对或者从机 SPI 根本没启动。还有一种可能是从机 NSS 配置错误硬件 NSS 模式下 CS 没拉低从机不工作。解决先检查 MISO 线是否接好用万用表测从机 MISO 引脚在空闲时是不是高阻。然后确认从机HAL_SPI_Receive_IT是否在主机发送前就调用了。最后检查从机 NSS 配置如果 CubeMX 里选了 Hardware InputCS 必须接对如果选了 Software需要在接收前手动把 NSS 内部拉低或者直接用__HAL_SPI_ENABLE使能。4.2 第一帧数据总是丢失现象主机发第一帧从机没反应发第二帧开始正常。原因从机启动接收中断需要时间主机发第一帧时从机还没进入接收状态。或者从机在回调里重新启动接收的时机太晚主机第二帧已经发完了。解决从机初始化后先调一次HAL_SPI_Receive_IT再进主循环。主机发送前加 10ms 延时等从机就绪。更稳妥的做法是主机先发一个无效帧「预热」从机收到后丢弃从第二帧开始正式通信。另外回调里重新启动接收要放在最前面不要等处理完数据再启动。4.3 DMA 发送时数据错位或只发了一半现象主机用 DMA 发送长数据从机收到的数据错位或者只收到前半部分。原因DMA 传输完成中断和 SPI 传输完成中断的优先级没配好DMA 传完就关了 CS最后一个字节还没移出。或者 DMA 缓冲区是局部变量函数返回后内存被回收DMA 读到的是垃圾数据。解决DMA 发送时CS 拉高要放在 DMA 传输完成回调里不要放在HAL_SPI_Transmit_DMA后面直接拉高。缓冲区用全局变量或静态变量确保 DMA 访问期间内存有效。如果数据错位检查 DMA 的传输宽度是不是配成了 Half-Word 而 SPI 是 8 位两者要一致。4.4 高速通信时偶发误码现象波特率降到 1MHz 时通信正常升到 9MHz 后偶尔收到错误数据。原因杜邦线太长、没有屏蔽高速时钟下信号反射和串扰导致误码。或者从机中断优先级太低收数据时被其他中断打断SPI 移位寄存器溢出。解决缩短接线长度最好在 10cm 以内SCK 和 MOSI 走线尽量短。从机 SPI 中断优先级设到最高或者用 DMA 接收减少中断次数。如果还不行在 SCK 和地之间并一个 33pF 电容滤高频毛刺。实在要长距离通信加一片 SPI 缓冲芯片或者改用差分方案。4.5 从机回调里重新启动接收导致死锁现象从机跑一段时间后不再接收数据复位才能恢复。原因回调里调用HAL_SPI_Receive_IT时如果上一次接收还没完全关闭HAL 库会返回 HAL_BUSY导致接收没有重新启动。或者回调里调用了其他 HAL 函数和 SPI 中断产生竞争。解决在回调里先检查HAL_SPI_GetState状态确认是 READY 再启动。或者用HAL_SPI_Receive_IT的返回值判断如果返回 HAL_BUSY在主循环里重试。更简单的办法是用 DMA 接收DMA 完成回调里重新启动DMA 的竞争问题比中断少。5. 进阶技巧用双缓冲和帧校验把 SPI 通信做稳前面讲的例程能跑通基本通信但实际项目里往往要连续传大量数据还要保证不丢帧、不错帧。我一般会在从机端加双缓冲和帧校验主机端加应答重传。这套组合拳打下来SPI 通信的稳定性会提升一个档次。双缓冲的思路是从机准备两个接收缓冲区 A 和 B当前用 A 接收时主循环处理 B 里的上一帧数据A 收满后切换到 B 接收主循环处理 A。这样接收和处理互不阻塞高速连续帧也不会丢。实现上可以用 DMA 的双缓冲模式或者手动在回调里切换缓冲区指针。// 从机双缓冲定义 uint8_t rx_buf_a[32]; uint8_t rx_buf_b[32]; uint8_t *active_buf rx_buf_a; // 当前接收缓冲区 uint8_t *ready_buf NULL; // 待处理缓冲区 volatile uint8_t frame_ready 0; // 启动接收绑定当前 active_buf void SPI_Slave_StartReceive(void) { HAL_SPI_Receive_IT(hspi1, active_buf, 32); } // 回调里切换缓冲区 void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi) { if (hspi-Instance SPI1) { ready_buf active_buf; // 当前缓冲区标记为待处理 active_buf (active_buf rx_buf_a) ? rx_buf_b : rx_buf_a; // 切换 frame_ready 1; HAL_SPI_Receive_IT(hspi1, active_buf, 32); // 用新缓冲区继续收 } } // 主循环处理 ready_buf void Main_Loop(void) { if (frame_ready) { frame_ready 0; // 校验帧头、长度、CRC if (ready_buf[0] 0xAA ready_buf[1] 0x55) { uint8_t len ready_buf[2]; uint8_t crc ready_buf[3 len]; // 计算校验并处理数据 } } }帧校验我一般用「帧头 长度 数据 CRC8」的格式。帧头用 0xAA55 这种不容易出现在数据里的值长度字段告诉从机这一帧有多少字节CRC8 校验数据完整性。主机发送前算好 CRC从机收到后重新算一遍不一致就丢弃并请求重传。重传机制可以用一个简单的应答帧从机收到有效帧后回一个 ACK主机收到 ACK 才发下一帧超时没收到就重发。验证这套机制是否可靠我通常用两个指标连续发送 10000 帧统计丢帧率和误码率。丢帧率应该为 0误码率在 CRC 校验下也应该为 0。如果还有误码说明硬件接线或时钟余量有问题先降波特率再查线。另外可以在主机端加一个计数器每发一帧计数加一从机收到后把计数回传主机对比发送和回传的计数就能知道有没有丢帧。最后说一个我自己的习惯每次调 SPI 通信先不写业务代码只写一个最简单的「主机发 0xAA、从机收到后翻转 LED」的测试例程。这个例程跑通了再往上加协议和业务逻辑。这样一旦出问题能快速定位是底层配置问题还是应用层问题。SPI 这东西底层通了上层就是体力活底层不通上层怎么写都是白搭。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

2026 企业 AI 办公工具选型指南:框架、产品盘点与落地路径

2026 企业 AI 办公工具选型指南:框架、产品盘点与落地路径

企业调研AI办公工具的过程中,很容易陷入几个典型的认知误区。不少团队一开始只数功能点,看谁的聊天框能生成的内容类型多,谁的宣传页列了二三十个功能模块,就直接敲定采购方案,最后用了半个月发现团队没人愿意用&#…

2026/10/14 18:04:29 阅读更多 →
PyQt5学生信息管理系统开发实战:环境搭建、功能实现与答辩避坑指南

PyQt5学生信息管理系统开发实战:环境搭建、功能实现与答辩避坑指南

简介:PyQt5学生信息管理系统是一份面向Python初学者和毕业设计者的完整源码包,基于PyQt5实现学生信息的增删改查,整合pymysql、xlwt、MySQL及QtDesigner/pyuic工具链,适合用作桌面端GUI开发入门练手项目。压缩包共34个文件&#x…

2026/10/14 18:04:29 阅读更多 →
2026 企业 AI 办公工具选型指南:如何匹配组织场景挑选平台

2026 企业 AI 办公工具选型指南:如何匹配组织场景挑选平台

企业数字化转型进程中,AI办公工具已经从试点应用逐步走向常态化落地。不少IT负责人在选型阶段,习惯直接对比产品功能清单,或是单纯依据采购预算、市场声量做决策,上线之后才发现工具无法适配内部业务流程,企业知识库无…

2026/10/14 18:04:29 阅读更多 →

最新新闻

AI高薪风口来袭!小白程序员如何抓住机遇,收藏这份学习指南?

AI高薪风口来袭!小白程序员如何抓住机遇,收藏这份学习指南?

文章主要介绍了AI行业的薪资现状,从实习生日薪5000元到年薪上亿,AI人才市场正在经历一场白热化的争夺战。文章指出,AI正在“入侵”每一个行业,人才缺口超过500万,普通人有机会分到一杯羹。文章建议普通人通过系统学习掌…

2026/10/14 21:14:53 阅读更多 →
大厂校招薪资大揭秘:小白程序员必看,收藏这份热门岗位薪资指南!

大厂校招薪资大揭秘:小白程序员必看,收藏这份热门岗位薪资指南!

本文汇总了字节跳动、腾讯、阿里巴巴等热门大厂校招薪资情况,涵盖前后端开发、AI/Agent方向、测开等岗位。数据来源于往届案例及公开信息,详细展示了不同级别薪资待遇及福利。文章提醒求职者在选择Offer时,不仅要关注总包,还要了解…

2026/10/14 21:14:53 阅读更多 →
如何快速上手cad-viewer:10分钟在浏览器中打开你的第一份DWG/DWG图纸

如何快速上手cad-viewer:10分钟在浏览器中打开你的第一份DWG/DWG图纸

前端3D渲染图形学 【免费下载链接】cad-viewer The world’s first fully web-based DXF/DWG viewer and editor that runs entirely in the browser — no backend server required. 项目地址: https://gitcode.com/gh_mirrors/cad/cad-viewer 点击查看 免费下载 …

2026/10/14 21:14:53 阅读更多 →
大模型高薪诱惑:年薪百万?小白必看收藏!

大模型高薪诱惑:年薪百万?小白必看收藏!

一篇顶会一作AIGC方向211本硕,某短视频大厂算法岗月入4.5万,实际年薪或超百万。但需注意,高薪并非保底,且薪资构成复杂,包括签字费、期权等。大厂算法岗普通档薪资为26k-36k,该岗位薪资远超普通水平&#x…

2026/10/14 21:14:53 阅读更多 →
AI工程师两大门派:传统算法与AI大模型应用开发,收藏这份入门指南!

AI工程师两大门派:传统算法与AI大模型应用开发,收藏这份入门指南!

文章介绍了AI领域的两大门派:传统算法工程师和AI大模型应用开发工程师。前者是“基建狂魔”,通过数学和编程解决实际问题,如精准推荐、最优路线规划等;后者是“场景魔术师”,利用现成大模型解决业务落地难题&#xff0…

2026/10/14 21:14:53 阅读更多 →
阿里云短信签名模板审核避坑指南与Python调用实战

阿里云短信签名模板审核避坑指南与Python调用实战

简介:本资源是一份面向Java开发者的企业级短信集成实战示例,聚焦阿里云短信服务(Dysmsapi)的快速接入与稳定调用,适用于用户注册验证、密码重置、订单通知等典型业务场景。压缩包共92个文件,含68个C#源码文…

2026/10/14 21:13:53 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/14 16:30:29 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

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

2026/10/13 22:01:27 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

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

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

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

2026/10/14 7:53:34 阅读更多 →