Skip to the content
2 layers100 × 80 mmMeganecan gw, the printed circuit board in 3D

Meganecan gw

Barbedx/MeganeCAN3

ESP32-based CAN-bus companion for Renault Megane 2 infotainment displays.

Size
100 × 80 mm
Copper layers
2
Thickness
1.6 mm
Parts
68
Nets
76
Pads
236
Tracks, vias
573, 379
Pours
2
Routed
Yes
KiCad
KiCad 10.0 (file 20260206)
Created
2 Apr 2025
Updated
1 Oct 2026

Overview

Meganecan gw is an open-source printed circuit board by Barbedx, from the Barbedx/MeganeCAN repository on GitHub. It is a 2-layer board of 100 × 80 mm with 68 parts on 76 nets, routed with 573 tracks and 379 vias.

It is built around the SN65HVD230D, LM2596S-3.3 and ESP32-WROVER-IB, with the NUP2105L, 1812L110/16V and SMBJ24A among its other parts. Its parts include 18 capacitors, 16 resistors, 8 diodes, 7 connectors and 5 ICs. It belongs with the ESP32 and RISC-V boards of this library.

Main components

9

README

README.md

MeganeCAN

ESP32-based CAN-bus companion for Renault Megane 2 infotainment displays.

Connects an ESP32 to the car CAN bus and acts as a smart sidecar: renders custom menus and text on the OEM display, bridges phone media controls via Bluetooth (Apple Media Service or BLE Keyboard), exposes a web UI for live configuration, and supports OTA firmware updates. Optional ELM327/OBD diagnostics via WiFi.

🇺🇦 When you use this project, you automatically support Ukraine.

⚠️ Safety: hobby project for off-road / testing use. Do not interact with menus while driving.


Features

CategoryDetails
CAN bus500 Kbps, GPIO 3 (RX) / GPIO 4 (TX). Listens to OEM frames and replies to sync/ack.
DisplayEmulates the head-unit radio over ISO-TP-style CAN to drive the OEM display. Two radio protocols: Carminat (large monochrome display only) and UpdateList (both the monochrome updatelist_menu and the 8-segment updatelist).
BluetoothAMS mode: advertises as a BLE peripheral — the iPhone pairs from Settings → Bluetooth, then the ESP reads now-playing (AMS), notifications (ANCS) and clock (CTS) as a GATT client over that link. Keyboard mode: BLE HID peripheral.
Web UIPsychicHttp async server on port 80. Configure display type, BT mode, text, time.
OTAElegantOTA at /update.
DiagnosticsELM327 over WiFi STA (V-LINK adapter). Queries ECUs and exposes JSON at /api/live.

Hardware

Minimum:

  • ESP32-C3 (or ESP32) development board
  • CAN transceiver — SN65HVD230 / TJA1050 / MCP2551
  • Wiring to CAN-H, CAN-L, GND, and a fused 5 V supply

Do not add a second 120 Ω terminator if the bus is already terminated.

Pinout (ESP32-C3):

SignalGPIO
CAN RX3
CAN TX4

Architecture

Class diagram

text
IDisplay  (pure virtual interface)
│  tick(), recv(), processEvents()
│  setText(), setState(), setTime()
│  showMenu(), ProcessKey(), onKeyPressed()
│
└── AffaDisplayBase  (abstract — ISO-TP CAN send engine)
      │  affa3_send() / affa3_do_send()
      │  funcs[] array  (registered CAN function IDs + ack state)
      │  _sync_status   (FAILED | PEER_ALIVE | START | FUNCSREG)
      │  setMediaInfo()  — virtual no-op, overridden by subclasses
      │  tickMedia()     — virtual no-op, overridden by subclasses
      │  setKeyHandler() — installs the key callback
      │
      ├── Affa2Base  (Affa2 CAN protocol — basic text, fallback default)
      │     funcs: { 0x121 SETTEXT, 0x1B1 DISPLAY_CTRL }
      │     sync ping: 0x3DF   filler: 0x81
      │     │
      │     ├── Affa2Display      (8-segment — adds media title scroll)
      │     │     + setMediaInfo()  builds "Artist - Title" string
      │     │     + tickMedia()     scrolls 8-char window every 400 ms
      │     │
      │     └── Affa2MenuDisplay  (full-LED — stub, same CAN protocol)
      │
      └── Affa3NavDisplay  (Affa3Nav "Carminat" — full nav display)

