全部例程

CAN - 控制器局域网络接口

W55MH32 其他标签

2025/02/12 更新

控制器局域网(Controller Area Network, CAN)作为工业自动化和汽车电子领域的核心通信技术, 以其高可靠性和实时性著称。下面我们将基于W55MH32以太网单片机的bxCAN(基本扩展 CAN)模块,系统讲解CAN通信的原理、架构及实际应用要点,和大家一起学习和使用这一技术。

CAN通信概述

简介

CAN(Controller Area Network)是一种用于实时控制的串行通信总线,由博世公司开发,广泛应用于汽车电子、工业自动化等领域。

功能特点

CAN接口有以下特点:

  • 多主通信机制:网络中所有节点均可在任意时刻主动发送数据,无需中央控制器协调。当多个节点同时发送时,通过标识符仲裁决定优先级,确保高优先级消息优先传输。
  • 非破坏性总线仲裁:仲裁过程基于标识符的二进制值,数值越小优先级越高。仲裁失败的节点会自动停止发送,避免总线冲突。
  • 错误处理:集成CRC校验、应答校验等多种错误检测机制,检测到错误时自动发送错误帧,并通过错误计数器实现故障界定(主动错误、被动错误、离线状态)。
  • 实时性保障:通信速率最高可达1Mbps(短距离),满足实时控制场景需求。

CAN帧结构与类型

帧类型 作用 结构特点
数据帧 传输数据 包含帧起始、仲裁段、控制段、数据段、CRC段、应答段和帧结束,数据长度0-8字节。
远程帧 请求其他节点发送数据 无数据段,仲裁段标识符用于标识请求的消息,接收节点接收到远程帧后发送对应数据帧。
错误帧 检测到错误时发送 由错误标志(6个连续显性位)和错误界定符(8个隐性位)组成,所有节点检测到错误时发送。
过载帧 通知其他节点自身接收缓冲器满 用于延缓数据传输,由过载标志和过载界定符组成。

标识符与优先级

CAN帧通过标识符(ID)确定优先级,ID越小优先级越高。
W55MH32支持两种帧格式:

  • 标准帧:11位ID。
  • 扩展帧:29位ID。

标识符不代表节点地址,而是与消息内容相关。例如,汽车中“发动机转速”的消息可能对应标识符0x100,所有订阅该消息的节点均可接收。

W55MH32的bxCAN模块详解

功能特点

W55MH32 集成的 bxCAN(Basic Extended CAN)模块是一款高性能 CAN 控制器, 专为减轻 CPU 负荷设计,其关键特性包括:

  • 协议兼容性:完全支持CAN 2.0A和2.0B主动模式,波特率最高1Mbps。
  • 发送处理能力:3个独立发送邮箱,支持优先级配置和时间戳记录(发送帧起始时刻的定时器值)。
  • 接收管理机制:2个3级深度的接收FIFO(FIFO0和FIFO1),硬件自动管理报文存储,减少CPU干预。
  • 灵活的过滤系统:14个可配置的过滤器组,支持32位或16位过滤宽度,可设置为屏蔽位模式或标识符列表模式。
  • 时间触发通信:支持TTCM模式,内部16位定时器为报文添加时间戳,适用于对实时性要求极高的场景。

硬件资源与内存映射

bxCAN模块的硬件资源包括:

  • 引脚连接:通过CANTX和CANRX引脚连接外部收发器(如TJA1050),总线需接120Ω终端电阻。
  • 寄存器组:包含控制寄存器(CAN_MCR)、状态寄存器(CAN_MSR)、发送状态寄存器(CAN_TSR)、接收FIFO寄存器(CAN_RF0R/CAN_RF1R)等,共32个32位寄存器,地址范围0x00至0x31C。
  • 共享内存:与USB模块共用512字节SRAM,用于数据收发缓冲,二者不可同时使用。

时钟与波特率

bxCAN的时钟源自APB1总线,其波特率计算公式为:
波特率 = 系统时钟 / [2 × CAN_BRP × (CAN_SJW + 1 + CAN_BS1 + 1 + CAN_BS2 + 1)]

  • CAN_BRP:波特率分频器,范围1~1024。
  • CAN_SJW:重新同步跳跃宽度,决定总线相位误差的补偿能力。
  • CAN_BS1/CAN_BS2:时间段1 和时间段2,用于定义采样点位置和位时序。

