news 2026/10/5 6:00:28

CAN总线开发实战:从协议原理到CANFD与TI平台调试经验

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
CAN总线开发实战:从协议原理到CANFD与TI平台调试经验

这些年调试过的CAN节点加起来也有几十个了,从最初用单片机外挂独立CAN控制器,到后来在TI的C2000和Sitara平台上做整车级通信网络,踩过的坑连起来大概能绕测试台架一圈。最近正好在整理手头的开发笔记,干脆把CAN开发里那些最容易被忽略、又最能决定项目成败的细节重新梳理一遍。这篇笔记主要围绕CAN协议的核心机制、报文解析、CANFD演进,以及TI平台上的实际开发流程来写,适合正在做嵌入式通信、汽车电子或者工业控制的朋友参考,无论是刚入门的还是已经踩过不少坑的,应该都能找到点有用的东西。

先说明一下,这篇不是教科书式的协议翻译,而是我在实际项目里一遍遍试错后沉淀下来的经验。很多结论可能和理论书上的表述不完全一样,但都是经过逻辑推导和现场验证过的。

1. 为什么CAN总线在工业与汽车领域三十年不倒

1.1 从物理层到应用层,CAN到底解决了什么问题

CAN(Controller Area Network,控制器局域网)从1986年被Bosch提出来到现在将近四十年,依然是汽车电子、工业控制、医疗设备里最主流的现场总线之一。它能在这么长时间里保持生命力,不是因为技术多先进,而是因为它把成本、可靠性和实时性这三件事平衡得非常好。两根双绞线,多个节点直接并联挂上去,没有主从关系,任何一个节点想发数据就能发,这种设计在当年以“主从轮询”为主流的工业总线环境里,算得上是相当激进的创新。

实际项目里,CAN最大的价值可以总结成三句话:多主通信、高抗干扰、低成本。多主意味着不需要专门的总控节点来调度,电机控制器、BMS(电池管理系统)、仪表盘、充电机各自都是平等的通信方,谁有数据谁就发起传输;抗干扰靠的是差分信号和一套严格的错误处理机制;成本上,一颗带CAN外设的MCU,加上两个终端电阻和一组收发器,一套节点硬件的成本可以压得非常低。

对比一下其他总线就更有感觉了。RS485也是差分信号,但它是主从式问询,主机不在,从机之间根本没法直接通信;RS232更是点对点,距离和速率都受限。CAN用11位或者29位标识符做了“内容寻址”,总线上的节点只关心报文ID,不关心具体是谁发的,这种模型天然适合分布式控制系统。我之前做商用车电池管理系统的时候,整车控制器、电池包、热管理控制器、充电桩全部挂在同一条CAN总线上,消息ID一规划好,整个系统的数据调度就非常清晰,后续加节点也方便,只要分配新的ID段就行。

1.2 两个电平的默契:差分信号如何保证抗干扰能力

CAN物理层用的两条线分别叫CANH和CANL。总线上有两种电平状态:显性电平对应逻辑0,隐性电平对应逻辑1。总线空闲的时候是隐性,一旦有节点要发送数据,就会主动拉出显性电平。这里有一个非常重要的知识点:显性位能够覆盖隐性位。这不仅是物理特性,更是后面整个仲裁机制赖以存在的基础。

差分信号的抗干扰能力在于接收器看的是CANH和CANL两端的压差,而不是单端对地的绝对电压。工业现场电机启停的瞬间,地电位可能来回跳动几伏,单端信号早就被干扰得面目全非了,但CAN的接收器只关心两侧差值,所以较强的共模干扰都能被抑制掉。TI的TCAN1042系列收发器内部还做了总线故障保护,耐压能力能做到±58V级别,接错线或者总线对电源短路的时候不容易直接烧毁,这在现场调试时能省下不少维修费。

终端电阻是CAN网络里必须交代的关键点。CAN总线的物理两端各需要一个120Ω的匹配电阻,目的是匹配传输线阻抗、抑制信号反射。高频信号在长线上传播时,如果末端阻抗不匹配,信号会产生反射波,把正常波形弄脏,轻则误码率上升,重则直接通信失败。我实测过,总线上只有两个节点且都忘了接终端电阻时,短距离还能勉强通信,线一拉长波形上升沿就出现明显振铃,误码率高得没法用。所以硬件设计阶段,终端电阻的位置就必须规划好,不能等现场出问题了再来补。

2. 报文结构、ID设计与会话仲裁:把数据送上总线的底层逻辑

2.1 标准帧与扩展帧,数据场的边界

CAN报文分两大类:标准帧(CAN 2.0A,11位ID)和扩展帧(CAN 2.0B,29位ID)。标准帧的ID范围是0x000到0x7FF,总共2048个;扩展帧的ID范围大得多,适合节点数量多、消息类型复杂的系统。选择哪种帧格式,不是拍脑袋决定的,而是要看项目规模。如果系统里只有几十条消息,标准帧完全够用,而且标准帧的仲裁场更短,总线利用率更高。如果涉及多ECU、多网段、跨平台复用,建议直接上扩展帧,给未来留足余量。

帧结构上,数据帧包含SOF(起始帧)、仲裁场、控制场、数据场、CRC场、ACK场和EOF。数据场最多8字节,这是经典CAN被讨论最多的一个话题。为什么定8字节?当年Bosch做协议设计时,考虑到汽车控制类消息普遍较小,发动机转速、车速、油门开度这些信号加起来也就几个字节,8字节已经覆盖绝大多数场景,还能限制单帧占用的总线时间,保证控制类消息的实时性。这个设计一直沿用到现在,直到CANFD的出现才打破了8字节的上限。

控制场的DLC字段表示数据长度,合法值是0到8。这里有个非常容易掉进去的坑:如果DLC填了非法值,比如9到15,经典CAN会把这一帧当成错误帧处理,接收方直接丢弃。更隐蔽的问题是,不少人做发送时固定填充DLC为8,但有效数据只有2到3个字节,后面的字节全是随机数或者上次残留,接收方如果不做掩码处理,就会解析出莫名其妙的“跳变”数据。我的建议是:发送方严格按照实际有效字节数填DLC,接收方做解析时也要先检查DLC再取数,别盲目信任发出来的报文。

2.2 仲裁机制不是“排队”,而是“抢线”

CAN的仲裁机制是很多人最津津乐道的设计。多个节点同时发送时,协议不做“预约排队”,而是让所有发送者在总线上直接“抢”。怎么抢?靠ID逐位比较。因为显性位(0)会覆盖隐性位(1),所以当两个节点同时发报文、某一位一个发0一个发1时,发1的节点会看到总线上实际是0,就知道自己仲裁失败,立刻转为接收状态,发0的节点继续发送。整个过程不打断任何一帧的完整传输。

这个过程叫“非破坏性仲裁”。也就是说,胜出节点的发送完全不受干扰,总线效率很高,而且仲裁的优先级机制和通信机制是同一个,不需要额外的优先级判断代码。ID数值越小,优先级越高。所以项目规划时,最重要的控制消息,比如整车扭矩指令、刹车指令,应该分配较小的ID;诊断类、配置类这些对实时性要求低的消息,用较大的ID。这样即使总线繁忙,关键控制消息也能优先抢到总线。

我踩过的一个坑是在同一个网络里混用了标准帧和扩展帧。CAN 2.0B的控制器如果配置不当,标准帧和扩展帧可能被识别成同一个ID。因为标准帧的11位ID和扩展帧的基础ID部分如果相同,仲裁时会因为RTR位和SRR位的显隐性差异出现识别混淆,轻则消息串线,重则导致某个节点一直收不到正确报文。后来我给自己定了一条规矩:同一条总线上尽量只用一种帧格式。如果产品定义硬是要求混用,那ID规划时必须避开相同的基础ID段,同时在滤波配置里显式区分标准和扩展标识。

2.3 大端小端:报文解析中绕不开的字节序

这可能是初学者最容易翻车的地方。CAN协议本身规定多字节数据是“高位在前”的大端风格,比如16位速度值0x1234,先发0x12再发0x34。但实际工程里的编码方式还要看具体信号定义,特别是从J1939、UDS、OBD-II这些协议体系下来时,信号在字节内部的位序定义可以绕到怀疑人生。

