RPi5 LineageOS Android Head Unit

June 3, 2024
943
Views

Steering Wheel Controls

This was tricky to setup. For my car, Toyota, they use a particular resistance for each button pressed. I ended up using a ESP32 and a voltage divider to accomplish this. I had a total of 5 buttons, Volume Up, Volume Down, Next Track, Previous Track and Mode.

Below are the values measured for each button pressed. The mode button and the combo switch were both using a separate wire and a Ground, leaving me with a total of 3 wires to work with.

Combo
Float = 100k
Volume+ = 1k
Volume- = 3.1k
NextTrack = 1.8ohms
PrevTrack = 330ohms

MODE Switch
Float = 200k
Press = 100k

Below is a wiring diagram of the Combo and Mode switches.

With these values, I calculated that using a Resistor value of 1k gives me significant voltage steps between all buttons being pressed for the COMBO switch. The easiest was the MODE switch as there were only 2 resistance values, just wire the mode switch directly to any ADC GPIO and check the voltages produced when pressed vs floating and that’s the mode switch done. So for example, a good voltage step for the mode switch would be 1v when pressed and 3v when floating, or vice versa. Note that you need to use a dedicated ADC on the ESP for this to work. From memory there are around 4 or 5 ADC inputs on the ESP32.

Obviously, the COMBO switch was a little trickier. For this I used a 1K resistor which gave me voltage reading with steps far apart that I could read the different buttons being pressed. For example,

         if (id(adc_sensor).state < 1) {
             id(next_trk).toggle();
         }
         else if ((id(adc_sensor).state >= 2) and (id(adc_sensor).state <= 3)) {
             id(previous_trk).toggle();
         }
         else if ((id(adc_sensor).state > 5) and (id(adc_sensor).state <= 6)) {
             id(vol_up).toggle();
         }
         else if ((id(adc_sensor).state > 8) and (id(adc_sensor).state <= 9)) {
             id(vol_down).toggle();
         }

With the following Lambda, When the ADC reads a voltage below 1v, it will activate the Next Track output on the ESP32, when the ADC reads a voltage between 2 and 3v, it will activate the output for Previous Track, when the ADC reads a voltage between 5 and 6v, it will activate the output for Volume Up and so on. This allows for some inconsistencies in the ADC readings to still be able to activate the correct function for the button pressed. The Voltage Divider wiring diagram is below.

 3V3 >---|____|---.---(O---.---O O---|___|---.  Volume-   3100
           1k      |        |   ___    ___    |
                   |        }---O O---|___|---{  Volume+   1000
                   |        |   ___    ___    |
  ADC <------------'        }---O O---|___|---{  PrevTrack  330
                            |   ___    ___    |
                            `---O O---|___|---{  NextTrack    2
                                              |
   0V <----------------(O---------------------'

Next, I used ESPHome to flash the ESP as this was the easier for me. The code is below.

sensor:
  - platform: adc
    pin: 34
    name: "VCC Voltage"
    id: adc_sensor
    update_interval: 0.3s
    attenuation: auto
    filters:
      - multiply: 3.3
    on_value:
     then:
       lambda: |-
         if (id(adc_sensor).state < 1) {
             id(next_trk).toggle();
         }
         else if ((id(adc_sensor).state >= 2) and (id(adc_sensor).state <= 3)) {
             id(previous_trk).toggle();
         }
         else if ((id(adc_sensor).state > 5) and (id(adc_sensor).state <= 6)) {
             id(vol_up).toggle();
         }
         else if ((id(adc_sensor).state > 8) and (id(adc_sensor).state <= 9)) {
             id(vol_down).toggle();
         }

switch:
  - platform: gpio
    pin: GPIO17
    id: vol_up
    name: "volume_up"
    inverted: true
    on_turn_on:
      then:
      - delay: 200ms 
      - switch.turn_off: vol_up

  - platform: gpio
    pin: GPIO27
    id: vol_down
    name: "volume_down"
    inverted: true
    on_turn_on:
      then:
      - delay: 200ms
      - switch.turn_off: vol_down

  - platform: gpio
    pin: GPIO21
    id: next_trk
    name: "next_track"
    inverted: true
    on_turn_on:
      then:
      - delay: 200ms 
      - switch.turn_off: next_trk

  - platform: gpio
    pin: GPIO22
    id: previous_trk
    name: "previous_track"
    inverted: true
    on_turn_on:
      then:
      - delay: 200ms 
      - switch.turn_off: previous_trk

We now have our ADC inputs mapped to the appropriate outputs. This means when we push the Volume Up button, it should activate the Volume Up output on GPIO 17 for example. This will give us a short to GROUND as it is a PULL-DOWN output on GPIO 17.

Now, we wire the outputs to the Raspberry PI. Choose 5 GPIO inputs on the Raspberry PI to use. I had some issues with certain GPIOs just refusing to work, so if you set everything up and 1 or 2 buttons don’t function, just try different GPIOs.

Once wired, you need to define the buttons and GPIOs in the Android kernel. By default, the LineageOS image has the ability to enable volume controls on GPIOs 20 and 26.

Now, we can tell LineageOS what each button does, using Key Codes. See here for a full list of the available key codes. Edit your /boot/config.txt and add your required key codes for the correct GPIO pins. Here is the GPIOs and the key codes I used.

# Keys
dtoverlay=gpio-key,gpio=26,keycode=115,label="VOLUME_UP"
dtoverlay=gpio-key,gpio=20,keycode=114,label="VOLUME_DOWN"
dtoverlay=gpio-key,gpio=21,keycode=165,label="MEDIA_PREVIOUS"
dtoverlay=gpio-key,gpio=13,keycode=163,label="MEDIA_NEXT"
dtoverlay=gpio-key,gpio=4,keycode=580,label="APP_SWITCH"

With this, all volume keys work, as well as next and previous track. My “Mode” switch on the steering wheel works as the app switcher, but this could do whatever you like, just adjust the key code accordingly.

1 2 3 4 5 6 7

Leave a Reply