Main loop data flow

text
 ┌──────────┐   CAN frame   ┌──────────────────┐
 │ CAN bus  │ ────────────► │ gotFrame()        │
 └──────────┘               │ → display->recv() │
                            └──────────────────┘

 ┌───────────────┐  MediaInformation  ┌─────────────────────────┐
 │ BLE / AMS     │ ─────────────────► │ onDataUpdateCallback()  │
 │ (iPhone)      │                    │ → display->setMediaInfo()│
 └───────────────┘                    └─────────────────────────┘

 loop()
   ├── serialCommands.ReadSerial()
   ├── ElegantOTA.loop()
   ├── Bluetooth::Service()          (AMS mode)
   ├── display->tickMedia()          (non-blocking scroll)
   ├── auto-time sync via CTS        (AMS mode, once per connection)
   ├── display->processEvents()
   └── elmManager->tick()            (OBD, when WiFi connected)

BT key mapping

Physical keyAMS actionBLE Keyboard HID
PauseTogglePlayPauseKEY_MEDIA_PLAY_PAUSE
RollUpNextTrackKEY_MEDIA_NEXT_TRACK
RollDownPreviousTrackKEY_MEDIA_PREVIOUS_TRACK
VolumeUp (hold)VolumeUp ×15KEY_MEDIA_VOLUME_UP

CAN packet IDs

Affa2 protocol (Affa2Base / Affa2Display / Affa2MenuDisplay)

ConstantIDDirectionPurpose
PACKET_ID_SYNC0x3DFESP32 → displayAlive ping
PACKET_ID_SYNC_REPLY0x3CFdisplay → ESP32Sync ack
PACKET_ID_SETTEXT0x121ESP32 → displaySet text
PACKET_ID_DISPLAY_CTRL0x1B1ESP32 → displayEnable (0x02) / Disable (0x00)
PACKET_ID_KEYPRESSED0x0A9display → ESP32Key press event
PACKET_REPLY_FLAG0x400—OR-mask on ID for ack frames
PACKET_FILLER0x81—Padding byte

Affa3Nav (Carminat)

ConstantIDDirectionPurpose
PACKET_ID_SYNC0x3AFESP32 → displayAlive ping
PACKET_ID_SYNC_REPLY0x3CFdisplay → ESP32Sync ack
PACKET_ID_SETTEXT / DISPLAY_CTRL0x151ESP32 → displayText + enable/disable (same ID, different payload)
PACKET_ID_NAV0x1F1ESP32 → displayMenu / nav data
PACKET_ID_KEYPRESSED0x1C1display → ESP32Key press event
PACKET_FILLER0x00—Padding byte

Scroll lock indicator byte (Affa3Nav showMenu):

ValueMeaning
0x00No scroll arrows
0x07Scroll up arrow
0x0BScroll down arrow
0x0CBoth arrows

Display types

Selected via NVS key config/display_type. Takes effect after restart.

NVS valueClassNotes
carminat (default)CarminatDisplayCarminat protocol → large monochrome display: full media screen + interactive menu + notification popups
updatelist_menuUpdateListMenuDisplayUpdateList protocol → monochrome LCD (menu); extends UpdateListDisplay
updatelistUpdateListDisplayUpdateList protocol → 8-segment display; scrolls "Artist - Title" (8-char window, 400 ms/step) via AMS
(other / unset)UpdateListBaseUpdateList protocol base — fallback

Note: older docs/sections below may still use the pre-rename class names (Affa2*/Affa3Nav*) and values (affa2/affa3). The authoritative model is the matrix above and CLAUDE.md.


NVS configuration keys