TI的Datasheet和SDK例程里经常强调Motorola格式和Intel格式的区别。Motorola格式就是我们常说的大端,一个16位信号从某个字节的高位开始连续排列;Intel格式是小端,信号的低位部分在前,而且信号跨字节时排列方向是倒着来的。很多人在写解析代码时只按大端处理,拿着仿真报文测没问题,一接实车数据就乱套。

我自己的处理原则是:对于所有多字节信号,统一封装“大端无符号数解析函数”和“小端无符号数解析函数”,然后在信号配置表里给每个信号标注格式类型,解析时先查表再调函数。TI的DriverLib里提供了从报文中按起始位和长度提取信号的函数,你只需要按DBC或者Excel信号表把每个信号的起始位填对,解析逻辑不要自己造轮子,能少踩很多坑。

2.4 位填充机制与错误处理:总线为什么“自愈”能力强

CAN还有个容易被忽略但非常重要的机制:位填充。发送方在连续发送5个相同电平的位之后,会自动插入一个反相位的填充位;接收方检测到连续6个相同电平的位时,就会判定为位填充错误。这个机制的作用是保证总线有足够多的电平跳变,接收方可以持续从信号边沿提取时钟同步信息,同时也能检测出一部分通信错误。

错误处理方面,CAN节点内部维护了发送错误计数器和接收错误计数器。出现错误时计数器按规则累加,正常收发时计数器逐渐递减。当某个节点的发送错误计数超过255时,节点会进入bus-off状态,主动切断与总线的连接,避免自己一个节点把整条总线拖死。这种“犯错就下线”的设计是CAN能有高可靠性的重要原因。总线上某根线短路、某个节点故障,其他节点通常还能继续通信,这对工业现场来说太重要了。

3. 从CAN到CANFD:带宽不够时的自然演进

3.1 CANFD到底改了什么

经典CAN 1Mbps的速率和8字节的数据场,在传统动力域还能撑一撑,但到了智能驾驶、高频OTA升级、多传感器数据同步的场景,带宽就成了明显的天花板。CANFD(CAN with Flexible Data-rate)就是在这种需求背景下出现的。

CANFD带来了两个核心变化。第一,数据场最大支持64字节,相比经典CAN的8字节提升了整整8倍;第二,数据段的波特率可以跳到仲裁段的数倍甚至更高,典型应用是仲裁段500kbps、数据段2Mbps到5Mbps。这样一来,单帧能携带的数据量大了,传输速率快了,同样一批数据占用总线的时间短了,整条总线的吞吐量自然上去了。做OTA升级的时候,64字节一帧和8字节一帧的效率差异简直是天壤之别。

注意CANFD没有改变仲裁机制和帧ID的语义,它只是在速率上做了区分。同一帧报文里,仲裁段仍然用相对较低的波特率,等到BRS位之后切到高速率传输数据段。接收端必须在BRS位之后马上同步到新的位速率。如果某个节点不支持CANFD或者配置不对,它会对高速段的采样产生错位,表现为报告格式错误,甚至可能把整个网络拖进反复重发的状态。

3.2 经典CAN与CANFD混用时的兼容性细节

CANFD在设计时考虑了向下兼容,物理层还是同一套CANH/CANL差分信号,所以一个网络里可以同时存在经典CAN节点和CANFD节点。但这里有一个关键细节:CANFD发送的FD帧里,FDF位(在经典CAN里叫EDL位)是隐性电平,经典CAN节点如果不知道这个位扩展的定义,就会把帧解析成错误格式,整个通信链路就断了。

所以要在工程上做混用,要么把总线上所有节点都升级为支持CANFD,要么用支持“CANFD和Classic CAN混合收发”的网关做桥接。TI的C2000系列MCU,比如TMS320F28379D的MCAN模块,以及Sitara AM64x的MCAN外设,都原生支持CANFD。只需要在SDK里把BRS和Data速率配置好,协议栈层面的兼容性基本交给硬件处理就行。

