Close

It started to work

A project log for JVC to Clio

I want to use a JVC car radio with Renault Clio's built-in steering wheel remote

nmeth-csabaNĂ©meth Csaba 12/17/2017 at 08:070 Comments

The first version of the working code.

Let me mention here that this is my first project with Arduino and I never used c++ before. But I was intent and did not give up.

I will clean the code but at least it works well. There are still some unused function what I found in other's similar projects. I left them in the code because I will use them later.

Big thanks for @Manu. His code was very useful for my project: https://hackaday.io/project/27439-smart-car-radio

/*
  ############################################################################################################
  #  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.  #
  ############################################################################################################

  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.

  ############
  # JVC part #
  ############
             _____________
  +---------|_____________|---------+
  | .1  ·2  ·3  ·4  ·5  ·6  ·7  ·8  |
  |                                 |
  | ˙9  ·10 ·11 ·12 ·13 ·14 ·15 ·16 |
  +---------------------------------+

  1 Ground             (black)
  2 +12v ignition      (red)
  3 Power Antenna      (blue/white)        ***  Arduino INPUT (Is powered on?)
  4 Not connected      (not connected)
  5 Right rear +       (grey)
  6 Right rear -       (grey/black)
  7 Left rear -        (white/black)
  8 Left rear +        (white)
  9 +12v battery       (yellow)
  10 +12v illumination (orange/white)
  11 Not connected     (not connected)
  12 Remote control    (blue/yellow)       ***  Arduino OUTPUT
  13 Right front +     (purple)
  14 Right front -     (purple/black)
  15 Left front -      (green/black)
  16 Left front +      (green)

  Protocol specifications for pin12:
  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

  Special thanks to: https://www.avforums.com/threads/jvc-stalk-adapter-diy.248455/page-3

  #############
  # Clio part #
  #############
  ISO 10487 defines a standard for connectors for the head unit to the car's electrical system, consisting of a system of four different connectors typically used in head units for car audio.
   Power (A)
     +-----------------+
     | |1  |3  |5  |7  |
    _|                 |
   |_  |2  |4  |6  |8  |
     +-----------------+

   A1 Speed signal  (pink)              *** Arduino INPUT
   A2 Phone mute    (not connected)     ---                      ---
   A3 Not connected (not connected)     ---                      ---
   A4 +12v battery  (red/pink)          ---                      *** JVC
   A5 Antenna motor (grey)                  Arduino ***          *** JVC
   A6 Illumination  (blue)              ---                      *** JVC
   A7 +12v switched (yellow/pink)       *** Arduino              *** JVC
   A8 Ground        (black)             *** Arduino              *** JVC

  Miscellaneous (C)
    +--------------+
   ++   -    -     |
  ++    5    2     |
  |  -     -       +-+
 ++  6     3         |
 ++     -    -     +-+
  |     4    1     +-+
  +----------------+
  1 CANL          (brown)
  2 CANH          (purple)
  3 CANH          (purple)
  4 CANL          (brown)
  5 Radio ON      (grey)               ***  Arduino OUTPUT
  6 Not connected

  Special thanks to: http://megane.com.pl/topic/47797-wyswietlacz-radia-update-list-protokol/
  Protocol: CAN 11bit, 1Mbps

  ################
  # Arduino part #
  ################

             +---+
      +------|USB|------+
      |[ ]J1 +---+      |
      |( )TX0     RAW( )|  RAW
      |( )RXI     GND( )|  GND
 GND  |( )GND     RST( )|  RESET
 GND  |( )GND     VCC( )|  VCC
      |( )2        A3( )|
INT0  |(I)3   /\   A2( )|
      |(O)4  /  \  A1( )|
      |(O)5  \  /  A0( )|
      |(I)6   \/   15( )|  SCK
      |( )7        14( )|  MISO
      |( )8        16( )|  MOSI
      |(I)9        10( )|
      +-----------------+
           ProMicro
  I = Input
  O = Output

  3 CAN_INTERRUPT_PIN
  4 JVC_REMOTE_PIN
  5 CLIO_DISPLAY_PIN
  6 JVC_ANTENNA_PIN
  9 CLIO_SPEEDSIGNAL_PIN

*/

