当前位置: 首页 > news >正文

C++ CAN总线数据处理框架解析

C++ CAN总线数据处理框架解析

本文将详细解析一个用于CAN总线数据处理的C++框架,重点介绍其中使用的各种C++特性和重要语法。

一、头文件概览

这个头文件(main_public.h)定义了一个完整的CAN总线数据处理系统,包含了数据结构、同步机制和核心功能函数。下面我们分层解析其中的各个组成部分。

#ifndef MAIN_PUBLIC_H
#define MAIN_PUBLIC_H#include <cstdio>
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <linux/can.h>
#include <linux/can/raw.h>
#include <net/if.h>
#include <chrono>
#include <array>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <map>
#include "AUXILIARY_PROPULSION_CAN.h"// 常量定义
constexpr size_t CAN_SIZE = 3;      // 目前最多3路CAN诊断
constexpr size_t BUFFER_SIZE = 6000;
constexpr size_t FRAME_SIZE = 100;
constexpr size_t RESULT_BUFFER_SIZE = 6000;
constexpr size_t RESULT_FRAME_SIZE = 10;// CAN接口名称
extern const std::string INTERFACE_NAMES[CAN_SIZE];// 索引数组
extern int write_indices[CAN_SIZE];
extern int read_indices[CAN_SIZE];
extern int local_write_indices[CAN_SIZE];// 时间戳数组
extern std::array<int64_t, CAN_SIZE> current_Timestamp;
extern std::array<int64_t, CAN_SIZE> last_Timestamp;
extern std::array<int32_t, CAN_SIZE> ID;
extern std::array<int64_t, CAN_SIZE> remote_Timestamp1;
extern std::array<int64_t, CAN_SIZE> back_Timestamp1;
extern std::array<int64_t, CAN_SIZE> current_Timestamp_1;
extern std::array<int64_t, CAN_SIZE> last_Timestamp_1;
extern std::array<int64_t, CAN_SIZE> last_Timestamp_2;
extern std::array<int64_t, CAN_SIZE> last_Timestamp_3;// 缓冲区
extern std::array<std::array<std::array<unsigned char, FRAME_SIZE>, BUFFER_SIZE>, CAN_SIZE> g_packet_buffer;
extern std::array<std::array<std::array<unsigned char, RESULT_FRAME_SIZE>, RESULT_BUFFER_SIZE>, CAN_SIZE> Result_Buffer;// 同步工具
extern std::mutex buffer_mutex;
extern std::condition_variable data_condition;
extern std::mutex result_mutex;
extern bool running;// 时间戳记录
extern std::map<size_t, std::map<uint32_t, int64_t>> last_timestamp_map;// 函数声明
int64_t calcFrameInterval(size_t channel, uint32_t frame_id, int64_t current_timestamp);
void parseData();
void summarizeResults();void process_can_data(const struct can_frame &frame, int interface_index);#endif

二、基础类型与标准库组件

1. 基本数据类型

int64_t  // 64位有符号整数,用于时间戳存储
int32_t  // 32位有符号整数,用于CAN ID存储
uint32_t // 32位无符号整数
uint16_t // 16位无符号整数
uint8_t  // 8位无符号整数
size_t   // 无符号整数,用于表示大小/索引

2. 标准库容器

std::array      // 固定大小数组,替代原生数组
std::map        // 关联容器,用于键值对存储
std::string     // 字符串类

3. 多线程支持

std::mutex               // 互斥锁
std::condition_variable  // 条件变量
std::thread              // 线程类

三、核心数据结构解析

1. 常量定义

constexpr size_t CAN_SIZE = 3;  // 编译时常量,表示CAN通道数量
constexpr size_t BUFFER_SIZE = 6000;  // 缓冲区大小

constexpr表示这些值在编译时即可确定,编译器会进行优化。
区别与联系

  1. const保证运行时不可修改,值可在运行时确定
  2. constexpr要求编译时必须确定值,用于编译期计算
  3. 所有constexpr都隐含const特性,但const不一定是constexpr

