当前位置: 首页 > news >正文

【CAN总线】STM32 的 CAN 总线通信开发笔记(基于 HAL)

目录

  1. CAN 总线简介
  2. 波特率计算与误差分析(含 9.735K 与 10K 差异)
  3. 初始化配置详解
  4. CAN 模式说明
  5. CAN 滤波器原理与配置
  6. 消息发送流程
  7. 接收机制(FIFO 与中断)
  8. 源代码与调试技巧

1. ✅ CAN 总线简介

🔸 CAN 是什么?

  • CAN(Controller Area Network) 是一种 多主机、面向消息 的通信协议,最早由德国 Bosch 公司为汽车电子开发,现广泛应用于工业自动化、电梯、医疗、机器人等领域。

🔸 CAN 报文结构:

每条 CAN 消息包含以下主要字段:

字段含义
ID报文标识符:决定优先级(ID 越小优先级越高)
DLCData Length Code:表示数据区长度(0~8 字节)
DATA有效负载:最多 8 字节的数据

🔸 差分通信原理(CAN_H / CAN_L):

CAN 使用 差分信号 进行通信,有两根线:

线名正常值主动发送“显性”时主动发送“隐性”时
CAN_H~2.5V拉高到 ~3.5V保持 2.5V
CAN_L~2.5V拉低到 ~1.5V保持 2.5V
✅ 特性:
  • 抗干扰能力强:差分信号可抵消共模干扰
  • 无需接地基准:因为只看两线间电压差
  • 远距离通信稳定:典型可达 40 米(1 Mbps)~ 1 km(50 kbps)
显性(Dominant) vs 隐性(Recessive):
状态CAN_H – CAN_L 差值作用
显性位(Dominant)~2V用于强制总线为 0(主导)
隐性位(Recessive)0V表示总线为 1(弱化)

如果两个节点同时发送:一个发“0”(显性),一个发“1”(隐性),最终总线呈现“0” → 实现自动仲裁


🔸 总线拓扑与终端电阻

  • CAN 总线为典型的 双绞线总线拓扑

  • 两端各有一个 120Ω终端电阻,用于:

    • 匹配阻抗,防止反射
    • 稳定电平,增强抗干扰
  • 终端电阻 必须存在,不能省略


2. 📐 波特率计算与误差分析

波特率计算公式:

CAN 波特率=fAPB1Prescaler×(1+BS1+BS2) \text{CAN 波特率} = \frac{f_{\text{APB1}}}{\text{Prescaler} \times (\text{1} + \text{BS1} + \text{BS2})} CAN 波特率=Prescaler×(1+BS1+BS2)fAPB1

示例:

  • APB1 = 48 MHz
  • Prescaler = 3
  • BS1 = 12TQ, BS2 = 2TQ

波特率=48MHz3×(1+12+2)=48MHz45≈1.0667Mbps \text{波特率} = \frac{48 \text{MHz}}{3 \times (1 + 12 + 2)} = \frac{48 \text{MHz}}{45} ≈ 1.0667 \text{Mbps} 波特率=3×(1+12+2)48MHz=4548MHz1.0667Mbps

可以用此公式计算 10Kbps、9.735Kbps 等配置。


📌 重要:波特率偏差与通信不对称问题

实测现象解释:
发送端接收端能否通信原因
9.735K10K接收端采样点早,容忍慢速发
10K9.735K接收端采样点晚,采不到位变
  • CAN 控制器能容忍一定的波特率误差(一般 ±1.5%~3%)
  • 误差在发送快、接收慢时更容易出错
  • 实际项目中应确保所有节点波特率一致!

3. 🧱 初始化配置详解

初始化在 MX_CAN1_Init() 中完成,关键结构体为:

hcan1.Init.Prescaler = 3;
hcan1.Init.Mode = CAN_MODE_NORMAL;
hcan1.Init.SyncJumpWidth = CAN_SJW_1TQ;
hcan1.Init.TimeSeg1 = CAN_BS1_12TQ;
hcan1.Init.TimeSeg2 = CAN_BS2_2TQ;

参数含义:

参数名说明
Prescaler分频器,影响总线波特率
Mode工作模式(NORMAL / LOOPBACK / SILENT)
SyncJumpWidth (SJW)同步跳转宽度,建议设置为 1
TimeSeg1比特前段采样点(数据采样位于 Bit 的后1/2~3/4)
TimeSeg2比特后段,用于同步