例如,当系统时钟为72MHz,设置CAN_BRP=9、CAN_SJW=1、CAN_BS1=6、CAN_BS2=3时,波特率为1Mbps,采样点位于75%位时间处,兼顾抗干扰性和同步能力。

bxCAN工作模式与状态转换

工作模式

bxCAN支持三种主要工作模式,通过CAN_MCR寄存器配置:

初始化模式

  • 进入条件:软件设置CAN_MCR.INRQ=1,等待CAN_MSR.INAK=1确认。
  • 功能:仅在此模式下可配置波特率(CAN_BTR寄存器)、过滤器组参数(位宽、模式、FIFO关联等)。/CAN_RF1R)等,共32个32位寄存器,地址范围0x00至0x31C。
  • 注意事项:初始化完成后需退出该模式才能正常通信,退出时需等待总线空闲(检测到11个连续隐性位)。

正常模式

  • 工作状态:支持完整的收发功能,节点与总线同步后即可发送和接收报文。
  • 同步机制:通过检测帧起始位(SOF)的上升沿实现位同步,后续通过重新同步调整相位误差。/CAN_RF1R)等,共32个32位寄存器,地址范围0x00至0x31C。
  • 典型应用:工业控制中的数据交互、汽车ECU间的实时通信。

睡眠模式

  • 低功耗设计:时钟停止,仅保留唤醒逻辑,功耗显著降低。
  • 唤醒方式:软件清CAN_MCR.SLEEP位,或硬件检测到总线活动(需设置CAN_MCR.AWUM=1)。
  • 应用场景:电池供电设备的节能模式,如车载传感器的待机状态。

测试模式:静默与环回

为便于开发调试,bxCAN支持两种测试模式,需在初始化模式下配置CAN_BTR寄存器:

静默模式(SILM=1)

  • 特性:可接收数据帧和远程帧,但发送时仅输出隐性位,不影响总线状态。
  • 用途:用于分析总线活动,不干扰现有通信,如故障诊断时的总线监听。

环回模式(LBKM=1)

  • 特性:发送的报文直接在内部回环至接收端,忽略CANRX引脚输入。
  • 用途:自测试场景,无需外部硬件即可验证收发功能,如模块出厂前的自测。

环回静默模式(SILM=1+LBKM=1)

  • 组合特性:兼具环回和静默功能,发送报文不影响外部总线,仅内部回环测试。
  • 安全测试:适用于"热自测试",避免干扰实际总线通信。

报文发送与接收处理机制

发送处理流程

bxCAN的发送流程由硬件自动管理,软件只需配置邮箱并请求发送:

  1. 邮箱选择:3个发送邮箱(邮箱0~2),空闲时可选择任意邮箱。
  2. 参数配置:设置标识符(STDID/EXID)、帧类型(数据帧/远程帧)、数据长度(DLC)和数据内容。
  3. 发送请求:置位CAN_TIxR.TXRQ位,邮箱状态变为"挂号",等待仲裁。
  4. 仲裁与发送:仲裁成功后进入"发送中"状态,发送完成后CAN_TSR.TXOK位置1,邮箱重置为空闲。

发送优先级规则

  • 标识符优先:CAN_MCR.TXFP=0时,标识符数值小的报文优先发送;标识符相同时,邮箱号小的优先。
  • FIFO优先:CAN_MCR.TXFP=1时,按发送请求的先后顺序排队,适用于分段发送场景。

接收管理与FIFO机制

接收处理通过2个3级深度FIFO实现,完全由硬件管理:

  1. 有效报文判定:正确接收(无CRC错误、格式错误等)且通过过滤器检查的报文为有效报文。
  2. FIFO存储:有效报文按接收顺序存入FIFO,FIFO0和FIFO1各自独立,可通过过滤器配置关联到不同FIFO。
  3. 状态标识:CAN_RF0R/FIFO1R中的FMP[1:0]位指示FIFO中的报文数量(0~3),满时FULL位被置1。
  4. 溢出处理:可配置FIFO锁定模式(CAN_MCR.RFLM=1),此时新报文会被丢弃;否则最新报文覆盖旧报文。