选型时还要看收发器。CANFD数据段跑到5Mbps时,对收发器的环路时延、显性到隐性的转换时间要求比经典CAN高很多,老型号收发器在高速段很容易产生位错误。TI的TCAN1042和TCAN1044系列明确标注支持CANFD,而且带VIO电平转换引脚,可以适配3.3V和5V的MCU IO电平,这个在异构平台互联时特别实用。

3.3 五个方面对比:经典CAN与CANFD的差异速查

不管是在方案选型阶段还是写技术方案的时候,都需要把经典CAN和CANFD的差异讲清楚。我按几个关键维度整理了一张对比表,方便大家直接参考。

对比维度经典CAN(CAN 2.0)CANFD
数据场长度最大8字节最大64字节
仲裁段速率最高1Mbps通常500kbps-1Mbps
数据段速率与仲裁段相同最高5Mbps甚至更高
帧格式标准帧/扩展帧在原有格式上扩展BRS/ESI位
兼容性经典CAN节点为主FD节点可发经典帧,经典节点收不了FD帧
典型场景传统动力、车身控制智能驾驶、OTA、大数据交互

选型逻辑很简单:如果现有网络已经非常稳定,消息量不大,没必要为了“新”而上CANFD;如果是新项目,且预见到后续有大量数据交互的需求,直接上CANFD框架,硬件成本增加几乎可以忽略,但为未来省下的回炉改板麻烦是无法估量的。

4. TI平台上的CAN开发实操:从SDK到滤波器配置

4.1 TI SDK的选择:C2000系列和Sitara系列的差异

在TI生态里做CAN开发,最常碰到的两类平台是C2000系列MCU和Sitara系列MPU,它们的开发方式和适用场景差别很大。

C2000系列,包括TMS320F28379D、TMS320F280049C这些型号,是TI的老牌实时控制MCU,主打电机控制、电源控制、逆变器。它的CAN模块在较新型号上是支持CANFD的MCAN外设,同时向下兼容经典CAN。开发环境用CCS(Code Composer Studio)加C2000Ware SDK,外设驱动里有完整的CAN初始化、发送、接收、中断例程,直接参考官方例程改成自己的业务逻辑就行。C2000的优势是实时性强,适合对控制周期有苛刻要求的场合。

Sitara系列,比如AM335x、AM64x、AM243x,是偏应用处理的MPU,通常跑Linux或者RTOS。AM64x的MCAN控制器支持CANFD,Linux下可以走SocketCAN方案,这是目前非常成熟且好用的一个组合。开发时用Processor SDK RTOS/Linux,在设备树里配置好MCAN节点的时钟、引脚、中断,系统起来后注册一个can口,直接用命令行就能收发:

# 配置并启动can0,仲裁段500kbps,数据段2Mbps,使能FD ip link set can0 down ip link set can0 type can bitrate 500000 dbitrate 2000000 fd on ip link set can0 up # 用cansend发送一帧标准ID报文 cansend can0 123#DEADBEEF # 用candump监听总线数据 candump can0

注意前两行的顺序不能反,网络接口必须在down状态下才能改配置参数。如果你改完配置后直接ip link set can0 up,配置可能没生效,用ip -details link show can0检查一下实际参数更稳妥。

我的选型经验是:如果项目是纯实时控制场景,数据量不大,C2000裸机或者RTOS开发足够;如果要跑复杂的协议栈、对接上位机、做文件系统或者远程升级,优先考虑Sitara加SocketCAN,直接用ip命令、candump、cansend这些工具调起线来效率高太多。

4.2 CAN报文滤波器配置:掩码模式与列表模式的取舍

TI的SysConfig工具里,C2000和Sitara的外设配置界面都可以可视化配置CAN滤波器,不用手动去算寄存器位掩码。但理解底层原理依然重要,因为你总要面对“为什么我配置了滤波器却收不到报文”这类问题。

报文ID滤波器一般分两种模式。一种是掩码模式:设置一个Mask和一个ID,接收时执行“接收ID与Mask按位与,再与预设ID比较”,Mask为1的位必须匹配,Mask为0的位不关心。掩码模式适合接收一组连续ID,比如想收0x100到0x10F,掩码设成0x1F0就可以实现。另一种是列表模式:把希望接收的ID逐一列入,只有命中的才接收,适合精确接收几个固定ID。

