跳转至

CAN总线通信

概述

CAN(Controller Area Network,控制器局域网)是一种多主串行通信总线,广泛应用于汽车电子、工业自动化等领域。

CAN特点

  1. 多主通信:所有节点可主动发送数据
  2. 实时性强:优先级机制保证高优先级消息优先传输
  3. 可靠性高:错误检测、错误处理机制完善
  4. 抗干扰:差分信号,抗电磁干扰能力强
  5. 距离远:最大通信距离10km(5kbps)

CAN协议

标准帧格式

Text Only
帧起始(SOF) + 标识符(11bit) + RTR + IDE + r0 + DLC + 数据(0-8字节) + CRC + ACK + 帧结束

扩展帧格式

Text Only
帧起始(SOF) + 标识符(29bit) + ... + 数据(0-8字节) + CRC + ACK + 帧结束

帧类型

帧类型 说明
数据帧 携带数据信息
远程帧 请求数据
错误帧 检测到错误时发送
过载帧 请求延迟下一帧

波特率计算

Text Only
1
2
3
4
5
6
波特率 = Fpclk / (Prescaler * (1 + BS1 + BS2))

例:Fpclk = 42MHz, Prescaler = 6, BS1 = 5, BS2 = 2
波特率 = 42MHz / (6 * (1 + 5 + 2)) = 42MHz / 48 = 875kbps

常用波特率:125kbps, 250kbps, 500kbps, 1Mbps

Linux SocketCAN

配置CAN接口

Bash
# 加载CAN模块
modprobe can
modprobe can_raw
modprobe vcan    # 虚拟CAN

# 配置物理CAN接口
ip link set can0 type can bitrate 500000
ip link set can0 up

# 配置带终止符
ip link set can0 type can bitrate 500000 terminate on

# 关闭接口
ip link set can0 down

使用can-utils

Bash
# 安装工具
apt install can-utils

# 发送CAN帧
cansend can0 123#DEADBEEF          # 标准帧,ID=0x123
cansend can0 123#R                 # 远程帧
cansend can0 1F334455#11223344DEAD # 扩展帧

# 接收CAN帧
candump can0

# 监控总线
cansniffer can0

# 发送序列
cangen can0 -g 10 -I 123 -D 11223344 -L 4

