从零开始之stm32之CAN通信

发布于:2025-08-14 ⋅ 阅读:(24) ⋅ 点赞:(0)

从小白的视角了解并实现简单的STM32F103的CAN通信,直接上手。

一、CAN协议简介

CAN总线上传输的信息称为报文,当总线空闲时任何连接的单元都可以开始发送新的报文,有5种类型的帧:数据帧、遥控帧、错误帧、过载帧、帧间隔。数据帧有两种格式:标准格式和扩展格式主要区别在于ID长度。

二、参数含义和设置

STM32CubeIDE关于CAN的参数设置

1、位时序参数说明和设置

Prescaler:预分频系数,定义CAN单位时钟周期=APB1外设时钟频率/分频系数,取值范围:1 ~ 1024,上述APB频率 = 36MHz。

用打电话的方式,表示设置参数:

步骤 CAN总线对应 参数作用
1. 你说:"今~天~晚~上~7~点~" TimeSeg1(匀速发送数据) 放慢语速,确保对方听清每个字(信号稳定)
2. 停顿时间 TimeSeg2(等待ACK) 留时间让对方回应(同步采样点)
3. 对方可能在停顿内回应:    
- ✅ "好!"(ACK) 正常接收,继续通信 同步成功,无需调整
- ❌ 没回应(信号延迟) SyncJumpWidth 发挥作用!  
4. 你决定最多再等一等

SyncJumpWidth

等待xTq,x个单位时间

允许对方稍晚回应(时钟容错)
- 如果多等的时间内对方回应 同步成功 通信继续
- 如果多等的时间内仍无回应 判定不同步,重传数据 自动重传或报错

参数确定:

1、优先确定Prescaler:

计算 Prescaler = CAN_CLK / (波特率 × (1 + TimeSeg1 + TimeSeg2)),取最接近的整数值。

例如:36MHz目标1Mbps,假设 1+6+2=9Tq → Prescaler = 36MHz/(1MHz×9) = 4。

2、TimeSeg1/TimeSeg2分配:

短距离:TimeSeg1可小(如5~6Tq),TimeSeg2=1~2Tq。

长距离:TimeSeg1需大(如8~12Tq),TimeSeg2=2~4Tq。

规则:TimeSeg1 ≥ TimeSeg2 + SyncJumpWidth。

3、SyncJumpWidth选择:

高速CAN(≥500kbps):1Tq(严格同步)。

低速/工业CAN(≤250kbps):2Tq(增强容错)。

4、抗干扰优化:

增大TimeSeg1和SyncJumpWidth可提升稳定性,但会降低波特率。

若通信不稳定,逐步增加TimeSeg1(每次+1Tq)并测试。

2、基本参数说明和设置

参数说明:

基本参数 含义
Time Triggered Communication Mode
(时间触发通信模式)
作用:为接收帧添加时间戳(用于时间同步协议)
ENABLE:硬件记录帧的接收时间戳(存储在CAN_RDTxR寄存器)
DISABLE:不记录时间戳(默认)
Automatic Bus-Off Management
(自动总线关闭管理)
作用:节点错误计数器超限后是否自动恢复
ENABLE:节点进入"Bus-Off"状态后,自动等待128次11位隐性位后恢复通信
DISABLE:需手动调用HAL_CAN_ResetError()恢复,适合需要人工干预的调试场景
Automatic Wake-up Mode
(自动唤醒模式)
作用:CAN从休眠模式唤醒的条件
ENABLE:检测到总线活动时自动唤醒(适合低功耗设备)
DISABLE:必须通过软件或硬件信号唤醒(如按键触发)
Automatic Retransmission
(自动重传)
作用:控制发送失败时是否自动重传
ENABLE:发送失败后自动重传(默认),确保数据可靠性,适合实时性要求高的场景(如汽车ECU)
DISABLE:仅发送一次,失败不重试。用于严格时序控制(如CAN FD协议中避免报文堆积)
Receive Fifo Locked Mode
(接收FIFO锁定模式)
接收FIFO满时的处理策略
ENABLE:FIFO满时丢弃新数据(避免覆盖旧数据)
DISABLE:新数据覆盖最旧数据(默认)
Transmit Fifo Priority
(发送FIFO优先级)
作用:发送邮箱的调度策略
ENABLE:按报文ID优先级发送(低ID优先)
DISABLE:按FIFO顺序发送(默认

3、高级参数设置和说明

测试模式说明

Test Mode
测试模式
CAN工作模式:
Normal:正常模式(向总线发送或从总线接收数据)
Loopback:环回模式(自发自收,无需硬件连接,用于本地回环测试)
Silent:静默模式(监听总线,不响应,),只监听不干扰,适合诊断
Loopback combined with Silent:静默环回模式(本地回环+总线监听),混合用途,调试复杂场景

4、开启中断

可以在System Core --> NVIC中设置中断优先等级

三、实际例程

1、CAN初始化

增加接收滤波器,可以用于筛选接收数据,硬件级过滤,可以

  1. 降低CPU负担(避免处理无关报文)

  2. 提高实时性(仅处理目标数据)

  3. 必须配置(否则无法接收数据)

  4. 灵活可控(支持掩码/列表两种过滤模式)

/**
 * @brief CAN Initialization Function
 * @param None
 * @retval None
 */
static void MX_CAN_Init(void)
{

  /* USER CODE BEGIN CAN_Init 0 */

  /* USER CODE END CAN_Init 0 */

  /* USER CODE BEGIN CAN_Init 1 */

  /* USER CODE END CAN_Init 1 */
  hcan.Instance = CAN1;
  hcan.Init.Prescaler = 4;
  hcan.Init.Mode = CAN_MODE_LOOPBACK;
  hcan.Init.SyncJumpWidth = CAN_SJW_1TQ;
  hcan.Init.TimeSeg1 = CAN_BS1_5TQ;
  hcan.Init.TimeSeg2 = CAN_BS2_3TQ;
  hcan.Init.TimeTriggeredMode = DISABLE;
  hcan.Init.AutoBusOff = DISABLE;
  hcan.Init.AutoWakeUp = ENABLE;
  hcan.Init.AutoRetransmission = DISABLE;
  hcan.Init.ReceiveFifoLocked = DISABLE;
  hcan.Init.TransmitFifoPriority = DISABLE;
  if (HAL_CAN_Init(&hcan) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN CAN_Init 2 */

  /* USER CODE BEGIN CAN_Init 2 */

  CAN_FilterTypeDef sFilterConfig;
  sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;
  sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT;
  sFilterConfig.FilterIdHigh = 0x0000; // 可以保持为0接收所有ID
  sFilterConfig.FilterIdLow = 0x0000;
  sFilterConfig.FilterMaskIdHigh = 0x0000;
  sFilterConfig.FilterMaskIdLow = 0x0000;
  sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0;
  sFilterConfig.FilterBank = 0; // 添加过滤器组号
  sFilterConfig.SlaveStartFilterBank = 14;
  sFilterConfig.FilterActivation = ENABLE;

  HAL_StatusTypeDef state = HAL_CAN_ConfigFilter(&hcan, &sFilterConfig);
  if (state != HAL_OK)
  {
    Error_Handler();
  }

  state = HAL_CAN_Start(&hcan);
  if (state != HAL_OK)
  {
    Error_Handler();
  }

  state = HAL_CAN_ActivateNotification(&hcan, CAN_IT_RX_FIFO0_MSG_PENDING);
  if (state != HAL_OK)
  {
    Error_Handler();
  }

  /* USER CODE END CAN_Init 2 */
  /* USER CODE END CAN_Init 2 */
}

关于接收滤波器说明与设置:

参数

说明

FilterMode
(过滤模式)
可选值:
CAN_FILTERMODE_IDMASK(掩码模式)
CAN_FILTERMODE_IDLIST(列表模式)
作用:
掩码模式:通过FilterId和FilterMask组合定义ID范围(类似通配符规则)
示例:FilterId=0x100, FilterMask=0xFF00 → 接收ID范围0x100~0x1FF
列表模式:精确匹配FilterId和FilterMask中指定的ID(最多2个32位ID或4个16位ID)
示例:FilterId=0x123, FilterMask=0x456 → 仅接收ID为0x123或0x456的帧
FilterScale
(过滤器尺度)
可选值:
CAN_FILTERSCALE_32BIT(32位宽过滤器)
CAN_FILTERSCALE_16BIT(16位宽过滤器)
作用:
32位模式:单个过滤器可处理1个完整扩展帧ID(29位)或2个标准帧ID(11位)。
16位模式:单个过滤器可处理2个标准帧ID(11位)或部分扩展帧ID(需组合使用)。
选择建议:
扩展帧或复杂过滤规则 → 32位模式。
仅标准帧且需更多过滤组 → 16位模式。
FilterIdHigh
FilterIdLow
(过滤器ID值)
作用:定义待匹配的基准ID值(具体解释取决于FilterMode和FilterScale)
32位模式下的存储规则
标准帧ID(11位):
FilterIdHigh = (StdId << 5);  // 左移5位对齐到寄存器位[15:5]
FilterIdLow = 0x0000;         // 低16位未使用
扩展帧ID(29位):
FilterIdHigh = (ExtId >> 13) & 0xFFFF;  // 高16位(bit28~bit13)
FilterIdLow = ((ExtId << 3) & 0xFFF8) | 0x04; // 低13位(bit12~bit0)+ IDE=1
FilterMaskIdHigh
FilterMaskIdLow
(过滤器掩码)
作用:
掩码模式:定义ID中哪些位必须匹配(1=必须匹配,0=不关心)。
示例:FilterId=0x100, FilterMask=0xFF00 → 匹配所有0x1XX的ID。
列表模式:作为第二个ID值(与FilterId组成精确匹配列表)。
特殊值:
全0x0000:接收所有ID(关闭过滤)。
全0xFFFF:精确匹配FilterId
FilterFIFOAssignment
(FIFO分配)
可选值:
CAN_RX_FIFO0 或 CAN_RX_FIFO1
作用:
指定匹配的报文存入哪个接收FIFO(STM32 CAN控制器有2个接收FIFO)。
推荐配置:
通常使用FIFO0,FIFO1可用于优先级更高的报文
FilterBank
(过滤器组编号)
取值范围:
单CAN设备:0~13(如STM32F103)
双CAN设备:0~27(如STM32F105)
作用:
选择具体的硬件过滤器组(STM32提供多个独立过滤器组,可并行工作)
SlaveStartFilterBank
(从CAN过滤器起始组)
作用:
在双CAN模式(如CAN1+CAN2)下,定义从CAN实例(CAN2)的起始过滤器组编号。
示例:SlaveStartFilterBank=14 → CAN1用组0~13,CAN2用组14~27。
单CAN模式:此参数无效,但需保留(通常设为14)
FilterActivation
(过滤器激活)
可选值:
ENABLE:立即激活过滤器。
DISABLE:配置但不启用(需后续手动激活)。
关键点:
必须至少有一个激活的过滤器,否则CAN控制器会丢弃所有报文!

2、添加中断回调函数

STM32 HAL库中CAN接收中断的回调函数,当CAN控制器的接收FIFO0(接收缓冲区0)中有新消息到达时,会自动触发此函数。

/* USER CODE BEGIN 4 */

void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *CanHandle)
{
  /* Get RX message */
  if (HAL_CAN_GetRxMessage(CanHandle, CAN_RX_FIFO0, &RxHeader, RxData) != HAL_OK)
  {
    /* Reception Error */
    Error_Handler();
  }

  // 接收到的数据:RxData[8]
  // 接收到的ID值:RxHeader.StdId
}

/* USER CODE END 4 */

3、发送数据

将CAN报文放入发送邮箱,由硬件自动发送到总线上

  • 非阻塞式发送:函数仅将数据存入CAN控制器的发送邮箱,实际发送由硬件完成,无需CPU持续等待。

    if (HAL_CAN_AddTxMessage(&hcan, &txHeader, TxData, &TxMailbox) != HAL_OK)
    {
      Error_Handler();
    }

4、整个例程

可以通过调试查看接收的数据是否对应发送数据。

/* USER CODE BEGIN Header */
/**
 ******************************************************************************
 * @file           : main.c
 * @brief          : Main program body
 ******************************************************************************
 * @attention
 *
 * Copyright (c) 2025 STMicroelectronics.
 * All rights reserved.
 *
 * This software is licensed under terms that can be found in the LICENSE file
 * in the root directory of this software component.
 * If no LICENSE file comes with this software, it is provided AS-IS.
 *
 ******************************************************************************
 */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
CAN_HandleTypeDef hcan;

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_CAN_Init(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

CAN_TxHeaderTypeDef txHeader =
    {
        .StdId = 0x123,
        .ExtId = 0x00,
        .IDE = CAN_ID_STD,
        .RTR = CAN_RTR_DATA,
        .DLC = 8,
        .TransmitGlobalTime = DISABLE};
CAN_RxHeaderTypeDef RxHeader;
uint8_t TxData[8] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}; // 定义数据
uint8_t RxData[8];
uint32_t TxMailbox;

/* USER CODE END 0 */

/**
 * @brief  The application entry point.
 * @retval int
 */
int main(void)
{

  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_CAN_Init();
  /* USER CODE BEGIN 2 */

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */

    if (HAL_CAN_AddTxMessage(&hcan, &txHeader, TxData, &TxMailbox) != HAL_OK)
    {
      Error_Handler();
    }

    HAL_Delay(2000);

    HAL_GPIO_TogglePin(SYS_LED_GPIO_Port, SYS_LED_Pin);
  }
  /* USER CODE END 3 */
}

