SX Microcontroller based Keyboard IO

California Dreamin SX USB Keyboard Demo

by Michael Hetherington

Used in the EXCELLENT Atapchi: World's Smallest Low-speed USB Analyzer

;California dreamin' (aka SX USB Virtual Peripheral) Version 1.00
;Copyright (C) 2001 Michael Hetherington
;chinook@pacific.net.sg
;
;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.
;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.


;SX28AC enumerates as generic USB keyboard and types the lyrics of "California dreamin'"
;on every press of led toggling key (Caps Lock, Scroll Lock and Num Lock). 
;Works perfectly with Windows 2000 PC and any text editor.
;It has not been tested on Macs or Unix platform yet. 
;Power can be driven from USB port, however independent 5V source is recommended. 
;The following design does not meet all USB requirements
;and provides just an example of simple interface to USB using Scenix MCU and widely
;available components: 
;bus driver 74LCX125
;Resonator - 50 MHz (Murata)
;Environment - SX Key
		
		DEVICE	SX28L,oscxtmax,turbo,stackx_optionx
   		RESET   Start


rx_lo		equ	1	; D+ USB line 
rx_hi		equ	2	; D- USB line pulled by 1.5K to 3.3V
comp		equ	3	; comparator output
tx_lo		equ	4	; drive D+
tx_hi		equ	5	; drive D-
oe		equ	6	; line driver output enable (inverted)



usb_port	equ	rb
usb_port_tris	equ	%10001110

rx_lo_pin	equ	rb.1	; respective IC pins
rx_hi_pin	equ	rb.2
comp_pin	equ	rb.3
tx_lo_pin 	equ	rb.4
tx_hi_pin	equ	rb.5 
oe_pin		equ	rb.6

;**********************************************************************************
; Buffer break-down
; Starting address $03
; We have 8 buffers to store data
;**********************************************************************************

address_lo	equ	$30

sync_bit	equ	7	; to keep synchronized (10001000100....)
ep1_in		equ	6	; to keep respective token packets
ep0_in		equ	5	; to give ACK, NACK or STALL
ep0_out		equ	4
setup		equ	3
all_data	equ	2	; all incoming data
ep1_data	equ	1	; buffer of 14 bytes to keep IN data 
ep0_data	equ	0	; buffer of 14 bytes to keep IN data and OUT data

;**********************************************************************************
; Token packets buffers are underutilised
; Use upper address for handshake packets
; Starting address $09
;**********************************************************************************

address_hi	equ	$90

ack		equ	6
nack		equ	5
stall		equ	4

;**********************************************************************************
;  For USB interface execution of interrupt will be done
;  every 20-25 cycles to catch the start of a packet.
;  Once the packet arrived 1,5 MHz acquisition rate should be maintained.
;  With 50MHz resonator it means interrupt executes every 33 cycles 3 times and 
;  every 34 cycles 1 time, this cycle will be called leap cycle (after leap year)
;********************************************************************************** 

int_idle	equ	25
int_active	equ	33
int_leap	equ	34


usb_states	equ	$08		;main body state machine
int_states	equ	$09		;interrupt state machine

buffer_count	equ	$0A		;bit transmit/receive counter in buffers		
temp		equ	$0B		;register to hold function specific values
					;holds current value of rb

usb_mask	equ	$0C		;masking different buffers on reset and address
					;change

packet_rx	equ	$0D		;packet mask while Rx
packet_tx	equ	$0D		;packet mask while Tx
reset_timer	equ	$0D		;monitor the time out for the bus
					;to detect reset conditions for Rx

tx_timer	equ	$0E		;the number of bits to send
					;it is loaded with the value of
					;usb_ep0_counter or usb_ep1_counter

usb_flags	equ	$0F		;general flags

flg_setup	equ	usb_flags.0	;setup packet was received
flg_out		equ	usb_flags.1	;out packet was received
flg_ep0_send	equ	usb_flags.2	;data is ready for transmission from ep0
flg_ep0_in	equ	usb_flags.3	;in packet for ep0 is received
flg_ep1_send	equ	usb_flags.4	;data is ready for transmission from ep1
flg_ep1_in	equ	usb_flags.5	;in packet for ep1 is received
flg_ep0_data	equ	usb_flags.6	;data 0/1 toggle for ep0
flg_ep1_data	equ	usb_flags.7	;data 0/1 toggle for ep1	



		org	$10
USB_BANK	=	$

usb_hold	equ	$10		;the same memory cell
usb_ep0_byte	equ	$11		;keeps temporary variables
usb_sub_states 	equ	$12		;substates of main state machine
usb_ep0_table_s equ	$13		;ep0 tx table start	
usb_ep0_table_e equ	$14		;ep0 tx table end
usb_ep1_table_s equ	$15		;ep1 tx table start
usb_ep1_table_e equ	$16		;ep1 tx table end

usb_buffer	equ	$17
usb_address	equ	$18
usb_temp	equ	$19
usb_bit_stuff	equ	$1A
usb_bit_count	equ	$1B

usb_ep0_counter	equ	$1C		;the number of bits to send on EP0 IN packet 
usb_ep1_counter	equ	$1D		;the number of bits to send on EP1 IN packet

usb_crc5	equ	$1F
usb_crc16_hi	equ	$1F
usb_crc16_lo	equ	$1E

		org	0
Interrupt

		mov	temp,rb	
		mov	w,int_states
		add	PC,w

IDLE_loc		=	$	
;***********************************************************************************
; IDLE STATE
; D- stays high
; D+ stays low
;***********************************************************************************
		sb	temp.rx_hi		;catch the start of a packet:	
		jmp	IDLE_x			;when D- falls
		clr	reset_timer		;if D- stays high clear timer
		mov	w,#-int_idle	
		retiw
