Teensy 4.0/4.1 tri-CAN OBD VCI for VAG — drive an active diagnostic session and losslessly log the wire at the same time, from one plug.
Status — fw 0.9.14 / Console 0.9.15, pre-1.0. Dual-head hardware-validated on a 2013 Audi A6 C7: both heads read VIN / part numbers / the gateway part over multi-frame ISO-TP, and Head 2 (listen-only) losslessly logged a live ODIS Component-Protection session while the dealer tool drove it. Builds for Teensy 4.0 (Orthrus) and 4.1 (Cerberus); ships a desktop app — CerberusConsole (sniff · diagnostics · one-click firmware) — auto-built as a downloadable
.exeon the Releases page. Dual USB serial: the board enumerates as two COM ports — a smart command/CAN port and an always-transparent K-line cable for NefMoto & other dumb-KKL tools. The K-line / KWP2000 / KW1281 path is firmware-complete but bench-untested (needs the transceiver). The write / CP paths are bench/experimental — not validated end-to-end on a car. OLED is built-to-API, not panel-verified. Don't point write features at a car you can't recover. MIT-licensed.
Cerberus turns a Teensy 4.0/4.1 (NXP i.MX RT1062, 600 MHz Cortex-M7) into a request-level VAG VCI: you hand it a UDS request and the firmware runs the whole ISO-TP transaction on-device. Three independent FlexCAN controllers let it do what a normal single-channel adapter can't — run an active UDS/CP exchange on one head while losslessly logging the unmasked wire on a second, so you can capture a Component-Protection handshake as you drive it.
Three heads guarding the gate — the gate being VAG's gateway (J533) and everything it hides behind Component Protection. Cerberus is the three-headed guardian of the gate; its two-headed brother Orthrus (Teensy 4.0) is the compact watch-dog. See the lore.
| Head | FlexCAN | Teensy pins | Role |
|---|---|---|---|
| 1 | CAN1 | 22 / 23 | Active VCI — Diagnostic CAN (OBD 6/14), 500 k. UDS read/write/CP, SCAN, RAW, CANX. |
| 2 | CAN2 | 0 / 1 | Always-on logger — a 2nd SN65HVD230 paralleled on the same OBD 6/14, held LISTEN-ONLY. Captures the bus while Head 1 drives (MON). |
| 3 | Serial2 (UART) | 7 / 8 | K-line / KWP2000 — single-wire 12 V bus (OBD 7) via a K-line transceiver (TJA1021), for pre-CAN VAG. ISO 14230 fast + 5-baud slow init, framed KWP requests. (K-line is a UART, not CAN — repurposed from the CAN3 spare. Firmware built, bench-untested pending the transceiver.) |
Both heads tap the 500 k diag bus. On gateway cars (incl. the C7) the OBD port is firewalled to the diagnostic CAN, and the gateway routes the comfort/CP traffic (seat, HVAC) onto that bus over VW TP 2.0 — so Head 2 logs it right there. A direct comfort-bus tap is a future Head-3 plug-in (
TJA1055T/3). See docs/HARDWARE.md.
- Request-level UDS/ISO-TP on-device — single + multi-frame, flow control, block-size/STmin,
0x78response-pending, all handled on the Teensy. Writes need no special command (a payload7 bytes is auto First-Frame/CF framed).
- Dual-head capture — active Head 1 + always-on listen-only Head 2 logger (
MON), backed by a 256 KB OCRAM ring buffer so a USB stall or a long blocking transaction never drops frames (MON:statreports peak/dropped). - Modes —
MODE:vci|sniff|dual: one command sets both heads.sniff= BOTH heads listen-only = zero bus footprint, safe to log a live ODIS/dealer session without touching it. SELFTEST— factory/bring-up QC: loopback both controller cores + a Head-1→Head-2 wire check, PASS/FAIL, no extra gear.REBOOTdrops to the bootloader so the host can flash.- SLCAN (Lawicel) mode — drops into the wider ecosystem: SavvyCAN, python-can,
slcand→SocketCAN. EMUresponder mode — emulate a module (UDS server with rule-matched responses) to probe how J533 / a tester reacts. Bench play; needs a 2nd node to drive it.- Optional SSD1306 OLED HUD (auto-detected) — live mode title + per-head VU bars.
- Builds for Teensy 4.0 and 4.1 — same features;
INFOreports the board so the app flashes the right hex. (4.0 = compact 2-head unit; 4.1 = solder-friendly Head 3 + SD/ethernet headroom.)
A plug-and-go GUI (host/cerberus_console.py, pyserial + tkinter — no Simos-Suite needed),
organised by Cerberus's heads:
- CANBUS (Heads 1–2) — Sniff (passive Head-2 trace + chronological Record session CSV) and Diagnostics (Head-1 UDS: VIN/Part, Read + Clear DTCs with SAE J2012 English text).
- K-Line (Head 3) — KWP2000 and KW1281: init (fast / 5-baud / KW1281), session, ECU-ID,
Read + Clear faults (VAG DIDB text) + live measuring blocks (scaled via
formula.py), per module address. Shows which 2nd COM port is the always-dumb K-line cable to point NefMoto at. (Needs the K-line transceiver — bench-untested.) - Firmware — running version + board vs bundled, one-click flash/update (via
REBOOT+teensy_loader_cli, or a PlatformIO-upload fallback when run from source), matching hex/--mcuper detected board; blank-board picker for a never-flashed Teensy.
DTC text comes from two vetted tables, branched by transport: SAE J2012 (saedb/, ~1,680 generic
P/U codes) for CAN/UDS, and the VAG DIDB (didb/, 4,817 fault-locations) for K-line. Switching
views sets the right MODE automatically; version + board show in the title bar; it auto-reconnects
if a USB link drops. Prebuilt app: grab CerberusConsole.exe from the Releases
page (CI-built on every version bump, bundles both hexes + the flasher), or build it yourself with
cd host && build_exe.bat (see host/BUILD-EXE.md).
# A) one-click: CerberusConsole -> Firmware tab -> Flash (auto-picks the hex for your board)
# B) no toolchain: open firmware/cerberus-can-teensy4{0,1}.hex in Teensy Loader (INFO shows version + board)
# C) from source (PlatformIO):
pip install platformio
python -m platformio run -e teensy41 -t upload # or: -e teensy40(The prebuilt CerberusConsole.exe on the Releases page flashes with one click — no toolchain.)
Head 1 (active) through a 3.3 V SN65HVD230: OBD 6→CANH, 14→CANL, 4→GND; board CTX→Teensy 22,
CRX→Teensy 23, VCC→3V3, GND→GND.
Head 2 (logger) — a second SN65HVD230: CANH→OBD 6, CANL→OBD 14 (paralleled on Head 1),
CTX→Teensy 1, CRX→Teensy 0, VCC→3V3, GND→GND.
- Remove the 120 Ω terminator from both breakouts — the car already has ~60 Ω; the taps must add none.
- Tie Teensy GND to OBD/chassis GND.
- (Clone boards mislabel CTX/CRX — if no comms, swap the pair.)
OLED (optional): SDA→18, SCL→19, VCC→3V3, GND→GND. Auto-detected at 0x3C/0x3D; 128×64 default
(#define OLED_128x64; comment for a 0.91" 128×32).
Full BOM + bring-up: docs/BUILD.md.
ASCII, one command per line:
<TX>:<RX>:<HEX> UDS on Head 1 (shorthand) 710:77A:2200BE
UDS:<bus>:<TX>:<RX>:<HEX> full ISO-TP UDS on bus 1|2 UDS:1:710:77A:2E00BE…
RAW:<bus>:<ID>:<HEX> send ONE classic frame (no ISO-TP)
CANX:<bus>:<ID>:<HEX>[:ms] send one frame then listen ms (low-level send-then-listen)
SCAN:<bus>[:lo:hi[:win]] active responder sweep (TesterPresent)
SNIFF:<bus>:<ms>[:lo:hi] passive LISTEN-ONLY dump
MON:on[:lo:hi] | off | stat always-on Head-2 ring-buffered logger -> M2:<ms>:<id>:<hex>[:OVR]
MODE:vci|sniff|dual set both heads (sniff = BOTH listen-only, zero footprint); MODE reports
HEAD2:active|lom flip Head 2 between a 2nd active VCI and the listen-only logger
H2TEST Head-1-listens-while-Head-2-transmits TX-path self-test
SELFTEST QC: CAN1/CAN2 loopback + H1->H2 wire check -> PASS/FAIL
EMU:on:<bus>:<REQ>:<RESP> emulate a module (UDS responder) EMU:add:<prefix>:<resp> rules
TP:<bus>:<TX>:<ms> | TP:STOP background TesterPresent keep-alive
STATS:<bus> CAN error counters / bus health
SLCAN enter Lawicel mode on Head 1 (reset to exit)
KWP:fast[:tgt] | slow:<addr> Head 3 K-line / KWP2000 init (ISO 14230 fast / 5-baud slow)
KWP:<hex> | raw:<hex> | off framed KWP request / raw bytes / close (pre-CAN VAG)
KWP:passthrough[:baud] transparent "dumb KKL cable" — raw K-line<->USB (reset to exit); for NefMoto et al.
REBOOT jump to the bootloader (host firmware flash)
INFO / PING INFO -> CERBERUS:<ver> board=T4.0|T4.1 …
-> OK:<resphex> | ERR:<reason> | RX:<ms>:<id>:<data> … DONE:<n>
cerberus_console.py— the CerberusConsole desktop app (above). Start here.build_exe.batfreezes it to a single.exe. (cerberus_sniff_gui.pyis the older minimal sniffer, now superseded.)cerberus_sniff.py— liveSNIFF/MONcapture, per-ID summary, CSV out.cerberus_decode.py— turn a capture into labeled UDS/KWP exchanges (ISO-TP + VW TP 2.0), flagging the CP-relevant services (TrainICA,0x00BEIKA write, SecurityAccess).cerberus_probe.py— Experiment 1: reads0x00BEacross J533/J255/J136/J285 (VIN-bound vs module-bound verdict).cerberus_write.py— careful generic UDS write: read-before → confirm →2E→ read-back,--dry-run.sample_capture.csv— a VIN-free real-C7 slice to try the decoder with no hardware.
Seen in the wild: a real SCAN of a 2013 Audi A6 C7 — one head
mapped and named the whole car off the gateway.
- Head 1 @ 500 k — request-level ISO-TP/UDS read + write
- Hardware listen-only sniff (LOM)
- Dual-head: active VCI + always-on ring-buffered logger (
MON) — validated on a live car -
MODEvci/sniff/dual — zero-footprint sniff alongside a dealer/ODIS tool - Captured a live ODIS Component-Protection session via the dual tap
-
SELFTESTQC +REBOOThost-flash; Teensy 4.0 + 4.1 dual-target builds - CerberusConsole desktop app (sniff · diagnostics · one-click firmware) + single-
.exebuild - SLCAN ecosystem interop (SavvyCAN / python-can / SocketCAN)
- OLED status HUD (mode + VU bars)
- Simos-Suite drives Cerberus (dual-head driver + CP Capture live view +
set_mode) -
EMUresponder mode — fake a module to probe the gateway / CP from the other side - Head 3 configurable tap channel (CAN-FD /
TJA1055T/3comfort bus) - K-line / KWP2000 (Head 3, pre-CAN VAG) — firmware built (
KWP: ISO 14230 fast + 5-baud init, framed requests); bench-untested pending the K-line transceiver + a pre-CAN car - Dual USB serial — K-line dumb-cable port — the board enumerates two COM ports: a smart
command/CAN port (this Console) and an always-transparent K-line KKL port for tools that own the
protocol (NefMoto, VCDS-dumb). No mode to flip; smart KWP auto-yields the K-line while the 2nd
port is open and reclaims it on close. (
KWP:passthroughremains a single-port fallback.) Bench-untested, fast-init focus (5-baud/break init still TBD) - On-device SD logging (RTC + coin cell for real timestamps)
- Software-switchable CAN termination — 110 Ω + TS5A3157 (10 Ω Rₒₙ → ~120 Ω total) across OBD
6/14 on GPIO pin 2 (
TERM:on|off, default off = car-safe). Run the switch on the 3V3 rail, on the CANL leg — its node stays ~1.5–2.5 V and VIH = 0.7×V+ = 2.3 V, so the 3V3 GPIO drives it directly (no level shifter; a 5 V rail would need 3.5 V). Bench mode terminates; high-Z when tapping a car. (Until then: a 120 Ω "terminator plug" across 6/14 does the same for bench work.)
Named for the guardian hounds of Greek myth. The naming encodes the architecture: heads = buses, guardians watch, the messenger acts. See docs/LORE.md.
| Tool | Myth | Board | Heads | Job |
|---|---|---|---|---|
| Orthrus | two-headed hound | Teensy 4.0 | KWP · CAN | the compact watch-dog |
| Cerberus | three-headed guardian of the gate | Teensy 4.1 | KWP · CAN · DoIP | watch the whole gate |
| Hermes-CP | the messenger who passes the guard | one route | — | cross the threshold, fix CP, leave (private) |
Orthrus and Cerberus are one codebase, two build targets — see docs/PRODUCT-LINE.md. Orthrus and Cerberus guard the gate; Hermes is the one who walks past them.
- Simos-Suite — VAG ECU/TCU tooling + CP work (drives Cerberus directly)
- esp32-isotp-ble-bridge-c7vag — the prior-gen BLE bridge (archived — superseded by Cerberus)
- VAG-CP-Docs — Component Protection research
MIT. Built for owners.