NamespaceKeyTypeDefaultValues
configdisplay_typeStringaffa3navaffa2 / affa2menu / affa3 / affa3nav
configbt_modeStringamsams / keyboard
configauto_timeBooltrueSync display clock from phone via CTS
displayautoRestoreBoolfalseRestore last text on boot
displaylastTextString—Last text shown (restored on boot)
displaywelcomeTextString—Scroll on boot (overrides default)

Build & flash

bash
# Build
pio run

# Flash over USB
pio run -t upload

# Serial monitor (115200 baud)
pio device monitor

# Build + flash in one step
pio run -t upload && pio device monitor

Target: esp32-c3-devkitm-1 (see platformio.ini).

WiFi / secrets

Edit src/secrets.h (not tracked by git):

cpp
#define Soft_AP_WIFI_SSID "MeganeCAN"
#define Soft_AP_WIFI_PASS "yourpassword"

The file is ignored via:

bash
git update-index --assume-unchanged src/secrets.h

Web UI (http://<device-ip>/)

RouteMethodPurpose
/GETMain configuration page
/setDisplayPOSTSet display type, restarts ESP32
/setbtmodePOSTSet BT mode + auto_time, restarts ESP32
/config/restoreGETRead (?enable absent) or write (?enable=1/0) auto-restore flag
/staticGETShow static text (?text=, ?save to persist)
/scrollGETScroll text (?text=, ?save to save as welcome)
/settimeGETSet display clock (?time=HHMM)
/setVoltageGETDebug voltage override
/getdisplaytypeGETReturns current NVS display_type string
/getbtmodeGETReturns current NVS bt_mode string
/getautotimeGETReturns 1 or 0
/getlasttextGETReturns last saved text
/getwelcometextGETReturns saved welcome text
/api/liveGETELM327 diagnostic snapshot JSON
/affa3/setMenuGETSend menu frame (?caption=, ?name1=, ?name2=, ?scrollLock=)
/affa3testGETAffa3 display test page
/emulateGETButton emulation page
/emulate/keyPOSTInject a key press (?key=, ?hold=)
/updateGETElegantOTA firmware upload

Serial commands (115200 baud)

CommandExampleEffect
eeEnable display
ddDisable display
st <HHMM>st 1430Set display clock
msr <text> [delay_ms]msr HELLO 300Scroll text right
msl <text> [delay_ms]msl MEGANE 250Scroll text left

ELM327 / OBD diagnostics

Note: ELM327 connection is currently disabled in firmware (connectToElm() is commented out). The infrastructure is complete but needs WiFi STA / BLE coexistence tuning before re-enabling.

WiFi STA connects to V-LINK (open network, TCP 192.168.0.10:35000). Cycles through PID plans querying:

CAN headerECU
7E0Engine
743Gearbox / transmission
744HVAC
745Additional
74DGearbox / alternator

Results exposed at GET /api/live as JSON. To add a PID plan: create PidPlan_XXX.h and register it in buildCombinedPlan() inside MyELMManager.h.


Source layout

text
src/
├── main.cpp                    — setup(), loop(), HandleKey(), initDisplay()
├── apple_media_service.cpp/.h  — BLE AMS client (NimBLE)
├── bluetooth.cpp/.h            — BLE scan + connect + CTS
├── current_time_service.cpp/.h — BLE Current Time Service (clock sync)
├── secrets.h                   — WiFi credentials (not tracked)
│
├── display/
│   ├── IDisplay.h              — pure virtual display interface
│   ├── AffaDisplayBase.cpp/.h  — ISO-TP CAN engine, sync state machine
│   ├── AffaCommonConstants.h   — shared enums (AffaKey, AffaError, SyncStatus…)
│   │
│   ├── Affa2/
│   │   ├── Affa2Constants.h         — Affa2 CAN packet IDs + namespace
│   │   ├── Affa2Base.cpp/.h         — Affa2 protocol implementation (base class)
│   │   ├── Affa2Display.h/.cpp      — 8-segment variant, Spotify title scroll
│   │   └── Affa2MenuDisplay.h       — full-LED variant stub
│   │
│   └── Affa3Nav/
│       ├── Affa3NavConstants.h
│       ├── Affa3NavDisplay.cpp/.h   — full media screen + menu system
│       ├── AuxModeTracker.cpp/.h
│       └── Menu/
│           ├── Menu.cpp/.h          — hierarchical menu (Field, List, MultiField)

Parts

68
QtyValueFootprintReferences
9100nF/50V X7RCapacitor_SMD:C_0805_2012Metric_Pad1.18x1.45mm_HandSolderC2, C5, C6, C8, C10, C12, C13, C15, C18
41kResistor_SMD:R_0805_2012Metric_Pad1.20x1.40mm_HandSolderR7, R13, R14, R15
460.4RResistor_SMD:R_0805_2012Metric_Pad1.20x1.40mm_HandSolderR9, R10, R11, R12
4MountingHole_3.2mm_M3MountingHole:MountingHole_3.2mm_M3H1, H2, H3, H4
310kResistor_SMD:R_0805_2012Metric_Pad1.20x1.40mm_HandSolderR2, R3, R4
310uF/16V X5RCapacitor_SMD:C_0805_2012Metric_Pad1.18x1.45mm_HandSolderC4, C9, C11
20RResistor_SMD:R_0805_2012Metric_Pad1.20x1.40mm_HandSolderR5, R6
2100uF/50VCapacitor_THT:CP_Radial_D10.0mm_P5.00mmC1, C16
2NUP2105LPackage_TO_SOT_SMD:SOT-23D7, D8
2SN65HVD230Package_SO:SOIC-8_3.9x4.9mm_P1.27mmU3, U4
110uF/50V X7R 1210Capacitor_SMD:C_1210_3225Metric_Pad1.33x2.70mm_HandSolderC17
11812L110/16Fuse:Fuse_1812_4532Metric_Pad1.30x3.40mm_HandSolderF1
1220RResistor_SMD:R_0805_2012Metric_Pad1.20x1.40mm_HandSolderR8
1220uF/6.3V low-ESRCapacitor_THT:CP_Radial_D6.3mm_P2.50mmC3
122uF/10V X7R 1210Capacitor_SMD:C_1210_3225Metric_Pad1.33x2.70mm_HandSolderC7
133uH/3AInductor_SMD:L_12x12mm_H8mmL1
1470RResistor_SMD:R_0805_2012Metric_Pad1.20x1.40mm_HandSolderR16
147kResistor_SMD:R_0805_2012Metric_Pad1.20x1.40mm_HandSolderR1
147uF/10VCapacitor_THT:CP_Radial_D5.0mm_P2.00mmC14
1BLULED_SMD:LED_0805_2012Metric_Pad1.15x1.40mm_HandSolderD5
1BOOTButton_Switch_THT:SW_PUSH_6mmSW2
1BUCK-ALTConnector_PinHeader_2.54mm:PinHeader_1x04_P2.54mm_VerticalJ7
1ESP32-C3 SuperMiniMeganeCAN:MODULE_ESP32-C3-SUPERMINIU2
1ESP32-WROVER-IBMeganeCAN:ESP32-WROVER-IBU1
1EXPConnector_PinHeader_2.54mm:PinHeader_1x12_P2.54mm_VerticalJ6
1GRNLED_SMD:LED_0805_2012Metric_Pad1.15x1.40mm_HandSolderD4
1HUConnector_JST:JST_XH_B3B-XH-A_1x03_P2.50mm_VerticalJ4
1JIGConnector_PinHeader_2.54mm:PinHeader_1x06_P2.54mm_VerticalJ5
1LM2596S-3.3Package_TO_SOT_SMD:TO-263-5_TabPin3U5
1MMCANTerminalBlock_Phoenix:TerminalBlock_Phoenix_MKDS-1,5-3-5.08_1x03_P5.08mm_HorizontalJ3
1MMCAN-TERM-HJumper:SolderJumper-2_P1.3mm_Open_RoundedPad1.0x1.5mmJP3
1MMCAN-TERM-LJumper:SolderJumper-2_P1.3mm_Open_RoundedPad1.0x1.5mmJP5
1Mini-360 (MP2307)MeganeCAN:MINI360_4pad_SocketPS1
1PWRTerminalBlock_Phoenix:TerminalBlock_Phoenix_MKDS-1,5-3-5.08_1x03_P5.08mm_HorizontalJ1
1RESETButton_Switch_THT:SW_PUSH_6mmSW1
1SMBJ24ADiode_SMD:D_SMB_HandsolderingD2
1SRC-SELJumper:SolderJumper-3_P1.3mm_Bridged12_RoundedPad1.0x1.5mmJP1
1SS34Diode_SMD:D_SMA-SMB_Universal_HandsolderingD3
1SS36Diode_SMD:D_SMA-SMB_Universal_HandsolderingD6
1SS56Diode_SMD:D_SMA-SMB_Universal_HandsolderingD1
1VCANTerminalBlock_Phoenix:TerminalBlock_Phoenix_MKDS-1,5-3-5.08_1x03_P5.08mm_HorizontalJ2
1VCAN-TERM-HJumper:SolderJumper-2_P1.3mm_Open_RoundedPad1.0x1.5mmJP2
1VCAN-TERM-LJumper:SolderJumper-2_P1.3mm_Open_RoundedPad1.0x1.5mmJP4

Similar boards

  • 2 layers95 × 75 mmSmart Cabinet Fan, the printed circuit board in 3D

    Smart Cabinet Fan

    ginomcfino/smart-fan-controller

    Dual-fan controller with temperature sensing, tachometer monitoring, overtemperature alarm, and manual override. Designed in KiCad.

    61 parts
  • 4 layers65 × 56 mmROCK 5 CANbus HAT, the printed circuit board in 3D

    ROCK 5 CANbus HAT

    eiten/rock5b_can_pwr

    Radxa Rock 5B CANbus and Power HAT

    41 parts
  • 4 layers100 × 51.5 mmSTM32 CONTROLLER DESIGN, the printed circuit board in 3D

    STM32 CONTROLLER DESIGN

    shreyadascoder2004/stm32-can-modbus-gateway

    4-layer STM32 industrial gateway bridging RS-485/Modbus-RTU and CAN — KiCad schematic/layout + C firmware

    51 parts1MIT
  • 2 layers92 × 92 mmPerfect Vision ECU, the printed circuit board in 3D

    Perfect Vision ECU

    phellberg76/Perfect-Vision-ECU

    Perfect Vision ECU - KiCad hardware design

    59 parts
  • 4 layers88.5 × 33.8 mmFoba, the printed circuit board in 3D

    Foba

    MarvinAnner/Foba_PCB

    58 parts
  • 2 layers74 × 32 mmColorSensor01, the printed circuit board in 3D

    ColorSensor01

    nevzathipertek/color_sensor

    99 parts1
  • 2 layers139.9 × 85.8 mmPas hw, the printed circuit board in 3D

    Pas hw

    NGOQUOC98/ESP32_MODBUS_PCB_MASTER

    This project was in development phase - we will remove this remark after release

    198 parts1
  • 4 layers92.1 × 62.8 mmMotor controller, the printed circuit board in 3D

    Motor controller

    mohamed-belal-10/can-motor-controller-node

    CAN-connected closed-loop DC motor controller node — KiCad hardware (STM32F103, DRV8874, SN65HVD230) with an AS5600 encoder sub-PCB

    55 partsMIT

Questions

How big is the Meganecan gw PCB?

It measures 100 × 80 mm and has 2 copper layers, 1.6 mm thick.

What parts does the Meganecan gw use?

68 parts, built around the SN65HVD230D, LM2596S-3.3, ESP32-WROVER-IB and NUP2105L. The full list of its parts is on this page, with links to each part's symbol and footprint where PCBKit's library has them.

Where can I download the Meganecan gw KiCad files?

From the Barbedx/MeganeCAN repository on GitHub, at the commit this page was made from: the board is hardware/meganecan-gw/meganecan-gw.kicad_pcb and its schematic hardware/meganecan-gw/meganecan-gw.kicad_sch. PCBKit opens the board in the browser, with nothing to install.

Can I use the Meganecan gw design in my own project?

Its repository states no licence, so its author keeps all rights: ask Barbedx before reusing the design.