实际踩过的坑是掩码位设得太宽了,导致总线上无关消息全部进到接收FIFO里,驱动层中断负载飙升,甚至把低优先级任务饿死。反过来,掩码设太严,扩展帧和标准帧的ID位没对齐,导致有用的消息被滤掉了,总线上一台设备的数据其他节点都收不到。我的排查方法是:先用“全通过”模式把所有ID全部放行,用candump确认总线上实际出现了哪些ID,再按需收紧掩码或者整理列表。这个“先全收再收紧”的思路在TI的CAN例程调试里屡试不爽。

还有一个容易忽略的细节:如果你同时使能了FIFO和专用缓冲,有些MCU自动把某些报文路由到FIFO、某些路由到专用缓冲,而你在中断里只读了其中一个,另一个满了之后会产生溢出中断,表现为“总线上有报文但应用层收不到”。所以配置完滤波器之后,别急着烧程序,先读一遍中断标志寄存器,确认哪些中断确实使能了。

4.3 TI收发器选型和总线硬件设计要点

MCU的CAN控制器只是逻辑侧,真正和总线物理接触的是CAN收发器。TI在CAN收发器上的产品线覆盖很广,比较经典的是SN65HVD230和TCAN1042系列。SN65HVD230是3.3V供电、面向经典CAN的入门款,便宜好用,低速场景完全够;TCAN1042支持CANFD,有VIO电平转换,还有TXD显性超时保护、总线故障保护这些高级功能,适合对可靠性要求高的场合。

做硬件设计时,除了前面讲过的终端电阻,还要注意防护电路。工业现场总线末端通常加TVS管和共模电感,防止浪涌和电磁干扰。PCB布线时,CANH和CANL要尽量等长、靠近走线,保持差分对的对称性。参考地连接也很关键,总线收发器的地必须和节点系统地可靠连接,否则共模电压漂移一大,通信就会乱。TI自己的评估板,比如LAUNCHXL-F28379D上CAN部分的布局和防护电路可以直接抄。

TXD显性超时保护是个很多工程师不知道的功能。如果一个节点因为软件跑飞导致TXD一直拉低,它会通过持续显性电平把整条总线霸占住,其他所有节点都发不出去。TCAN1042这类现代收发器内部有TXD Dominant Timeout机制,TXD保持显性超过约定时间后会自动释放总线。这个功能默认可能不是全部型号都开启,选型或者画板子的时候要留意选带这个功能的型号,或者用MCU的看门狗配合兜底。

5. 调试现场实录:5个高频问题的排查方法

5.1 can not open com port:打不开调试口的常见原因

“can not open com port”这个报错在CAN调试工具里非常常见,不管是PCAN、CANalyzer还是国内厂家的CANTest,都见过这句话。第一次遇到别急,按顺序排查基本能定位:

第一,USB驱动是否安装。很多USB转CAN工具需要单独安装驱动,Windows系统下插上设备后如果只显示“未知设备”,大概率是驱动缺失或者驱动签名问题。第二,COM口号是否被占用。串口终端、调试器、蓝牙适配器都占用COM号,尤其是电脑上插了一堆USB设备的情况,工具选的COM号和实际设备号对不上就会报错。第三,通道参数是否设置正确。有些工具打开端口时要同时配置波特率和终端电阻开关,参数配置不合法会直接报open失败。第四,设备本身是否正常上电,有些USB-CAN设备必须外部供电才能完成枚举。

5.2 报文解析乱码:波特率之外的几个隐蔽因素

波特率不匹配是最明显的,做过CAN通信的都会先想到这个。但有一个现象容易被忽略:同一网络里所有节点配置的波特率标称值都是500k,实际通信却时好时坏。原因是不同节点的时钟源精度不同,位时间的实际误差超过了协议允许的容限。CAN协议要求位时间误差一般控制在±0.5%以内,采样点位置不同容限会略有区别,所以晶振选型和初始化配置都不能太随意。

TI的MCU在CAN外设初始化时,会按照系统时钟自动计算位时间寄存器,前提是你在初始化里给定的期望波特率和采样点位置要和总线上的其他模块保持一致。位时间采样点的推荐值:经典CAN一般放在75%到87.5%之间,CANFD数据段高速率时采样点可能要更靠后,通常推荐80%附近,具体看总线上所有成员的兼容范围。

我调过一例“单独收发都正常,多节点同时跑就偶发bus-off”的案例,最后查出来是两个供应商模块的采样点设置分别是70%和85%。单独通信时因为报文少、时序宽松,问题不明显;多节点并发时,总线负载一上来,采样点偏差导致的位错误就开始出现。所以新车型或者新设备联调时,不只要对波特率,采样点位置也要对齐。

5.3 错误帧风暴:当总线被“卡死”之后

如果总线上出现大量错误帧,用CANalyzer或者CANTest的错误帧统计页面能看到计数一直在涨。第一件事,用示波器接在CANH和CANL上看波形:显性电平应该在CANH约3.5V、CANL约1.5V,差分约2V;隐性时CANH和CANL都约2.5V,差分接近0V。如果波形是平的或者电平明显不对,大概率是收发器损坏、线束短路、终端电阻缺失或者节点供电异常。

如果波形正常,再看节点。最容易引发错误帧风暴的原因是某个节点TXD一直拉低(对应前面提过的显性超时),或者波特率不一致。把怀疑对象一个个从总线上摘掉,观察错误帧计数是否停止,这个办法原始但有效。等总线恢复正常后,再通过CAN控制器的错误计数器寄存器定位具体是哪个节点在持续报错,TCAN收发器和多数MCU的CAN外设都能读取发送错误计数器和接收错误计数器。

5.4 一根过长的分支线引发的“血案”

这里补充一个硬件细节。CAN总线规范要求“手拉手”线性拓扑,也就是从节点A到节点B再到节点C,一条总线串下去,两端各加一个终端电阻。实际工程里,有人为了方便把几个节点用短的T型分支线接到主干线上,分支一长,阻抗不连续,信号反射就会加重,高速段尤其明显。CANFD数据段跑到2Mbps以上之后,这个问题会被放大到肉眼可见的程度。经验值是分支线尽量控制在0.3米以内,如果确实避免不了,可以考虑使用CAN集线器做星型转接,让每一路分支都满足阻抗要求。

5.5 常见问题排查速查表

现象优先排查方向处理手段
调试工具打不开端口USB驱动、COM口号占用、设备上电重装驱动,关闭其他串口工具
CAN报文完全收不到波特率、ID滤波、终端电阻先用全通过模式抓包
总线偶发bus-off终端电阻、采样点、时钟精度对齐采样点,检查线缆长度
错误帧计数持续上涨TXD拉低、线序、共模电压逐节点隔离排查
CANFD高速段乱码BRS配置、收发器FD能力、分支线升级收发器,缩短分支线

收尾的几句话

整理完这篇笔记,我自己也重新过了一遍这些年做CAN开发的思路。从单纯调通一个点对点通信,到如今在TI多平台、CANFD混合网络里做可靠的多节点数据交换,最深的体会是:CAN开发的门槛说高不高,但真正到了现场,很多问题都不是协议看不懂,而是细节没做到位——一个120Ω电阻,一个DLC的填充字节,一个采样点的百分比,都可能在关键时刻给你上一课。

最后再分享一个我养成的习惯:每次联调前,先花十分钟把总线的物理层状态、波特率、采样点、终端电阻、滤波器配置这五样东西全部确认一遍,再开始抓包。这个动作看起来不起眼,但能把后面至少一小时的排查时间省下来。希望这篇笔记能帮你少走一些弯路。

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

ESP8266+红外发射管,手把手把老空调接入Home Assistant

/* 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 5:59:22

高通平台音频播放杂音定位:从DSP配置到硬件排查的完整实战指南

/* 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 5:57:32

告别串口升级:用MDK下载算法一键烧写STM32H750外部QSPI Flash

/* 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 5:57:05

ROS2+STM32双核机器人全栈实践:从物理引脚到导航地图

/* 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 5:56:22

示波器抓取MIPI D-PHY波形:HS/LP切换时序详解

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

作者头像 李华