From Arduino To Automotive: How I Escaped The IDE And Owned The Bus

From Arduino To Automotive: How I Escaped The IDE And Owned The Bus

从 Arduino 到汽车:我是如何逃离 IDE 并掌控总线的

Arduino taught me how to build. Bare metal taught me how the build actually works. I have a lot of respect for Arduino. I mean that sincerely. It is a legitimate engineering platform, not a toy. It put a C compiler, a bootloader, and a sane hardware abstraction layer into the hands of millions of people and said go make something. Museums run on Arduino. Satellites have run on Arduino. My first three products that actually made money ran on Arduino. It is also a ceiling. And that is not an insult. Every good abstraction is a ceiling by design. The question is whether you have hit it yet. I hit it when I tried to make an ESP32 do something timing critical while also staying connected to WiFi. And that moment pushed me all the way from the IDE to the CAN bus. Arduino 教会了我如何构建项目,而“裸机开发”(Bare metal)则教会了我构建过程的底层原理。我对 Arduino 怀有崇高的敬意,这是真心的。它是一个正经的工程平台,而非玩具。它将 C 编译器、引导加载程序(bootloader)以及一套合理的硬件抽象层交到了数百万人的手中,并鼓励大家去创造。博物馆在用 Arduino,卫星也在用 Arduino。我最初三个盈利的产品都是基于 Arduino 开发的。但它同时也是一个“天花板”,这并非贬义。每一个优秀的抽象层在设计之初就注定是一个天花板,问题在于你是否已经触碰到了它。当我尝试让 ESP32 在保持 WiFi 连接的同时执行对时序要求极高的任务时,我触碰到了这个天花板。那一刻,我从 IDE 一路探索到了 CAN 总线。

Arduino Is A Great Place To Start And A Hard Place To Stay

Arduino 是绝佳的起点,但很难作为终点

Here is what Arduino gets right that almost no one else did. It solved distribution. You install the IDE, you pick your board, you press upload. You do not need to fight OpenOCD, or figure out why your toolchain is building for the wrong architecture, or learn what a linker script does on day one. It solved documentation by giving you functions that read like English. analogRead() does what it says. Wire.begin() does what you expect. It also solved community. When you get stuck, someone else has been stuck there before and left a forum post. For prototyping, for education, for one off installations, for a huge number of commercial products, it is absolutely the right tool. It is stable, it is well tested, and the core libraries for AVR and ESP32 and RP2040 are written by people who are better at driver development than most of us will ever be. Arduino 在分发机制上做得非常出色,几乎无人能及。你只需安装 IDE,选择开发板,点击上传即可。你不需要去折腾 OpenOCD,不需要去排查为什么工具链构建出了错误的架构,也不需要在第一天就去学习链接脚本(linker script)的作用。它通过提供像英语一样易读的函数解决了文档问题。analogRead() 名副其实,Wire.begin() 也如你所愿。它还解决了社区支持问题:当你遇到困难时,总有人曾经遇到过同样的问题并留下了论坛帖子。对于原型设计、教学、一次性安装项目以及大量商业产品来说,它绝对是正确的工具。它稳定、经过充分测试,且 AVR、ESP32 和 RP2040 的核心库是由那些在驱动开发方面远超我们大多数人的专家编写的。

But Arduino makes a trade to get that simplicity. It hides the real time operating system underneath. On ESP32, you are always running FreeRTOS whether you ask for it or not. Arduino just puts your setup() and loop() inside a task for you and quietly creates another task for WiFi and networking. That is elegant until you need to control those tasks. My project needed to sample a sensor at 20kHz with consistent timing, write to SD, and keep a WebSocket alive. In Arduino land, my options were delay() and millis() and hoping. The jitter was terrible. The WiFi would drop when I blocked for too long. The watchdog would bite. I was not fighting my code. I was fighting the assumptions baked into the framework. I did not leave Arduino because it is bad. I left because I wanted to see the whole machine. I wanted to see what was under loop(). 但 Arduino 为了这种简洁性做出了一定的妥协。它隐藏了底层的实时操作系统(RTOS)。在 ESP32 上,无论你是否需要,它始终在运行 FreeRTOS。Arduino 只是将你的 setup()loop() 放入一个任务中,并悄悄为 WiFi 和网络创建了另一个任务。这在不需要精细控制任务时非常优雅,但当你需要控制这些任务时,问题就来了。我的项目需要以 20kHz 的频率对传感器进行稳定时序的采样,同时还要写入 SD 卡并保持 WebSocket 连接。在 Arduino 的世界里,我只能依赖 delay()millis(),然后祈祷一切顺利。抖动(jitter)非常严重,一旦我阻塞时间过长,WiFi 就会断开,看门狗(watchdog)也会触发重启。我不是在与我的代码作斗争,而是在与框架内置的假设作斗争。我离开 Arduino 并非因为它不好,而是因为我想看清整台机器的运作方式。我想看看 loop() 下面到底是什么。

What Lives Under loop()

loop() 下面有什么?

