; usbgadget.asm - USB firmware, LCD driver, etc... for PIC18F4550 ; Copyright (C) 2009 Rik Snel ; ; This program is free software; you can redistribute it and/or modify ; it under the terms of the GNU General Public License as published by ; the Free Software Foundation; either version 3 of the License, or ; (at your option) any later version. ; ; This program is distributed in the hope that it will be useful, ; but WITHOUT ANY WARRANTY; without even the implied warranty of ; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ; GNU General Public License for more details. ; ; You should have received a copy of the GNU General Public License ; along with this program; if not, write to the Free Software ; Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA ; values in CAPS, self defined pointers and labels not, radix dec processor pic18f4550 list n=0,st=off ; must be 1 for FULL speed and 0 for LOW speed #define USB_FULL_SPEED 0 #define DEBUG_USB 1 #include "p18f4550.inc" config PLLDIV=5, USBDIV=2, FOSC=HSPLL_HS if (USB_FULL_SPEED == 1) config CPUDIV=OSC1_PLL2 else config CPUDIV=OSC3_PLL4 endif config FCMEN=OFF, IESO=OFF, PWRT=ON, BOR=ON, BORV=1, VREGEN=ON config WDT=OFF, WDTPS=1, MCLRE=OFF, PBADEN=OFF, CCP2MX=ON config STVREN=ON, LVP=OFF, ICPRT=OFF, XINST=OFF, DEBUG=OFF ; code/data 'protection' off config CP0=OFF, CP1=OFF, CP2=OFF, CP3=OFF, CPB=OFF, CPD=OFF, WRT0=OFF config WRT1=OFF, WRT2=OFF, WRT3=OFF, WRTB=OFF, WRTC=OFF, WRTD=OFF config EBTR0=OFF, EBTR1=OFF, EBTR2=OFF, EBTR3=OFF, EBTRB=OFF ; note: FSR0 is used for display ram, functions unrelated to the display ; should not touch it (non priority interrupts must restore it after use) ; FSR1 points to USB buffers ; FSR2 points to USB buffer descriptors ; bank 0: some common stuff display_ram_line0 equ 0x00 display_ram_line1 equ 0x10 ; display_states (bit7 and 6 are unused): bit5: ; - 0 'write data from bit4:0' ; - 1 set LCD addr to display_state<<2 display_state equ 0x21 display_byte_tmp equ 0x22 wait_W40usec_inner equ 0x24 wait_W40usec_outer equ 0x25 usb_device_statusl equ 0x26 usb_device_statush equ 0x27 usb_address_tmp equ 0x29 usb_trn_counter equ 0x2B usb_ep0_state equ 0x30 usb_ep0_ptrl equ 0x31 usb_ep0_ptrh equ 0x32 usb_ep0_ptru equ 0x33 usb_ep0_cntl equ 0x34 usb_ep0_cnth equ 0x35 ; 'software interrupt support' #define SWINTMR2_DISPLAY 0 #define SWINTMR2_DIMMER 1 swint_tmr2 equ 0x36 ; error is an uncompleted CONTROL transfer based on VALID setup usb_ep0_error_counter equ 0x37 usb_ep0_success_counter equ 0x38 usb_tmp equ 0x39 usb_in_buffer_sizel equ 0x3A usart_buffer_len equ 0x3B ir_state equ 0x3C eusart_tmp equ 0x3D eusart_state equ 0x3E eusart_addr_len equ 0x3F eusart_buffer equ 0x40 ; CONTROL endpoint states #define USB_CESTATE_OUT_TO_RAM 0 #define USB_CESTATE_IN_FROM_RAM 1 #define USB_CESTATE_IN_FROM_PRGM 2 #define USB_CESTATE_STATUS_OUT 3 #define USB_CESTATE_STATUS_IN 4 ; BDxSTAT bits BC8 equ 0 BC9 equ 1 PID0 equ 2 BSTALL equ 2 PID1 equ 3 DTSEN equ 3 PID2 equ 4 INCDIS equ 4 PID3 equ 5 KEN equ 5 DTS equ 6 UOWN equ 7 ; bank 4; usb buffer descriptors BD0STAT equ 0x00 BD0CNT equ 0x01 BD0ADRL equ 0x02 BD0ADRH equ 0x03 BD1STAT equ 0x04 BD1CNT equ 0x05 BD1ADRL equ 0x06 BD1ADRH equ 0x07 ; bank 5; setup packet CDIR equ 7 ; CONTROL transfer direction bit in bmRequestType ; offsets in SETUP data payload REQUESTTYPE equ 0x00 REQUEST equ 0x01 VALUEL equ 0x02 VALUEH equ 0x03 INDEXL equ 0x04 INDEXH equ 0x05 LENGTHL equ 0x06 LENGTHH equ 0x07 ; some definitions to make USB code readable ; standard device requests #define USB_REQUEST_GET_STATUS 0x00 #define USB_REQUEST_CLEAR_FEATURE 0x01 #define USB_REQUEST_SET_FEATURE 0x03 #define USB_REQUEST_SET_ADDRESS 0x05 #define USB_REQUEST_GET_DESCRIPTOR 0x06 #define USB_REQUEST_SET_DESCRIPTOR 0x07 #define USB_REQUEST_GET_CONFIGURATION 0x08 #define USB_REQUEST_SET_CONFIGURATION 0x09 #define USB_REQUEST_GET_INTERFACE 0x0A #define USB_REQUEST_SET_INTERFACE 0x11 #define USB_REQUEST_SYNCH_FRAME 0x12 ; vendor requests INDEX must be offset, LENGTH is obvious #include "vdr_shared.h" ; descriptor ID's #define USB_DEVICE_DESCRIPTOR 0x01 #define USB_CONFIGURATION_DESCRIPTOR 0x02 #define USB_STRING_DESCRIPTOR 0x03 #define USB_INTERFACE_DESCRIPTOR 0x04 #define USB_ENDPOINT_DESCRIPTOR 0x05 load_prgm_addr macro addr movlw upper addr movwf TBLPTRU movlw high addr movwf TBLPTRH movlw low addr movwf TBLPTRL endm org 0x0000 reset_vector ; the chip starts executing our code here bsf RCON, IPEN ; enable interrupt priority bra continue_init org 0x0008 ihp_entry btfss INTCON, RBIF retfie FAST ; no known interrupt source btg LATE, 0 movf PORTB, w bcf INTCON, RBIF retfie FAST org 0x0018 ilp_entry ; most interrupts are not *that* timing critical, so they have ; low priority, we check them all in order ilp_tmr2_entry ; timer2 interrupt (software interrupt timer 2) btfss PIR1, TMR2IF bra ilp_eusart_entry clrf TMR2 ; calls are at least 40usec apart, may be more (40usec is ; only 160 instruction cycles (at 48MHz Fosc), and it gets ; reset at PR2) ilp_tmr2_display_entry btfss swint_tmr2, SWINTMR2_DISPLAY bra ilp_tmr2_exit ; this is a 'software interrupt'-retfie ilp_tmr2_display_state_10_and_11 btfss display_state, 5 bra ilp_tmr2_display_state_00_and_01 movlw 0x80 btfsc display_state, 4 movlw 0xC0 rcall display_send_command bcf display_state, 5 bra ilp_tmr2_display_exit ilp_tmr2_display_state_00_and_01 lfsr FSR0, 0x000 movff display_state, FSR0L movf POSTINC0, w call display_send_data movff FSR0L, display_state movlw 0x0F andwf display_state, w bnz ilp_tmr2_display_exit btfsc display_state, 5 bra ilp_tmr2_display_finished bsf display_state, 5 bra ilp_tmr2_display_exit ilp_tmr2_display_finished ; display updated, disable interrupt ; display_refresh will turn it on as needed bcf swint_tmr2, SWINTMR2_DISPLAY ilp_tmr2_display_exit ; software interrupt for display must only be ; disabled if update is complete ilp_tmr2_exit bcf PIR1, TMR2IF ilp_eusart_entry btfss PIE1, TXIE ; check if the interrupt is enabled bra ilp_usb_entry btfss PIR1, TXIF ; then check the flag bra ilp_usb_entry btg LATE, 1 ; calculate parity movff eusart_buffer, eusart_tmp swapf eusart_tmp, w xorwf eusart_tmp, f rrcf eusart_tmp, w xorwf eusart_tmp, f btfsc eusart_tmp, 2 incf eusart_tmp, f bsf TXSTA, TX9D btfss eusart_tmp, 0 bcf TXSTA, TX9D movff eusart_buffer, TXREG ; disable interrupt bcf PIE1, TXIE ilp_eusart_exit ; TXIF clears itself ilp_usb_entry btfss PIR2, USBIF bra ilp_exit ilp_usb_urst_entry btfss UIR, URSTIF bra ilp_usb_trn_entry ; reset our temp address (which updates UADDR at every STATUS_IN) clrf usb_address_tmp clrf usb_ep0_state bcf LATE, 2 ilp_usb_urst_exit bcf UIR, URSTIF ilp_usb_trn_entry btfss UIR, TRNIF bra ilp_usb_exit incf usb_trn_counter ; we have an IN, OUT or SETUP packet if (DEBUG_USB == 1) ; setup display and display STALL interrupt count lfsr FSR0, display_ram_line0 movf usb_trn_counter, w call display_byte movf usb_ep0_success_counter, w call display_byte movf usb_ep0_error_counter, w call display_byte call display_space endif ; USTAT points us directly to the correct buffer descriptor lfsr FSR2, 0x400 movlw 0x7C andwf USTAT, w ; take all relevant bits addwf FSR2L, f ; load FSR1 with the address of the USB buffer (1st byte, of course) LFSR FSR1, 0x500 ; ASSUME: buffer size is 0x40, there are at most 4 buffers ; FSR2 contains address of buffer descriptor swapf FSR2L, w movwf FSR1L ; queue empty IN buffer by default clrf usb_in_buffer_sizel ; pic18f4550's SIE supplies the packet PID in BDxSTAT (INDF2) ; in bits 5-2. The only possible values are ; - 0001 OUT ; - 1001 IN ; - 1101 SETUP ; notice: BSTALL=PID0 bit is always set, we assume this in the code ilp_usb_trn_setup btfss INDF2, PID2 bra ilp_usb_trn_out if (DEBUG_USB == 1) ; display type movlw 's' movwf POSTINC0 endif ; SETUP packets may not have the correct datax toggle, fix it bcf INDF2, DTS ; this is a setup packet, check if there was a transaction ; in progress, increment error counter if so, the SIE dequeues ; IN buffers in this case (OUT buffers can stay, of course) movf usb_ep0_state, w bz ilp_usb_trn_setup_in clrf usb_ep0_state incf usb_ep0_error_counter ; check the direction of the possible data packet ilp_usb_trn_setup_in btfss INDF1, CDIR bra ilp_usb_trn_setup_out if (DEBUG_USB == 1) ; display type movlw 'I' movwf POSTINC0 endif ; we only support Type=Vendor and Type=Standard (so bit 5 must be clear) btfsc INDF1, 5 bra ilp_usb_trn_queue_in_error ilp_usb_trn_setup_in_std btfsc INDF1, 6 bra ilp_usb_trn_setup_in_vdr if (DEBUG_USB == 1) ; display type movlw 'S' movwf POSTINC0 endif movlw 0x03 andwf INDF1, f ilp_usb_trn_setup_in_std_dev tstfsz INDF1 bra ilp_usb_trn_setup_in_std_intf if (DEBUG_USB == 1) ; display type movlw 'd' movwf POSTINC0 endif ilp_usb_trn_setup_in_std_dev_get_desc movlw USB_REQUEST_GET_DESCRIPTOR cpfseq PREINC1 bra ilp_usb_trn_setup_in_std_dev_get_status bsf usb_ep0_state, USB_CESTATE_IN_FROM_PRGM incf FSR1L ; read descriptor type first ilp_usb_trn_setup_in_std_dev_get_desc_dev movlw USB_DEVICE_DESCRIPTOR cpfseq PREINC1 bra ilp_usb_trn_setup_in_std_dev_get_desc_conf load_prgm_addr usb_device_descriptor bra ilp_usb_trn_setup_in_std_dev_get_desc_cont ilp_usb_trn_setup_in_std_dev_get_desc_conf movlw USB_CONFIGURATION_DESCRIPTOR cpfseq POSTDEC1 bra ilp_usb_trn_queue_in_error ; we only have config descriptor 0 movf INDF1, w bz ilp_usb_trn_setup_in_std_dev_get_desc_conf_cont bra ilp_usb_trn_queue_in_error ilp_usb_trn_setup_in_std_dev_get_desc_conf_cont load_prgm_addr usb_config_descriptor ilp_usb_trn_setup_in_std_dev_get_desc_cont rcall usb_load_cntlh_and_addrout bsf FSR1L, 6 ; go to output buffer bra ilp_usb_trn_in_from_prgm_loop ilp_usb_trn_setup_in_std_dev_get_status movlw USB_REQUEST_GET_STATUS cpfseq INDF1 bra ilp_usb_trn_queue_in_error rcall usb_load_cntlh_and_addrout bsf FSR1L, 6 ; go to output buffer movlw 0xFE cpfseq usb_ep0_cntl bra ilp_usb_trn_queue_in_error movlw 0xFF cpfseq usb_ep0_cnth bra ilp_usb_trn_queue_in_error bsf usb_ep0_state, USB_CESTATE_IN_FROM_RAM movff FSR2L, usb_tmp lfsr FSR2, usb_device_statusl bra ilp_usb_trn_in_from_ram_loop ilp_usb_trn_setup_in_std_intf decfsz INDF1 bra ilp_usb_trn_setup_in_std_endp if (DEBUG_USB == 1) ; display type movlw 'i' movwf POSTINC0 endif bra ilp_usb_trn_queue_in_error ilp_usb_trn_setup_in_std_endp decfsz INDF1 bra ilp_usb_trn_queue_in_error if (DEBUG_USB == 1) ; display type movlw 'e' movwf POSTINC0 endif bra ilp_usb_trn_queue_in_error ilp_usb_trn_setup_in_vdr if (DEBUG_USB == 1) ; display type movlw 'V' movwf POSTINC0 endif ilp_usb_trn_setup_in_vdr_read movlw USB_VDRREQ_READ_RAM cpfseq PREINC1 bra ilp_usb_trn_queue_in_error if (DEBUG_USB == 1) ; display type movlw 'R' movwf POSTINC0 endif movff FSR2L, usb_tmp bsf FSR1L, 2 movff POSTDEC1, FSR2H movff POSTDEC1, FSR2L rcall usb_load_cntlh_and_addrout bsf FSR1L, 6 ; go to output buffer bsf usb_ep0_state, USB_CESTATE_IN_FROM_RAM bra ilp_usb_trn_in_from_ram_loop ilp_usb_trn_setup_out if (DEBUG_USB == 1) ; display type movlw 'O' movwf POSTINC0 endif ; we only support Type=Vendor and Type=Standard (so bit 5 must be clear) btfsc INDF1, 5 bra ilp_usb_trn_queue_in_error ilp_usb_trn_setup_out_std btfsc INDF1, 6 bra ilp_usb_trn_setup_out_vdr if (DEBUG_USB == 1) ; display type movlw 'S' movwf POSTINC0 endif movlw 0x03 andwf INDF1, f ilp_usb_trn_setup_out_std_dev tstfsz INDF1 bra ilp_usb_trn_setup_out_std_intf if (DEBUG_USB == 1) ; display type movlw 'd' movwf POSTINC0 endif ilp_usb_trn_setup_out_std_dev_set_addr movlw USB_REQUEST_SET_ADDRESS cpfseq PREINC1 bra ilp_usb_trn_setup_out_std_dev_set_conf movff PREINC1, usb_address_tmp bsf usb_ep0_state, USB_CESTATE_STATUS_IN bra ilp_usb_trn_queue_in_success ilp_usb_trn_setup_out_std_dev_set_conf movlw USB_REQUEST_SET_CONFIGURATION cpfseq INDF1 bra ilp_usb_trn_queue_in_error ; argument must be 0 or 1 movlw 0xFE andwf PREINC1, w bz ilp_usb_trn_setup_out_std_dev_set_conf_success bra ilp_usb_trn_queue_in_error ilp_usb_trn_setup_out_std_dev_set_conf_success bsf LATE, 2 bsf usb_ep0_state, USB_CESTATE_STATUS_IN bra ilp_usb_trn_queue_in_success ilp_usb_trn_setup_out_std_intf decfsz INDF1 bra ilp_usb_trn_setup_out_std_endp if (DEBUG_USB == 1) ; display type movlw 'i' movwf POSTINC0 endif bra ilp_usb_trn_queue_in_error ilp_usb_trn_setup_out_std_endp decfsz INDF1 bra ilp_usb_trn_queue_in_error if (DEBUG_USB == 1) ; display type movlw 'e' movwf POSTINC0 endif bra ilp_usb_trn_queue_in_error ilp_usb_trn_setup_out_vdr if (DEBUG_USB == 1) ; display type movlw 'V' movwf POSTINC0 endif ilp_usb_trn_setup_out_vdr_write_ram movlw USB_VDRREQ_WRITE_RAM cpfseq PREINC1 bra ilp_usb_trn_setup_out_vdr_display_refresh if (DEBUG_USB == 1) ; display type movlw 'W' movwf POSTINC0 endif ; read offset bsf FSR1L, 2 movff POSTDEC1, usb_ep0_ptrh movff POSTDEC1, usb_ep0_ptrl rcall usb_load_cntlh_and_addrout bsf usb_ep0_state, USB_CESTATE_OUT_TO_RAM bra ilp_usb_trn_queue_out_success ilp_usb_trn_setup_out_vdr_display_refresh movlw USB_VDRREQ_DISPLAY_REFRESH cpfseq INDF1 bra ilp_usb_trn_setup_out_vdr_txie if (DEBUG_USB == 1) ; display type movlw 'D' movwf POSTINC0 endif rcall display_refresh bsf usb_ep0_state, USB_CESTATE_STATUS_IN bra ilp_usb_trn_queue_in_success ilp_usb_trn_setup_out_vdr_txie movlw USB_VDRREQ_TXIE cpfseq INDF1 bra ilp_usb_trn_queue_in_error if (DEBUG_USB == 1) ; display type movlw 'T' movwf POSTINC0 endif bsf PIE1, TXIE bsf usb_ep0_state, USB_CESTATE_STATUS_IN bra ilp_usb_trn_queue_in_success ilp_usb_trn_out btfsc INDF2, PID3 bra ilp_usb_trn_in if (DEBUG_USB == 1) ; display type movlw 'o' movwf POSTINC0 endif ilp_usb_trn_out_to_ram btfss usb_ep0_state, USB_CESTATE_OUT_TO_RAM bra ilp_usb_trn_out_status movff FSR2L, usb_tmp movff PREINC2, usb_in_buffer_sizel movff usb_ep0_ptrh, FSR2H movff usb_ep0_ptrl, FSR2L ilp_usb_trn_out_to_ram_loop movff POSTINC1, POSTINC2 incf usb_ep0_cntl bnz ilp_usb_trn_out_to_ram_loop_cont incf usb_ep0_cnth bnz ilp_usb_trn_out_to_ram_loop_cont bra ilp_usb_trn_out_to_ram_loop_finish ilp_usb_trn_out_to_ram_loop_cont decfsz usb_in_buffer_sizel bra ilp_usb_trn_out_to_ram_loop ; need more data movff FSR2H, usb_ep0_ptrh movff FSR2L, usb_ep0_ptrl lfsr FSR2, 0x400 movff usb_tmp, FSR2L bra ilp_usb_trn_queue_out_success ilp_usb_trn_out_to_ram_loop_finish ; restore FSR2 lfsr FSR2, 0x400 movff usb_tmp, FSR2L ; we expect no more data, check if host wants to send too much decfsz usb_in_buffer_sizel bra ilp_usb_trn_queue_in_error ; naughty host ; otherwise we are happy clrf usb_ep0_state bsf usb_ep0_state, USB_CESTATE_STATUS_IN bra ilp_usb_trn_queue_in_success ilp_usb_trn_out_status btfss usb_ep0_state, USB_CESTATE_STATUS_OUT bra ilp_usb_trn_queue_out_error ilp_usb_trn_status clrf usb_ep0_state incf usb_ep0_success_counter bra ilp_usb_trn_queue_out_success ilp_usb_trn_in if (DEBUG_USB == 1) ; display type movlw 'i' movwf POSTINC0 endif ilp_usb_trn_in_from_ram btfss usb_ep0_state, USB_CESTATE_IN_FROM_RAM bra ilp_usb_trn_in_from_prgm ; must we send more bytes? (16bit check) tstfsz usb_ep0_cntl bra ilp_usb_trn_in_from_ram_loop_init tstfsz usb_ep0_cnth bra ilp_usb_trn_in_from_ram_loop_init clrf usb_ep0_state bsf usb_ep0_state, USB_CESTATE_STATUS_OUT bra ilp_usb_trn_exit ; don't do anything, we're done sending data ; the STATUS OUT buffer is already queued ilp_usb_trn_in_from_ram_loop_init movff FSR2L, usb_tmp movff usb_ep0_ptrh, FSR2H movff usb_ep0_ptrl, FSR2L ilp_usb_trn_in_from_ram_loop movff POSTINC2, POSTINC1 incf usb_ep0_cntl bnz ilp_usb_trn_in_from_ram_loop_cont incf usb_ep0_cnth bnz ilp_usb_trn_in_from_ram_loop_cont bra ilp_usb_trn_in_from_ram_loop_finish ilp_usb_trn_in_from_ram_loop_cont btfss FSR1L, 3 bra ilp_usb_trn_in_from_ram_loop ilp_usb_trn_in_from_ram_loop_finish movlw 0x3F andwf FSR1L, w movwf usb_in_buffer_sizel movff FSR2H, usb_ep0_ptrh movff FSR2L, usb_ep0_ptrl lfsr FSR2, 0x400 movff usb_tmp, FSR2L bra ilp_usb_trn_queue_in_success ilp_usb_trn_in_from_prgm btfss usb_ep0_state, USB_CESTATE_IN_FROM_PRGM bra ilp_usb_trn_in_status ; must we send more bytes? (16bit check) tstfsz usb_ep0_cntl bra ilp_usb_trn_in_from_prgm_loop_init tstfsz usb_ep0_cnth bra ilp_usb_trn_in_from_prgm_loop_init clrf usb_ep0_state bsf usb_ep0_state, USB_CESTATE_STATUS_OUT bra ilp_usb_trn_exit ; don't do anything, we're done sending data ; the STATUS OUT buffer is already queued ilp_usb_trn_in_from_prgm_loop_init movff usb_ep0_ptru, TBLPTRU movff usb_ep0_ptrh, TBLPTRH movff usb_ep0_ptrl, TBLPTRL ilp_usb_trn_in_from_prgm_loop tblrd*+ movff TABLAT, POSTINC1 incf usb_ep0_cntl bnz ilp_usb_trn_in_from_prgm_loop_cont incf usb_ep0_cnth bnz ilp_usb_trn_in_from_prgm_loop_cont bra ilp_usb_trn_in_from_prgm_loop_finish ilp_usb_trn_in_from_prgm_loop_cont btfss FSR1L, 3 bra ilp_usb_trn_in_from_prgm_loop ilp_usb_trn_in_from_prgm_loop_finish movlw 0x3F andwf FSR1L, w movwf usb_in_buffer_sizel movff TBLPTRU, usb_ep0_ptru movff TBLPTRH, usb_ep0_ptrh movff TBLPTRL, usb_ep0_ptrl bra ilp_usb_trn_queue_in_success ilp_usb_trn_in_status btfss usb_ep0_state, USB_CESTATE_STATUS_IN bra ilp_usb_trn_queue_in_error ; do this every time, it doesn't hurt movff usb_address_tmp, UADDR bra ilp_usb_trn_status ilp_usb_trn_queue_in_success btg INDF2, BSTALL ilp_usb_trn_queue_in_error movlw 1<