Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
/* Melbus CDCHGR Emulator
* Program that emulates the MELBUS communication from a CD-changer (CD-CHGR) in a Volvo V70 (HU-xxxx) to enable AUX-input through the 8-pin DIN-contact.
* This setup is using an Arduino Nano 5v clone
*
* The HU enables the CD-CHGR in its source-menue after a successful initialization procedure is accomplished.
* The HU will remove the CD-CHGR everytime the car starts if it wont get an response from CD-CHGR (second init-procedure).
*
* Karl Hagström 2015-11-04
* mod by S. Zeller 2016-03-14
*
* This project went realy smooth thanks to these sources:
* http://volvo.wot.lv/wiki/doku.php?id=melbus
* https://github.com/festlv/screen-control/blob/master/Screen_control/melbus.cpp
* http://forums.swedespeed.com/showthread.php?50450-VW-Phatbox-to-Volvo-Transplant-(How-To)&highlight=phatbox
*
* pulse train width=120us (15us per clock cycle), high phase between two pulse trains is 540us-600us
*/
#define SERDBG 1
const uint8_t MELBUS_CLOCKBIT_INT = 1; //interrupt numer (INT1) on DDR3
const uint8_t MELBUS_CLOCKBIT = 3; //Pin D3 - CLK
const uint8_t MELBUS_DATA = 4; //Pin D4 - Data
const uint8_t MELBUS_BUSY = 5; //Pin D5 - Busy
volatile uint8_t melbus_ReceivedByte = 0;
volatile uint8_t melbus_CharBytes = 0;
volatile uint8_t melbus_OutByte = 0xFF;
volatile uint8_t melbus_LastReadByte[12] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
volatile uint8_t melbus_SendBuffer[9] = {0x00,0x02,0x00,0x01,0x80,0x01,0xC7,0x0A,0x02};
volatile uint8_t melbus_SendCnt=0;
volatile uint8_t melbus_DiscBuffer[6] = {0x00,0xFC,0xFF,0x4A,0xFC,0xFF};
volatile uint8_t melbus_DiscCnt=0;
volatile uint8_t melbus_Bitposition = 0x80;
volatile bool InitialSequence_ext = false;
volatile bool ByteIsRead = false;
volatile bool sending_byte = false;
volatile bool melbus_MasterRequested = false;
volatile bool melbus_MasterRequestAccepted = false;
volatile bool testbool = false;
volatile bool AllowInterruptRead = true;
volatile int incomingByte = 0; // for incoming serial data
//Startup seequence
void setup() {
//Data is deafult input high
pinMode(MELBUS_DATA, INPUT_PULLUP);
//Activate interrupt on clock pin (INT1, D3)
attachInterrupt(MELBUS_CLOCKBIT_INT, MELBUS_CLOCK_INTERRUPT, FALLING);
//Set Clockpin-interrupt to input
pinMode(MELBUS_CLOCKBIT, INPUT_PULLUP);
#ifdef SERDBG
//Initiate serial communication to debug via serial-usb (arduino)
Serial.begin(230400);
Serial.println("Initiating contact with Melbus:");
#endif
//Call function that tells HU that we want to register a new device
melbus_Init_CDCHRG();
}
//Main loop
void loop() {
//Waiting for the clock interrupt to trigger 8 times to read one byte before evaluating the data
#ifdef SERDBG
if (ByteIsRead) {
//Reset bool to enable reading of next byte
ByteIsRead=false;
if (incomingByte == ' ') {
if(melbus_LastReadByte[11] == 0x0 && (melbus_LastReadByte[10] == 0x4A || melbus_LastReadByte[10] == 0x4C || melbus_LastReadByte[10] == 0x4E) && melbus_LastReadByte[9] == 0xEC && melbus_LastReadByte[8] == 0x57 && melbus_LastReadByte[7] == 0x57 && melbus_LastReadByte[6] == 0x49 && melbus_LastReadByte[5] == 0x52 && melbus_LastReadByte[4] == 0xAF && melbus_LastReadByte[3] == 0xE0 && melbus_LastReadByte[2] == 0x0)
{
melbus_CharBytes=8; //print RDS station name
}
if(melbus_LastReadByte[1] == 0x0 && melbus_LastReadByte[0] == 0x4A)
{
Serial.println("\n LCD is master: (no CD init)");
}
else if(melbus_LastReadByte[1] == 0x0 && melbus_LastReadByte[0] == 0x4C)
{
Serial.println("\n LCD is master: (???)");
}
else if(melbus_LastReadByte[1] == 0x0 && melbus_LastReadByte[0] == 0x4E)
{
Serial.println("\n LCD is master: (with CD init)");
}
else if(melbus_LastReadByte[1] == 0x80 && melbus_LastReadByte[0] == 0x4E)
{
Serial.println("\n ???");
}
else if(melbus_LastReadByte[1] == 0xE8 && melbus_LastReadByte[0] == 0x4E)
{
Serial.println("\n ???");
}
else if(melbus_LastReadByte[1] == 0xF9 && melbus_LastReadByte[0] == 0x49)
{
Serial.println("\n HU is master: ");
}
else if(melbus_LastReadByte[1] == 0x80 && melbus_LastReadByte[0] == 0x49)
{
Serial.println("\n HU is master: ");
}
else if(melbus_LastReadByte[1] == 0xE8 && melbus_LastReadByte[0] == 0x49)
{
Serial.println("\n HU is master: ");
}
else if(melbus_LastReadByte[1] == 0xE9 && melbus_LastReadByte[0] == 0x4B)
{
Serial.println("\n HU is master: -> CDC");
}
else if(melbus_LastReadByte[1] == 0x81 && melbus_LastReadByte[0] == 0x4B)
{
Serial.println("\n HU is master: -> CDP");
}
else if(melbus_LastReadByte[1] == 0xF9 && melbus_LastReadByte[0] == 0x4E)
{
Serial.println("\n HU is master: ");
}
else if(melbus_LastReadByte[1] == 0x50 && melbus_LastReadByte[0] == 0x4E)
{
Serial.println("\n HU is master: ");
}
else if(melbus_LastReadByte[1] == 0x50 && melbus_LastReadByte[0] == 0x4C)
{
Serial.println("\n HU is master: ");
}
else if(melbus_LastReadByte[1] == 0x50 && melbus_LastReadByte[0] == 0x4A)
{
Serial.println("\n HU is master: ");
}
else if(melbus_LastReadByte[1] == 0xF8 && melbus_LastReadByte[0] == 0x4C)
{
Serial.println("\n HU is master: ");
}
if (melbus_CharBytes) {
Serial.write(melbus_LastReadByte[1]);
melbus_CharBytes--;
}else
{
Serial.print(melbus_LastReadByte[1],HEX);
Serial.write(' ');
}
}
}
#endif
//If BUSYPIN is HIGH => HU is in between transmissions
if (digitalRead(MELBUS_BUSY)==HIGH)
{
//Make sure we are in sync when reading the bits by resetting the clock reader
#ifdef SERDBG
if (melbus_Bitposition != 0x80) {
Serial.println(melbus_Bitposition,HEX);
Serial.println("\n not in sync! ");
}
#endif
if (incomingByte != 'k') {
melbus_Bitposition = 0x80;
melbus_OutByte = 0xFF;
melbus_SendCnt=0;
melbus_DiscCnt=0;
DDRD &= ~(1<<MELBUS_DATA);
PORTD |= (1<<MELBUS_DATA);
}
}
#ifdef SERDBG
if (Serial.available() > 0) {
// read the incoming byte:
incomingByte = Serial.read();
}
if (incomingByte == 'i') {
melbus_Init_CDCHRG();
Serial.println("\n forced init: ");
incomingByte=0;
}
#endif
if ((melbus_Bitposition == 0x80) && (PIND & (1<<MELBUS_CLOCKBIT)))
{
delayMicroseconds(7);
DDRD &= ~(1<<MELBUS_DATA);
PORTD |= (1<<MELBUS_DATA);
}
}
//Notify HU that we want to trigger the first initiate procedure to add a new device (CD-CHGR) by pulling BUSY line low for 1s
void melbus_Init_CDCHRG() {
//Disabel interrupt on INT1 quicker then: detachInterrupt(MELBUS_CLOCKBIT_INT);
EIMSK &= ~(1<<INT1);
// Wait untill Busy-line goes high (not busy) before we pull BUSY low to request init
while(digitalRead(MELBUS_BUSY)==LOW){}
delayMicroseconds(10);
pinMode(MELBUS_BUSY, OUTPUT);
digitalWrite(MELBUS_BUSY, LOW);
delay(1200);
digitalWrite(MELBUS_BUSY, HIGH);
pinMode(MELBUS_BUSY, INPUT_PULLUP);
//Enable interrupt on INT1, quicker then: attachInterrupt(MELBUS_CLOCKBIT_INT, MELBUS_CLOCK_INTERRUPT, RISING);
EIMSK |= (1<<INT1);
}
//Global external interrupt that triggers when clock pin goes high after it has been low for a short time => time to read datapin
void MELBUS_CLOCK_INTERRUPT() {
//Read status of Datapin and set status of current bit in recv_byte
if(melbus_OutByte & melbus_Bitposition){
DDRD &= (~(1<<MELBUS_DATA));
PORTD |= (1<<MELBUS_DATA);
}
//if bit [i] is "0" - make databpin low
else{
PORTD &= (~(1<<MELBUS_DATA));
DDRD |= (1<<MELBUS_DATA);
}
if (PIND & (1<<MELBUS_DATA)){
melbus_ReceivedByte |= melbus_Bitposition; //set bit nr [melbus_Bitposition] to "1"
}
else {
melbus_ReceivedByte &=~melbus_Bitposition; //set bit nr [melbus_Bitposition] to "0"
}
//if all the bits in the byte are read:
if (melbus_Bitposition==0x01) {
//Move every lastreadbyte one step down the array to keep track of former bytes
for(int i=11;i>0;i--){
melbus_LastReadByte[i] = melbus_LastReadByte[i-1];
}
if (melbus_OutByte != 0xFF) {
melbus_LastReadByte[0] = melbus_OutByte;
melbus_OutByte = 0xFF;
} else {
//Insert the newly read byte into first position of array
melbus_LastReadByte[0] = melbus_ReceivedByte;
}
//set bool to true to evaluate the bytes in main loop
ByteIsRead = true;
//Reset bitcount to first bit in byte
melbus_Bitposition=0x80;
if(melbus_LastReadByte[2] == 0x07 && (melbus_LastReadByte[1] == 0x1A || melbus_LastReadByte[1] == 0x4A) && melbus_LastReadByte[0] == 0xEE)
{
InitialSequence_ext = true;
}
else if(melbus_LastReadByte[2] == 0x0 && (melbus_LastReadByte[1] == 0x1C || melbus_LastReadByte[1] == 0x4C) && melbus_LastReadByte[0] == 0xED)
{
InitialSequence_ext = true;
}
else if((melbus_LastReadByte[0] == 0xE8 || melbus_LastReadByte[0] == 0xE9) && InitialSequence_ext == true){
InitialSequence_ext = false;
//Returning the expected byte to the HU, to confirm that the CD-CHGR is present (0xEE)! see "ID Response"-table here http://volvo.wot.lv/wiki/doku.php?id=melbus
melbus_OutByte = 0xEE;
}
else if((melbus_LastReadByte[2] == 0xE8 || melbus_LastReadByte[2] == 0xE9) && (melbus_LastReadByte[1] == 0x1E || melbus_LastReadByte[1] == 0x4E) && melbus_LastReadByte[0] == 0xEF)
{
// CartInfo
melbus_DiscCnt=6;
}
else if((melbus_LastReadByte[2] == 0xE8 || melbus_LastReadByte[2] == 0xE9) && (melbus_LastReadByte[1] == 0x19 || melbus_LastReadByte[1] == 0x49) && melbus_LastReadByte[0] == 0x22)
{
// Powerdown
melbus_OutByte = 0x00; // respond to powerdown;
melbus_SendBuffer[1]=0x02; // STOP
melbus_SendBuffer[8]=0x02; // STOP
}
else if((melbus_LastReadByte[2] == 0xE8 || melbus_LastReadByte[2] == 0xE9) && (melbus_LastReadByte[1] == 0x19 || melbus_LastReadByte[1] == 0x49) && melbus_LastReadByte[0] == 0x52)
{
// RND
}
else if((melbus_LastReadByte[2] == 0xE8 || melbus_LastReadByte[2] == 0xE9) && (melbus_LastReadByte[1] == 0x19 || melbus_LastReadByte[1] == 0x49) && melbus_LastReadByte[0] == 0x29)
{
// FF
}
else if((melbus_LastReadByte[2] == 0xE8 || melbus_LastReadByte[2] == 0xE9) && (melbus_LastReadByte[1] == 0x19 || melbus_LastReadByte[1] == 0x49) && melbus_LastReadByte[0] == 0x2F)
{
// FR
melbus_OutByte = 0x00; // respond to start;
melbus_SendBuffer[1]=0x08; // START
melbus_SendBuffer[8]=0x08; // START
}
else if((melbus_LastReadByte[3] == 0xE8 || melbus_LastReadByte[3] == 0xE9) && (melbus_LastReadByte[2] == 0x1A || melbus_LastReadByte[2] == 0x4A) && melbus_LastReadByte[1] == 0x50 && melbus_LastReadByte[0] == 0x01)
{
// D-
melbus_SendBuffer[3]--;
melbus_SendBuffer[5]=0x01;
}
else if((melbus_LastReadByte[3] == 0xE8 || melbus_LastReadByte[3] == 0xE9) && (melbus_LastReadByte[2] == 0x1A || melbus_LastReadByte[2] == 0x4A) && melbus_LastReadByte[1] == 0x50 && melbus_LastReadByte[0] == 0x41)
{
// D+
melbus_SendBuffer[3]++;
melbus_SendBuffer[5]=0x01;
}
else if((melbus_LastReadByte[4] == 0xE8 || melbus_LastReadByte[4] == 0xE9) && (melbus_LastReadByte[3] == 0x1B || melbus_LastReadByte[3] == 0x4B) && melbus_LastReadByte[2] == 0x2D && melbus_LastReadByte[1] == 0x00 && melbus_LastReadByte[0] == 0x01)
{
// T-
melbus_SendBuffer[5]--;
}
else if((melbus_LastReadByte[4] == 0xE8 || melbus_LastReadByte[4] == 0xE9) && (melbus_LastReadByte[3] == 0x1B || melbus_LastReadByte[3] == 0x4B) && melbus_LastReadByte[2] == 0x2D && melbus_LastReadByte[1] == 0x40 && melbus_LastReadByte[0] == 0x01)
{
// T+
melbus_SendBuffer[5]++;
}
else if((melbus_LastReadByte[4] == 0xE8 || melbus_LastReadByte[4] == 0xE9) && (melbus_LastReadByte[3] == 0x1B || melbus_LastReadByte[3] == 0x4B) && melbus_LastReadByte[2] == 0xE0 && melbus_LastReadByte[1] == 0x01 && melbus_LastReadByte[0] == 0x08 ){
// Playinfo
melbus_SendCnt=9;
}
if (melbus_SendCnt) {
melbus_OutByte=melbus_SendBuffer[9-melbus_SendCnt];
melbus_SendCnt--;
} else if (melbus_DiscCnt) {
melbus_OutByte=melbus_DiscBuffer[6-melbus_DiscCnt];
melbus_DiscCnt--;
}
} else {
//set bitnumber to address of next bit in byte
melbus_Bitposition>>=1;
}
EIFR |= (1 << INTF1);
}
@nenkovarna