IDLE_x			
		inc	reset_timer		;monitor the line for reset condition
		snz				
		jmp	BUS_RESET_loc
	        sb	usb_port.comp		;if the output of comp falls
						;which means D+ went up, the start of the  
		jmp	BUS_SYNC_loc		;packet is detected
		mov	w,#-int_idle		;get ready to receive		
		retiw

BUS_RESET_loc				
;***********************************************************************************
; BUS RESET STATE
; Single ended 0 is detected for the period of 8uS 
;***********************************************************************************
Bus_reset	
		bank	USB_BANK
		clr	usb_address		;naturally, address is 0 now
		mov	usb_states,#(USB_RESET_loc-USB_RESET_loc)
		mov	int_states,#(IDLE_loc - IDLE_loc)
		clr	reset_timer		;leave interrupt in idle state
		mov	w,#-int_idle		;update the usb buffer contents
		retiw				;in the main body

BUS_SYNC_loc	=	$
;***********************************************************************************
; BUS SYNCHRONIZATION STATE
; We have to synchronize SX clock with coming data based on sync pattern
; D- lo hi lo hi lo hi lo lo
; D+ hi lo hi lo hi lo hi hi
; This state is over when we catch the start of D- lo
;***********************************************************************************
		mov	packet_rx,#$01		;
		mov	int_states,#(RECEIVE_SYNC_loc-IDLE_loc)
		mov	fsr,#address_lo
		clrb	ind.all_data		;seed with 0 all data buffer
		clrb	ind.ep0_data		;seed with 0 endpoint data buffer
		mov	fsr,#address_hi

:loop1	
		snb	comp_pin		;while output of comp is low, do not care
		jmp	:loop2			;once it is high, start searching for low
		jmp	:loop1
:loop2	
		sb	comp_pin		;catch low of comp_pin
		jmp	BUS_SYNC_x		
		jmp	:loop2		

BUS_SYNC_x
		mov	RTCC,#($FF-$28) 	;IMPORTANT!		
		reti				;to keep synchronized the value
						;in RTCC should be aroud:
						;$FF - #int_active - (3 to 10) cycles
RECEIVE_SYNC_loc	=	$
;***********************************************************************************
; RECEIVE SYCHRONIZATION STATE
; OK the clock is synchronized but we are in the middle of sync pattern
; to get the first data bit lets look for two consecutive low's of D-
;***********************************************************************************
		inc	packet_rx		;once we have two low's
		snb	temp.comp		;it is the last bit of SYNC
		clr	packet_rx
 		mov	w,#(RECEIVE_loc-IDLE_loc)
		snb	flg_setup		;if the prevoius packet was setup
						;receive data
		mov	w,#(RECEIVE_SETUP_loc - IDLE_loc)
		snb	packet_rx.1
		mov	int_states,w
		snb	packet_rx.1
		clr	packet_rx
		mov	buffer_count,#$30	;always start with the firts byte
						;in the buffer
 		mov	w,#-int_active
		retiw	
RECEIVE_loc		=	$
;***********************************************************************************
;  RECEIVE PACKET STATE 
;  The most time hungry state
;  Receive and store all packets, check what packet arrived
;***********************************************************************************
		mov	fsr,buffer_count	;load fsr with the buffer address
		snb	temp.comp
		jmp	:high
:low	
		mov	w,#%00000100		;record received packet
		xor	ind,w			

		snb	ind.all_data		;is one of token packets
		mov	w,#$FF
		xor	w,ind
		or	packet_rx,w

		inc	fsr			;save the state of rb	
		setb	fsr.4			;for the future
		clrb	ind.all_data
		mov	buffer_count,fsr

		mov	w,#-int_active
		snb	ind.sync_bit
		mov	w,#-int_leap
		retiw		

:high
		sb	temp.rx_hi
		jmp	EOP_loc

		clr	w
		snb	ind.all_data
		mov	w,#$FF
		xor	w,ind
		or	packet_rx,w

		inc	fsr			;save the state of rb
		setb	fsr.4
		setb	ind.all_data
		mov	buffer_count,fsr
	
		mov	w,#-int_active
		snb	ind.sync_bit
		mov	w,#-int_leap
		retiw	
RECEIVE_SETUP_loc		=	$
;***********************************************************************************
;  RECEIVE SETUP STATE 
;  Setup packet data, actually there is no need to check what type of packet arrived
;  but we are going to do it anyway just in case one more setup packet arrives
;  EP0 IN buffer is used for OUT as well
;***********************************************************************************
		mov	fsr,buffer_count	;load fsr with the buffer address
		snb	temp.comp
		jmp	:high
:low	
		mov	w,#%00000001		;record received packet
		xor	ind,w			

		snb	ind.ep0_data		;is one of token packets
		mov	w,#$FF
		xor	w,ind
		or	packet_rx,w

		inc	fsr			;save the state of rb	
		setb	fsr.4			;for the future
		clrb	ind.ep0_data
		mov	buffer_count,fsr

		mov	w,#-int_active
		snb	ind.sync_bit
		mov	w,#-int_leap
		retiw		

:high
		sb	temp.rx_hi
		jmp	EOP_loc

		clr	w
		snb	ind.ep0_data
		mov	w,#$FF
		xor	w,ind
		or	packet_rx,w

		inc	fsr			;save the state of rb
		setb	fsr.4
		setb	ind.ep0_data
		mov	buffer_count,fsr
	
		mov	w,#-int_active
		snb	ind.sync_bit
		mov	w,#-int_leap
		retiw	

EOP_loc		=	$
;***********************************************************************************
; END OF PACKET STATE
;***********************************************************************************
		
		;check packets first

		bank	USB_BANK

		sb	packet_rx.setup
		jmp	:setup			   ;setup packet arrived
		
		sb	packet_rx.ep0_out
		jmp	:Endpoint0_out		   ;endpoint 0 out packet arrived

		sb	packet_rx.ep1_in  
   		jmp	:Endpoint1_in

		sb	packet_rx.ep0_in
		jmp	:Endpoint0_in
							
		;no packets identified, check previous packet

		snb	flg_setup
		jmp	:ack_setup		  ; ACK setup

		snb	flg_out	
		jmp	:ack_out		  ; ACK out

		snb	flg_ep0_in
		jmp	:release_ep0		

		snb	flg_ep1_in
		jmp	:release_ep1

				
:no_reply
		mov	w,#(IDLE_loc-IDLE_loc) 	    ;If we can not identify the packet
		mov	int_states,w		    ;keep cool, do not reply
		mov	rtcc,#($FF-$0A)		    ;return to interrupt as fast 
		reti				    ;as possible
:setup
		setb	flg_setup
		mov	w,#(IDLE_loc-IDLE_loc) 	    
		mov	int_states,w		    
		mov	rtcc,#($FF-$0F)		    ;return to interrupt as fast 
		reti				    ;as possible
:Endpoint0_out
		setb	flg_out
		mov	w,#(IDLE_loc-IDLE_loc)
		mov	int_states,w
		mov	rtcc,#($FF-$0A)		    ;return to interrupt as fast 
		reti				    ;as possible
:Endpoint1_in
		sb	flg_ep1_send		    ;anything to send?
		jmp	:nack			    ;if not NACK
		setb	flg_ep1_in		    ;set IN packet flag on
		mov	w,#(TRANSMIT_loc-IDLE_loc)
		mov	int_states,w
		mov	packet_tx,#%11111101	    ;set transmit mask
		clrb	usb_port.oe		    ;enable output
		mov	buffer_count,#$30
		mov	tx_timer,usb_ep1_counter
		mov	rtcc,#($FF-$21)		    ;wait about 1 bit before transmit
		reti			    

:Endpoint0_in
		sb	flg_ep0_send		    ;anything to send?
		jmp	:nack			    ;if not, NACK
		setb	flg_ep0_in		    ;set IN packet flag on
		mov	w,#(TRANSMIT_loc-IDLE_loc)
		mov	int_states,w
		mov	packet_tx,#%11111110	    ;set transmit mask
		clrb	usb_port.oe		    ;enable output
		mov	buffer_count,#$30
		mov	tx_timer,usb_ep0_counter
		mov	rtcc,#($FF-$21)		    ;wait about 1 bit before transmit
		reti
:ack_setup
		clrb	flg_setup
		mov	w,#(USB_SETUP_loc - USB_RESET_loc)
		mov	usb_states,w		
		jmp	:ack
:ack_out
		clrb	flg_out			    ;that's where have to check what
		jmp	:ack			    ;out packet data packet is about
						    ;in our case it would be just 
						    ;some configuration settings so 
						    ;so ACK them and skip without a second
						    ;thought
:release_ep0
		clrb	flg_ep0_send	            ;that's where we have to check properly
		clrb	flg_ep0_in		    ;if ACK from the host is received
		jmp	:no_reply		    ;but lets just skip it
:release_ep1
		clrb	flg_ep1_send
		clrb	flg_ep1_in
		jmp	:no_reply
:nack
		mov	w,#(TRANSMIT_loc-IDLE_loc)
		mov	int_states,w
		mov	buffer_count,#$78
		mov	packet_tx,#%11011111
		mov	tx_timer,#$10		    ;NACK is only 16 bit long
		mov	rtcc,#($FF-$21)		    ;wait about 1 bit before transmit
		reti
:ack
		mov	w,#(TRANSMIT_loc-IDLE_loc)
		mov	int_states,w
		mov	buffer_count,#$78
		mov	packet_tx,#%10111111
		mov	tx_timer,#$10		    ;ACK is only 16 bit long
		mov	rtcc,#($FF-$42)		    ;wait about 2 bit before transmit
		reti	

	
TRANSMIT_loc	=	$	
;***********************************************************************************
; TRANSMIT STATE
;***********************************************************************************
		clrb	usb_port.oe		    ;enable output
		mov	fsr,buffer_count
		mov	w,/packet_tx
		and	w,ind
		sz
		jmp	:dont_toggle_bus
		xor	usb_port,#%00110000	     ;tx_lo	equ	4	
						     ;tx_hi	equ	5

:dont_toggle_bus
		mov	w,#(EOP_TRANSMIT_loc-IDLE_loc)
		dec	tx_timer
		snz
		mov	int_states,w

		inc	buffer_count
		setb	buffer_count.4	
		mov	w,#-int_active
		snb	ind.sync_bit
		mov	w,#-int_leap
		retiw		

EOP_TRANSMIT_loc	=	$
;***********************************************************************************
; EOP TRANSMIT STATE
;***********************************************************************************
		clrb	usb_port.tx_hi			;hold SE0 for 2 bits
		clrb	usb_port.tx_lo			;it is one of the traps
							;once eop is > 3 bits
							;host will not accept the 
		mov	w,#(END_TRANSMIT_loc-IDLE_loc)	;packet
		snb	tx_timer.0	
		mov	int_states,w
		inc	tx_timer
		mov	w,#-int_active
		retiw


END_TRANSMIT_loc
		setb	usb_port.tx_hi
		mov	w,#(IDLE_loc - IDLE_loc)
		mov	int_states,w
		setb	usb_port.oe		;disable output		
		mov	rtcc,#($FF-$0F)		;return to interrupt as fast as
		reti				;possible