# 查看统计
cat /sys/class/net/can0/statistics/*

C语言SocketCAN编程

C
#include <linux/can.h>
#include <linux/can/raw.h>
#include <sys/socket.h>
#include <net/if.h>

int can_init(const char *ifname)
{
    int s = socket(PF_CAN, SOCK_RAW, CAN_RAW);
    
    struct ifreq ifr;
    strcpy(ifr.ifr_name, ifname);
    ioctl(s, SIOCGIFINDEX, &ifr);
    
    struct sockaddr_can addr;
    memset(&addr, 0, sizeof(addr));
    addr.can_family = AF_CAN;
    addr.can_ifindex = ifr.ifr_ifindex;
    
    bind(s, (struct sockaddr *)&addr, sizeof(addr));
    
    return s;
}

// 发送CAN帧
int can_send(int s, uint32_t id, uint8_t *data, uint8_t len)
{
    struct can_frame frame;
    
    frame.can_id = id;
    frame.can_dlc = len;
    memcpy(frame.data, data, len);
    
    return write(s, &frame, sizeof(frame));
}

// 发送扩展帧
int can_send_extended(int s, uint32_t id, uint8_t *data, uint8_t len)
{
    struct can_frame frame;
    
    frame.can_id = id | CAN_EFF_FLAG;
    frame.can_dlc = len;
    memcpy(frame.data, data, len);
    
    return write(s, &frame, sizeof(frame));
}

// 接收CAN帧
int can_recv(int s, uint32_t *id, uint8_t *data, uint8_t *len)
{
    struct can_frame frame;
    
    int nbytes = read(s, &frame, sizeof(frame));
    if (nbytes < 0) return -1;
    
    *id = frame.can_id & CAN_EFF_MASK;
    *len = frame.can_dlc;
    memcpy(data, frame.data, *len);
    
    return 0;
}

// 设置过滤器
void can_set_filter(int s, uint32_t id, uint32_t mask)
{
    struct can_filter filter;
    filter.can_id = id;
    filter.can_mask = mask;
    setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, &filter, sizeof(filter));
}

STM32 CAN编程

HAL库初始化

C
#include "stm32f4xx_hal.h"

CAN_HandleTypeDef hcan1;

void CAN_Init(void)
{
    hcan1.Instance = CAN1;
    hcan1.Init.Prescaler = 6;
    hcan1.Init.Mode = CAN_MODE_NORMAL;
    hcan1.Init.SyncJumpWidth = CAN_SJW_1TQ;
    hcan1.Init.TimeSeg1 = CAN_BS1_5TQ;
    hcan1.Init.TimeSeg2 = CAN_BS2_2TQ;
    hcan1.Init.TimeTriggeredCommunicationMode = DISABLE;
    hcan1.Init.AutoBusOff = DISABLE;
    hcan1.Init.AutoWakeUp = DISABLE;
    hcan1.Init.AutoRetransmission = ENABLE;
    hcan1.Init.ReceiveFifoLocked = DISABLE;
    hcan1.Init.TransmitFifoPriority = DISABLE;
    HAL_CAN_Init(&hcan1);
}

// 配置过滤器
void CAN_Filter_Config(void)
{
    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;
    
    HAL_CAN_ConfigFilter(&hcan1, &sFilterConfig);
}

发送接收

C
// 发送CAN帧
HAL_StatusTypeDef can_send(uint32_t id, uint8_t *data, uint8_t len)
{
    CAN_TxHeaderTypeDef txHeader;
    uint32_t txMailbox;
    
    txHeader.StdId = id;
    txHeader.ExtId = 0;
    txHeader.IDE = CAN_ID_STD;
    txHeader.RTR = CAN_RTR_DATA;
    txHeader.DLC = len;
    txHeader.TransmitGlobalTime = DISABLE;
    
    return HAL_CAN_AddTxMessage(&hcan1, &txHeader, data, &txMailbox);
}

// 接收CAN帧
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
{
    CAN_RxHeaderTypeDef rxHeader;
    uint8_t rxData[8];
    
    HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &rxHeader, rxData);
    
    // 处理接收数据
    uint32_t id = rxHeader.StdId;
    // ...
}

// 启动接收
HAL_CAN_Start(&hcan1);
HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING);

CAN应用层协议

SAE J1939

重型车辆标准协议,使用29位扩展ID。

Text Only
1
2
3
4
PGN (Parameter Group Number): 18位
SA (Source Address): 8位
DA (Destination Address): 8位
Priority: 3位

CANopen

工业自动化标准协议。

C
1
2
3
4
5
6
7
8
// NMT (Network Management)
// ID: 0x000, 数据: [命令, 节点ID]

// PDO (Process Data Object)
// ID: 0x180 + 节点ID, 数据: 过程数据

// SDO (Service Data Object)
// ID: 0x600 + 节点ID (请求), 0x580 + 节点ID (响应)

OBD-II

车载诊断协议。

C
1
2
3
4
5
6
// 请求PID
uint8_t request[] = {0x02, 0x01, pid, 0x00, 0x00, 0x00, 0x00, 0x00};
can_send(0x7DF, request, 8);

// 响应ID: 0x7E8 + ECU编号
// 数据格式: [长度, 0x41, pid, 数据...]

CAN总线调试

分析工具

Bash
# 使用Wireshark分析CAN
# 安装canbus插件

# 使用canlogserver
canlogserver -l can0

# 转换为ASCII格式
log2asc logfile.log can0

# 转换为BLF格式
log2blf logfile.log output.blf

常见问题

  1. Error Active -> Error Passive -> Bus Off
  2. 检查总线终端电阻(120Ω)
  3. 检查波特率配置
  4. 检查线路连接

  5. 数据丢失

  6. 检查过滤器配置
  7. 检查接收缓冲区溢出

  8. 通信不稳定

  9. 检查终端电阻
  10. 降低波特率
  11. 检查电源和接地

参考资料