网易首页 > 网易号 > 正文 申请入驻

CAN通信与定时器在Linux系统中的C++实践

0
分享至


1. CAN通信概述

CAN(Controller Area Network)是一种多主、广播式的串行通信总线,广泛应用于汽车电子、工业控制等领域。CAN总线具有高可靠性、实时性强、抗干扰能力突出等特点,其数据帧结构如下:

  • 标识符(ID) :11位(标准帧)或29位(扩展帧),决定消息优先级。

  • 数据长度码(DLC) :0~8字节,表示数据段长度。

  • 数据段 :最多8字节的有效载荷。

  • CRC场 :15位循环冗余校验,保证数据完整性。

在Linux系统中,通常通过SocketCAN(内核子系统)访问CAN总线。SocketCAN提供标准的BSD Socket API,使得CAN设备可以像普通网络设备一样被操作。

2. Linux SocketCAN编程基础

SocketCAN的核心数据结构是struct can_frame(定义在linux/can.h):

struct can_frame {
canid_t can_id; // CAN标识符,低29位有效,最高位标记扩展帧/远程帧
__u8 can_dlc; // 数据长度(0~8)
__u8 data[8]; // 数据载荷
};

基本操作流程:

  1. 创建socket: socket(PF_CAN, SOCK_RAW, CAN_RAW)

  2. 指定CAN接口(如 can0 )并绑定: bind(sockfd, (struct sockaddr*)&addr, sizeof(addr))

  3. 发送/接收 can_frame : write / sendto 或 read / recvfrom

  4. 可设置过滤器( struct can_filter )以接收特定ID的帧。

3. 定时器在CAN通信中的典型应用

CAN通信往往需要周期性操作或超时控制:

  • 周期性发送 :例如以固定间隔发送状态帧、心跳帧或传感器指令。

  • 接收超时 :等待预期应答帧时,若超过时间未收到则重发或报错。

  • 轮询与事件解耦 :使用定时器触发非阻塞检查,避免忙等待。

Linux下常用的高精度定时器方案:

  • timerfd :将定时器事件转化为文件描述符,可集成到 epoll / select 循环中。

  • POSIX timer : timer_create + 信号或回调,但信号处理复杂。

  • sleep/usleep/ nanosleep :精度低或阻塞线程,适合简单场景。

本文采用timerfd + epoll的事件驱动模型,实现单线程同时处理CAN接收与周期性发送,具有良好的实时性和可扩展性。

4. 完整C++示例:周期性CAN发送与实时接收 4.1 代码结构

  • 初始化CAN套接字(虚拟接口vcan0或物理接口)。

  • 创建timerfd,设置周期为500ms。

  • 使用 epoll 同时监听CAN socket和timerfd。

  • timerfd到期时,构造并发送一条CAN帧(ID=0x123,数据字节递增)。

  • CAN socket可读时,读取并打印接收到的所有帧。

4.2 代码实现

#include  
          
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include

class CanTimerDemo {
public:
CanTimerDemo(const std::string& ifname, int period_ms)
: ifname_(ifname), period_ms_(period_ms), can_sock_(-1), timer_fd_(-1), epoll_fd_(-1) {}

~CanTimerDemo() {
if (can_sock_ >= 0) close(can_sock_);
if (timer_fd_ >= 0) close(timer_fd_);
if (epoll_fd_ >= 0) close(epoll_fd_);
}

bool initialize() {
// 1. 创建CAN socket
can_sock_ = socket(PF_CAN, SOCK_RAW, CAN_RAW);
if (can_sock_ < 0) {
perror("socket");
return false;
}

// 2. 绑定到指定CAN接口
struct ifreq ifr;
strcpy(ifr.ifr_name, ifname_.c_str());
if (ioctl(can_sock_, SIOCGIFINDEX, &ifr) < 0) {
perror("ioctl");
return false;
}

struct sockaddr_can addr;
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
if (bind(can_sock_, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
perror("bind");
return false;
}

// 可选:设置非阻塞,便于epoll处理
int flags = fcntl(can_sock_, F_GETFL, 0);
fcntl(can_sock_, F_SETFL, flags | O_NONBLOCK);

// 3. 创建timerfd
timer_fd_ = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK);
if (timer_fd_ < 0) {
perror("timerfd_create");
return false;
}

struct itimerspec timer_spec;
timer_spec.it_value.tv_sec = period_ms_ / 1000;
timer_spec.it_value.tv_nsec = (period_ms_ % 1000) * 1000000;
timer_spec.it_interval = timer_spec.it_value; // 周期触发
if (timerfd_settime(timer_fd_, 0, &timer_spec, nullptr) < 0) {
perror("timerfd_settime");
return false;
}

// 4. 创建epoll并注册两个fd
epoll_fd_ = epoll_create1(0);
if (epoll_fd_ < 0) {
perror("epoll_create1");
return false;
}

struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = can_sock_;
if (epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, can_sock_, &ev) < 0) {
perror("epoll_ctl: can_sock");
return false;
}

ev.events = EPOLLIN;
ev.data.fd = timer_fd_;
if (epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, timer_fd_, &ev) < 0) {
perror("epoll_ctl: timer_fd");
return false;
}