;***********************************************************************************
; TOKEN
; The macro calls supporting function Token to fill in
; token buffers based on the current usb_address
;***********************************************************************************
TOKEN		MACRO	3
		mov	usb_mask,#\1		;set the buffer
		mov	usb_temp,#\2		;set the value of PID
		mov	usb_hold,#\3		;hold the endpoint number
		call	Set_token
		ENDM
;***********************************************************************************
; HANDSHAKE
; To fill respective USB buffers with handshake packets
;***********************************************************************************
HANDSHAKE	MACRO	2
;**********************************************************************************
; PID
;**********************************************************************************
	
		mov	usb_mask,#\1
		mov	usb_hold,#\2		
		call	Set_handshake
		ENDM
	
;**********************************************************************************
; Start
;**********************************************************************************
Start
 		mov	ra,#%0000	
		mov	rc,#%00000000
		mov	!ra,#%11111110 		;all input	
		mov	!rc,#%11111111 		;all input

		mov	!rb,#usb_port_tris 	
		clrb	usb_port.tx_lo
		setb	usb_port.tx_hi
		setb	usb_port.oe

		mode	$08
		mov	!rb,#$00      		;enable comparator
		mode	$0F

		
		clr	fsr			;reset all ram banks
:zero_ram	sb	fsr.4			;are we on low half of bank?
		setb	fsr.3			;If so, don't touch regs 0-7
		clr	ind			;clear using indirect addressing
		incsz	fsr			;repeat until done
		jmp	:zero_ram
	
		mov	fsr,#$30		;seed the bit stream
		
:bit_stream	
		setb	fsr.4	
		setb	ind.sync_bit
		add	fsr,#4
		sc
		jmp	:bit_stream

		bank	USB_BANK
		mov	usb_states,#(USB_INTERRUPT_loc - USB_RESET_loc)
		mov	int_states,#(BUS_RESET_loc - IDLE_loc)
		mov	!OPTION,#%10001000	;Enable RTCC rollover interrupt
 						;RTCC inc on clock cycle, no prescaler
		jmp	@Main

		org	$200
;**********************************************************************************
; MAIN
; state machine
; 1. RESET - initialise all buffers
; 2. SETUP packet received - get ready to process it
; 3. INTERRUPT transfer 
;**********************************************************************************
Main
		mov	w,usb_states
		add	PC,w

USB_RESET_loc		=	$
		jmp	_USB_RESET
USB_SETUP_loc		=	$
		jmp	_USB_SETUP
USB_INTERRUPT_loc	=	$
		jmp	_USB_INTERRUPT
;**********************************************************************************
; CONTROL
; state machine
;**********************************************************************************

USB_CONTROL_loc		=	$
		bank	USB_BANK
		mov	w,usb_sub_states
		add	PC,w	
REQUEST_loc		=	$
		jmp	_REQUEST
STANDARD_Req_loc	=	$
		jmp	_STANDARD_Req
ADDRESSED_loc		=	$
		jmp	_ADDRESSED
DESCRIPTOR_loc		=	$
		jmp	_DESCRIPTOR
LOAD_loc		=	$
		jmp	_LOAD
CLASS_Req_loc		=	$
		jmp	_CLASS


;**********************************************************************************
; SUPPORTING FUNCTIONS 
; Jump tabel
;**********************************************************************************
Bit_push
		jmp	_Bit_push
Bit_pull	
		jmp	_Bit_pull
Crc5_calculate	
		jmp	_Crc5_calculate		
Crc16_calculate	
		jmp	_Crc16_calculate
Set_token
		jmp	_Set_token
Set_handshake
		jmp	_Set_handshake
Load_ep0_0	
		jmp	_Load_ep0_0
Load_ep0
		jmp	@_Load_ep0
Load_ep1
		jmp	@_Load_ep1
	
;**********************************************************************************
_USB_RESET
								;Based on the current usb_address
								;fill in:
		TOKEN	%10111111,%01101001,%00000001		;Endpoint 1 IN packet
		TOKEN	%11011111,%01101001,%00000000		;Endpoint 0 IN packet
		TOKEN	%11101111,%11100001,%00000000		;Endpoint 0 OUT packet
		TOKEN	%11110111,%00101101,%00000000		;SETUP packet
		HANDSHAKE %10111111,%11010010			;ACK
		HANDSHAKE %11011111,%01011010			;NACK
		;HANDSHAKE %11101111,%00011110			;STALL
		mov	usb_states,#(USB_INTERRUPT_loc - USB_RESET_loc)
		jmp	Main
_USB_SETUP
		mov	usb_states,#(USB_CONTROL_loc - USB_RESET_loc)
		mov	usb_sub_states,#(REQUEST_loc - REQUEST_loc)
		jmp	Main		
_REQUEST		
		mov	usb_buffer,#$38		;skip PID
		mov	usb_bit_stuff,#$06	;set bit stuffing
		mov	usb_mask,#%11111110	;ep0_in buffer
		mov	usb_bit_count,#$08	;always one byte
		setb    flg_ep0_data		;after setup ep0 responds with DATA 1
		call	Bit_pull
		mov	w,#(STANDARD_Req_loc - REQUEST_loc)
		snb	usb_temp.5		;if bit 5 = 0 - standard request
						;if bit 5 = 1 - class request
		mov	w,#(CLASS_Req_loc - REQUEST_loc)
		mov	usb_sub_states,w
		jmp	Main	
	
