'******************************************************************************* ' ' HMLiberator ' ' Version: 0.worksnotcomplete ' ' by Steve Vigneau ' ' http://nuxx.net/wiki/HMLiberator ' ' This device is designed to sit between the Honda Music Link (HML) and any ' iPod with a dock connector. It accepts the Mode 4 (AiR) commands from ' the HML and replies as if it is an iPod, fooling the HML into thinking ' an iPod is always connected. Replies are sent to the HML as needed in ' order to maintain functionality, and selective commands are passed to ' the connected iPod. ' ' Developed on mikroElektronika's EasyPIC4 with a PIC16F688. ' '******************************************************************************* ' ' References: ' ' All iPod Remote protocol information acquired from either iPod Linux ' project (http://ipodlinux.org) or direct observation of commands and ' responses. ' ' Information about the Mode 4 communication between the Honda Music Link ' and iPod has been gathered through direct observation by myself. ' ' The USART Interrupt procedures are largely based on the irq_usart library for ' mikroBasic by mikroElektronika forum users 'jpc' and 'lsv'. Parts of the ' library which weren't needed have been removed, some things have been cleaned ' up for readability, and a few other changes have been made. ' ' mikroElektronika Forum links: ' ' jpc's thread 'interrupt-driven Usart library' for mikroPascal: ' http://www.mikroe.com/forum/viewtopic.php?t=3335 ' ' irq_usart mikroBasic port thread with posts from 'jpc' and 'lsv': ' http://www.mikroe.com/forum/viewtopic.php?t=4208 ' ' Many other ideas were taken from various discussion threads around the ' mikroElektronika forums. Thanks very much to everyone whose postings there ' helped. ' '******************************************************************************* ' ' Hardware Notes: ' ' - Both hardware and software UARTs set to run at 9600. ' ' - Specific Pins on PIC16F688 (14-Pin): ' RC5 / Pin 5 is Hardware UART RX ' RC4 / Pin 6 is Hardware UART TX ' RC3 / Pin 7 is Software UART RX (Must be specified, but we don't use it.) ' RC2 / Pin 8 is Software UART TX ' '******************************************************************************* ' ' To Do: ' ' - State holding current track? changetrack() where 0 and 1 is prev and next? ' - Keep time of false tracks? (May be a bad idea as then the head unit display ' will roll over when actual track hasn't. ' - Keep state of current track. Is first track 0x00 or 0x01? ' - Why does sending the EUSART loads of data hang the PIC? ' ' - Recalculate 'TMR1' for 20MHz crystal. ' '******************************************************************************* program HMLiberator '******************************************************************************* ' ' General Constants ' '******************************************************************************* const RXBUFSIZE as byte = 40 ' RX FIFO buffer size. '******************************************************************************* ' ' Constants which may be sent by the HMLiberator as iPod commands or responses. ' Some of these are normal remote control commands, others are tailored replies ' for making it appear as an iPod to the Honda Music Link. ' ' iPod commands are formatted as follows: ' First Byte: Number of Command Bytes ' Middle Byte(s): iPod Command Itself ' Last Byte: Checksum calculated with (0x100 - size and command bytes) & 0xFF ' ' See http://nuxx.net/wiki/Apple_Accessory_Protocol for more information on ' Apple Accessory Protocol commands. Note that the header bytes (0xFF and ' 0x55) are left off of the constants, as they are inserted by sendcommand(). ' Checksums have been precalculated to save time. ' '******************************************************************************* ' Commands to be sent to the iPod. const MODE2 as byte[5] = (0x03,0x00,0x01,0x02,0xFA) 'Simple Remote const MODE4 as byte[5] = (0x03,0x00,0x01,0x04,0xF8) 'Adv. Remote const RELBUTTON as byte[5] = (0x03,0x02,0x00,0x00,0xFB) 'Release Button const PLAYBUTTON as byte[5] = (0x03,0x02,0x00,0x01,0xFA) 'Play Button const VOLUP as byte[5] = (0x03,0x02,0x00,0x02,0xF9) 'Volume Up const VOLDOWN as byte[5] = (0x03,0x02,0x00,0x04,0xF7) 'Volume Down const FFBUTTON as byte[5] = (0x03,0x02,0x00,0x08,0xF3) '>>| Button const RWBUTTON as byte[5] = (0x03,0x02,0x00,0x10,0xEB) '|<< Button const NEXTALBUM as byte[5] = (0x03,0x02,0x00,0x20,0xDB) 'Next Album const PREVALBUM as byte[5] = (0x03,0x02,0x00,0x40,0xBB) 'Previous Album const STOPPLAY as byte[5] = (0x03,0x02,0x00,0x80,0x7B) 'Stop Play const JUSTPLAY as byte[6] = (0x04,0x02,0x00,0x00,0x01,0xF9) 'Play if Paused const JUSTPAUSE as byte[6] = (0x04,0x02,0x00,0x00,0x02,0xF8) 'Pause if Playing const MUTE as byte[6] = (0x04,0x02,0x00,0x00,0x04,0xF6) 'Mutes iPod const NEXTPLIST as byte[6] = (0x04,0x02,0x00,0x00,0x20,0xDA) 'Next Playlist const PREVPLIST as byte[6] = (0x04,0x02,0x00,0x00,0x40,0xBA) 'Previous Playlist const SHUFFLE as byte[6] = (0x04,0x02,0x00,0x00,0x80,0x7A) 'Shuffle Mode const REPEAT as byte[7] = (0x05,0x02,0x00,0x00,0x00,0x01,0xF8) 'Repeat Mode const POWEROFF as byte[7] = (0x05,0x02,0x00,0x00,0x00,0x04,0xF5)'Sleeps iPod const POWERON as byte[7] = (0x05,0x02,0x00,0x00,0x00,0x08,0xF1) 'Wakes iPod const MENUBUTTON as byte[7] = (0x05,0x02,0x00,0x00,0x00,0x40,0xB9)'Menu Button const SELBUTTON as byte[7] = (0x05,0x02,0x00,0x00,0x00,0x80,0x79) 'Select Button const SCROLLUP as byte[8] = (0x06,0x02,0x00,0x00,0x00,0x00,0x01,0xF7)'Scroll up. const SCROLLDOWN as byte[8] = (0x06,0x02,0x00,0x00,0x00,0x00,0x02,0xF6)'Scroll down. ' Replies to the Honda Music Link ' Reply To: Get Type and Size (0x00 0x12 / 0xE7) ' Reply As: 30GB 5G / Video const RGETTYPESIZE as byte[7] = (0x05,0x04,0x00,0x13,0x01,0x0B,0xD8) ' Reply To: Start Polling Mode (0x00 0x26 0x01) ' Reply As: Polling Mode Set (Okay, command was 0x00 0x26) const RSETPOLLING as byte[8] = (0x06,0x04,0x00,0x01,0x00,0x00,0x26,0xCF) ' Reply To: Set Shuffle Mode; Off (0x00 0x2E 0x00) ' Reply As: Shuffle Mode Set (Okay, command was 0x00 0x2E) const RSETSHUFFLE as byte[8] = (0x06,0x04,0x00,0x01,0x00,0x00,0x2E,0xC7) ' Reply To: Set Repeat Mode: All Songs (0x00 0x31 0x02) ' Reply As: Repeat Mode Set (Okay, command was 0x00 0x31) const RSETREPEAT as byte[8] = (0x06,0x04,0x00,0x01,0x00,0x00,0x31,0xC4) ' Reply To: AiR Playback Command (0x00 0x29) ' Reply As: OK, command was (0x00 0x29) const RAIRPLAYBACK as byte[8] = (0x06,0x04,0x00,0x01,0x00,0x00,0x29,0xCC) ' Reply To: Switch To Main Library Playlist (0x00 0x16) ' Reply As: (Okay, command was 0x00 0x16) const RSWITCHMAINPLAYLIST as byte[8] = (0x06,0x04,0x00,0x01,0x00,0x00,0x16,0xDF) ' Reply To: Get Count of Playlists (0x00 0x18 0x01) ' Reply As: Reply to 0x18, Two Playlists (0x02) const RGETPLAYLISTCOUNT as byte[9] = (0x07,0x04,0x00,0x19,0x00,0x00,0x00,0x02,0xDA) ' Reply to: Get Count of Songs (0x00 0x18 0x05) ' Reply As: Reply to 0x18, ninety-nine Songs (0x63) const RGETTOTALSONGCOUNT as byte[9] = (0x07,0x04,0x00,0x19,0x00,0x00,0x00,0x63,0x79) ' Reply To: Get names for playlists, starting at first, total of two playlists. ' (00 1A, 01, 00 00 00 00, 00 00 00 02) ' Reply As: Offset Zero (iPod Name) (00 00 00 00), then name of item: HMLiberator const RPLAYLIST0 as byte[21] = (0x13,0x04,0x00,0x1B,0x00,0x00,0x00,0x00,0x48, 0x4D,0x4C,0x69,0x62,0x65,0x72,0x61,0x74,0x6F, 0x72,0x00,0x95) ' Reply As: Offset One (00 00 00 01), then name of item: FreesYouriPod const RPLAYLIST1 as byte[25] = (0x17,0x04,0x00,0x1B,0x00,0x00,0x00,0x01,0x46, 0x72,0x65,0x65,0x73,0x20,0x59,0x6F,0x75,0x72, 0x20,0x69,0x50,0x6F,0x64,0x00,0x59) ' Reply To: Get Number of Songs In Current Playlist (0x00 0x35) ' Reply As: 99 Songs (0x00 0x63) const RSONGSINPLAYLIST as byte[9] = (0x07,0x04,0x00,0x36,0x00,0x00,0x00,0x63,0x5C) ' Reply To: Get Current Position In Playlist (0x00 0x1E) ' Reply As: Song 01 (0x00 0x0D) const RPOSINPLAYLIST as byte[9] = (0x07,0x04,0x00,0x1F,0x00,0x00,0x00,0x01,0xD5) ' Reply To: Get Time and Status Info (0x00 0x1C) ' Reply As: Track Length 00:05:00, Elapsed 00:00:23 ' Currently Stopped: const RTIMESTATUSSTOPPED as byte[14] = (0x0C,0x04,0x00,0x1D,0x00,0x04,0x93,0xE0, 0x00,0x00,0x59,0xD8,0x00,0x2B) ' Currently Playing: const RTIMESTATUSPLAYING as byte[14] = (0x0C,0x04,0x00,0x1D,0x00,0x04,0x93,0xE0, 0x00,0x00,0x59,0xD8,0x01,0x2A) ' Currently Paused: const RTIMESTATUSPAUSED as byte[14] = (0x0C,0x04,0x00,0x1D,0x00,0x04,0x93,0xE0, 0x00,0x00,0x59,0xD8,0x02,0x29) ' Reply To: None / Polling Mode Announcement (Normally sent every 500ms.) ' Reply As: Time Elapsed 23 seconds. const POLLINGANNOUNCEMENT as byte[10] = (0x08,0x04,0x00,0x27,0x04,0x00,0x00, 0x59,0xD8,0x98) const RTOUNKNOWN28 as byte[10] = (0x08,0x04,0x00,0x27,0x01,0x00,0x00,0x00,0x0D, 0xBF) '******************************************************************************* ' ' Global Variables ' '******************************************************************************* dim rxhead, rxtail, pollingcounter as byte dim rxbufempty, rxbuffull as boolean dim rxbuf as byte[RXBUFSIZE] dim TMR1 as word absolute $0E ' TMR1H and TMR1L start here. '******************************************************************************* ' ' Functions, Procedures, and Usage: ' ' sendcommand() ' Takes two arguments, the command to send (a constant containing the iPod ' command) and the destination UART. ' ' destuart = 0 sends to the Honda Music Link via EUSART. ' destuart = 1 sends to the iPod via software UART. ' ' eg: sendcommand(POWEROFF, 1) ' ' datawaiting() ' Returns true if there is data waiting in the RX FIFO, false if not. ' ' readfifo() ' If a byte is waiting in the RX FIFO, read it in. ' '******************************************************************************* ' sendcommand() sub procedure sendcommand(dim const arrayptr as ^byte, dim destuart as byte) dim address as longint dim bytestowrite, i as byte address = arrayptr ' Get the initial address of the array. bytestowrite = arrayptr^ ' How many bytes is the whole command? bytestowrite = bytestowrite + 1 ' We'll write through one more than that. if destuart = 0 then ' To HML / hardware USART. usart_write(0xFF) usart_write(0x55) for i = 0 to bytestowrite arrayptr = address + i usart_write(arrayptr^) next i end if if destuart = 1 then ' To iPod / software UART. soft_uart_write(0xFF) soft_uart_write(0x55) for i = 0 to bytestowrite arrayptr = address + i soft_uart_write(arrayptr^) next i end if end sub ' datawaiting() sub function datawaiting as boolean if rxbufempty then result = false else result = true end if end sub ' readfifo() sub function readfifo as byte dim j as byte j = 0 ' If the buffer is empty, read 0. if rxbufempty = false then ' As long as the buffer isn't empty... j = rxbuf[rxhead] ' Read in the contents of the head of the FIFO. rxbuf[rxhead] = 0 ' Set the previously read byte to 0. Destructive read. rxbuffull = false ' Since we've read data, the buffer can no longer be full. inc(rxhead) ' Move the head pointer to the next place. if rxhead = RXBUFSIZE then ' If the head is now greater than buffer size, rxhead = 0 ' move it back to place 0. end if if rxhead = RXtail then ' If the head and tail are in the same place, rxbufempty = true ' the buffer can be considered to be empty. end if end if result = j ' Return the contents of j, the byte of data. end sub ' interrupt() sub procedure interrupt dim rxstatus, k as byte if PIR1.RCIF = 1 then ' If it's the EUSART interrupt... rxstatus = RCSTA if rxstatus and %00000110 = 0 then ' Compare RCSTA and mask to check for errors. if rxbuffull = false then ' Only read if the FIFO isn't full. k = RCREG ' If there were no errors, read the data register. rxbuf[rxtail] = k ' Write the data to the tail. inc(rxtail) ' Move the tail pointer to the next place. if rxtail = RXBUFSIZE then ' If the tail is further along than the end of rxtail = 0 ' the buffer, move it back to place 0. end if if rxtail = rxhead then ' If the tail has been moved to the place where rxbuffull = true ' head is, then the FIFO is full. end if rxbufempty = false ' Indicate that the FIFO is not empty. end if else ' Whoops! Error... setbit(RCSTA,CREN) ' Set continuous receive enable. end if clearbit(PIR1,RCIF) ' Mark the USART receive buffer as not full. else if PIR1.TMR1IF = 1 then ' If it's the timer interrupt... inc(pollingcounter) ' Move the polling counter by one. TMR1 = -29115 ' Preload TMR1H and TMR1L PIR1.TMR1IF = 0 ' Clear TMR1IF end if end if end sub '******************************************************************************* ' ' mmmmmmmmmeat mmmmmmmmmakes things go. ' '******************************************************************************* main: dim datasize, aircommand, playingstatus, pollingmodeon, curtrack, arg1, i as byte ' FIFO Stuff memset(@rxbuf,0,RXBUFSIZE) ' Fill the buffer with zeros. rxhead = 0 ' Head is at byte 0. rxtail = 0 ' Tail is at byte 0. rxbufempty = true ' Buffer is empty. rxbuffull = false ' Buffer is not full. usart_init(9600) ' EUSART speed. setbit(RCSTA,CREN) ' Set continuous receive enable. ' iPod Response Status Stuff playingstatus = 0x02 ' iPod start as paused. curtrack = 0x01 ' On first track. pollingmodeon = false ' We want this to start at off. pollingcounter = 0 ' Start counting from zero, of course. ' Set up the timer and EUSART interrupts. TMR1 = -29115 ' Preload TMR1H and TMR1L T1CON = $21 ' 1:4 Prescaler, Turn Timer1 On PIE1 = $21 ' Enable EUSART Receive Interrupt and Timer1 Overflow Interrupt INTCON = $C0 ' Enable Global and Peripherial interrupts. ' Initialze the Software UART. soft_uart_init(PORTC, 3, 2, 9600, 0) while true ' Run forever. if pollingcounter >= 11 then if pollingmodeon = true then sendcommand(POLLINGANNOUNCEMENT, 0) end if pollingcounter = 0 end if while datawaiting ' Is there data waiting in the FIFO for us to work on? if readfifo() = 0xFF then ' Header 1 present? delay_ms(50) ' This happens fast enough so we need to wait a little. if readfifo() = 0x55 then ' Header 2 present? delay_ms(50) ' Wait again... datasize = readfifo() if readfifo() = 0x04 then ' We need to reply to Mode 4 stuff. readfifo() ' Next byte is always 0x00 in AiR aircommand = readfifo() ' Next byte will be the command itself. select case aircommand case 0x12 ' Get iPod type and size. sendcommand(RGETTYPESIZE, 0) case 0x18 ' Get count of type. arg1 = readfifo() select case arg1 case 0x01 ' Playlist sendcommand(RGETPLAYLISTCOUNT, 0) case 0x02 ' Artist break case 0x03 ' Album break case 0x04 ' Genre break case 0x05 ' Song sendcommand(RGETTOTALSONGCOUNT, 0) case 0x06 ' Composer break case else continue end select case 0x16 ' Switch to main library playlist. sendcommand(RSWITCHMAINPLAYLIST, 0) case 0x1A ' Get names for playlists. We assume two, starting at 0. sendcommand(RPLAYLIST0, 0) ' 0 = HMLiberator sendcommand(RPLAYLIST1, 0) ' 1 = Frees Your iPod readfifo() ' Throw out the next nine data bits, which are readfifo() ' before the checksum. readfifo() readfifo() readfifo() readfifo() readfifo() readfifo() readfifo() case 0x1C ' Get time and status info. select case playingstatus ' Response based on state. case 0x00 sendcommand(RTIMESTATUSSTOPPED, 0) case 0x01 sendcommand(RTIMESTATUSPLAYING, 0) case 0x02 sendcommand(RTIMESTATUSPAUSED, 0) case else continue end select case 0x1E ' Get current position in playlist. sendcommand(RPOSINPLAYLIST, 0) case 0x26 ' Set polling mode. sendcommand(RSETPOLLING, 0) arg1 = readfifo() select case arg1 case 0x00 ' Stop polling. pollingmodeon = false case 0x01 ' Start polling. pollingmodeon = true case else continue end select case 0x28 ' Execute playlist and jump to particular song. sendcommand(RTOUNKNOWN28, 0) readfifo() readfifo() readfifo() readfifo() case 0x29 ' AiR Command Mode arg1 = readfifo() select case arg1 case 0x01 ' Play/Pause if playingstatus = 0x01 then playingstatus = 0x02 else if playingstatus = 0x02 then playingstatus = 0x01 end if end if sendcommand(RAIRPLAYBACK, 0) sendcommand(PLAYBUTTON, 1) sendcommand(RELBUTTON, 1) case 0x02 ' Stop playingstatus = 0x00 sendcommand(RAIRPLAYBACK, 0) sendcommand(STOPPLAY, 1) sendcommand(RELBUTTON, 1) case 0x03 ' Skip++ sendcommand(RAIRPLAYBACK, 0) sendcommand(FFBUTTON, 1) sendcommand(RELBUTTON, 1) case 0x04 ' Skip-- sendcommand(RAIRPLAYBACK, 0) sendcommand(RWBUTTON, 1) sendcommand(RELBUTTON, 1) case 0x05 ' FFWD sendcommand(RAIRPLAYBACK, 0) case 0x06 ' FRWD sendcommand(RAIRPLAYBACK, 0) case 0x07 ' Stop FF/FRWD sendcommand(RAIRPLAYBACK, 0) case else continue end select case 0x2E ' Set Shuffle mode. sendcommand(RSETSHUFFLE, 0) case 0x31 ' Set Repeat mode. sendcommand(RSETREPEAT, 0) readfifo() ' Throw out the next byte; the repeat mode. case 0x35 ' Get number of songs in current playlist. sendcommand(RSONGSINPLAYLIST, 0) case else ' Didn't match any of the Mode 4 commands we know of. continue end select else ' We don't need to reply to anything else, so just dump it all. for i = 0 to datasize - 2 readfifo() next i end if readfifo() ' Toss the next byte, which is just the checksum. end if end if wend wend end.