建议:保持 (1 + BS1 + BS2) 在 8~25TQ 之间,系统更稳定。


4. ⚙️ 模式(CAN_Mode)—— CAN的运行方式

模式枚举值描述是否发送是否接收是否驱动CAN总线典型用途
正常模式CAN_MODE_NORMAL标准工作模式,支持发送与接收✅ 是✅ 是✅ 是实际通信使用
回环模式CAN_MODE_LOOPBACK自发自收,控制器将发送数据直接回收到自身的接收缓冲区,不发送到总线✅ 是✅ 是❌ 否单机调试、发送逻辑测试
静默模式CAN_MODE_SILENT只监听总线上的数据,不主动发送或响应,且不会干扰总线❌ 否✅ 是❌ 否被动监听,调试总线或与未知设备共存
静默+回环模式CAN_MODE_SILENT_LOOPBACK回环 + 静默,完全断开物理总线,仅内部测试✅ 是✅ 是❌ 否回环测试,不接触外部,适合自动化测试

📝 模式使用建议:

  • 调试阶段:可使用 LOOPBACK 模式验证发送与接收逻辑,确认程序框架正确;
  • 监听设备:例如日志记录器/监控工具,可用 SILENT 模式无干扰地接入总线;
  • 真实通信:正式运行时必须使用 NORMAL 模式才能与其他节点通信;
  • 复杂测试:若需断开物理总线测试通信流程,用 SILENT_LOOPBACK 模式最安全。

❗ 注意事项:

  • LOOPBACKSILENT_LOOPBACK 模式下 不会产生物理电平变化,示波器上看不到 CAN 波形;
  • SILENT 模式下设备不可主动发送,即使调用发送函数也不会生效;
  • 切换为 NORMAL 模式前,必须确保波特率、滤波器、邮箱、FIFO 等配置无误。

5. 🧊 CAN 滤波器(Filter)

📌 目的:

CAN 总线是多主广播机制,所有节点都能看到所有消息。滤波器的作用是:

  • 筛选出感兴趣的消息(指定 ID);
  • 屏蔽无关的数据帧,减轻 MCU 中断负担和内存开销;
  • 支持多个滤波器组(F1xx 系列通常为 14 个);