#include <SPI.h>
#include "mcp_can.h" // https://github.com/coryjfowler/MCP_CAN_lib
#include "mcp_can_dfs.h"
#include "SimpleTimer.h"

SimpleTimer timer;

// Define JVC commands
const int JVC_VOL_UP         = 0x04; // Volume Up
const int JVC_VOL_DOWN       = 0x05; // Volume Down
const int JVC_SOURCE         = 0x08;
//const int JVC_EQUALIZER    = 0x0D;
const int JVC_MUTE           = 0x0E;
const int JVC_TRACK_FORW     = 0x12;
const int JVC_TRACK_BACK     = 0x13;
const int JVC_FOLDER_FORW    = 0x14;
const int JVC_FOLDER_BACK    = 0x15;
const int JVC_VOICE_CONTROL  = 0x1A;
//const int JVC_UNKNOWN1     = 0x37;
//const int JVC_UNKNOWN2     = 0x58;
const int JVC_PULSE_WIDTH    = 527; // Pulse width in µs
const int JVC_ADDRESS        = 0x47; // Address that the radio responds to
const int JVC_REMOTE_PIN     = 4; // Connect optocoupler input through a 1k resistor to this pin

const int CAN_INTERRUPT_PIN       = 3;
const int CAN_SPI_CS_PIN          = 10;
MCP_CAN CAN(CAN_SPI_CS_PIN); // Set CS pin
unsigned char CAN_MESSAGE_LENGTH  = 0;
unsigned char CAN_RECEIVED_MESSAGE[8];
long unsigned int CAN_ID;
byte CAN_SEND_RESULT;
unsigned char CAN_TEST_MESSAGE[]  = {
  0x10, /* Set text */
  0x1C, /* If we want to inform about them display here 0x1C and additional bytes further */
  0x7F, /* ??? */
  0x55, /* For old type: station number not set */
  0x55, /* Normal text */
  '1', '2', '3', '4', '5', '6', '7', '8', /* 8 characters for the old type */
  0x10, /* Serparator? */
  'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd', '!', '\0', /* 12 characters for new type + null byte */
};
const unsigned char CLIO_CAN_KEEPALIVE[8]                = {0x79, 0x00, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81};
const unsigned char CLIO_CAN_KEEPALIVE_ACK[8]            = {0x69, 0x00, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2};
const unsigned char CLIO_CAN_5C1_MESSAGE[8]              = {0x74, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81};

const unsigned char CLIO_CAN_REMOTE_VOL_UP[8]            = {0x03, 0x89, 0x00, 0x03, 0xA2, 0xA2, 0xA2, 0xA2};
const unsigned char CLIO_CAN_REMOTE_VOL_UP_LONG[8]       = {0x03, 0x89, 0x00, 0x43, 0xA2, 0xA2, 0xA2, 0xA2};
const unsigned char CLIO_CAN_REMOTE_VOL_DOWN[8]          = {0x03, 0x89, 0x00, 0x04, 0xA2, 0xA2, 0xA2, 0xA2};
const unsigned char CLIO_CAN_REMOTE_VOL_DOWN_LONG[8]     = {0x03, 0x89, 0x00, 0x44, 0xA2, 0xA2, 0xA2, 0xA2};
const unsigned char CLIO_CAN_REMOTE_PAUSE[8]             = {0x03, 0x89, 0x00, 0x05, 0xA2, 0xA2, 0xA2, 0xA2};
const unsigned char CLIO_CAN_REMOTE_PAUSE_LONG[8]        = {0x03, 0x89, 0x00, 0x85, 0xA2, 0xA2, 0xA2, 0xA2};
const unsigned char CLIO_CAN_REMOTE_SOURCE_RIGHT[8]      = {0x03, 0x89, 0x00, 0x01, 0xA2, 0xA2, 0xA2, 0xA2};
const unsigned char CLIO_CAN_REMOTE_SOURCE_RIGHT_LONG[8] = {0x03, 0x89, 0x00, 0x81, 0xA2, 0xA2, 0xA2, 0xA2};
const unsigned char CLIO_CAN_REMOTE_SOURCE_LEFT[8]       = {0x03, 0x89, 0x00, 0x02, 0xA2, 0xA2, 0xA2, 0xA2};
const unsigned char CLIO_CAN_REMOTE_SOURCE_LEFT_LONG[8]  = {0x03, 0x89, 0x00, 0x82, 0xA2, 0xA2, 0xA2, 0xA2};
const unsigned char CLIO_CAN_REMOTE_SELECT[8]            = {0x03, 0x89, 0x00, 0x00, 0xA2, 0xA2, 0xA2, 0xA2};
const unsigned char CLIO_CAN_REMOTE_SELECT_LONG[8]       = {0x03, 0x89, 0x00, 0x80, 0xA2, 0xA2, 0xA2, 0xA2};
const unsigned char CLIO_CAN_REMOTE_ROLL_DOWN[8]         = {0x03, 0x89, 0x01, 0x01, 0xA2, 0xA2, 0xA2, 0xA2};
const unsigned char CLIO_CAN_REMOTE_ROLL_UP[8]           = {0x03, 0x89, 0x01, 0x41, 0xA2, 0xA2, 0xA2, 0xA2};
const unsigned char CLIO_CAN_REMOTE_ACK[8]               = {0x74, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81};
/* Remote - JVC remapping
  vol_up              - Vol+
  vol_up_long         - Vol++
  vol_down            - Vol-
  vol_down_long       - Vol--
  mute                - Mute
  mute_long           - Mute
  source_left         - FolderBack
  source_left_long    - TrackBack long
  source_right        - FolderForw
  source_right_long   - TrackForw long
  select              - Source
  select_long         - Voice Control
  roll_up             - TrackBack
  roll_down           - TrackForw
  
*/

