-
JVC to Clio 2.0
01/23/2019 at 13:45 • 0 commentsVersion 2.0 with new hardware will be docuemted here: https://hackaday.io/project/163553-jvc-to-clio-20
-
To-do's
12/17/2017 at 08:26 • 0 comments- I think it have to replace SimpleTimer to e.g. CounTimer for the display control because I will re-use the timers and seems to me that SimpleTimer does not support it: "After f has been called, the interval is deleted, therefore the value timerId is no longer valid."
- I did not found JVC commands for the short press phone button and volume knob press. It means I cannot initiate any calls and I cannot terminate them. I found command for long press of phone button but it reject the call. JVC supports voice control but I am afraid it would be very unreliable (in Hungarian language) and very unconfortable and dangerous during driving if it recognizes wrong names and/or commands. Therefore I think I will use bluetooth. If anyone has knowledge or hints on it please share with me. My plan is that arduino will connect to my phone (Honor 9) via Bluetooth, read the contact list (send it to Clio's display) one by one. Then I will be able to initiate and terminate calls with a remote control button. At this point I do not know how should I start. Which bluetooth shield/module do I need or is it even possible. But I hope so ;)
Unfortunately I did not find any similar project yet.
-
It started to work
12/17/2017 at 08:07 • 0 commentsThe 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
JVC part
11/26/2017 at 13:27 • 0 commentsI have tried some version of JVC controller program I found on the Internet but only this one worked well.
Unfortunately I don't remember where I found it, but I will try to get the source URL and share it.
/* ############# # 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 ################ # Arduino part # ################ +---+ +------|USB|------+ |[ ]J1 +---+ | |( )TX0 RAW( )| RAW |( )RXI GND( )| GND GND |( )GND RST( )| RESET GND |( )GND VCC( )| VCC |( )2 A3( )| INT0 |( )3 /\ A2( )| |(O)4 / \ A1( )| |(O)5 \ / A0( )| |(I)6 \/ 15( )| SCK |( )7 14( )| MISO |( )8 16( )| MOSI |(I)9 10( )| +-----------------+ ProMicro 4 JVC_REMOTE_PIN 5 CLIO_DISPLAY_PIN 6 JVC_ANTENNA_PIN 9 CLIO_SPEEDSIGNAL_PIN */ // 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 // Connect optocoupler input through a 1k resistor to this pin #define JVC_REMOTE_PIN 4 // D8 // Pulse width in µs #define PULSEWIDTH 527 // Address that the radio responds to #define ADDRESS 0x47 int IncomingByte = 0; // Initialize Serial Buffer void setup() { pinMode(JVC_REMOTE_PIN, OUTPUT); // Set pin to output digitalWrite(JVC_REMOTE_PIN, LOW); // Output LOW to make sure optocoupler is off Serial.begin(9600); Serial.println("1 - Volume Up"); Serial.println("2 - Volume Down"); Serial.println("3 - Source"); Serial.println("4 - Sound"); Serial.println("5 - Mute"); Serial.println("6 - Skip Fwd"); Serial.println("7 - Skip Back"); Serial.println("8 - Skip Fwd Hold"); Serial.println("9 - Skip Back Hold"); } void loop() { if (Serial.available() > 0) { IncomingByte = Serial.read(); switch (IncomingByte) { case '1': SendCommand(VOLUP); break; case '2': SendCommand(VOLDOWN); break; case '3': SendCommand(SOURCE); break; case '4': SendCommand(EQUALIZER); break; case '5': SendCommand(MUTE); break; case '6': SendCommand(TRACKFORW); break; case '7': SendCommand(TRACKBACK); break; case '8': SendCommand(FOLDERFORW); break; case '9': SendCommand(FOLDERBACK); break; default:; } } } // 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) { Serial.println("Sending"); 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() { // Serial.println("zero"); digitalWrite(JVC_REMOTE_PIN, HIGH); // Output HIGH for 1 pulse width delayMicroseconds(PULSEWIDTH); digitalWrite(JVC_REMOTE_PIN, LOW); // Output LOW for 1 pulse width delayMicroseconds(PULSEWIDTH); } // Signals to transmit a '1' bit void SendOne() { // Serial.println("uno"); digitalWrite(JVC_REMOTE_PIN, HIGH); // Output HIGH for 1 pulse width delayMicroseconds(PULSEWIDTH); digitalWrite(JVC_REMOTE_PIN, LOW); // Output LOW for 3 pulse widths 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(JVC_REMOTE_PIN, LOW); // Make sure output is LOW for 1 pulse width, so the header starts with a rising edge delayMicroseconds(PULSEWIDTH * 1); digitalWrite(JVC_REMOTE_PIN, HIGH); // Start of header, output HIGH for 16 pulse widths delayMicroseconds(PULSEWIDTH * 16); digitalWrite(JVC_REMOTE_PIN, LOW); // Second part of header, output LOW 8 pulse widths delayMicroseconds(PULSEWIDTH * 8); // START BIT: always 1 SendOne(); } // Signals to follow a command to the radio void Postamble() { // STOP BITS: always 1 SendOne(); SendOne(); }
Speed Signal measuring
11/24/2017 at 19:57 • 0 commentsI measured values on pin A1 of the ISO 10487 connector.
Here you are the measured values:
Speed (km/h) Pulse duration (μs) 30 ~13000 40 ~10000 50 ~7700 70 ~5400 90 ~4200 100 ~3750 100 ~3400 120 ~3200 It can be calculated more accurate values with "distance = speed x time" formula. It seems the distance is around 0,105m. I this case the duration are the followings:
Speed (km/h) Pulse duration (μs) 30 12600 40 9450 50 7560 70 5400 90 4200 100 3780 110 3436 120 3150 140 2700 150 2520 The arduino program for the measuring was the following:
/* 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 A2 Phone mute A3 Not connected A4 +12v battery A5 Antenna motor A6 Illumination A7 +12v switched A8 Ground *** +---+ +------|USB|------+ |[ ]J1 +---+ | |( )TX0 RAW( )| RAW |( )RXI GND( )| GND GND |( )GND RST( )| RESET GND |( )GND VCC( )| VCC |( )2 A3( )| INT0 |( )3 /\ A2( )| |( )4 / \ A1( )| |( )5 \ / A0( )| |( )6 \/ 15( )| SCK |(X)7 14( )| MISO |( )8 16( )| MOSI |( )9 10( )| +-----------------+ ProMicro */ int SignalPin = 7; unsigned long duration; void setup() { Serial.begin(9600); pinMode(SignalPin, INPUT_PULLUP); } void loop() { duration = pulseIn(SignalPin, LOW); Serial.println(duration); delay(1000); }
I forgot to mention that there was a 4N25 optocoupler on pin7. I will attach some pictures about my prototype board soon...