时间戳与过滤器匹配序号

  • 时间戳:每个接收报文记录帧起始时刻的16位定时器值,存入CAN_RDTxR.TIME[15:0],用于分析报文延迟。
  • 过滤器匹配序号(FMI):记录匹配的过滤器组编号,存入CAN_RDTxR.FMI[7:0],软件可据此快速区分报文类型。

标识符过滤系统解析

过滤机制的核心价值

CAN网络中,节点通常只关注部分报文,过滤器的作用是硬件层面筛选有效报文,减轻CPU负荷。bxCAN的过滤系统具有以下特点:

  • 14个过滤器组:每个组可独立配置,支持标准帧和扩展帧过滤。
  • 可变位宽:每个组可设置为32位单过滤器或2个16位过滤器。
  • 双模式配置:屏蔽位模式或标识符列表模式,灵活适应不同场景。

屏蔽位模式与列表模式

屏蔽位模式

  • 工作原理:每个过滤器组包含1个标识符寄存器和1个屏蔽寄存器,屏蔽位为1的位置需严格匹配,为0的位置"不关心"。
  • 典型应用:过滤一组相关报文,如标识符0x100~0x1FF的标准帧,可设置标识符为0x100,屏蔽码为0x0FF。

标识符列表模式

  • 工作原理:两个寄存器均作为标识符寄存器,报文必须与其中一个完全匹配才会通过过滤。
  • 典型应用:精确过滤特定报文,如只接收标识符为0x200的扩展帧,可设置两个寄存器均为0x200的扩展帧格式。

过滤器优先级规则

当报文匹配多个过滤器时,优先级由以下规则决定:

  1. 位宽优先:32位过滤器优先级高于16位过滤器。
  2. 模式优先:列表模式优先级高于屏蔽位模式。
  3. 编号优先:过滤器组编号小的优先级高(0-13)。

例如,32位列表模式的过滤器组0优先级高于16位屏蔽位模式的过滤器组1,确保关键报文优先处理。

错误管理与总线健康监测

错误检测与计数器机制

bxCAN通过发送错误计数器(TEC)和接收错误计数器(REC)实现故障界定:

  • 错误类型:支持位错误、填充错误、CRC错误、格式错误、应答错误等,错误码存入CAN_ESR.LEC[2:0]。
  • 计数器更新:
    • 发送错误时TEC加1,接收错误时REC加1。
    • 成功发送/接收时,TEC和REC减1(超过127时减至120)。
  • 状态转换:
    • TEC或REC > 127时进入错误被动状态,此时发送错误帧为隐性位。
    • TEC > 255时进入离线状态,无法收发报文,需通过自动恢复或软件干预重新上线。

离线恢复机制

离线状态的恢复方式由CAN_MCR.ABOM位决定:

  • 自动恢复(ABOM=1):检测到128次11个连续隐性位后自动恢复为错误主动状态。
  • 手动恢复(ABOM=0):软件需先请求进入初始化模式,再退出,等待总线同步后恢复。

错误中断与状态监控

通过CAN_IER寄存器可配置多种错误中断:

  • 错误警告中断(EWGIE=1):TEC或REC ≥ 96时触发,提示总线可能出现异常。
  • 错误被动中断(EPVIE=1):进入错误被动状态时触发,需关注总线稳定性。
  • 离线中断(BOFIE=1):进入离线状态时触发,需启动恢复流程。
  • 上次错误码中断(LECIE=1):每次错误时更新错误码并触发中断,便于快速定位问题。

应用场景

工业自动化网络

  • 多设备协同控制:如PLC与传感器、执行器间的实时数据交互,通过CANopen协议实现设备联网。
  • 抗干扰设计:采用隔离收发器(如CTM1051)和屏蔽双绞线,总线两端接120Ω终端电阻,降低电磁干扰影响。

汽车电子系统

  • 车载网络:连接发动机控制单元(ECU)、ABS、仪表盘等,遵循J1939或CANopen协议。
  • 低功耗需求:利用睡眠模式和唤醒功能,减少待机功耗,满足汽车电源管理要求。

注意事项

