STM32中的CAN总线详解:从原理到实战

前言:为什么CAN总线是嵌入式通信的"硬通货"?

在嵌入式通信领域,CAN(Controller Area Network)总线凭借其高可靠性实时性多节点通信能力,成为汽车电子、工业控制、智能设备等领域的"标配"。想象一下:一辆汽车中有几十个ECU(电子控制单元),从发动机控制到车窗调节,都需要实时交换数据——CAN总线正是为这种多节点、高干扰环境设计的通信协议。

STM32几乎全系列都集成了高性能CAN控制器(bxCAN),支持标准帧/扩展帧、中断/DMA传输、灵活的滤波功能,完美适配工业级应用。本文将从CAN总线的基本原理讲起,深入解析STM32的CAN外设结构、配置方法、通信流程和实战案例,帮助你彻底掌握STM32 CAN通信开发。

一、CAN总线基础:为什么它能在恶劣环境中可靠通信?

在学习STM32的CAN外设前,我们需要先理解CAN总线的核心特性——这是掌握后续内容的基础。

1.1 CAN总线的核心优势

CAN总线由博世公司在1980年代为汽车电子开发,经过30多年发展,已成为国际标准(ISO 11898),其核心优势包括:

  • 多主通信:总线上的每个节点都可主动发送数据,无需中央控制器,避免单点故障;
  • 非破坏性仲裁:多个节点同时发送时,通过ID优先级仲裁,优先级高的节点继续发送,低优先级的自动退让,不会破坏数据;
  • 差分信号传输:通过CAN_H和CAN_L两根线传输差分信号,抗电磁干扰能力极强(适合工业和汽车环境);
  • 远距离传输:速率125kbps时传输距离可达500m,满足大多数工业场景;
  • 错误检测与自动重传:内置CRC校验、位填充、应答机制,确保数据可靠传输,错误帧会自动重传。

1.2 CAN总线的基本概念

(1)帧结构:CAN数据的"包装格式"

CAN总线通过"帧"传输数据,最常用的是数据帧(用于传输有效数据),其结构如下(标准帧):

字段长度(位)功能描述
起始位1帧开始标志(低电平)
仲裁11包含标准ID(11位),用于仲裁
控制场6包含数据长度(DLC,0~8字节)
数据场0~64有效数据(0~8字节,扩展帧支持更多)
CRC场16循环冗余校验,检测数据错误
应答场2接收节点确认收到正确数据
结束位7帧结束标志(高电平)

扩展帧与标准帧的区别是仲裁场包含29位ID(11位标准ID+18位扩展ID),支持更多节点和更复杂的ID规划。

(2)位时序:如何保证不同节点的同步?

CAN总线是异步通信,节点间没有统一的时钟线,通过位时序实现同步。每个位被分为4个时间段:

  • 同步段(SS):用于同步各节点时钟,长度1TQ(Time Quantum,时间量子);
  • 传播段(PS):补偿信号传输延迟,长度1~8TQ;
  • 相位缓冲段1(PBS1):可延长,用于重同步,长度1~8TQ;
  • 相位缓冲段2(PBS2):等于PBS1或信号传播时间,长度1~8TQ。

波特率计算:波特率 = 1 / (总TQ),其中总TQ = SS + PS + PBS1 + PBS2(通常总TQ=8~25)。

例如:STM32的CAN时钟为36MHz,若总TQ=18,则波特率=36MHz / 18 = 2Mbps。

1.3 CAN节点的硬件组成

一个完整的CAN节点包括:

  • MCU中的CAN控制器(如STM32的bxCAN):负责帧的组装、发送、接收和错误检测;
  • CAN收发器(如SN65HVD230):将控制器输出的TTL电平转换为CAN总线的差分信号(CAN_H/CAN_L);
  • 终端电阻(120Ω):接在总线两端,匹配阻抗,防止信号反射。

典型电路:STM32的CAN_TX(如PB6)和CAN_RX(如PB5)连接到SN65HVD230的TXD和RXD,SN65HVD230的CAN_H和CAN_L接总线,两端各接120Ω电阻。
典型电路

二、STM32的CAN外设:bxCAN控制器的强大之处

