How to get steering wheel audio controls in a TJ

LJGreg

TJ Enthusiast
Supporting Member
Joined
Aug 12, 2018
Messages
607
Location
Pitt Meadows, BC, Canada
I purchased a leather wrapped Jeep WJ steering wheel which had audio controls that I wanted to get working. Unfortunately the TJ clock spring only has 1 wire for the cruise control signals and the WJ clock spring is not compatible in the TJ.

IMG_2071.jpeg


I learned a lot about how this system works so I am going to detail this all out. If you just want the solution you can skip to my next post.

The way these buttons work is called a resistance ladder. Each button is wired in parallel and each has a unique resistor to ground. The PCM puts out +5v and with each press the voltage drops by a unique amount that can be detected through the single wire. For cruise control there is also a 20.5k idle resistor so the PCM can detect any trouble conditions.

Here are the actual resistor values for each button resistor:

Idle 20.5kΩ
Cruise On/Off 470Ω
Cruise Cancel 1.25kΩ
Cruise Coast 2.94kΩ
Cruise Set 5.49kΩ
Cruise Resume/Accel 15.4kΩ

Left Audio Up 261Ω
Left Audio Down 681Ω
Left Audio Centre 162Ω
Right Audio Up 1.21kΩ
Right Audio Down 3.01kΩ
Right Audio Centre 51.1kΩ

*Since these are wired in parallel with the 20.5k idle resistor the measured resistance is slightly different.

My initial idea was to wire the audio buttons in parallel with the cruise buttons to send all the signals down the existing SWI (steering wheel interface) signal wire. I could then tap into the wire without cutting it and redirect the signal to something like a PAC SWI-RC. I discovered a few issues with this idea:

1. If you have cruise control turned on and the PCM sees any button it doesn’t recognize (eg. volume up) it will turn the cruise control off.

2. The idle circuit has a 20.5kΩ resistance value which the SWI-RC would detect as a button press. Most SWI are open circuits when no button has been pressed.

3. Both the PCM and the SWI-RC are putting out +5v on the SWI wire so this would cause issues.

4. Some of the resistance values are so close together (eg. Right Audio Up 1.21kΩ & Cruise Cancel 1.25kΩ) that it is hard to reliably detect which button was pressed.

So my idea was to install an Arduino Nano (small computer) in between the SWI signal wire and the PCM and SWI-RC. The Arduino would read the incoming voltage and based on the drop it would decide whether to pass the signal along to the PCM or the SWI-RC. I was also going to have to shift the audio resistance values by adding a resistor in series.
 
So here is what I came up with but first a disclaimer:

This was an experiment I performed on my own vehicle under safe conditions. I am simply documenting the information based on what I learned. Since this requires making changes to the cruise control signal wire it could potentially lead to unsafe conditions and should not be attempted.

I did not have any previous Arduino or soldering experience but I do have a passable understanding of electronics and programming.

Parts List (I purchased it all through DigiKey):

Arduino Nano Every (1050-ABX00033-ND) $14.24
5kΩ DigiPot MCP4261-502E/P-ND $1.21
Prototype Board (1528-1195-ND) $4.50
20.5kΩ resistor (PPC20.5KXCT-ND) $0.17
1.25kΩ Resistor (1.24KXBK-ND) $0.08
470Ω Resistor (S470CACT-ND) $0.07
2.94kΩ Resistor (2.94KXBK-ND) $0.08
5.49kΩ Resistor (5.49KXBK-ND) $0.08
15.4kΩ Resistor (15.4KXBK-ND) $0.08
2x 5.6kΩ Resistors (S5.6KCATR-ND) $0.10
1x 10kΩ Resistor (10.0KXBK-ND) $0.10
PAC SWI-RC $30

I found that by shifting the audio resistance values by 5.6kΩ it spaced them enough apart that each button could be uniquely identified.

Assuming you have a WJ steering wheel, start by cutting off the smaller harness for both the left and right audio control buttons, wiring a 5.6kΩ resistor in series (doesn’t matter which wire) and splice it into the larger harness wires. I used heat shrink around the resistor and then tesca tape for the entire thing.