硬件连接与终端配置

  • 总线两端必须接入120Ω终端电阻,中间节点无需额外电阻,避免阻抗不匹配导致信号反射。
  • 工业场景建议使用隔离收发器(如CTM1051)和屏蔽双绞线,屏蔽层单点接地以降低电磁干扰。

波特率一致性配置

  • 所有节点的波特率参数(CAN_BRP、BS1、BS2、SJW)必须完全一致,建议通过寄存器硬编码避免计算误差。
  • 长距离通信(>100m)需降低波特率至50kbps以下,并将采样点后移至80%位时间处(增大BS1参数)。

过滤器与FIFO管理

  • 未使用的过滤器组需设置为非激活状态(CAN_FA1R对应位清0),避免无效报文占用资源。
  • 接收FIFO需定期查询或启用中断(如FMPIE),防止因软件处理延迟导致报文溢出。

错误处理与恢复机制

  • 监控发送/接收错误计数器(TEC/REC),超过96时触发预警,超过127时切换至降级模式。
  • 离线恢复时,若使用手动模式(ABOM=0),需确保退出初始化模式后等待总线同步完成(CAN_MSR.INAK=0)。

低功耗模式操作规范

  • 进入睡眠模式前需确认所有发送邮箱为空(CAN_TSR.TME=1),唤醒时优先使用硬件总线活动检测(AWUM=1)以降低功耗。

程序设计

CAN_LoopBack例程

CAN_LoopBack例程实现了CAN总线回环测试程序,通过串口接收指令控制CAN数据发送, 并自动接收回环数据进行打印输出,用于验证CAN控制器功能。以下是实现过程和结果验证。

CAN初始化

CAN_Mode_Init()函数是W55MH32的CAN总线初始化函数,内容如下:

 
  uint8_t CAN_Mode_Init(uint8_t tsjw, uint8_t tbs2, uint8_t tbs1, uint16_t brp, uint8_t mode)
  {
      GPIO_InitTypeDef      GPIO_InitStructure;
      CAN_InitTypeDef       CAN_InitStructure;
      CAN_FilterInitTypeDef CAN_FilterInitStructure;
  #if CAN_RX0_INT_ENABLE
      NVIC_InitTypeDef NVIC_InitStructure;
  #endif

      RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE);
      RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);

      GPIO_PinRemapConfig(GPIO_Remap1_CAN1, ENABLE);

      GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_9;
      GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
      GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF_PP; //reuse push pull
      GPIO_Init(GPIOB, &GPIO_InitStructure);           //initialize io

      GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_8;
      GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //pull up input
      GPIO_Init(GPIOB, &GPIO_InitStructure);        //initialize io

      //CAN unit settings
      CAN_InitStructure.CAN_TTCM = DISABLE; //non time triggered communication mode
      CAN_InitStructure.CAN_ABOM = DISABLE; //software automatic offline management
      CAN_InitStructure.CAN_AWUM = DISABLE; //Sleep mode Wake up via software (clear the SLEEP bit of CAN- > MCR)
      CAN_InitStructure.CAN_NART = ENABLE;  //prohibit automatic transmission of messages
      CAN_InitStructure.CAN_RFLM = DISABLE; //message not locked new overwrite old
      CAN_InitStructure.CAN_TXFP = DISABLE; //priority is determined by the message identifier
      CAN_InitStructure.CAN_Mode = mode;    //Mode settings: mode: 0, normal mode; 1, loop mode;
      //set baud rate
      CAN_InitStructure.CAN_SJW       = tsjw;                                   //Resynchronize jump width (Tsjw) to tsjw + 1 time unit  	 CAN_SJW_2tq CAN_SJW_3tq CAN_SJW_4tq
      CAN_InitStructure.CAN_BS1       = tbs1;                                   //Tbs1=tbs1+1 unit of timeCAN_BS1_1tq ~CAN_BS1_
      CAN_InitStructure.CAN_BS2       = tbs2;                                   //Tbs2=tbs2+1 unit of timeCAN_BS2_1tq ~	CAN_
      CAN_InitStructure.CAN_Prescaler = brp;                                    //The frequency division factor (Fdiv) is brp + 1
      CAN_Init(CAN1, &CAN_InitStructure);                                       //Initialize CAN1

      CAN_FilterInitStructure.CAN_FilterNumber         = 0;                     //filter0
      CAN_FilterInitStructure.CAN_FilterMode           = CAN_FilterMode_IdMask; //shielding mode
      CAN_FilterInitStructure.CAN_FilterScale          = CAN_FilterScale_32bit; //32 bit width
      CAN_FilterInitStructure.CAN_FilterIdHigh         = 0x0000;                //32 bit id
      CAN_FilterInitStructure.CAN_FilterIdLow          = 0x0000;
      CAN_FilterInitStructure.CAN_FilterMaskIdHigh     = 0x0000;                //32 bit mask
      CAN_FilterInitStructure.CAN_FilterMaskIdLow      = 0x0000;
      CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_Filter_FIFO0;      //filter 0 is associated with fifo0
      CAN_FilterInitStructure.CAN_FilterActivation     = ENABLE;                //activate filter 0

      CAN_FilterInit(&CAN_FilterInitStructure);                                 //filter initialization

  #if CAN_RX0_INT_ENABLE
      CAN_ITConfig(CAN1, CAN_IT_FMP0, ENABLE); //FIFO0 message registration interruption allowed.

      NVIC_InitStructure.NVIC_IRQChannel                   = USB_LP_CAN1_RX0_IRQn;
      NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; // the primary priority is 1
      NVIC_InitStructure.NVIC_IRQChannelSubPriority        = 0; // the secondary priority is 0
      NVIC_InitStructure.NVIC_IRQChannelCmd                = ENABLE;
      NVIC_Init(&NVIC_InitStructure);
  #endif
      return 0;
  }

