当前位置: 代码网 > 服务器>网络>网络协议 > 【物联网】深入理解CAN通信:原理、应用和实现(超详细,万字警告)

【物联网】深入理解CAN通信:原理、应用和实现(超详细,万字警告)

2024年08月02日 网络协议 我要评论
CAN通信是一种多节点通信协议,最早由Bosch公司开发并在1986年首次推出。它被广泛应用于汽车电子控制系统、工业自动化领域以及其他需求多节点通信的应用场景中。CAN通信的特点之一是支持多节点之间的高速数据传输,适用于需要高实时性和高可靠性的系统。CAN总线由两根线组成,分别是CAN_H(CAN High)和CAN_L(CAN Low)。CAN总线使用不同的电压电平来表示0和1,并通过差分信号传输来抗干扰。

1. 什么是can通信?

can通信是一种多节点通信协议,最早由bosch公司开发并在1986年首次推出。它被广泛应用于汽车电子控制系统、工业自动化领域以及其他需求多节点通信的应用场景中。

can通信的特点之一是支持多节点之间的高速数据传输,适用于需要高实时性和高可靠性的系统。can总线由两根线组成,分别是can_h(can high)和can_l(can low)。can总线使用不同的电压电平来表示0和1,并通过差分信号传输来抗干扰。

2. can通信原理

can通信采用csma/cd(carrier sense multiple access with collision detection)的工作原理。简单来说,这意味着每个节点都可以在总线上发送消息,但在发送之前需要先监听总线上的通信情况。

当一个节点要发送消息时,首先会监听总线,如果没有其他节点正在发送消息,它就可以开始发送。如果同时有多个节点尝试发送消息,就会发生冲突。在can总线上使用的是非毁坏性冲突检测机制,冲突的节点会立即停止发送,并在发送完自己的消息后再次来检测冲突。

can通信中还使用了位定时传输方式,即总线上的每个位都有固定的时间段。发送节点将每个位的电平保持一段时间,接收节点则在相应的时间段内检测位的电平。这种位定时传输方式确保了数据的同步和准确性。

此外,can通信还通过帧的优先级来管理消息的传输。较低优先级的帧会在总线上等待较高优先级的帧发送完毕后再发送,确保重要消息的及时传输。

3. can通信的应用领域

can通信被广泛应用于各种领域,特别是在汽车和工业控制系统中。

在汽车领域,can通信用于连接汽车的各个控制单元,如发动机控制单元(ecu)、刹车系统、仪表盘等。can总线提供了高速、实时的数据传输,使得这些控制单元能够相互通信和协调工作,实现车辆的高效控制和监测。

在工业控制系统中,can通信被用于连接各种设备和传感器,例如机器人、plc(可编程逻辑控制器)、传感器网络等。通过can总线,这些设备可以实现实时数据交换和远程控制,从而提高生产效率和系统的可靠性。

除了汽车和工业控制,can通信还应用于其他领域,包括航空航天、医疗设备、能源管理等。can通信的高可靠性和抗干扰能力使其成为处理实时数据和多节点通信的理想选择。

4. can帧格式与标识符

can通信使用帧格式来传输数据。can帧分为标准帧扩展帧两种格式。

标准帧由11位标识符、数据域、控制域和crc(循环冗余校验)组成。标识符用于标识消息的优先级和内容,数据域用于传输实际的数据,控制域包含帧的控制信息,而crc用于发送节点计算校验和,接收节点用于验证数据的完整性。

扩展帧使用29位标识符,其他组成部分与标准帧相同。扩展帧的使用使得can网络能够处理更多的节点和更大的数据量。

标识符的选择对于can通信至关重要。帧使用的标识符决定了其在总线上的优先级,较低的标识符意味着较高的优先级。在设计和配置can网络时,需要合理设置标识符以确保系统的正确运行。

5. can通信管理和控制

can通信的管理和控制涉及多个方面,包括位定时、通信模式、错误处理和故障状态等。

5.1 can总线位定时(bit timing)

在can通信中,位定时是指将每个can总线的位划分为不同的时间段,并将位的电平保持一段时间。位定时可以通过设置同步段(sync segment)、传播段(propagation segment)和相位段(phase segment)来实现。

同步段用于确保总线上的节点能够在每个位开始时达到同步。传播段定义了信号在总线上传播的时间,而相位段用于确定位值的边界。

位定时的设置很关键,它直接影响到通信的可靠性和性能。在系统设计中,需要根据总线的特性和系统要求合理地设置位定时参数,以确保数据的正确传输。

5.2 can通信模式

can通信有几种不同的模式,可以通过配置can控制器的寄存器来选择适合应用需求的模式。

常见的can通信模式包括:

  • 正常模式(normal mode):用于实际通信,节点能够发送和接收数据。
    -** 监听模式(listen-only mode)**:节点只能监听总线上的通信,但不能发送消息。
  • 回环模式(loopback mode):发送的帧会回环到本地接收,用于自测和调试。
  • 静默模式(silent mode):节点只能监听总线上的通信,不能发送消息,并向其他节点传递错误状态。

5.3 can错误处理与故障状态

在can通信中,可能会出现一些错误情况,例如位错误、格式错误、crc错误、接收溢出等。can控制器会检测这些错误,并根据具体的错误类型生成相应的错误码。

在错误处理中,可以通过读取错误状态寄存器来获取错误码和错误类型。常见的错误处理方法包括:

  • 重传机制:如果发送的帧在总线上出现错误,发送节点可以重传帧以确保数据的正确传输。
  • 错误状态清除:将错误计数器重置为0,清除错误状态以使节点恢复正常通信。
  • 错误管理器(error management):通过协议定义的错误管理器对错误进行处理和控制,例如决定是否启用自动重传。
  • 错误标志位(error flag):通过检查错误标志位确定是否有错误发生,并根据需要采取相应的处理措施。

6. 使用hal库实现can通信

对于使用hal(hardware abstraction layer)库进行stm32开发的用户,hal库提供了高级的抽象接口和简洁的函数调用,方便编写和管理can通信。

6.1 stm32 can通信配置:

a. 使能can外设:在初始化阶段,需要使能所选择的can外设,并配置相应的时钟。

b. 配置can控制器:通过设置can控制器的特殊寄存器,配置can通信的参数,包括波特率、工作模式、过滤器等。

c. 配置gpio引脚:将相关的gpio引脚配置为can模式,以实现can数据的收发。

d. 初始化can通信:通过初始化can控制器的寄存器,准备can通信的环境,包括清除任何悬空状态和错误标志。

e. 开始can通信:使能can控制器的接收和发送功能,使其处于工作状态。

6.2 步骤的代码实现

  1. 包含所需的头文件:

    #include "stm32f4xx_hal.h"
    #include "stm32f4xx_hal_can.h"
    
  2. 定义can_handletypedef结构体和can消息结构体:

    can_handletypedef hcan;
    can_txheadertypedef txheader;
    can_rxheadertypedef rxheader;
    uint8_t txdata[8];
    uint8_t rxdata[8];
    
  3. 配置can模式和参数:

    hcan.instance = can1;
    hcan.init.mode = can_mode_normal;
    hcan.init.autobusoff = enable;
    hcan.init.autoretransmission = enable;
    hcan.init.autowakeup = disable;
    hcan.init.receivefifolocked = disable;
    hcan.init.timetriggeredmode = disable;
    hcan.init.transmitfifopriority = disable;
    hcan.init.prescaler = 10;
    hcan.init.syncjumpwidth = can_sjw_1tq;
    hcan.init.timeseg1 = can_bs1_8tq;
    hcan.init.timeseg2 = can_bs2_7tq;
    
    if (hal_can_init(&hcan) != hal_ok) {
        // 错误处理
    }
    
  4. 配置can过滤器:

    can_filtertypedef sfilterconfig;
    sfilterconfig.filterbank = 0;
    sfilterconfig.filtermode = can_filtermode_idmask;
    sfilterconfig.filterscale = can_filterscale_32bit;
    sfilterconfig.filteridhigh = 0x0000;
    sfilterconfig.filteridlow = 0x0000;
    sfilterconfig.filtermaskidhigh = 0x0000;
    sfilterconfig.filtermaskidlow = 0x0000;
    sfilterconfig.filterfifoassignment = can_rx_fifo0;
    sfilterconfig.filteractivation = enable;
    sfilterconfig.slavestartfilterbank = 0;
    
    if (hal_can_configfilter(&hcan, &sfilterconfig) != hal_ok) {
        // 错误处理
    }
    
  5. 启动can:

    if (hal_can_start(&hcan) != hal_ok) {
        // 错误处理
    }
    
  6. 发送和接收数据:

    if (hal_can_addtxmessage(&hcan, &txheader, txdata, &txmailbox) != hal_ok) {
        // 错误处理
    }
    
    if (hal_can_getrxmessage(&hcan, can_rx_fifo0, &rxheader, rxdata) != hal_ok) {
        // 错误处理
    }
    