/**
 * @brief System Clock Configuration
 * @retval None
 */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Initializes the RCC Oscillators according to the specified parameters
   * in the RCC_OscInitTypeDef structure.
   */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses clocks
   */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }
}

/**
 * @brief CAN Initialization Function
 * @param None
 * @retval None
 */
static void MX_CAN_Init(void)
{

  /* USER CODE BEGIN CAN_Init 0 */

  /* USER CODE END CAN_Init 0 */

  /* USER CODE BEGIN CAN_Init 1 */

  /* USER CODE END CAN_Init 1 */
  hcan.Instance = CAN1;
  hcan.Init.Prescaler = 4;
  hcan.Init.Mode = CAN_MODE_LOOPBACK;
  hcan.Init.SyncJumpWidth = CAN_SJW_1TQ;
  hcan.Init.TimeSeg1 = CAN_BS1_5TQ;
  hcan.Init.TimeSeg2 = CAN_BS2_3TQ;
  hcan.Init.TimeTriggeredMode = DISABLE;
  hcan.Init.AutoBusOff = DISABLE;
  hcan.Init.AutoWakeUp = ENABLE;
  hcan.Init.AutoRetransmission = DISABLE;
  hcan.Init.ReceiveFifoLocked = DISABLE;
  hcan.Init.TransmitFifoPriority = DISABLE;
  if (HAL_CAN_Init(&hcan) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN CAN_Init 2 */

  /* USER CODE BEGIN CAN_Init 2 */

  CAN_FilterTypeDef sFilterConfig;
  sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;
  sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT;
  sFilterConfig.FilterIdHigh = 0x0000; // 可以保持为0接收所有ID
  sFilterConfig.FilterIdLow = 0x0000;
  sFilterConfig.FilterMaskIdHigh = 0x0000;
  sFilterConfig.FilterMaskIdLow = 0x0000;
  sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0;
  sFilterConfig.FilterBank = 0; // 添加过滤器组号
  sFilterConfig.SlaveStartFilterBank = 14;
  sFilterConfig.FilterActivation = ENABLE;

  HAL_StatusTypeDef state = HAL_CAN_ConfigFilter(&hcan, &sFilterConfig);
  if (state != HAL_OK)
  {
    Error_Handler();
  }

  state = HAL_CAN_Start(&hcan);
  if (state != HAL_OK)
  {
    Error_Handler();
  }

  state = HAL_CAN_ActivateNotification(&hcan, CAN_IT_RX_FIFO0_MSG_PENDING);
  if (state != HAL_OK)
  {
    Error_Handler();
  }

  /* USER CODE END CAN_Init 2 */
  /* USER CODE END CAN_Init 2 */
}

/**
 * @brief GPIO Initialization Function
 * @param None
 * @retval None
 */
static void MX_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};
  /* USER CODE BEGIN MX_GPIO_Init_1 */
  /* USER CODE END MX_GPIO_Init_1 */

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOC_CLK_ENABLE();
  __HAL_RCC_GPIOD_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(SYS_LED_GPIO_Port, SYS_LED_Pin, GPIO_PIN_SET);

  /*Configure GPIO pin : SYS_LED_Pin */
  GPIO_InitStruct.Pin = SYS_LED_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(SYS_LED_GPIO_Port, &GPIO_InitStruct);

  /* USER CODE BEGIN MX_GPIO_Init_2 */
  /* USER CODE END MX_GPIO_Init_2 */
}