_STANDARD_Req
		mov	usb_bit_count,#$08
		call	Bit_pull
		cje	usb_temp,#$05,:set_address
		cje	usb_temp,#$06,:get_descriptor
						;all other requests would be acknowledged 
		call	Load_ep0_0		
		mov	usb_states,#(USB_INTERRUPT_loc - USB_RESET_loc)
		jmp	Main
:set_address
		mov	usb_bit_count,#$08	;get the new USB address
		call	Bit_pull
		mov	usb_address,usb_temp	;save the new address		
		call	Load_ep0_0
		mov	usb_sub_states,#(ADDRESSED_loc - REQUEST_loc)
		jmp	Main
:get_descriptor	
		mov	usb_sub_states,#(DESCRIPTOR_loc - REQUEST_loc)
		jmp	Main

_ADDRESSED
		mov	w,#(USB_RESET_loc - USB_RESET_loc)
		sb	flg_ep0_send		;once the status stage is over
						;update all buffers with new address
						;token and handshake packets
		mov	usb_states,w
		jmp	Main	

_DESCRIPTOR
		mov	usb_bit_count,#$08	;as descriptor number is in the 4th byte
		call	Bit_pull		;we have to skip one byte
		mov	usb_bit_count,#$08
		call	Bit_pull
						;standard
		cje	usb_temp,#$01,:device	
		cje	usb_temp,#$02,:configuration
						;class specific

		cje	usb_temp,#$22,:report	
						;other descriptors are not supported
		call	Load_ep0_0		
		mov	usb_states,#(USB_INTERRUPT_loc - USB_RESET_loc)		
		jmp	Main
:device
		mov	usb_ep0_table_s,#(Device_ds - EP0_table_s)
		mov	usb_ep0_table_e,#(Device_de - EP0_table_s)
		mov	usb_sub_states,#(LOAD_loc - REQUEST_loc)
		jmp	Main
:configuration
		mov	usb_bit_count,#$08	;skip one byte	
		call	Bit_pull
		mov	usb_bit_count,#$08	;skip one more byte	
		call	Bit_pull
		mov	usb_bit_count,#$08	;as descriptor size is in the 7th byte
		call	Bit_pull
		cje	usb_temp,#$09,:basic    ;if the given size is only 9 byte,
						;load basic descriptor
						;otherwise load full
:full
		mov	usb_ep0_table_s,#(Config_ds - EP0_table_s)
		mov	usb_ep0_table_e,#(Endpoint_de - EP0_table_s)
		mov	usb_sub_states,#(LOAD_loc - REQUEST_loc)
		jmp	Main
:basic
		mov	usb_ep0_table_s,#(Config_ds - EP0_table_s)
		mov	usb_ep0_table_e,#(Config_de - EP0_table_s)
		mov	usb_sub_states,#(LOAD_loc - REQUEST_loc)
		jmp	Main
:report
		mov	usb_ep0_table_s,#(Report_ds - EP0_table_s)
		mov	usb_ep0_table_e,#(Report_de - EP0_table_s)
		mov	usb_sub_states,#(LOAD_loc - REQUEST_loc)
		jmp	Main
_LOAD
		call	Load_ep0
		cjb	usb_ep0_table_s,usb_ep0_table_e,Main
		mov	usb_states,#(USB_INTERRUPT_loc - USB_RESET_loc)
		jmp	Main
		
_CLASS			=	$
		mov	usb_bit_count,#$08
		call	Bit_pull
		cjne	usb_temp,#$09,:nothing	;if it is set_report request
						;send the song
		mov	usb_ep1_table_s,#(Hello_s - EP1_table_s)
		mov	usb_ep1_table_e,#(Hello_e - EP1_table_s)
:nothing
		call	Load_ep0_0		;class requests are not supported
						;just acknowledged
		mov	usb_states,#(USB_INTERRUPT_loc - USB_RESET_loc)
		jmp	Main

_USB_INTERRUPT		=	$
		call	Load_ep1
		jmp	Main


;**********************************************************************************
; Bit_push
; Supporting function to store data in endpoint 0 and endpoint 1 buffers
; for transmission to host (buffer mask is %11111110 and %11111101)
; usb_temp is the source of data
; usb_bit_count is the number of bits to store (8 for data, 7 for address, etc)
; usb_buffer is the address from which to start
; Bit stuffing is provided
;**********************************************************************************
_Bit_push
		sb	usb_mask.0
		inc	usb_ep0_counter
		sb	usb_mask.1
		inc	usb_ep1_counter

		test	usb_bit_stuff
		sz
		jmp	:bit_push_resume
		sb	usb_mask.0
		inc	usb_ep0_counter			;one more bit
		sb	usb_mask.1
		inc	usb_ep1_counter
		mov	usb_bit_stuff,#$06
		mov	w,usb_mask
		mov	fsr,usb_buffer
		and	ind,w
		bank	USB_BANK
		inc	usb_buffer
		setb	usb_buffer.4
:bit_push_resume
		rr	usb_temp
		sc
		jmp	:zero_bit
:one_bit
		dec	usb_bit_stuff
		mov	fsr,usb_buffer
		mov	w,/usb_mask
		or	ind,w
		jmp	Bit_push_x
:zero_bit
		mov	usb_bit_stuff,#$06
		mov	fsr,usb_buffer
		mov	w,usb_mask
		and	ind,w
Bit_push_x
	
		bank	USB_BANK
		inc	usb_buffer
		setb	usb_buffer.4
		decsz	usb_bit_count



		jmp	_Bit_push
		retp
;**********************************************************************************
; Bit_pull
; Retrieve data from receive buffer (buffer mask %11111110).
; Due to our device configuration only endpoint 0 supports OUT
; packets. This function gets rid of bit stuffing and returns in
; usb_temp the number of bits specified in usb_bit_count
; usb_buffer is the address of the first bit to start with.
;**********************************************************************************

