SocketCAN 编程实践
编程基础
SocketCAN 架构概述
SocketCAN 将 CAN 总线集成到 Linux 网络子系统,使得 CAN 通信可以使用标准的 Socket API 进行编程。
核心概念
Socket 类型
SocketCAN 使用 PF_CAN 协议族,支持多种 Socket 类型:
| Socket 类型 | 协议 | 用途 |
|---|---|---|
| SOCK_RAW | CAN_RAW | 原始 CAN 帧收发(最常用) |
| SOCK_DGRAM | CAN_BCM | 广播管理器(周期性发送) |
| SOCK_SEQPACKET | CAN_ISOTP | ISO-TP 协议(诊断通信) |
数据结构
CAN 2.0 帧结构:
c
#include <linux/can.h>
struct can_frame {
canid_t can_id; /* 32位CAN ID + EFF/RTR/ERR标志 */
__u8 can_dlc; /* 数据长度(0-8) */
__u8 __pad; /* 填充字节 */
__u8 __res0; /* 保留字节 */
__u8 __res1; /* 保留字节 */
__u8 data[8] __attribute__((aligned(8))); /* 数据 */
};CAN FD 帧结构:
c
#include <linux/can.h>
struct canfd_frame {
canid_t can_id; /* 32位CAN ID + EFF/RTR/ERR标志 */
__u8 len; /* 数据长度(0-64) */
__u8 flags; /* CAN FD标志(BRS, ESI) */
__u8 __res0; /* 保留字节 */
__u8 __res1; /* 保留字节 */
__u8 data[64] __attribute__((aligned(8))); /* 数据 */
};CAN ID 标志位:
c
/* CAN ID 标志位定义 */
#define CAN_EFF_FLAG 0x80000000U /* 扩展帧标志(29位ID) */
#define CAN_RTR_FLAG 0x40000000U /* 远程帧标志 */
#define CAN_ERR_FLAG 0x20000000U /* 错误帧标志 */
/* ID 掩码 */
#define CAN_SFF_MASK 0x000007FFU /* 标准帧ID掩码(11位) */
#define CAN_EFF_MASK 0x1FFFFFFFU /* 扩展帧ID掩码(29位) */开发环境配置
必需的头文件
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <linux/can.h>
#include <linux/can/raw.h>编译配置
bash
# 编译CAN程序
gcc -o can_app can_app.c
# 如果需要链接其他库
gcc -o can_app can_app.c -lpthread
# 带调试信息编译
gcc -g -o can_app can_app.c完整示例程序
示例1:基础收发程序
这是一个完整可编译运行的 CAN 收发程序,包含完整的错误处理。
c
/* can_basic.c - 基础CAN收发示例 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <linux/can.h>
#include <linux/can/raw.h>
int main(int argc, char *argv[])
{
int s; /* Socket文件描述符 */
struct sockaddr_can addr;
struct ifreq ifr;
struct can_frame frame;
int nbytes;
/* 检查参数 */
if (argc != 2) {
fprintf(stderr, "用法: %s <CAN接口名>\n", argv[0]);
fprintf(stderr, "示例: %s can0\n", argv[0]);
return 1;
}
/* 1. 创建Socket */
s = socket(PF_CAN, SOCK_RAW, CAN_RAW);
if (s < 0) {
perror("创建socket失败");
return 1;
}
/* 2. 指定CAN接口 */
strcpy(ifr.ifr_name, argv[1]);
if (ioctl(s, SIOCGIFINDEX, &ifr) < 0) {
perror("获取接口索引失败");
close(s);
return 1;
}
/* 3. 绑定Socket到CAN接口 */
memset(&addr, 0, sizeof(addr));
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("绑定socket失败");
close(s);
return 1;
}
printf("成功绑定到接口: %s\n", argv[1]);
/* 4. 发送CAN帧 */
memset(&frame, 0, sizeof(struct can_frame));
frame.can_id = 0x123; /* 标准帧ID */
frame.can_dlc = 8; /* 数据长度 */
frame.data[0] = 0x11;
frame.data[1] = 0x22;
frame.data[2] = 0x33;
frame.data[3] = 0x44;
frame.data[4] = 0x55;
frame.data[5] = 0x66;
frame.data[6] = 0x77;
frame.data[7] = 0x88;
nbytes = write(s, &frame, sizeof(struct can_frame));
if (nbytes < 0) {
perror("发送CAN帧失败");
close(s);
return 1;
}
printf("发送CAN帧: ID=0x%03X DLC=%d\n", frame.can_id, frame.can_dlc);
/* 5. 接收CAN帧 */
printf("等待接收CAN帧...\n");
nbytes = read(s, &frame, sizeof(struct can_frame));
if (nbytes < 0) {
perror("接收CAN帧失败");
close(s);
return 1;
}
/* 6. 解析并显示接收到的帧 */
printf("接收CAN帧: ID=0x%03X DLC=%d 数据: ", frame.can_id, frame.can_dlc);
for (int i = 0; i < frame.can_dlc; i++) {
printf("%02X ", frame.data[i]);
}
printf("\n");
/* 7. 关闭Socket */
close(s);
return 0;
}编译和运行:
bash
# 编译
gcc -o can_basic can_basic.c
# 运行(需要先配置can0接口)
sudo ip link set can0 type can bitrate 500000
sudo ip link set can0 up
./can_basic can0示例2:CAN FD 通信程序
支持 CAN FD 高速数据传输的完整示例。
c
/* canfd_example.c - CAN FD收发示例 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <linux/can.h>
#include <linux/can/raw.h>
int main(int argc, char *argv[])
{
int s;
struct sockaddr_can addr;
struct ifreq ifr;
struct canfd_frame frame;
int enable_canfd = 1;
int nbytes;
if (argc != 2) {
fprintf(stderr, "用法: %s <CAN接口名>\n", argv[0]);
return 1;
}
/* 创建Socket */
s = socket(PF_CAN, SOCK_RAW, CAN_RAW);
if (s < 0) {
perror("创建socket失败");
return 1;
}
/* 启用CAN FD支持 */
if (setsockopt(s, SOL_CAN_RAW, CAN_RAW_FD_FRAMES,
&enable_canfd, sizeof(enable_canfd)) < 0) {
perror("启用CAN FD失败");
close(s);
return 1;
}
/* 指定并绑定CAN接口 */
strcpy(ifr.ifr_name, argv[1]);
if (ioctl(s, SIOCGIFINDEX, &ifr) < 0) {
perror("获取接口索引失败");
close(s);
return 1;
}
memset(&addr, 0, sizeof(addr));
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("绑定socket失败");
close(s);
return 1;
}
printf("CAN FD模式已启用,绑定到接口: %s\n", argv[1]);
/* 构造CAN FD帧(64字节数据) */
memset(&frame, 0, sizeof(struct canfd_frame));
frame.can_id = 0x456;
frame.len = 64; /* CAN FD最大长度 */
frame.flags = CANFD_BRS; /* 启用比特率切换 */
/* 填充测试数据 */
for (int i = 0; i < 64; i++) {
frame.data[i] = i;
}
/* 发送CAN FD帧 */
nbytes = write(s, &frame, sizeof(struct canfd_frame));
if (nbytes < 0) {
perror("发送CAN FD帧失败");
close(s);
return 1;
}
printf("发送CAN FD帧: ID=0x%03X 长度=%d 标志=0x%02X\n",
frame.can_id, frame.len, frame.flags);
/* 接收CAN FD帧 */
printf("等待接收CAN FD帧...\n");
nbytes = read(s, &frame, sizeof(struct canfd_frame));
if (nbytes < 0) {
perror("接收失败");
close(s);
return 1;
}
/* 显示接收到的数据 */
printf("接收CAN FD帧: ID=0x%03X 长度=%d\n", frame.can_id, frame.len);
printf("数据: ");
for (int i = 0; i < frame.len; i++) {
printf("%02X ", frame.data[i]);
if ((i + 1) % 16 == 0) printf("\n ");
}
printf("\n");
close(s);
return 0;
}CAN FD 配置和运行:
bash
# 编译
gcc -o canfd_example canfd_example.c
# 配置CAN FD接口
sudo ip link set can0 type can bitrate 1000000 dbitrate 5000000 fd on
sudo ip link set can0 up
# 运行
./canfd_example can0参考资源
官方文档
开源项目
can-utils:SocketCAN 官方工具集,优秀的代码参考 https://gitee.com/ChengDu-KunHong/can-utils
openarmcan:包含电机控制应用示例 https://gitee.com/ChengDu-KunHong/openarmcan