; USB 512-Word CDC Bootloader for PIC16(L)F1454/5/9
; Copyright (c) 2015, Matt Sarnoff (msarnoff.org)
; Heavily modified 2017 by Henrik Haftmann (henrik.haftmann@gmail.com)
; for better CDC compatibility and extra commands for twiddeling with I/O
; Verified application use, added flash read function (with 'R' flag),
; and configuration space R/W access, 2018
; To enter the bootloader, !MCLR (= RA3) must be pulsed low.
; This allows for cheap parallel port adapters for flashing this bootloader.
; If there is no application programmed, the bootloader is active at powerup.
; Other reset sources, as Power-On, Brown-Out, Watchdog, and RESET instruction
; will lead to execute application code.
; The application code never sees !MCLR as reset source in PCON register,
; and must maintain PCON.3 set when other reset sources are used.
; (Other reset sources are RESET instruction, Watchdog, Stack Overflow, etc.)
; To be detected as a valid application, the lower 8 bits of the first
; instruction word (at address 0x200) must NOT be 0xFF.
; At application start, the device is configured with a 48 MHz CPU clock,
; using the internal oscillator and 3x PLL.
; Code notes:
; - Labels that do not begin with an underscore can be called as functions.
; Labels that begin with an underscore are not safe to call,
; they should only be reached via goto.
;
; - FSR[01][HL] are used as temporary registers in several places,
; e.g. as loop counters. They're accessible regardless of the current bank
; and automatically saved/restored on interrupt.
;
; - As much stuff as possible is packed into bank 0 of RAM.
; This includes the buffer descriptors, bootloader state,
; endpoint buffers 0 OUT, 0 IN, and the beginning of the 64-byte 1 OUT buffer.
;
; - Notification endpoint 2 is enabled but never used (= returns NAK to host)
; The endpoint 2 IN buffer descriptor is left uninitialized.
; The endpoint 2 OUT buffer descriptor is used as 4 bytes of Bank0 RAM.
;
; - The programming protocol is described in the 'usb16f1prog' script.
; It is very minimal, but does provide checksum verification.
; Writing the ID words (0x8000-8003) is now supported.
; Writing the configuration words is not possible via self-programming.
radix dec
list n=0
include "p16f1454.inc"
include "mymacros.i15"
; Configuration
; !MCLR/RA3 pin must be configured as !MCLR.
; The 0x200 words of bootloader itself is write-protected by _WRT_BOOT fuse bits
;TODO: The bootloader should support low-power USB idle.
__config _CONFIG1,_FOSC_INTOSC&_WDTE_SWDTEN&_PWRTE_ON
__config _CONFIG2,_WRT_BOOT&_CPUDIV_NOCLKDIV
; Constants used by the bootloader communications protocol.
; Command is determined by the length of the data packet received.
; If length == 1, Reset Device. The one byte sent must be 'R'. Returns 1 or INVALID_COMMAND
; If length == 2, read from address. So user can read from PORT etc. Returns byte read.
; If length == 3, write to address. So user can set PORT pins etc. Returns 1.
; If length == 4, Prepare program/readout: Set Address/Checksum/Erase Flag:
; {word address; byte checksum; char eraseflag;} // eraseflag=='E': erase this row
; // eraseflag=='R': read this row
; If length == 64, transfer data for writing one flash page.
; Otherwise, it's a wrong command.
; Status codes returned from device to host
BSTAT_OK equ 1 ; all ok
BSTAT_INVALID_COMMAND equ 2 ; host sent an invalid command
BSTAT_INVALID_CHECKSUM equ 3 ; invalid checksum, no write performed
BSTAT_VERIFY_FAILED equ 4 ; data written to flash doesn't match data sent by host
CONFIG_DESC_LEN equ 67 ; total length of configuration descriptor and sub-descriptors
E0_SSH equ 3 ; endpoint 0 buffer size: 1<<3 = 8
E1O_SSH equ 6 ; endpoint 1 OUT (CDC data) buffer size 1<<6 = 64
E1I_SSH equ 6 ; endpoint 1 IN (CDC data) buffer size
;Exy_STA bits
BSTALL equ 2
DTSEN equ 3
DTS equ 6
UOWN equ 7
;list of Bank0 RAM variables, starting with BDT
cblock 0x20 ;Bank 0 (completely full)
E0O_STA
E0O_CNT
E0O_ADR:2
E0I_STA
E0I_CNT
E0I_ADR:2
E1O_STA
E1O_CNT
E1O_ADR:2
E1I_STA
E1I_CNT
E1I_ADR:2
; Since we're only using 5 endpoints, use the BDT area for buffers,
; and use the 4 bytes normally occupied by the EP2 OUT buffer descriptor for variables.
USB_STATE ; some flags, see below
SETUPDAT_PTR ; pointer to descriptor to be sent (low byte only, high byte is 0x81 for all descriptors)
SETUPDAT_CNT ; remaining bytes to be sent
CHECKSUM ; for saving expected checksum
E2I_STA
E2I_CNT
E2I_ADR:2
E0_BUF:1<<E0_SSH ; shared buffer for EP0 Setup, Out, and In transfer
E1O_BUF:1<<E1O_SSH ; At address 0x40, last 16 bytes hang over into Bank 1
endc
cblock 0xB0 ; Bank 1 (completely full)
E1I_BUF:1<<E1I_SSH ; 64 byte for EP1 IN
endc
bmRequestType equ E0_BUF+0
bRequest equ E0_BUF+1
wValueL equ E0_BUF+2
wValueH equ E0_BUF+3
wLengthL equ E0_BUF+6
wLengthH equ E0_BUF+7
LIN_E0_BUF equ E0_BUF+0x1FE0 ;0x2018 .. 0x201F
LIN_E1O_BUF equ E1O_BUF+0x1FE0 ;0x2020 .. 0x205F
LIN_E1I_BUF equ E1I_BUF+0x1FB0 ;0x2060 .. 0x209F
; USB_STATE bit flags
IS_CONTROL_READ equ 7 ; current EP0 transaction is a control read
READFLASH equ 6 ; return 64 byte, not 1
PENDING_RESET equ 5 ; do RESET opcode when idle
POLL_FROM_APP equ 4 ; usbPoll is called from outside: call usbRx @ 0x202
CDC_DCD equ 1 ; Data Carrier Detect bit (for CDC)
CONFIGURED equ 0 ; the device is configured
goto onReset ; to be continued further down in the file
goto usbInit
bra usbPoll ; Make callable from application code
bra usbTx ; Any data chunk must end with a short packet
pagesel 0x204
goto 0x204 ; to application interrupt routine
usbPoll
banksel USB_STATE
bsf USB_STATE,POLL_FROM_APP
usbPoll1
banksel UIR ; Bank 29
btfsc UIR,URSTIF ; reset?
goto usbReset ; if so, reset the USB interface
; service transactions
btfss UIR,TRNIF
return
movfw USTAT ; store the status (endpoint number + direction)
movwf FSR1H ; in that temp register
bcf UIR,TRNIF ; clear flag and advance USTAT fifo
banksel E0O_STA ; Bank 0
andlw 0x78 ; endpoint number !=0 ?
brnz usb_service_cdc ; if not endpoint 0, it's a CDC message
; Handles a control transfer on endpoint 0.
; args: BSR=0, FSR1H=USTAT
; clobbers: W, FSR1H
btfsc FSR1H,DIR ; is it an IN transfer or an OUT/SETUP?
bra _usb_ctrl_in
; it's an OUT or SETUP transfer
movfw E0O_STA ; All non-PID bits are zero here because SETUP comes with DATA0
xorlw 13<<2 ; 13=PID_SETUP — is it a SETUP packet?
brnz arm_e0o ; if not, it's a DATA or STATUS OUT, ignore data and rearm buffer
; Handles a SETUP control transfer on endpoint 0.
; BSR=0
clrf E0O_STA ; let start with DATA1
clrf E0I_STA ; let start with DATA1
clrf SETUPDAT_CNT ; No data phase as standard
bcf USB_STATE,IS_CONTROL_READ
; get bmRequestType, but don't bother checking whether it's standard/class/vendor...
; the CDC and standard requests we'll receive have distinct bRequest numbers
btfsc bmRequestType,7 ; is this device->host?
bsf USB_STATE,IS_CONTROL_READ ; if so, this is a control read
movfw bRequest
; is it Set Address?
addlw -5 ;5 = SET_ADDRESS
brz _setup_complete
; check request number: is it Get Descriptor?
decfsz WREG,w ;6 = GET_DESCRIPTOR
bra _no_usb_get_descriptor
; Handles a Get Descriptor request.
; check descriptor type
decfsz wValueH,w ;1 = DESC_DEVICE
bra _no_desc_device
movlwf low DEVICE_DESCRIPTOR,SETUPDAT_PTR
movlw 18
bra _setup_complete_w
_no_desc_device
assume 0
decfsz WREG,w ;2 = DESC_CONFIG
bra _usb_ctrl_invalid
movlwf low CONFIGURATION_DESCRIPTOR,SETUPDAT_PTR
movlw CONFIG_DESC_LEN ; length includes all subordinate descriptors
bra _setup_complete_w
_no_usb_get_descriptor
; is it Get Configuration?
decf WREG,w
decfsz WREG,w ;8 = GET_CONFIG
bra _no_usb_get_configuration
; Handles a Get Configuration request.
; BSR=0
; load a pointer to either a 0 or a 1 in ROM
; the 0 and 1 have been chosen so that they are adjacent
movfw USB_STATE
andlw 1<<CONFIGURED ; bit 0
addlw low CONFIGURATION_01_CONSTANT
movwf SETUPDAT_PTR
movlw 1
bra _setup_complete_w
_no_usb_get_configuration
decfsz WREG,w ;9 = SET_CONFIG
bra _no_usb_set_config
; Handles a Set Configuration request.
; BSR=0
assume 0
movfw wValueL ; should be 0 or 1
movwf USB_STATE
; Initializes the buffers for the CDC endpoints (1 OUT, 1 IN, and 2 IN).
; clobbers: W, BSR=0
movlw 1<<DTSEN ; expect DATA0 first
call arm_e1o_w
movwf E1I_STA ; USB NAK and toggle bit set: Send first packet as DATA0
bra _setup_complete
_no_usb_set_config
addlw 9-0x20 ;0x20 = SET_LINE_CODING
brnz _no_set_line_coding
banksel UCON
bcf UCON,PKTDIS
banksel 0
bcf E0I_STA,UOWN
clrf E0I_CNT ; we'll be sending a zero-length packet
movlw 0 ; ignore odd/even
bra _armbfs
_no_set_line_coding
decfsz WREG,w ;0x21 = GET_LINE_CODING
bra _no_get_line_coding
movlwf low LINE_CODING,SETUPDAT_PTR
movlw 7
bra _setup_complete_w
_no_get_line_coding
decfsz WREG,w
bra _usb_ctrl_invalid ;0x22 = SET_CONTROL_LINE_STATE
bsf USB_STATE,CDC_DCD
; unhandled request? fall through to _usb_ctrl_invalid
movlw 7
_setup_complete_w
movwf SETUPDAT_CNT
; If requested length is shorter, transfer less than descriptor size
tstf wLengthH
brnz _setup_complete ; more than 255 bytes requested (Windows 7: 0x109)
subwf wLengthL,w
brc _setup_complete ; if W <= f, no need to adjust
movfw wLengthL
movwf SETUPDAT_CNT
; Finishes a successful SETUP transaction.
; SETUPDAT_CNT contains transfer length.
_setup_complete
banksel UCON
bcf UCON,PKTDIS ; reenable packet processing
banksel USB_STATE
btfss bmRequestType,7
bra _cwrite
; this is a control read; prepare the IN endpoint for the data stage
; and the OUT endpoint for the status stage
call ep0_send_in ; read data into IN buffer
movlw 1<<DTS|1<<DTSEN ; make OUT buffer ready for status stage
; value in W is used to specify the EP0 OUT flags
_armbfs call arm_e0o_w
; Send next packet to Endpoint 0 In, size already set to E0I_CNT
arm_e0i
comf E0I_STA,w ; toggle DTS
andlw 1<<DTS
iorlw 1<<DTSEN
arm_e0i_w ; W specifies STAT flags
movwf E0I_STA
bsf E0I_STA,UOWN
return
; this is a control write: prepare the IN endpoint for the status stage
; and the OUT endpoint for the next SETUP transaction
_cwrite bcf E0I_STA,UOWN ; ensure we have ownership of the buffer
clrf E0I_CNT ; we'll be sending a zero-length packet
movlw 1<<DTSEN|1<<BSTALL ; make OUT buffer ready for next SETUP packet
bra _armbfs ; arm OUT and IN buffers
; Finishes a rejected SETUP transaction: the endpoints are stalled
_usb_ctrl_invalid
banksel UCON
bcf UCON,PKTDIS ; reenable packet processing
banksel E0I_STA
movlw 1<<DTSEN|1<<BSTALL
call arm_e0i_w
arm_ep0_out_stall ;for next SETUP
movlw 1<<DTSEN|1<<BSTALL
bra arm_e0o_w
; Prepare reception of next Endpoint 0 Out Data (or Setup) packet
arm_e0o
comf E0O_STA,w ; toggle DTS
andlw 1<<DTS
iorlw 1<<DTSEN
arm_e0o_w ; W specifies STAT flags
;assume E0_BUF
movwf E0O_STA
movlwf 1<<E0_SSH,E0O_CNT ; reset the buffer count
bsf E0O_STA,UOWN ; arm the OUT endpoint
return
; Handles an IN control transfer on endpoint 0.
; Can be (descriptor) data or an acknowledge packet
; BSR=0
_usb_ctrl_in
btfss USB_STATE,IS_CONTROL_READ ; is this a control read or write?
bra _check_for_pending_address
; fetch more data and re-arm the IN endpoint
call ep0_send_in
bra arm_e0i ; arm the IN buffer
; if this is the status stage of a Set Address request, assign the address here.
; The OUT buffer has already been armed for the next SETUP.
_check_for_pending_address
movfw bRequest
xorlw 5
skpz
return
; read the address out of the setup packed in the OUT buffer
movfw wValueL
banksel UADDR
movwf UADDR
return
; Reads descriptor data from SETUPDAT_PTR, copies it to the EP0 IN buffer,
; and decrements SETUPDAT_CNT.
; Cannot simply point EP0_ADDR to flash as it is not accessible by USB SIE
; args: BSR=0
; returns: SETUPDAT_PTR advanced
; SETUPDAT_CNT decremented
; clobbers: W, FSR0, FSR1
ep0_send_in
clrf E0I_CNT ; initialize transfer length to 0
tstf SETUPDAT_CNT ; do nothing if there are 0 bytes to send
skpnz
return
movfw SETUPDAT_PTR ; set up source pointer
movwf FSR0L
movlwf high DEVICE_DESCRIPTOR,FSR0H ;high part always 1, so 0x81 here
movlwf E0_BUF,FSR1L ; Here: Banked address (not a problem), set up destination pointer
clrf FSR1H
; byte copy loop
_bcopy btfsc E0I_CNT,E0_SSH ; 8 Bytes full?
bra _bcdone
moviw FSR0++
movwi FSR1++
incf E0I_CNT,f ; increase number of bytes copied
decfsz SETUPDAT_CNT,f ; decrement number of bytes remaining
bra _bcopy
; write back the updated source pointer
_bcdone movfw FSR0L
movwf SETUPDAT_PTR
return
;Send data chunk to host. Note that a short packet ends a transfer unit (= chunk for ReadFile).
;In case the last packet is full, a zero-length packet must be appended to mark the end of chunk.
;args: W=Bytes (0..64)
; EP1IN buffer filled with data
;clobbers: W,bank=0
usbTx banksel 0
_u1 btfsc E1I_STA,UOWN ; spinloop until previous buffer is read by USB host
bra _u1 ; (Bug: May hang if USB connection is somehow lost in between)
movwf E1I_CNT ; put transfer size
comf E1I_STA,w ; toggle DTS
andlw 1<<DTS ; clear all other bits (STALL too)
iorlw 1<<DTSEN ; 0↕001000 = use toggle bit
movwf E1I_STA
bsf E1I_STA,UOWN ; give to SIE
return
; Services a transaction on one of the CDC endpoints.
; args: USTAT value in FSR1H
; BSR=0
; clobbers: W, FSR0, FSR1
usb_service_cdc
btfsc FSR1H,ENDP1 ; ignore endpoint 2
return
btfss FSR1H,DIR ; if endpoint 1 IN, host had read the status; check for reset
bra _no_in
btfsc USB_STATE,PENDING_RESET
btfsc E1I_STA,UOWN ; Don't reset before any response is really read by host
return
reset ; reset controller and jump to application
_no_in
movfw E1O_CNT ; sets Z!
btfss USB_STATE,POLL_FROM_APP
bra _no_app
call 0x202 ; call application code for handling E1O_BUF = RxBuf data
; The application code should then:
; * inspect W = length and E1O_BUF = RxBuf
; * act as commanded by W and buffer content
; * optionally: put data into E1I_BUF, call usbTx with W=length, as often as needed
; * return
pagesel 0 ; restore PCLATH
bra _arm_e1o
_no_app
brz _arm_e1o ; (ignore and rearm OUT buffer)
bcf USB_STATE,READFLASH
call bootloader_exec_cmd ; execute command; status returned in W
btfss USB_STATE,READFLASH
bra _one ; no
movwf FSR1H ; 32 = loop count (PMADR is already set up)
;movlw high LIN_E1I_BUF;=32
movwf FSR0H
movlwf low LIN_E1I_BUF,FSR0L ;=0x60
banksel PMCON1
_rloop bsf PMCON1,RD ; Capture one flash word
nop
nop
movfw PMDATL ; read LoByte
movwi FSR0++ ; save it
movfw PMDATH ; read HiByte
movwi FSR0++ ; save it
incf PMADRL,f ; don't straddle lo-hi boundaries
decfsz FSR1H
bra _rloop
movlw 64
call usbTx
clrw
bra _full ; send zero-length packet to terminate transfer
_one
banksel E1I_BUF ; Bank 1
movwf E1I_BUF ; put status into IN buffer
movlw 1 ; output byte count is 1
_full call usbTx
_arm_e1o
banksel E1O_STA ; bank 0
comf E1O_STA,w ; data toggle
andlw 1<<DTS
iorlw 1<<DTSEN ; 0↕001000, ↕ = toggle
arm_e1o_w
movwf E1O_STA
movlwf 1<<E1O_SSH,E1O_CNT ; length = full size
bsf E1O_STA,UOWN ; rearm OUT buffer
retlw 1<<DTS|1<<DTSEN ; 01001000
; Executes a bootloader command.
; args: command payload in EP1 OUT buffer
; BSR=0
; returns: status code in W
; clobbers: BSR,FSR0,FSR1,E1O_CNT
bootloader_exec_cmd
movfw E1O_BUF+1 ; address upper bits
movwf FSR1H
movfw E1O_BUF+0 ; address lower bits (in W and FSR1L)
movwf FSR1L
; check length of data packet
decfsz E1O_CNT,f
bra _no_reset
; len==1: Resets the device if the received byte matches the reset character.
xorlw 'R' ; check received character
skpz
retlw BSTAT_INVALID_COMMAND
; command is valid, process IN request first, then reset this microcontroller
bsf USB_STATE,PENDING_RESET ;enqueue to do a reset instruction
retlw BSTAT_OK
_no_reset
decfsz E1O_CNT,f
bra _no_read_port
; len==2: Read byte
movfw INDF1
return
_no_read_port
decfsz E1O_CNT,f
bra _no_write_port
; len==3: Write byte
movfw E1O_BUF+2
movwf INDF1
retlw BSTAT_OK
_no_write_port
decfsz E1O_CNT,f
bra _no_set_params
; len==4: Set write/read address, expected checksum of the next 32 words,
; and erases the row at that address if the last byte of the command matches "E"
banksel PMCON1 ;bank 3
clrf PMCON1
movfw FSR1H ;copy address to PMADR
movwf PMADRH
btfsc FSR1H,7
bsf PMCON1,CFGS ;select configuration space for given word address >= 32K
movfw FSR1L
movwf PMADRL
banksel E1O_BUF ;bank 0
andlw 0x1F ;lower 5 bits must be 0 for programming and readout
skpz
retlw BSTAT_INVALID_COMMAND
movfw E1O_BUF+2 ;expected checksum
movwf CHECKSUM ;save for verification during write command
movfw E1O_BUF+3 ;command flag
xorlw 'R' ;read?
brnz _no_r
bsf USB_STATE,READFLASH
retlw 32
_no_r
; do we need to erase?
xorlw 'R'^'E'
skpz
retlw BSTAT_OK ; if no reset command is given, return OK
; Erases the row of flash in PMADRH:PMADRL.
banksel PMCON1 ;bank 3
bsf PMCON1,FREE
bsf PMCON1,WREN ; enable write and erase to program memory
call flash_unlock ; stalls until erase finishes
;bcf PMCON1,WREN ; clear write enable flag
retlw BSTAT_OK
_no_set_params
movlw 64-4
xorwf E1O_CNT,f
skpz
retlw BSTAT_INVALID_COMMAND
; Verifies that the checksum of the 32 words (64 bytes) in the EP1 OUT buffer
; matches the previously sent value. If so, the 32 bytes are then written to
; flash memory at the address in PMADRH:PMADRL. (set by a prior command)
; The expected checksum is the two's complement of the sum of the bytes.
; If the data is valid, we can add the checksum to the sum of the bytes and
; the result will be 0. We initialize a temporary register with the expected
; checksum, and then add each byte to it as it's processed.
; If the value in the temp register is 0 after all 64 bytes have been copied
; to the write latches, proceed with the write.
movlwf low LIN_E1O_BUF,FSR0L ; set up read pointer
;movlw high LIN_E1O_BUF 0x2020: High == Low
movwf FSR0H
movfwf CHECKSUM,FSR1L ; keep checksum for next try and make it addressable
banksel PMCON1
bsf PMCON1,LWLO ; write to latches only
bsf PMCON1,WREN
; simultaneously compute the checksum of the 32 words and copy them to the
; write latches
movlwf 32,FSR1H ; number of words to write, use for loop count
_wloop moviw FSR0++ ; load lower byte
addwf FSR1L ; add lower byte to checksum
movwf PMDATL ; copy to write latch
moviw FSR0++ ; load upper byte
addwf FSR1L ; add upper byte to checksum
movwf PMDATH ; copy to write latch
; after writing the last word to PMDATH:PMDATL,
; don't execute the unlock sequence or advance the address pointer!
decf FSR1H ; decrement loop count
brz _wcksum ; if 0, we're done writing to the latches
; still have more words to go
call flash_unlock ; execute unlock sequence
incf PMADRL,f ; increment write address
bra _wloop
; verify the checksum
_wcksum bcf PMCON1,LWLO ; flash entire page while writing last word
tstf FSR1L
skpz
retlw BSTAT_INVALID_CHECKSUM ; if there's a mismatch, abort the write
; checksum is valid, write the data
;bsf PMCON1,WREN ;still set
call flash_unlock ; stalls until write finishes
; verify the write: compare each byte in the buffer to its counterpart that
; was just written to flash.
; we do this backwards so we don't waste instructions resetting the pointers.
; (note: PMADRH:PMADRL is already pointing at the last written word, but FSR0
; is pointing to one byte past the end of the buffer)
;bcf PMCON1,WREN ; clear write enable
bsf FSR1H,5 ; set loop count to 32 (just need to set one bit because it's already 0)
_vloop bsf PMCON1,RD ; read word from flash
nop ; 2 required nops
nop
moviw --FSR0 ; get high byte of expected word
subwf PMDATH,w ; compare with high byte written to flash
skpz
retlw BSTAT_VERIFY_FAILED
moviw --FSR0 ; get low byte of expected word
subwf PMDATL,w ; compare with low byte written to flash
skpz
retlw BSTAT_VERIFY_FAILED
decf PMADRL,f ; decrement read address
decfsz FSR1H,f ; decrement loop count
bra _vloop
retlw BSTAT_OK
; Executes the flash unlock sequence, performing an erase or write.
; args: PMCON1 bits CFGS, LWLO, FREE and WREN set appropriately
assume PMCON2 ;BSR=3
; clobbers: W
flash_unlock
movlwf 0x55,PMCON2
movlwf 0xAA,PMCON2
bsf PMCON1,WR
nop
nop
ret return
onReset
; Check for valid application code: the lower 8 bits of the first word cannot be 0xFF
clrf FSR0L
movlwf 0x82,FSR0H ; set high bit to read program memory 0x200
banksel PCON ; Bank 1
comf INDF0,w ; ",f" should not modify flash too
brz _stay_in_boot ; if we have no application, enter bootloader mode
; We have a valid application. Check if reset reason was reset pin
;It looks like a silicon bug: !RMCLR is cleared on powerup! In opposite to data sheet.
;This would lead to always staying in boot loader, never entering the application.
;As a workaround, if !POR is cleared, set !RMCLR.
;And !POR too so that next Reset invocation doesn't see POR again.
;Sorry for inconvenience for the application program, it doesn't see POR anymore
;Furthermore, powerup with keeping !MCLR low won't work, and a misbehaving application
;(clearing !POR bit) will defeat entering bootloader by !MCLR pin.
;That can easily happen if TMR1L, BORCON, PMCON2, SSP1CON2, IOCBF, PWM2CON, UADDR are written
;with wrong bank selection.
btfsc PCON,NOT_POR
bra skipfix ; !POR was (already) set
bsf PCON,NOT_POR
bsf PCON,NOT_RMCLR
skipfix btfss PCON,NOT_RMCLR ; If !MCLR was low, stay in boot looder
bra _stay_in_boot
; Start the application with BSR=0
banksel 0
pagesel 0x200
goto 0x200
; Not entering application code: initialize the USB interface and wait for commands.
_stay_in_boot
bsf PCON,NOT_RMCLR ; clear !MCLR reset reason for next onReset
; bcf OPTION_REG,NOT_WPUEN ; Enable weak pull-ups
call usbInit
; Idle loop. In bootloader mode, the MCU just spins here. No interrupts.
_loop call usbPoll1
bra _loop
usbInit
; Configure the oscillator (48MHz from INTOSC using 3x PLL)
banksel OSCCON ; bank 1
movlwf 0xFC,OSCCON
; Wait for the oscillator and PLL to stabilize
_wosc comf OSCSTAT,w
andlw (1<<PLLRDY)|(1<<HFIOFR)|(1<<HFIOFS)
brnz _wosc
; Enable active clock tuning
banksel ACTCON ; Bank 7
movlwf (1<<ACTSRC)|(1<<ACTEN),ACTCON ; source = USB
; Initialize USB
call usbReset
; Attach to the bus (could be a subroutine, but inlining it saves 2 instructions)
banksel UCON ; Bank 29
clrf UCON ; reset UCON
bsf UCON,USBEN ; enable USB module and wait until ready
return
; Initializes the USB system and resets all associated registers.
; clobbers: W,BSR=0,FSR0,FSR1L
usbReset
; clear USB registers
banksel UIR ; Bank 29
clrf UIR
; set configuration
movlwf (1<<UPUEN)|(1<<FSEN),UCFG ; enable pullups, full speed, no ping-pong buffering
;movlwf (1<<TRNIE)|(1<<URSTIE),UIE ; TODO: Need interrupts for CPU wakeup later
; clear all BDT entries, variables, and buffers
clrf FSR0H
movlwf 0x20,FSR0L ; BDT starts at 0x2000 aka 0x20, clear all Bank0 variables except RxBuf
movwf FSR1L ; use 0x20 as loop count
clrw
_ramclr movwi FSR0++
decfsz FSR1L,f
bra _ramclr
; reset ping-pong buffers and address
banksel UCON ; Bank 29: All about USB
bsf UCON,PPBRST
clrf UADDR
bcf UCON,PKTDIS ; enable packet processing
bcf UCON,PPBRST ; clear ping-pong buffer reset flag
; flush pending transactions
_tflush btfss UIR,TRNIF
bra _initep
bcf UIR,TRNIF
call ret ; need at least 6 cycles before checking TRNIF again
bra _tflush
; initialize endpoints:
; 0 for control
; 1 for CDC bulk data
; 2 for CDC notifications (dummy IN endpoint, returns NAK, neither STALL nor silence)
; my intuition was that I should wait until a SET_CONFIGURATION is received
; before setting up endpoints 1 and 2... but there seemed to be a timing issue
; when doing so, so I moved them here
_initep movlwf 1<<EPHSHK|1<<EPOUTEN|1<<EPINEN,UEP0
movlwf 1<<EPHSHK|1<<EPCONDIS|1<<EPOUTEN|1<<EPINEN,UEP1
movlwf 1<<EPHSHK|1<<EPCONDIS|1<<EPINEN,UEP2
; initialize endpoint buffers and counts
banksel E0O_ADR
movlwf low LIN_E0_BUF,E0O_ADR ; set endpoint 0 address low
movwf E0I_ADR
movlwf low LIN_E1O_BUF,E1O_ADR ; set endpoint 1 OUT address low
movlwf low LIN_E1I_BUF,E1I_ADR ; set endpoint 1 IN address low
movlwf high LIN_E0_BUF,E0O_ADR+1 ; set all ADRH values to 0x20
movwf E0I_ADR+1
movwf E1O_ADR+1
movwf E1I_ADR+1
goto arm_ep0_out_stall
; Descriptors
dtw macro x
dt low x,high x
endm
DEVICE_DESCRIPTOR
dt 18 ; bLength
dt 1 ; bDescriptorType
dtw 0x0200 ; bcdUSB (USB 2.0)
dt 2 ; bDeviceClass (communication device)
dt 0 ; bDeviceSubclass
dt 0 ; bDeviceProtocol
dt 1<<E0_SSH ; bMaxPacketSize0 (8 bytes)
dtw 0x04D8 ; idVendor
dtw 0xEFDA ; idProduct
dtw 1 ; bcdDevice (1)
dt 0 ; iManufacturer
dt 0 ; iProduct
dt 0 ; iSerialNumber
dt 1 ; bNumConfigurations
CONFIGURATION_DESCRIPTOR
dt 9 ; bLength
dt 2 ; bDescriptorType
dtw CONFIG_DESC_LEN ; wTotalLength
dt 2 ; bNumInterfaces
dt 1 ; bConfigurationValue
dt 0 ; iConfiguration
dt 0x80 ; bmAttributes
dt 50 ; bMaxPower
;INTERFACE_DESCRIPTOR_0
dt 9 ; bLength
dt 4 ; bDescriptorType (INTERFACE)
dt 0 ; bInterfaceNumber
CONFIGURATION_01_CONSTANT ; Two adjanced addresses containing [0,1]
dt 0 ; bAlternateSetting
dt 1 ; bNumEndpoints
dt 2 ; bInterfaceClass (communication)
dt 2 ; bInterfaceSubclass (abstract control model)
dt 1 ; bInterfaceProtocol (V.25ter, common AT commands)
dt 0 ; iInterface
;HEADER_FUNCTIONAL_DESCRIPTOR
dt 5 ; bFunctionLength
dt 0x24 ; bDescriptorType (CS_INTERFACE)
dt 0 ; bDescriptorSubtype (header functional descriptor)
dtw 0x0110 ; bcdCDC (specification version, 1.1)
;ABSTRACT_CONTROL_MANAGEMENT_FUNCTIONAL_DESCRIPTOR
dt 4 ; bFunctionLength
dt 0x24 ; bDescriptorType (CS_INTERFACE)
dt 2 ; bDescriptorSubtype (abstract control management functional descriptor)
dt 0x02 ; bmCapabilities
;UNION_FUNCTIONAL_DESCRIPTOR
dt 5 ; bFunctionLength
dt 0x24 ; bDescriptorType (CS_INTERFACE)
dt 6 ; bDescriptorSubtype (union functional descriptor)
dt 0 ; bMasterInterface
dt 1 ; bSlaveInterface0
;CALL_MANAGEMENT_FUNCTIONAL_DESCRIPTOR
dt 5 ; bFunctionLength
dt 0x24 ; bDescriptorType (CS_INTERFACE)
dt 1 ; bDescriptorSubtype (call management functional descriptor)
dt 3 ; bmCapabilities
dt 1 ; dDataInterface
;ENDPOINT_DESCRIPTOR_2_IN
dt 7 ; bLength
dt 5 ; bDescriptorType (ENDPOINT)
dt 0x82 ; bEndpointAddress (2 IN)
dt 0x03 ; bmAttributes (transfer type: interrupt)
dtw 8 ; wMaxPacketSize (8)
dt 2 ; bInterval
;INTERFACE_DESCRIPTOR_1
dt 9 ; bLength
dt 4 ; bDescriptorType (INTERFACE)
dt 1 ; bInterfaceNumber
dt 0 ; bAlternateSetting
dt 2 ; bNumEndpoints
dt 0x0A ; bInterfaceClass (data)
dt 0 ; bInterfaceSubclass
dt 0 ; bInterfaceProtocol
dt 0 ; iInterface
;ENDPOINT_DESCRIPTOR_1_OUT
dt 7 ; bLength
dt 5 ; bDescriptorType (ENDPOINT)
dt 0x01 ; bEndpointAddress (1 OUT)
dt 0x02 ; bmAttributes (transfer type: bulk)
dtw 1<<E1O_SSH ; wMaxPacketSize (64)
dt 0 ; bInterval
;ENDPOINT_DESCRIPTOR_1_IN
dt 7 ; bLength
dt 5 ; bDescriptorType (ENDPOINT)
dt 0x81 ; bEndpointAddress (1 IN)
dt 0x02 ; bmAttributes (transfer type: bulk)
dtw 1<<E1I_SSH ; wMaxPacketSize (64)
dt 0 ; bInterval
serialStateNotification2
dt 0xA1,0x20,0,0,0,0,2,0
serialStateNotification
dt 3,0
LINE_CODING
dtw 9600
dt 0,0,0,0,8
if $>0x200
error "Too much code"
endif
end
Detected encoding: UTF-8 | 0
|