String Txt = "";      // text to display
String savedTxt = ""; // new text waiting to be displayed
bool flagUpdateTxt = false;

const int CLIO_DISPLAY_PIN = 5; // Connect optocoupler input through a 1k resistor to this pin

const int JVC_ANTENNA_PIN = 6;

const int CLIO_SPEEDSIGNAL_PIN = 9;
unsigned long CLIO_SPEEDSIGNAL_duration;
byte CLIO_SPEED;


void setup() {
  Serial.begin(9600);
  delay(1000);
  Serial.println("Serial configured");

  // CAN 11 bits 500kbauds
  if (CAN.begin(MCP_ANY, CAN_500KBPS, MCP_8MHZ) == CAN_OK)  // Baud rates defined in mcp_can_dfs.h
    Serial.println("CAN Init OK.");
  else
    Serial.println("CAN Init Failed.");
  CAN.setMode(MCP_NORMAL);
  pinMode(CAN_INTERRUPT_PIN, INPUT); // start interrupt
  pinMode(JVC_REMOTE_PIN, OUTPUT); // Set pin to output
  digitalWrite(JVC_REMOTE_PIN, LOW);  // Output LOW to make sure optocoupler is off
  pinMode(CLIO_DISPLAY_PIN, OUTPUT);
  digitalWrite(CLIO_DISPLAY_PIN, LOW);  // Output LOW to make sure optocoupler is off
  pinMode(JVC_ANTENNA_PIN, INPUT_PULLUP);
  pinMode(CLIO_SPEEDSIGNAL_PIN, INPUT_PULLUP);

  CLIO_CAN_startSync();
  delay(1);
  CLIO_CAN_syncOK();
  delay(1);
  //  startSync();
  //  delay(1);
  CLIO_CAN_syncDisp(); // triggers 1c1 and 0a9 on the display side: response 5C1 and 4A9
  delay(10);
  CAN.sendMsgBuf(0x5C1, 0, 8, CLIO_CAN_5C1_MESSAGE);
  CAN.sendMsgBuf(0x4A9, 0, 8, CLIO_CAN_REMOTE_ACK);
  CLIO_CAN_initDisplay();
  delay(1);
  CLIO_CAN_registerDisplay();
  delay(1);
  CLIO_CAN_enableDisplay();
  delay(50);
  delay(10);
  timer.setInterval(700, CLIO_CAN_syncOK);
  Serial.println("Setup done");
}

void loop() {
  // receive CAN
  if (!digitalRead(CAN_INTERRUPT_PIN))
  {
    CAN.readMsgBuf(&CAN_ID, &CAN_MESSAGE_LENGTH, CAN_RECEIVED_MESSAGE);    // read data,  CAN_MESSAGE_LENGTH: data length, buf: data buf
    if (CAN_ID == 0x3CF && memcmp(CAN_RECEIVED_MESSAGE, CLIO_CAN_KEEPALIVE_ACK, 8) == 0) { // pong received
      //      CAN.sendMsgBuf(0x3DF, 0, 8, CLIO_CAN_KEEPALIVE);
      //Serial.println("Pion");Serial.println();
    }
    //    else if(CAN_ID == 0x521) { // TODO: check entire frame (not just can ID)
    //      Serial.println("TEXT ACK received"); Serial.println();
    //    }
    else if (CAN_ID == 0x1C1) {
      CAN.sendMsgBuf(0x5C1, 0, 8, CLIO_CAN_5C1_MESSAGE);
    }
    // Receiving from the steering wheel remote
    else if (CAN_ID == 0x0A9) {
      CAN_SEND_RESULT = CAN.sendMsgBuf(0x4A9, 0, 8, CLIO_CAN_REMOTE_ACK);
      if (CAN_SEND_RESULT == CAN_OK)
        Serial.println("CLIO_CAN_REMOTE_ACK Message Sent Successfully!");
      else
        Serial.println("Error Sending CLIO_CAN_REMOTE_ACK Message...");
        
      if (memcmp(CAN_RECEIVED_MESSAGE, CLIO_CAN_REMOTE_PAUSE, 8) == 0) {
        Serial.println("mute received");
        JVC_SendCommand(JVC_MUTE);
        Serial.println("Mute was sent to JVC");
      }
      else if (memcmp(CAN_RECEIVED_MESSAGE, CLIO_CAN_REMOTE_PAUSE_LONG, 8) == 0) {
        Serial.println("long mute received");
        JVC_SendCommand(JVC_MUTE);
        Serial.println("Mute was sent to JVC");
      }
      else if (memcmp(CAN_RECEIVED_MESSAGE, CLIO_CAN_REMOTE_VOL_UP, 8) == 0) {
        Serial.println("Vol+ received");
                JVC_SendCommand(JVC_VOL_UP);
        Serial.println("Vol+ was sent to JVC");
      }
      else if (memcmp(CAN_RECEIVED_MESSAGE, CLIO_CAN_REMOTE_VOL_UP_LONG, 8) == 0) {
        Serial.println("Vol++ received");
        JVC_SendCommand(JVC_VOL_UP); //need to be fix
        delay(50);
        JVC_SendCommand(JVC_VOL_UP);
        Serial.println("Vol++ was sent to JVC");
      }
      else if (memcmp(CAN_RECEIVED_MESSAGE, CLIO_CAN_REMOTE_VOL_DOWN, 8) == 0) {
        Serial.println("Vol- received");
        JVC_SendCommand(JVC_VOL_DOWN);
        Serial.println("Vol- was sent to JVC");
      }
      else if (memcmp(CAN_RECEIVED_MESSAGE, CLIO_CAN_REMOTE_VOL_DOWN_LONG, 8) == 0) {
        Serial.println("Vol-- received");
        JVC_SendCommand(JVC_VOL_DOWN); //need to be fix
        delay(50);
        JVC_SendCommand(JVC_VOL_DOWN);
        Serial.println("Vol-- was sent to JVC");
      }
      else if (memcmp(CAN_RECEIVED_MESSAGE, CLIO_CAN_REMOTE_SELECT, 8) == 0) {
        Serial.println("Select received");
        JVC_SendCommand(JVC_SOURCE); //need to be fix
        Serial.println("Source was sent to JVC");
      }
      else if (memcmp(CAN_RECEIVED_MESSAGE, CLIO_CAN_REMOTE_SELECT_LONG, 8) == 0) {
        Serial.println("Long select received");
        JVC_SendCommand(JVC_VOICE_CONTROL); //need to be fix
        Serial.println("Voice Control was sent to JVC");
      }
      else if (memcmp(CAN_RECEIVED_MESSAGE, CLIO_CAN_REMOTE_SOURCE_LEFT, 8) == 0) {
        Serial.println("Source left received");
        JVC_SendCommand(JVC_FOLDER_BACK); //need to be fix
        Serial.println("Folder back was sent to JVC");
      }
      else if (memcmp(CAN_RECEIVED_MESSAGE, CLIO_CAN_REMOTE_SOURCE_LEFT_LONG, 8) == 0) {
        Serial.println("Long Source left received");
        JVC_SendCommand(JVC_FOLDER_BACK); //need to be fix
        Serial.println("Folder back was sent to JVC");
      }
      else if (memcmp(CAN_RECEIVED_MESSAGE, CLIO_CAN_REMOTE_SOURCE_RIGHT, 8) == 0) {
        Serial.println("Source right received");
        JVC_SendCommand(JVC_FOLDER_FORW); //need to be fix
        Serial.println("Folder forw was sent to JVC");
      }
      else if (memcmp(CAN_RECEIVED_MESSAGE, CLIO_CAN_REMOTE_SOURCE_RIGHT_LONG, 8) == 0) {
        Serial.println("Long Source right received");
        JVC_SendCommand(JVC_FOLDER_FORW); //need to be fix
        Serial.println("Folder forw was sent to JVC");
      }
      else if (memcmp(CAN_RECEIVED_MESSAGE, CLIO_CAN_REMOTE_ROLL_DOWN, 8) == 0) {
        Serial.println("Roll down received");
        JVC_SendCommand(JVC_TRACK_FORW); //need to be fix
        Serial.println("Track forw was sent to JVC");
      }
      else if (memcmp(CAN_RECEIVED_MESSAGE, CLIO_CAN_REMOTE_ROLL_UP, 8) == 0) {
        Serial.println("Roll up received");
        JVC_SendCommand(JVC_TRACK_BACK); //need to be fix
        Serial.println("Track back was sent to JVC");
      }
      else {
        Serial.print("CAN ID: ");
        Serial.print(CAN_ID, HEX);
        Serial.print(" Data: ");
        for(int i = 0; i<CAN_MESSAGE_LENGTH; i++)           // Print each byte of the data
        {
          if(CAN_RECEIVED_MESSAGE[i] < 0x10)                // If data byte is less than 0x10, add a leading zero
          {
            Serial.print("0");
          }
          Serial.print(CAN_RECEIVED_MESSAGE[i], HEX);
          Serial.print(" ");
        }
        Serial.println();
      }
    }
    else {
      Serial.print("CAN ID: ");
      Serial.print(CAN_ID, HEX);
      Serial.print(" Data: ");
      for(int i = 0; i<CAN_MESSAGE_LENGTH; i++)           // Print each byte of the data
      {
        if(CAN_RECEIVED_MESSAGE[i] < 0x10)                // If data byte is less than 0x10, add a leading zero
        {
          Serial.print("0");
        }
        Serial.print(CAN_RECEIVED_MESSAGE[i], HEX);
        Serial.print(" ");
      }
      Serial.println();
    }
  }
  // TODO: periodically send sync command to display (100ms -> 1sec)
  //CLIO_CAN_syncOK();
  timer.run();

//  if (flagUpdateTxt) {
//    Serial.println("flagUpdate");
//    Txt = savedTxt;
//    flagUpdateTxt = false;
//    Serial.print(Txt);
//    Serial.println(Txt.length() - 1); // doesn't count ending char

    // pre-filter known bad strings:
//    Txt.replace("OI FM", "OUI FM");
//    Txt.replace("%antexts1", "");
//    Txt.replace("%antexts2", "");
//    Serial.println(Txt);
//  }
  
//    static int count = 0;
//    if(Txt == "") { // empty string at startup
//      display8ByteString("DOROTTYA");
//      if(count++ > 25) {
//        Txt == "";
//        display8ByteString("        ");
//      }
//    }
  
  
   // String teststr = "ZACK DE LA ROCHA - WE WANT IT ALL";
  //scrollDisplay(teststr/*Txt*/);
  //wordScroll(/*teststr*/Txt);
    //semiScroll(/*teststr*/Txt);
    //delay(200);
}