6.3 实现的整体代码

// 包含所需的头文件
#include "stm32f4xx_hal.h"

// can消息结构体
can_txheadertypedef txheader;
can_rxheadertypedef rxheader;
uint8_t txdata[8];
uint8_t rxdata[8];

void can_configuration(void)
{
  // 初始化can控制器
  hcan.instance = can1;
  hcan.init.prescaler = 10;
  hcan.init.mode = can_mode_normal;
  hcan.init.syncjumpwidth = can_sjw_1tq;
  hcan.init.timeseg1 = can_bs1_8tq;
  hcan.init.timeseg2 = can_bs2_7tq;
  hcan.init.timetriggeredmode = disable;
  hcan.init.autobusoff = enable;
  hcan.init.autowakeup = disable;
  hcan.init.autoretransmission = enable;
  hcan.init.receivefifolocked = disable;
  hal_can_init(&hcan);

  // 配置can过滤器
  can_filtertypedef sfilterconfig;
  sfilterconfig.filterbank = 0;
  sfilterconfig.filtermode = can_filtermode_idmask;
  sfilterconfig.filterscale = can_filterscale_32bit;
  sfilterconfig.filteridhigh = 0x0000;
  sfilterconfig.filteridlow = 0x0000;
  sfilterconfig.filtermaskidhigh = 0x0000;
  sfilterconfig.filtermaskidlow = 0x0000;
  sfilterconfig.filter
继续
maskidhigh = 0x0000;
sfilterconfig.filtermaskidlow = 0x0000;
sfilterconfig.filterfifoassignment = can_filter_fifo0;
sfilterconfig.filteractivation = enable;
sfilterconfig.slavestartfilterbank = 14;
hal_can_configfilter(&hcan, &sfilterconfig);

// 启动can通信
hal_can_start(&hcan);

// 配置can消息头
txheader.stdid = 0x123;
txheader.extid = 0x00;
txheader.rtr = can_rtr_data;
txheader.ide = can_id_std;
txheader.dlc = 8;
txheader.transmitglobaltime = disable;
}

void can_senddata(uint8_t* data, uint32_t length)
{
// 填充发送消息的数据
for (uint8_t i = 0; i < length; i++) {
txdata[i] = data[i];
}

// 发送消息
if (hal_can_addtxmessage(&hcan, &txheader, txdata, &txmailbox) == hal_ok) {
hal_can_transmit(&hcan, 100);
}
}

void can_receivedata(void)
{
// 接收消息
if (hal_can_getrxfifofilllevel(&hcan, can_rx_fifo0) > 0) {
if (hal_can_getrxmessage(&hcan, can_rx_fifo0, &rxheader, rxdata) == hal_ok) {
// 处理接收到的数据
// …
}
}
}

int main(void)
{
// 初始化hal库和其他外设

// 配置can通信
can_configuration();

while (1) {
// 发送数据
uint8_t senddata[] = {0x01, 0x02, 0x03};
can_senddata(senddata, sizeof(senddata));

// 接收数据
can_receivedata();
}
}

以上示例代码演示了一个简单的can发送和接收流程。首先,在can_configuration函数中,配置can控制器和过滤器。然后,在主函数中,使用can_senddata函数发送数据,使用can_receivedata函数接收数据。

此外,为了调试和监测can通信,hal库提供了一些有用的函数和工具。例如,可以使用hal_can_getstate()函数获取can控制器的当前状态,包括初始化、准备就绪、发送中、传输完成等。还可以使用hal_can_geterrorcounter()函数获取错误计数器的值,以及使用hal_can_getrxmessage()函数获取接收到的can帧的相关信息。