小例子

const int x = rand();       // 合法,运行时确定 
constexpr int y = 10 * 2;   // 合法,编译时计算(20)
constexpr int z = x;        // 错误!x不是常量表达式

2. 多维数组容器

// 三维数组:CAN通道 × 缓冲区大小 × 每帧大小
std::array<std::array<std::array<unsigned char, FRAME_SIZE>, BUFFER_SIZE>, CAN_SIZE> g_packet_buffer;

这种嵌套的std::array提供了:

  • 类型安全
  • 固定大小保证
  • 方便的迭代器访问
  • 边界检查(通过at()方法)

3. 时间戳管理

std::array<int64_t, CAN_SIZE> current_Timestamp;  // 每个通道的当前时间戳
std::map<size_t, std::map<uint32_t, int64_t>> last_timestamp_map;  // 通道+ID的时间戳记录

使用std::map实现了两级查找表,用于记录每个通道上每个CAN ID的最后时间戳。

四、同步机制详解

1. 互斥锁与条件变量

extern std::mutex buffer_mutex;  // 保护缓冲区的互斥锁
extern std::condition_variable data_condition;  // 数据到达通知

典型的生产者-消费者模式:

  • 生产者(process_can_data)获取数据后通知
  • 消费者(parseData)等待条件变量

2. 原子标志

extern bool running;  // 程序运行控制标志

在多线程环境中,对bool的读写通常是原子的,但更严谨的做法是使用std::atomic<bool>

五、关键函数分析

1. CAN数据处理函数

void process_can_data(const struct can_frame &frame, int interface_index);

参数说明:

  • const can_frame &: CAN帧的常量引用,避免拷贝
  • interface_index: CAN接口索引(0-2)

2. 时间间隔计算

int64_t calcFrameInterval(size_t channel, uint32_t frame_id, int64_t current_timestamp);

实现了帧间隔计算功能,返回微秒级间隔。

3. 解析与汇总线程

void parseData();         // 数据解析线程函数
void summarizeResults();  // 结果汇总线程函数

六、重要C++特性应用

1. 类型别名与常量表达式

constexpr size_t FRAME_SIZE = 100;  // 编译期常量

2. 现代C++数组管理

使用std::array替代原生数组:

  • 避免数组到指针的隐式转换
  • 提供size()等成员函数
  • 支持范围for循环

3. 线程安全设计

通过mutex+condition_variable实现:

  • 数据缓冲区的线程安全访问
  • 高效的通知机制
  • 避免忙等待

七、性能考量

  1. 内存布局:多维std::array在内存中是连续的,有利于缓存利用
  2. 零拷贝设计:使用引用传递CAN帧,避免数据拷贝
  3. 时间精度:使用纳秒级时间戳(std::chrono::nanoseconds)
  4. 缓冲区设计:循环缓冲区实现(通过模运算管理索引)

八、其他

  1. 使用std::atomic替代普通bool作为运行标志
  2. 考虑使用std::shared_mutex实现读写锁
  3. 添加异常处理机制
  4. 实现配置文件加载,使参数可配置

这个框架展示了现代C++在嵌入式系统开发中的强大能力,结合了类型安全、高性能和清晰的抽象。


拓展:C++ 中 array 和 vector 的用法与区别

1、基本概念

std::array
  • 固定大小的序列容器,大小在编译时确定
  • 封装了C风格数组,提供STL容器接口
  • 存储在栈上(除非作为对象的一部分分配在堆上)
std::vector
  • 动态大小的序列容器,可在运行时调整大小
  • 数据存储在堆上
  • 提供自动内存管理

2、主要区别

