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