GitHunt

FIS-Display

VW Passat B6 FIS/MFA Display — Hardware Connections and Software Protocols.

Disclaimer

The creator takes no responsibility for damages, immobile car, injury, or any other loss arising from the use of this project, its firmware, PCB design, or documentation. Use at your own risk.

What this project does

Navit is an open-source, modular turn-by-turn navigation engine that can run on Linux, Android, and other platforms. This project uses Navit on a host device (with D-Bus enabled) to drive the car's FIS/MFA display.

This repository provides firmware, PCB design, and documentation to show Navit navigation (and optionally media, call, clock) on the VW Passat B6 (3C) FIS/MFA display. A host device runs Navit with D-Bus and sends a simple serial protocol to a Raspberry Pi Pico 2 W over USB or Bluetooth (pair once as FIS-Bridge). The Pico injects frames onto the car's 3LB bus during idle gaps so the cluster shows turn-by-turn directions, street name, distance, and maneuver icons; when not navigating, it can show media track info, incoming call caller ID, or a clock screen with GPS time, ETA (e.g. ARR 14:32), remaining distance, and compass heading. Optional CAN bus support (MCP2561, disabled by default) is included when the firmware functions for that is finished. The Pico is a middleman only; all navigation logic stays on the host. When CAN bus is enabled, it can adjust your clock automatically. A separate display-test firmware is provided for bench-testing the cluster without a Navit host.

PCB and schematic are designed in KiCad. Design files and Gerbers are in pcb-files/. See Gerber files and PCB manufacturing for what they are and how to use them.

Note: The firmware in firmware/ is experimental and untested on a real vehicle. Validate on the bench before connecting to the car. See firmware/README.md for build and flash instructions.

Gerber files and PCB manufacturing

What are Gerber files? Gerber is the standard format used by PCB manufacturers to produce printed circuit boards. Each file describes one layer of the board (copper, solder mask, silkscreen, etc.) as vector graphics. The manufacturer combines these files to fabricate and assemble the PCB. This repository provides Gerbers exported from the KiCad project in pcb-files/FIS-display/.

What is in this repo: In pcb-files/FIS-display/gerbers/ you will find:

  • Copper layers: F_Cu.gbr (top copper), B_Cu.gbr (bottom copper)
  • Solder mask: F_Mask.gbr, B_Mask.gbr
  • Silkscreen: F_Silkscreen.gbr, B_Silkscreen.gbr
  • Solder paste (SMD stencil): F_Paste.gbr, B_Paste.gbr
  • Board outline: Edge_Cuts.gbr
  • Drill files: *.drl (through-hole and non-plated drill data)

How to use them:

  1. Ordering PCBs: Zip the contents of the gerbers/ folder (all .gbr and .drl files, and the .gbrjob file if the manufacturer supports it) and upload the zip to a PCB fab (e.g. JLCPCB, PCBWay, OSH Park, or similar). Select the correct units (usually mm) and board thickness; the fab will use the Gerbers to produce the boards. Use the same BOM as in firmware/BOM.md for sourcing components.

  2. Viewing without KiCad: You can inspect the layers with a Gerber viewer (e.g. GerberView, or the online viewer many fabs provide) to check traces, pads, and outline before ordering.

  3. Editing the design: Open the KiCad project in pcb-files/FIS-display/ (.kicad_pro, .kicad_sch, .kicad_pcb) in KiCad. After any change, re-export Gerbers from KiCad (File → Plot, then generate drill files) and replace the files in gerbers/ before ordering new boards.


Table of contents


Project structure

This repository uses no symlinks; all paths are normal directories and files so that every system can clone and use it without symlink support.

Item Description
firmware/ Main Pico 2 W firmware: 3LB RX/TX, serial protocol, nav/media/clock injection, graphics.
firmware/README.md Build, flash, pinout, and firmware behaviour.
firmware/BOM.md Bill of materials for the PCB.
firmware-display-test/ Standalone display test firmware: cycles through all modes (nav text, icons, call, media, clock) every 4 seconds.
firmware-display-test/README.md How to build and use the display test.
nav-icons/ Navigation and status icon SVGs (sources for the bitmaps in firmware/fis_nav_icons.h). See nav-icons/README.md for adding new icons.
tools/ Build/convert helpers. tools/svg_to_fis_icon.py converts SVG to the 64x64 1-bit C array format used by the firmware.
PQ35_46_ACAN_KMatrix_V5.20.6F_20160530_MH.xlsx VW/Audi PQ35/46 CAN matrix (reference).
PQ35_46_ACAN_Glossary_DE_EN.md German–English translation table for the CAN matrix document.
pcb-files/ PCB design (KiCad) and Gerber files for manufacturing. See Gerber files and PCB manufacturing.

