STM32F407驱动AD9854:电赛扫频与寄存器配置高频考点解析

简介:本资源面向参加电子设计竞赛或学习STM32嵌入式开发的学生与工程师,提供基于STM32F407与HAL库驱动AD9854 DDS模块输出正弦波并实现扫频功能的完整工程。信号源在扫频仪、阻抗分析仪中应用广泛,相比单片机DAC或FPGA ROM方案,AD9854可输出频率更高、幅度更平坦的信号,适合需要掌握DDS原理与高频信号生成的进阶练习者。压缩包共1123个文件,约43.66MB,以592个C源文件、283个头文件、78个汇编文件及46个ICF链接配置为主,另含工程文件、库文件与说明文档,构成可直接编译的完整工程。目前已有2638人学习下载。读者可从中获取STM32F407与AD9854的硬件接口配置、HAL库驱动代码、扫频逻辑实现及工程目录组织方式,便于快速复现实验并理解DDS信号源设计思路。

1. 电赛小练习里最容易被问倒的,不是代码而是AD9854的时序

很多人以为电赛题只要把STM32F407的HAL库调通、AD9854能出波形就稳了,结果评委一句“你扫频时相位怎么处理的”直接卡壳。这场考核考的不是你背了多少API,而是你能不能把“单片机控制DDS芯片”这条链路讲清楚:从GPIO模拟时序、SPI/并口写寄存器,到频率控制字计算、扫频步进与驻留时间,每一步都可能被追问。适合已经能点灯、能跑通串口,但对“AD9854模块输出以及扫频”没有完整答题框架的备赛者。拿分点不在功能演示,而在你能不能说清每个参数为什么这么设、换一个指标该怎么改。

2. 高频考点:AD9854寄存器配置与STM32F407接口时序

2.1 并口写时序:为什么HAL库的GPIO翻转速度会翻车

AD9854常见模块多采用并行接口,STM32F407用HAL库操作GPIO时,最容易踩的坑是HAL_GPIO_WritePin的调用开销。单次写寄存器需要拉低片选、送地址、送数据、拉高写脉冲,如果每步都走HAL函数,一个写周期可能超过1微秒,导致AD9854采样不到。正确做法是直接操作BSRR寄存器,把地址和数据线打包成一次写。

// 假设数据线接在GPIOD 0-7,地址线接GPIOD 8-13 // 片选、写脉冲接GPIOD 14、15 #define AD9854_DATA_PORT GPIOD #define AD9854_ADDR_SHIFT 8 #define AD9854_CS_PIN GPIO_PIN_14 #define AD9854_WR_PIN GPIO_PIN_15 void AD9854_WriteReg(uint8_t addr, uint8_t data) { // 先把地址和数据合并到低16位,一次写入BSRR uint32_t out = (uint32_t)data | ((uint32_t)addr << AD9854_ADDR_SHIFT); // 片选拉低,写脉冲拉低 AD9854_DATA_PORT->BSRR = (uint32_t)(AD9854_CS_PIN | AD9854_WR_PIN) << 16; // 送地址和数据 AD9854_DATA_PORT->BSRR = out; // 写脉冲拉高,锁存数据 AD9854_DATA_PORT->BSRR = AD9854_WR_PIN; // 片选拉高 AD9854_DATA_PORT->BSRR = AD9854_CS_PIN; }

这段代码的关键是BSRR的低16位置1、高16位置1表示清零。参数说明:AD9854_ADDR_SHIFT取决于地址线实际接在哪些引脚,如果地址线接在PD8-PD13,移位就是8;数据线接PD0-PD7则不用移。写脉冲的宽度由GPIO翻转速度决定,STM32F407在168MHz主频下,BSRR操作约几个时钟周期,能满足AD9854的并行时序要求。如果换用HAL库,写脉冲可能宽达几百纳秒,AD9854虽然能识别,但扫频时频繁写寄存器会拖慢整体刷新率。

2.2 频率控制字计算:扫频步进与输出频率的换算

AD9854的 output frequency 公式是:

[ f_{out} = \frac{FREQ_WORD \times f_{sysclk}}{2^{48}} ]

其中f_sysclk是AD9854的系统时钟,常见模块板上是300MHz或1GHz,具体看晶振和倍频配置。很多人在扫频时直接改频率控制字,但忘了频率控制字是48位,需要分6个字节写入。下面是一个计算并写入频率字的函数:

// 写入48位频率控制字,reg_base是频率控制字寄存器的起始地址 void AD9854_SetFrequency(uint64_t freq_word) { for (int i = 0; i < 6; i++) { AD9854_WriteReg(0x04 + i, (uint8_t)(freq_word >> (8 * i))); } } // 根据目标频率计算频率控制字 uint64_t AD9854_CalcFreqWord(double target_freq, double sysclk) { // 2^48 = 281474976710656.0 return (uint64_t)((target_freq * 281474976710656.0) / sysclk); }

参数说明:sysclk必须和实际硬件一致,如果模块上晶振是50MHz、内部倍频到300MHz,就填300e6。扫频时,步进频率对应频率控制字的增量是固定的,可以预先算好增量,每次累加,避免重复做浮点乘法。注意uint64_t在STM32F407上是64位,HAL库支持,但打印时要用%llu。

2.3 扫频实现:线性扫频与步进驻留时间的配合

扫频不是简单地把频率从低到高写一遍。评委常问:“你的扫频步进是多少?每步停留多久?怎么保证相位连续?”线性扫频的要点是:频率控制字按固定增量递增,每个频点保持一段时间让后级电路稳定。如果驻留时间太短,示波器上看不出稳定波形;太长则扫频周期过大。

// 线性扫频:从start_freq到stop_freq,步进step_freq,每步延时dwell_ms void AD9854_Sweep(double start_freq, double stop_freq, double step_freq, uint32_t dwell_ms) { uint64_t start_word = AD9854_CalcFreqWord(start_freq, 300e6); uint64_t stop_word = AD9854_CalcFreqWord(stop_freq, 300e6); uint64_t step_word = AD9854_CalcFreqWord(step_freq, 300e6) - AD9854_CalcFreqWord(0, 300e6); for (uint64_t word = start_word; word <= stop_word; word += step_word) { AD9854_SetFrequency(word); HAL_Delay(dwell_ms); } }

这里step_word的计算用AD9854_CalcFreqWord(step_freq, 300e6) - AD9854_CalcFreqWord(0, 300e6),因为频率控制字是线性的,直接减0频对应的字就是增量。dwell_ms根据后级滤波器和示波器余辉决定,一般1ms到10ms。如果扫频范围宽,建议用定时器中断代替HAL_Delay,避免阻塞其他任务。

2.4 真题演练:并行接口与SPI接口的取舍

面试题:“AD9854支持SPI和并行两种接口,你在电赛里为什么选并行?如果换成SPI,代码要改哪里?”

答题思路:先对比两种接口的速度和引脚占用。并行接口一次写8位数据加6位地址,一个写周期十几个GPIO翻转,STM32F407的BSRR操作可以做到几十纳秒,适合高速扫频。SPI接口只需要3根线,但AD9854的SPI最高时钟有限,写48位频率字需要多个字节,扫频时刷新率不如并行。如果换成SPI,需要改AD9854_WriteReg为SPI发送函数,注意AD9854的SPI模式是CPOL=0、CPHA=0还是其他,查手册确认;另外SPI的片选和写脉冲由硬件自动产生,不需要手动翻转GPIO。面试官最想听的是你根据扫频速度要求做了取舍,而不是“随便选的”。

3. 高频考点:HAL库配置与AD9854初始化流程

3.1 时钟与GPIO初始化:别让默认电平干扰AD9854

STM32F407的GPIO在复位后是浮空输入,AD9854的片选和写脉冲如果悬空,可能被误触发。初始化时要先把片选和写脉冲置为高电平,再配置为推挽输出。下面是一段初始化代码:

void AD9854_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; __HAL_RCC_GPIOD_CLK_ENABLE(); // 先写BSRR把片选和写脉冲拉高,再配置输出 GPIOD->BSRR = AD9854_CS_PIN | AD9854_WR_PIN; GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3 | GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7 | GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13 | AD9854_CS_PIN | AD9854_WR_PIN; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; HAL_GPIO_Init(GPIOD, &GPIO_InitStruct); }

参数说明:GPIO_SPEED_FREQ_VERY_HIGH对应STM32F407的最高翻转速度,扫频时写寄存器频繁,速度等级低会导致波形失真。Pull设为GPIO_NOPULL,因为外部AD9854模块通常有上拉,不需要内部再拉。注意先写BSRR再HAL_GPIO_Init,否则初始化瞬间可能出现低电平毛刺。

3.2 AD9854初始化寄存器序列:顺序错了就不出波

AD9854上电后需要按顺序写多个寄存器:先复位,再配置系统时钟倍频,然后设置输出模式、频率字、相位字,最后开启输出。常见错误是跳过复位或先写频率字再配置时钟,导致频率算错。下面是一个最小初始化序列:

void AD9854_Init(void) { AD9854_GPIO_Init(); // 1. 复位 AD9854_WriteReg(0x1D, 0x00); HAL_Delay(10); // 2. 系统时钟倍频,假设外部50MHz,倍频到300MHz AD9854_WriteReg(0x1E, 0x06); // 倍频系数6 // 3. 输出模式:单频,关闭扫频模式 AD9854_WriteReg(0x00, 0x00); // 4. 设置频率字为1kHz AD9854_SetFrequency(AD9854_CalcFreqWord(1000, 300e6)); // 5. 相位字清零 for (int i = 0; i < 2; i++) { AD9854_WriteReg(0x05 + i, 0x00); } // 6. 开启输出 AD9854_WriteReg(0x1D, 0x01); }

参数说明:寄存器地址0x1D是控制寄存器,写0x00复位、写0x01开启输出;0x1E是时钟倍频寄存器,具体倍频值查AD9854手册。HAL_Delay(10)给复位留时间,不能省。如果初始化后没有波形,先检查0x1D是否写入了0x01,再检查频率字是否算错。

3.3 真题演练:如何用示波器判断AD9854是否工作

面试题:“你上电后AD9854没有输出,怎么排查?”

答题思路:分三层排查。第一层查电源和晶振,AD9854模块的供电是否正常,晶振是否起振,可以用示波器看晶振引脚。第二层查控制信号,用示波器或逻辑分析仪抓片选、写脉冲、数据线,看STM32有没有发出写操作,时序是否符合手册。第三层查寄存器配置,读回AD9854的寄存器(如果支持读)或重新写一遍初始化序列,重点确认0x1D控制寄存器和频率字。面试官想听的是你有系统的排查顺序,而不是“换一块模块试试”。

4. 易错点:扫频与输出环节的五个失分陷阱

4.1 频率控制字溢出:48位不是64位

错在哪:用uint32_t存频率控制字,或者计算时先乘后除导致溢出。为什么错:AD9854的频率控制字是48位,uint32_t只有32位,高位被截断,输出频率完全不对。怎么答对:用uint64_t,并且先做除法再做乘法,或者用浮点数算完再转uint64_t。如果评委问“为什么不用double”,答“double在STM32F407上是软件浮点,扫频时频繁计算会拖慢速度,所以预计算增量”。

4.2 扫频步进计算错误:把频率步进直接当频率字步进

错在哪:step_word = step_freq,没有乘以2^48 / sysclk。为什么错:频率控制字和输出频率是线性关系,但系数不是1,直接加频率值会导致步进巨大或极小。怎么答对:用AD9854_CalcFreqWord(step_freq, sysclk) - AD9854_CalcFreqWord(0, sysclk)算增量,或者预先算好系数。

4.3 写脉冲宽度不足:HAL库函数调用太慢

错在哪:用HAL_GPIO_WritePin拉低再拉高写脉冲,中间没有延时,但函数调用本身占用时间,导致脉冲宽度不稳定。为什么错:AD9854要求写脉冲最小宽度,太窄会写不进去,太宽会降低扫频速度。怎么答对:直接操作BSRR,一次写入完成地址和数据,写脉冲由BSRR的置位和复位产生,宽度只有几个时钟周期,稳定可靠。

4.4 扫频时忘记更新相位:相位不连续导致波形畸变

错在哪:只改频率字,没有管相位累加器。为什么错:AD9854内部相位累加器在频率字改变时会继续累加,如果频率跳变太大,相位不连续,输出波形会出现毛刺。怎么答对:扫频时如果要求相位连续,需要在每个频点写入频率字后,等待一段时间让相位累加器稳定,或者使用AD9854的扫频模式(内部自动扫频),而不是手动写频率字。

4.5 初始化顺序颠倒:先开输出再配置频率

错在哪:先写0x1D开启输出,再写频率字。为什么错:输出开启时频率字还是默认值或随机值,后级电路可能收到错误频率。怎么答对:严格按“复位→时钟→模式→频率→相位→输出”的顺序,最后一步才开启输出。

5. 答题技巧:电赛小练习的押题思路与现场演示习惯

电赛小练习的评委提问往往围绕“你怎么知道这样是对的”展开。我的血泪经验是:不要等评委问,主动把关键参数和验证方法讲出来。比如演示扫频时,先说“我现在设置的是从1kHz到10kHz,步进100Hz,每步停留2ms,示波器上可以看到频率线性增加”,然后指着示波器屏幕让评委看。如果评委问“为什么步进是100Hz”,答“因为后级滤波器带宽是1kHz,步进太大会跳过细节,太小则扫频周期太长”。

押题思路可以按三个方向准备。第一,接口时序类:并行和SPI的区别、GPIO速度对写周期的影响、如何用逻辑分析仪抓时序。第二,频率计算类:频率控制字公式、扫频步进换算、系统时钟倍频配置。第三,系统调试类:没有输出怎么排查、扫频波形不连续怎么调、如何用定时器优化扫频。

现场演示时,我习惯把示波器调到余辉模式,这样扫频的轨迹能留在屏幕上,评委一眼就能看到频率范围。另外,提前把关键寄存器地址和计算公式写在纸上,评委问的时候直接指,比临时翻代码更从容。代码里一定要有注释,尤其是频率控制字计算和扫频循环,评委看代码时能快速理解你的思路。

最后,从那以后我每次做电赛题都强制走一遍“参数自检”:频率控制字用计算器验算、写脉冲用示波器看宽度、扫频范围用示波器看首尾频率。希望帮到你。

本文还有配套的精品资源,点击获取

相关新闻

亚马逊新手开广告总踩坑?实时查竞价,帮你告别盲目出价

亚马逊新手开广告总踩坑?实时查竞价,帮你告别盲目出价

很多亚马逊新手上架新品之后&#xff0c;最焦虑的就是广告。选品的时候只算了拿货、头程、平台佣金&#xff0c;完全没预估广告点击成本&#xff1b;等到上架开广告&#xff0c;出价全靠猜。出价给高了&#xff0c;预算半天烧光&#xff0c;ACoS一路飙升&#xff0c;订单赚的钱…

2026/10/21 2:30:55 阅读更多 →
〖实战〗电商物流管理系统详细设计:从订单接入、分仓路由到逆向售后的全链路工程实现

〖实战〗电商物流管理系统详细设计:从订单接入、分仓路由到逆向售后的全链路工程实现

一、电商物流系统真正难的不是“发货”&#xff0c;是“在正确的时间&#xff0c;用正确的仓&#xff0c;发出正确的货”电商物流系统常被理解为“OMS 接单 WMS 拣货 TMS 配送”的流水线。这个理解遗漏了真正的决策密度所在。一笔订单进入系统后&#xff0c;要回答的问题远不…

2026/10/21 2:30:55 阅读更多 →
机器人如何把二维画面变成三维动作?ECCV 2026 3DWay 论文解读

机器人如何把二维画面变成三维动作?ECCV 2026 3DWay 论文解读

TOC &#x1f30c;你好&#xff01;这里是 晓雨的笔记本在所有感兴趣的领域扩展知识&#xff0c;感谢你的陪伴与支持~&#x1f44b; 欢迎添加好友 is_yu_ei&#xff0c;不定期掉落福利资讯写在最前面 版权声明&#xff1a;本文为原创&#xff0c;遵循 CC 4.0 BY-SA 协议。转载请…

2026/10/21 2:30:55 阅读更多 →

最新新闻

无人机航拍小目标检测数据集构建:从场景设计到智慧交通与应急救援落地

无人机航拍小目标检测数据集构建:从场景设计到智慧交通与应急救援落地

我做无人机视觉落地也有几年了&#xff0c;今年被问得最多的问题不是“模型怎么调”&#xff0c;而是“模型怎么到了现场就不灵”。很多朋友拿着公开的航拍检测模型&#xff0c;在桌面演示上mAP能到七八十个点&#xff0c;可一部署到国内的十字路口、老小区、城乡结合部&#x…

2026/10/21 3:07:07 阅读更多 →
Seay源代码审计系统实战:PHP漏洞扫描与规则扩展

Seay源代码审计系统实战:PHP漏洞扫描与规则扩展

简介&#xff1a;Seay源代码审计系统是一款基于C#开发的PHP代码安全审计工具&#xff0c;主要运行于Windows平台&#xff0c;面向网络安全初学者、CTF/AWD参赛选手及渗透测试人员&#xff0c;用于快速定位PHP项目中的安全隐患。它能够识别SQL注入、代码执行、命令执行、文件包含…

2026/10/21 3:07:07 阅读更多 →
Seay源代码审计系统实战:PHP静态代码审计与AWD快速排查指南

Seay源代码审计系统实战:PHP静态代码审计与AWD快速排查指南

简介&#xff1a;Seay源代码审计系统是一款基于C#开发的PHP代码安全审计工具&#xff0c;主要运行于Windows平台&#xff0c;面向网络安全初学者、CTF/AWD参赛选手及渗透测试人员&#xff0c;用于快速定位PHP项目中的安全隐患。它可检测SQL注入、代码执行、命令执行、文件包含、…

2026/10/21 3:07:07 阅读更多 →
程序员 AI 出图避坑:椒图 AI 与 Nano Banana Pro 实战指南

程序员 AI 出图避坑:椒图 AI 与 Nano Banana Pro 实战指南

如果你也是那种被拉去“顺带做个图”就浑身难受的程序员&#xff0c;这篇文章应该能帮你省下不少救心丸。我自己的本职是写后端&#xff0c;大部分时间对着终端和接口文档&#xff0c;但只要涉及方案评审、产品汇报、开源项目宣传&#xff0c;就逃不开一个尴尬任务&#xff1a;…

2026/10/21 3:07:07 阅读更多 →
从能生成到有质感:Seedance 2.0镜头语言全解析

从能生成到有质感:Seedance 2.0镜头语言全解析

先说一个我自己的真实感受&#xff1a;第一次用 Seedance 2.0 生成视频时&#xff0c;我很兴奋&#xff0c;因为画面真的“能动了”。但等我把五六段素材拼到一起&#xff0c;越看越不对劲——画面在动&#xff0c;情绪却没动&#xff1b;每个镜头单拎出来都不差&#xff0c;连…

2026/10/21 3:07:07 阅读更多 →
RAG效果差?问题多半出在知识库管理:清洗、分块与治理实战

RAG效果差?问题多半出在知识库管理:清洗、分块与治理实战

先说明一点&#xff1a;这个标题我深有同感。做了大半年RAG相关的东西&#xff0c;见过不少团队把精力全压在召回链路调参上&#xff0c;结果效果上不去&#xff0c;最后排查一圈&#xff0c;问题全出在知识库本身——脏数据、错分块、无版本、无评测&#xff0c;源头一乱&…

2026/10/21 3:06:07 阅读更多 →

日新闻

统一内存+量化:锐龙AI Max PRO 400如何本地跑320B大模型

统一内存+量化:锐龙AI Max PRO 400如何本地跑320B大模型

这段时间硬件圈和AI圈都在传一个事&#xff1a;AMD锐龙AI Max PRO 400系列把最高192GB统一内存搬进了PC形态的产品里&#xff0c;按宣传口径&#xff0c;本地跑320B参数的大家伙不再是纸面概念。我第一反应倒不是“内存真大”&#xff0c;而是这话背后的含金量——它把过去只能…

2026/10/21 0:00:11 阅读更多 →
AI助手深度使用体验:对话质量、长文本处理与指令遵循如何留住用户

AI助手深度使用体验:对话质量、长文本处理与指令遵循如何留住用户

1. 从“封号重开”这个行为说起&#xff1a;用户到底在追求什么“封号重开”这四个字&#xff0c;放在任何产品语境里都带着一股决绝的味道。一个账号从零开始积累使用记录、调教偏好、沉淀工作流&#xff0c;背后是大量的时间和精力成本。正常情况下&#xff0c;没有人愿意主动…

2026/10/21 0:00:11 阅读更多 →
ROS中YOLOv3工程落地:通信同步、坐标变换与性能调优

ROS中YOLOv3工程落地:通信同步、坐标变换与性能调优

1. 为什么在ROS里硬刚YOLOv3不是“炫技”&#xff0c;而是工程落地的必经之路我第一次在某高校机器人实验室看到那台搭载Jetson TX2的移动底盘原地打转、反复识别错垃圾桶时&#xff0c;就意识到&#xff1a;单纯跑通darknet_ros demo和真正让YOLOv3在ROS里“干活”&#xff0c…

2026/10/21 0:00:11 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型&#xff08;GMM&#xff09;的语音识别系统&#xff0c;很多人第一反应就是一堆数学公式&#xff0c;觉得门槛很高。但如果你真的在MATLAB里把它落地跑通&#xff0c;会发现核心其实就是三件事&#xff1a;把声音变成特征向量&#xff0c;用GMM给每个类别…

2026/10/20 10:02:13 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人&#xff0c;但落到具体操作上&#xff0c;无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程&#xff1a;对图像做反白、改调色板、彩色转灰度&#xff0c;最后把像素数据导出成txt文件。单看每一…

2026/10/20 20:43:59 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词&#xff0c;你会发现一个很有意思的现象&#xff1a;每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型&#xff0c;有人指的是运动控制系统&#xff0c;还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/20 2:50:50 阅读更多 →

月新闻

我发现了一个新思路:用 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/20 10:02:08 阅读更多 →
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/20 9:50:17 阅读更多 →
黑夜航拍船只数据集训练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/20 9:50:45 阅读更多 →