STM32的CAN控制器称为bxCAN(Basic Extended CAN),支持CAN 2.0A/B标准,不同系列(F1/F4/L4)的CAN外设功能略有差异,但核心结构一致。

2.1 bxCAN控制器的核心特性

  • 支持标准帧(11位ID)和扩展帧(29位ID)
  • 3个发送邮箱:可缓存3帧待发送数据,支持优先级发送;
  • 2个接收FIFO:每个FIFO有3级深度,可缓存3帧接收数据,减轻CPU负担;
  • 灵活的滤波功能:14个滤波器组,支持屏蔽位模式和列表模式,精准过滤目标报文;
  • 多种中断源:发送完成、接收FIFO满、错误警告等,支持中断和DMA传输;
  • 总线错误管理:检测总线错误(位错误、CRC错误等),自动进入错误状态。

2.2 bxCAN的硬件结构

理解bxCAN的结构有助于后续配置,核心模块包括:
bxCAN结构示意图

(1)发送部分:3个发送邮箱

发送邮箱(Tx Mailbox)是发送数据的"缓冲区",每个邮箱包含:

  • 标识符寄存器(TXID):存储标准/扩展ID;
  • 数据长度和数据寄存器(TXDLR、TXDATA):存储数据长度和有效数据;
  • 控制寄存器(TXCTRL):配置发送优先级、帧类型等。

发送流程:当邮箱状态为"空"时,CPU填充邮箱数据,设置"发送请求",CAN控制器会自动 arbitration(仲裁)并发送,发送完成后邮箱状态变为"空"。

(2)接收部分:2个FIFO + 14个滤波器
  • 接收FIFO:FIFO0和FIFO1,用于缓存接收的有效报文。当FIFO中的报文数达到阈值(如3帧),会触发中断;
  • 滤波器组:共14个(STM32F103),每个滤波器可配置为屏蔽位模式(按ID掩码过滤)或列表模式(精确匹配ID),过滤后的报文才会存入FIFO。
(3)波特率发生器

根据输入时钟(APB1时钟,最高36MHz for F1)和配置的位时序参数(预分频器、同步段、相位段),生成CAN总线需要的波特率。

三、STM32 CAN配置步骤:从CubeMX到代码实现

本节以STM32F103C8T6STM32CubeMX 6.6.0为例,结合HAL库,详细讲解CAN通信的配置流程。

3.1 硬件准备

  • 开发板:STM32F103C8T6最小系统板;STM32F103C8T6最小系统板

  • CAN收发器:TJA1050模块(带120Ω终端电阻,可通过跳线选择);TJA1050模块

  • 接线:STM32的PA11(CAN_RX)接TJA1050的RXD,PA12(CAN_TX)接TJA1050的TXD,TJA1050的CAN_H和CAN_L接总线(若单节点测试,可短接CAN_H和CAN_L,或接2个节点形成回路)。

3.2 CubeMX配置步骤

步骤1:新建工程,选择芯片

打开CubeMX,搜索"STM32F103C8T6",创建新工程。

步骤2:配置时钟树

CAN外设挂载在APB1总线上,需确保APB1时钟正确(最高36MHz for F1):

  1. 配置RCC:HSE选择"Crystal/Ceramic Resonator"(8MHz外部晶振);
  2. 配置PLL:PLLMUL=×9,使系统时钟=72MHz;
  3. APB1 Prescaler=×2,使APB1时钟=36MHz(满足CAN时钟需求)。
步骤3:配置CAN外设
  1. 引脚配置:在Pinout视图中,将PA11配置为"CAN_RX",PA12配置为"CAN_TX";
  2. 配置CAN模式:在Configuration→Connectivity→CAN中,设置:
    • Mode:“Normal”(正常模式,收发数据)或"Loopback"(回环模式,用于自测,发送的报文会自己接收);
    • Prescaler(预分频器):根据波特率需求设置,例如"6"(后续计算波特率);
    • 位时序参数(Bit Timing):
      • Sync Jump Width (SJW):“1tq”;
      • Time Segment 1 (BS1):“8tq”;
      • Time Segment 2 (BS2):“3tq”;
        (总TQ = Prescaler × (SJW+BS1+BS2) = 6×(1+8+3)=72 → 波特率=36MHz/72=500kbps)
