# Python(C\_Cpp)分析模拟Arduino和Raspberry Pi(单板机)CAN总线和车载单元测试

<details>

<summary>关键词</summary>

Arduino | Raspberry Pi | 单板机 | CAN总线 | Python | C/C++ | 发送端/接收端 | 温湿度 | LED | LCD | 颜色 | 数据嗅探 | 按钮 | STM32 | Qt

</details>

{% embed url="https://viadean.notion.site/Python-C_Cpp-Arduino-Raspberry-Pi-CAN-e59691c5555247a28668cdf788472909?pvs=4" %}

{% content-ref url="../zhi-dian-mi-jin-brief" %}
[zhi-dian-mi-jin-brief](../zhi-dian-mi-jin-brief)
{% endcontent-ref %}

### 要点

1. CAN总线释义：物理层结构，数据帧，数据交换，总线接口物理模块。
2. 一对Arduino CAN （Arduino C 处理）总线项目：
   1. 发送端发送随机数据，接收端接收并计算。
   2. 发送端点动信号，接收端触发LED亮灯。
   3. 发送端温度传感器检测计算，接收端蜂鸣器报警。
   4. 接收端发送请求指令，发送端接收到指令后，发送温度数据。
   5. 外部点动信号触发接收端，接收端发送请求指令，发送端接收到指令后，发送温度数据。
   6. 三种外部点动信号触发发送端，发送端发送对应指令，接收端接收后，显示对应颜色的LED。
   7. 发送端温湿度传感器检测计算，接收端接收温湿度数据并LCD显示。
   8. 两个发送端两个温度传感器检测计算，一个接收端接收两个温度数据并LCD显示。
   9. 两个发送端两个温度传感器检测计算，一个接收端根据两个外部点动信号触发，分别显示两个发送端温度数据并LCD显示。
   10. 模拟逻辑分析器，接收端定时嗅探发送端数据
3. Raspberry Pi和Arduino CAN总线（Arduino C 处理）项目：
   1. 发送端（Raspberry Pi）发送字符数据，接收端（Arduino）接收并显示在串口监控
   2. 发送端（Arduino）发送数字，接收端（Raspberry Pi）接收并显示
   3. 发送端（Raspberry Pi)发送指令，接收端（Arduino）接收指令控制两个LED
   4. （Python 处理）两个外部信号分别触发发送端（Raspberry Pi），接收端（Arduino)分别控制两个LED
   5. （Python 和Arduino C 处理）两个外部点动信号触发一个发送端（Arduino），分别控制一个接收端（Raspberry Pi)和一个接收端（Arduino）
4. Qt C++ CAN总线数据捕捉器模拟操控车载总线
5. Python单板机车载诊断 II 端口提取 CAN总线数据并云端监控
6. Arduino 和STM32 发动机控制单元渗透测试

### CAN总线

控制区域网络 (CAN) 总线是一种串行通信协议，允许设备以可靠且高效的方式交换数据。它广泛应用于车辆中，像神经系统一样连接车辆中的ECU。CAN 总线数据可以提供有关所连接设备的性能和状态的宝贵见解。然而，由于高数据速率、低带宽和可变的网络条件，收集和处理 CAN 总线数据可能具有挑战性。

工作原理

CAN总线是一种分散式通信协议。其分散式方法使其成为可靠性和实时性能至关重要的汽车和工业系统应用的理想选择。在 CAN 网络中，所有节点都通过双绞线或光纤电缆连接。 每个节点都有自己的微控制器，负责处理传入消息和发送传出消息。 数据由共享总线上的节点广播，允许所有其他节点接收它。 通讯过程的主要阶段是：

1. 仲裁：为了防止多个节点同时尝试传输时发生冲突，CAN 使用基于消息优先级的仲裁过程。消息的标识符值越低，其优先级越高。
2. 错误检测：内置错误检测机制可确保 CAN 网络内的数据完整性。其中包括循环冗余校验 (CRC)、帧校验序列 (FCS) 和来自接收节点的确认位。
3. 故障限制：如果任何节点在传输过程中检测到错误或故障，它将进入“错误被动”状态，直到恢复正常运行。这可以防止错误传输影响整个系统功能。

报文结构

CAN 总线系统中的消息结构对于设备之间的高效通信至关重要。 该协议使用由多个字段组成的数据帧格式，包括标识符、控制字段、数据字段和错误检测机制。

* 标识符：这个唯一值决定网络上每条消息的优先级。 在标准 11 位标识符 (CAN 2.0A) 中，最多有 2048 个不同的优先级可用。 扩展 29 位标识符 (CAN 2.0B) 提供了更多选项，具有超过 5 亿个不同值。
* 数据长度代码 (DLC)：位于控制字段内，该代码指定数据字段中存在多少字节 - 范围从 0 到 8 个字节。
* 数据字段：包含以字节大小的段在节点之间传输的实际信息。
* 循环冗余校验 (CRC)：一种内置错误检测机制，通过检测传输错误并在必要时请求重传来确保可靠的通信。
* 确认时隙：接收节点使用的单个位来确认消息的成功接收或指示需要重传的错误。
* 错误帧：CAN 消息传递的可选部分，允许节点在检测到自身传输问题或从网络上其他设备接收到的消息时发出信号。

