; WINAMP HARDWARE INTERFACE ;=========================== ; ; Jethro Donaldson ; 2004 ; ;---[Declarations]--- ; processor 16F628 include "p16F628.inc" __config _BODEN_OFF & _CP_OFF & _DATA_CP_OFF & _PWRTE_ON & _WDT_OFF & _LVP_OFF & _MCLRE_ON & _HS_OSC ; cblock 0x20 DialInc ; Dial registers, not quite yet implemented by host software DialDec DelayL ; Delay Low Byte DelayH ; Delay High Byte DisplayL ; Display Low Byte (eg. 0x45 displays '45' on the display. HEX A thru F display blank) DisplayH ; Display High Byte DisplayM ; Display Misc Byte (bits 0 to 3 flag the display decimal points, bits 4 to 7 will eventually represent the bar graph) LoopCounter ; General purpose loop counter ShiftData ; Data to be sent to 74HC595 is loaded into this register prior to calling SHIFTOUT TempF ; Temporary file register, for general purpose use DevStatus ; Device Status Register 76543210 ; |||||||+- MDE (Mode Flag: 0=Alternate, 1=Normal) ; ||||||+-- WMP (Winamp Flag; 0=Closed, 1=Running) ; |||||+--- DSP (Display Enable: 0=Off, 1=On) ; ||||+---- TME (Time Flag: 0=Remove leading zeros, 1=Remove only one leading zero; for displaying times) ; |||+----- ; ||+------ ; |+------- ; +-------- endc ; cblock 0x7E W_SH ; W shadow register for ISR STATUS_SH ; STATUS shadow register for ISR endc ; Ver equ D'03' ; Firmware version = (Ver + 100) / 100 - 0x00 represents 1.00 RBfr equ 0x76 ; Serial buffer is processed when this location is reached ; SH_CP equ 2 ; 74HC595 SH_CP pin ST_CP equ 3 ; 74HC595 ST_CP pin DS equ 4 ; 74HC595 DS (data) pin ; MDE equ 0 ; DevStatus bits WMP equ 1 DSP equ 2 TME equ 3 ; ;---[Reset Vector]--- ; org 0x0000 goto INIT ; Jump over ISR from reset ; ;---[Interrupt Vector]--- ; org 0x0004 movwf W_SH ; Backup W and STATUS to shadow registers movf STATUS, W movwf STATUS_SH bcf STATUS, RP0 btfsc INTCON, T0IF ; Test for interrupt source: call INT_T0IF ; - Timer0 Overflow btfsc INTCON, INTF ; call INT_INTF ; - External Interrupt btfsc PIR1, RCIF ; call INT_RCIF ; - USART Recieve movf STATUS_SH, W ; Restore W and STATUS from shadow registers movwf STATUS movf W_SH, W retfie ; Return from interrupt ; ;---[Initialization]--- ; INIT: ; INIT - Initializes the system after reset or power-up clrf PORTA ; Clear all these file registers, just for the hell of it.... clrf PORTB clrf DialInc clrf DialDec clrf DisplayL clrf DisplayH clrf DisplayM clrf ShiftData clrf DevStatus movlw 0x70 ; Intialise FSR to the shared RAM at the end of each bank movwf FSR ; This is the start of the USART recieve buffer movlw 0x07 ; Disable analog function and enable digital I/O on PORTA movwf CMCON movlw 0x3C ; This stuff is for the PWM output into the LM3419 movwf CCP1CON ; Unless you have fixed the circuit, best ignore this movlw 0x0F movwf CCPR1L movlw 0x04 movwf T2CON bsf STATUS, RP0 ; Select bank 1 movlw 0xFF ; PWM module again, see above movwf PR2 movlw 0xC0 ; Intialize I/O directions: movwf TRISA ; - PORTA: IIOOOOOO movlw 0x53 ; movwf TRISB ; - PORTB: OIOIOOII movlw 0x08 ; Make sure Timer0 doesn't have the prescaler assigned to it movwf OPTION_REG btfss PORTB, 0 ; Set external interrupt edge to reflect the current state of the INT pin bsf OPTION_REG, INTEDG ; This more or less initialises the rotary encoder btfsc PORTB, 0 bcf OPTION_REG, INTEDG movlw 0x03 ; USART Baud Rate at 19200 movwf SPBRG ; Intialise USART for continuous recieve and automatic transmit movlw 0x20 movwf TXSTA bsf PIE1, RCIE ; Enable USART Recieve interrupt bcf STATUS, RP0 ; Select bank 0 movlw 0x90 ; More USART setup, see the 16F628 datasheet movwf RCSTA movlw 0xE0 ; Enable required interrupts movwf INTCON bsf DevStatus, MDE ; Force normal mode after reset, not strictly neccessary goto MAIN ; ;---[Main Routines]--- ; MAIN: btfss PORTB, 4 ; Poll PORTB, 4 for the mode switch call CHANGE_MODE ; Jump here if it was pressed goto MAIN ; Run this loop forever, everything else runs from interrupts ; ;---[ISR Routines]--- ; INT_T0IF: ; INT_T0IF - Maintains the multiplexed 7-Seg display btfss DevStatus, TME ; Check the TME bit in DevStatus to find out what kind of leading zero blanking is requried call LZ_NUMBER ; Call this to blank all leading zeros btfsc DevStatus, TME call LZ_TIMER ; Call this to blank only the first leading zero btfss DevStatus, DSP ; If display is not enabled then clear all the decimal points, as they aren't affected by the /BI on the 4511B clrf DisplayM ; DisplayM will be refreshed upon the next communication from the host software btfss DevStatus, DSP ; Set PORTB, 7 from the DSP bit of DevStatus bcf PORTB, 7 ; This line is connected to the blaning input of the 4511B, meaning it enables and disables the display btfsc DevStatus, DSP bsf PORTB, 7 btfss DevStatus, MDE ; Set the bottom LED from the MDE bit of DevStatus bcf PORTA, 0 ; This shows the user what mode the system is in (Normal/Alternate) btfsc DevStatus, MDE bsf PORTA, 0 btfss DevStatus, WMP ; Set the top LED from the WMP bit of DevStatus bcf PORTA, 1 ; This tells the use if Winamp is running or not btfsc DevStatus, WMP bsf PORTA, 1 btfss DisplayM, 0 ; All this stuff from here on handles the display multiplexing, through a single cycle bcf PORTB, 5 ; Insert a 1 second (or so) delay above this to see the multiplexing in action btfsc DisplayM, 0 bsf PORTB, 5 movf DisplayL, W andlw 0x0F addlw 0x80 movwf ShiftData call SHIFTOUT swapf DisplayL, F clrf ShiftData call SHIFTOUT btfss DisplayM, 1 bcf PORTB, 5 btfsc DisplayM, 1 bsf PORTB, 5 movf DisplayL, W andlw 0x0F addlw 0x40 movwf ShiftData call SHIFTOUT swapf DisplayL, F clrf ShiftData call SHIFTOUT btfss DisplayM, 2 bcf PORTB, 5 btfsc DisplayM, 2 bsf PORTB, 5 movf DisplayH, W andlw 0x0F addlw 0x20 movwf ShiftData call SHIFTOUT swapf DisplayH, F clrf ShiftData call SHIFTOUT btfss DisplayM, 3 bcf PORTB, 5 btfsc DisplayM, 3 bsf PORTB, 5 movf DisplayH, W andlw 0x0F addlw 0x10 movwf ShiftData call SHIFTOUT swapf DisplayH, F clrf ShiftData call SHIFTOUT bcf PORTB, 5 bcf INTCON, T0IF ; Clear the interrupt flag to stop the interrupt recurring return ; Return to main ISR ; INT_INTF: ; INT_INTF - Respondes to input from the rotary encoder bsf STATUS, RP0 btfsc OPTION_REG, INTEDG ; Check which transition has occured, branch accordingly goto INTF_RISE goto INTF_FALL INTF_RISE: ; Interrupt was rising edge, check state of other bit stream, branch accordingly bcf OPTION_REG, INTEDG bcf STATUS, RP0 btfss PORTB, 6 goto ENC_INC goto ENC_DEC INTF_FALL: ; Interrupt was falling edge, check state of other bit stream, branch accordingly bsf OPTION_REG, INTEDG bcf STATUS, RP0 btfsc PORTB, 6 goto ENC_INC goto ENC_DEC ENC_INC: ; Rotary encoder was turned clockwise: Increment DialInc incf DialInc, F bcf INTCON, INTF ; Clear interrupt flag and return return ENC_DEC: ; Rotary encoder was turned counter-clockwise: Increment DialDec incf DialDec, F bcf INTCON, INTF ; Clear interrupt flag and return return ; INT_RCIF: ; INT_RCIF - Recieves a character into the recieve buffer movf RCREG, W ; Read recieve FIFO into W movwf INDF ; Copy W to current buffer position, which is determined by FSR incf FSR, F ; Set next buffer position movf FSR, W ; Check whether FSR = RBfr (recieve buffer is "full") xorlw RBfr btfsc STATUS, Z call PROCESS_BUFFER ; If buffer is "full", call PROCESS_BUFFER bcf PIR1, RCIF ; Clear interrupt flag and return return ; ;---[Sub Routines]--- ; CHANGE_MODE: ; CHANGE_MODE - Change between Normal and Alternate button modes call DELAY ; Delay for debounce btfss PORTB, 4 ; Loop back until button is released. goto CHANGE_MODE ; Because the rest of the hardware interface is interrupt driven, holding this button down will not hang the hardware btfss DevStatus, MDE ; Check what mode is active, branch accordingly goto MODE_NRM goto MODE_ALT MODE_NRM: ; New mode is "Normal", set DevStatus and return bsf DevStatus, MDE return MODE_ALT: ; New mode is "Alternate", clear DevStatus and return bcf DevStatus, MDE return ; DELAY: ; This sub-routine creates a short delay (~10ms) by running the CPU in circles movlw 0xCF movwf DelayL movlw 0x08 movwf DelayH DELAY_LOOP: decfsz DelayL, F goto $ + 2 decfsz DelayH, F goto DELAY_LOOP return ; LZ_NUMBER: ; This sub-routine clears all leading zeros from DisplayH and DisplayL by setting leading zeros to Fh movf DisplayH, W andlw 0xF0 xorlw 0x00 btfss STATUS, Z return movlw 0xF0 addwf DisplayH, F movf DisplayH, W andlw 0x0F xorlw 0x00 btfss STATUS, Z return movlw 0x0F addwf DisplayH, F movf DisplayL, W andlw 0xF0 xorlw 0x00 btfss STATUS, Z return movlw 0xF0 addwf DisplayL, F return ; LZ_TIMER: ; This sub-routine clears a single leading zero, (for time displays) by setting it to Fh if necessary movf DisplayH, W andlw 0xF0 xorlw 0x00 btfss STATUS, Z return movlw 0xF0 addwf DisplayH, F return ; PROCESS_BUFFER: ; PROCESS_BUFFER - Processes and clears the recieve buffer movlw 0x70 ; Reset FSR to start of recieve buffer movwf FSR clrf TempF PROCESS_CHECKSUM: ; The last byte recieved into the recieve buffer should be a checksum. (sum of all bytes, complemented) movf INDF, W addwf TempF, F incf FSR, F movf FSR, W xorlw RBfr - 1 btfss STATUS, Z goto PROCESS_CHECKSUM ; This first loop adds all bytes in the recieve buffer except the last recieved comf TempF, W ; Complement sub of all bytes and check it against last byte recieved xorwf INDF, W btfss STATUS, Z goto PROCESS_COMPLETE ; If check sum is invalid, data packet is ignored by skipping to PROCESS_COMPLETE movf 0x70, W ; Process Buffer: First three bytes in buffer are moved unaltered to DisplayH, DisplayL and DisplayM movwf DisplayH movf 0x71, W movwf DisplayL movf 0x72, W movwf DisplayM comf 0x73, W ; The last two bytes an update of DevStatus, preceeded by a byte of flags for the update andwf DevStatus, F ; If a bit of the fourth byte is set, the corresponding bit of the fifth byte is forced onto DevStatus movf 0x73, W ; This is done by clearing all bits in DevStatus which correspond to a set bit in the fourth byte, and adding the fifth byte to DevStatus andwf 0x74, F movf 0x74, W addwf DevStatus, F movlw 0x70 ; Reset recieve buffer pointer movwf FSR PROCESS_COMPLETE: clrf INDF ; Clear recieve buffer incf FSR, F movf FSR, W xorlw RBfr btfss STATUS, Z goto PROCESS_COMPLETE movlw 0x70 ; Reset buffer pointer again movwf FSR movf DialInc, W ; Determine which way the dial has turned the most, this is done because the clockwise detection is a bit shoddy at present subwf DialDec, W btfss STATUS, C goto DIAL_INC goto DIAL_DEC DIAL_INC: ; A byte is transmitted back to the host software representing the number of detents the rotary encoder has turned (bits 0 to 6) movf DialInc, W ; Bit 7 of this byte represents the direction of the rotation. 1 = Clockwise, 0 = Counter-Clockwise movwf TempF bsf TempF, 7 goto TX_DEVSTATUS DIAL_DEC: movf DialDec, W movwf TempF goto TX_DEVSTATUS TX_DEVSTATUS: ; DevStatus is transmitted back to host software (it will be used mainly to determine the hardware mode) movf DevStatus, W btfss PIR1, TXIF ; TXIF is polled to ensure TXREG has been transferred to the TX shift register. This avoids lost data when sending more than two consecutive bytes.... goto TX_DEVSTATUS movwf TXREG TX_DIAL: movf TempF, W ; The rotary encoder byte is transmitted here btfss PIR1, TXIF goto TX_DIAL movwf TXREG TX_VER: movlw Ver ; Version number is transmitted back to host btfss PIR1, TXIF goto TX_VER movwf TXREG clrf DialInc clrf DialDec return ; SHIFTOUT: ; SHIFTOUT - Sends ShiftData to the 74HC595 movlw 0x08 movwf LoopCounter ; Move number of bits to be shifted out by standard SPI protocol SHIFTOUT_LOOP: rlf ShiftData, F ; Rotate ShiftData left, most significant bit goes into STATUS btfss STATUS, C ; The DS pin of the 74HC595 is set according to the bit in STATUS bcf PORTA, DS btfsc STATUS, C bsf PORTA, DS bsf PORTA, SH_CP ; SH_CP (Shift Register Clock) is pulsed, latching the bit into the 74HC595 nop bcf PORTA, SH_CP decfsz LoopCounter, F ; Decrement LoopCounter, if it's reached zero then the correct number of bits has been sent goto SHIFTOUT_LOOP ; If not, loop and do it again bsf PORTA, ST_CP ; Pulsing ST_CP latches the shift register into the storage register in the 74HC595. (this puts the data on the pins) nop bcf PORTA, ST_CP return ; ;---[Program End]--- ; end