Note: I’d been meaning to post about this for a while, and since the project made the MAKE blog, I figure it is high time…
After having two aftermarket stereo systems stolen from my car in less than a year’s time, I decided to give up and go back to the factory radio. The problem? It only has tape and radio inputs, and I prefer to have access to my entire MP3 collection. The traditional way to fix this is to either get an FM broadcaster or a tape adapter, but where is the fun in that? Instead, I elected to wire in an external input, and built a little circuit to let me switch between the two. Here is a photo set to document the process:
Source code after the break.
This code is written for a PIC 12F629 as the controller, largely because I had a bunch of them sitting around. If you are starting from scratch, I would suggest skipping this bit and spending the whole $4 on a part that has an open source C compiler (*cough* AVR *cough*).
; Input multiplexer for car stereo system ; ; By Matt Mets, completed in 2008 ; ; This code is released into the public domain. Attribution is appreciated. ; Build configuration PROCESSOR 12F629 include p12f629.inc ; Global Register Definitions STATE equ 0x20 ; System state: ; 0=radio/tape, 1=external input ; (other bits are don't care) SLEEP_CNT equ 0x30 ; For use in sleep routine SLEEP_CNT_L equ 0x31 BTN_CNT equ 0x32 ; Countdown for button press BTN_STATE equ 0x33 ; Read the GPIO BTN_DOWN equ 0x34 ; Last known button state ; EEPROM Definitions STATE_ADDR equ 0x00 ; Last known system status ; Set up the I/O ports to be in the right state bsf STATUS,RP0 ; Bank 1 movlw 0x39 ; Set GP<2:1> as output movwf TRISIO ; and set GP<5:3,0> as input bcf STATUS,RP0 ; Bank 0 ; Load the last-known state from the EEPROM call LOAD_ST ; Main loop START call CHECK_BTN ; If button pressed, do some debouncing btfss BTN_DOWN,0 goto START ; Otherwise, repeat movlw 0xff ; Must have x counts of button down movfw BTN_CNT ; before toggling BTN_LOOP call DO_SLEEP_L ; sleep for a bit call CHECK_BTN ; If button released, return to loop btfss BTN_DOWN,0 goto START movlw 0x01 ; Subtract 1 from the count subwf BTN_CNT,1 btfss STATUS,Z ; If non-zero, loop again goto BTN_LOOP call TOGGLE_ST ; Otherwise, toggle BTN_LOOPE call DO_SLEEP ; Don't return until the button is call CHECK_BTN btfsc BTN_DOWN,0 goto BTN_LOOPE goto START ; See if we can tell when the button has been pressed. Because it is being ; driven by a matrix controller, it is 'down' when both sides are at the same ; potential. CHECK_BTN clrf BTN_DOWN ; Clear the button down register movf GPIO,0 ; Capture the input state movwf BTN_STATE btfss BTN_STATE,3 ; Determine state of buttons goto CHECK_LOW CHECK_HIGH btfss BTN_STATE,4 ; Check if both buttons are high return goto SET_BTN CHECK_LOW btfsc BTN_STATE,4 ; Check if both buttons are low return SET_BTN comf BTN_DOWN,1 ; Set the button down register return ; Toggle the system state (then apply it) TOGGLE_ST movlw 0x01 ; Flip the state variable xorwf STATE,1 call WRITE_ST ; Then store it in EEPROM ; Apply the system state to the hardware APPLY_ST btfss STATE,0 ; If the low bit is 0, set radio/tape goto APPLY_ST1 APPLY_ST0 movlw 0x02 ; Select radio/tape input movwf GPIO return APPLY_ST1 movlw 0x04 ; Select aux input movwf GPIO return ; Read the last value of the State register from the EEPROM LOAD_ST bsf STATUS,RP0 ; Bank 1 movlw STATE_ADDR ; Program the address to read movwf EEADR bsf EECON1,RD ; Read the data from EEPROM movf EEDATA,0 ; and load it to the state regiser movwf STATE bcf STATUS,RP0 ; Bank 0 call APPLY_ST ; And apply the state return ; Write the current value of the State register to the EEPROM WRITE_ST bsf STATUS,RP0 ; Bank 1 movf STATE,0 ; Load the state register value movwf EEDATA movlw STATE_ADDR ; And its address movwf EEADR WRITE_SEQ bsf EECON1,WREN ; Enable write movlw 0x55 ; Special dance to unlock writes movwf EECON2 ; (this feature prevents runaway code movlw 0xAA ; from trashing the EEPROM) movwf EECON2 bsf EECON1,WR ; Start the write bcf EECON1,WREN ; Disable any future writes bcf STATUS,RP0 ; Bank 0 return ; Sleep for some time DO_SLEEP movlw 0xff ; Count down from 255 movwf SLEEP_CNT DO_SLEEPI decfsz SLEEP_CNT,1 ; Decrement loop goto DO_SLEEPI return ; Sleep for even longer DO_SLEEP_L movlw 0x6 ; Count down from 255 movwf SLEEP_CNT_L DO_SLEEP_LI call DO_SLEEP decfsz SLEEP_CNT_L,1 ; Decrement loop goto DO_SLEEP_LI return END