Spartelfant

Renault Twingo / Clio steering wheel remote via Arduino Nano

Jan 14th, 2016 (edited)
9,270
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C 12.46 KB | None | 0 0
  1. // ############################################################################################################
  2. // #  This code is meant to interface a 6-wire Renault Twingo / Clio steering wheel remote control with       #
  3. // #  a JVC car radio equipped with a 'steering wheel remote input'. Hardware used is an Arduino Nano-clone.  #
  4. // ############################################################################################################
  5. //
  6. // -- EDIT 2017-12-18 --
  7. // By popular request I've drawn a schematic. You can view it at [removed outdated link]
  8. // -- END OF EDIT --
  9. // -- EDIT 2021-08-17 --
  10. // Thanks to Leander Stark for pointing out an error in the schematic. The updated schematic is available at https://imgur.com/XBAL4d1
  11. // -- END OF EDIT --
  12. //
  13. // The steering wheel remote connection on the radio (blue/yellow wire or Tip in case of a TS connector)
  14. //   is connected to a pull-up resistor in the radio circuitry.
  15. //   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.
  16. //   Pulses are sent by pulling the radio's input to ground.
  17. //   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).
  18. //   Whenever I refer to HIGH or LOW, I'm talking about the Arduino output.
  19. // Protocol specifications:
  20. //   Pulse width                    527.5 µs
  21. //   Pulse interval for sending 0  1055.0 µs (HIGH for 1 pulse width, LOW for 1 pulse width)
  22. //   Pulse interval for sending 1  2110.0 µs (HIGH for 1 pulse width, LOW for 3 pulse widths)
  23. //   Note: since the delayMicroseconds() function accepts only unsigned integers, we're using a pulse width of 527 µs
  24. // Data packets are constructed as follows:
  25. //   HEADER     always LOW (1 pulse width), HIGH (16 pulse widths), LOW (8 pulse widths)
  26. //   START BIT  always 1
  27. //   ADDRESS    7-bits (0x00 to 0x7F), send LSB first, always 0x47 for JVC KD-R531, probably the same for all JVC car radio's
  28. //   COMMAND    7-bits (0x00 to 0x7F), send LSB first, see next section for list of known commands for JVC KD-R531
  29. //   STOP BITS  always 1, 1
  30. //   Note: the ADDRESS, COMMAND and STOP BITS are repeated 3 times to ensure the radio properly receives them.
  31. // Known commands for JVC KD-R531:
  32. //   HEX   DEC  BIN(7b)  FUNCTION
  33. //   0x04    4  0000100  Volume +
  34. //   0x05    5  0000101  Volume -
  35. //   0x08    8  0001000  Source cycle
  36. //   0x0D   13  0001101  Equalizer preset cycle
  37. //   0x0E   14  0001110  Mute toggle / Play/pause toggle
  38. //   0x12   18  0010010  Tuner Search + / Track + (and Manual Tune + / Fast Forward with press & hold)
  39. //   0x13   19  0010011  Tuner Search - / Track - (and Manual Tune - / Fast Rewind with press & hold)
  40. //   0x14   20  0010100  Tuner Preset + / USB Folder +
  41. //   0x15   21  0010101  Tuner Preset - / USB Folder -
  42. //   0x37   55  0110111  UNKNOWN, appears to be a sort of reset as well as a display test
  43. //   0x58   88  1011000  UNKNOWN, displays 'SN WRITING' where WRITING is blinking
  44.  
  45. // Define commands
  46. #define VOLUP       0x04
  47. #define VOLDOWN     0x05
  48. #define SOURCE      0x08
  49. #define EQUALIZER   0x0D
  50. #define MUTE        0x0E
  51. #define TRACKFORW   0x12
  52. #define TRACKBACK   0x13
  53. #define FOLDERFORW  0x14
  54. #define FOLDERBACK  0x15
  55. #define UNKNOWN1    0x37
  56. #define UNKNOWN2    0x58
  57.  
  58. // Renault Twingo / Clio steering wheel remote wire functions
  59. // pin_cycle_current  1     0              2
  60. //         OUTPUTS    BLUE  GREEN          YELLOW
  61. // INPUTS  PIN#       3     5              6
  62. // BLACK   2          MUTE  TOP RIGHT BTN  TOP LEFT BTN
  63. // RED     4          VOL+  BOTTOM BTN     VOL-
  64. //                    HIGH  HIGH           LOW    SCROLL UP (CCW)
  65. // BROWN   7          HIGH  LOW            HIGH  SCROLLWHEEL
  66. //                    LOW   HIGH           HIGH   SCROLL DN (CW)
  67. // Outputs are set LOW one at a time (the other outputs will be HIGH). Inputs (with internal pull-up) are then evaluated.
  68. //   If an input is being pulled LOW this means a button is being pressed. Taking into account which output is currently LOW
  69. //   we know which button this is. For example, is output pin 3 (Blue wire) is currently LOW and we also read LOW on
  70. //   input pin 2 (Black) we know the MUTE button is being pressed.
  71. // For the scrollwheel we must take into account its last known position in order to determine if there has been a change.
  72. // We can determine the direction based on which pins are being pulled LOW.
  73.  
  74. // Connect Renault Twingo / Clio steering wheel remote wires to these pins
  75. #define BLACKPIN    2 // D2
  76. #define BLUEPIN     3 // D3
  77. #define REDPIN      4 // D4
  78. #define GREENPIN    5 // D5
  79. #define YELLOWPIN   6 // D6
  80. #define BROWNPIN    7 // D7
  81.  
  82. // Connect optocoupler input through a 1k resistor to this pin
  83. #define OUTPUTPIN   8 // D8
  84.  
  85. // On-board LED, useful for debugging
  86. #define LEDPIN     13 // D13
  87.  
  88. // Pulse width in µs
  89. #define PULSEWIDTH 527
  90.  
  91. // Address that the radio responds to
  92. #define ADDRESS 0x47
  93.  
  94. // Set number of output pins and put those pins in an array to cycle through when polling the input pins
  95. #define OUT_PINS 3
  96. unsigned char out_pins[OUT_PINS] = {GREENPIN, BLUEPIN, YELLOWPIN};
  97.  
  98. void setup() {
  99.   pinMode(OUTPUTPIN, OUTPUT);    // Set the proper pin as output
  100.   digitalWrite(OUTPUTPIN, LOW);  // Output LOW to make sure optocoupler is off
  101.  
  102.   // Set the pins connected to the steering wheel remote as input / output
  103.   pinMode(BLACKPIN, INPUT_PULLUP);
  104.   pinMode(BLUEPIN, OUTPUT);
  105.   pinMode(REDPIN, INPUT_PULLUP);
  106.   pinMode(GREENPIN, OUTPUT);
  107.   pinMode(YELLOWPIN, OUTPUT);
  108.   pinMode(BROWNPIN, INPUT_PULLUP);
  109.  
  110.   pinMode(LEDPIN, OUTPUT);                  // Set pin connected to on-board LED as output...
  111.   digitalWrite(LEDPIN, LOW);                // ...and turn LED off
  112.   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
  113.     delay(100);
  114.     digitalWrite(LEDPIN, !digitalRead(LEDPIN));
  115.   }
  116.   delay(100);
  117.   digitalWrite(LEDPIN, LOW);                // Make sure LED ends up being off
  118. }
  119.  
  120. // The steering wheel remote has 6 buttons and a scrollwheel, interfaced via 6 wires.
  121. // This function will cycle through the output pins, setting one pin LOW at a time.
  122. // It will then poll the input pins to see which input pins - if any - are pulled LOW.
  123. unsigned char GetInput(void) {
  124.   static unsigned char pin_cycle_current = 0;  // To keep track of which output pin is currently LOW
  125.   static unsigned char pin_cycle_stored;       // To store the last known scrollwheel position
  126.   static boolean first_run = true;             // After booting, there is no known last position for the scrollwheel
  127.                                                // So on the first poll of the scrollwheel just store the current position and don't send a command
  128.   unsigned char i;
  129.  
  130.   if (++pin_cycle_current > (OUT_PINS - 1)) pin_cycle_current = 0;     // Reset pin_cycle_current counter after last pin
  131.  
  132.   for (i = 0; i < OUT_PINS; i++) {                                     // Cycle through the output pins, setting one of them LOW and the rest HIGH
  133.     if (i == pin_cycle_current)
  134.       digitalWrite(out_pins[i], LOW);
  135.     else
  136.       digitalWrite(out_pins[i], HIGH);
  137.   }
  138.  
  139.   if (!digitalRead(BROWNPIN)) {                                        // We're only interested if this pin is being pulled LOW
  140.     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
  141.                                                                        //   we came through here, then the scrollwheel has changed position
  142.       signed char scrollwheel_current = pin_cycle_current - pin_cycle_stored; // Result of this calculation can range from -2 to 2
  143.       pin_cycle_stored = pin_cycle_current;                            // Store which output pin is currently LOW
  144.       if (first_run) {                                                 // If this is the first run, don't send a command
  145.         first_run = false;                                             //   (since there was no previously known scrollwheel position)
  146.         return 0;
  147.       }
  148.       if ((scrollwheel_current == 1) || (scrollwheel_current == -2)) { // If above calculation resulted in 1 or -2 the scrollwheel was rotated up (ccw)
  149.         return FOLDERBACK;
  150.       }else {                                                          // If above calculation resulted in anything else the scrollwheel was rotated down (cw)
  151.         return FOLDERFORW;
  152.       }
  153.     }
  154.   }
  155.  
  156.   if (!digitalRead(REDPIN)) {   // We're only interested if this pin is being pulled LOW
  157.     switch(pin_cycle_current) {
  158.     case 0:                     // RED (input) is LOW while GREEN (output) is LOW: bottom button pressed
  159.       return SOURCE;
  160.     case 1:                     // RED (input) is LOW while BLUE (output) is LOW: volume + button pressed
  161.       return VOLUP;
  162.     case 2:                     // RED (input) is LOW while YELLOW (output) is LOW: volume - button pressed
  163.       return VOLDOWN;
  164.     }
  165.   }
  166.  
  167.   if (!digitalRead(BLACKPIN)) { // We're only interested if this pin is being pulled LOW
  168.     switch(pin_cycle_current) {
  169.     case 0:                     // BLACK (input) is LOW while GREEN (output) is LOW: top right button is pressed
  170.       return TRACKFORW;
  171.     case 1:                     // BLACK (input) is LOW while BLUE (output) is LOW: mute button is pressed
  172.       return MUTE;
  173.     case 2:                     // BLACK (input) is LOW while YELLOW (output) is LOW: top left button is pressed
  174.       return TRACKBACK;
  175.     }
  176.   }
  177.  
  178.   return 0;
  179. }
  180.  
  181. void loop() {
  182.   unsigned char Key = GetInput();  // If any buttons are being pressed the GetInput() function will return the appropriate command code
  183.  
  184.   if (Key) {  // If no buttons are being pressed the function will have returned 0 and no command will be sent
  185.     SendCommand(Key);
  186.   }
  187. }
  188.  
  189. // Send a value (7 bits, LSB is sent first, value can be an address or command)
  190. void SendValue(unsigned char value) {
  191.   unsigned char i, tmp = 1;
  192.   for (i = 0; i < sizeof(value) * 8 - 1; i++) {
  193.     if (value & tmp)  // Do a bitwise AND on the value and tmp
  194.       SendOne();
  195.     else
  196.       SendZero();
  197.     tmp = tmp << 1; // Bitshift left by 1
  198.   }
  199. }
  200.  
  201. // Send a command to the radio, including the header, start bit, address and stop bits
  202. void SendCommand(unsigned char value) {
  203.   unsigned char i;
  204.   Preamble();                         // Send signals to precede a command to the radio
  205.   for (i = 0; i < 3; i++) {           // Repeat address, command and stop bits three times so radio will pick them up properly
  206.     SendValue(ADDRESS);               // Send the address
  207.     SendValue((unsigned char)value);  // Send the command
  208.     Postamble();                      // Send signals to follow a command to the radio
  209.   }
  210. }
  211.  
  212. // Signals to transmit a '0' bit
  213. void SendZero() {
  214.   digitalWrite(OUTPUTPIN, HIGH);      // Output HIGH for 1 pulse width
  215.   digitalWrite(LEDPIN, HIGH);         // Turn on on-board LED
  216.   delayMicroseconds(PULSEWIDTH);
  217.   digitalWrite(OUTPUTPIN, LOW);       // Output LOW for 1 pulse width
  218.   digitalWrite(LEDPIN, LOW);          // Turn off on-board LED
  219.   delayMicroseconds(PULSEWIDTH);
  220. }
  221.  
  222. // Signals to transmit a '1' bit
  223. void SendOne() {
  224.   digitalWrite(OUTPUTPIN, HIGH);      // Output HIGH for 1 pulse width
  225.   digitalWrite(LEDPIN, HIGH);         // Turn on on-board LED
  226.   delayMicroseconds(PULSEWIDTH);
  227.   digitalWrite(OUTPUTPIN, LOW);       // Output LOW for 3 pulse widths
  228.   digitalWrite(LEDPIN, LOW);          // Turn off on-board LED
  229.   delayMicroseconds(PULSEWIDTH * 3);
  230. }
  231.  
  232. // Signals to precede a command to the radio
  233. void Preamble() {
  234.   // HEADER: always LOW (1 pulse width), HIGH (16 pulse widths), LOW (8 pulse widths)
  235.   digitalWrite(OUTPUTPIN, LOW);       // Make sure output is LOW for 1 pulse width, so the header starts with a rising edge
  236.   digitalWrite(LEDPIN, LOW);          // Turn off on-board LED
  237.   delayMicroseconds(PULSEWIDTH * 1);
  238.   digitalWrite(OUTPUTPIN, HIGH);      // Start of header, output HIGH for 16 pulse widths
  239.   digitalWrite(LEDPIN, HIGH);         // Turn on on-board LED
  240.   delayMicroseconds(PULSEWIDTH * 16);
  241.   digitalWrite(OUTPUTPIN, LOW);       // Second part of header, output LOW 8 pulse widths
  242.   digitalWrite(LEDPIN, LOW);          // Turn off on-board LED
  243.   delayMicroseconds(PULSEWIDTH * 8);
  244.  
  245.   // START BIT: always 1
  246.   SendOne();
  247. }
  248.  
  249. // Signals to follow a command to the radio
  250. void Postamble() {
  251.   // STOP BITS: always 1
  252.   SendOne();
  253.   SendOne();
  254. }
Add Comment
Please, Sign In to add comment