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.
Diff: CDC.cpp
- 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