void printDisp(char text_to_display) {
  char *arg;
  int aNumber;

  String s = "";

  //  Serial.println("Display: ");

  arg = text_to_display;
  while (arg != NULL) {
    s += arg;
    arg = text_to_display;
    if (arg != NULL) { // check if space separator (if several spaces, just one is inserted)
      s += ' ';
    }
    if (s.length() > 8) { // check if length max
      s.remove(8);
    }
  }
  //  Serial.print(s); Serial.print(" ("); Serial.print(s.length()); Serial.println(")");
  //  Serial.println();

  while (s.length() < 8) {
    s += ' '; // pad string with spaces to make 8 byte string
  }

  String TextCmd = "";
  TextCmd += '\x10';
  TextCmd += '\x19';
  TextCmd += '\x76';
  TextCmd += '\x60';
  TextCmd += '\x01';
  TextCmd += s;
  TextCmd += '\x10';
  TextCmd += s;
  TextCmd += "    ";  // 4 spaces to make a 12-byte string
  char charArray[28] = {'\0'};
  TextCmd.toCharArray(charArray, 27);
  //  Serial.println(TextCmd);
  //  for(int i = 0; i < 28; i++) {
  //    Serial.print(charArray[i], HEX); Serial.print(' ');
  //  }
  //  Serial.println();
  send_to_display(0x121, (byte *)(charArray), 27);
}

void send_to_display(word id, byte * data, byte datasz) {
  do_send_to(id, data, datasz, 0x81);
}

void do_send_to(word id, byte * data, byte datasz, byte filler) {
  unsigned char packet[8] = {'\0'};
  byte packetnum = 0, i, slen = datasz;

  while (slen > 0) {
    i = 0;
    if (packetnum > 0) {
      packet[0] = 0x20 + packetnum; /* Another package with one message */
      i++;
    }

    while ((i < 8) && (slen > 0)) {
      packet[i] = *data;
      data++;
      slen--;
      i++;
    }

    for (; i < 8; i++)
      packet[i] = filler;

    CAN.sendMsgBuf((unsigned long)(id), 0, 8, packet);
    //canTransmit((unsigned long)(id), packet, 8);
    //    Serial.println(packetnum);
    packetnum++;
    delay(2); /* TODO: Here we should wait for the display / radio response instead of the delay */
    if (!digitalRead(CAN_INTERRUPT_PIN))
    {
      CAN.readMsgBuf(&CAN_ID, &CAN_MESSAGE_LENGTH, CAN_RECEIVED_MESSAGE);    // read data,  CAN_MESSAGE_LENGTH: data length, buf: data buf
      //        Serial.print(CAN.getCanId(), HEX); Serial.println(" received");
    }
  }
}

void display8ByteString(String s) {
  if (s.length() != 8 ) {
    Serial.println("String must be 8 bytes long");
    return;
  }

  String TextCmd = "";
  TextCmd += '\x10';
  TextCmd += '\x19';
  TextCmd += '\x76';
  TextCmd += '\x60';
  TextCmd += '\x01';
  TextCmd += s;
  TextCmd += '\x10';
  TextCmd += s.substring(0, 7);
  TextCmd += "    ";  // 4 spaces to make a 12-byte string
  char charArray[28] = {'\0'};
  TextCmd.toCharArray(charArray, 27);
  //  Serial.println(TextCmd);
  //  for(int i = 0; i < 28; i++) {
  //    Serial.print(charArray[i], HEX); Serial.print(' ');
  //  }
  //  Serial.println();
  send_to_display(0x121, (byte *)(charArray), 27);
}

void scrollDisplay(String s) {
  if(s.length() == 0) {
    return;  
  }

  s.trim(); // remove \r\n at the end of string
  
  //Serial.print(s); Serial.print(" ("); Serial.print(s.length()); Serial.println(")");
  //Serial.println();

  String s8 = s.substring(0,8);
  while( s8.length() < 8) {
    s8 += ' ';  // pad to 8 byte string  
  }
  //Serial.println(s8);
  
  // display 1st 8 bytes
  display8ByteString(s8);
  CLIO_CAN_syncOK(); // every 100ms to 1sec
  delay(1000);
  CLIO_CAN_syncOK(); // every 100ms to 1sec
  
  // scroll after 8 bytes
  for(int i=8;i<s.length();i++) {
    //Serial.println(i);
    if( (i+8) >= s.length()) {
      // pad last section to 8 bytes
      s8 = s.substring(i,s.length());
      while(s8.length()<8) {
        s8 += ' '; // pad string with spaces to make 8 byte string
      }
      i=s.length(); // stop scrolling at last section
    }
    else {
      s8 = s.substring(i,i+8);  
    }
    
    //Serial.println(s8);
    display8ByteString(s8);
    delay(500);
    CLIO_CAN_syncOK(); // every 100ms to 1sec
  }
}

