尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
PLCopen轴状态机串联:从MC_Power到MC_MoveRelative的ST实现
做运动控制的朋友应该都经历过这样的场景。程序一行一行码完了逻辑看着没毛病MC_Power的Enable给了TRUEMC_Home的Execute也给了TRUEMC_MoveRelative的距离和速度都填好了一上电轴纹丝不动或者更邪门轴动了但不管你怎么发相对运动指令它都往一个方向跑再或者轴一使能就直接往限位方向冲吓得人手忙脚乱去按急停。这些问题的根子绝大多数不在参数配错而在三个功能块的调用顺序没有按照PLCopen的轴状态机规则来。MC_Power负责把轴的功率级打开MC_Home负责建立坐标系MC_MoveRelative负责在这个坐标系里做相对位移它们之间不是三个独立的功能块排排坐而是一条必须严格递进的因果链先使能、等确认、再回零、等完成、才能运动。把这条链用ST的状态机代码串起来就是标题里说的状态机串联。这篇文章我会把这套链条从头到尾拆开讲。先讲PLCopen轴状态机的流转逻辑再逐个拆MC_Power、MC_Home、MC_MoveRelative的接口细节和工程坑点最后给出一份可直接套用的ST代码框架附带调试实测中的时序问题和报警排查思路。适合刚接触运动控制、被轴为什么不动折磨过的工程师也适合已经在用但想搞清楚底层状态机逻辑的同行。1. 轴状态机不是文档里的装饰图它定义了你所有指令的合法顺序1.1 一条轴的完整生命周期PLCopen为单轴定义了一套标准状态机。大多数运动控制库无论是CODESYS生态、TwinCAT还是各家国产平台都遵循这个模型只是命名的细节略有出入。核心状态就这么几个Disabled轴被禁用功率级关闭任何运动指令在这个状态下基本都会被拒绝或者报轴未使能之类的错误。Standstill轴已使能处于静止待命状态坐标参考有效可以接受MC_Home、MC_MoveAbsolute、MC_MoveRelative等指令。Homing正在执行回零流程轴可能正在低速找原点这个状态下大多数非回零指令会被拒绝或导致CommandAborted。Discrete Motion正在执行离散运动相对移动、绝对移动、点动都属于这一类指令完成后自动回到Standstill。ErrorStop任何功能块报错后轴会进入错误停止状态必须执行MC_Reset等复位操作才能退出。这条链的关键在于指令不是你想发就能发。每个MC功能块都有它前置的合法状态。用一句话概括从Disabled到Standstill只能靠MC_Power拉起来从Standstill到Homing只能靠MC_Home从Standstill到Discrete Motion只能靠运动指令。1.2 三个功能块各自管哪一段我把这几个指令和状态机的对应关系列成表格调试的时候对照着看非常直观功能块合法前置状态触发后状态完成回调MC_Power(EnableTRUE)DisabledStandstillEnabledTRUEMC_Home(Execute上升沿)StandstillHomingDoneTRUE回到StandstillMC_MoveRelative(Execute上升沿)StandstillDiscrete MotionDoneTRUE回到StandstillMC_ResetErrorStop按实现回到Standstill或DisabledDoneTRUE这里有个容易忽略的点MC_Home和MC_MoveRelative的前置状态都是Standstill。也就是说你必须在MC_Power的Enabled输出变成TRUE之后才能发回零指令在回零的Done没到之前不能发任何运动指令。不然轻则指令被丢弃重则回零被中途打断CommandAborted轴停在半路你再发啥它都处于一种说不清的状态。1.3 不看状态机的典型翻车现场我见过最多的三种错法。第一种是梯形图画串并行把MC_Power、MC_Home、MC_MoveRelative三行指令全放在同一个扫描周期里执行觉得反正程序从上往下扫总会按顺序来。问题是运动控制功能块内部有自己的状态机同一周期内你让轴同时使能、回零、移动库根本分不清谁先谁后最终结果就是某条指令被忽略或者后一条把前一条中止掉。第二种是使能后不等Enabled直接发回零。MC_Power的Enable从TRUE到轴真正进入Standstill中间有功率级建立的时间可能是几十毫秒到几百毫秒。你在这个窗口期发MC_Home大多数库直接报错少数库会默默把指令吞掉之后怎么都等不到Done程序卡死在回零状态。第三种最隐蔽回零没完成就发绝对运动指令。有些平台的绝对运动指令在没有完成回零时会按机械坐标执行而此时轴的坐标还是上电后的默认值通常为0指令一去不复返轴直接往极限位置跑。这种问题一旦出现就是撞机级别的事故。2. MC_Power使能细节电平触发、状态判定与掉使能行为2.1 接口拆解MC_Power这个功能块看起来是几个BOOL管脚实际用起来比表象复杂。标准的PLCopen接口如下Axis轴引用这是功能块的第一个参数必须绑定到已经配置好的轴对象上很多人调试半天发现指令发不出去最后才意识到轴引用根本没绑对。Enable电平型输入TRUE表示持续请求使能FALSE则请求掉使能。注意它是电平信号不是上升沿你必须一直给它TRUE才能维持使能状态。EnablePositive / EnableNegative分别控制正方向和负方向使能。很多项目里这两个引脚需要单独接缺一条会导致某个方向发指令时报方向未使能。Stop停止输入TRUE时会触发轴的停止流程减速停车但它和Enable不一样Stop置TRUE再复位轴并不会掉使能只是停在那里。输出方面最重要的三个Enabled轴已完成使能即已进入Standstill可接受指令、Busy正在处理使能/掉使能请求、Error/ErrorID错误标志和错误码。2.2 使能完成怎么判定这段内容的核心经验是看Enabled不要看Enable。你把Enable置TRUE之后功能块的内部驱动、功率级建立需要时间Busy可能处于TRUE而Enabled还要晚一步。只有Enabled变为TRUE才代表轴已经进入Standstill可以安全地发下一级指令了。有个细节值得注意很多库在掉使能时Enabled从TRUE变FALSE的过程也伴随Busy。如果你在掉使能过程中发运动指令同样会被拒绝。所以状态机里等使能完成和等掉使能完成是两个对称的等待条件都要写。另外部分平台还提供读轴状态的辅助功能块比如MC_ReadAxisStatus之类的接口能把Standstill、Homing、Discrete Motion这些状态直接读出来。使能完成后建议顺手读一下轴状态确认确实是Standstill而不是停留在某些异常状态这比单纯看Enabled更保险。2.3 掉使能时飞行中的轴会发生什么这是新手容易忽略的坑。Enable从TRUE变FALSE轴如果正在运动不同库的处理策略不一样常见的有两种一种是立即切断功率轴靠机械制动或惯性滑行危险容易冲出位置另一种是触发停止流程等轴停稳后再掉使能安全。我这边的经验是生产设备上绝对不要让轴在运动过程中随意掉使能停机逻辑应该走MC_Power的Stop输入或者单独的运动停止指令等轴真正停到Standstill再断开使能。这个习惯能帮你避免一堆莫名其妙的跟踪误差和撞机事故。注意Stop信号通常也是电平型的。Stop置TRUE轴会停车并保持使能Stop复位后轴回到Standstill可以继续接受指令。这比掉使能再重新使能要快得多是单轴往复运动场景下的首选停机方式。3. MC_Home回零坐标系建立过程中的模式、时序与失败处理3.1 回零模式选择MC_Home的HomingMode决定了轴用什么方式找原点。常见的模式包括找原点开关加Z相轴先低速朝一个方向找原点开关找到后再反向低速找编码器Z相脉冲以Z相当作精确零点。这是绝大多数伺服轴的标准做法精度最高。仅找原点开关碰到开关沿就算回零完成精度取决于开关的重复性适合要求不高的场合。当前位置设零轴不动直接把当前坐标设为Position参数值。适用于已经有人为校过零位的轴或者非必须回零的场合。找限位开关轴朝限位方向运动撞到限位后反向找原点开关常用于原点开关不在行程中间、需要先跑到端头再折返的机构。选择模式的核心依据是机构上装了哪些传感器。原点开关装在行程中间就用前两种装在边上就要用带限位折返的模式。这块最好在设备机械设计时就定下来因为回零路径直接关系到调试效率。Position参数也容易被忽略。它表示回零完成后把当前位置标记为哪个坐标值。多数项目默认设为0但如果你有多段流水线每段轴的机械原点可能对应不同的工件坐标系坐标这里可以直接填一个偏移值省掉后续的坐标补偿。3.2 回零的触发与Done握手MC_Home的Execute是上升沿触发。回零指令发出后Busy变为TRUE轴开始执行回零动作回零完成后Busy变FALSEDone输出一个脉冲或者一个周期的高电平取决于库的实现。这个Done信号不会自己维持你必须在它出现的那个扫描周期里抓住它并且把Execute复位。我见过不少新手在这里犯的错Execute保持TRUE然后每个扫描周期都检查Done想当然地以为Execute一直是TRUE回零完了Done也一直是TRUE。但实际操作是回零完成后如果你不把Execute拉低功能块无法完成握手复位部分库会认为你要再次触发回零或者干脆让Done消失程序永远等不到那个信号。正确写法是状态机进入回零状态时置ExecuteTRUE检测到Done上升沿的同一周期内立刻把Execute清FALSE然后切到下一状态。3.3 回零失败怎么办回零失败太常见了。最常见的原因是原点开关信号没接对、开关坏了或者回零方向选反轴一路跑到限位都没碰到原点。这时候轴会进入ErrorStop或者一直Busy下去表现是程序卡在回零状态轴低速顶着限位不动电机嗡嗡响。工程上必须给回零加超时保护。我一般在回零启动时同时启动一个TON定时器时间设定为从当前机械位置到最远端可能原点的最大行程 ÷ 回零低速速度 × 1.5几秒到十几秒不等。超时未完成立刻终止回零流程置错误状态在HMI上报回零超时请检查原点开关。这个保护看似简单实际能省掉大量现场蹲守排查的时间。另一个细节回零中途如果误发了其他运动指令MC_Home会被CommandAborted轴停在半路坐标可能已经被写成了乱七八糟的值。所以回零状态下的互锁非常关键任何旋钮、按钮、上位机指令想打断回零都应该走先取消回零、再进入停止状态、确认坐标有效后再重新回零的流程而不是直接怼一条运动指令进来。4. MC_MoveRelative相对运动参数语义与命令仲裁规则4.1 参数逐个说清楚MC_MoveRelative在PLCopen里的关键参数有这几个Distance相对位移量单位是轴的用户单位毫米、度等。正负号表示方向。Velocity目标速度。注意有的库传的是恒速段的速度有的库传的是最大速度包含加减速过程的速度规划上界读库文档时要分清。Acceleration / Deceleration加速段和减速段的加速度单位通常是mm/s²。很多库允许填0表示使用轴配置里的默认值。Jerk加加速度即加速度变化率。老库不支持新库和高端伺服一般都有如果轴运动时有明显顿挫可以在这里做S型加减速平滑。Direction方向枚举有正向、负向、当前方向等。取当前方向时Distance的正负才有意义如果你显式指定了方向又给了符号相反的Distance部分库会直接报参数错误。还有一个参数层面的经验相对运动的速度和加速度在连续往复定位时加减速阶段占整个运动周期的比重很大。如果设备节拍不够与其盲目提高Velocity不如检查Acceleration和Jerk设置很多时候平滑加减速之后整体节拍反而更快因为冲击减小了伺服跟踪性能更好。4.2 为什么必须等Home的Done这个问题的答案是状态机强制的MC_MoveRelative的前置状态是Standstill而完成回零是轴从Homing回到Standstill的必要条件。从工程逻辑上讲也说得通没有回零轴的坐标参考是上电默认值虽然相对运动理论上只需要一个可重复的起点但你对当前位置没有定义任何跟位置相关的判断比如限位检查、软限位判断都是失真的库干脆不允许你在这种状态下做运动。所以串联顺序就是铁律MC_Power的Enabled为TRUE → 执行MC_Home → 等待Done → 这时轴才具备执行MC_MoveRelative的资格。4.3 运动中来了新指令怎么办PLCopen里的MC_MoveRelative默认是非缓冲执行也就是说如果你在轴还在运动ActiveTRUE时给MC_MoveRelative的Execute一个新的上升沿库会中止当前运动触发当前指令的CommandAborted然后立刻开始执行新指令。这在连续定位场景下是个大坑——如果你用模式开关切换目标位置逻辑上可能想让轴走完当前这段再走下一段但非缓冲模式下它会直接打断当前运动从中间位置重新规划轨迹导致加工面上出现明显的停顿接痕。正确的连续运动做法有两个方向一是等Done再发下一条代码里就是当前指令Done后Execute清零再在下一状态置ExecuteTRUE这是最简单也最可控的二是使用带缓冲区模式的功能块很多库提供带Buffered/Aborting模式参数的绝对运动指令让新指令在前一条的终点衔接轨迹连续无停顿。如果你的库不支持缓冲模式老老实实等Done不要贪快。注意一条MC_MoveRelative指令完成后轴的最终坐标等于起点坐标加上Distance而且轴回到Standstill。你可以在Done后的同一周期里读取轴的当前位置做验证这是排查轴为什么没走到位最直接的手段。5. 三段式状态机串联的ST代码从R_TRIG到CASE的完整实现5.1 为什么必须用上升沿而不是布尔赋值很多初学者写ST上来就是MC_Home.Execute : TRUE;然后就完了。问题在于MC_Home的Execute是边沿触发如果你在每个扫描周期都给它TRUE它只会在第一次边沿时触发一次之后一直保持TRUE并不会重复触发。这本身不是问题但当你需要在回零完成后再回零比如设备每次启动都回零时就麻烦了你没办法用一个脉冲去再次触发。正确做法是给Execute信号做边沿控制先把外部请求做成R_TRIG脉冲再在合适的状态里把Execute置TRUE完成后再清FALSE。R_TRIG的标准用法是定义一个R_TRIG实例把外部按钮或上位机指令作为CLK输入Q输出就是一个仅持续一个扫描周期的脉冲。这个脉冲用来触发状态机的切换而功能块的Execute信号在状态机内部管理两者互不干扰。这也是状态机串联和直接调用功能块的本质区别状态机负责管什么时候干什么功能块负责干这件事。5.2 一份可直接套用的CASE状态机代码下面这段ST代码是我在多个项目里复用过的骨架去掉了具体轴名和品牌逻辑可直接移植。引脚名称在不同平台略有差别比如有的平台叫bEnable有的叫Enable对应调整即可。VAR nState : INT : 0; nPrevState : INT : 0; xStartCmd : BOOL; // 外部启动按钮 xMoveRequest : BOOL; // 外部相对运动请求 xResetRequest : BOOL; // 外部复位请求 rMoveDistance : LREAL : 100.0; rMoveVelocity : LREAL : 50.0; triStart : R_TRIG; triMove : R_TRIG; triReset : R_TRIG; fbPower : MC_Power; fbHome : MC_Home; fbMove : MC_MoveRelative; fbReset : MC_Reset; tonHome : TON; tonMove : TON; xHomeTimeout : BOOL; xMoveTimeout : BOOL; END_VAR主逻辑放在程序段里triStart(CLK : xStartCmd); triMove(CLK : xMoveRequest); triReset(CLK : xResetRequest); // 状态切换时统一复位定时器 IF nState nPrevState THEN tonHome(IN : FALSE, PT : T#5S); tonMove(IN : FALSE, PT : T#10S); nPrevState : nState; END_IF CASE nState OF 0: // 空闲待机 fbPower.Enable : FALSE; fbHome.Execute : FALSE; fbMove.Execute : FALSE; IF triStart.Q THEN nState : 1; END_IF 1: // 请求使能并等待 fbPower.Enable : TRUE; fbPower.EnablePositive : TRUE; fbPower.EnableNegative : TRUE; IF fbPower.Enabled THEN nState : 2; ELSIF fbPower.Error THEN nState : 99; END_IF 2: // 触发回零并等待完成 fbHome.Execute : TRUE; tonHome(IN : TRUE, PT : T#5S); xHomeTimeout : tonHome.Q; IF fbHome.Done THEN fbHome.Execute : FALSE; nState : 3; ELSIF fbHome.Error OR xHomeTimeout THEN fbHome.Execute : FALSE; nState : 99; END_IF 3: // 等待外部运动请求 fbHome.Execute : FALSE; fbMove.Execute : FALSE; IF triMove.Q THEN nState : 4; END_IF 4: // 执行一次相对移动 fbMove.Execute : TRUE; fbMove.Distance : rMoveDistance; fbMove.Velocity : rMoveVelocity; fbMove.Acceleration : 100.0; fbMove.Deceleration : 100.0; tonMove(IN : TRUE, PT : T#10S); xMoveTimeout : tonMove.Q; IF fbMove.Done THEN fbMove.Execute : FALSE; nState : 3; // 回到待机可再次请求 ELSIF fbMove.Error OR xMoveTimeout THEN fbMove.Execute : FALSE; nState : 99; END_IF 99: // 错误处理等待复位 fbPower.Enable : FALSE; fbHome.Execute : FALSE; fbMove.Execute : FALSE; IF triReset.Q THEN fbReset.Execute : TRUE; IF fbReset.Done THEN fbReset.Execute : FALSE; nState : 0; // 复位完成后重新走流程 END_IF END_IF END_CASE这段代码有几个细节值得说明。状态0里把三个功能块的Enable/Execute全部清FALSE是为了保证上电后所有功能块处于一个干净的初始状态。状态1里Enable和EnablePositive/EnableNegative一起置TRUE避免方向使能缺失。状态2里回零和超时定时器同时启动目的是给回零设一个硬性时间上限。状态3是一个等待指令的空转态正因为有了这个状态MC_MoveRelative每次触发都是一个干净的上升沿不会出现连续运动时的重触发问题。5.3 超时计时器的一个小坑TON的IN输入必须是电平我这边在状态2和状态4里把IN接到TRUE同时依赖状态切换时统一复位这个逻辑来清零。如果你忘了那段状态切换时统一复位定时器的代码TON会在离开这个状态后仍然保持计时下次进状态时Q可能直接就是TRUE导致误报超时。这是我在现场踩过的坑代码注释里专门标了出来。5.4 上升沿信号在状态机里怎么过渡你可能会问triMove.Q这个脉冲只持续一个扫描周期状态3里捕捉到它切到状态4那状态4里triMove.Q早就没了fbMove.Execute靠谁来置TRUE答案是fbMove.Execute在状态4里是直接赋TRUE的它不依赖外部脉冲只依赖当前状态是4这个事实。外部脉冲的作用仅仅是触发状态切换一旦切进来功能块的执行信号完全由状态机接管这就是状态机和直接调用的另一个区别指令的执行权在状态机手里不在外部信号手里。6. 实测调试笔记报警排查、Done脉冲宽度与超时设计6.1 三个高频报警的排查链路先说轴未使能就发指令这类报警。表现形式通常是MC_Home或MC_MoveRelative的ErrorID返回一个轴未使能或不允许在当前状态执行的代码。排查链路由三句组成一是查MC_Power.Enabled是否真的为TRUE不是查Enable输入二是查轴当前是否处于Standstill而不是ErrorStop三是查你的状态机是否在上一个等待条件没满足时就提前切到了下一个状态。前两个查库第三个查你自己的程序逻辑多半问题出在第三个。再说CommandAborted。这个报警本身不是故障而是库在告诉你有别的指令插进来把当前指令打断了。排查时先分清是谁打断了谁是不是状态机在等待Done期间又给同一个轴发了其他指令是不是上位机在回零过程中点了一下启动是不是定时器误报触发了错误复位流程解法是靠状态机内部的互锁逻辑把各种指令分隔开一条轴在同一时刻只允许一个运动源持有控制权。最后说ErrorStop卡死。轴进入ErrorStop后MC_Reset是唯一的出口但很多人忽略了一个关键顺序必须先把导致错误的根因消除再执行MC_Reset。比如回零超时是因为原点开关坏了你换了开关不按复位轴还是ErrorStop你按了复位但开关还是坏的下次回零照样超时。正确的流程是消除根因修信号、改参数→ MC_Reset复位状态机 → 重新走使能-回零-运动的完整链路。特别注意ErrorStop状态下直接置MC_Power.EnableTRUE不一定能拉出这个状态必须先Reset。6.2 关于Done信号宽度的实测经验不同品牌库的Done信号宽度不一样有的只在指令完成后的第一个扫描周期拉高一个周期有的会一直保持TRUE直到Execute复位还有的在Execute保持TRUE的情况下会周期性重复拉高。我实测过几套库最舒服的是Done在Execute复位后变FALSE这种实现因为它给了你足够的周期窗口去锁存。遇到Done只有单周期脉冲的库必须在状态机的同一扫描周期里同时完成抓到Done → 清Execute → 切状态三个动作代码里就要写成状态2里看到fbHome.Done就立刻把Execute清掉再转移不能隔一个周期再清。6.3 我的几个习惯性写法第一所有运动相关功能块集中放在同一个程序组织单元里统一放在状态机里调度绝不在HMI、上位机、梯形图里分散调用。第二每个运动指令都配一个TON超时超时时置错误状态并复位相关输出宁可误报停机也不要让轴傻跑。第三在状态机的进入和退出处加调试变量nState、nPrevState在线监控状态跳转出问题看状态码比看功能块的ErrorID直观得多。这些习惯不是从文档里学的是一趟趟现场调试熬出来的。状态机串联这件事本质上不是代码技巧而是让你对轴现在处于什么状态、下一步合法动作是什么始终保持一个清晰的预期。把这份预期固化成代码结构运动控制项目的稳定性会立刻上一个台阶。
RELATED