return true;
}

void run() {
const int MAX_EVENTS = 4;
struct epoll_event events[MAX_EVENTS];
uint8_t send_counter = 0;

std::cout << "Starting CAN + Timer demo on " << ifname_ << ", period = "
<< period_ms_ << " ms" << std::endl;

while (true) {
int nfds = epoll_wait(epoll_fd_, events, MAX_EVENTS, -1);
for (int i = 0; i < nfds; ++i) {
if (events[i].data.fd == can_sock_) {
// 接收CAN帧(可能多个,循环读取直到EAGAIN)
struct can_frame frame;
ssize_t nbytes = read(can_sock_, &frame, sizeof(frame));
if (nbytes == sizeof(frame)) {
printf("Rx: ID=0x%03X, DLC=%d, Data=", frame.can_id, frame.can_dlc);
for (int j = 0; j < frame.can_dlc; ++j)
printf("%02X ", frame.data[j]);
printf("\n");
} else if (nbytes < 0 && errno != EAGAIN) {
perror("read CAN");
}
}
else if (events[i].data.fd == timer_fd_) {
// 定时器到期,清除过期计数(必须读)
uint64_t expirations;
ssize_t s = read(timer_fd_, &expirations, sizeof(expirations));
if (s != sizeof(expirations)) {
perror("read timerfd");
continue;
}

// 构造并发送CAN帧
struct can_frame tx_frame;
tx_frame.can_id = 0x123; // 标准帧ID
tx_frame.can_dlc = 8;
for (int i = 0; i < 8; ++i)
tx_frame.data[i] = send_counter + i;
send_counter++;

ssize_t nbytes = write(can_sock_, &tx_frame, sizeof(tx_frame));
if (nbytes != sizeof(tx_frame)) {
perror("write CAN");
} else {
printf("Tx: ID=0x%03X, DLC=%d, Data=", tx_frame.can_id, tx_frame.can_dlc);
for (int i = 0; i < tx_frame.can_dlc; ++i)
printf("%02X ", tx_frame.data[i]);
printf("\n");
}
}
}
}
}

private:
std::string ifname_;
int period_ms_;
int can_sock_;
int timer_fd_;
int epoll_fd_;
};

int main(int argc, char* argv[]) {
std::string can_interface = "vcan0"; // 可改为实际接口,如can0
int period_ms = 500; // 发送周期500ms

if (argc >= 2) can_interface = argv[1];
if (argc >= 3) period_ms = std::stoi(argv[2]);

CanTimerDemo demo(can_interface, period_ms);
if (!demo.initialize()) {
std::cerr << "Initialization failed" << std::endl;
return 1;
}
demo.run();
return 0;
}
4.3 编译与运行说明

编译命令(Linux):

g++ -std=c++11 -o can_timer_demo can_timer_demo.cpp

准备虚拟CAN接口(用于测试,无需硬件):

sudo modprobe vcan
sudo ip link add dev vcan0 type vcan
sudo ip link set vcan0 up

运行:

# 使用vcan0接口,周期500ms
./can_timer_demo vcan0 500

测试接收:在另一终端开启candump监听:

candump vcan0

您将看到每500ms发送一帧ID=0x123的数据,且数据字节递增。

5. 关键点解析

  • 非阻塞+epoll : timerfd 和 can_sock 均设为非阻塞,避免了读操作阻塞线程,保证了事件驱动的实时性。

  • 定时器精度 : timerfd 基于高精度时钟(CLOCK_MONOTONIC),周期可达纳秒级,但受系统调度影响,实际周期误差通常在微秒到毫秒级。

  • CAN过滤器 :本示例未设置过滤器,接收所有CAN帧。若需只接收特定ID,可使用 setsockopt 设置 can_filter 数组,显著降低处理负载。

  • 多线程替代方案 :也可以创建独立线程进行周期性发送,但使用 epoll 单线程模型避免了锁竞争,代码更简洁。

  • 错误处理 :示例中简化了持续运行的错误恢复,实际生产环境应区分可恢复错误(如EAGAIN)与致命错误。

6. 高级话题:接收超时实现

基于上述模型,可以轻松实现接收超时:增加一个额外的timerfd作为超时定时器。例如,在发送请求帧后启动一个短周期定时器,若超时未收到应答则触发重发。伪代码思路:

  • 维护一个待应答状态和超时定时器。

  • 每次发送请求时,重新设置超时定时器。

  • 当超时定时器到期时,检查是否已收到应答,否则执行重发逻辑。

7. 总结

本文深入讲解了CAN通信在Linux下的SocketCAN编程模型,并重点阐述了timerfd + epoll如何优雅地实现周期性发送与事件驱动接收。提供的完整C++示例可直接在虚拟CAN接口上运行验证,为实际项目中集成CAN通信和定时任务提供了清晰的参考范式。对于更复杂的场景(如多周期任务、动态定时器调整),可基于本模型进一步扩展。

注:运行示例需要Linux内核支持SocketCAN(内核版本≥2.6.25)以及虚拟CAN模块(vcan)。实际使用物理CAN接口时,请确保接线正确并配置比特率等参数。

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
养老金“特权设计”被骂上热搜,郑秉文用双轨制特权?真相不简单

养老金“特权设计”被骂上热搜,郑秉文用双轨制特权?真相不简单

青梅侃史啊
2026-07-30 22:02:45
日媒称孙颖莎狂热粉丝“攻陷”亚运会,日本球迷:饭圈正在削弱国乒

日媒称孙颖莎狂热粉丝“攻陷”亚运会,日本球迷:饭圈正在削弱国乒

新杀猪的秀才
2026-09-27 23:07:25
美股历史表现最差的月份竟然是...

美股历史表现最差的月份竟然是...

格隆汇
2026-09-02 11:45:15
美国中将公开承认:打完伊朗,我才看清中国到底有多强大!

美国中将公开承认:打完伊朗,我才看清中国到底有多强大!

果妈聊娱乐
2026-09-17 08:20:56
八年婚姻尘埃落定,杨幂暗藏通透格局,为51岁未再婚刘恺威留后路

八年婚姻尘埃落定,杨幂暗藏通透格局,为51岁未再婚刘恺威留后路

阿绐聊社会
2026-09-24 19:47:12
19岁中国速滑新星被冰刀割断大动脉,左腿截肢后首发声,网友泪目

19岁中国速滑新星被冰刀割断大动脉,左腿截肢后首发声,网友泪目

星娱叨叨社
2026-08-25 15:16:56
打赏陪睡案女主播合租室友发声:魏莹每月陪睡一两次,枕头没干过,经常哭到缺氧

打赏陪睡案女主播合租室友发声:魏莹每月陪睡一两次,枕头没干过,经常哭到缺氧

世界圈
2026-08-24 20:54:42
张本智和暴跳如雷!输国乒不丢人,真正崩盘的原因,全在队友身上

张本智和暴跳如雷!输国乒不丢人,真正崩盘的原因,全在队友身上

烟雨洛神生
2026-09-28 02:46:20
浪子回头?高市早苗一反常态,对中国称呼再变,彻底摊牌立场!

浪子回头?高市早苗一反常态,对中国称呼再变,彻底摊牌立场!