/* USER CODE BEGIN 4 */

void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *CanHandle)
{
  /* Get RX message */
  if (HAL_CAN_GetRxMessage(CanHandle, CAN_RX_FIFO0, &RxHeader, RxData) != HAL_OK)
  {
    /* Reception Error */
    Error_Handler();
  }

  // 接收到的数据:RxData[8]
  // 接收到的ID值:RxHeader.StdId
}

/* USER CODE END 4 */

/**
 * @brief  This function is executed in case of error occurrence.
 * @retval None
 */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef USE_FULL_ASSERT
/**
 * @brief  Reports the name of the source file and the source line number
 *         where the assert_param error has occurred.
 * @param  file: pointer to the source file name
 * @param  line: assert_param error line source number
 * @retval None
 */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

四、整个通信逻辑

CAN通信关键环节详解

1、发送端逻辑

步骤 操作 硬件/软件 校验机制
组帧 填充ID、数据、帧类型(标准/扩展) 软件
CRC计算 硬件自动计算15位CRC,附加到帧尾 硬件 CRC校验字段
发送仲裁 竞争总线:低ID优先发送 硬件

2. 总线传输

  • 广播特性:所有节点同时收到数据,但只有通过过滤的节点会处理。

  • 物理层校验

    • 差分信号(CAN_H/CAN_L)抗干扰

    • 显性电平(0)覆盖隐性电平(1)实现仲裁