7. 使用标准库实现can通信

除了hal库,还可以使用标准库进行can通信的实现。标准库相对较底层,需要直接操作寄存器和位操作来配置和控制can控制器。

与hal库类似,使用标准库进行can通信需要进行can总线的初始化、过滤器的配置、数据的发送和接收等操作。不同之处在于,需要直接操作寄存器来完成这些任务。

7.1 实现步骤

  • 配置gpio端口和引脚,用于连接can总线。
  • 配置can控制器的工作模式、波特率、位定时参数等。
  • 配置can过滤器,设置过滤器的标识符、执行类型等。
  • 使用can发送函数将数据发送到总线上。
  • 使用can接收函数接收来自其他节点的数据。

7.2 步骤的代码实现

当使用标准库实现stm32的can通信时,需要按照以下步骤进行配置和操作:

  1. 配置can1时钟:

    rcc_apb1periphclockcmd(rcc_apb1periph_can1, enable);
    
  2. 初始化can1:

    can_inittypedef can_initstructure;
    can_initstructure.can_ttcm = disable;
    can_initstructure.can_abom = disable;
    can_initstructure.can_awum = disable;
    can_initstructure.can_nart = disable;
    can_initstructure.can_rflm = disable;
    can_initstructure.can_txfp = disable;
    can_initstructure.can_mode = can_mode_normal;
    can_initstructure.can_sjw = can_sjw_1tq;
    can_initstructure.can_bs1 = can_bs1_8tq;
    can_initstructure.can_bs2 = can_bs2_7tq;
    can_initstructure.can_prescaler = 10;
    can_init(can1, &can_initstructure);
    
  3. 配置can过滤器:

    can_filterinittypedef can_filterinitstructure;
    can_filterinitstructure.can_filternumber = 0;
    can_filterinitstructure.can_filtermode = can_filtermode_idmask;
    can_filterinitstructure.can_filterscale = can_filterscale_32bit;
    can_filterinitstructure.can_filteridhigh = 0x0000;
    can_filterinitstructure.can_filteridlow = 0x0000;
    can_filterinitstructure.can_filtermaskidhigh = 0x0000;
    can_filterinitstructure.can_filtermaskidlow = 0x0000;
    can_filterinitstructure.can_filterfifoassignment = can_fifo0;
    can_filterinitstructure.can_filteractivation = enable;
    can_filterinit(&can_filterinitstructure);
    
  4. 使能can1接收中断:

    nvic_inittypedef nvic_initstructure;
    nvic_initstructure.nvic_irqchannel = can1_rx0_irqn;
    nvic_initstructure.nvic_irqchannelpreemptionpriority = 0;
    nvic_initstructure.nvic_irqchannelsubpriority = 0;
    nvic_initstructure.nvic_irqchannelcmd = enable;
    nvic_init(&nvic_initstructure);
    can_itconfig(can1, can_it_fmp0, enable);
    
  5. 实现can1接收中断处理函数:

    void can1_rx0_irqhandler(void) {
      if (can_getitstatus(can1, can_it_fmp0) != reset) {
        can_receive(can1, can_fifo0, &rxmessage);
        // 处理接收到的消息
      }
    }
    
  6. 编写can发送函数:

    void can_senddata(uint8_t* data, uint8_t length) {
      // 填充发送消息的数据
      for (uint8_t i = 0; i < length; i++) {
        txmessage.data[i] = data[i];
      }
    
      // 设置发送消息的参数
      txmessage.stdid = 0x123;
      txmessage.extid = 0x00;
      txmessage.ide = can_id_standard;
      txmessage.rtr = can_rtr_data;
      txmessage.dlc = length;
    
      // 发送消息
      uint8_t mailbox;
      can_transmit(can1, &txmessage);
    }
    
  7. 在主函数中调用相关函数:

    int main(void) {
      // 初始化can配置
      can_configuration();
    
      while ((can1->msr & can_msr_inak) == can_msr_inak) {
        // 等待can1退出初始化模式
      }
    
      while (1) {
        // 发送数据
        uint8_t senddata[] = {0x01, 0x02, 0x03};
        can_senddata(senddata, sizeof(senddata));
    
        // 延时一段时间
        for (volatile uint32_t i = 0; i < 1000000; i++) {}
      }
    }
    