菁菁子衿
2026-08-01 10:53:25
凯恩:支撑脚打滑很倒霉,就算罚进也会因二次触球被判无效

凯恩:支撑脚打滑很倒霉,就算罚进也会因二次触球被判无效

懂球帝
2026-09-27 06:27:44
许晴独居北京大平层57岁未婚,家里请了保姆,一个人早餐吃10道菜

许晴独居北京大平层57岁未婚,家里请了保姆,一个人早餐吃10道菜

椰黄娱乐
2026-09-16 10:20:02
出轨率第一的亚洲小国,红灯区比日本还泛滥,号称“男人的天堂”

出轨率第一的亚洲小国,红灯区比日本还泛滥,号称“男人的天堂”

大鱼简科
2026-09-20 09:39:18
小心!欧洲被打服了,中东也不闹了,美国的目标只剩下东亚

小心!欧洲被打服了,中东也不闹了,美国的目标只剩下东亚

西府赵王爷
2025-03-22 15:47:18
为何中国宁愿把电力输向越南,让越南夜晚灯火辉煌,却对朝鲜几乎绝不供电?这事看着奇怪,其实道理很简单!

为何中国宁愿把电力输向越南,让越南夜晚灯火辉煌,却对朝鲜几乎绝不供电?这事看着奇怪,其实道理很简单!

磊子讲史
2026-08-10 15:13:10
林诗栋vs林昀儒时间已定!CCTV5直播,手握三大优势进决赛

林诗栋vs林昀儒时间已定!CCTV5直播,手握三大优势进决赛

潇湘烟雨水
2026-09-27 09:37:56
谁能执掌中国篮球?三大热门人选浮出水面,优缺点一目了然

谁能执掌中国篮球?三大热门人选浮出水面,优缺点一目了然

体育见习官
2026-09-27 19:57:08
刘欢中秋节病逝!父母曾反对他与卢璐结婚,他生前有一大遗憾

刘欢中秋节病逝!父母曾反对他与卢璐结婚,他生前有一大遗憾

情感大头说说
2026-09-27 18:11:59
冠军梦被击碎!张本智和最后一局为何输成1-11?林诗栋说得很霸气

冠军梦被击碎!张本智和最后一局为何输成1-11?林诗栋说得很霸气

十点街球体育
2026-09-27 22:40:04
湄公河惨案首犯糯康吐露内情:金三角毒枭不怕云南武警手中的枪,他们心底真正恐惧的,是一道尘封三十年、无人敢触碰的禁令

湄公河惨案首犯糯康吐露内情:金三角毒枭不怕云南武警手中的枪,他们心底真正恐惧的,是一道尘封三十年、无人敢触碰的禁令

磊子讲史
2026-07-09 11:21:42
10月1日开始,银行有定期存款的,要做好两手准备!

10月1日开始,银行有定期存款的,要做好两手准备!

爱看剧的阿峰
2026-09-27 13:42:10
2026-09-28 04:08:49
新能源自动驾驶 incentive-icons
新能源自动驾驶
专注于半导体行业资讯
1015文章数 348关注度
往期回顾 全部

科技要闻

星舰第14飞,为什么准备绕地球六圈?

头条要闻

孙颖莎2比4不敌王曼昱摘银 称可能等不到下届亚运会了

头条要闻

孙颖莎2比4不敌王曼昱摘银 称可能等不到下届亚运会了

体育要闻

114项指控成立,曼城已经完蛋了吗?

娱乐要闻

刘欢去世后谣言四起,韩磊被翻旧账

财经要闻

“为了看短剧,我妈两年花了22万 ”

汽车要闻

MAZDA EZ-60激光版上市 焕新全系11.39万元起

态度原创

房产
时尚
旅游
游戏
公开课

房产要闻

全年霸榜TOP1!南海・叁號院周年官宣:新作即将登场

兰香如故:谁为打工人的情绪工伤买单?

旅游要闻

走遍贵阳烟火街巷,这座百年古楼,藏着整座城的风骨!

黑曜石确认《天外世界2》的剧情DLC仍在开发中

公开课

李玫瑾:为什么性格比能力更重要?

无障碍浏览 进入关怀版