该函数通过配置GPIOB引脚(PB8接收上拉、PB9发送复用)和CAN1控制器,设置工作模式 (含回环测试)、位时序参数(同步段、位段1/2、分频系数)以确定波特率,使用32位ID屏蔽过滤器接收所有消息, 并可选择性开启FIFO0中断(需定义CAN_RX0_INT_ENABLE),最终完成CAN总线的初始化配置,为通信做准备。

CAN数据发送

Can_Send_Msg()为CAN总线数据发送函数:

 
    uint8_t Can_Send_Msg(uint8_t *msg, uint8_t len)
  {
      uint8_t  mbox;
      uint16_t i = 0;
      CanTxMsg TxMessage;
      TxMessage.StdId = 0x12;            // standard identifier
      TxMessage.ExtId = 0x12;            // set extension identifier
      TxMessage.IDE   = CAN_Id_Standard; // standard frame
      TxMessage.RTR   = CAN_RTR_Data;    // data frame
      TxMessage.DLC   = len;             // the length of the data to be sent
      for (i = 0; i < len; i++)
          TxMessage.Data[i] = msg[i];
      mbox = CAN_Transmit(CAN1, &TxMessage);
      i    = 0;
      while ((CAN_TransmitStatus(CAN1, mbox) == CAN_TxStatus_Failed) && (i < 0XFFF)) i++; //waiting for the end of sending
      if (i >= 0XFFF) return 1;
      return 0;
  }   
              

此程序首先配置CAN帧参数(标准ID为0x12、标准数据帧、数据长度由参数指定),将待发送数据填充至发送缓冲区, 然后调用发送函数并通过邮箱状态轮询机制(最多等待0XFFF次)确认发送结果,最终返回成功(0)或失败(1)。

CAN数据接收

Can_Receive_Msg()为CAN总线数据接收函数:

 
  uint8_t Can_Receive_Msg(u8 *buf)
  {
      uint32_t i;
      CanRxMsg RxMessage;
      if (CAN_MessagePending(CAN1, CAN_FIFO0) == 0) return 0; //no data received exit directly
      CAN_Receive(CAN1, CAN_FIFO0, &RxMessage);               //read data
      for (i = 0; i < 8; i++)
          buf[i] = RxMessage.Data[i];
      return RxMessage.DLC;
  }  

通过检查CAN1的FIFO0接收缓冲区状态,若有数据则读取CAN帧信息(含ID、数据等),将有效数据(最多8字节)复制到用户缓冲区,并返回实际数据长度(DLC), 若无数据则直接返回0,实现了简洁的非阻塞式CAN数据接收功能。

串口接收

