[ Sniffing CAN bus with the ESP32 Bit-Pirate ]

The ESP32 Bit-Pirate is a Bus-Pirate-style multi-tool firmware for the
ESP32-S3, a web-based CLI that speaks a pile of protocols, CAN among
them. Bolt an MCP2515 module onto it and you have a pocket CAN sniffer
that captures to its own flash and hands the log off over Wi-Fi. No
laptop tethered to a SocketCAN adapter, no dashboard torn apart.

This walks the whole loop: wire it to the bus, sniff, pull the capture
off, and open it in SavvyCAN for decoding, using the two small
converter scripts from the Scripts section. New to CAN itself? Read
the generic CAN bus introduction first; this guide assumes you know
what an arbitration ID and a data frame are.

[ What you need ]

  — An ESP32-S3 board with at least 8 MB of flash — a plain S3
    DevKit, LILYGO T-Embed, M5 Cardputer, Seeed Xiao S3, etc. (any S3
    that Bit-Pirate supports).
  — An MCP2515 CAN module — the common blue SPI board pairing an
    MCP2515 controller with a TJA1050 transceiver, CANH/CANL screw
    terminals, and an INT pin. Bit-Pirate drives CAN through this,
    not the ESP32's built-in controller.
  — Jumper wires and a way onto the bus — an OBD-II breakout, or
    a tap on the vehicle connector you're after.
  — A host machine with Python 3 and SavvyCAN for the analysis end.
The 3.3 V / 5 V gotcha. Most MCP2515 modules are 5 V parts, and
the ESP32 is 3.3 V. Powering the whole module at 5 V feeds 5 V back
out of the MCP2515's SO (MISO) pin into a 3.3 V ESP32 input, over
spec. The clean fixes are a logic-level shifter on the SPI lines, or
the well-documented module mod that runs the MCP2515 at 3.3 V while
keeping the TJA1050 at 5 V. Bench-test on a spare bus before you trust
it on a vehicle.

[ Wiring the MCP2515 ]

The module talks to the ESP32 over SPI, and to the car over CANH / CANL.

SPI side: module → ESP32-S3

    VCC    3.3 V / 5 V   (see the gotcha above)
    GND    GND
    CS     chip-select GPIO
    SO     MISO GPIO
    SI     MOSI GPIO
    SCK    SCK GPIO
    INT    interrupt GPIO   (optional but recommended)

Pick free, SPI-capable GPIOs on your board; the exact pins vary by
S3 model, and you tell Bit-Pirate which ones you used with config
(next section), so there's no fixed mapping to memorize. Keep the SPI
leads short.

Bus side: module → vehicle

    CANH   →  CAN High
    CANL   →  CAN Low

On the OBD-II connector that's pin 6 (CAN-H) and pin 14 (CAN-L) for
the powertrain bus. On a JEEP you can also tap CAN-C or CAN-IHS behind
the glovebox; see the Jeep CAN primer for those connectors.

Termination. These modules ship with a 120 Ω resistor across
CANH/CANL. A vehicle bus is already terminated at both ends, so a
third 120 Ω tap loads it down; for a short read-only sniff it's
usually tolerated, but if the bus gets cranky, lift that resistor
(cut the jumper or desolder it).
Wiring the MCP2515 module to the ESP32-S3 and the vehicle bus The MCP2515 CAN module connects to the ESP32-S3 over seven SPI lines: VCC to 3.3 or 5 volts, GND to GND, CS to a chip-select GPIO, SO to MISO, SI to MOSI, SCK to SCK, and INT to an interrupt GPIO. On the bus side the module's CANH goes to OBD-II pin 6 (CAN High) and CANL to OBD-II pin 14 (CAN Low). MCP2515 SPI CAN module VCC GND CS SO SI SCK INT ESP32-S3 SPI GPIOs (your pick) 3.3 / 5 V GND CS MISO MOSI SCK INT SPI INT optional to the vehicle bus CANH → OBD-II pin 6 (CAN High) CANL → OBD-II pin 14 (CAN Low)
The MCP2515 talks to the ESP32-S3 over SPI — seven lines, where SO is MISO and SI is MOSI, and INT is optional — and to the car over CANH / CANL. Which GPIOs you use is your call; you declare them in config. Mind the 3.3 V / 5 V gotcha on the power line (orange).

