# Volvo LIN-to-IR Controller (ATtiny84) This project implements the production firmware for the ATtiny84 to interface a Volvo V50 Steering Wheel Module (SWM) LIN bus with a Raspberry Pi running LineageOS (Android). It decodes steering wheel navigation buttons from the vehicle's LIN bus and translates them into bit-banged RC-6 Mode 6A (MCE) remote control commands sent over a direct wire connection. ## Pin Configurations (ATtiny84) | ATtiny84 Pin | Digital Pin (Arduino) | Function | Description | | :--- | :--- | :--- | :--- | | **PA0** | `10` | IR Output | Direct-wired to Raspberry Pi GPIO 24 | | **PB2** | `2` | LIN RX | SoftwareSerial RX from MCP2004 RXD | | **PB0** | `0` | LIN TX | SoftwareSerial TX (Unused dummy) | | **PB1** | `1` | LIN FAULT/TXE | MCP2004 Fault Detect / Transmit Enable | | **PB3** | `11` | LIN CS | MCP2004 Chip Select (Active High) | | **PA7** | `3` | Debug LED | Flash on command transmission | ## Key Mapping The Steering Wheel Module (SWM) sends frames on LIN ID `0x20` with the navigation key statuses. The firmware decodes these and maps them to the following Microsoft MCE remote scancodes: | Button | LIN Frame Trigger | Active Button Code | MCE Scancode | Action on LineageOS | | :--- | :--- | :--- | :--- | :--- | | **UP** | `d0 & 0x01` | `1` | `0x800f041e` | Navigate Up | | **DOWN** | `d0 & 0x02` | `2` | `0x800f041f` | Navigate Down | | **LEFT** | `d0 & 0x04` | `3` | `0x800f0420` | Navigate Left | | **RIGHT** | `d0 & 0x08` | `4` | `0x800f0421` | Navigate Right | | **ENTER** | `d1 & 0x08` | `5` | `0x800f0422` | Select / OK | | **BACK** | `d1 & 0x01` | `6` | `0x800f0423` | Back / Exit | | **F12 (Shutdown)** | LIN Silent > 5s | `7` | `0x800f046f` | Trigger Shutdown (via MacroDroid) | *Note: In previous revisions, `ENTER` and `BACK` were reversed. This has been corrected so that pressing `ENTER` maps to `0x800f0422` and `BACK` maps to `0x800f0423`.* ## Car-Off (Shutdown) Detection To automatically power off the Raspberry Pi when the car is turned off: 1. **Activity Monitor**: The ATtiny monitors LIN bus activity. The Central Electronic Module (CEM) continuously polls the LIN bus when the ignition is on. 2. **Timeout**: If no LIN serial data is received for 5 seconds, the ATtiny assumes the car is off. 3. **Shutdown Signal**: It transitions the car status to "off" and sends the MCE code `0x800f046f` (mapped to `KEY_F12`) 3 times to ensure transmission reliability. 4. **State Protection**: It will not send the shutdown command again until the car is turned back on and LIN traffic resumes (which sets `is_car_on` back to `true`). ## Raspberry Pi Configuration ### 1. Keymap Setup Remap scancode `0x800f046f` to `KEY_F12` on the Raspberry Pi: - The custom TOML configuration is saved at `/data/rc-rc6-mce.toml` on the Pi. - To apply it dynamically: ```bash /vendor/bin/ir-keytable -w /data/rc-rc6-mce.toml ``` ### 2. MacroDroid Persistence & Trigger Configure two macros in **MacroDroid** to manage keymap loading and shutdown: - **Macro 1: Load Keymap on Boot** - **Trigger**: *Device Boot* - **Action**: *Run Shell Command* (Check **Run as Root**) ```bash /vendor/bin/ir-keytable -w /data/rc-rc6-mce.toml ``` - **Macro 2: Shutdown on F12** - **Trigger**: *Media / Key Pressed* -> Select **F12** - **Action**: *Run Shell Command* (Check **Run as Root**) ```bash reboot -p ``` ## Protocol and Timing - **Protocol**: RC-6 Mode 6A (MCE). - **Time Unit (1T)**: `444us`. - **Modulation**: None. Timing is bit-banged directly since the output pin is wired directly to the Pi's IR receiver GPIO (which expects demodulated active-low signals). - **Idle (Space)**: `HIGH` (3.3V). - **Active Pulse (Mark)**: `LOW` (0V). - **Toggle Bit**: Alternates state on each new button press, but remains constant for repeated holds. - **Repeat Interval**: Holds trigger repeat IR commands sent every `250ms`. ## Software Architecture 1. **SoftwareSerial Interrupt Isolation**: Since SoftwareSerial RX interrupts consume about 1ms per byte, they will disrupt the precise microsecond-level timing of bit-banged IR frames. To avoid this, interrupts are disabled (`noInterrupts()`) during the 37ms window of IR transmission and re-enabled (`interrupts()`) immediately afterward. 2. **Release detection**: The SWM continuously transmits frames on the LIN bus. If no button frames are detected or a frame with all 0s is received, the current active button resets to idle. A timeout of `200ms` ensures that if the LIN bus goes quiet, button repeats cease immediately.