1. System Overview

┌──────────────────────────────────────────────────────────┐
│  Host device running Navit with D-Bus enabled            │
│                                                          │
│  Examples:                                               │
│  • Android head unit (Navit built with D-Bus support)    │
│  • Linux carputer / Raspberry Pi running Navit           │
│  • Any platform where libbinding_dbus.so is active       │
│                                                          │
│  D-Bus listener translates Navit signals to serial       │
│  protocol and forwards to Pico over USB or Bluetooth     │
└────────────┬─────────────────────────┬───────────────────┘
             │                         │
     USB CDC serial            Bluetooth SPP
     /dev/ttyACM0              (onboard CYW43439)
     (also powers Pico)        "FIS-Bridge" PIN 0000
             │                         │
             └──────────┬──────────────┘
                        │
               ┌────────▼─────────┐
               │ Raspberry Pi     │
               │ Pico 2 W         │
               │ (RP2350)         │
               │                  │
               │ Middleman:       │
               │ serial -> 3LB;   │
               │ optional: GPIO   │
               │ 11/12 -> MCP2561 │
               └────────┬─────────┘
                        │
         ┌──────────────┼──────────────┐
         │              │              │
         ▼              │              ▼
 3LB (ENA/CLK/DATA)     │      Optional CAN (if enabled):
 via BS170 level        │      GPIO 11 (TX), 12 (RX) ->
 shifters (3.3V<->5V)   │      MCP2561 (TXD/RXD) -> CAN-H/CAN-L
         │              │              │
         ▼              │              ▼
 ┌──────────────────┐   │   ┌──────────────────────────┐
 │ VW Passat B6 (3C) │   │   │ Comfort/infotainment CAN  │
 │ FIS/MFA cluster   │◄──┘   │ 100 kbit/s (when fitted) │
 │ 64x88 px, 1-bit   │       └──────────────────────────┘
 └────────┬──────────┘
          │
          ▼
 ┌───────────────────────┐
 │ Original ECU          │
 │ (talks 3LB natively,  │
 │  Pico co-exists via   │
 │  ENA arbitration)     │
 └───────────────────────┘

The Pico 2 W is a pure middleman. It has no navigation intelligence — it only receives
the serial protocol from the host device and injects the translated frames onto the 3LB bus.
All navigation logic stays on the host device running Navit. Optionally, when CAN is enabled
(see 5.6 Pico 2 W GPIO Pinout), the Pico can communicate with the
vehicle comfort/infotainment CAN (100 kbit/s) via GPIO 11/12 to the MCP2561 (TXD/RXD only).

The original ECU continues to talk to the FIS/MFA natively over 3LB at all times. The Pico
co-exists on the bus using the ENA line for arbitration — no relay or analog switch is needed.

Note: The OEM radio has been replaced with an aftermarket head unit. The head unit does
not communicate over 3LB. Only the original ECU talks to the FIS/MFA natively over 3LB.


2. Host Device — Navit with D-Bus

2.1 Supported Platforms

Navit can be built with D-Bus support (libbinding_dbus.so) on multiple platforms:

Platform Notes
Android head unit Navit built with D-Bus support. Android includes D-Bus (android_external_dbus)
Linux carputer Native D-Bus session bus, full support
Raspberry Pi (Linux) Native D-Bus session bus, full support
Any Linux-based system Native D-Bus session bus, full support

Enable D-Bus in navit.xml:

<plugin path="$NAVIT_LIBDIR/*/${NAVIT_LIBPREFIX}libbinding_dbus.so" active="yes"/>

2.2 D-Bus Service Details

Item Value
Service name org.navit_project.navit
Bus type Session bus
Main object /org/navit_project/navit/default_navit
Route object /org/navit_project/navit/default_navit/default_route
Vehicle object /org/navit_project/navit/default_navit/default_vehicle

2.3 Key Attributes to Subscribe To