_Bit_pull
		test	usb_bit_stuff
		sz
		jmp	:bit_pull_resume
		mov	usb_bit_stuff,#$06
		inc	usb_buffer
		setb	usb_buffer.4
:bit_pull_resume
		mov	fsr,usb_buffer
		mov	w,/usb_mask
		and	w,ind	
		bank	USB_BANK
		snz
		jmp	:zero_bit
:one_bit
		dec	usb_bit_stuff
		stc
		rr	usb_temp
		jmp	Bit_pull_x
:zero_bit
		mov	usb_bit_stuff,#$06
		clc
		rr	usb_temp

Bit_pull_x
		inc	usb_buffer
		setb	usb_buffer.4
		decsz	usb_bit_count
		jmp	_Bit_pull
		retp

;**********************************************************************************
; Crc5_calculate, the MSb is in usb_crc5.0
;**********************************************************************************
_Crc5_calculate 
		clc
		rr	usb_crc5
		mov	w,#%00010100
		snb	usb_temp.0
		jmp	:one_bit
:zero_bit
		snc		
		xor	usb_crc5,w
		jmp	Crc5_calculate_x
:one_bit
		sc	
		xor	usb_crc5,w

Crc5_calculate_x
		rr	usb_temp
		decsz	usb_bit_count		
		jmp	_Crc5_calculate	
		retp

;**********************************************************************************
; Crc16_calculate, the MSb is in usb_crc16_lo.0
;**********************************************************************************
_Crc16_calculate 
		clc
		rr	usb_crc16_hi
		rr	usb_crc16_lo
		mov	w,#%10100000
		snb	usb_temp.0
		jmp	:one_bit
:zero_bit
		snc		
		xor	usb_crc16_hi,w
		mov	w,#%00000001
		snc
		xor	usb_crc16_lo,w

		jmp	Crc16_calculate_x
:one_bit
		sc		
		xor	usb_crc16_hi,w
		mov	w,#%00000001
		sc
		xor	usb_crc16_lo,w

Crc16_calculate_x
		rr	usb_temp
		decsz	usb_bit_count		
		jmp	_Crc16_calculate	
		retp


;**********************************************************************************
; Set_token
;**********************************************************************************
 _Set_token
		mov	usb_buffer,#address_lo	;all token buffers start at address_lo
		mov	usb_bit_stuff,#$06	;initialise bit stuffing
		mov	usb_bit_count,#$08	;PID is always 8-bit long
		mov	usb_crc5,#%00011111	;seed crc5 with all 1's
		call	Bit_push
;**********************************************************************************
; Address
; as it belongs to the data field, calculate crc
;**********************************************************************************
		mov	usb_temp,usb_address
		mov	usb_bit_count,#$07	;address is only 7-bit long
		call	Crc5_calculate		;start calculating Crc5
		mov	usb_temp,usb_address	
		mov	usb_bit_count,#$07
		call	Bit_push		;fill in the buffer
;**********************************************************************************
; Endpoint
;**********************************************************************************
		mov	usb_temp,usb_hold	;set the endpoint number
		mov	usb_bit_count,#$04	;the number is 4-bit long
		call	Crc5_calculate
		mov	usb_temp,usb_hold
		mov	usb_bit_count,#$04
		call	Bit_push
;**********************************************************************************
; Crc5
;**********************************************************************************
		xor	usb_crc5,#$FF		;invert CRC5
		mov	usb_temp,usb_crc5
		mov	usb_bit_count,#$05
		call	Bit_push
		mov	fsr,usb_buffer
		mov	w,/usb_mask		;why don't we add 1's
		or	ind,w			;in case EOP is detected late
		inc	fsr
		setb	fsr.4
		or	ind,w
		bank	USB_BANK
		retp

;**********************************************************************************
; Set_handshake
;**********************************************************************************
_Set_handshake
;**********************************************************************************
; Synchronization
;**********************************************************************************
		mov	usb_buffer,#$78		;the actual handshake starts from
						;address_hi, however we save sync pattern
						;starting $78, because we need to send 
						;handshakes too
		mov	usb_bit_stuff,#$06	;nobody needs it for handshake, too small

		mov	usb_temp,#%10000000	;sync
		mov	usb_bit_count,#$08
		call	Bit_push
		mov	usb_bit_count,#$08	;always 8
		mov	usb_temp,usb_hold
;**********************************************************************************
; Pid
;**********************************************************************************
		call	Bit_push
		mov	fsr,usb_buffer
		mov	w,/usb_mask		;why don't we add 1's
		or	ind,w			;in case EOP is detected late
		inc	fsr
		setb	fsr.4
		or	ind,w
		bank	USB_BANK


		retp

;**********************************************************************************
; Load_ep0_0
; load IN buffer for endpoint 0 with 0 bytes of data
;**********************************************************************************
_Load_ep0_0
		mov	usb_buffer,#address_lo	;the actual handshake starts from
		mov	usb_bit_stuff,#$06	
		mov	usb_mask,#%11111110
		mov	usb_temp,#%10000000	;sync
		mov	usb_bit_count,#$08
		clr	usb_ep0_counter
		call	Bit_push
		mov	usb_bit_count,#$08
		mov	usb_temp,#%01001011	;always DATA1 pid
		call	Bit_push
		mov	usb_bit_count,#$08
		mov	usb_temp,#%00000000	;crc16 inverted
		call	Bit_push
		mov	usb_bit_count,#$08
		mov	usb_temp,#%00000000	;crc16 inverted
		call	Bit_push
		setb	flg_ep0_send		;there is something to send
		retp	
		
		org	$500
