发布于 2026-01-06 3 阅读
0

ECU模拟器

ECU模拟器

问题

我想为我的汽车制作数字仪表/指示器,而无需坐在车里操作。

规划

我大致知道该怎么做:让我的车载诊断(OBD)设备与Arduino通信,Arduino像汽车的ECU(电子控制单元)一样发送信息。然后,OBD设备再将这些信息转发给连接到它的任何设备。

值得一提的是,我使用的OBD设备是基于ELM327平台的。我用的是Vgate iCar Pro,但所有廉价的OBD设备实际上都大同小异。它们只是外壳不同,或者连接手机/电脑的方式不同,例如WiFi、蓝牙低功耗(BLE)或物理连接。iCar Pro使用蓝牙低功耗(BLE)连接手机或其他支持BLE的设备,所以我选择了它。

ECU模拟器粗略计划

这篇博文,我将重点介绍虚线矩形框内的区域,即Arduino接收和响应来自OBD设备的请求的部分。

如何?

有了大致的想法后,我接下来需要弄清楚如何接收和回复来自 OBD 设备的消息,那么汽车通常是如何做到这一点的呢?

大多数汽车都使用一种叫做控制器局域网 (CAN) 的技术。CAN 允许许多传感器(或其他任何设备)通过公共总线(CAN 总线)进行通信,而无需像网状网络那样互连,这有助于减少汽车中的线路数量。带有(某种程度上)标准化 ID 的 CAN 数据帧通过 CAN 总线发送。在这个项目中,我只需要 RPM 参数 ID,但还有很多其他参数。我找到了一个很方便的工具,它显示了大多数常见的 CAN ID 以及数据帧示例(https://www.csselectronics.com/pages/OBD-pid-table-on-board-diagnostics-j1979)。

为了让我的 Arduino 和 OBD 设备通信,我需要使用 CAN 总线将它们连接起来并发送 CAN 数据帧。接下来,我需要弄清楚如何实现 CAN 总线。那么,我有哪些选择呢?

由于这只是个周末项目,我希望它既经济实惠又能是个学习机会,所以我查找了支持CAN协议的微控制器以及实现该功能所需的电子元件。但当时疫情后订单积压严重,这些微控制器都缺货。所以我选择了最快捷但也最贵的方案,订购了一个MCP2515模块。

这些模块包含了通过 CAN 总线发送消息所需的一切。我只需要将它们连接到 Arduino 和 OBD 设备即可。

因此,加上 MCP2515 模块后,上面的图表现在看起来像下面这样,其中 MCP2515 处理 CAN,而 Arduino 决定如何处理消息。

更新后的ECU模拟方案

组装

有了计划之后,接下来我需要弄清楚如何将 OBD 设备、MCP2515 和 Arduino 连接起来。由于 MCP2515 是一个相当常见的模块,所以有很多关于如何将其连接到 Arduino 的文档。

此外,我还连接了一个电位器来改变转速值。虽然可以通过代码随机化返回的值,但这并非必需,但有总比没有好。我还加装了一个 OBD 母头接口,方便连接 OBD 设备。如果没有这个接口,每次将 OBD 设备从车上移到模拟器上时,都必须手动将 MCP2515 连接到 OBD 设备的引脚上。

接线图

接线图

接线时需要注意以下几点:

VIN(输入电压)必须为 12V,因为你的 OBD 设备很可能需要 12V 电压,就像它连接到汽车上一样。所以,如果你使用的设备没有将 VIN 上的电压降压到 5V 或 3.3V 来驱动你的微控制器,那么你的微控制器很可能会烧毁。

对于这种设置,您需要将 MCP2515 上的 J1 接头引脚短接,这会给 CAN 总线增加 120 欧姆的电阻。如果您将 MCP2515 直接连接到汽车上,则无需这样做。

我还设计了一个可3D打印的盒子,用来放置ECU模拟器和电位器,并带有一个OBD接口。您可以在这里找到它:https://www.thingiverse.com/thing: 5532047

代码

组装完成后,我们就可以开始编写代码了。对于像这样的小型硬件相关项目,我喜欢使用 Arduino 框架和 PlatformIO。

我需要的代码必须满足以下要求:

  • 利用电位器

  • 使用 MCP2515 模块

  • 处理传入的 RPM 消息

  • 构建 RPM CAN 消息

  • 发送 RPM 消息响应

我会一步一步地讲解,最后给出最终结果(如果你只想看代码,可以直接跳到最后)。

利用Arduino 框架读取电位器的电阻值非常简单。我们只需要使用analogRead相应的函数以及连接电位器信号线的头文件名称即可。例如,A0将电位器的值赋给一个变量的代码大致如下int potent_rpm = analogRead(A0);

使用 MCP2515 模块,我不会为这个项目重新开发,而且有很多库可以处理 CAN 总线相关的功能。我使用的是https://github.com/autowp/arduino-mcp2515,因为它有文档并且支持发送和接收 CAN 消息。

处理传入的转速信息。当ECU模拟器收到来自OBD设备的传入信息时,它只需要关注CAN参数ID。因此,我们可以使用switch语句,并根据参数ID来决定发送什么信息。

例如



    unsigned char p_id = read_can_msg.data[2];

    switch (p_id)
      {
      case 0:
        ...
        break;

      case 12:
        ...
        break;

      default:
        break;
      }


Enter fullscreen mode Exit fullscreen mode

现在我们知道了接收到的消息,就可以构建 RPM CAN 消息并发送响应了。借助我们引入的库,我们可以构建一条消息发送给 OBD 设备。同样,使用 CSS Electronics 的工具,您可以找到要发送的消息示例。switch 语句大致如下所示:



    unsigned char p_id = read_can_msg.data[2];

    switch (p_id)
      {
      case 0:
        write_can_msg.can_id  = 2024;
        write_can_msg.can_dlc = 8;
        write_can_msg.data[0] = 6;
        write_can_msg.data[1] = 65;
        write_can_msg.data[2] = 0;            
        write_can_msg.data[3] = 255;
        write_can_msg.data[4] = 255;
        write_can_msg.data[5] = 255;
        write_can_msg.data[6] = 255;
        write_can_msg.data[7] = 170;

        mcp2515.sendMessage(&write_can_msg);
        break;

      case 12:
        write_can_msg.can_id  = 2024;
        write_can_msg.can_dlc = 8;
        write_can_msg.data[0] = 4;
        write_can_msg.data[1] = 65;
        write_can_msg.data[2] = 12;
        write_can_msg.data[3] = potent_rpm;
        write_can_msg.data[4] = 0;
        write_can_msg.data[5] = 170;
        write_can_msg.data[6] = 170;
        write_can_msg.data[7] = 170;

        mcp2515.sendMessage(&write_can_msg);
        break;

      default:
        break;
      }



Enter fullscreen mode Exit fullscreen mode

您可能已经注意到案例 0,我添加它是因为我发现我用来调试 ECU 模拟器的 OBD 移动应用程序通常会首先请求该信息,以查看 ECU 上所有支持的 CAN 参数 ID。如果应用程序没有收到对该消息的响应,它们通常会超时。

如果你只想盗用ECU模拟器,最终的版本看起来是这样的:



#include <SPI.h>
#include <mcp2515.h>

struct can_frame read_can_msg;
struct can_frame write_can_msg;
MCP2515 mcp2515(10);


void setup() {
  Serial.begin(9600);

  mcp2515.reset();
  mcp2515.setBitrate(CAN_500KBPS, MCP_8MHZ);
  mcp2515.setNormalMode();
}

void loop() {
  if (mcp2515.readMessage(&read_can_msg) == MCP2515::ERROR_OK) {
    int potent_rpm = analogRead(A0) / 10;
    unsigned char p_id = read_can_msg.data[2];

    switch (p_id)
      {
      case 0:
        write_can_msg.can_id  = 2024;
        write_can_msg.can_dlc = 8;
        write_can_msg.data[0] = 6;
        write_can_msg.data[1] = 65;
        write_can_msg.data[2] = 0;            
        write_can_msg.data[3] = 255;
        write_can_msg.data[4] = 255;
        write_can_msg.data[5] = 255;
        write_can_msg.data[6] = 255;
        write_can_msg.data[7] = 170;

        mcp2515.sendMessage(&write_can_msg);
        break;

      case 12:
        write_can_msg.can_id  = 2024;
        write_can_msg.can_dlc = 8;
        write_can_msg.data[0] = 4;
        write_can_msg.data[1] = 65;
        write_can_msg.data[2] = 12;
        write_can_msg.data[3] = potent_rpm;
        write_can_msg.data[4] = 0;
        write_can_msg.data[5] = 170;
        write_can_msg.data[6] = 170;
        write_can_msg.data[7] = 170;

        mcp2515.sendMessage(&write_can_msg);
        break;

      default:
        break;
      }
  }
}


Enter fullscreen mode Exit fullscreen mode

如果您遇到问题,以下几点或许会有帮助:

由于我使用的是 Arduino Uno 克隆板,波特率可能会有所不同Serial.begin(9600);

int potent_rpm = analogRead(A0) / 10;因为我用的电位器阻值比较高,所以我把电位器的值除以十。

该线路的mcp2515.setBitrate(CAN_500KBPS, MCP_8MHZ);时钟频率应与你MCP2515模块上安装的时钟频率相匹配。我的模块是8MHz,但我见过其他模块是16MHz的。

最终结果

最终结果

所有部件都连接好,代码也上传到 Arduino 后,最终产品完全符合我的预期。我成功设置了一个换挡指示灯,这在我的车上是很难实现的。以前每次想看看指示灯是否亮着,都得把油门踩到底。

我计划读取歧管压力并计算增压值(单位为 PSI),因为不知何故,我的车虽然出厂时就配备了涡轮增压器,但却没有显示涡轮增压器是否处于工作状态。

这跟dev.to上的大多数话题略有不同,但我在寻找相关帮助时,更希望得到一些指导,而不是规格表和那些看起来乱七八糟的时序图。

文章来源:https://dev.to/devjameshay/basic-ecu-simulator-using-an-arduino-1064