Attribute Meaning
navigation_next_turn Upcoming maneuver type
navigation_distance_turn Metres to next turn
navigation_street_name Next street name
navigation_street_name_systematic Route number
navigation_status Routing state
position_speed Current speed
eta Estimated arrival (Unix timestamp); sent as NAV:ETA, shown in local time on FIS clock line
destination_length Remaining distance to destination (m); sent as NAV:REMAIN, shown on FIS clock line
position_direction Heading in degrees (0-360); sent as NAV:HEAD, shown as compass (N, NE, E, ...) on FIS
position_time_iso8601 GPS time UTC, format YYYY-MM-DDTHH:MM:SSZ (for FIS clock)
position_coord_geo GPS latitude/longitude (for timezone lookup; vehicle attribute)

2.4 Navigation Status Values

Value Meaning
no_route No active route
no_destination No destination set
position_wait Waiting for GPS
calculating Initial calculation
recalculating Rerouting
routing Active guidance

2.5 D-Bus Listener → Pico Bridge Script

This script runs on the host device and forwards Navit D-Bus signals to the Pico over either
USB CDC or Bluetooth SPP. Configure SERIAL_PORT to match your transport:

  • USB CDC: typically /dev/ttyACM0 (Linux/Android)
  • Bluetooth SPP: the rfcomm device created when paired to FIS-Bridge, e.g. /dev/rfcomm0
#!/usr/bin/env python3
"""
Navit D-Bus listener -> Pico 2 W serial bridge.
Runs on any platform where Navit is built with libbinding_dbus.so active:
Android (with D-Bus support), Linux carputer, Raspberry Pi, etc.
Transport: USB CDC or Bluetooth SPP -- configure SERIAL_PORT below.
"""

import serial
import dbus
import dbus.mainloop.glib
from gi.repository import GLib

SERIAL_PORT = "/dev/ttyACM0"   # USB CDC, or e.g. /dev/rfcomm0 for Bluetooth SPP
BAUD = 115200

WATCHED = [
    "navigation_next_turn",
    "navigation_distance_turn",
    "navigation_street_name",
    "navigation_status",
    "eta",
    "destination_length",      # remaining distance (m) -> NAV:REMAIN, shown on FIS clock line
    "position_direction",      # heading 0-360 -> NAV:HEAD, shown as compass (N, NE, E, ...)
    "position_time_iso8601",   # UTC GPS time for FIS clock (format YYYY-MM-DDTHH:MM:SSZ)
    "position_coord_geo",      # lat/lon for timezone lookup (vehicle attribute)
]

MANEUVER_MAP = {
    "straight":          "straight",
    "turn_left":         "turn_left",
    "turn_right":        "turn_right",
    "turn_slight_left":  "slight_left",
    "turn_slight_right": "slight_right",
    "turn_sharp_left":   "sharp_left",
    "turn_sharp_right":  "sharp_right",
    "u_turn":            "u_turn",
    "keep_left":         "keep_left",
    "keep_right":        "keep_right",
    "destination":       "destination",
}

class NavitBridge:
    def __init__(self):
        self.ser = serial.Serial(SERIAL_PORT, BAUD, timeout=1)
        dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
        bus = dbus.SessionBus()
        obj = bus.get_object("org.navit_project.navit",
                             "/org/navit_project/navit/default_navit")
        iface = dbus.Interface(obj, dbus_interface="org.navit_project.navit")
        cb_path = iface.callback_attr_new()
        for attr in WATCHED:
            iface.add_attr(cb_path, attr)
        bus.add_signal_receiver(
            self.on_update,
            signal_name="callback_attr_update",
            dbus_interface="org.navit_project.navit",
            path=cb_path,
        )

    def on_update(self, attr_name, value):
        msg = None
        if attr_name == "navigation_next_turn":
            code = MANEUVER_MAP.get(str(value), str(value))
            msg = f"NAV:TURN:{code}\n"
        elif attr_name == "navigation_distance_turn":
            msg = f"NAV:DIST:{int(value)}\n"
        elif attr_name == "navigation_street_name":
            msg = f"NAV:STREET:{str(value)[:20]}\n"
        elif attr_name == "navigation_status":
            msg = f"NAV:STATUS:{str(value)}\n"
        elif attr_name == "eta":
            msg = f"NAV:ETA:{int(value)}\n"
        elif attr_name == "destination_length":
            msg = f"NAV:REMAIN:{int(value)}\n"
        elif attr_name == "position_direction":
            msg = f"NAV:HEAD:{int(value)}\n"
        elif attr_name == "position_time_iso8601":
            msg = f"NAV:TIME:{str(value)}\n"
        elif attr_name == "position_coord_geo":
            # value is typically (lng, lat) or struct; adapt to your D-Bus binding
            try:
                lat, lon = float(value[1]), float(value[0])  # (lng, lat) from Navit
                msg = f"NAV:POS:{lat:.4f},{lon:.4f}\n"
            except (IndexError, TypeError, ValueError):
                pass
        if msg:
            self.ser.write(msg.encode())

    def run(self):
        GLib.MainLoop().run()

if __name__ == "__main__":
    NavitBridge().run()

3. Serial Protocol (Host → Pico, 115200 baud, newline-terminated ASCII)

The same protocol is used regardless of whether the transport is USB CDC or Bluetooth SPP.

Message Meaning
NAV:TURN:<code> Upcoming maneuver type
NAV:DIST:<metres> Distance to next turn
NAV:STREET:<n> Next street name (max 20 chars)
NAV:STATUS:<status> Navit routing status
NAV:ETA:<unix_ts> Estimated arrival (Unix timestamp); converted to local time and shown on clock line 2 as e.g. ARR14:32
NAV:REMAIN:<metres> Remaining distance to destination (from Navit destination_length); shown on clock line
NAV:HEAD:<degrees> Heading 0-360 (from Navit position_direction); shown as compass on clock line
NAV:TIME:<iso8601> GPS time UTC, e.g. 2026-03-14T12:34:56Z (from Navit position_time_iso8601)
NAV:POS:<lat>,<lon> GPS position in decimal degrees (from Navit position_coord_geo), e.g. 59.91,10.75
BT:TRACK:<info> Media track info from head unit
BT:CALL:<caller> Incoming call caller ID
BT:CALLEND Call ended

Feature toggles (clock screen): Send CFG:<name>:0 or CFG:<name>:1 to enable or disable what is shown. Clock/ETA/compass/remain default to 1 (on). CAN bus is supported by the firmware but disabled by default (0).

Message Effect
CFG:CLOCK:0 / CFG:CLOCK:1 Clock (time from GPS, set automatically); 0 = do not show clock on FIS
CFG:ETA:0 / CFG:ETA:1 ETA in local time on clock line 2 (e.g. ARR14:32)
CFG:COMPASS:0 / CFG:COMPASS:1 Compass heading (N, NE, ...) on clock line 2
CFG:REMAIN:0 / CFG:REMAIN:1 Remaining distance on clock line 2
CFG:CAN:0 / CFG:CAN:1 CAN bus support (supported by firmware, disabled by default). Enable with 1 when external MCP2561 hardware is fitted.

4. Transport — USB CDC or Bluetooth SPP

The host device can connect to the Pico over either transport. The Pico listens on both
USB CDC and Bluetooth SPP simultaneously.

USB CDC

  • Connect a USB cable from the host to the Pico USB port
  • Pico appears as /dev/ttyACM0 (Linux) or equivalent on Android
  • Also provides 5 V power to the Pico via VSYS — no separate power supply needed
  • Baud rate: 115200

Bluetooth SPP

  • Pico advertises as FIS-Bridge using classic Bluetooth SPP via BTstack (onboard CYW43439)
  • No external Bluetooth module required
  • Pair once: scan for FIS-Bridge, enter PIN 0000 if prompted — reconnects automatically
  • On Linux: Pico appears as /dev/rfcomm0 after pairing
  • Baud rate: 115200

If using Bluetooth only (no USB), the Pico needs a separate 5 V power source via the VSYS
pin — e.g. a car USB adapter or a 12 V → 5 V regulator on the PCB.


5. Hardware

5.1 Controller — Raspberry Pi Pico 2 W

Property Value
MCU RP2350 (dual-core Cortex-M33, 150 MHz)
RAM 520 KB SRAM
Flash 4 MB
Wireless Infineon CYW43439 — WiFi + classic Bluetooth
Logic level 3.3 V GPIO
Power 5 V via VSYS (USB or external)
DigiKey P/N SC0919

5.2 The 3LB (Three-Line Bus)

Wire Function Direction
ENA Enable / bus arbitration Bidirectional
DATA Serial data Master → Cluster
CLK Clock Master → Cluster

Bus runs at 5 V logic, 2300 Hz,Pico GPIO is 3.3 V — level shifting required on all lines.

Bus arbitration: Before transmitting, a master raises ENA to claim the bus. If ENA is already
high it waits. The cluster acknowledges with a 100 µs pulse. The Pico and ECU safely share the
bus without any relay or switch.

5.3 Level Shifting

BS170 N-channel MOSFET (TO-92, through-hole), one per 3LB GPIO line (3 RX + 3 TX).
Each channel uses two 2.2 kΩ pull-up resistors — one to 3.3 V, one to 5 V. Use non-inductive resistors (e.g. metal film or SMD thick/thin film); avoid wirewound types.

Part DigiKey P/N Qty
BS170 MOSFET TO-92 THT BS170-ND 6
2.2 kΩ resistor 0.25 W THT (non-inductive) 12

BS170 pinout (flat face toward you, left to right): Source — Gate — Drain.

5.4 Instrument Cluster Connector (Green T32a)

T32a Pin Signal
30 DATA
31 CLK
32 ENA

Connector type: Kostal MLK 1.2 / MQS 0.63 mm. Use a VAG pin tool to back-probe terminals.
Do not cut harness wires unless you are absolutely sure what you are doing.
One can cut the wires needed, add female and male that can connect together or to ECU and gauge cluster side connectors on the PCBs headers. Then it easy to remove the pcb for repairs and restore original function.

5.5 Inline PCB Connector

JST PH 2.00 mm pitch, 3-position, one pair each side. Makes the PCB fully removable —
unplug both sides and connect them directly to bypass the board.

Part DigiKey P/N Qty
JST PHR-3 receptacle housing 455-1705-ND 2
JST B3B-PH-K PCB header (0.079"/2.00 mm pitch, single row) 455-1126-ND 2
JST SPH-002T-P0.5S crimp terminals (24–32 AWG) 455-2148TR-ND 6

Any 3-position single-row 2.00 mm pitch THT header is a suitable alternative for the PCB header.

5.6 Pico 2 W GPIO Pinout

3LB RX — monitor ECU frames, detect bus idle (inputs, via level shifter):

GPIO Signal
GPIO0 FIS_PIN_ENA — monitors bus idle state
GPIO1 FIS_PIN_CLK — input to PIO SM0
GPIO2 FIS_PIN_DATA — input to PIO SM0

3LB TX — inject frames during idle gaps (outputs, via level shifter):

GPIO Signal
GPIO3 FIS_PIN_ENA_OUT — claims bus before injecting
GPIO4 FIS_PIN_CLK_OUT — PIO SM1 side-set
GPIO5 FIS_PIN_DATA_OUT — PIO SM1 out_base

Optional CAN (when CAN enabled, MCP2561): The CAN controller used is the MCP2561. It has only 2 signal connections: TXD and RXD. 3.3 V logic; no level shifter needed. See firmware/fis_can.h and firmware README.

GPIO Signal Direction Notes
GPIO11 FIS_CAN_PIN_TX Out TX CAN to MCP2561 TXD
GPIO12 FIS_CAN_PIN_RX In RX CAN from MCP2561 RXD

MCP2561 CANH/CANL connect to vehicle comfort/infotainment CAN (100 kbit/s). For when to add a 120 ohm termination (tapped in middle vs replacing radio vs bench), see firmware README.

5.7 Cluster Coding Prerequisite

Code the cluster using VCDS or OBDeleven, Module 17 — Instruments:
enable "Navigation present". Without this the navigation slot in the FIS/MFA is inactive
even if the Pico sends correct 3LB frames.


6. The 3LB Protocol

6.1 Open Source Libraries (Reference)

Library URL Notes
TLBFISLib https://github.com/domnulvlad/TLBFISLib Most complete, actively maintained
VAGFISWriter https://github.com/tomaskovacik/VAGFISWriter Original reverse-engineered implementation
FISCuntrol https://github.com/adamforbes92/FISCuntrol Complete project, author uses in own car
FISBlocks https://github.com/ibanezgomez/FISBlocks 3LB + KWP1281 OBD combined

The Pico firmware reimplements 3LB directly in PIO state machines timed to the BAP FCNav
SDP30DF48V280F specification — no Arduino library dependency.

6.2 Display

The FIS/MFA (Fahrerinformationssystem / Multifunktionsanzeige) screen is a 64×88 pixel
monochrome LCD
, 1-bit. Navigation maneuver icons are pre-generated 64×64 px 1-bit arrays
stored in flash (firmware/fis_nav_icons.h) and rendered with:

GraphicFromArray(x, y, width, height, array, 0); // 0 = flash/PROGMEM

6.3 OEM radio: CAN time and display (reference only)

The original VW radio set the instrument cluster time via the CAN bus (CAN-H / CAN-L), using time it received from the FM RDS network. This project does not use CAN for the clock; the Pico sets the FIS clock from GPS (Navit) over serial and 3LB only. The following is documented for reference.

OEM messages not implemented: The specific CAN messages below (mDiagnose_1, mEinheiten) are not implemented. The firmware does support optional CAN bus (MCP2561, disabled by default); the FIS clock is driven solely by the serial protocol (NAV:TIME, timezone conversion, and 3LB injection) described earlier.

Message CAN ID Length Period Description
mDiagnose_1 0x7D0 (2000) 8 bytes 1000 ms Date and time from radio (e.g. RDS).
mEinheiten 0x60E (1550) 2 bytes 1000 ms Display format flags (date/clock on-off, day of week).

mDiagnose_1 (0x7D0) — date and time

Signal Byte Start bit Length Range Unit
DI1_Jahr (Year) 4 4 7 bits 0–127 → 2000–2127 Year
DI1_Monat (Month) 5 3 4 bits 1–12 Month
DI1_Tag (Day) 5 7 5 bits 0–31 Day
DI1_Stunde (Hour) 6 4 5 bits 0–23 Hours
DI1_Minute (Minute) 7 1 6 bits 0–59 Minutes
DI1_Sekunde (Second) 7 7 6 bits 0–59 Seconds
DI1_Zeit_alt (time stale) 8 7 1 bit 1 = time old/stale

Sent cyclically every 1000 ms.

mEinheiten (0x60E) — display format

Signal Byte Bit(s) Description
EH1_Datum_Anzeige 1 6 Date display on/off
EH1_Uhr_Anzeige 1 7 Clock display on/off
EH1_Wochentag 2 4–6 Day of week

Sent cyclically every 1000 ms.

6.4 Other potentially useful CAN messages (reference only)

The following CAN frames are not yet implemented in the firmware. They are documented for reference. The firmware supports optional CAN bus (MCP2561, disabled by default) for future use (e.g. to read cluster/vehicle state or adapt FIS output).

Useful for FIS/display

  • mKombi_1 (0x320) — sent by the cluster, 10 ms cyclic: KO1_kmh (vehicle speed, bytes 4–5, 15 bits), KO1_angez_kmh (displayed speed including offset, bytes 6–7), KO1_Tankinhalt (fuel level), KO1_Warn_Tank (low fuel, 7 L), KO1_Oeldruck (oil pressure warning), KO1_Kuehlmittel (coolant warning), KO1_Vorgluehen (glow plug, diesel).
  • mKombi_2 (0x420) — from cluster, 200 ms cyclic: KO2_gef_Auss_T / KO2_Aussen_T (outside temperature), KO2_Bel_Displ (display brightness %, for dimming FIS at night), KO2_Fehlereintr (fault stored).
  • mEinheiten (0x60E) — units: EH1_Uhr_Anzeige (12h/24h), EH1_Datum_Anzeige (EU/US date), EH1_Wochentag (day of week 1=Mon…7=Sun), EH1_Einh_Temp (°C/°F), EH1_Einh_Strck (km/miles).

Situational awareness

  • mGate_Komf_1 (0x390), 100 ms: GK1_Rueckfahr (reverse), GK1_Warnblk_Status (hazards), GK1_Bremslicht, GK1_Abblendlicht / GK1_Fernlicht (headlights/high beam), GK1_Wischer_vorn (wipers), GK1_SH_laeuft (aux heater), door status (driver, passenger, rear L/R).
  • mZAS_1 (0x572) — ignition: ZA1_Klemme_15 (ignition on), ZA1_Klemme_50 (starter), ZA1_S_Kontakt (key in).
  • mClima_1 (0x5E0) — CL1_Kompressor (A/C on), CL1_Hecksch / CL1_Frontsch (rear/front screen heat), CL1_Restwaerme (residual heat).

Parking (if equipped)

  • Parkhilfe_01 (0x497) — PDC: obstacle front/rear, beeper active, audio ducking request, system state.

Other

  • mKombi_3 (0x520) — KO3_Kilometer (odometer), KO3_Standzeit (parked duration).
  • mSysteminfo_1 (0x5D0) — SY1_Fzg_Derivat (body style), SY1_Notbrems_Status (emergency braking).

Signal reference tables

mKombi_1 — 0x320 (from cluster, 10 ms cyclic)

Signal Byte Start bit Len Raw range Physical Unit Scale
KO1_kmh 4 1 15 0–32600 0–326 km/h 0.01
KO1_angez_kmh 6 6 10 0–1018 0–325.76 km/h 0.32
KO1_Tankinhalt 3 0 7 0–126 0–126 litres 1
KO1_Warn_Tank 1 6 1 1 = warning (7 L)
KO1_Oeldruck 1 2 1 1 = low pressure
KO1_Kuehlmittel 1 4 1 1 = coolant low
KO1_Vorgluehen 1 7 1 1 = glow plug on

KO1_angez_kmh spans bytes 6–7 (10 bits from bit 6); KO1_kmh spans bytes 4–5 (15 bits from bit 1). Little-endian.

mKombi_2 — 0x420 (from cluster, 200 ms cyclic)

Signal Byte Start bit Len Raw range Physical Unit Offset Scale
KO2_gef_Auss_T 2 0 8 0–254 −50 to +77 °C −50 0.5
KO2_Aussen_T 3 0 8 0–254 −50 to +77 °C −50 0.5
KO2_Bel_Displ 6 0 7 0–100 0–100 % 0 1
KO2_Fehlereintr 1 7 1 1 = fault stored

Temperature: °C = (raw × 0.5) − 50. Raw 0xFF = invalid.

mEinheiten — 0x60E (1000 ms cyclic)

Signal Byte Start bit Len Values
EH1_Uhr_Anzeige 1 7 1 0 = 24 h, 1 = 12 h
EH1_Datum_Anzeige 1 6 1 0 = EU (DD.MM), 1 = US (MM/DD)
EH1_Wochentag 2 4 3 0 = init, 1 = Mon … 7 = Sun
EH1_Einh_Temp 1 1 1 0 = °C, 1 = °F
EH1_Einh_Strck 1 0 1 0 = km, 1 = miles

mGate_Komf_1 — 0x390 (100 ms cyclic)

Signal Byte Start bit Len Notes
GK1_Rueckfahr 4 4 1 1 = reverse
GK1_Warnblk_Status 7 7 1 1 = hazards on
GK1_Bremslicht 8 3 1 1 = brake light on
GK1_Abblendlicht 7 0 1 1 = dipped beam
GK1_Fernlicht 7 1 1 1 = high beam
GK1_Wischer_vorn 7 2 1 1 = wipers moving
GK1_SH_laeuft 8 0 1 1 = aux heater on
GK1_Fa_Tuerkont 3 0 1 Driver door
BSK_BT_geoeffnet 6 1 1 Passenger door
BSK_HL_geoeffnet 4 2 1 Rear left door
BSK_HR_geoeffnet 4 3 1 Rear right door

mZAS_1 — 0x572 (ignition/key)

Signal Byte Start bit Len Notes
ZA1_S_Kontakt 1 0 1 1 = key inserted
ZA1_Klemme_15 1 1 1 1 = ignition on
ZA1_Klemme_50 1 3 1 1 = starter engaged

mClima_1 — 0x5E0

Signal Byte Start bit Len Notes
CL1_Kompressor 1 4 1 1 = A/C on
CL1_Hecksch 1 2 1 1 = rear screen heat
CL1_Frontsch 1 3 1 1 = front screen heat
CL1_Restwaerme 7 3 1 1 = residual heat on

Parkhilfe_01 — 0x497 (if equipped)

Signal Byte Start bit Len Notes
PH_Opt_Anz_V_Hindernis 3 2 1 1 = obstacle front
PH_Opt_Anz_H_Hindernis 3 3 1 1 = obstacle rear
PH_Tongeber_V_aktiv 3 4 1 1 = PDC beep front
PH_Tongeber_H_aktiv 3 5 1 1 = PDC beep rear
PH_Anf_Audioabsenkung 3 7 1 1 = audio duck request
PH_Systemzustand 8 2 3 0 = off, 1 = reverse, 2 = front, 3 = both, 7 = fault

mKombi_3 — 0x520

Signal Byte Start bit Len Raw range Physical Unit Scale
KO3_Kilometer 6 0 20 0–1048575 0–1048575 km 1
KO3_Standzeit 4 0 15 0–32767 0–131068 seconds ×4

KO3_Standzeit = time since last ignition-off in 4-second steps (max ~36.4 h).

mSysteminfo_1 — 0x5D0

Signal Byte Start bit Len Values
SY1_Fzg_Derivat 3 0 4 0 = saloon, 1 = notchback, 2 = estate, 3 = hatchback, 4 = coupé, 5 = cabriolet, 6 = offroad, 9 = other
SY1_Notbrems_Status 8 0 1 1 = emergency braking

7. Firmware Runtime Behaviour

Core 0:

  • Initialises USB CDC and BTstack SPP (FIS-Bridge, PIN 0000)
  • Reads NAV:*, BT:*, and CFG:* messages from both interfaces non-blocking
  • Updates shared nav_state_t and feature toggles (fis_config_t) under a critical section
  • When CAN is enabled (CFG:CAN:1), runs optional CAN poll (firmware supports CAN; stub until MCP2561 driver is completed)

Core 1:

  • Monitors 3LB ENA/CLK/DATA via PIO SM0 (RX sniffer)
  • Detects idle gaps between ECU transmissions
  • When bus is idle, checks nav_state_t and feature toggles (fis_config_t), then injects:
    • Active call → call frame (highest priority)
    • Active navigation (routing / recalculating) → nav icon + street name + distance
    • Media info present, no nav → track name frame
    • Clock enabled and GPS time available → clock frame (local time on line 1; line 2: remain / ETA / compass / date per toggles)
    • Otherwise → do nothing; ECU frames reach FIS/MFA unmodified

Injection: Wait ENA LOW (bus idle) → raise ENA → transmit via PIO SM1 → lower ENA → ECU resumes.


8. Software Stack Summary

Layer Technology Where it runs
Navigation engine Navit (with libbinding_dbus.so) Host device (Android, Linux, etc.)
D-Bus listener + serial bridge Python 3 (dbus, pyserial) Host device
Serial transport USB CDC or Bluetooth SPP Host ↔ Pico
Bus monitor + arbitration PIO SM0 (3LB RX) Pico 2 W
FIS/MFA frame injector PIO SM1 (3LB TX) Pico 2 W
Bluetooth receiver BTstack SPP (CYW43439) Pico 2 W
FIS/MFA display 3LB hardware (ENA/DATA/CLK) Pico → Cluster

9. Key References

Resource URL
PQ35/46 CAN K-Matrix (VW/Audi) https://github.com/Supermagnum/FIS-Display/blob/main/PQ35_46_ACAN_KMatrix_V5.20.6F_20160530_MH.xlsx
PQ35/46 CAN Glossary (DE–EN) https://github.com/Supermagnum/FIS-Display/blob/main/PQ35_46_ACAN_Glossary_DE_EN.md
Navit D-Bus external dashboard spec https://github.com/Supermagnum/navit/blob/feature/navit-dash/docs/development/navit_dbus_external_dashboards.rst
Navit project https://github.com/navit-gps/navit
TLBFISLib https://github.com/domnulvlad/TLBFISLib
VAGFISWriter https://github.com/tomaskovacik/VAGFISWriter
VAGFISReader theory of operation https://github.com/tomaskovacik/VAGFISReader/wiki/Theory-of-operation
FISCuntrol https://github.com/adamforbes92/FISCuntrol
FISBlocks https://github.com/ibanezgomez/FISBlocks
FIS-Fool intercept concept http://kuni.bplaced.net/_Homepage/FisFool/FisFool_Overview_e.html
BAP FCNav SDP30DF48V280F (3LB timing spec) https://github.com/Supermagnum/FIS-Display/blob/main/pdfcoffee.com_bap-fcnav-sdp30df48v280fpdf-pdf-free.pdf
SVG maneuver icons https://github.com/Supermagnum/navit/tree/feature/navit-dash/docs/development/svg-examples
Raspberry Pi Pico 2 W datasheet https://datasheets.raspberrypi.com/picow/pico-2-w-datasheet.pdf

Platform: VW Passat B6 (3C), 2005–2010, PQ46 platform.
Cluster part numbers: 3C0 920 8xx series.
FIS/MFA screen: 64×88 px monochrome LCD, 1-bit, 3LB protocol.

Supermagnum/FIS-Display | GitHunt