步骤4:配置中断(可选)

若需要通过中断处理收发,需配置NVIC:

  1. 在Configuration→NVIC中,勾选"CAN_RX0_IRQn"(FIFO0接收中断)和"CAN_TX_IRQn"(发送完成中断);
  2. 设置中断优先级(如抢占优先级1,子优先级0)。
步骤5:生成代码

设置工程路径和IDE(如MDK-ARM),点击"Generate Code"生成初始化代码。

3.3 HAL库CAN核心函数解析

生成的代码中,CAN相关核心函数位于can.cstm32f1xx_hal_can.h,主要包括:

(1)初始化函数:MX_CAN_Init()

自动生成的初始化函数,配置CAN模式、波特率、滤波器等(后续需手动完善滤波器配置):

CAN_HandleTypeDef hcan;static void MX_CAN_Init(void)
{hcan.Instance = CAN1;hcan.Init.Prescaler = 6;hcan.Init.Mode = CAN_MODE_NORMAL; // 正常模式hcan.Init.SyncJumpWidth = CAN_SJW_1TQ;hcan.Init.TimeSeg1 = CAN_BS1_8TQ;hcan.Init.TimeSeg2 = CAN_BS2_3TQ;hcan.Init.TimeTriggeredMode = DISABLE;hcan.Init.AutoBusOff = ENABLE; // 自动退出总线关闭状态hcan.Init.AutoRetransmission = ENABLE; // 自动重传hcan.Init.ReceiveFifoLocked = DISABLE;hcan.Init.TransmitFifoPriority = DISABLE;if (HAL_CAN_Init(&hcan) != HAL_OK){Error_Handler();}
}
(2)滤波器配置函数:CAN_Filter_Config()

滤波器需要手动配置,用于过滤目标ID的报文,示例:

void CAN_Filter_Config(void)
{CAN_FilterTypeDef can_filter_st;can_filter_st.FilterActivation = ENABLE; // 使能滤波器can_filter_st.FilterMode = CAN_FILTERMODE_IDMASK; // 屏蔽位模式can_filter_st.FilterScale = CAN_FILTERSCALE_32BIT; // 32位滤波器// 配置滤波器ID和掩码(只接收ID=0x123的标准帧)can_filter_st.FilterIdHigh = 0x123 << 5; // 标准ID的高16位(左移5位是因为标准ID占11位)can_filter_st.FilterIdLow = 0x0000;can_filter_st.FilterMaskIdHigh = 0xFFF << 5; // 掩码高16位(全1表示严格匹配)can_filter_st.FilterMaskIdLow = 0x0000;can_filter_st.FilterBank = 0; // 使用第0个滤波器组can_filter_st.FilterFIFOAssignment = CAN_FILTER_FIFO0; // 匹配的报文存入FIFO0if(HAL_CAN_ConfigFilter(&hcan, &can_filter_st) != HAL_OK){Error_Handler();}
}

滤波器配置说明

  • 屏蔽位模式:FilterId是目标ID,FilterMaskId是掩码,掩码为1的位必须严格匹配,为0的位可忽略;
  • 列表模式:FilterId是需要匹配的ID列表,只有完全匹配的ID才会被接收;
  • 32位/16位模式:32位模式可同时过滤标准帧和扩展帧,16位模式适合单独过滤。
(3)发送函数:CAN_Send_Message()

封装发送流程,填充报文并启动发送:

uint8_t CAN_Send_Message(uint32_t id, uint8_t *data, uint8_t len)
{CAN_TxHeaderTypeDef tx_header;uint8_t tx_mailbox; // 存储使用的发送邮箱编号// 配置发送头部tx_header.StdId = id; // 标准IDtx_header.ExtId = 0; // 扩展ID(不使用)tx_header.RTR = CAN_RTR_DATA; // 数据帧tx_header.IDE = CAN_ID_STD; // 标准帧tx_header.DLC = len; // 数据长度(0~8)tx_header.TransmitGlobalTime = DISABLE;// 等待发送邮箱空闲if(HAL_CAN_GetTxMailboxesFreeLevel(&hcan) == 0){return 1; // 邮箱满,发送失败}// 发送数据if(HAL_CAN_AddTxMessage(&hcan, &tx_header, data, &tx_mailbox) != HAL_OK){return 2; // 发送失败}return 0; // 发送成功
}
(4)接收函数:CAN_Receive_Message()