特性std::arraystd::vector
大小固定,编译时确定动态,运行时可变
内存位置通常栈上堆上
内存管理自动(栈分配)自动(堆分配)
性能更快(无动态分配)稍慢(可能涉及重新分配)
适用场景已知大小的数据集合大小不确定或需要变化的数据集合

3、基本用法示例

1) std::array 示例
#include <array>
#include <iostream>int main() {// 创建并初始化std::array<int, 5> arr = {1, 2, 3, 4, 5};// 访问元素std::cout << "第一个元素: " << arr[0] << std::endl;std::cout << "最后一个元素: " << arr.back() << std::endl;// 迭代std::cout << "所有元素: ";for(int num : arr) {std::cout << num << " ";}std::cout << std::endl;// 大小固定std::cout << "数组大小: " << arr.size() << std::endl;return 0;
}
2) std::vector 示例
#include <vector>
#include <iostream>int main() {// 创建空vectorstd::vector<int> vec;// 添加元素vec.push_back(10);vec.push_back(20);vec.push_back(30);// 访问元素std::cout << "第一个元素: " << vec[0] << std::endl;std::cout << "最后一个元素: " << vec.back() << std::endl;// 迭代std::cout << "所有元素: ";for(int num : vec) {std::cout << num << " ";}std::cout << std::endl;// 大小可变vec.pop_back();  // 移除最后一个元素std::cout << "当前大小: " << vec.size() << std::endl;// 调整大小vec.resize(5, 100);  // 调整为大小5,新元素初始化为100std::cout << "调整后大小: " << vec.size() << std::endl;return 0;
}

4、选择建议

使用 std::array 当:
  • 数据大小在编译时已知且固定
  • 需要更高的性能(避免堆分配)
  • 需要容器特性但保持类似数组的性能
使用 std::vector 当:
  • 数据大小在运行时才能确定
  • 需要频繁添加或删除元素
  • 需要自动管理内存
  • 数据量可能很大(避免栈溢出)

5、性能注意事项

  1. std::array 访问速度与原生数组相当,没有额外开销
  2. std::vectorpush_back 操作可能导致重新分配,使用 reserve() 预分配可以优化
  3. 对于小型数据集,std::array 通常更高效
  4. 大型数据集应使用 std::vector 以避免栈溢出

6、高级用法

std::array 的高级初始化
std::array<std::string, 3> colors = {"Red", "Green", "Blue"};
std::vector 的容量管理
std::vector<int> numbers;
numbers.reserve(100);  // 预分配空间,避免多次重新分配
for(int i = 0; i < 100; ++i) {numbers.push_back(i);
}

两者都支持STL算法:

#include <algorithm>std::array<int, 5> arr = {5, 3, 1, 4, 2};
std::sort(arr.begin(), arr.end());  // 排序

拓展二:extern 关键字详解

extern 是 C/C++ 中的一个存储类说明符,主要用于声明变量或函数的外部链接性。它在多文件编程中扮演着重要角色。

1、基本概念

extern 的主要用途是:

  1. 声明但不定义变量或函数
  2. 指示编译器该变量/函数在其他编译单元中定义
  3. 实现多个源文件共享同一个变量或函数

2、典型用法

1) 声明外部变量(最常用)

头文件 (globals.h)

extern int globalVar;  // 声明(非定义)

源文件A (file1.cpp)

#include "globals.h"
int globalVar = 42;    // 实际定义

源文件B (file2.cpp)

#include "globals.h"
void foo() {globalVar = 10;    // 使用在file1.cpp中定义的变量
}
2) 声明外部函数

头文件 (utils.h)

extern void helperFunction();  // 声明外部函数

源文件 (utils.cpp)

#include "utils.h"
void helperFunction() {  // 实际定义// 函数实现
}
3) 与 const 结合使用
extern const int MAX_SIZE;  // 声明外部常量