相关推荐

NI万用表驱动从zip到稳定读数:C++环境搭建与避坑指南

NI万用表驱动从zip到稳定读数:C++环境搭建与避坑指南

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

📅 2026/10/30 6:05:00
OFDM-IM MATLAB仿真源码详解:索引调制与BER曲线实战

OFDM-IM MATLAB仿真源码详解:索引调制与BER曲线实战

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

📅 2026/10/30 6:05:00
Sonnet 5.5接入与Claude Code配置实战指南

Sonnet 5.5接入与Claude Code配置实战指南

刚看完Anthropic的新版模型发布信息,说实话第一反应不是“哇吊打谁”,而是“那旧工作流里的模型调用逻辑要不要立刻换”。很多人的第一反应都是去看它跑分怎么贴脸Opus,其实我更关心另一件事:这个战力级别的模型,落在我…

📅 2026/10/30 6:00:00
MORE NEWS

更多资讯

📰

Redis缓存击穿问题与解决方案详解

1. Redis缓存击穿问题解析缓存击穿是Redis使用过程中常见的高并发场景问题,它发生在某个热点key突然失效的瞬间。想象一下双十一秒杀场景,某个热门商品缓存刚好过期,此时成千上万的请求瞬间穿透缓存直达数据库,这种"万箭穿心…

📰

脚本PASS但底层读全零:老化测试中隐藏的自动化陷阱排查指南

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

📰

车规工规芯片认证的三阶穿透甄别法

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

📰

ESP32模组选型指南:30秒读懂N、R、H、U后缀含义

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

📰

Linux内核PM Core功耗管理:分层架构、挂起流程与调试实战

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

📰

LoRa自组网三大路线对比:洪泛、路由与网络栈如何选型

/* 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

本月热门

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

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

📞 💬