GetCmd()为USART1串口单字符接收函数:

 
  uint8_t GetCmd(void)
  {
      uint8_t tmp = 0;

      if (USART_GetFlagStatus(USART1, USART_FLAG_RXNE))
      {
          tmp = USART_ReceiveData(USART1);
      }
      return tmp;
  }  

通过检查接收缓冲区非空标志位(RXNE)判断是否有数据可读,若有则读取并返回接收到的字符(8位),若无则返回0。

主程序

main()函数为CAN总线回环测试主程序:

 
  int main(void)
  {
      uint8_t           res, i, key;
      uint8_t           canbuf[8];
      RCC_ClocksTypeDef clocks;

      delay_init();
      UART_Configuration(115200);
      RCC_GetClocksFreq(&clocks);

      printf("\n");
      printf("SYSCLK: %3.1fMhz, HCLK: %3.1fMhz, PCLK1: %3.1fMhz, PCLK2: %3.1fMhz, ADCCLK: %3.1fMhz\n",
            (float)clocks.SYSCLK_Frequency / 1000000, (float)clocks.HCLK_Frequency / 1000000,
            (float)clocks.PCLK1_Frequency / 1000000, (float)clocks.PCLK2_Frequency / 1000000, (float)clocks.ADCCLK_Frequency / 1000000);

      printf("CAN LoopBack Test.\n");

      CAN_Mode_Init(CAN_SJW_1tq, CAN_BS2_8tq, CAN_BS1_9tq, 4, CAN_Mode_LoopBack); //CAN initialize loopback mode,baud rate 500Kbps

      while (1)
      {
          if (GetCmd() == 's')
          {
              for (i = 0; i < DATA_LEN; i++)
              {
                  canbuf[i] = 0x5A + i;
              }
              res = Can_Send_Msg(canbuf, 8); //send 8 bytes
              if (res == 0)
                  printf("Can Loop Back Send Data Success\n");
              else
                  printf("Can Loop Back Send Data Fail\n");
          }

          key = Can_Receive_Msg(canbuf);
          if (key)
          {
              printf("Can Loop Back Recv Data Success\n");
              for (i = 0; i < key; i++)
              {
                  printf("canbuf[%d] = 0x%x\n", i, canbuf[i]);
              }
          }
      }
  } 

通过初始化系统时钟、UART1和CAN控制器(配置为回环模式,波特率500Kbps),在主循环中监听串口输入,当接收到“s”字符时发送8字节测试数据(0x5A~0x61), 并持续检查CAN接收缓冲区,若收到数据则打印内容,实现自收自发的通信验证。

下载验证

程序下载运行后,首先打印了系统各时钟的频率和示例名称,我们手动通过串口发送“s”之后,W55MH32便会通过CAN接口发送数据并接收来实现回环测试。 发送和接收成功后都会打印出对应的提示信息,接收成功后同时也会将接收缓冲区的内容打印出来:

Blog Image

CAN_Normal例程

CAN_Normal是CAN总线正常模式通信测试程序,与回环测试不同,它需通过物理总线与外部CAN节点通信。 该例程除了CAN初始化的CAN模式参数变成的CAN_Mode_Normal之外, CAN初始化函数内容、发送、接收函数、主程序等与CAN_LoopBack例程保持一致。改动如下:

 
  CAN_Mode_Init(CAN_SJW_1tq, CAN_BS2_8tq, CAN_BS1_9tq, 4, CAN_Mode_Normal); //baud rate 500Kbps  

下载验证

程序下载运行后,首先打印了系统各时钟的频率和示例名称。通过串口发送“s”后,W55MH32便会通过CAN接口发送数据, 发送成功后,串口打印“Can Normal Send Data Success”的信息。

Blog Image Blog Image

总结

本文聚焦W55MH32的bxCAN模块,详解CAN通信核心内容。先介绍CAN协议特性与帧结构, 再解析bxCAN的发送邮箱、接收FIFO及工作模式,接着阐述收发机制、过滤规则、错误管理和波特率配置,最后从硬件连接、 参数匹配、过滤配置等方面给出示例程序以及讲解,帮助大家理解CAN的使用。

下载本章例程

我们提供完整的工程文件以及配套开发板,方便你随时测试,快速完成产品开发:

开发环境: Keil MDK5 配套开发板