7.3 实现的整体代码

#include "stm32f4xx.h"

// can消息结构体
cantxmsgtypedef txmessage;
canrxmsgtypedef rxmessage;

void can_configuration(void) {
  // 使能can1时钟
  rcc_apb1periphclockcmd(rcc_apb1periph_can1, enable);

  // can1初始化
  can_inittypedef can_initstructure;
  can_initstructure.can_ttcm = disable;
  can_initstructure.can_abom = disable;
  can_initstructure.can_awum = disable;
  can_initstructure.can_nart = disable;
  can_initstructure.can_rflm = disable;
  can_initstructure.can_txfp = disable;
  can_initstructure.can_mode = can_mode_normal;
  can_initstructure.can_sjw = can_sjw_1tq;
  can_initstructure.can_bs1 = can_bs1_8tq;
  can_initstructure.can_bs2 = can_bs2_7tq;
  can_initstructure.can_prescaler = 10;
  can_init(can1, &can_initstructure);

  // can过滤器配置
  can_filterinittypedef can_filterinitstructure;
  can_filterinitstructure.can_filternumber = 0;
  can_filterinitstructure.can_filtermode = can_filtermode_idmask;
  can_filterinitstructure.can_filterscale = can_filterscale_32bit;
  can_filterinitstructure.can_filteridhigh = 0x0000;
  can_filterinitstructure.can_filteridlow = 0x0000;
  can_filterinitstructure.can_filtermaskidhigh = 0x0000;
  can_filterinitstructure.can_filtermaskidlow = 0x0000;
  can_filterinitstructure.can_filterfifoassignment = can_fifo0;
  can_filterinitstructure.can_filteractivation = enable;
  can_filterinit(&can_filterinitstructure);

  // can接收中断使能
  can_itconfig(can1, can_it_fmp0, enable);

  // 使能can1
  can_cmd(can1, enable);

  // nvic中断配置
  nvic_inittypedef nvic_initstructure;
  nvic_initstructure.nvic_irqchannel = can1_rx0_irqn;
  nvic_initstructure.nvic_irqchannelpreemptionpriority = 0;
  nvic_initstructure.nvic_irqchannelsubpriority = 0;
  nvic_initstructure.nvic_irqchannelcmd = enable;
  nvic_init(&nvic_initstructure);
}

void can_senddata(uint8_t* data, uint8_t length) {
  // 填充发送消息的数据
  for (uint8_t i = 0; i < length; i++) {
    txmessage.data[i] = data[i];
  }

  // 设置发送消息的参数
  txmessage.stdid = 0x123;
  txmessage.extid = 0x00;
  txmessage.ide = can_id_standard;
  txmessage.rtr = can_rtr_data;
  txmessage.dlc = length;

  // 发送消息
  uint8_t transmissionmailbox = 0;
  can_transmit(can1, &txmessage);
}

void can1_rx0_irqhandler(void) {
  if (can_getitstatus(can1, can_it_fmp0) != reset) {
    // 接收到消息
    can_receive(can1, can_fifo0, &rxmessage);

    // 处理接收到的消息
    // ...
  }
}

int main(void) {
  // 初始化can配置
  can_configuration();

  while ((can1->msr & can_msr_inak) == can_msr_inak) {
    // 等待can1退出初始化模式
  }

  while (1) {
    // 发送数据
    uint8_t senddata[] = {0x01, 0x02, 0x03};
    can_senddata(senddata, sizeof(senddata));

    // 延时一段时间
    for (volatile uint32_t i = 0; i < 1000000; i++) {}
  }
}

8. can通信实际案例分析

在汽车电控系统中,can通信被广泛应用于各个控制单元之间的数据传输。例如,在引擎控制单元(ecu)中,通过can总线与刹车系统的控制单元、仪表盘的控制单元进行通信。引擎控制单元可以向刹车系统发送刹车指令,同时获取仪表盘上的车速和引擎转速等信息。

在工业控制系统中,can通信常用于连接不同的设备和传感器,以实现实时数据交换和协调工作。例如,在机器人系统中,通过can总线连接各个关节控制器和传感器,实现机器人的协调运动和数据传输。

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com