IMG_2076.jpeg


IMG_2098.jpeg


IMG_2127.jpeg


IMG_2102.jpeg


Create a prototype board using the following schematic. This may look intimidating at first but it is really just matching pin to pin. I used an Arduino Nano Every.

Screen Shot 2020-06-08 at 9.01.16 AM.png


IMG_2143.jpeg


Here is what the board is doing:

1. It is putting +5v out on PIN A4 (SWI_SIGNAL_IN) which is part of a voltage divider circuit and will be wired to your steering wheel SWI wire. It reads the voltage drop to detect what button is pressed.

2. It is taking the +5v from the PCM through the SWI_SIGNAL_OUT_TO_PCM wire and passing it through a 20.5k resistor (R1). If no button is pressed it then goes straight to ground. If a button is pressed it is then opening up a single PIN between D2 to D7 which adds a resistor in parallel to the circuit. This drops the voltage to match what the actual buttons would do so the PCM can detect the cruise control action.

3. I also have a MCP4261 which is a digital potentiometer. It is basically a digitally controlled resistor that through the program we can set what resistance we would like. When we detect an audio button was pressed on PIN A4 we set one of 6 resistance values (we have 6 audio buttons) on this chip which is wired to the SWI-RC. The SWI-RC just thinks a steering wheel button was pressed. I did try to use PWM to mimic the voltage drop so I didn't need the DPOT but I couldn't find a way to get it working properly.

Remove the bottom panel of the steering wheel and find the purple wire coming out of the clockspring harness. This is the +5v SWI signal wire. You will need to cut the wire and connect the clockspring end to the SWI_SIGNAL_IN and the PCM end to the SWI_SIGNAL_OUT_TO_PCM. I used crimp connectors with adhesive heatshrink and then tesca taped the harness back together.

IMG_2147.jpeg


IMG_2148.jpeg


I chose to install my prototype board and SWI-RC behind the stereo so I ran the two jumper wires up behind the dash and into the stereo housing.

I spliced the SWI-RC and the prototype board into the existing stereo harness. I made sure to put the prototype board behind the existing SWI-RC fuse to ensure it was protected. I thought about 3d printing a nice case for the board but in the end just tape it up to the SWI-RC.

IMG_2144.jpeg


IMG_2146.jpeg


The final wiring looked like this:

prototype board: SWI_SIGNAL_OUT_TO_PCM -> Purple Wire PCM side
prototype board: SWI_SIGNAL_IN -> Purple Wire Clockspring side
prototype board: SWI_SIGNAL_OUT_TO_PAC -> White wire on SWI-RC
prototype board: +12V -> spliced in to PAC Red wire
prototype board: GND -> spliced in to PAC Black wire

SWI-RC: Red -> spliced in to Stereo harness Red (Accessory power)
SWI-RC: Black -> spliced in to Stereo harness Black (Ground)
SWI-RC: Yellow -> spliced in to Stereo harness Yellow (constant 12V) behind

I then followed the SWI-RC universal instructions for my stereo to program the buttons and actions I wanted.

The next post is the Arduino code which is constantly reading an internal voltage and then outputting the necessary change. I found that I needed to smooth the incoming voltage as leading and trailing edge button presses would sometimes be interpreted as other buttons presses. Taking an average of 10 readings gives me 100% reliability.

I am sure I forgot a bunch of details but if anyone is looking to do this I would be happy to provide assistance as you go along.
 
Last edited:
/*
* Jeep TJ Steering Wheel Audio Controls using a WJ steering wheel
*
* Audio buttons need to have a 5.6k OHM resistor added and then wired in parallel the cruise control buttons
* SWI_IN_PIN is the SWI signal on the clockspring end
* SWI_OUT__PCM_PIN is the SWI signal on the PCM end
*
*/

#include <Smoothed.h>
#include <SPI.h>

#define SWI_IN_PIN A4
#define SWI_CRUISE_ONOFF_PIN 2
#define SWI_CRUISE_CANCEL_PIN 3
#define SWI_CRUISE_COAST_PIN 4
#define SWI_CRUISE_SET_PIN 5
#define SWI_CRUISE_RESACCEL_PIN 6

#define DPOT_CS_PIN 7
#define DPOT_SHDN_PIN 8

// using the full range of the 5k DPOT (0-255 values)
const int rightdownResistance = 0;
const int rightupResistance = 50;
const int rightcentreResistance = 100;
const int leftdownResistance = 150;
const int leftupResistance = 200;
const int leftcentreResistance = 250;

Smoothed <int> mySensor;

void setup() {
Serial.begin(9600);

pinMode(SWI_IN_PIN, INPUT);
pinMode(DPOT_CS_PIN, OUTPUT);

// all cruise buttons in the not pressed state
pinMode(SWI_CRUISE_ONOFF_PIN, INPUT);
pinMode(SWI_CRUISE_CANCEL_PIN, INPUT);
pinMode(SWI_CRUISE_COAST_PIN, INPUT);
pinMode(SWI_CRUISE_SET_PIN, INPUT);
pinMode(SWI_CRUISE_RESACCEL_PIN, INPUT);

// DPOT shutdown
pinMode(DPOT_SHDN_PIN, OUTPUT);
digitalWrite(DPOT_SHDN_PIN, LOW);

SPI.begin();

mySensor.begin(SMOOTHED_AVERAGE, 10);
}

void loop() {
for (int i=0; i<=10; i++) {
mySensor.add(analogRead(SWI_IN_PIN));
}

switch (mySensor.get()) {
case 478 ... 484:
Serial.println("RES/ACCEL");
pinMode(SWI_CRUISE_RESACCEL_PIN, OUTPUT);
break;
case 419 ... 425:
Serial.println("Right Down");
setPotentiometer(rightdownResistance);
break;
case 387 ... 393:
Serial.println("Right Up");
setPotentiometer(rightupResistance);
break;
case 363 ... 369:
Serial.println("Right Centre");
setPotentiometer(rightcentreResistance);
break;
case 309 ... 314:
Serial.println("Set");
pinMode(SWI_CRUISE_SET_PIN, OUTPUT);
break;
case 208 ... 214:
Serial.println("Coast");
pinMode(SWI_CRUISE_CANCEL_PIN, OUTPUT);
break;
case 105 ... 112:
Serial.println("Cancel");
pinMode(SWI_CRUISE_CANCEL_PIN, OUTPUT);
break;
case 61 ... 67:
Serial.println("Left Down");
setPotentiometer(leftdownResistance);
break;
case 43 ... 50:
Serial.println("On/Off");
pinMode(SWI_CRUISE_ONOFF_PIN, OUTPUT);
break;
case 23 ... 30:
Serial.println("Left Up");
setPotentiometer(leftupResistance);
break;
case 13 ... 21:
Serial.println("Left Centre");
setPotentiometer(leftcentreResistance);
break;
default:
// set wiper shutdown to ON
digitalWrite(DPOT_SHDN_PIN, LOW);
pinMode(SWI_CRUISE_ONOFF_PIN, INPUT);
pinMode(SWI_CRUISE_RESACCEL_PIN, INPUT);
pinMode(SWI_CRUISE_SET_PIN, INPUT);
pinMode(SWI_CRUISE_CANCEL_PIN, INPUT);
pinMode(SWI_CRUISE_COAST_PIN, INPUT);
break;
}
mySensor.clear();
}

void setPotentiometer (int resistance) {
// set resistance of wiper
digitalWrite(DPOT_CS_PIN, LOW);
SPI.transfer(0);
SPI.transfer(resistance);
digitalWrite(DPOT_CS_PIN, HIGH);

// set wiper shutdown to OFF
digitalWrite(DPOT_SHDN_PIN, HIGH);
}
 
Last edited:
Excellent work!

I'm a bit rusty on my coding (Mechanical engineer, not software guy - just enough to be dangerous) but a couple of questions:

Did you mean to set the pin mode of SWI_CRUISE_ONOFF_PIN twice in setup?