If you open the ESP32 Technical Reference Manual, it is over 600 pages. It is not scary. It is just thorough. It describes the actual chip. Two Xtensa LX6 cores at 240MHz, an interrupt matrix that lets you route almost any peripheral to almost any interrupt, hardware timers, DMA engines, eFuses, clock trees that let you gate power to entire subsystems. Arduino gives you a friendly front door to that building. Bare metal means you get the master key. Going bare metal on ESP32 does not mean writing everything in assembly. It means using ESP-IDF directly, owning the boot process, and understanding that your application starts long before app_main(). It means you write the linker script that decides where your code lives in flash versus RAM. It means you configure the interrupt allocator yourself. It means you decide which core does what. 如果你打开 ESP32 的技术参考手册,会发现它有 600 多页。这并不可怕,只是非常详尽。它描述了芯片的真实面貌:两个 240MHz 的 Xtensa LX6 核心、一个可以将几乎任何外设路由到几乎任何中断的中断矩阵、硬件定时器、DMA 引擎、eFuses,以及允许你关闭整个子系统电源的时钟树。Arduino 为你提供了进入这座大楼的友好前门,而裸机开发则意味着你拿到了主钥匙。在 ESP32 上进行裸机开发并不意味着要用汇编语言编写一切,而是指直接使用 ESP-IDF,掌控引导过程,并理解你的应用程序在 app_main() 运行之前就已经开始了。这意味着你需要编写链接脚本来决定代码存放在 Flash 还是 RAM 中,需要自己配置中断分配器,并决定哪个核心负责什么任务。

The first time you do it, nothing boots. You get a Guru Meditation Error and a register dump. The second time, you get a blinking LED but it is your blinking LED. You wrote the GPIO muxing. You set the clock source. You cleared the interrupt. Then you start to get superpowers. You can get sub microsecond timing because you are not going through layers of abstraction that check if the pin is valid on every call. You can pin WiFi to core 0 and your real time loop to core 1 and they actually stay out of each other’s way. You can tell the brownout detector and the task watchdog exactly what you are doing so they stop resetting you for being busy. Your binary goes from 800KB to 80KB because you only linked what you use. It is not that Arduino cannot do this. It is that when you need this level of control, it is easier to work with the silicon directly than to fight an abstraction that was trying to protect you. That transition, from friendly wrappers to direct register control, is the foundation for everything that came after. Once you can command one microcontroller completely, you start noticing how chatty microcontrollers are with each other. Especially the ones in your car. 第一次尝试时,系统无法启动,你会看到“Guru Meditation Error”和寄存器转储。第二次尝试时,你终于点亮了 LED,但这属于你自己的 LED——是你编写了 GPIO 复用,设置了时钟源,并清除了中断。接着,你开始获得“超能力”。你可以实现亚微秒级的时序,因为你不再需要经过那些在每次调用时都要检查引脚是否有效的抽象层。你可以将 WiFi 绑定到核心 0,将实时循环绑定到核心 1,让它们互不干扰。你可以明确告诉欠压检测器和任务看门狗你在做什么,这样它们就不会因为你太忙而频繁重置系统。你的二进制文件大小可以从 800KB 缩减到 80KB,因为你只链接了用到的部分。并不是说 Arduino 做不到这些,而是当你需要这种级别的控制时,直接操作芯片比对抗那些试图“保护”你的抽象层要容易得多。这种从友好的封装转向直接寄存器控制的转变,是后续一切成就的基础。一旦你能完全掌控一个微控制器,你就会开始注意到微控制器之间是多么“健谈”。尤其是你车里的那些。

Your Car Is Not A Car. It Is A Network With Cupholders

你的车不是车,而是一个带杯架的网络

Every modern vehicle is a distributed system. Depending on who counts, your average new car has between 40 and 100 ECUs. Engine, transmission, brakes, steering, airbags, doors, instrument cluster, infotainment, all of them are computers. They talk over CAN bus. Controller Area Network. Two wires, CAN High and CAN Low, twisted together. It was designed by Bosch in the 80s for reliability in noisy environments. It is brilliantly robust. It is also completely trusting. CAN has no authentication. No encryption. No source address validation in the base protocol. Any node can send a frame with any ID, and every other node will believe it. Arbitration is handled by ID priority. Lower ID means higher priority. That is the only security model. When I connected an ESP32 and a $3 transceiver to a bench setup, not even a real car at first, just a salvaged instrument cluster and a body control module from a junkyard, and saw the traffic, it was like hearing a building talk to itself. Hundreds of frames per second. RPM, wheel speeds, steering angle, door switches, seatbelt status, all in plaintext. Reverse engineering CAN is not magic. It is patience and method. You log traffic at rest. You log traffic while you change one thing. You open the driver door, what changed? You press the brake, what new frame appears? You turn the steering wheel two d 每一辆现代汽车都是一个分布式系统。根据统计方式的不同,一辆普通的新车拥有 40 到 100 个电子控制单元(ECU)。发动机、变速箱、刹车、转向、安全气囊、车门、仪表盘、信息娱乐系统,它们全都是计算机。它们通过 CAN 总线(控制器局域网)进行通信。CAN 总线由两根绞合在一起的导线组成:CAN High 和 CAN Low。它由博世(Bosch)在 80 年代设计,旨在高噪声环境下保持可靠性。它极其稳健,但也完全缺乏信任机制。CAN 协议本身没有身份验证,没有加密,也没有源地址验证。任何节点都可以发送带有任何 ID 的帧,而其他所有节点都会相信它。仲裁通过 ID 优先级处理,ID 越小优先级越高,这就是它唯一的安全模型。当我将 ESP32 和一个 3 美元的收发器连接到测试台(起初甚至不是真车,只是从废车场淘来的仪表盘和车身控制模块)并观察流量时,那感觉就像是在听一座建筑在自言自语。每秒数百帧的数据:转速、轮速、转向角、车门开关、安全带状态,全部以明文形式传输。逆向工程 CAN 总线并非魔法,而是耐心与方法的结合。你记录静止时的流量,记录改变某一个状态时的流量。打开驾驶座车门,什么变了?踩下刹车,出现了什么新帧?转动方向盘两度……