🧱 配置结构体(CAN_FilterTypeDef

成员含义说明
FilterBank滤波器编号取值范围 0 ~ N(最多 28 个,取决于芯片)
FilterMode模式掩码模式(IDMASK)或列表模式(IDLIST)
FilterScale尺寸16位模式 or 32位模式
FilterIdHigh/Low滤波 ID高/低 16 位;取决于扩展/标准帧
FilterMaskIdHigh/Low滤波掩码与 FilterId 比较的屏蔽位
FilterFIFOAssignment分配到哪个 FIFOFIFO0 或 FIFO1
FilterActivation激活开关启用或禁用该滤波器
SlaveStartFilterBank用于双 CAN 的分界线F4/H7 系列特有

🧭 两种模式详解

1️⃣ 掩码模式(CAN_FILTERMODE_IDMASK

作用:按照 ID 的某些位进行匹配(常用于 ID 分段匹配)

  • 匹配规则:

    接收ID & Mask == FilterID & Mask
    
  • 掩码为 0:表示该位“忽略”;

  • 掩码为 1:表示该位“必须精确匹配”;

  • 适合批量匹配某个范围的 ID

示例:接收所有帧(“全开”滤波器)

FilterIdHigh = 0x0000;
FilterMaskIdHigh = 0x0000;  // 所有位都“忽略” → 接收所有帧

示例:只接收 ID 为 0x321 的标准帧

FilterIdHigh = 0x321 << 5;         // 标准 ID 左移 5 位
FilterMaskIdHigh = 0xFFE0;         // 只比较前 11 位

2️⃣ 列表模式(CAN_FILTERMODE_IDLIST

作用:只接收 特定 的几个 ID,不考虑掩码

  • 结构体中 ID 和 Mask 字段被看作 4 个完整 ID 的列表(如果是 16-bit 模式就是 4 个,32-bit 模式就是 2 个);
  • 适合精确接收多个确定 ID(非范围)

示例:只接收 0x100 和 0x200

FilterMode = IDLIST;
FilterIdHigh     = 0x100 << 5;
FilterMaskIdHigh = 0x200 << 5;

📏 滤波器位移说明(标准帧 vs 扩展帧)

  • 标准帧(11位 ID):要左移 5 位后写入 FilterIdHigh
  • 扩展帧(29位 ID):要左移 3 位后拆分成高 16/低 16,写入 FilterIdHigh/Low

🧪 滤波器配置完整示例(接收所有 ID)

CAN_FilterTypeDef canFilter;
canFilter.FilterBank = 0;
canFilter.FilterMode = CAN_FILTERMODE_IDMASK;
canFilter.FilterScale = CAN_FILTERSCALE_32BIT;
canFilter.FilterIdHigh = 0x0000;
canFilter.FilterIdLow  = 0x0000;
canFilter.FilterMaskIdHigh = 0x0000;
canFilter.FilterMaskIdLow  = 0x0000;
canFilter.FilterFIFOAssignment = CAN_FILTER_FIFO0;
canFilter.FilterActivation = ENABLE;
HAL_CAN_ConfigFilter(&hcan1, &canFilter);

📎 FIFO 分配与使用

  • 每个滤波器必须明确分配到 CAN_FILTER_FIFO0FIFO1

  • 接收中断也分 FIFO 注册(HAL_CAN_ActivateNotification()

    HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING);
    

🚦 注意事项

  • 滤波器必须在 HAL_CAN_Start() 之前配置;
  • 若接收不到消息,优先检查滤波器是否太严
  • 同时开启多个滤波器时,FilterBank 编号不可重复;
  • 多路接收时可以将不同的 ID 分配给 FIFO0 / FIFO1,便于分类处理。

6. 📤 消息发送流程

CAN_TxHeaderTypeDef txHeader;
HAL_CAN_AddTxMessage(&hcan1, &txHeader, data, &txMailbox);
  • 使用三个邮箱(Tx Mailbox)
  • 可用 HAL_CAN_GetTxMailboxesFreeLevel() 查看空闲邮箱数
  • DLC 为实际发送数据长度

7. 📥 接收逻辑详解(FIFO + 中断)

🔸 什么是 FIFO?

  • FIFO:First In First Out(先进先出)缓冲区

  • STM32 的 CAN 控制器内置两个硬件 FIFO:

    • FIFO0(接收队列0)
    • FIFO1(接收队列1)
  • 每个 FIFO 最多缓存 3 个完整 CAN 帧(包括 ID、DLC、数据等)

  • 当接收到的帧匹配某个滤波器,就被分配到相应 FIFO 中。


🔸 FIFO0 与 FIFO1 的区别?

特性FIFO0FIFO1
可使用中断CAN_IT_RX_FIFO0_MSG_PENDINGCAN_IT_RX_FIFO1_MSG_PENDING
可使用回调函数HAL_CAN_RxFifo0MsgPendingCallbackHAL_CAN_RxFifo1MsgPendingCallback
默认选择默认使用 FIFO0(除非滤波器指定使用 FIFO1)
目的可以按需分组消息来源、优先级或协议功能,分别放入 FIFO0/1

FIFO0 与 FIFO1 可以同时使用!
可以设置不同的滤波器,将不同 ID 的报文路由到不同的 FIFO,提升任务并行性与可维护性。


🔸 使用中断接收的推荐方式:

  1. 启用中断

    HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING);
    
  2. 实现回调函数(FIFO0 示例):

    void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
    {CAN_RxHeaderTypeDef rxHeader;uint8_t rxData[8];HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &rxHeader, rxData);// 处理数据printf("Recv ID=0x%X DLC=%d Data=...", rxHeader.StdId, rxHeader.DLC);
    }
    
  3. 好处

    • 不需要轮询接收(节省 CPU 资源)
    • 多个帧自动缓存到 FIFO,防止漏帧
    • 响应及时可靠

🔸 所有 CAN 中断说明:

STM32 中与 CAN 相关的 IRQ 一共四种,使用时建议根据用途精细启用:

中断向量名称描述用途是否必须
CAN1_TX_IRQn发送邮箱中断发送完成 / 可用时触发可选,若需处理发送确认
CAN1_RX0_IRQnFIFO0 接收中断接收到帧(匹配滤波器并进入 FIFO0)✅ 推荐开启
CAN1_RX1_IRQnFIFO1 接收中断同上,针对 FIFO1如使用 FIFO1,则必须
CAN1_SCE_IRQn状态变化与错误中断(SCE)错误帧、仲裁失败、总线关闭等可选,适合诊断问题

若只用 FIFO0 接收,可以仅启用 CAN1_RX0_IRQnCAN_IT_RX_FIFO0_MSG_PENDING。其余中断可用 HAL_NVIC_DisableIRQ(...) 关闭,以节省资源。


🔸 HAL 中断控制常用函数:

函数功能
HAL_NVIC_EnableIRQ(IRQn_Type IRQn)启用指定中断向量
HAL_NVIC_DisableIRQ(IRQn_Type IRQn)禁用指定中断
__HAL_CAN_ENABLE_IT()启用 CAN 中的特定中断标志位(如 RX、TX)
__HAL_CAN_CLEAR_FLAG()清除中断标志

✅ 示例配置(启用 FIFO0 接收中断):

HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING);
HAL_NVIC_SetPriority(CAN1_RX0_IRQn, 1, 0);
HAL_NVIC_EnableIRQ(CAN1_RX0_IRQn);