Hello,
I'm interessed of this project and want to build one for my volvo HU.
Are you have a site or topic with this project with wiring conections, mp3 module...
thanks in advance :)
regards,
N.Stefanov

@archi
archi commented Mar 21, 2017 edited

@nenkovarna take a look at http://gizmosnack.blogspot.de/2015/11/aux-in-volvo-hu-xxxx-radio.html

The code also has the wiring in the variables:

const uint8_t MELBUS_CLOCKBIT_INT = 1; //interrupt numer (INT1) on DDR3
const uint8_t MELBUS_CLOCKBIT = 3; //Pin D3 - CLK
const uint8_t MELBUS_DATA = 4; //Pin D4  - Data
const uint8_t MELBUS_BUSY = 5; //Pin D5  - Busy

Just connect the Clock to Pin D3, Data to D4 and Busy to D5.
The link has a diagram on which you can see which pins on the radio connector are Clock/Data/Busy (I think originally it is either from festlv [who also runs wot.lv], or some forum).

Also: Take a look at the websites mentioned in the gist comment.
I also have a project on https://github.com/archi/melbus - but haven't got around to updating it (I think this gist is better functionality-wise).

Good luck with your project :)

@VincentGijsen

I also started tinkering with the HU (850). your code works very nice.

I was wondering if you already figured out how to address the sending of text. I find it quite a shame the those who have figured it out (by sniffing the melbus with the original equipment), keep the results to themselves... If i would have the sat-tuner I would definitly sniff the connection and at least share the whole session, either via decoding the protocol, or the logical analyser dumps.

Just thinking out loud: I see that there is some code to show RDS information, have you tried using the same format and sending the same packages while tuned to the SAT channel? i'll try this myself in the coming days, but perhaps you've already tried?

I also might write some 'brute-force' code to just send a truckload of combinations to the radio, and see if I can get some garbage on the display...

You wouldn't happen to know the hardware used in the grom/imiv? I might fire up the hex-editor or IDA and try to wrestle through the hex files, looking for bytes similar to the public availble id's used by melbus... however I rather not. I would really like to send some text or a phone-number to my display using my arduino + bluetooth module.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment