Simulated the CD changer of a Saab to implement a bluetooth connection to the car stereo. Control of playback device (phone) with steering wheel buttons. Needs a RN52 bluetooth transciever and a CAN transiever. So far only audio playback and control via steering wheel buttons implemented. Hands free calling planned.

Dependencies:   mbed

Revision:
0:6cf6e566c0da
Child:
2:10c60edc8573
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CDC.cpp	Mon Jan 04 15:31:12 2016 +0000
@@ -0,0 +1,382 @@
+// I-Bus information from http://pikkupossu.1g.fi/tomi/projects/i-bus/i-bus.html
+// 290h - Steering wheel and SID buttons | Rx
+// 328h - SID audio text | Tx
+// 32Ch - ACC to SID text
+// 32Fh - TWICE to SID text
+// 337h - SPA to SID text
+// 348h - SID audio text control | Tx
+// 34Ch - ACC to SID text control
+// 34Fh - TWICE to SID text control
+// 357h - SPA to SID text control
+// 368h - SID text priority | Tx
+// 3C0h - CD Changer control | Rx
+// 3C8h - CD Changer information | Tx
+// 430h - SID beep request | Tx
+// 6A1h - Audio head unit | Rx
+// 6A2h - CD changer | Tx
+// 320h - Doors, central locking and seat belts | Rx
+
+/** Includes **/
+#include "CDC.h"
+
+/** Various constants used for identifying the CDC **/
+#define CDC_APL_ADR              0x11
+#define CDC_SID_FUNCTION_ID      0x19
+
+/** TX IDs: **/
+#define GENERAL_STATUS_CDC       0x3C8
+#define DISPLAY_RESOURCE_REQ     0x348 // 'Stolen' from the IHU since the CDC doesn't send this message
+#define WRITE_TEXT_ON_DISPLAY    0x328 // 'Stolen' from the IHU since the CDC doesn't send this message
+#define NODE_STATUS_TX           0x6A2
+#define SOUND_REQUEST            0x430
+
+/** RX IDs: **/
+#define LOCK_STATUS             0x320
+#define IHU_BUTTONS             0x3C0
+#define DISPLAY_RESOURCE_GRANT  0x368
+#define NODE_STATUS_RX          0x6A1
+#define STEERING_WHEEL_BUTTONS  0x290
+
+
+/** Variables: **/
+bool cdc_active = false; // True while our module, the simulated CDC, is active.
+bool mute = false;
+int toggle_shuffle = 1;
+int ninefive_cmd[] = {0x32,0x00,0x00,0x16,0x01,0x02,0x00,0x00,-1};
+int beep_cmd[] = {0x80,0x04,0x00,0x00,0x00,0x00,0x00,0x00,-1};
+int cdc_status_cmd[] = {0xE0,0x00,0x01,0x41,0x01,0x00,0x00,0xD0,-1};
+int display_request_cmd[] = {CDC_APL_ADR,0x02,0x05,CDC_SID_FUNCTION_ID,0x00,0x00,0x00,0x00,-1};
+//int display_clear_cmd[] = {CDC_APL_ADR,0x02,0xFF,CDC_SID_FUNCTION_ID,0x00,0x00,0x00,0x00,-1};
+
+/** Com: **/
+CAN can(p9, p10);
+CANMessage CAN_TxMsg;
+CANMessage CAN_RxMsg;
+CANMessage CAN_DispMsg[3];
+
+/** I/O: **/
+DigitalOut led1(LED1, 0);
+DigitalOut led2(LED2, 0);
+
+/******************************************************************************
+ * PUBLIC METHODS
+ ******************************************************************************/
+
+/** Initializes CDC **/
+void CDC::init() {
+    can.frequency(47619);
+    printf("CAN Frequency set\r\n");
+    CAN_wrFilter(1, LOCK_STATUS);
+    CAN_wrFilter(1, IHU_BUTTONS);
+    CAN_wrFilter(1, DISPLAY_RESOURCE_GRANT);
+    CAN_wrFilter(1, NODE_STATUS_RX);
+    CAN_wrFilter(1, STEERING_WHEEL_BUTTONS);
+    CAN_wrFilter(1, DISPLAY_RESOURCE_REQ);
+    printf("CAN Filters set\r\n");
+    
+    CAN_TxMsg.len = 8;
+    CAN_DispMsg[0].len = 8;
+    CAN_DispMsg[1].len = 8;
+    CAN_DispMsg[2].len = 8;
+    CAN_DispMsg[0].id = WRITE_TEXT_ON_DISPLAY;
+    CAN_DispMsg[1].id = WRITE_TEXT_ON_DISPLAY;
+    CAN_DispMsg[2].id = WRITE_TEXT_ON_DISPLAY;
+    CAN_DispMsg[0].data[0] = 0x42; // order, new
+    CAN_DispMsg[1].data[0] = 0x01; // order
+    CAN_DispMsg[2].data[0] = 0x00; // order
+    CAN_DispMsg[0].data[1] = 0x96; // Address of the SID
+    CAN_DispMsg[1].data[1] = 0x96; // Address of the SID
+    CAN_DispMsg[2].data[1] = 0x96; // Address of the SID
+    CAN_DispMsg[0].data[2] = 0x02; // Writing to row 2
+    CAN_DispMsg[1].data[2] = 0x02; // Writing to row 2
+    CAN_DispMsg[2].data[2] = 0x02; // Writing to row 2
+    display("BT REDO");
+}
+
+/** Handles an incoming (RX) frame **/
+IBUS_COMMAND CDC::get_cmd() {  
+     if(can.read(CAN_RxMsg)) {
+        led2 = !led2;
+        CAN_TxMsg.data[0]++;
+        switch (CAN_RxMsg.id) {
+            case LOCK_STATUS:
+            {
+                int locked = (CAN_RxMsg.data[1] >> 7) & 1;
+                if(locked){
+                    return IBUS_DOORS_LOCKED;
+                }
+                else{
+                    return IBUS_DOORS_UNLOCKED;
+                }
+            }
+            case DISPLAY_RESOURCE_REQ:
+                send_can_frame(DISPLAY_RESOURCE_REQ, display_request_cmd);
+                send_can_frame(GENERAL_STATUS_CDC, cdc_status_cmd);
+                led1 = !led1;
+                break;
+            case NODE_STATUS_RX:
+                send_can_frame(NODE_STATUS_TX, ninefive_cmd);
+                break;
+            case IHU_BUTTONS:
+                return get_ihu_cmd();
+            case STEERING_WHEEL_BUTTONS:
+                return get_steering_wheel_cmd();
+            case DISPLAY_RESOURCE_GRANT:
+                if ((CAN_RxMsg.data[0] == 0x02) && (CAN_RxMsg.data[1] == CDC_SID_FUNCTION_ID)) {
+                    //We have been granted the right to write text to the second row in the SID
+                    display_request_cmd[2] = 0x04;
+                    display_update();
+                }
+                else if (CAN_RxMsg.data[0] == 0x02) {
+                    //Someone else has been granted the second row, we need to back down
+                    display_request_cmd[2] = 0x05;
+                }
+                else if (CAN_RxMsg.data[0] == 0x00) {
+                    //Someone else has been granted the entire display, we need to back down
+                    display_request_cmd[2] = 0x05;
+                }
+                break;
+        }
+        return IBUS_OTHER_MESSAGE;
+    }
+    return IBUS_NO_MESSAGE;
+}
+
+/** Handles the IHU_BUTTONS frame that the IHU sends us when it wants to control some feature of the CDC **/
+IBUS_COMMAND CDC::get_ihu_cmd() {
+    bool event = (CAN_RxMsg.data[0] == 0x80);
+    if (!event) {
+        //Ignore the message if it wasn't sent on event
+        return IBUS_OTHER_MESSAGE;
+    }
+    switch (CAN_RxMsg.data[1]) {    
+        case 0x24: // CDC = ON (CD/RDM button has been pressed twice)
+            cdc_active = true;
+            return IBUS_CDC_ON;
+        case 0x14: // CDC = OFF (Back to Radio or Tape mode)
+            cdc_active = false;
+            return IBUS_CDC_OFF;
+    }
+    if (cdc_active) {
+        switch (CAN_RxMsg.data[1]) {
+            case 0x35: // Track up
+                return IBUS_SKIP_FW;
+            case 0x36: // Track down
+                return IBUS_SKIP_BW;
+        }
+    }
+    return IBUS_OTHER_MESSAGE;
+}
+
+/** Handles the STEERING_WHEEL_BUTTONS frame * TODO connect the SID button events to actions **/
+IBUS_COMMAND CDC::get_steering_wheel_cmd() {
+    
+    bool event = (CAN_RxMsg.data[0] == 0x80);
+    if (!event) {
+        //Ignore the message if it wasn't sent on event
+        return IBUS_OTHER_MESSAGE;
+    }
+    if (!cdc_active) {
+        return IBUS_OTHER_MESSAGE;
+    }
+    switch (CAN_RxMsg.data[4]) {
+        case 0x04: // NXT button on wheel
+            return IBUS_NEXT;
+        case 0x10: // Seek+ button on wheel
+            return IBUS_SKIP_FW;
+        case 0x08: // Seek- button on wheel
+            return IBUS_SKIP_BW;
+        case 0x40: // Vol+ button on wheel
+            return IBUS_VOLUME_UP;
+        case 0x80: // Vol- button on wheel
+            return IBUS_VOLUME_DOWN;
+    }
+    switch (CAN_RxMsg.data[5]) {
+        case 0x40: //  SET button on SID
+            return IBUS_SET;
+        case 0x80: //  CLEAR button on SID
+            return IBUS_CLEAR;
+    }
+    return IBUS_OTHER_MESSAGE;
+}
+
+
+
+/** Writes the provided text on the SID. NOTE the character set used by the SID is slightly nonstandard. "Normal" characters should work fine. **/
+void CDC::display(char text[]) {
+    if (!text) {
+        return;
+    }
+    
+    //Notify the SID that we want to display a message
+    send_can_frame(DISPLAY_RESOURCE_REQ, display_request_cmd);
+    
+    // Copy the provided string and make sure we have a new array of the correct length:
+    char display_text[15];
+    int i, n;
+    n = strlen(text);
+    if(n > 15) {
+        n = 15;
+    }
+    for (i = 0; i < n; i++) {
+        display_text[i] = text[i];
+    }
+    for (i = n + 1; i < 15; i++) {
+        display_text[i] = 0;
+    }
+    
+    CAN_DispMsg[0].data[2] = 0x82; // Sent on basetime, writing to row 2, message changed
+    CAN_DispMsg[0].data[3] = display_text[0];
+    CAN_DispMsg[0].data[4] = display_text[1];
+    CAN_DispMsg[0].data[5] = display_text[2];
+    CAN_DispMsg[0].data[6] = display_text[3];
+    CAN_DispMsg[0].data[7] = display_text[4];
+    
+    CAN_DispMsg[1].data[3] = display_text[5];
+    CAN_DispMsg[1].data[4] = display_text[6];
+    CAN_DispMsg[1].data[5] = display_text[7];
+    CAN_DispMsg[1].data[6] = display_text[8];
+    CAN_DispMsg[1].data[7] = display_text[9];
+    
+    CAN_DispMsg[2].data[3] = display_text[10];
+    CAN_DispMsg[2].data[4] = display_text[11];
+    CAN_DispMsg[2].data[5] = display_text[12];
+    CAN_DispMsg[2].data[6] = display_text[13];
+    CAN_DispMsg[2].data[7] = display_text[14];
+}
+
+/** Writes the provided text on the SID. NOTE the character set used by the SID is slightly nonstandard. "Normal" characters should work fine. **/
+void CDC::display_update() {
+    can.write(CAN_DispMsg[0]);
+    can.write(CAN_DispMsg[1]);
+    can.write(CAN_DispMsg[2]);
+    CAN_DispMsg[0].data[2] = 0x02; // Message not changed next transmission unless display() is called 
+}
+
+/** Sets the playback time in the cdc_status message **/
+void CDC::set_time(char minutes, char seconds) {
+    cdc_status_cmd[5] = minutes;
+    cdc_status_cmd[6] = seconds;
+}
+
+
+/** Formats and puts a frame on CAN bus **/
+void CDC::send_can_frame(int message_id, int *msg) {
+    CAN_TxMsg.id = message_id;
+    int i = 0;
+    while (msg[i] != -1) {
+        CAN_TxMsg.data[i] = msg[i];
+        i++;
+    }
+    can.write(CAN_TxMsg);
+}
+
+/** DEBUG: Prints the CAN TX frame to serial output **/
+void CDC::print_can_frame(CANMessage *msg){
+    printf("CAN message %X:", msg->id);
+    for (int i = 0; i < msg->len; i++) {
+        printf(" %X", msg->data[i]);
+    }
+    printf("\r\n");
+}
+
+/*----------------------------------------------------------------------------
+  setup acceptance filter.  CAN controller (1..2)
+ *----------------------------------------------------------------------------*/
+void CDC::CAN_wrFilter(uint32_t ctrl, uint32_t id)  {
+  static int CAN_std_cnt = 0;
+  static int CAN_ext_cnt = 0;
+         uint32_t buf0, buf1;
+         int cnt1, cnt2, bound1;
+
+  /* Acceptance Filter Memory full */
+  if ((((CAN_std_cnt + 1) >> 1) + CAN_ext_cnt) >= 512)
+    return;                                       /* error: objects full */
+
+  /* Setup Acceptance Filter Configuration 
+    Acceptance Filter Mode Register = Off  */                                 
+  LPC_CANAF->AFMR = 0x00000001;
+
+    id |= (ctrl-1) << 13;                        /* Add controller number */
+    id &= 0x0000F7FF;                            /* Mask out 16-bits of ID */
+
+    /* Move all remaining extended mask entries one place up                 
+       if new entry will increase standard ID filters list   */
+    if ((CAN_std_cnt & 0x0001) == 0 && CAN_ext_cnt != 0) {
+      cnt1   = (CAN_std_cnt >> 1);
+      bound1 = CAN_ext_cnt;
+      buf0   = LPC_CANAF_RAM->mask[cnt1];
+      while (bound1--)  {
+        cnt1++;
+        buf1 = LPC_CANAF_RAM->mask[cnt1];
+        LPC_CANAF_RAM->mask[cnt1] = buf0;
+        buf0 = buf1;
+      }        
+    }
+
+    if (CAN_std_cnt == 0)  {                     /* For entering first  ID */
+      LPC_CANAF_RAM->mask[0] = 0x0000FFFF | (id << 16);
+    }  else if (CAN_std_cnt == 1)  {             /* For entering second ID */
+      if ((LPC_CANAF_RAM->mask[0] >> 16) > id)
+        LPC_CANAF_RAM->mask[0] = (LPC_CANAF_RAM->mask[0] >> 16) | (id << 16);
+      else
+        LPC_CANAF_RAM->mask[0] = (LPC_CANAF_RAM->mask[0] & 0xFFFF0000) | id;
+    }  else  {
+      /* Find where to insert new ID */
+      cnt1 = 0;
+      cnt2 = CAN_std_cnt;
+      bound1 = (CAN_std_cnt - 1) >> 1;
+      while (cnt1 <= bound1)  {                  /* Loop through standard existing IDs */
+        if ((LPC_CANAF_RAM->mask[cnt1] >> 16) > id)  {
+          cnt2 = cnt1 * 2;
+          break;
+        }
+        if ((LPC_CANAF_RAM->mask[cnt1] & 0x0000FFFF) > id)  {
+          cnt2 = cnt1 * 2 + 1;
+          break;
+        }
+        cnt1++;                                  /* cnt1 = U32 where to insert new ID */
+      }                                          /* cnt2 = U16 where to insert new ID */
+
+      if (cnt1 > bound1)  {                      /* Adding ID as last entry */
+        if ((CAN_std_cnt & 0x0001) == 0)         /* Even number of IDs exists */
+          LPC_CANAF_RAM->mask[cnt1]  = 0x0000FFFF | (id << 16);
+        else                                     /* Odd  number of IDs exists */
+          LPC_CANAF_RAM->mask[cnt1]  = (LPC_CANAF_RAM->mask[cnt1] & 0xFFFF0000) | id;
+      }  else  {
+        buf0 = LPC_CANAF_RAM->mask[cnt1];        /* Remember current entry */
+        if ((cnt2 & 0x0001) == 0)                /* Insert new mask to even address */
+          buf1 = (id << 16) | (buf0 >> 16);
+        else                                     /* Insert new mask to odd  address */
+          buf1 = (buf0 & 0xFFFF0000) | id;
+     
+        LPC_CANAF_RAM->mask[cnt1] = buf1;        /* Insert mask */
+
+        bound1 = CAN_std_cnt >> 1;
+        /* Move all remaining standard mask entries one place up */
+        while (cnt1 < bound1)  {
+          cnt1++;
+          buf1  = LPC_CANAF_RAM->mask[cnt1];
+          LPC_CANAF_RAM->mask[cnt1] = (buf1 >> 16) | (buf0 << 16);
+          buf0  = buf1;
+        }
+
+        if ((CAN_std_cnt & 0x0001) == 0)         /* Even number of IDs exists */
+          LPC_CANAF_RAM->mask[cnt1] = (LPC_CANAF_RAM->mask[cnt1] & 0xFFFF0000) | (0x0000FFFF);
+      }
+    }
+    CAN_std_cnt++;
+  
+  /* Calculate std ID start address (buf0) and ext ID start address (buf1) */
+  buf0 = ((CAN_std_cnt + 1) >> 1) << 2;
+  buf1 = buf0 + (CAN_ext_cnt << 2);
+
+  /* Setup acceptance filter pointers */
+  LPC_CANAF->SFF_sa     = 0;
+  LPC_CANAF->SFF_GRP_sa = buf0;
+  LPC_CANAF->EFF_sa     = buf0;
+  LPC_CANAF->EFF_GRP_sa = buf1;
+  LPC_CANAF->ENDofTable = buf1;
+
+  LPC_CANAF->AFMR = 0x00000000;                  /* Use acceptance filter */
+}
\ No newline at end of file