从FIFO接收报文(查询方式):

uint8_t CAN_Receive_Message(uint32_t *id, uint8_t *data, uint8_t *len)
{CAN_RxHeaderTypeDef rx_header;// 检查FIFO0是否有数据if(HAL_CAN_GetRxMessage(&hcan, CAN_RX_FIFO0, &rx_header, data) != HAL_OK){return 1; // 接收失败}*id = rx_header.StdId; // 获取接收的ID*len = rx_header.DLC; // 获取数据长度return 0; // 接收成功
}
(5)中断服务函数

若使用中断接收,需实现中断服务函数和回调函数:

// FIFO0接收中断服务程序
void CAN1_RX0_IRQHandler(void)
{HAL_CAN_IRQHandler(&hcan);
}// 接收回调函数
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
{uint32_t id;uint8_t data[8], len;// 接收数据if(CAN_Receive_Message(&id, data, &len) == 0){// 处理接收的数据(如打印)printf("收到ID:0x%X, 数据:", id);for(uint8_t i=0; i<len; i++){printf("%02X ", data[i]);}printf("\r\n");}
}

四、实战案例:STM32 CAN节点通信测试

本节通过两个案例(回环测试和双节点通信),验证CAN配置的正确性。

4.1 回环模式测试(单节点)

回环模式下,STM32发送的CAN报文会被自己接收,无需外部节点,适合调试:

步骤1:修改CAN模式为回环

MX_CAN_Init()中,将hcan.Init.Mode改为CAN_MODE_LOOPBACK

步骤2:主函数代码
int main(void)
{HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_USART1_UART_Init(); // 初始化串口(用于打印信息)MX_CAN_Init();// 配置滤波器CAN_Filter_Config();// 启动CANif(HAL_CAN_Start(&hcan) != HAL_OK){Error_Handler();}// 使能FIFO0接收中断if(HAL_CAN_ActivateNotification(&hcan, CAN_IT_RX_FIFO0_MSG_PENDING) != HAL_OK){Error_Handler();}uint8_t send_data[8] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88};uint8_t send_cnt = 0;while (1){// 每1秒发送一次数据if(send_cnt >= 100){if(CAN_Send_Message(0x123, send_data, 8) == 0){printf("发送成功: ID=0x123, 数据:11 22 33 44 55 66 77 88\r\n");}else{printf("发送失败\r\n");}send_cnt = 0;}HAL_Delay(10);send_cnt++;}
}
测试结果

程序运行后,串口会打印"发送成功"和"收到数据"的信息,说明回环通信正常。

4.2 双节点通信(多节点)

准备两个STM32节点(Node A和Node B),配置相同波特率(500kbps),实现双向通信:

  • Node A:发送ID=0x123的报文,接收ID=0x456的报文;
  • Node B:发送ID=0x456的报文,接收ID=0x123的报文。
Node A核心代码(发送0x123,接收0x456)
// 滤波器配置(接收0x456)
can_filter_st.FilterIdHigh = 0x456 <<5;
can_filter_st.FilterMaskIdHigh = 0xFFF<<5;// 主循环发送0x123
while(1)
{CAN_Send_Message(0x123, send_data, 8);HAL_Delay(1000);
}
Node B核心代码(发送0x456,接收0x123)
// 滤波器配置(接收0x123)
can_filter_st.FilterIdHigh = 0x123 <<5;
can_filter_st.FilterMaskIdHigh = 0xFFF<<5;// 主循环发送0x456
while(1)
{CAN_Send_Message(0x456, send_data, 8);HAL_Delay(1000);
}
测试结果

两个节点通过CAN总线连接后,Node A会收到Node B发送的0x456报文,Node B会收到Node A发送的0x123报文,实现双向通信。

五、CAN高级特性:中断、DMA与错误处理

5.1 中断与DMA:提高通信效率

  • 中断方式:适合报文数量少、实时性要求高的场景,接收/发送完成后立即触发中断,CPU及时处理;
  • DMA方式:适合大量报文传输,通过DMA直接将数据搬运到内存,减少CPU干预(仅部分STM32系列支持,如F4)。

中断配置补充:除了接收FIFO中断,还可配置以下中断:

  • CAN_IT_TX_MAILBOX_EMPTY:发送邮箱空(可用于连续发送);
  • CAN_IT_ERROR:总线错误(用于错误处理);
  • CAN_IT_WAKEUP:休眠唤醒(低功耗场景)。

5.2 CAN错误处理:保证总线可靠性

CAN控制器会检测总线错误,并根据错误计数进入不同状态:

  • 主动错误状态:错误较少,可正常发送错误帧,通知其他节点;
  • 被动错误状态:错误较多,只能接收,发送前需等待总线空闲;
  • 总线关闭状态:错误严重,无法参与通信,需软件复位(HAL_CAN_ResetError)恢复。

错误处理示例

void CAN_Error_Handler(void)
{if((hcan.Instance->ESR & CAN_ESR_BOFF) != 0) // 检测到总线关闭{printf("CAN总线关闭,尝试恢复...\r\n");HAL_CAN_ResetError(&hcan); // 复位错误状态HAL_CAN_Start(&hcan); // 重新启动CAN}
}// 在主循环中定期检查
while(1)
{if(HAL_CAN_GetError(&hcan) != HAL_CAN_ERROR_NONE){CAN_Error_Handler();}// ... 其他代码 ...
}

六、常见问题与解决方案:避坑指南

6.1 通信失败:波特率不匹配

现象:发送报文后,接收方收不到,或收到乱码。

原因

  • 两个节点的波特率计算错误,导致时钟不同步;
  • 位时序参数(BS1、BS2、SJW)配置不一致。

解决方案

  1. 重新计算波特率:确保APB1时钟 / (Prescaler × (SJW+BS1+BS2))在两个节点完全一致;
  2. 推荐位时序参数:对于500kbps,可使用Prescaler=6, SJW=1, BS1=8, BS2=3(总TQ=12,36MHz/6/12=500kbps)。

6.2 接收不到报文:滤波器配置错误

现象:总线有数据,但本节点收不到。

原因

  • 滤波器未使能(FilterActivation=DISABLE);
  • 滤波器ID或掩码配置错误,目标ID被过滤;
  • FIFO溢出(报文过多未及时处理,导致新报文被丢弃)。

解决方案

  1. 检查滤波器使能状态,确保FilterActivation=ENABLE
  2. 用回环模式测试滤波器:发送目标ID,若能收到,说明滤波器配置正确;
  3. 及时处理FIFO数据,避免溢出(可增加FIFO满中断)。

6.3 总线错误:硬件或接线问题

现象:频繁进入总线错误状态,甚至总线关闭。

原因

  • CAN_H和CAN_L接反或短路;
  • 终端电阻缺失或阻值错误(应为120Ω);
  • 总线长度过长,超出对应波特率的最大距离;
  • 电源干扰(未接地或纹波过大)。

解决方案

  1. 用万用表检查CAN_H和CAN_L是否短路,接线是否正确;
  2. 确保总线两端各接一个120Ω终端电阻;
  3. 降低波特率(如从1Mbps降为500kbps),延长传输距离;
  4. 加强电源滤波,确保接地良好。

七、扩展

  • CAN FD:支持更高数据速率(8Mbps)和更长数据帧(64字节),适合大数据量传输;
  • CANopen协议:在CAN基础上的高层协议,定义了标准化的通信对象和设备模型;
  • 多节点网络管理:学习如何设计CAN网络的ID分配、优先级规划和故障诊断。

掌握STM32 CAN通信,能为工业控制、汽车电子等领域的开发打下坚实基础——这是嵌入式工程师进阶的重要技能。建议结合实际硬件多做测试,尤其是滤波器配置和错误处理,才能真正理解CAN总线的精髓。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/915010.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/915010.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【鸿蒙HarmonyOS】鸿蒙app开发入门到实战教程(二):封装自定义可复用组件

