news 2026/10/21 1:08:07

STM32 CAN总线实战:从差分信号到bxCAN过滤器配置

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32 CAN总线实战:从差分信号到bxCAN过滤器配置

1. 为什么CAN总线在嵌入式领域始终占有一席之地

如果你拆开过任何一辆近十年生产的汽车,或者接触过工业自动化产线上的PLC控制柜,大概率会看到两根绞在一起、末端各带一个120欧姆电阻的线——那就是CAN总线。很多刚入行的朋友第一次接触CAN,往往是因为项目里要对接某个电机驱动器、BMS电池管理模块或者车载OBD接口,翻遍手头的教程发现全是"显性电平""隐性电平""位填充"这类术语,看完更懵了。

我自己第一次用CAN是在一个多轴运动控制项目上,当时用STM32F103对接三个伺服驱动器,一开始想当然地按UART的思路去接TX/RX,结果折腾了一下午一个字节都没收到。后来才明白CAN的收发器把TX/RX转换成了差分信号,物理层逻辑和串口完全不是一回事。这篇文章就是想把当年踩过的坑、绕过的弯,连同CAN从物理层到STM32固件配置的完整链路,一次性讲清楚。

CAN全称Controller Area Network,中文叫控制器局域网,最早是上世纪八十年代由博世公司为汽车电子设计的一种多主总线协议。它的核心价值在于:多节点共享一条总线、无需主机轮询、自带优先级仲裁和错误检测。这三点决定了它在电磁环境复杂、节点数量多、实时性要求高的场景里,比RS485和UART都更合适。STM32几乎全系列都集成了bxCAN外设(Basic Extended CAN),配合一颗几块钱的收发器芯片就能跑起来,这也是它成为嵌入式工程师必修课的原因。

这篇文章适合三类人看:一是刚学完STM32基础外设、想进阶到总线通信的初学者;二是从UART/I2C转过来、被CAN的仲裁机制绕晕的开发者;三是需要在项目里快速搭起CAN通信、不想啃几百页参考手册的实战派。我会从物理层的硬件连接讲起,一路走到STM32的过滤器配置和收发代码,中间穿插示波器实测波形和常见故障排查,尽量让每个环节都能直接抄作业。

2. 差分信号、终端电阻与CAN收发器的硬件真相

2.1 为什么CAN用两根线而不是一根

CAN总线物理层用的是差分信号,CAN_H和CAN_L两根线。所谓差分,就是接收端判断的是两根线之间的电压差,而不是某根线对地的绝对电压。这样做的好处非常直接:外界电磁干扰通常会同时耦合到两根线上,形成共模干扰,而差分接收端只关心差值,共模部分被自然抵消掉了。汽车里点火线圈、电机、继电器到处都在制造噪声,单端信号早就被淹没了,差分是唯一靠谱的选择。

具体电平定义是这样的:当总线处于隐性状态(逻辑1)时,CAN_H和CAN_L都大约在2.5V,压差接近0V;当处于显性状态(逻辑0)时,CAN_H被拉到约3.5V,CAN_L被拉到约1.5V,压差约2V。注意这里有个反直觉的点:显性电平对应逻辑0,隐性对应逻辑1。这和UART里低电平起始位有点像,但CAN的仲裁机制正是建立在"显性覆盖隐性"这个物理特性上的——只要有一个节点发显性位,总线就是显性,这个特性后面讲仲裁时会反复用到。

提示:如果你用万用表量CAN_H和CAN_L对地电压,隐性时都应该是2.5V左右。如果某一根明显偏离,比如CAN_H只有1V,那基本可以判断收发器损坏或者总线短路。

2.2 终端电阻不是可选项,是必须项

CAN总线两端各需要一个120欧姆的终端电阻,这个电阻的作用是消除信号反射。信号在电缆中传播时,如果遇到阻抗不连续的地方(比如线缆末端开路),能量会反射回来,和原始信号叠加,造成波形畸变甚至误码。120欧姆这个值不是随便定的,它约等于双绞线的特性阻抗,匹配之后反射最小。

