Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // ############################################################################################################
- // # This code is meant to interface a 6-wire Renault Twingo / Clio steering wheel remote control with #
- // # a JVC car radio equipped with a 'steering wheel remote input'. Hardware used is an Arduino Nano-clone. #
- // ############################################################################################################
- //
- // -- EDIT 2017-12-18 --
- // By popular request I've drawn a schematic. You can view it at [removed outdated link]
- // -- END OF EDIT --
- // -- EDIT 2021-08-17 --
- // Thanks to Leander Stark for pointing out an error in the schematic. The updated schematic is available at https://imgur.com/XBAL4d1
- // -- END OF EDIT --
- //
- // The steering wheel remote connection on the radio (blue/yellow wire or Tip in case of a TS connector)
- // is connected to a pull-up resistor in the radio circuitry.
- // Data is sent in the form of pulse interval modulation, meaning the interval following a pulse determines if we're sending a 0 or a 1.
- // Pulses are sent by pulling the radio's input to ground.
- // I'm driving an optocoupler to pull the radio's input to ground, so a HIGH Arduino output makes for a LOW radio input (= a pulse).
- // Whenever I refer to HIGH or LOW, I'm talking about the Arduino output.
- // Protocol specifications:
- // Pulse width 527.5 µs
- // Pulse interval for sending 0 1055.0 µs (HIGH for 1 pulse width, LOW for 1 pulse width)
- // Pulse interval for sending 1 2110.0 µs (HIGH for 1 pulse width, LOW for 3 pulse widths)
- // Note: since the delayMicroseconds() function accepts only unsigned integers, we're using a pulse width of 527 µs
- // Data packets are constructed as follows:
- // HEADER always LOW (1 pulse width), HIGH (16 pulse widths), LOW (8 pulse widths)
- // START BIT always 1
- // ADDRESS 7-bits (0x00 to 0x7F), send LSB first, always 0x47 for JVC KD-R531, probably the same for all JVC car radio's
- // COMMAND 7-bits (0x00 to 0x7F), send LSB first, see next section for list of known commands for JVC KD-R531
- // STOP BITS always 1, 1
- // Note: the ADDRESS, COMMAND and STOP BITS are repeated 3 times to ensure the radio properly receives them.
- // Known commands for JVC KD-R531:
- // HEX DEC BIN(7b) FUNCTION
- // 0x04 4 0000100 Volume +
- // 0x05 5 0000101 Volume -
- // 0x08 8 0001000 Source cycle
- // 0x0D 13 0001101 Equalizer preset cycle
- // 0x0E 14 0001110 Mute toggle / Play/pause toggle
- // 0x12 18 0010010 Tuner Search + / Track + (and Manual Tune + / Fast Forward with press & hold)
- // 0x13 19 0010011 Tuner Search - / Track - (and Manual Tune - / Fast Rewind with press & hold)
- // 0x14 20 0010100 Tuner Preset + / USB Folder +
- // 0x15 21 0010101 Tuner Preset - / USB Folder -
- // 0x37 55 0110111 UNKNOWN, appears to be a sort of reset as well as a display test
- // 0x58 88 1011000 UNKNOWN, displays 'SN WRITING' where WRITING is blinking
- // Define commands
- #define VOLUP 0x04
- #define VOLDOWN 0x05
- #define SOURCE 0x08
- #define EQUALIZER 0x0D
- #define MUTE 0x0E
- #define TRACKFORW 0x12
- #define TRACKBACK 0x13
- #define FOLDERFORW 0x14
- #define FOLDERBACK 0x15
- #define UNKNOWN1 0x37
- #define UNKNOWN2 0x58
- // Renault Twingo / Clio steering wheel remote wire functions
- // pin_cycle_current 1 0 2
- // OUTPUTS BLUE GREEN YELLOW
- // INPUTS PIN# 3 5 6
- // BLACK 2 MUTE TOP RIGHT BTN TOP LEFT BTN
- // RED 4 VOL+ BOTTOM BTN VOL-
- // HIGH HIGH LOW SCROLL UP (CCW)
- // BROWN 7 HIGH LOW HIGH SCROLLWHEEL
- // LOW HIGH HIGH SCROLL DN (CW)
- // Outputs are set LOW one at a time (the other outputs will be HIGH). Inputs (with internal pull-up) are then evaluated.
- // If an input is being pulled LOW this means a button is being pressed. Taking into account which output is currently LOW
- // we know which button this is. For example, is output pin 3 (Blue wire) is currently LOW and we also read LOW on
- // input pin 2 (Black) we know the MUTE button is being pressed.
- // For the scrollwheel we must take into account its last known position in order to determine if there has been a change.
- // We can determine the direction based on which pins are being pulled LOW.
- // Connect Renault Twingo / Clio steering wheel remote wires to these pins
- #define BLACKPIN 2 // D2
- #define BLUEPIN 3 // D3
- #define REDPIN 4 // D4
- #define GREENPIN 5 // D5
- #define YELLOWPIN 6 // D6
- #define BROWNPIN 7 // D7
- // Connect optocoupler input through a 1k resistor to this pin
- #define OUTPUTPIN 8 // D8
- // On-board LED, useful for debugging
- #define LEDPIN 13 // D13
- // Pulse width in µs
- #define PULSEWIDTH 527
- // Address that the radio responds to
- #define ADDRESS 0x47
- // Set number of output pins and put those pins in an array to cycle through when polling the input pins
- #define OUT_PINS 3
- unsigned char out_pins[OUT_PINS] = {GREENPIN, BLUEPIN, YELLOWPIN};
- void setup() {
- pinMode(OUTPUTPIN, OUTPUT); // Set the proper pin as output
- digitalWrite(OUTPUTPIN, LOW); // Output LOW to make sure optocoupler is off
- // Set the pins connected to the steering wheel remote as input / output
- pinMode(BLACKPIN, INPUT_PULLUP);
- pinMode(BLUEPIN, OUTPUT);
- pinMode(REDPIN, INPUT_PULLUP);
- pinMode(GREENPIN, OUTPUT);
- pinMode(YELLOWPIN, OUTPUT);
- pinMode(BROWNPIN, INPUT_PULLUP);
- pinMode(LEDPIN, OUTPUT); // Set pin connected to on-board LED as output...
- digitalWrite(LEDPIN, LOW); // ...and turn LED off
- for (unsigned char i = 0; i <= 7; i++) { // Flash on-board LED a few times so it's easy to see when the Arduino is ready
- delay(100);
- digitalWrite(LEDPIN, !digitalRead(LEDPIN));
- }
- delay(100);
- digitalWrite(LEDPIN, LOW); // Make sure LED ends up being off
- }
- // The steering wheel remote has 6 buttons and a scrollwheel, interfaced via 6 wires.
- // This function will cycle through the output pins, setting one pin LOW at a time.
- // It will then poll the input pins to see which input pins - if any - are pulled LOW.
- unsigned char GetInput(void) {
- static unsigned char pin_cycle_current = 0; // To keep track of which output pin is currently LOW
- static unsigned char pin_cycle_stored; // To store the last known scrollwheel position
- static boolean first_run = true; // After booting, there is no known last position for the scrollwheel
- // So on the first poll of the scrollwheel just store the current position and don't send a command
- unsigned char i;
- if (++pin_cycle_current > (OUT_PINS - 1)) pin_cycle_current = 0; // Reset pin_cycle_current counter after last pin
- for (i = 0; i < OUT_PINS; i++) { // Cycle through the output pins, setting one of them LOW and the rest HIGH
- if (i == pin_cycle_current)
- digitalWrite(out_pins[i], LOW);
- else
- digitalWrite(out_pins[i], HIGH);
- }
- if (!digitalRead(BROWNPIN)) { // We're only interested if this pin is being pulled LOW
- if (pin_cycle_current != pin_cycle_stored) { // If the output that's currently LOW is different from the one that was LOW the last time
- // we came through here, then the scrollwheel has changed position
- signed char scrollwheel_current = pin_cycle_current - pin_cycle_stored; // Result of this calculation can range from -2 to 2
- pin_cycle_stored = pin_cycle_current; // Store which output pin is currently LOW
- if (first_run) { // If this is the first run, don't send a command
- first_run = false; // (since there was no previously known scrollwheel position)
- return 0;
- }
- if ((scrollwheel_current == 1) || (scrollwheel_current == -2)) { // If above calculation resulted in 1 or -2 the scrollwheel was rotated up (ccw)
- return FOLDERBACK;
- }else { // If above calculation resulted in anything else the scrollwheel was rotated down (cw)
- return FOLDERFORW;
- }
- }
- }
- if (!digitalRead(REDPIN)) { // We're only interested if this pin is being pulled LOW
- switch(pin_cycle_current) {
- case 0: // RED (input) is LOW while GREEN (output) is LOW: bottom button pressed
- return SOURCE;
- case 1: // RED (input) is LOW while BLUE (output) is LOW: volume + button pressed
- return VOLUP;
- case 2: // RED (input) is LOW while YELLOW (output) is LOW: volume - button pressed
- return VOLDOWN;
- }
- }
- if (!digitalRead(BLACKPIN)) { // We're only interested if this pin is being pulled LOW
- switch(pin_cycle_current) {
- case 0: // BLACK (input) is LOW while GREEN (output) is LOW: top right button is pressed
- return TRACKFORW;
- case 1: // BLACK (input) is LOW while BLUE (output) is LOW: mute button is pressed
- return MUTE;
- case 2: // BLACK (input) is LOW while YELLOW (output) is LOW: top left button is pressed
- return TRACKBACK;
- }
- }
- return 0;
- }
- void loop() {
- unsigned char Key = GetInput(); // If any buttons are being pressed the GetInput() function will return the appropriate command code
- if (Key) { // If no buttons are being pressed the function will have returned 0 and no command will be sent
- SendCommand(Key);
- }
- }
- // Send a value (7 bits, LSB is sent first, value can be an address or command)
- void SendValue(unsigned char value) {
- unsigned char i, tmp = 1;
- for (i = 0; i < sizeof(value) * 8 - 1; i++) {
- if (value & tmp) // Do a bitwise AND on the value and tmp
- SendOne();
- else
- SendZero();
- tmp = tmp << 1; // Bitshift left by 1
- }
- }
- // Send a command to the radio, including the header, start bit, address and stop bits
- void SendCommand(unsigned char value) {
- unsigned char i;
- Preamble(); // Send signals to precede a command to the radio
- for (i = 0; i < 3; i++) { // Repeat address, command and stop bits three times so radio will pick them up properly
- SendValue(ADDRESS); // Send the address
- SendValue((unsigned char)value); // Send the command
- Postamble(); // Send signals to follow a command to the radio
- }
- }
- // Signals to transmit a '0' bit
- void SendZero() {
- digitalWrite(OUTPUTPIN, HIGH); // Output HIGH for 1 pulse width
- digitalWrite(LEDPIN, HIGH); // Turn on on-board LED
- delayMicroseconds(PULSEWIDTH);
- digitalWrite(OUTPUTPIN, LOW); // Output LOW for 1 pulse width
- digitalWrite(LEDPIN, LOW); // Turn off on-board LED
- delayMicroseconds(PULSEWIDTH);
- }
- // Signals to transmit a '1' bit
- void SendOne() {
- digitalWrite(OUTPUTPIN, HIGH); // Output HIGH for 1 pulse width
- digitalWrite(LEDPIN, HIGH); // Turn on on-board LED
- delayMicroseconds(PULSEWIDTH);
- digitalWrite(OUTPUTPIN, LOW); // Output LOW for 3 pulse widths
- digitalWrite(LEDPIN, LOW); // Turn off on-board LED
- delayMicroseconds(PULSEWIDTH * 3);
- }
- // Signals to precede a command to the radio
- void Preamble() {
- // HEADER: always LOW (1 pulse width), HIGH (16 pulse widths), LOW (8 pulse widths)
- digitalWrite(OUTPUTPIN, LOW); // Make sure output is LOW for 1 pulse width, so the header starts with a rising edge
- digitalWrite(LEDPIN, LOW); // Turn off on-board LED
- delayMicroseconds(PULSEWIDTH * 1);
- digitalWrite(OUTPUTPIN, HIGH); // Start of header, output HIGH for 16 pulse widths
- digitalWrite(LEDPIN, HIGH); // Turn on on-board LED
- delayMicroseconds(PULSEWIDTH * 16);
- digitalWrite(OUTPUTPIN, LOW); // Second part of header, output LOW 8 pulse widths
- digitalWrite(LEDPIN, LOW); // Turn off on-board LED
- delayMicroseconds(PULSEWIDTH * 8);
- // START BIT: always 1
- SendOne();
- }
- // Signals to follow a command to the radio
- void Postamble() {
- // STOP BITS: always 1
- SendOne();
- SendOne();
- }
Add Comment
Please, Sign In to add comment