;**********************************************************************************
; Load_ep0
; load IN buffer for endpoint 0 with data from the table
; starting address usb_ep0_table_s 
;**********************************************************************************
_Load_ep0
;**********************************************************************************
; Synchronization
; is first
;**********************************************************************************
		cjae	usb_ep0_table_s,usb_ep0_table_e,_Load_ep0_x
		snb	flg_ep0_send
		jmp	_Load_ep0_x		;do not load anything if send flag 
						;is set or there is nothing to send
		mov	usb_ep0_byte,#$09	;set the number of bytes counter
		mov	usb_buffer,#address_lo
		mov	usb_bit_stuff,#$06
		mov	usb_bit_count,#$08	;always 8
		mov	usb_crc16_hi,#$FF	;seed crc16 registers
		mov	usb_crc16_lo,#$FF
		mov	usb_temp,#%10000000	;start with sync	
		mov	usb_mask,#%11111110	;set to IN endpoint 0 buffer
		clr	usb_ep0_counter		

		call	@Bit_push		
;**********************************************************************************
; Pid
; Data 0/1
;**********************************************************************************		
		mov	w,#%11000011		
		snb	flg_EP0_data
		mov	w,#%01001011
		mov	usb_temp,w
		xor	usb_flags,#%01000000	;toggle DATA 1/0
		mov	usb_bit_count,#$08
		call	@Bit_push		;DATA 1/0
;**********************************************************************************
; 8 or less bytes
; of real data
;**********************************************************************************
:data
		cjae	usb_ep0_table_s,usb_ep0_table_e,:crc16
		dec	usb_ep0_byte
		snz
		jmp	:crc16
		mov	w,usb_ep0_table_s
		call	EP0_table
		mov	usb_temp,w
		mov	usb_bit_count,#$08
		call	@Crc16_calculate
		mov	w,usb_ep0_table_s
		call	EP0_table
		mov	usb_temp,w
		mov	usb_bit_count,#$08
		call	@Bit_push
		inc	usb_ep0_table_s
		jmp	:data
;**********************************************************************************
; 2 bytes
; of CRC
;**********************************************************************************
:crc16
		mov	usb_temp,usb_crc16_lo
		xor	usb_temp,#$FF
		mov	usb_bit_count,#$08
		call	@Bit_push
		mov	usb_temp,usb_crc16_hi
		xor	usb_temp,#$FF
		mov	usb_bit_count,#$08
		call	@Bit_push
		setb	flg_ep0_send		;set flag for sending
		retp
_Load_ep0_x
		retp	

		org	$700
;**********************************************************************************
; Load_ep1
; load IN buffer for endpoint 0 with data from the table
; starting address usb_ep1_table_s 
;**********************************************************************************
_Load_ep1
;**********************************************************************************
; Synchronization
; is first
;**********************************************************************************

		snb	flg_ep1_send
		jmp	_Load_ep1_x			;do not load anything if send flag 
						;is set or there is nothing to send
		mov	usb_buffer,#address_lo
		mov	usb_bit_stuff,#$06
		mov	usb_bit_count,#$08	;always 8
		mov	usb_crc16_hi,#$FF	;seed crc16 registers
		mov	usb_crc16_lo,#$FF
		mov	usb_temp,#%10000000	;start with sync	
		mov	usb_mask,#%11111101	;set usb_mask to IN endpoint 1 buffer
		clr	usb_ep1_counter		
		call	@Bit_push		
;**********************************************************************************
; Pid
; Data 0/1
;**********************************************************************************
		clr	usb_hold
		mov	usb_temp,#%11000011
		sb	flg_EP1_data
		jmp	:data0
:data1						;for data 1 packet send make code
	 	cjae	usb_ep1_table_s,usb_ep1_table_e,_Load_ep1_x
		mov	w,usb_ep1_table_s
		call	EP1_table
		mov	usb_hold,w
		inc	usb_ep1_table_s
		mov	usb_temp,#%01001011
:data0						;for data 0 packet send break code
		mov	usb_bit_count,#$08
		xor	usb_flags,#%10000000	;toggle DATA 1/0
		call	@Bit_push		;DATA 1/0
;**********************************************************************************
; Always 8 bytes 
; of real data
;**********************************************************************************
DATABYTE	MACRO
		clr	usb_temp
		mov	usb_bit_count,#$08
		call	@Crc16_calculate
		clr	usb_temp
		mov	usb_bit_count,#$08
		call	@Bit_push
		ENDM
		
		DATABYTE			;byte 0 - $00
		DATABYTE			;byte 1 - $00
		mov	usb_temp,usb_hold	;byte 2 - $00 for DATA 0
		mov	usb_bit_count,#$08	;	  value from EP1_table
		call	@Crc16_calculate	;	  for DATA 1	
		mov	usb_temp,usb_hold
		mov	usb_bit_count,#$08
		call	@Bit_push
		DATABYTE			;byte 3 - $00
		DATABYTE			;byte 4 - $00
		DATABYTE			;byte 5	- $00
		DATABYTE			;byte 6	- $00
		DATABYTE			;byte 7 - $00
		
;**********************************************************************************
; 2 bytes
; of CRC
;**********************************************************************************
:crc16
		mov	usb_temp,usb_crc16_lo
		xor	usb_temp,#$FF
		mov	usb_bit_count,#$08
		call	@Bit_push
		mov	usb_temp,usb_crc16_hi
		xor	usb_temp,#$FF
		mov	usb_bit_count,#$08
		call	@Bit_push
		setb	flg_ep1_send		;set flag for sending
		retp
_Load_ep1_x
		retp

		org	$400
EP0_table
		jmp	PC+w