如果想使用 FIFO1 接收,记得:

  • 将对应滤波器的 FilterFIFOAssignment 设置为 CAN_FILTER_FIFO1
  • 同时使能 CAN_IT_RX_FIFO1_MSG_PENDING
  • 实现 HAL_CAN_RxFifo1MsgPendingCallback(...)

8. 🛠️ 源码与调试技巧

can.c:

/* USER CODE BEGIN Header */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "can.h"/* USER CODE BEGIN 0 */
#include "stdio.h"  // printf用
void CAN1_Start(void);
void CAN1_ConfigFilter(void);
/* USER CODE END 0 */CAN_HandleTypeDef hcan1;/* CAN1 init function */
void MX_CAN1_Init(void)
{/* USER CODE BEGIN CAN1_Init 0 *//* USER CODE END CAN1_Init 0 *//* USER CODE BEGIN CAN1_Init 1 *//* USER CODE END CAN1_Init 1 */hcan1.Instance = CAN1;hcan1.Init.Prescaler = 3;hcan1.Init.Mode = CAN_MODE_NORMAL;hcan1.Init.SyncJumpWidth = CAN_SJW_1TQ;hcan1.Init.TimeSeg1 = CAN_BS1_12TQ;hcan1.Init.TimeSeg2 = CAN_BS2_2TQ;hcan1.Init.TimeTriggeredMode = DISABLE;hcan1.Init.AutoBusOff = ENABLE;hcan1.Init.AutoWakeUp = DISABLE;hcan1.Init.AutoRetransmission = ENABLE;hcan1.Init.ReceiveFifoLocked = DISABLE;hcan1.Init.TransmitFifoPriority = DISABLE;if (HAL_CAN_Init(&hcan1) != HAL_OK){Error_Handler();}/* USER CODE BEGIN CAN1_Init 2 */CAN1_ConfigFilter();CAN1_Start();/* USER CODE END CAN1_Init 2 */}void HAL_CAN_MspInit(CAN_HandleTypeDef* canHandle)
{GPIO_InitTypeDef GPIO_InitStruct = {0};if(canHandle->Instance==CAN1){/* USER CODE BEGIN CAN1_MspInit 0 *//* USER CODE END CAN1_MspInit 0 *//* CAN1 clock enable */__HAL_RCC_CAN1_CLK_ENABLE();__HAL_RCC_GPIOA_CLK_ENABLE();/**CAN1 GPIO ConfigurationPA11     ------> CAN1_RXPA12     ------> CAN1_TX*/GPIO_InitStruct.Pin = GPIO_PIN_11|GPIO_PIN_12;GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;GPIO_InitStruct.Pull = GPIO_NOPULL;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;GPIO_InitStruct.Alternate = GPIO_AF9_CAN1;HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);/* CAN1 interrupt Init */HAL_NVIC_SetPriority(CAN1_TX_IRQn, 5, 0);HAL_NVIC_EnableIRQ(CAN1_TX_IRQn);HAL_NVIC_SetPriority(CAN1_RX0_IRQn, 5, 0);HAL_NVIC_EnableIRQ(CAN1_RX0_IRQn);HAL_NVIC_SetPriority(CAN1_RX1_IRQn, 5, 0);HAL_NVIC_EnableIRQ(CAN1_RX1_IRQn);HAL_NVIC_SetPriority(CAN1_SCE_IRQn, 5, 0);HAL_NVIC_EnableIRQ(CAN1_SCE_IRQn);/* USER CODE BEGIN CAN1_MspInit 1 *//* USER CODE END CAN1_MspInit 1 */}
}void HAL_CAN_MspDeInit(CAN_HandleTypeDef* canHandle)
{if(canHandle->Instance==CAN1){/* USER CODE BEGIN CAN1_MspDeInit 0 *//* USER CODE END CAN1_MspDeInit 0 *//* Peripheral clock disable */__HAL_RCC_CAN1_CLK_DISABLE();/**CAN1 GPIO ConfigurationPA11     ------> CAN1_RXPA12     ------> CAN1_TX*/HAL_GPIO_DeInit(GPIOA, GPIO_PIN_11|GPIO_PIN_12);/* CAN1 interrupt Deinit */HAL_NVIC_DisableIRQ(CAN1_TX_IRQn);HAL_NVIC_DisableIRQ(CAN1_RX0_IRQn);HAL_NVIC_DisableIRQ(CAN1_RX1_IRQn);HAL_NVIC_DisableIRQ(CAN1_SCE_IRQn);/* USER CODE BEGIN CAN1_MspDeInit 1 *//* USER CODE END CAN1_MspDeInit 1 */}
}/* USER CODE BEGIN 1 *//*** @brief 配置 CAN1 滤波器(接收所有标准帧)*/
void CAN1_ConfigFilter(void)
{CAN_FilterTypeDef canFilter;canFilter.FilterBank = 0;canFilter.FilterMode = CAN_FILTERMODE_IDMASK;canFilter.FilterScale = CAN_FILTERSCALE_32BIT;canFilter.FilterIdHigh     = 0x0000;canFilter.FilterIdLow      = 0x0000;canFilter.FilterMaskIdHigh = 0x0000;canFilter.FilterMaskIdLow  = 0x0000;canFilter.FilterFIFOAssignment = CAN_RX_FIFO0;canFilter.FilterActivation = ENABLE;canFilter.SlaveStartFilterBank = 14;if (HAL_CAN_ConfigFilter(&hcan1, &canFilter) != HAL_OK){Error_Handler();}
}
// /**
//  * @brief 配置 CAN1 滤波器(ID范围0x100~0x10F)
//  */
// void CAN1_ConfigFilter(void)
// {
//     CAN_FilterTypeDef canFilter = {0};//     canFilter.FilterBank = 0;
//     canFilter.FilterMode = CAN_FILTERMODE_IDMASK;
//     canFilter.FilterScale = CAN_FILTERSCALE_32BIT;//     // 过滤ID范围0x100~0x10F,掩码屏蔽最低4位
//     uint32_t filter_id = 0x100 << 21;
//     uint32_t filter_mask = 0x7F0 << 21;  // 掩码最低4位不比较,其他位比较//     canFilter.FilterIdHigh     = (filter_id >> 16) & 0xFFFF;
//     canFilter.FilterIdLow      = filter_id & 0xFFFF;
//     canFilter.FilterMaskIdHigh = (filter_mask >> 16) & 0xFFFF;
//     canFilter.FilterMaskIdLow  = filter_mask & 0xFFFF;//     canFilter.FilterFIFOAssignment = CAN_RX_FIFO0;
//     canFilter.FilterActivation = ENABLE;
//     canFilter.SlaveStartFilterBank = 14;//     if (HAL_CAN_ConfigFilter(&hcan1, &canFilter) != HAL_OK)
//     {
//         printf("Filter config failed!\r\n");
//         Error_Handler();
//     }
// }/*** @brief 启动 CAN1 并启用接收中断*/
void CAN1_Start(void)
{if (HAL_CAN_Start(&hcan1) != HAL_OK){printf("CAN Start Failed!\r\n");Error_Handler();}else{printf("CAN Started Successfully.\r\n");}if (HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING) != HAL_OK){printf("Enable RX Notification Failed!\r\n");Error_Handler();}else{printf("RX Notification Enabled.\r\n");}
}/*** @brief CAN1 发送一帧标准数据帧* @param StdId  标准帧ID* @param data   数据指针(最多8字节)* @param len    数据长度(0~8)*/
HAL_StatusTypeDef CAN1_SendMessage(uint32_t StdId, uint8_t *data, uint8_t len)
{CAN_TxHeaderTypeDef txHeader;uint32_t txMailbox;txHeader.StdId = StdId;txHeader.ExtId = 0;txHeader.IDE = CAN_ID_STD;txHeader.RTR = CAN_RTR_DATA;txHeader.DLC = len;txHeader.TransmitGlobalTime = DISABLE;if (HAL_CAN_AddTxMessage(&hcan1, &txHeader, data, &txMailbox) != HAL_OK){return HAL_ERROR;}return HAL_OK;
}/*** @brief 接收中断回调函数(FIFO0)*/
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
{if (hcan->Instance == CAN1){CAN_RxHeaderTypeDef rxHeader;uint8_t rxData[8];if (HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &rxHeader, rxData) == HAL_OK){printf("CAN1 RX: ID=0x%03X DLC=%d Data=", rxHeader.StdId, rxHeader.DLC);for (int i = 0; i < rxHeader.DLC; i++){printf("%02X ", rxData[i]);}printf("\r\n");}}
}/*** @brief 示例函数:仅发送一帧*/
void Example_CAN_Work(void)
{uint8_t exampleData[8] = {0x19, 0x92, 0x93, 0x74, 0x55, 0x36, 0x77, 0x00};if (HAL_CAN_GetTxMailboxesFreeLevel(&hcan1) > 0){if (CAN1_SendMessage(0x123, exampleData, 8) == HAL_OK){printf("CAN message sent.\r\n");}else{printf("CAN send error.\r\n");}}else{printf("No free CAN Tx mailbox!\r\n");}
}/* USER CODE END 1 */