组件的可复用性&#xff0c;对我们开发的app质量影响很大&#xff0c;看看鸿蒙中如何封装这种组件 实现效果代码实现 局部封装 Builder titleBuilder(title:string 默认标题) {// Builder装饰此函数&#xff0c;使其能以链式调用的方式配置并构建Text组件Row(){Text(title).fo…

Volo-HTTP 0.4.0发布:正式支持 HTTP/2,客户端易用性大幅提升!

&#x1f916; VOLO简介 Volo 是由字节跳动服务框架团队开源的一款高性能、易用的 Rust RPC 框架。 Volo 框架自身开销极低&#xff0c;并提供了命令行工具与灵活的中间件设计&#xff0c;让开发者可以轻松上手&#xff0c;享受 Rust 带来的开发乐趣。 本文介绍自 Volo-HTTP 0…

HTTP相关知识

文章目录一、基础特性与规范二、页面元素与布局三、交互与表单四、网络通信基础流程&#xff08;以浏览器访问网页为例&#xff09;五、配套技术与工具六、知识关联图&#xff08;简化版&#xff09;一、基础特性与规范 技术定位&#xff1a;HTML 是前端技术栈的核心标记语言&…

机器人-组成结构

目录 一、发展历程 二、软件硬件算法深读耦合 感知 - 决策 - 执行 1.机械系统&#xff1a; 2.驱动系统&#xff1a; 3.感知系统&#xff1a; 4.控制系统&#xff1a; 5.决策/智能系统&#xff1a; 6.电源系统&#xff1a; 7.总结 一、发展历程 国际标准化组织(ISO)对…

pycharm结构查看器

v表示整个文件中定义的变量&#xff0c;c是类灰色部分是继承的父类的&#xff0c;明亮的是定义的&#xff0c;其中m表示定义的函数&#xff0c;f表示

AdsPower 功能详解 | 应用中心使用指南:插件统一管理更高效、更安全!

当你使用 AdsPower 管理多个浏览器环境时&#xff0c;插件的统一配置就变得尤为重要。而「应用中心」正是帮助你集中管理浏览器插件的功能入口&#xff0c;搭配浏览器环境使用&#xff0c;可以让账号操作更便捷、团队协作更高效。这篇教程将带你快速上手 AdsPower 应用中心的核…

回归预测 | MATLAB实现DBO-BP蜣螂算法优化BP神经网络多输入单输出回归预测

回归预测 | MATLAB实现DBO-BP蜣螂算法优化BP神经网络多输入单输出回归预测 目录 回归预测 | MATLAB实现DBO-BP蜣螂算法优化BP神经网络多输入单输出回归预测 预测效果 基本介绍 主要功能 逻辑关联与算法流程 1. 数据准备 2. DBO优化BP参数 3. BP神经网络构建 4. 预测与评估 运行…

Lsposed/Xposed

1.环境 1.1 xposed: 安卓8以下使用 下载并安装xposed installer&#xff0c;模拟器需勾选设置-共享磁盘。 若提示未激活&#xff0c;需要下载sdk sdk下载地址 放置目录 /sdcard/Android/data/de.robv.android.xposed.installer/cache/downloads/framework/文件权限设置 777 …

3D工业相机是什么?如何选择和使用它?

工业自动化越来越深入&#xff0c;3D成像技术&#xff08;3D工业相机&#xff09;在工业生产中越来越重要。这篇文章就来说说3D工业相机的技术原理、具体能应用在哪以及怎么选3D相机&#xff0c;给大家做个参考。一、 技术定义与核心特点3D工业相机&#xff0c;简单说就是一种特…

有哪些好用的原型设计软件?墨刀、Axure等测评对比

下面是几款常用原型设计软件的简介和对比&#xff0c;重点对墨刀和Axure进行了测评分析&#xff0c;帮助你根据需求做出选择。&#x1f4a1;常见原型设计软件一览工具名称适合人群平台支持是否协作是否支持交互墨刀&#xff08;MockingBot&#xff09;产品经理/团队协作Web、Wi…

二叉树思想草稿

二叉树解体两种思路 是否可以通过遍历一遍二叉树得到答案&#xff1f; 用一个traverse函数配合外部变量实现遍历的思维模式 是否可以定义一个递归函数&#xff0c;通过子树的答案推导出原问题的答案&#xff1f; 递归三部曲&#xff1a; 函数定义&#xff0c;参数&#xff0c;返…

如何区分Bug是前端问题还是后端问题?

在软件测试中,精准定位Bug的归属(前端 or 后端)是高效协作的关键。以下是系统化的排查方法,结合技术细节和实战技巧: 1. 核心判断逻辑 「数据 vs 展示」二分法: 后端问题:数据本身错误(API返回错误数据/逻辑错误/数据库问题) 前端问题:数据正确但展示异常(UI渲染错…

深入解析操作系统中的文件控制块(FCB):从原理到现代实现演进

一、FCB的核心概念与基础结构文件控制块&#xff08;File Control Block, FCB&#xff09;是操作系统管理文件的核心元数据结构&#xff0c;充当文件系统与物理存储间的桥梁。FCB本质上是文件在内存中的映射&#xff0c;包含文件的所有管理信息。传统FCB通常占据32-44字节空间&…

python原生处理properties文件

这个工具类使用 Python 的 configparser 模块操作 .properties 文件&#xff0c;核心是将 .properties 格式适配为 configparser 支持的 .ini 格式。 核心代码解释 1. 类初始化与配置解析 class Properties:def __init__(self, file_path: str, encoding: str utf-8):self.fil…

【java 安全】 IO流

前言 IO是指 Input/Output&#xff0c;即输入和输出。以内存为中心&#xff1a; Input指从外部读入数据到内存&#xff0c;例如把文件从磁盘读取到内存&#xff0c;从网络读取数据到内存等等。Output指把数据从内存输出到外部&#xff0c;例如把数据从内存写入到文件&#xff0…

音视频同步技术初剖析:原理、实现与FFmpeg分析

音视频同步的基本原理 音视频同步主要依靠以下几个关键点&#xff1a;时间戳机制&#xff1a; 在封装格式(如MP4)中&#xff0c;音频帧和视频帧都带有时间戳(PTS, Presentation Time Stamp)这些时间戳表示该帧应该在什么时间被呈现同步策略&#xff1a; 音频为主时钟&#xff1…

掌控网页的魔法之书:JavaScript DOM的奇幻之旅

掌控网页的魔法之书&#xff1a;JavaScript DOM的奇幻之旅 在网页开发的世界里&#xff0c;JavaScript就像一位魔法师&#xff0c;而DOM&#xff08;文档对象模型&#xff09;则是它的魔法之书。没有DOM&#xff0c;JavaScript就像失去了咒语的巫师&#xff0c;无法操控网页的元…

【C语言】深入理解柔性数组:特点、使用与优势分析

C语言学习 柔性数组 友情链接&#xff1a;C语言专栏 文章目录C语言学习前言&#xff1a;柔性数组一、柔性数组的特点二、柔性数组的使用三、柔性数组的优势总结附录上文链接专栏前言&#xff1a; 在有结构体和动态内存分配的知识后&#xff0c;今天咱们来说说柔性数组吧&…

RV126平台NFS网络启动终极复盘报告

1. 初始目标与环境目标: 将RV1126开发板的启动方式&#xff0c;由从eMMC内部存储挂载根文件系统&#xff08;rootfs&#xff09;&#xff0c;切换为通过网络挂载位于NFS服务器上的根文件系统。动机: 提升开发调试效率&#xff0c;实现代码修改后仅需重启即可验证&#xff0c;免…

一台显示器上如何快速切换两台电脑主机?

我注意到很多人会遇到一个常见的情况&#xff1a;他们有两台电脑&#xff0c;一台旧的用来处理基本的办公任务&#xff0c;另一台新的用来玩游戏。新手通常会用 DP端口连接第一台电脑的显示器&#xff0c;用 HDMI 连接第二台电脑。当他们想在两台电脑之间切换时&#xff0c;经常…