A fully abstracted, non-RTOS C++ library emulating the Volkswagen CD Changer (CDC) protocol. It seamlessly works with VW Group head units (RNS-MFD, Gamma, Beta, Delta, Chorus, Symphony) over the customized VW SPI and NEC IR data bus.
Originally engineered with the ESP32’s dual-core FreeRTOS architecture, the CDC driver demanded massive RAM and SMP (Symmetric Multiprocessing) task pinning to prevent network jitter. VWCDC drops all OS requirements:
- Zero FreeRTOS Tasks: Entirely replaced by a single, non-blocking
loop(). - Zero
Stringallocations: Operates entirely on rawuint8_tmemory mapping, protecting low-memory chips (ATmega328, ATTiny) from fragmentation crashes. - Universal ISR Pattern: Drops
attachInterruptArg(an ESP32-only feature) in favor of a faststaticISR wrapper, meaningattachInterrupt(..., CHANGE)functions immediately on Arduino Uno, STM32, and PIC MCUs.
This sketch turns a lightweight $3 Arduino clone into a fully functioning CDC emulator that reports 6 discs, responds to steering wheel / radio faceplate buttons, and outputs status data.
#include <Arduino.h>
#include <SPI.h>
#include "VWCDC.h"
// Initialize instance
VWCDC cdc;
// CDC PIN Definitions (Example for typical Uno/Nano)
#define PIN_DATA_OUT 2 // Must be an interrupt-capable pin (D2 on Uno/Nano)
#define PIN_SCK 13 // SPI SCK
#define PIN_MOSI 11 // SPI MOSI
#define PIN_MISO 12 // SPI MISO (Not strictly used, logic strictly reads DataOut)
// Callback for VW Radio buttons being pressed
void onCdcButton(VWButton button) {
switch(button) {
case VWButton::NEXT: Serial.println("User pressed >> (Next)"); break;
case VWButton::PREV: Serial.println("User pressed << (Prev)"); break;
case VWButton::CD1: Serial.println("Selected Disc 1 (Custom Action)"); break;
case VWButton::CD6: Serial.println("Selected Disc 6"); break;
default: Serial.println("Other VW button pressed."); break;
}
}
// Telemetry/Diagnostic callback (Low-memory footprint text passing)
void onCdcDiag(const char* logMsg) {
Serial.print("CDC_DIAG: ");
Serial.println(logMsg);
}
void setup() {
Serial.begin(115200);
Serial.println("Starting VWCDC emulator...");
cdc.setButtonCallback(onCdcButton);
cdc.setDiagCallback(onCdcDiag);
// Binds hardware SPI and the NEC receiver ISR.
// The DataOut pin MUST support attachInterrupt(CHANGE).
cdc.begin(PIN_DATA_OUT, PIN_SCK, PIN_MOSI, PIN_MISO);
// Initial Dashboard spoofing
cdc.setDisc(1);
cdc.setTrack(1);
Serial.println("CDC Subsystem online.");
}
void loop() {
// This MUST be called as quickly and as often as possible.
// It processes SPI queues, calculates ISR buffer differences, and fires callbacks.
cdc.loop();
// Do NOT place multi-millisecond delay() statements in this loop,
// otherwise CDC SPI timing limits (3200µs) may be violated.
}begin(int dataOutPin, int sckPin, int mosiPin, int misoPin)- Start the hardware modules (SPI and External ISR).loop()- Non-blocking state machine processor. Put it invoid loop().setDisc(uint8_t disc),setTrack(uint8_t track)- Send spoofing updates to the radio's LCD (Track 01-99).setButtonCallback(VWCDCButtonCallback cb)- Register a listener for button presses from the head unit.setDiagCallback(VWCDCDiagCallback cb)- Receive debug strings without heap tracking.