实际接线时,两个120欧姆电阻分别接在总线最远的两端,并联在CAN_H和CAN_L之间。如果你用万用表在断电状态下量CAN_H和CAN_L之间的电阻,正常应该读到约60欧姆(两个120欧姆并联)。如果读到120欧姆,说明只有一端接了电阻;如果读到无穷大,说明两端都没接;如果读到接近0,说明短路了。这个"量60欧姆"的技巧是我调试时最常用的第一步,能快速判断总线物理层是否正常。

很多新手会问:短距离通信能不能省掉终端电阻?我的经验是,哪怕只有几十厘米的线,只要通信速率上到500kbps以上,省掉电阻后示波器上就能看到明显的振铃。低速长距离场景下影响小一些,但既然电阻成本几乎为零,没有理由省。

2.3 收发器芯片怎么选、怎么接

STM32的bxCAN外设输出的是TTL电平的CAN_TX和CAN_RX信号,不能直接挂到总线上,中间必须经过收发器芯片。常见的收发器有TJA1050、SN65HVD230、MCP2551等。TJA1050是5V供电的经典款,SN65HVD230是3.3V供电,和STM32电平更匹配,我个人更推荐后者,省掉电平转换的麻烦。

接线逻辑很简单:STM32的CAN_TX接收发器的TXD,CAN_RX接收发器的RXD,收发器的CAN_H和CAN_L接到总线。这里有个容易翻车的点:CAN_TX和CAN_RX不能接反,接反了收发器根本不工作,而且不会报错,现象就是发不出去也收不到。另外收发器的供电和地一定要和STM32共地,否则差分信号没有参考,通信必然失败。

收发器型号供电电压最高速率特点
TJA10505V1Mbps经典稳定,需注意电平匹配
SN65HVD2303.3V1Mbps与STM32直连,推荐
MCP25515V1Mbps老牌芯片,货源广
TJA10425V5Mbps支持CAN FD,高速场景

选型时还要注意一点:如果项目对EMC要求高,优先选带待机模式和斜率控制的收发器,比如TJA1042,它可以通过STB引脚进入低功耗,通过SPLIT引脚稳定共模电压。这些功能在实验室里看不出差别,但到了车载或工业现场就是能不能过认证的关键。

3. 从帧结构到仲裁:CAN协议里最容易被误解的机制

3.1 一帧CAN报文到底长什么样

标准CAN数据帧(CAN 2.0A)的结构可以拆成这么几段:帧起始(SOF)、仲裁段(11位ID + RTR)、控制段(IDE + 保留位 + 4位DLC)、数据段(0到8字节)、CRC段、ACK段、帧结束。扩展帧(CAN 2.0B)把ID扩展到29位,多了一个SRR和IDE位来区分。

初学者最容易搞混的是DLC和数据长度的关系。DLC是4位,取值0到8,表示数据段字节数。理论上DLC可以填9到15,但标准规定数据段最多8字节,填超过8的值行为未定义,实际项目中千万别这么干。另外RTR位区分数据帧和远程帧,远程帧没有数据段,用来请求其他节点发送数据,但现在用得越来越少,大多数项目直接用数据帧。

我建议新手先用CAN分析仪抓一帧真实报文,对着位定义逐段看一遍,比看十页手册都管用。比如一帧标准数据帧,ID是0x123,数据是01 02 03 04,抓出来大概是这个结构:

SOF | ID(11bit) | RTR | IDE | r0 | DLC(4bit) | Data(0-8B) | CRC | ACK | EOF 0 | 0x123 | 0 | 0 | 0 | 4 | 01 02 03 04| ... | ... | ...

3.2 仲裁机制:为什么ID越小优先级越高

CAN最精妙的设计就是非破坏性仲裁。总线上多个节点同时开始发送时,它们先发SOF,然后逐位发ID。每个节点在发每一位的同时也在监听总线,如果自己发的是隐性(1)但监听到显性(0),说明有更高优先级的节点在发,这个节点立刻退出仲裁,转为接收状态,而且已经发出的位不会丢失。

因为显性覆盖隐性,所以ID的二进制值越小,显性位出现得越早,优先级越高。比如ID 0x100(二进制1 0000 0000)和ID 0x200(10 0000 0000)同时发送,第几位就能分出胜负?0x100的第9位是0(显性),0x200的第9位是1(隐性),所以0x100赢。这就是为什么紧急报文(比如刹车信号)通常分配最小的ID。

这里有个实战经验:规划ID时一定要留出优先级梯度。我见过一个项目把所有节点ID随便分配,结果某个高频发送的传感器ID特别小,把总线上其他关键报文全堵住了。正确的做法是按实时性要求分层,比如0x000到0x0FF给安全相关,0x100到0x1FF给控制指令,0x200以上给状态上报。

3.3 位定时与采样点:波特率不是随便设的

CAN没有时钟线,所有节点靠位定时同步。一个位时间被分成四个段:同步段(SYNC_SEG)、传播段(PROP_SEG)、相位缓冲段1(PHASE_SEG1)、相位缓冲段2(PHASE_SEG2)。采样点位于PHASE_SEG1结束处,通常建议设在位时间的75%到87.5%之间。

以STM32为例,波特率计算公式是:

BaudRate = Fpclk1 / (Prescaler * (1 + BS1 + BS2))

其中BS1对应PHASE_SEG1,BS2对应PHASE_SEG2。假设APB1时钟是36MHz,想跑500kbps,可以取Prescaler=4,BS1=15,BS2=2,那么:

36MHz / (4 * (1 + 15 + 2)) = 36M / 72 = 500kbps

采样点位置 = (1 + BS1) / (1 + BS1 + BS2) = 16 / 18 ≈ 88.9%,稍微偏高但可接受。如果想让采样点落在87.5%,可以调成BS1=14,BS2=3,此时(1+14)/(1+14+3)=15/18≈83.3%。实际调试时,如果通信距离长、线缆质量差,采样点适当提前一点(比如80%)容错性更好。

注意:总线上所有节点的波特率和采样点必须一致,否则会出现位错误。我遇到过两个节点一个设87.5%一个设75%,短距离能通,线一长就疯狂报错,查了半天才发现是采样点不匹配。

4. STM32 bxCAN外设的初始化与过滤器配置实战

4.1 时钟、GPIO和CAN外设的初始化顺序

STM32的bxCAN挂在APB1总线上,初始化顺序不能乱。我的习惯是:先开GPIO和CAN时钟,再配GPIO复用,然后复位CAN外设,最后配CAN参数。以STM32F103为例,CAN_TX用PA12,CAN_RX用PA11,这两个脚要配成复用推挽输出和上拉输入。

// 1. 开时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE); // 2. 配GPIO GPIO_InitTypeDef gpio; gpio.GPIO_Pin = GPIO_Pin_12; // CAN_TX gpio.GPIO_Mode = GPIO_Mode_AF_PP; gpio.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &gpio); gpio.GPIO_Pin = GPIO_Pin_11; // CAN_RX gpio.GPIO_Mode = GPIO_Mode_IPU; GPIO_Init(GPIOA, &gpio); // 3. 复位CAN CAN_DeInit(CAN1); // 4. 配CAN参数 CAN_InitTypeDef can; CAN_StructInit(&can); can.CAN_TTCM = DISABLE; can.CAN_ABOM = ENABLE; // 自动离线恢复 can.CAN_AWUM = ENABLE; // 自动唤醒 can.CAN_NART = DISABLE; // 自动重传 can.CAN_RFLM = DISABLE; // 接收FIFO不锁定 can.CAN_TXFP = DISABLE; // 发送优先级由ID决定 can.CAN_Mode = CAN_Mode_Normal; can.CAN_SJW = CAN_SJW_1tq; can.CAN_BS1 = CAN_BS1_15tq; can.CAN_BS2 = CAN_BS2_2tq; can.CAN_Prescaler = 4; CAN_Init(CAN1, &can);