### CAN总线模拟

创建 Linux虚拟CAN接口

虚拟 CAN 接口是开发 CAN 节点时的完美解决方案，无需实际的物理 CAN 总线。 将虚拟 CAN 接口视为软件 CAN 适配器，通过它您可以访问模拟 CAN 总线。 Linux 内核的 SocketCAN 子系统内置了对虚拟 CAN 接口的支持。

在 Linux 上，内核直接提供 CAN 功能。 它被称为 SocketCAN。 SocketCAN 不仅包含硬件驱动程序和控制器局域网的网络协议栈。 它还支持虚拟 CAN 设备。

虚拟 CAN 接口的唯一限制是您无法直接访问硬件 CAN 节点。 但是，您可以使用 CAN 网关内核模块 can-gw 并在物理和虚拟 CAN 总线之间路由消息，以绕过此限制。

在 Linux 上使用虚拟 CAN 接口之前，您需要在终端中执行以下三个步骤：

* 加载 vcan 内核模块： `sudo modprobe vcan`
* 创建虚拟 CAN 接口： `sudo ip link add dev vcan0 type vcan`
* 使虚拟 CAN 接口联机： `sudo ip link set up vcan0`

之后，您可以运行 `ip addr | grep "can"` 命令来验证虚拟 CAN 接口在 Linux 系统上是否可用且在线：

```bash
 pragmalin@ubuntuvm: \$|sudo modprobe vcan
 pragmalin@ubuntuvm: \$ sudo ip link add dev vcano type vcan
 pragmalin@ubuntuvm: \$ sudo ip link set up vcan0
 pragmalin@ubuntuvm: \$ ip addr | grep "can"
 3:vcan0: <NOARP,UP,LOWER_UP> mtu 72 qdisc noqueue state UNKNOWN group default qlen 1000
```

如果您之前在 Linux 下使用过真正的 CAN 硬件，您会注意到一个区别：ip addr 命令的输出显示状态为 UNKNOWN，而不是 UP。这对于 Linux 上的虚拟 CAN 接口来说是正常的。

请注意，您不必在 Linux 上为虚拟 CAN 接口配置 CAN 比特率。 SocketCAN子系统模拟CAN总线，不需要做任何通信同步。

您可以使用基本的 bash 脚本自动执行这些步骤：

```bash
 #!/bin/bash
 # Make sure the script runs with super user privileges.
 [ "$UID" -eq 0 ] || exec sudo bash "$0" "$@"
 # Load the kernel module.
 modprobe vcan
 # Create the virtual CAN interface.
 ip link add dev vcan0 type vcan
 # Bring the virtual CAN interface online.
 ip link set up vcan0
```

将这些内容保存到您的主目录中的文件中。例如：\~/vcan.sh。然后将其标记为可执行文件：chmod +x \~/vcan.sh。从现在开始，您只需运行此脚本即可在 Linux 系统上获取 vcan0 虚拟 CAN 接口：

* `cd ~`
* `./vcan.sh`

请注意，该脚本会自动提示您输入 sudo 密码。

### C++ CAN简易测试

利用上述虚拟CAN接口

```cpp
 #include <QCanBus>
 #include <QCanBusDevice>
 #include <QCanBusFrame>
 #include <QDebug>
 ​
 int main()
 {
     // Show enumerating the available plugins.
     qDebug() << "Available plugins:";
     for (auto plugin : QCanBus::instance()->plugins()) {
         qDebug() << "  " << plugin;
     }
 ​
     // Create device.
     QCanBusDevice *device = QCanBus::instance()->createDevice("socketcan", "vcan0");
     if (device != nullptr) {
         qDebug() << "Created device, state is:" << device->state();
     } else {
         qFatal("Unable to create CAN device.");
     }
 ​
     // Connect.
     if (device->connectDevice()) {
         qDebug() << "Connected, state is:" << device->state();
     } else {
         qDebug() << "Connect failed, error is:" << device->errorString();
     }
 ​
     // Create a data frame.
     QCanBusFrame frame(QCanBusFrame::DataFrame, "12345");
 ​
     // Send it.
     if (device->writeFrame(frame)) {
         qDebug() << "Wrote frame, state is:" << device->state();
     } else {
         qDebug() << "Write failed, error is:" << device->errorString();
     }
 ​
     // Disconnect.
     device->disconnectDevice();
     qDebug() << "Disconnected, state is:" << device->state();
 ​
     return 0;
 }
```