3. 接收端逻辑

步骤 操作 硬件/软件 校验机制
硬件过滤 比较报文ID与过滤器规则 硬件 ID匹配
CRC校验 硬件自动验证CRC,错误则丢弃 硬件 CRC校验
ACK响应 接收节点发送ACK位(显性0) 硬件 ACK确认
数据存储 通过校验的帧存入FIFO 硬件
软件校验 检查DLC、数据有效性等 软件 自定义校验(如和校验)

4. 硬件级校验

校验类型 实现方式 失败处理
CRC校验 数据链路层 硬件计算15位CRC 自动丢弃错误帧
ACK响应 数据链路层 接收节点发送ACK位 发送端重传
格式校验 数据链路层 检查帧格式(如EOF) 触发错误帧

5、错误处理机制

错误类型 检测方式 处理动作
CRC错误 硬件自动检测 丢弃帧,错误计数器+1
ACK缺失 发送节点未检测到ACK位 自动重传(若AutoRetransmission=ENABLE)
总线离线 错误计数器>255 进入Bus-Off状态,需复位或等待恢复

6、应用场景建议

  • 汽车ECU:严格ID过滤 + CRC校验 + AutoRetransmission

  • 工业控制:自定义软件校验 + 长TimeSeg1抗干扰

  • 诊断工具:接收所有ID(FilterMask=0) + 软件解析

通过上述机制,CAN总线实现高可靠、实时的分布式通信。


网站公告

今日签到

点亮在社区的每一天
去签到