这里几个参数值得展开说。**ABOM(Auto Bus-Off Management)**建议打开,当CAN进入总线关闭状态(错误计数超过255)时能自动恢复,否则需要手动干预。**NART(No Automatic Retransmission)**建议关闭,也就是允许自动重传,这样丢帧概率低,但在总线负载极高时可能加剧拥堵,需要根据场景权衡。TXFP决定发送优先级是按ID还是按请求顺序,如果项目里有紧急报文,建议关闭TXFP让ID小的先发。

4.2 过滤器:CAN最绕但最重要的部分

STM32的CAN过滤器是新手最大的拦路虎。它一共有14个过滤器组(互联型有28个),每个组可以配成掩码模式或列表模式,可以关联到FIFO0或FIFO1。过滤器的本质是:对收到的报文ID做匹配,匹配通过才存入FIFO,否则直接丢弃。

掩码模式的逻辑是:(收到的ID & 掩码) == (过滤ID & 掩码)。比如你想接收ID范围0x100到0x1FF的所有标准帧,可以设过滤ID=0x100,掩码=0x700,这样只要高3位是001的ID都能通过。列表模式则是精确匹配,过滤ID里列出的值必须完全相等才通过。

CAN_FilterInitTypeDef filter; filter.CAN_FilterNumber = 0; filter.CAN_FilterMode = CAN_FilterMode_IdMask; filter.CAN_FilterScale = CAN_FilterScale_32bit; filter.CAN_FilterIdHigh = 0x100 << 5; // 标准ID左移5位 filter.CAN_FilterIdLow = 0x0000; filter.CAN_FilterMaskIdHigh = 0x700 << 5; filter.CAN_FilterMaskIdLow = 0x0000; filter.CAN_FilterFIFOAssignment = CAN_Filter_FIFO0; filter.CAN_FilterActivation = ENABLE; CAN_FilterInit(&filter);

这里有个巨坑:标准ID在过滤器寄存器里要左移5位,因为寄存器的高11位才是ID,低5位是RTR、IDE等标志。我当年就是忘了移位,结果过滤器死活不通过,抓包能看到总线有数据但FIFO里就是没有。扩展ID的移位更复杂,要拆成高低两段,建议直接参考手册里的位域图。

另一个经验是:过滤器数量有限,要省着用。如果项目里要接收的ID很多且不连续,优先用掩码模式圈定范围,再在软件里二次过滤。实在不够用,可以把FIFO设成不锁定模式(RFLM=1),新报文覆盖旧报文,避免溢出。

4.3 发送与接收的中断处理

发送比较简单,填好报文结构体,选一个空的发送邮箱,调用CAN_Transmit即可。STM32有三个发送邮箱,如果都满了会返回CAN_TxStatus_NoMailBox,这时候要么等要么丢弃。我一般会在发送前检查邮箱状态,或者用发送中断来驱动。

CanTxMsg tx; tx.StdId = 0x123; tx.ExtId = 0; tx.IDE = CAN_Id_Standard; tx.RTR = CAN_RTR_Data; tx.DLC = 4; tx.Data[0] = 0x01; tx.Data[1] = 0x02; tx.Data[2] = 0x03; tx.Data[3] = 0x04; uint8_t mailbox = CAN_Transmit(CAN1, &tx); if (mailbox == CAN_TxStatus_NoMailBox) { // 邮箱满,处理 }

接收推荐用中断。FIFO0和FIFO1各有自己的中断向量,在中断里调用CAN_Receive把报文取出来,然后清中断标志。注意必须把FIFO里的报文全部取完再退出中断,否则残留的报文会反复触发中断。

void USB_LP_CAN1_RX0_IRQHandler(void) { CanRxMsg rx; while (CAN_MessagePending(CAN1, CAN_FIFO0) > 0) { CAN_Receive(CAN1, CAN_FIFO0, &rx); // 处理rx.StdId和rx.Data } CAN_FIFORelease(CAN1, CAN_FIFO0); }

实测下来,如果总线负载率超过70%,接收中断会非常频繁,这时候要考虑用DMA或者提高中断优先级。我做过一个项目总线负载85%,用中断接收时主循环几乎跑不动,后来改成DMA搬运才缓解。

5. 调试现场:那些手册上不会写的CAN故障排查

5.1 通信完全不通时的排查链路

遇到CAN不通,我一般按这个顺序查:先量电阻,再看波形,最后查配置。量电阻就是前面说的,断电测CAN_H和CAN_L之间是不是60欧姆。如果不对,先解决终端电阻问题。电阻正常的话,上电用示波器看CAN_H和CAN_L,正常应该有明显的差分波形,隐性时两条线都在2.5V附近。

如果波形正常但通信不通,大概率是配置问题。重点查三处:波特率是否一致、过滤器是否放行、收发器TX/RX是否接反。我遇到过最隐蔽的一次是收发器的使能引脚没拉高,芯片一直处于待机模式,波形看着有但就是不通,查了两天才发现。

还有一种情况是只有一个节点时自发自收失败。CAN需要至少两个节点才能完成ACK应答,单节点发送时因为没有其他节点拉低ACK位,会一直重传并最终进入错误状态。测试时要么接两个节点,要么把模式设成环回模式(Loopback)自测。

5.2 偶发丢帧和错误帧的定位思路

偶发丢帧比完全不通更难查,因为它时好时坏。我的经验是先看错误计数器。STM32的CAN外设有发送错误计数(TEC)和接收错误计数(REC),可以通过CAN_GetLastErrorCode和CAN_GetErrorCounters读取。如果TEC持续增长,说明发送端有问题;REC增长则是接收端。

常见原因有几个:终端电阻只接了一端,短距离能通但长距离反射严重;波特率有微小偏差,两个节点晶振精度不够,累积误差导致采样错误;线缆太长或分支太多,CAN总线的分支长度建议不超过30厘米,主干长度和波特率成反比,500kbps下主干不超过100米。

现象可能原因排查方法
完全无波形收发器未供电/使能量收发器VCC和STB引脚
波形畸变振铃终端电阻缺失量总线电阻是否为60欧姆
偶发错误帧波特率偏差/采样点不一致核对两节点位定时参数
高负载丢帧FIFO溢出改用DMA或提高中断优先级
单节点无法发送无ACK应答接第二节点或设环回模式

5.3 总线负载率与实时性的平衡

CAN总线的实时性不是无限的。一帧标准数据帧大约108到130位(取决于数据长度和位填充),按500kbps算,一帧耗时约216到260微秒。如果总线上有10个节点,每个节点每10毫秒发一帧,负载率就是10 * 250us / 10ms = 25%,还算健康。但如果某个节点每1毫秒发一帧8字节数据,光它一个就占25%,再加上其他节点很容易超过70%的警戒线。

我的经验是总线负载率控制在50%以下,留出余量应对突发。如果实在降不下来,可以考虑几个方向:提高波特率(但受线缆长度限制)、减少发送频率、合并报文(把多个信号打包进一帧8字节)、或者用CAN FD(数据段提速到2Mbps以上,但需要收发器和MCU都支持)。

提示:计算负载率时别忘了位填充。CAN协议规定连续5个相同极性位后要插入一个相反位,最坏情况下每5位插1位,所以实际位数可能比理论值多20%。粗略估算时按理论值乘1.1到1.2比较稳妥。

6. 从能通到好用:CAN应用层的几个设计习惯

6.1 ID规划与信号打包

项目一旦超过三四个节点,ID规划就必须提前做。我的习惯是用一张表格管理,列出每个节点的发送ID、接收ID、周期、数据含义。ID分配上,把安全相关的放低段(高优先级),状态上报放高段。比如0x000到0x0FF给急停、故障,0x100到0x1FF给控制指令,0x200到0x2FF给传感器数据,0x300以上给调试信息。

信号打包也有讲究。一帧8字节,如果每个信号都单独发一帧,总线很快就被占满。合理的做法是把同一节点、同一周期的信号打包进一帧。比如一个电机驱动器,可以把位置、速度、电流、温度打包成一帧,用位域或者字节偏移来解析。打包时注意字节对齐,小端还是大端要统一,否则对接时数据全是乱的。

6.2 心跳、超时与故障降级

CAN本身有错误检测和自动重传,但它不保证应用层的可靠性。一个节点死机了,它不再发心跳,其他节点怎么知道?所以应用层必须自己做心跳机制。常见做法是每个节点周期性发一帧心跳报文,其他节点收到后刷新对应的超时计数器,超过一定时间没收到就判定该节点离线,触发降级策略。

降级策略要根据场景设计。比如一个多轴系统,某个轴离线了,是让整个系统停机,还是让其他轴继续运行?这取决于安全评估。我的建议是:安全相关的节点离线必须停机,非安全节点离线可以降级运行并报警。心跳周期一般设100毫秒到1秒,超时时间设3到5倍心跳周期,避免偶发丢帧误判。

6.3 用分析仪做回归测试

项目开发阶段,我强烈建议配一个CAN分析仪,便宜的USB-CAN盒子一两百块就能买到。它的价值不只是抓包,更重要的是做回归测试。每次改完代码,用分析仪录一段总线数据,对比改动前后的报文周期、ID、数据是否符合预期。我见过太多"改了一个地方结果把另一个功能搞挂"的情况,有分析仪录制的基线数据,回归测试效率能提高好几倍。

分析仪还能帮你发现隐藏问题,比如某个节点实际发送周期是10.3毫秒而不是设计的10毫秒,或者某帧数据的某个字节偶尔跳变。这些问题在功能测试时可能看不出来,但到了现场就是隐患。养成"改代码必抓包对比"的习惯,能省掉大量后期调试时间。

7. 写在最后的一点个人体会

CAN总线这东西,看手册觉得复杂,真正上手跑通一次之后就会发现它的设计其实非常优雅。仲裁机制、错误检测、自动重传这些特性,都是为恶劣环境和实时性量身定做的。我在实际项目里最大的体会是:物理层的问题占故障的一半以上,很多看似软件配置的错误,根子都在接线、终端电阻或者收发器上。所以每次遇到通信问题,先别急着改代码,拿万用表和示波器把物理层确认一遍,往往能少走很多弯路。

另一个建议是尽早引入CAN分析仪。它就像调试串口时的串口助手,没有它你只能盲猜,有了它总线上跑的是什么一目了然。刚开始可能觉得多花了一笔钱,但省下的调试时间绝对值得。等你把STM32的bxCAN跑通、能稳定收发之后,再去研究CAN FD或者CANopen协议栈,会发现底层逻辑都是相通的,上手会快很多。

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

Simulink RC暂态仿真:从建模到参数扫描的工程实践

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

作者头像 李华
网站建设 2026/10/21 1:04:42

机器视觉成像原理入门:从针孔模型到工业相机链路

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

作者头像 李华
网站建设 2026/10/21 1:04:39

低功耗气体传感模组设计:从选型到固件的全链路省电实战

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

作者头像 李华
网站建设 2026/10/21 1:03:37

UFS 3.1协议学习路径:从第11章切入掌握UPIU与命令交互

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

作者头像 李华
网站建设 2026/10/21 1:03:32

大模型“斩杀线”失效:多维度选型评估方法与实践

1. 从一句调侃说起&#xff1a;大模型“斩杀线”到底指什么“斩杀线”这个词最早来自游戏圈&#xff0c;指的是把对手血量压到某个阈值以下&#xff0c;一套连招就能直接带走的那条临界线。放到大模型语境里&#xff0c;它被借用来形容一种判断标准——某个模型在特定任务上的表…

作者头像 李华
网站建设 2026/10/21 1:02:51

嵌入式AI辅助开发STM32:从工具链到验收的完整流程

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

作者头像 李华