避免问题:

  • 确认终端电阻(120Ω × 2)
  • 检查波特率一致性
  • 使用 CAN分析仪 做抓包验证
  • 若收不到数据,先试试是否 开启中断 + 滤波器未屏蔽

✅ 代码启动顺序建议

MX_CAN1_Init();       // 初始化
CAN1_ConfigFilter();  // 设置滤波器(可接收哪些帧)
CAN1_Start();         // 启动 + 启用接收中断

✅ 示例发送(发送一帧):

uint8_t data[8] = {0x01, 0x02, 0x03};
CAN1_SendMessage(0x123, data, 3);

✅ 示例接收(中断自动触发)

void HAL_CAN_RxFifo0MsgPendingCallback(...)
{HAL_CAN_GetRxMessage(...);// 打印数据
}

🔚 总结

CAN 的收发流程并不复杂,难点在于波特率配置、滤波器使用、同步机制的理解。
亲测的 9.735kbps 与 10kbps 通信失败,正体现了接收采样点延迟带来的不对称性问题,是工程上值得注意的重要细节。

http://www.lryc.cn/news/603492.html

相关文章:

  • 【开源项目】轻量加速利器 HubProxy 自建 Docker、GitHub 下载加速服务
  • 系统改造:一次系统领域拆分的实战复盘
  • 多态示例。
  • kotlin使用mybatis plus lambdaQuery报错
  • XtestRunner一个比较好用好看的生成测试报告的工具
  • 系统间复制文档
  • 论文阅读--射频电源在半导体领域的应用
  • React--》实现 PDF 文件的预览操作
  • 配置daemon.json使得 Docker 容器能够使用服务器GPU【验证成功】
  • VitePress学习笔记
  • 彻底清理ArcGIS 10.2残留的步骤
  • Windows使用Powershell自动安装SqlServer2025服务器与SSMS管理工具
  • Vue.js 完全指南:从入门到精通
  • getgff.py脚本-python006
  • openbmc 阈值sensor分析
  • 计算机视觉(CV方向)算法基础
  • SketchUp纹理贴图插件Architextures安装使用图文教程
  • Linux sshfs 安全挂载远程文件系统 命令详解
  • Angular面试题目和答案大全
  • AR辅助前端设计:虚实融合场景下的设备维修指引界面开发实践
  • Mac m系列芯片安装node14版本使用nvm + Rosetta 2
  • YotoR模型:Transformer与YOLO新结合,打造“又快又准”的目标检测模型
  • VUE -- 基础知识讲解(一)
  • 【MySQL】数据库的简单介绍
  • Node.js 内置模块
  • 安卓模拟器 adb Frida hook 抓包
  • uniapp如何封装uni.request 全局使用
  • 自适应双门限的能量检测算法
  • 2025年中科院1区SCI-冬虫夏草优化算法Caterpillar Fungus Optimizer-附Matlab免费代码
  • 09 RK3568 Debian11 ES8388 模拟音频输出