I wonder if you could use the 50K version of the MCP4261 and the 2nd pot to mimic the cruise controls without having to use the resistor network - not sure is the ~200 ohm per step of the 50K version would be enough resolution to mimic the resistance values closely enough for the PCM to work.

May also comment out all the serial.print lines, unless you want to be able to plug in and check its working. Great for debugging, so it doesn't hurt to leave them.

I wonder if there's a different way to change from the high impedance to low without changing the pin_mode for your cruise output. Took me a second to figure out what was going on with the pinMode changes in the switch case.

Of course, if the 50K digitpot would work..this may not be an issue.

Great work. Now to convince the better 3/4 that I need a new steering wheel..
 
  • Like
Reactions: DougB
Did you mean to set the pin mode of SWI_CRUISE_ONOFF_PIN twice in setup?

Nope, good catch. It won't do any harm but I made the edit.

I wonder if you could use the 50K version of the MCP4261 and the 2nd pot to mimic the cruise controls without having to use the resistor network - not sure is the ~200 ohm per step of the 50K version would be enough resolution to mimic the resistance values closely enough for the PCM to work.

I actually tried that first. Even with the 40 OHM step of the 5k I couldn't get the PCM to detect it. The buttons all have 1% tolerance resistors so my only guess is the PCM has a very small margin of error.

May also comment out all the serial.print lines, unless you want to be able to plug in and check its working. Great for debugging, so it doesn't hurt to leave them.

Agreed, although I don't think it takes any extra effort if you don't have serial attached. If anything i would wrap them in an IF and have a debug on or off global variable.

I wonder if there's a different way to change from the high impedance to low without changing the pin_mode for your cruise output. Took me a second to figure out what was going on with the pinMode changes in the switch case.

I would be really interested if this could be done. I spend a few hours with the breadboard trying to get it work with no success. In the end I opted for the DPOT instead of PWM as it seems it would be the most reliable solution.
 
I just ordered up a three of these for $25. All designed using free Eagle CAD software and by watching Youtube tutorials. I am absolutely amazed at how DIY friendly all this is now a days.
Excellent project! Need to see a shot of the nice steering wheel installed.

Interested where you ordered the PCBs?
 
I am using a company called oshpark. I am sure there are cheaper suppliers in China but this is all new to me and they had the best website to order from.
Looks nice!

Thanks for the info. Nice that OshPark is USA made, too.
 
This is an awesome upgrade, I've missed having controls since getting my LJ.

Any chance you'd be willing to share plans for the PCB? That looks much cleaner than the project board and I'm sure more reliable.
 
This is an awesome upgrade, I've missed having controls since getting my LJ.

Any chance you'd be willing to share plans for the PCB? That looks much cleaner than the project board and I'm sure more reliable.

I should be getting my boards in a week or so. Once I have it working and confirm I didn't screw up the design I will share the Eagle file so you can order your own.
 
Didn't notice this before - but you added some caps to the PCB design that were not in the original list - guessing they are for power filtering?

Doing the math, the PCM would see the following for possible resistance values - this assumes 1% tolerance resistors on everything, and calculating the R-equivalent (Req) for the other resistors in parallel:

Item​
Req - Nominal
Req- low​
Req - high​
Possible difference
Idle205002039820602204
Cruise On/Off459.54574625
Cruise Cancel1178.21172118412
Cruise coast2571.22558258426
Cruise Set4330.34309435243
Cruise Resume/Accel8793.98750883888

Looking at the digital pots; they have pretty junk for tolerances (the MCP4261 above is 20%) - that may be the reason it's hard to correctly address the PCM using the D-pot outputs - the direct resistor method does seem to work much better for sensitive applications such as this.

I'm currently searching for a cheap WJ wheel to experiment with. I may just bench build it to replicate the entire setup for testing purposes. I'd like to explore some other possibilities for the controls - for example holding left center button to do certain things through the SWI.
 
Ya I am finding some of my button presses get lost ONLY when the engine is running. It looks like the analog to digital converter is all over the place due to the noisy power. I put in a voltage regulator and some capacitors but haven't had a change to test it out yet. I'll need to update my schematic and parts list.