EP0_table_s	=	$
		retw	$80
		retw	$06
		retw	$00
		retw	$01
		retw	$00
		retw	$00
		retw	$40
		retw	$00

Device_ds	=	$
		retw	(Device_de - Device_ds)		;length
		retw	$01				;type
		retw	$00,$01 			;USB specifications 1.0	
		retw	$00				;class code
		retw	$00				;subclass code
		retw	$00				;protocol	
		retw	$08				;max.packet size
		retw	$88,88				;vendor ID	
		retw	$02,00				;product ID
		retw    $01,00				;device release number
		retw	$00				;manufacturer string descriptor index
		retw	$00				;product string descriptor index
		retw	$00				;serial number string descriptor
		retw	$01				;number of possible configurations	
Device_de	=	$
Config_ds	=	$
		retw	(Config_de - Config_ds)		;length
		retw	$02				;type
		retw	(Endpoint_de - Config_ds),$00	;total data length
		retw	$01				;interface supported
		retw	$01				;configuration value
		retw	$00				;string descriptor index
		retw	$A0				;configuration
		retw	$32				;maximum power consumption
Config_de	=	$
Inter_ds	=	$
		retw	(Inter_de - Inter_ds)		;length
		retw	$04				;type
		retw	$00				;number of interfaces
		retw	$00				;alternate settings
		retw	$01				;number of endpoints
		retw	$03				;class code
		retw	$01				;subclass code
		retw	$01				;protocol code
		retw	$00				;string index
Inter_de	=	$
Class_ds	=	$
		retw	(Class_de - Class_ds)		;length
		retw	$21				;type
		retw	$00,$01				;HID class release number	
		retw	$00				;country code
		retw	$01				;number of HID descriptors to follow
		retw	$22				;report descriptor type
		retw	(Report_de - Report_ds),$00	;total length of report descriptor
Class_de	=	$
Endpoint_ds	=	$
		retw	(Endpoint_de - Endpoint_ds)	;length
		retw	$05				;type
		retw	$81				;encoded address (IN to endpoint 1)
		retw	$03				;endpoint attribute
		retw	$08,$00				;max. packet size
		retw	$0A				;polling interval
Endpoint_de	=	$
;**********************************************************************************
; Generic USB keyboard report, as give at page 70 of
; Device Class Definition for Human Interface Devices (HID)
;**********************************************************************************
Report_ds	=	$
		retw	$05,$01				;generic desktop
		retw	$09,$06				;keyboard
		retw	$A1,$01				;collection (application)
		retw	$05,$07				;key codes
		retw	$19,$E0				;usage minimum
		retw	$29,$E7				;usage maximum
		retw	$15,$00				;logical minimum
		retw	$25,$00				;logical maximum
		retw	$75,$01				;report size
		retw	$95,$08				;report count (8 bytes)
		retw	$81,$02				;input (data, variable, absolute)
		retw	$95,$01				;report count
		retw	$75,$08				;report size
		retw	$81,$01				;input (constant)
		retw	$95,$05				;report count
		retw	$75,$01				;report size
		retw	$05,$08				;leds
		retw	$19,$01				;usage minimum
		retw	$29,$05				;usage maximum
		retw	$91,$02				;output (data, variable, absolute)
		retw	$95,$03				;report count
		retw	$75,$01				;report size
		retw	$91,$01				;output (constant)
		retw	$95,$06				;report count
		retw	$75,$08				;report size
		retw	$15,$00				;logical minimum
		retw	$25,$65				;logical maximum
		retw	$05,$07				;key codes
		retw	$19,$00				;usage minimum
		retw	$29,$65				;usage maximum
		retw	$81,00				;input
		retw	$C0				;end collection
Report_de	=	$

		org	$600
EP1_table
		jmp	PC+w
EP1_table_s	=	$
Hello_s		=	$
		retw	$28
			;To my parents
		retw	$17,$12,$2C,$10,$1C,$2C,$13,$04,$15,$08,$11,$17,$16,$28,$28

			;All the leaves are brown and the sky is gray
		retw	$04,$0F,$0F,$2C,$17,$0B,$08,$2C,$0F,$08,$04,$19,$08,$16,$2C
		retw	$04,$15,$08,$2C,$05,$15,$12,$1A,$11,$2C,$04,$11,$07,$2C
		retw	$17,$0B,$08,$2C,$16,$0E,$1C,$2C,$0C,$16,$2C,$0A,$15,$04,$1C,$28

			;I've been for a walk on a winter's day
		retw	$0C,$34,$19,$08,$2C,$05,$08,$08,$11,$2C,$09,$12,$15,$2C,$04,$2C,$1A,$04,$0F,$0E,$2C
		retw	$12,$11,$2C,$04,$2C,$1A,$0C,$11,$17,$08,$15,$34,$16,$2C,$07,$04,$1C,$28

			;I'd be safe and warm if I was in L.A.
		retw	$0C,$34,$07,$2C,$05,$08,$2C,$16,$04,$09,$08,$2C,$04,$11,$07,$2C
		retw	$1A,$04,$15,$10,$2C,$0C,$09,$2C,$0C,$2C,$1A,$04,$16,$2C
		retw	$0C,$11,$2C,$0F,$37,$04,$37,$28

			;California dreamin' on such a winter's day
		retw	$06,$04,$0F,$0C,$09,$12,$15,$11,$0C,$04,$2C,$07,$15,$08,$04,$10,$0C,$11,$34,$2C
		retw	$12,$11,$2C,$16,$18,$06,$0B,$2C,$04,$2C,$1A,$0C,$11,$17,$08,$15,$34,$16,$2C
		retw	$07,$04,$1C,$28

				
Hello_e		=	$

Questions: