news 2026/10/5 6:07:40

Proteus中STM32 ADC采样恒为0?三个关键配置帮你快速定位

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Proteus中STM32 ADC采样恒为0?三个关键配置帮你快速定位

在Proteus 8.15里拖一个STM32F103C8,写了一段自觉没什么毛病的ADC采样代码,运行起来却发现ADC值死活是0,电位器从这头拧到那头,读数纹丝不动,连寄存器里的DR都是干干净净的0。这个场景我见过太多次了,不少人的第一反应是怀疑仿真模型坏了,或者芯片选型有问题,甚至有人直接把Proteus卸载换平台。但根据我这些年折腾仿真和实际板子的经验,十有八九不是模型的问题,而是三个看起来不起眼的配置没做对。

这篇文章就把我踩过的坑、排查的思路和能直接复现的完整工程都整理出来,包括ADC时钟、GPIO模式、软件触发这些最容易出错的地方。适合正在用Proteus做STM32仿真的学生、初学ADC采集的嵌入式爱好者,以及那些被“仿真读数为0”折磨到怀疑人生的朋友。

1. 先搞清楚仿真环境,再决定从哪下手

1.1 现象描述:ADC采出0和采出乱码是两回事

在Proteus 8.15里,STM32F103C8的ADC问题通常表现为两种:一是ADC数值恒为0,不管输入电压怎么变都是0,这种最常见;二是数值乱跳或者是一个固定不变的奇怪数字,比如4095、2047之类的。这两种问题其实指向的配置方向完全不一样。

恒为0,绝大多数时候是外设链路没打通,信号根本没有进入ADC模块,要么是时钟没给到,要么是触发信号没到,要么是引脚模式把模拟信号挡在了门外。而数值乱跳或固定高值,则更多指向采样时间太短、基准电压悬空、信号源阻抗过大这类问题。

我遇到的大多数提问都集中在“恒为0”这条上,所以这篇文章主要围绕这个现象展开。不过在章节里也会把采样时间、电源配置这些隐藏雷点一起讲清楚,因为这三者经常纠缠在一起。

1.2 别急着换芯片,先按“电源—时钟—引脚—触发—读取”的顺序排查

仿真和真实开发板不一样,真实板子出问题,你会怀疑焊接、怀疑引脚接触、怀疑芯片本身有暗伤。但Proteus里任何元件都是理想模型,芯片不会烧坏,焊接也不会虚焊。

所以我处理这类问题的固定思路是:先检查电源有没有给对,再看芯片时钟配置是否和仿真参数一致,然后检查GPIO模式是否为模拟输入,接着确认ADC触发有没有执行,最后才看读取方式。这个顺序不是随便排的,因为前面任何一环断了,后面的检查都是白做。

比如时钟没配置好,ADC模块压根不工作,你后面检查GPIO配置再正确也没用。又比如GPIO没配成模拟输入,就算软件触发正常,模拟信号也进不去。所以按链路顺序走,能省很多时间。

2. 配置一:ADC工作时钟没喂饱,采样电路直接罢工

2.1 ADC时钟从哪来,为什么频率不对会得到0

在STM32F103上,ADC外设挂在APB2总线上,也就是PCLK2。这颗芯片的ADC模块内部时钟最高只能跑到14MHz,如果超过这个频率,ADC内部的逐次逼近比较器就无法保证正确转换,结果值会变得不可靠,严重时就会输出0。

APB2总线如果按72MHz主频运行,就必须通过RCC_ADCCLKConfig设置ADC预分频,至少要6分频才能把72MHz降到12MHz。反过来,如果主频没那么高,比如只有36MHz,还照抄网上的6分频,那ADC时钟就只有6MHz,转换时间拉长但结果一般不会为0。

在Proteus仿真中,很多人直接用一个现成的工程模板,把SystemInit、GPIO、USART都粘过来,唯独漏了RCC_ADCCLKConfig这句话。此时ADC模块虽然已经被ADC_Cmd使能了,但没有时钟可用的外设是一个“幽灵设备”,寄存器状态不会被更新,读到的永远是复位值0。

2.2 在Proteus里确认主频的土办法,以及晶振属性怎么核对

Proteus的STM32模型非常看重芯片属性里设置的晶振频率。双击原理图中的芯片,打开Edit Component对话框,Crystal Frequency这一项默认可能是4MHz或8MHz,具体取决于你放置的库版本。

问题就出在这里:如果整份代码里SystemInit是按照外部8MHz晶振来计算PLL倍频系数的,但Proteus里芯片属性还停留在4MHz,那芯片实际运行的系统频率和你以为的频率就会不一致,最后算出来的ADC时钟也可能跑偏。虽然仿真器大多数时候不会像真机一样直接死机,但ADC模块对时钟敏感,表现成持续输出0并不奇怪。

所以我现在的习惯是:放置芯片后第一件事就是双击,把Crystal Frequency改成8MHz,然后在代码里用宏定义HSE_VALUE为8000000,两边对齐。如果你用的是HSI内部时钟,也要去代码里确认,别让仿真参数和工程设置打架。

2.3 时钟配置的代码到底该怎么写,检查点是什么

这是一段我在仿真项目里常用的RCC初始化代码,可以直接参考:

void RCC_Configuration(void) { SystemInit(); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1, ENABLE); RCC_ADCCLKConfig(RCC_PCLK2_Div6); }

其中RCC_PCLK2_Div6的意思是,把PCLK2作为输入,6分频后送给ADC。假设PCLK2 = 72MHz,那么ADC时钟 = 72 / 6 = 12MHz,处于STM32F103允许的14MHz上限之内。

检查的时候,建议在仿真暂停后,打开Debug菜单下的Watch Window,手动添加RCC->CFGR这个寄存器,查看ADCPRE字段,也就是位15到14。如果是00,说明没分频,这时候ADC时钟就等于PCLK2,妥妥超频了;如果是10,则代表6分频,符合预期。这一招在Proteus里看寄存器状态非常直观,比靠猜强多了。

3. 配置二:GPIO引脚没设成模拟输入,模拟信号被“拒之门外”

3.1 GPIO_Mode_AIN到底是什么,为什么少了它会读0

STM32的GPIO引脚有多种工作模式,比如浮空输入、上拉输入、下拉输入、推挽输出、复用功能输出,以及模拟输入。很多人写ADC代码时,其实知道要把引脚配成输入,但习惯性写成了GPIO_Mode_IN_FLOATING或GPIO_Mode_IPU,结果ADC读数一直不对。

模拟输入和普通数字输入的区别在于,模拟输入模式下,引脚内部与数字输入通路断开,外部模拟电压直接连接到ADC内部的采样保持电容上;而数字输入模式则会把信号先经过施密特触发器等数字电路,这会让模拟电压变得不可用,甚至被钳位到逻辑电平。

在真实芯片里,配错了GPIO模式,ADC也可能读到0或读到固定值。而在Proteus里面,模型的模拟开关特性会更严格,数字输入通路一打开,模拟信号就进不了ADC采样保持电路,最终结果就是DR寄存器输出恒0。

3.2 正确配置方式,以及在Proteus里检查引脚状态的技巧

以PA0作为ADC通道为例,初始化代码应该这样写:

GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; GPIO_Init(GPIOA, &GPIO_InitStructure);

注意,这里不要再设置GPIO_Speed,模拟输入模式不关心速度这个属性。

除了代码,Proteus的原理图也值得检查一遍。有些人习惯直接从旧工程把芯片复制过来,芯片引脚上可能残留了一些“虚拟激励”,比如蓝色或红色的那类方波信号源接在PA0上。这些激励会强行向引脚灌数字电平,ADC自然读不到电位器的电压。

检查方法很简单:原理图上选中PA0,看是否有额外的激励源连入。如果有,断开;如果不想大改,可以直接删除激励源。还有一种情况是PA0和PA1不小心在网络上短接了,造成互相干扰,也要排查。

3.3 初始化顺序的隐患:先开时钟再配置GPIO,顺序别搞反

这是仿真中比较隐蔽的一种错误。如果先执行GPIO_Init,回头才去调用RCC_APB2PeriphClockCmd使能GPIOA时钟,那GPIO_Init内部操作的是还没开启时钟的外设寄存器,配置不会生效,引脚保持复位状态。

复位状态下STM32的GPIO引脚通常是浮空输入,但ADC扫描时,模拟信号路径并未被正确激活,最终结果一样是0。所以代码里务必保证:先调用RCC时钟使能函数,再初始化GPIO,再初始化ADC。

我见过有人把GPIO初始化放在一个外设初始化函数里,把RCC配置放在另一个函数里,然后按照错误的函数调用顺序执行,代码编译毫无问题,但仿真就是不出数。这种问题在真实芯片上可能偶尔还能碰运气,但在Proteus里非常干脆,不给你任何机会。

4. 配置三:采样时间太短或软件触发没执行,结果永远是0

4.1 采样周期到底怎么算,设多大才能在仿真里不出虚数

很多初学者只知道要配置ADC_SampleTime,但并不知道这个值的含义。STM32F103的12位ADC完成一次转换,内部时间由两部分组成:采样阶段加上转换阶段。转换阶段固定需要12.5个ADC时钟周期,而采样阶段则通过寄存器配置。

标准外设库里提供了这些选项:

采样时间宏采样阶段周期数12MHz时钟下的采样时长
ADC_SampleTime_1Cycles51.50.125us
ADC_SampleTime_7Cycles57.50.625us
ADC_SampleTime_13Cycles513.51.125us
ADC_SampleTime_28Cycles528.52.375us
ADC_SampleTime_41Cycles541.53.458us
ADC_SampleTime_55Cycles555.54.625us
ADC_SampleTime_71Cycles571.55.958us
ADC_SampleTime_239Cycles5239.519.958us

在Proteus里,如果你选的是ADC_SampleTime_1Cycles5,采样时间只有0.125微秒。采样保持电容还来不及充满电,电压就被送去比较了,结果自然是0或偏差极大。这不是芯片坏了,是时间没给够。

所以我建议在仿真阶段直接选ADC_SampleTime_55Cycles5或71Cycles5,先把链路跑通,再去考虑优化采样速度。如果你接的是一个高阻抗信号源,比如10k电位器的抽头,等效源阻抗可能有几k,那就更需要足够长的采样周期。真实硬件选型时还要结合源阻抗做RC匹配计算,但仿真阶段用长采样时间是最稳的。

4.2 软件触发的三个必要环节,少一个都会读0

STM32F103的ADC规则组转换可以配置为外部触发或软件触发。如果不用定时器、PWM这类硬件触发,最简单的就是软件触发。软件触发看似简单,但其中有三步缺一不可。

第一步是调用ADC_Cmd使能ADC模块,这时候ADC从复位状态进入工作状态。第二步是调用ADC_SoftwareStartConvCmd(ADC1, ENABLE),向ADC注入一个启动转换的信号。第三步是等待转换完成,通常用while循环查询ADC_FLAG_EOC标志。

很多人只写了第一步,忘了第二步。结果是ADC模块是活的,但从未收到启动信号,转换流程根本没跑起来,DR寄存器保持复位值0,表现就是采样总为0。

我自己以前也犯过这种错误,以为ADC_Cmd就是启动转换。实际上,ADC_Cmd只是电源开关,真正按下“开始”按钮的是SoftwareStartConvCmd。

4.3 规则组和注入组的区别,以及连续转换模式的坑

STM32的ADC分成规则组和注入组。规则组就是常规的转换序列,适合大多数场景;注入组像“优先级更高的中断”,适合需要抢占同步的场景。如果代码里用的是ADC_InitStructure.ADC_Mode = ADC_Mode_Independent,默认操作的是规则组。

问题在于有些示例代码为了演示注入组功能,会额外配置ADC_InjectedChannelConfig,主循环里读的却是规则组的DR寄存器。由于注入组的数据存储在ADC_JDR里,规则组DR一直没更新,读出来永远是0。

另一种常见坑是连续转换模式。如果你把ADC_ContinuousConvMode设为ENABLE,ADC会不断转换并更新DR,这本是好事。但如果同时把ADC_ScanConvMode配成了ENABLE,而通道序列设置又不完整,Proteus中也可能出现转换完成后数据并未正确更新的情况。我的建议很直接:第一次调通时,全部用“单次转换、单通道、软件触发”的组合,等运行正常了再进阶到扫描、连续、DMA这些模式。

5. 完整复现:一个能在Proteus 8.15下跑通的ADC采样工程

5.1 原理图搭建要点:电源、晶振、电位器一个都不能少

下面直接给出一份我实测可以跑通的原理图配置清单。首先是芯片选择:从Proteus元件库中放置STM32F103C8。双击芯片,确认Crystal Frequency设置为8MHz,这是后面代码能正确计算系统时钟的基础。

然后是电源部分。Proteus默认的Power Terminal如果不修改,电压是5V,但STM32F103C8的工作电压是2.0V到3.6V,典型为3.3V。所以放置Power符号后,一定要双击,在String栏填入“+3.3V”。如果懒得改名字,也可以直接用仿真模型里的专门3.3V电源符号。这里特别提醒:5V直接进来虽然仿真不会烧芯片模型,但ADC的参考电压和相关比较器逻辑会出现诡异问题,读数偏移甚至为0。

晶振部分,在芯片的OSC_IN和OSC_OUT引脚上挂一个8MHz晶振,两端分别接20pF左右的电容到地。虽然仿真模型对晶振电路不太敏感,但维持这个习惯总没坏处。

ADC输入信号源,我习惯放一个POT-HG,也就是带手柄的电位器元件。三端分别接+3.3V、GND和PA0。PA0对应ADC1的通道0。

5.2 标准外设库完整代码,带详细注释直接抄

下面给出一份可以直接放进工程的完整代码,使用标准外设库。我这里没有用STM32CubeMX,因为很多Proteus用户的工程模板还是标准库风格,CubeMX生成HAL库代码的点点点操作反而容易在移植时出问题。

#include "stm32f10x.h" #include <stdio.h> void RCC_Configuration(void) { SystemInit(); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1, ENABLE); RCC_ADCCLKConfig(RCC_PCLK2_Div6); } void GPIO_Configuration(void) { GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; GPIO_Init(GPIOA, &GPIO_InitStructure); } void USART1_Configuration(void) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStructure); USART_InitStructure.USART_BaudRate = 115200; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_Init(USART1, &USART_InitStructure); USART_Cmd(USART1, ENABLE); } void ADC_Configuration(void) { ADC_InitTypeDef ADC_InitStructure; ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; ADC_InitStructure.ADC_ScanConvMode = DISABLE; ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfChannel = 1; ADC_Init(ADC1, &ADC_InitStructure); ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5); ADC_Cmd(ADC1, ENABLE); } uint16_t Read_ADC_Value(void) { ADC_SoftwareStartConvCmd(ADC1, ENABLE); while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET); return ADC_GetConversionValue(ADC1); } int main(void) { uint16_t adc_value = 0; RCC_Configuration(); GPIO_Configuration(); USART1_Configuration(); ADC_Configuration(); while (1) { adc_value = Read_ADC_Value(); printf("ADC value = %d, voltage = %d mV\r\n", adc_value, (adc_value * 3300) / 4095); Delay(200); } }

为了在Proteus的虚拟终端上看到输出,注意在工程里实现fputc重定向,优先把调试串口初始化和printf的底层输出对准USART1。如果嫌麻烦,也可以直接读全局变量,在仿真暂停时去看变量值。

5.3 在Proteus中接线与运行的详细步骤,连LED都能看到效果

打开Proteus后,按照前文搭建原理图。放置好POT-HG后,右下角可以看到三个引脚,通常中间是抽头。将抽头接入PA0,上端接+3.3V,下端接GND。

运行仿真后,双击POT-HG元件,会弹出属性窗口,其中的Resistance属性可以调整,还能直接用键盘方向键微调。或者实时用鼠标拖动电位器上的手柄,观察抽头输出电压的变化。

如果你想更直观看到效果,还可以在虚拟终端(Virtual Terminal)上观察串口输出。放置Virtual Terminal时,记得把RXD接到单片机的TX(PA9),TXD接到单片机的RX(PA10),否则数据发不出去或显示乱码。

我常用的一个辅助验证方式是给PA0再挂一个虚拟电压表,这样可以看到当前输入电压是多少,然后把它和串口打印出来的数值做对比。两者应该一致,误差不超过几个mV。

5.4 预期结果与精度对照,数值对了才算真的通

合理配置完成后,仿真运行,串口输出的数值应该随电位器转动手感呈现线性变化。这里列一组参考值,方便你快速判断是否正常:

输入电压理论ADC值(12位)收到的典型输出
0V00
1.65V20472046-2048
3.3V40954094-4095

如果数值接近这个表,说明三个核心配置都正确,剩下的问题就是功能优化了。如果数值虽然变化,但整体偏差较大,再进一步检查参考电压和信号源阻抗。

6. 排查实录:除了三要素,这些坑我也帮你们踩过了

6.1 坑1:Proteus默认电源5V带STM32,ADC读数诡异

这是个非常经典的坑。Proteus中的Power Terminal默认是5V,如果你直接把默认电源符号拖到STM32的VDD上,芯片能跑,程序也能跑,但ADC的参考电压会受到影响。出现的情况是,电位器电压明明在变,ADC读值也变,但最大值只有不到3000,或者整个曲线被压缩。

我的排查经历是,花了半小时质疑代码,最后看了一眼原理图上的电源网络标签,发现标的是VCC而不是+3.3V。改过来后,一切恢复正常。仿真中电源乱标不会有任何报错,它只会让结果偏离预期,这也是仿真调试中比较难发现的一类问题。

保险起见,我建议在STM32的每个电源引脚旁边都放上0.1uF的退耦电容,仿真中虽然不起实质作用,但能让你养成好习惯,以后画板子也不会忘。

6.2 坑2:晶振频率不一致,系统时钟自动跑偏

我见过一个工程,代码里SystemInit默认HSE = 8MHz,但Proteus里芯片默认4MHz,结果ADC时钟计算出来完全不在预期范围。更郁闷的是,这种问题在你更换Proteus版本,或从别人那里拷贝工程时特别容易复现。

还有一次,我把芯片属性里的Crystal Frequency改成了8MHz,却忘了代码里HSE_VALUE这个宏可能还是4MHz,两边没有对齐。虽然搭配不同的倍频系数,最终实际主频恰好落在某个值,但ADC模块的转换时序就是不对。

解决办法很直接:打开stm32f10x.h,确认HSE_VALUE宏定义。如果你所有项目都固定使用8MHz外部晶振,就把这个宏设为8000000,并且Proteus芯片属性也改成8MHz,保证两边一致。

6.3 坑3:仿真运行中看寄存器和变量,总看到旧值

Proteus的调试功能里可以打开Watch Window观察寄存器,但有个细节:如果仿真正在运行,Watch Window的数据可能没有实时刷新,你看到的DR值还是几秒前的旧数据。这会让人误以为ADC没在工作。

最好的办法是,在主循环里加一个全局变量adc_value,然后让仿真单步执行或者暂停,暂停后去Variables窗口查看这个变量的当前值。如果你用的是Debug优化模式,还要注意变量别被编译器优化掉,给变量加volatile修饰是最稳妥的。

如果你使用的是printf输出,还要注意串口数据在虚拟终端里的显示是滚动式的,如果打印频率太快,终端被刷屏,你看到的可能不是最新数据。我一般把延时控制在200ms以上,方便观察。

6.4 常见问题速查表:一眼定位问题

现象可能原因解决思路
ADC读数恒为0ADC时钟未配置或过高检查RCC_ADCCLKConfig和PCLK2分频
ADC读数恒为0GPIO模式不是AIN改成GPIO_Mode_AIN
ADC读数恒为0软件触发未执行调用ADC_SoftwareStartConvCmd
ADC读数恒为0规则组未配置,误用注入组只操作ADC1的DR和规则组通道
读数波动大采样时间太短使用55.5或71.5周期采样
读数接近4095引脚悬空或接线错误检查电位器接法,确保抽头连PA0
数据变化但范围不对电源电压默认5V超过芯片规格将Power Terminal改为+3.3V
数值呈非线性信号源阻抗过大加电压跟随器或减小信号源内阻
串口输出乱码波特率或虚拟终端接线不对检查USART配置和RXD/TXD交叉连接
寄存器不变仿真运行中未暂停暂停后查看,或使用全局变量观察

这张表我自己贴在工位上了,每次怀疑ADC有问题,先对照一遍,比翻手册还快。

我个人在实际操作中的体会是,Proteus仿真虽然和真实板子有差异,但它对“配置链完整性”的检查反而比真机更严格。真实芯片可能因为外部电路寄生参数而产生“碰巧能用”的表象,仿真模型则一板一眼,任何一环断了都会明确告诉你。所以遇到ADC采样总为0,真不是芯片的问题,而是你的配置链上某个环节松动。

最后再分享一个小技巧:在Proteus里遇到ADC读数异常,可以先不接程序,直接在原理图上用电压表测PA0的直流电压,确认信号源本身没问题。然后再在代码里加一个临时变量,把配置寄存器的关键字段读出来,通过串口打印或Watch Window确认。这种“剥洋葱”的定位方式,能让你在十分钟内定位到问题,而不是瞎猜乱改。

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

Autosar NVM状态机与NvM_WriteBlock读写时序实践指南

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

作者头像 李华
网站建设 2026/10/5 6:07:37

链式前向星与边表法详解:从数组模拟到高效存图

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

作者头像 李华
网站建设 2026/10/5 6:07:35

SAP PS项目计划成本与项目预算:从设计思路到可用性控制实操

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

作者头像 李华
网站建设 2026/10/5 6:07:18

Modscan32调试Modbus设备:常见报错与排查实战指南

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

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

STM32从上电复位到第一个任务:启动流程与RTOS切换全解析

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

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

GD32 TIMER0 PWM无波形?高级定时器主输出使能与配置全解析

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

作者头像 李华