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

CDC.cpp

Committer:
petter
Date:
2016-03-18
Revision:
16:7bb8b161e00b
Parent:
15:82c3cc87bd02

File content as of revision 16:7bb8b161e00b:

// 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};

/** 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);
DigitalOut enable_transceiver(p19);

/** Timers: **/
Timer playback;
Timer sleep_timer;

/******************************************************************************
 * PUBLIC METHODS
 ******************************************************************************/

/** Enables CAN traciever transmit **/
void CDC::enable() {
    //enable_transceiver = 0; //active
    can.monitor(0);
    sleep_timer.reset();
    sleep_timer.start();
}

/** Disables CAN traciever transmit **/
void CDC::disable() {
    //enable_transceiver = 1; //sleep
    can.monitor(1);
    sleep_timer.stop();
}

/** Initializes CDC **/
void CDC::init() {
    enable();
    can.frequency(47619);
    printf("CAN Frequency set\r\n");
    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("PETTERS BT");
    
    sleep_timer.start();
}

/** Handles an incoming (RX) frame **/
IBUS_COMMAND CDC::get_cmd() {
    if(can.read(CAN_RxMsg)) {
        led2 = !led2;
        switch (CAN_RxMsg.id) {
            case DISPLAY_RESOURCE_REQ:
                sleep_timer.reset();
                send_can_frame(DISPLAY_RESOURCE_REQ, display_request_cmd);
                update_elapsed_time();
                send_can_frame(GENERAL_STATUS_CDC, cdc_status_cmd);
                led1 = !led1;
                printf("cdc_acttive = %i", cdc_active);
                return IBUS_HEAD_UNIT_ON;
            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[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 {
                    //Someone else has been granted the display, we need to back down
                    display_request_cmd[2] = 0x05; //message is considered new
                }
                break;            
        }
        return IBUS_OTHER_MESSAGE;
    }    
    if(sleep_timer.read() > 5) {
        return IBUS_HEAD_UNIT_OFF;  
    }
    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; 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 elapsed time in the cdc_status message **/
void CDC::update_elapsed_time() {
    cdc_status_cmd[5] = (char)(((int)playback.read())/60);
    cdc_status_cmd[6] = (char)(((int)playback.read())%60);
}

/** Resets the elapsed time in the cdc_status message **/
void CDC::reset_elapsed_time() {
    playback.reset();
}

/** Stops the elapsed time in the cdc_status message **/
void CDC::stop_elapsed_time() {
    playback.stop();
}

/** Starts the elapsed time in the cdc_status message **/
void CDC::start_elapsed_time() {
    playback.start();
}

/** Sets the current track number as playing **/
void CDC::set_track(char track_number) {
    cdc_status_cmd[4] = track_number;
}


/** 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 */
}