[ Flash & connect ]

Flash ESP32-Bit-Pirate onto the S3 the same way as any S3 firmware:
follow the install steps in the project's repo. (For a refresher on
browser-based ESP32 flashing in general, the ESP32 web flasher
write-up covers the mechanics.)

Once it's running, reach the web CLI one of two ways:

  — Its own Wi-Fi AP — join the Bit-Pirate access point, then point
    a browser at http://192.168.4.1/ (the ESP32 SoftAP default).
  — USB serial — open the serial console at the firmware's baud for
    the same command prompt.

Everything below is typed at that Bit-Pirate prompt.

[ Configure the CAN mode ]

Enter CAN mode, then use config to set the SPI pins you wired and
the bus bitrate. Bit-Pirate supports 125, 250, 500, and 1000 kbps and
snaps whatever you give it to the nearest one.

Match the bitrate to the bus you're on; guess wrong and you'll see
nothing (or error frames):

    500 kbps    OBD-II powertrain / most modern CAN-C
    125 kbps    JEEP CAN-IHS (interior / comfort bus)
    250 kbps    a lot of J1939 / medium-duty

The on-site Bus & Message Reference lists which JEEP bus runs at
which speed. When in doubt, try 500 first; a correctly-clocked idle bus
still shows steady broadcast traffic within a second or two.

    config        set SPI pins (CS / SO / SI / SCK) and CAN speed
    status        show MCP2515 controller state and error flags

Run status after config; if the MCP2515 isn't wired right or the
bitrate is wrong, its error flags light up here before you waste time
staring at an empty sniff.

[ Sniff the bus ]

With pins and speed set, start the capture:

    sniff         capture and print every CAN frame
    send [id]     transmit a frame        (writes to the bus; see Safety)
    receive [id]  wait for a specific ID

sniff is passive; it only listens, so it's the safe place to start.
Frames print one per line, and the format is exactly what the converter
script downstream expects:
📥 26 | ID: 0x123 | DLC: 8 | Data: AA BB 01 02 03 04 05 06
— a running counter, the arbitration ID, the data length, and the
payload bytes. Let it run while you exercise the thing you're chasing
(press a button, open a door, blip the throttle) so the frames that
change get captured. Bit-Pirate writes the session to its LittleFS
flash, browsable and downloadable from the web interface.
Passive, but it still ACKs. sniff never sends data frames, so it's
safe on a live vehicle — but a normal-mode MCP2515 still hardware-ACKs the
frames it hears. On a real multi-node bus that's invisible. It only matters
two ways: on a bench bus with one lone transmitter, those ACKs are what let
it keep sending; and if you want a truly silent tap, use the MCP2515's
listen-only mode if the firmware exposes it — it receives without ACKing.

[ When you see nothing ]

An empty sniff on a bus you know is live almost always comes down to
one of these, roughly in the order they bite:

  crystal mismatch   The big one. Those blue MCP2515 modules ship with
                     either an 8 MHz or a 16 MHz crystal, and the driver
                     has to know which. Tell it 16 MHz when the board really
                     carries an 8 MHz can (or the reverse) and every bitrate
                     is off by 2× — you ask for 500k and actually run 250k,
                     so you see nothing or a wall of error frames. Read the
                     little silver can on the module (it's stamped 8.000
                     or 16.000) and set Bit-Pirate to match.
  wrong bitrate      Right crystal, wrong speed. Re-check it against
                     the bus (500 OBD-II, 125 JEEP CAN-IHS, 250 much J1939)
                     and watch status for error flags.
  CANH / CANL swap   High and low reversed. The bus won't talk to you;
                     swap the two and retry.
  wrong bus          You're clipped onto a bus that's genuinely idle,
                     or not the one carrying what you're chasing. Move the tap.
  MCP2515 not seen   status shows the controller unreachable — an
                     SPI wiring fault (CS/SO/SI/SCK on the wrong
                     GPIOs, or the pins you set in config don't match how
                     you actually wired it), or no power to the module.
  termination        Rare on a vehicle (already terminated at both
                     ends), but on a bench bus you need 120 Ω at each
                     end or the signalling never resolves.

Lean on status: it reports the MCP2515's mode and error counters, and a
bus that's clocked wrong or miswired lights those up at once — far faster
than staring at a silent capture.

[ Get the capture onto your machine ]

You could copy the serial text into a file by hand, but the tidy way is
to pull it straight off the device's flash over Wi-Fi with
bp_fetch.py: stdlib Python, nothing to install:

    python3 bp_fetch.py 192.168.4.1 --list                 list the flash
    python3 bp_fetch.py 192.168.4.1 --file can_capture.txt  download it

It hits Bit-Pirate's /littlefs/list and /littlefs/download HTTP
endpoints, so run it on the same network as the device (or joined to its
AP). Both scripts live in the Scripts section below and in the
bitpirate-to-savvycan repo.

[ Convert to SavvyCAN ]

SavvyCAN doesn't read Bit-Pirate's log directly, so run it through
bitpirate_to_savvycan.py, which rewrites it as a SavvyCAN-native
(GVRET) CSV:

    python3 bitpirate_to_savvycan.py can_capture.txt

That writes can_capture_savvy.csv. Or skip a step: bp_fetch.py
with --convert downloads and converts in one shot:

    python3 bp_fetch.py 192.168.4.1 --file can_capture.txt --convert

One catch: timing is synthetic. Bit-Pirate's sniff log carries no
per-frame timestamp, so the converter spaces frames a fixed
--interval apart (default 1 ms). Frame order is preserved, but the
absolute and relative timing is fabricated, perfect for decoding IDs
and payloads, DBC work, and ASCII scans; do not read bus-timing or
inter-frame gaps off it.

Pass a DBC with --dbc car.dbc and it also drops a <out>_decoded.txt
sidecar of human-readable signals (scalar signals, Intel or Motorola
byte order, signed/unsigned, factor/offset).

[ Analyze in SavvyCAN ]

Open SavvyCAN and load the CSV with File → Load Logs; pick the
GVRET/CSV format. From there the usual moves:

  — Filter by ID to isolate one module's chatter from the flood.
  — Sort / group to spot which IDs change when you actuated something.
  — Load a .dbc (File → Load DBC) to decode raw bytes into named
    signals; SavvyCAN handles the full DBC spec, multiplexing and all,
    where the sidecar decoder above only does plain scalars.
  — Graph a signal over the capture to watch it move.

Cross-reference anything you find against the Bus & Message Reference
for IDs that are already decoded, and remember the timing caveat: the
X-axis is frame order, not real seconds.

[ Safety ]

sniff is read-only and safe on a live vehicle. send and receive
are not; send puts real frames on the bus, and blasting arbitration
IDs at a moving car is how you set faults, drop modules into limp mode,
or worse. Capture and understand first; only transmit once you know what
an ID does and you're parked with nothing depending on the bus.

Writing the wrong bytes can require a dealer reflash to undo. The
CAN bus intro covers the general safe-practice rules; they all apply here.

[ Scripts ]

[ FAQ ]

Why does my ESP32 + MCP2515 CAN sniffer show no data?
Most often a crystal mismatch: the module has an 8 MHz or 16 MHz crystal and
the firmware must be told which, or every bitrate runs at half/double and you
capture nothing. After that it's the wrong bitrate, swapped CANH/CANL, or an
SPI fault — run status for the MCP2515 error flags. Full list in
When you see nothing.

What bitrate should I use for OBD-II?
500 kbps for the OBD-II powertrain bus on essentially all modern cars.
JEEP CAN-IHS runs 125 kbps and much J1939 medium-duty is 250 kbps; try 500
first and watch for steady broadcast traffic within a second or two.

Does my MCP2515 module have an 8 MHz or 16 MHz crystal?
Read the metal can on the board — it's stamped 8.000 or 16.000. The
common blue modules ship with both, so check yours rather than assume; the
wrong setting is the number-one reason a sniff comes up empty.

Is sniffing CAN on a live car safe?
sniff only listens, so yes — it never puts data frames on the bus.
send is the dangerous one: the wrong frames can set faults or drop modules
into limp mode. Capture and understand before you ever transmit.

Can I use the ESP32's built-in CAN controller instead of an MCP2515?
Yes — the ESP32 has a native TWAI (CAN 2.0) controller that only needs a
3.3 V transceiver like an SN65HVD230, no MCP2515. Bit-Pirate drives the
MCP2515 route; the native path is a good DIY option, noted in the ESP32
development guide.

[ See Also ]