3、重要特性

  1. 链接性extern 表示变量/函数具有外部链接(external linkage),可以被其他文件访问

  2. 存储持续时间:不影响变量的存储持续时间(全局变量始终是静态存储期)

  3. 与 static 对比

    • extern:扩大作用域到其他文件
    • static:限制作用域在当前文件
  4. 默认情况

    • 函数默认是 extern
    • 全局变量默认是 extern
    • const 全局变量默认是 static 的(C++中)

4、使用场景

  1. 共享全局变量:多个源文件需要访问同一个全局变量时

  2. 分离式编译

    • 声明放在头文件中
    • 定义放在一个源文件中
    • 其他文件通过包含头文件使用
  3. 跨文件函数调用:当一个源文件需要调用另一个源文件中定义的函数时

5、注意事项

  1. 避免滥用:过度使用全局变量(即使是extern的)会降低代码可维护性

  2. 初始化

    extern int x;     // 声明
    int x = 10;       // 定义并初始化
    extern int y = 5; // 定义(不推荐,会抵消extern作用)
    
  3. C与C++区别

    • 在C中,const全局变量默认有外部链接
    • 在C++中,const全局变量默认有内部链接(需要用extern显式指定外部链接)
  4. 头文件保护:使用#ifndef防止多次包含导致的重复声明

6、现代C++中的替代方案

虽然extern仍然有用,但现代C++更推荐:

  1. 使用命名空间组织全局变量
  2. 使用单例模式替代全局变量
  3. 尽量减少全局变量的使用

7、综合示例

config.h

#ifndef CONFIG_H
#define CONFIG_Hextern const char* APP_NAME;  // 声明
extern int debugLevel;        // 声明#endif

config.cpp

#include "config.h"const char* APP_NAME = "MyApp";  // 定义
int debugLevel = 1;              // 定义

main.cpp

#include "config.h"
#include <iostream>int main() {std::cout << "Running " << APP_NAME << std::endl;if(debugLevel > 0) {std::cout << "Debug mode enabled" << std::endl;}return 0;
}

在这个例子中,APP_NAMEdebugLevelconfig.cpp中定义,在main.cpp中通过包含头文件并使用extern声明来访问。

http://www.lryc.cn/news/573083.html

相关文章:

  • 力扣1477. 找两个和为目标值且不重叠的子数组
  • YOLO官方自带的数据集Dotav1,直接训练
  • Python爬虫实战:研究threading相关技术
  • 状态模式详解
  • Filecoin系列 - IPLD 技术分析
  • verilog HDLBits刷题“Module shift8”--模块 shift8---模块和向量
  • Python 的内置函数 hasattr
  • 中国设计 全球审美 | 安贝斯新产品发布会:以东方美学开辟控制台仿生智造新纪元
  • 【Koa系列】10min快速入门Koa
  • 蓝牙 5.0 新特性全解析:传输距离与速度提升的底层逻辑(面试宝典版)
  • 项目开发中途遇到困难的解决方案
  • 深入解析BERT:语言分类任务的革命性引擎
  • 创业知识概论
  • tkinter Entry(输入框)组件学习指南
  • 加密货币:比特币
  • 5.3 LED字符设备驱动
  • HarmonyOS 6 + 盘古大模型5.5
  • 【Python】Excel表格操作:ISBN转条形码
  • 西门子S7通信协议抓包分析应用
  • 【入门级-基础知识与编程环境:NOI以及相关活动的历史】
  • AI 产品的“嵌点”(Embedded Touchpoints)
  • python打卡day37
  • 智能体互联网新闻速递及深度分析【250620】
  • STM32[笔记]--开发环境的安装
  • 大数据Hadoop集群搭建
  • Linux (2)
  • Java常见八股-(6.算法+实施篇)
  • 知识蒸馏(Knowledge Distillation, KD)
  • gitea本地部署代码托管后仓库的新建与使用(配置好ssh密钥后仍然无法正常克隆仓库是什么原因)
  • 李宏毅 《生成式人工智能导论》| 第6讲-第8讲:大语言模型修炼史