void semiScroll(String s)
{
  if (s.length() == 0) {
    return;
  }

  s.trim(); // remove \r\n at the end of string

  String saveds = s;
  int Idx = 0;  // find the " - " index
  Idx = s.indexOf(" - ");
  if (Idx == -1) {
    exit;
  }
  //Serial.println(Idx);
  s = saveds.substring(0, Idx);
  //Serial.println(s);

  // Scroll artist string (until " - " token)
  String s8 = s.substring(0, 8);
  while ( s8.length() < 8) {
    s8 += ' ';  // pad to 8 byte string
  }
  //Serial.println(s8);

  // display 1st 8 bytes
  display8ByteString(s8);
  CLIO_CAN_syncOK(); // every 100ms to 1sec
  delay(1000);
  CLIO_CAN_syncOK(); // every 100ms to 1sec

  // scroll after 8 bytes
  if (s.length() > 8) {
    for (int i = 1; i < s.length(); i++) {
      //Serial.println(i);
      if ( (i + 8) >= s.length()) {
        // pad last section to 8 bytes
        s8 = s.substring(i, s.length());
        while (s8.length() < 8) {
          s8 += ' '; // pad string with spaces to make 8 byte string
        }
        i = s.length(); // stop scrolling at last section
      }
      else {
        s8 = s.substring(i, i + 8);
      }

      //Serial.println(s8);
      display8ByteString(s8);
      delay(500);
      CLIO_CAN_syncOK(); // every 100ms to 1sec
    }
  }


  // print separator
  display8ByteString("--------");
  CLIO_CAN_syncOK(); // every 100ms to 1sec
  delay(500);

  // Scroll song name
  s = saveds.substring(Idx + 3, saveds.length());
  //Serial.println(s);
  s8 = s.substring(0, 8);
  while ( s8.length() < 8) {
    s8 += ' ';  // pad to 8 byte string
  }
  //Serial.println(s8);

  // display 1st 8 bytes
  display8ByteString(s8);
  CLIO_CAN_syncOK(); // every 100ms to 1sec
  delay(1000);
  CLIO_CAN_syncOK(); // every 100ms to 1sec

  if (s.length() <= 8) {
    CLIO_CAN_syncOK();
    return;
  }

  // scroll after 8 bytes
  for (int i = 1; i < s.length(); i++) {
    //Serial.println(i);
    if ( (i + 8) >= s.length()) {
      // pad last section to 8 bytes
      s8 = s.substring(i, s.length());
      while (s8.length() < 8) {
        s8 += ' '; // pad string with spaces to make 8 byte string
      }
      i = s.length(); // stop scrolling at last section
    }
    else {
      s8 = s.substring(i, i + 8);
    }

    //Serial.println(s8);
    display8ByteString(s8);
    delay(500);
    CLIO_CAN_syncOK(); // every 100ms to 1sec
  }

  // print end separator
  display8ByteString("////////");
  CLIO_CAN_syncOK(); // every 100ms to 1sec
  delay(500);
}

void CLIO_CAN_startSync() {
  unsigned char startSyncMsg[8] = {0x7A, 0x01, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81};
  CAN_SEND_RESULT = CAN.sendMsgBuf(0x3DF, 0, 8, startSyncMsg);
  if (CAN_SEND_RESULT == CAN_OK)
    Serial.println("startSync Message Sent Successfully!");
  else
    Serial.println("Error Sending startSync Message...");
}

void CLIO_CAN_syncOK() {
  CAN.sendMsgBuf(0x3DF, 0, 8, CLIO_CAN_KEEPALIVE);
/*  if (CAN_SEND_RESULT == CAN_OK)
    Serial.println("syncOK Message Sent Successfully!");
  else
    Serial.println("Error Sending syncOK Message...");*/
}

void CLIO_CAN_syncDisp() {
  unsigned char syncDispMsg[8] = {0x70, 0x1A, 0x11, 0x00, 0x00, 0x00, 0x00, 0x01};
  CAN.sendMsgBuf(0x3DF, 0, 8, syncDispMsg);
  if (CAN_SEND_RESULT == CAN_OK)
    Serial.println("syncDisp Message Sent Successfully!");
  else
    Serial.println("Error Sending syncDisp Message...");
}

void CLIO_CAN_initDisplay() {
  unsigned char initDispMsg[8] = {0x70, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81};
  CAN_SEND_RESULT = CAN.sendMsgBuf(0x121, 0, 8, initDispMsg);
  if (CAN_SEND_RESULT == CAN_OK)
    Serial.println("initDisp Message Sent Successfully!");
  else
    Serial.println("Error Sending initDisp Message...");
}

void CLIO_CAN_registerDisplay() {
  unsigned char registerDispMsg[8] = {0x70, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81};
  CAN_SEND_RESULT = CAN.sendMsgBuf(0x1B1, 0, 8, registerDispMsg);
  if (CAN_SEND_RESULT == CAN_OK)
    Serial.println("registerDisp Message Sent Successfully!");
  else
    Serial.println("Error Sending registerDisp Message...");
}

void CLIO_CAN_enableDisplay() {
  unsigned char enableDispMsg[8] = {0x04, 0x52, 0x02, 0xFF, 0xFF, 0x81, 0x81, 0x81};
  CAN_SEND_RESULT = CAN.sendMsgBuf(0x1B1, 0, 8, enableDispMsg);
  if (CAN_SEND_RESULT == CAN_OK)
    Serial.println("enableDisp Message Sent Successfully!");
  else
    Serial.println("Error Sending enableDisp Message...");
}

void CLIO_CAN_clearDisplay() {
  display8ByteString("        ");
}

/* JVC Functions */
// Send a command to the radio, including the header, start bit, address and stop bits
void JVC_SendCommand(unsigned char value) {
  Serial.println("Sending JVC command");
  unsigned char i;
  JVC_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
    JVC_SendValue(JVC_ADDRESS);               // Send the address
    JVC_SendValue((unsigned char)value);  // Send the command
    JVC_Postamble();                      // Send signals to follow a command to the radio
  }
}

// Send a value (7 bits, LSB is sent first, value can be an address or command)
void JVC_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
      JVC_SendOne();
    else
      JVC_SendZero();
    tmp = tmp << 1; // Bitshift left by 1
  }
}

// Signals to transmit a '0' bit
void JVC_SendZero() {
  digitalWrite(JVC_REMOTE_PIN, HIGH);      // Output HIGH for 1 pulse width
  delayMicroseconds(JVC_PULSE_WIDTH);
  digitalWrite(JVC_REMOTE_PIN, LOW);       // Output LOW for 1 pulse width
  delayMicroseconds(JVC_PULSE_WIDTH);
}

// Signals to transmit a '1' bit
void JVC_SendOne() {
  digitalWrite(JVC_REMOTE_PIN, HIGH);      // Output HIGH for 1 pulse width
  delayMicroseconds(JVC_PULSE_WIDTH);
  digitalWrite(JVC_REMOTE_PIN, LOW);       // Output LOW for 3 pulse widths
  delayMicroseconds(JVC_PULSE_WIDTH * 3);
}

// Signals to precede a command to the radio
void JVC_Preamble() {
  // HEADER: always LOW (1 pulse width), HIGH (16 pulse widths), LOW (8 pulse widths)
  digitalWrite(JVC_REMOTE_PIN, LOW);       // Make sure output is LOW for 1 pulse width, so the header starts with a rising edge
  delayMicroseconds(JVC_PULSE_WIDTH * 1);
  digitalWrite(JVC_REMOTE_PIN, HIGH);      // Start of header, output HIGH for 16 pulse widths
  delayMicroseconds(JVC_PULSE_WIDTH * 16);
  digitalWrite(JVC_REMOTE_PIN, LOW);       // Second part of header, output LOW 8 pulse widths
  delayMicroseconds(JVC_PULSE_WIDTH * 8);

  // START BIT: always 1
  JVC_SendOne();
}

// Signals to follow a command to the radio

        
    

Discussions