|  | ;   usbdux_firmware.asm | 
|  | ;   Copyright (C) 2004,2009 Bernd Porr, Bernd.Porr@f2s.com | 
|  | ;   For usbdux.c | 
|  | ; | 
|  | ;   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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. | 
|  | ; | 
|  | ; | 
|  | ; Firmware: usbdux_firmware.asm for usbdux.c | 
|  | ; Description: University of Stirling USB DAQ & INCITE Technology Limited | 
|  | ; Devices: [ITL] USB-DUX (usbdux.o) | 
|  | ; Author: Bernd Porr <Bernd.Porr@f2s.com> | 
|  | ; Updated: 17 Apr 2009 | 
|  | ; Status: stable | 
|  | ; | 
|  | ;;; | 
|  | ;;; | 
|  | ;;; | 
|  |  | 
|  | .inc	fx2-include.asm | 
|  |  | 
|  | .equ	CHANNELLIST,80h	; channellist in indirect memory | 
|  |  | 
|  | .equ	CMD_FLAG,90h	; flag if next IN transf is DIO | 
|  | .equ	SGLCHANNEL,91h	; channel for INSN | 
|  | .equ	PWMFLAG,92h	; PWM | 
|  |  | 
|  | .equ	DIOSTAT0,98h	; last status of the digital port | 
|  | .equ	DIOSTAT1,99h	; same for the second counter | 
|  |  | 
|  | .equ	CTR0,0A0H	; counter 0 | 
|  | .equ	CTR1,0A2H	; counter 1 | 
|  |  | 
|  | .org	0000h		; after reset the processor starts here | 
|  | ljmp	main		; jump to the main loop | 
|  |  | 
|  | .org	000bh		; timer 0 irq | 
|  | ljmp	timer0_isr | 
|  |  | 
|  | .org	0043h		; the IRQ2-vector | 
|  | ljmp	jmptbl		; irq service-routine | 
|  |  | 
|  | .org	0100h		; start of the jump table | 
|  |  | 
|  | jmptbl:	ljmp	sudav_isr | 
|  | nop | 
|  | ljmp	sof_isr | 
|  | nop | 
|  | ljmp	sutok_isr | 
|  | nop | 
|  | ljmp	suspend_isr | 
|  | nop | 
|  | ljmp	usbreset_isr | 
|  | nop | 
|  | ljmp	hispeed_isr | 
|  | nop | 
|  | ljmp	ep0ack_isr | 
|  | nop | 
|  | ljmp	spare_isr | 
|  | nop | 
|  | ljmp	ep0in_isr | 
|  | nop | 
|  | ljmp	ep0out_isr | 
|  | nop | 
|  | ljmp	ep1in_isr | 
|  | nop | 
|  | ljmp	ep1out_isr | 
|  | nop | 
|  | ljmp	ep2_isr | 
|  | nop | 
|  | ljmp	ep4_isr | 
|  | nop | 
|  | ljmp	ep6_isr | 
|  | nop | 
|  | ljmp	ep8_isr | 
|  | nop | 
|  | ljmp	ibn_isr | 
|  | nop | 
|  | ljmp	spare_isr | 
|  | nop | 
|  | ljmp	ep0ping_isr | 
|  | nop | 
|  | ljmp	ep1ping_isr | 
|  | nop | 
|  | ljmp	ep2ping_isr | 
|  | nop | 
|  | ljmp	ep4ping_isr | 
|  | nop | 
|  | ljmp	ep6ping_isr | 
|  | nop | 
|  | ljmp	ep8ping_isr | 
|  | nop | 
|  | ljmp	errlimit_isr | 
|  | nop | 
|  | ljmp	spare_isr | 
|  | nop | 
|  | ljmp	spare_isr | 
|  | nop | 
|  | ljmp	spare_isr | 
|  | nop | 
|  | ljmp	ep2isoerr_isr | 
|  | nop | 
|  | ljmp	ep4isoerr_isr | 
|  | nop | 
|  | ljmp	ep6isoerr_isr | 
|  | nop | 
|  | ljmp	ep8isoerr_isr | 
|  |  | 
|  |  | 
|  | ;; dummy isr | 
|  | sudav_isr: | 
|  | sutok_isr: | 
|  | suspend_isr: | 
|  | usbreset_isr: | 
|  | hispeed_isr: | 
|  | ep0ack_isr: | 
|  | spare_isr: | 
|  | ep0in_isr: | 
|  | ep0out_isr: | 
|  | ep1in_isr: | 
|  | ibn_isr: | 
|  | ep0ping_isr: | 
|  | ep1ping_isr: | 
|  | ep2ping_isr: | 
|  | ep4ping_isr: | 
|  | ep6ping_isr: | 
|  | ep8ping_isr: | 
|  | errlimit_isr: | 
|  | ep2isoerr_isr: | 
|  | ep4isoerr_isr: | 
|  | ep6isoerr_isr: | 
|  | ep8isoerr_isr: | 
|  | ep6_isr: | 
|  | ep2_isr: | 
|  | ep4_isr: | 
|  |  | 
|  | push	dps | 
|  | push	dpl | 
|  | push	dph | 
|  | push	dpl1 | 
|  | push	dph1 | 
|  | push	acc | 
|  | push	psw | 
|  |  | 
|  | ;; clear the USB2 irq bit and return | 
|  | mov	a,EXIF | 
|  | clr	acc.4 | 
|  | mov	EXIF,a | 
|  |  | 
|  | pop	psw | 
|  | pop	acc | 
|  | pop	dph1 | 
|  | pop	dpl1 | 
|  | pop	dph | 
|  | pop	dpl | 
|  | pop	dps | 
|  |  | 
|  | reti | 
|  |  | 
|  |  | 
|  | ;;; main program | 
|  | ;;; basically only initialises the processor and | 
|  | ;;; then engages in an endless loop | 
|  | main: | 
|  | mov	DPTR,#CPUCS	; CPU control register | 
|  | mov	a,#00010000b	; 48Mhz | 
|  | lcall	syncdelaywr | 
|  |  | 
|  | mov     dptr,#REVCTL | 
|  | mov     a,#00000011b    ; allows skip | 
|  | lcall   syncdelaywr | 
|  |  | 
|  | mov	IP,#0		; all std 8051 int have low priority | 
|  | mov	EIP,#0FFH	; all FX2 interrupts have high priority | 
|  |  | 
|  | mov	dptr,#INTSETUP	; IRQ setup register | 
|  | mov	a,#08h		; enable autovector | 
|  | lcall	syncdelaywr | 
|  |  | 
|  | lcall	initAD		; init the ports to the converters | 
|  |  | 
|  | lcall	initeps		; init the isochronous data-transfer | 
|  |  | 
|  | lcall	init_timer | 
|  |  | 
|  | mloop2:	nop | 
|  |  | 
|  | ;;; pwm | 
|  | mov	r0,#PWMFLAG	; pwm on? | 
|  | mov	a,@r0		; get info | 
|  | jz	mloop2		; it's off | 
|  |  | 
|  | mov	a,GPIFTRIG	; GPIF status | 
|  | anl	a,#80h		; done bit | 
|  | jz	mloop2		; GPIF still busy | 
|  |  | 
|  | mov     a,#01h		; WR,EP4, 01 = EP4 | 
|  | mov     GPIFTRIG,a	; restart it | 
|  |  | 
|  | sjmp	mloop2		; loop for ever | 
|  |  | 
|  |  | 
|  | ;;; GPIF waveform for PWM | 
|  | waveform: | 
|  | ;;      0     1     2     3     4     5     6     7(not used) | 
|  | ;; len (gives 50.007Hz) | 
|  | .db	195,  195,  195,  195,  195,  195,  1,    1 | 
|  |  | 
|  | ;; opcode | 
|  | .db	002H, 006H, 002H, 002H, 002H, 002H, 002H, 002H | 
|  |  | 
|  | ;; out | 
|  | .db	0ffH, 0ffH, 0ffH, 0ffH, 0ffH, 0ffH, 0ffH, 0ffH | 
|  |  | 
|  | ;; log | 
|  | .db	000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H | 
|  |  | 
|  |  | 
|  | stopPWM: | 
|  | mov	r0,#PWMFLAG	; flag for PWM | 
|  | mov	a,#0		; PWM (for the main loop) | 
|  | mov	@r0,a		; set it | 
|  |  | 
|  | mov	dptr,#IFCONFIG	; switch off GPIF | 
|  | mov	a,#10000000b	; gpif, 30MHz, internal IFCLK | 
|  | lcall	syncdelaywr | 
|  | ret | 
|  |  | 
|  |  | 
|  | ;;; init PWM | 
|  | startPWM: | 
|  | mov	dptr,#IFCONFIG	; switch on IFCLK signal | 
|  | mov	a,#10000010b	; gpif, 30MHz, internal IFCLK | 
|  | lcall	syncdelaywr | 
|  |  | 
|  | mov	OEB,0FFH	; output to port B | 
|  |  | 
|  | mov	DPTR,#EP4CFG | 
|  | mov	a,#10100000b	; valid, out, bulk | 
|  | movx	@DPTR,a | 
|  |  | 
|  | ;; reset the endpoint | 
|  | mov	dptr,#FIFORESET | 
|  | mov	a,#80h		; NAK | 
|  | lcall	syncdelaywr | 
|  | mov	a,#84h		; reset EP4 + NAK | 
|  | lcall	syncdelaywr | 
|  | mov	a,#0		; normal op | 
|  | lcall	syncdelaywr | 
|  |  | 
|  | mov	dptr,#EP4BCL | 
|  | mov	a,#0H		; discard packets | 
|  | lcall	syncdelaywr	; empty FIFO buffer | 
|  | lcall	syncdelaywr	; empty FIFO buffer | 
|  |  | 
|  | ;; aborts all transfers by the GPIF | 
|  | mov	dptr,#GPIFABORT | 
|  | mov	a,#0ffh		; abort all transfers | 
|  | lcall	syncdelaywr | 
|  |  | 
|  | ;; wait for GPIF to finish | 
|  | wait_f_abort: | 
|  | mov	a,GPIFTRIG	; GPIF status | 
|  | anl	a,#80h		; done bit | 
|  | jz	wait_f_abort	; GPIF busy | 
|  |  | 
|  | mov     dptr,#GPIFCTLCFG | 
|  | mov     a,#10000000b    ; tri state for CTRL | 
|  | lcall   syncdelaywr | 
|  |  | 
|  | mov     dptr,#GPIFIDLECTL | 
|  | mov     a,#11110000b    ; all CTL outputs low | 
|  | lcall   syncdelaywr | 
|  |  | 
|  | ;; abort if FIFO is empty | 
|  | mov     a,#00000001b    ; abort if empty | 
|  | mov     dptr,#EP4GPIFFLGSEL | 
|  | lcall   syncdelaywr | 
|  |  | 
|  | ;; | 
|  | mov     a,#00000001b    ; stop if GPIF flg | 
|  | mov     dptr,#EP4GPIFPFSTOP | 
|  | lcall   syncdelaywr | 
|  |  | 
|  | ;; transaction counter | 
|  | mov	a,#0ffH | 
|  | mov	dptr,#GPIFTCB3 | 
|  | lcall	syncdelaywr | 
|  |  | 
|  | ;; transaction counter | 
|  | mov	a,#0ffH | 
|  | mov	dptr,#GPIFTCB2 | 
|  | lcall	syncdelaywr | 
|  |  | 
|  | ;; transaction counter | 
|  | mov	a,#0ffH		; 512 bytes | 
|  | mov	dptr,#GPIFTCB1 | 
|  | lcall	syncdelaywr | 
|  |  | 
|  | ;; transaction counter | 
|  | mov	a,#0ffH | 
|  | mov	dptr,#GPIFTCB0 | 
|  | lcall	syncdelaywr | 
|  |  | 
|  | ;; RDY pins. Not used here. | 
|  | mov     a,#0 | 
|  | mov     dptr,#GPIFREADYCFG | 
|  | lcall   syncdelaywr | 
|  |  | 
|  | ;; drives the output in the IDLE state | 
|  | mov     a,#1 | 
|  | mov     dptr,#GPIFIDLECS | 
|  | lcall   syncdelaywr | 
|  |  | 
|  | ;; direct data transfer from the EP to the GPIF | 
|  | mov	dptr,#EP4FIFOCFG | 
|  | mov	a,#00010000b	; autoout=1, byte-wide | 
|  | lcall	syncdelaywr | 
|  |  | 
|  | ;; waveform 0 is used for FIFO out | 
|  | mov	dptr,#GPIFWFSELECT | 
|  | mov	a,#00000000b | 
|  | movx	@dptr,a | 
|  | lcall	syncdelay | 
|  |  | 
|  | ;; transfer the delay byte from the EP to the waveform | 
|  | mov	dptr,#0e781h	; EP1 buffer | 
|  | movx	a,@dptr		; get the delay | 
|  | mov	dptr,#waveform	; points to the waveform | 
|  | mov	r2,#6		; fill 6 bytes | 
|  | timloop: | 
|  | movx	@dptr,a		; save timing in a xxx | 
|  | inc	dptr | 
|  | djnz	r2,timloop	; fill the 6 delay bytes | 
|  |  | 
|  | ;; load waveform | 
|  | mov     AUTOPTRH2,#0E4H ; XDATA0H | 
|  | lcall   syncdelay | 
|  | mov     AUTOPTRL2,#00H  ; XDATA0L | 
|  | lcall   syncdelay | 
|  |  | 
|  | mov	dptr,#waveform	; points to the waveform | 
|  |  | 
|  | mov     AUTOPTRSETUP,#7 ; autoinc and enable | 
|  | lcall   syncdelay | 
|  |  | 
|  | mov     r2,#20H         ; 32 bytes to transfer | 
|  |  | 
|  | wavetr: | 
|  | movx    a,@dptr | 
|  | inc	dptr | 
|  | push	dpl | 
|  | push	dph | 
|  | push	dpl1 | 
|  | push	dph1 | 
|  | mov     dptr,#XAUTODAT2 | 
|  | movx    @dptr,a | 
|  | lcall   syncdelay | 
|  | pop	dph1 | 
|  | pop	dpl1 | 
|  | pop	dph | 
|  | pop	dpl | 
|  | djnz    r2,wavetr | 
|  |  | 
|  | mov	dptr,#OUTPKTEND | 
|  | mov	a,#084H | 
|  | lcall	syncdelaywr | 
|  | lcall	syncdelaywr | 
|  |  | 
|  | mov	r0,#PWMFLAG	; flag for PWM | 
|  | mov	a,#1		; PWM (for the main loop) | 
|  | mov	@r0,a		; set it | 
|  |  | 
|  | ret | 
|  |  | 
|  |  | 
|  |  | 
|  | ;;; initialise the ports for the AD-converter | 
|  | initAD: | 
|  | mov	OEA,#27H	;PortA0,A1,A2,A5 Outputs | 
|  | mov	IOA,#22H	;/CS = 1, disable transfers to the converters | 
|  | ret | 
|  |  | 
|  |  | 
|  | ;;; init the timer for the soft counters | 
|  | init_timer: | 
|  | ;; init the timer for 2ms sampling rate | 
|  | mov	CKCON,#00000001b; CLKOUT/12 for timer | 
|  | mov	TL0,#010H	; 16 | 
|  | mov	TH0,#0H		; 256 | 
|  | mov	IE,#82H		; switch on timer interrupt (80H for all IRQs) | 
|  | mov	TMOD,#00000000b	; 13 bit counters | 
|  | setb	TCON.4		; enable timer 0 | 
|  | ret | 
|  |  | 
|  |  | 
|  | ;;; from here it's only IRQ handling... | 
|  |  | 
|  | ;;; A/D-conversion: | 
|  | ;;; control-byte in a, | 
|  | ;;; result in r3(low) and r4(high) | 
|  | ;;; this routine is optimised for speed | 
|  | readAD:				; mask the control byte | 
|  | anl	a,#01111100b	; only the channel, gain+pol are left | 
|  | orl	a,#10000001b	; start bit, external clock | 
|  | ;; set CS to low | 
|  | clr	IOA.1		; set /CS to zero | 
|  | ;; send the control byte to the AD-converter | 
|  | mov 	R2,#8		; bit-counter | 
|  | bitlp:	jnb     ACC.7,bitzero	; jump if Bit7 = 0? | 
|  | setb	IOA.2		; set the DIN bit | 
|  | sjmp	clock		; continue with the clock | 
|  | bitzero:clr	IOA.2		; clear the DIN bit | 
|  | clock:	setb	IOA.0		; SCLK = 1 | 
|  | clr	IOA.0		; SCLK = 0 | 
|  | rl      a               ; next Bit | 
|  | djnz    R2,bitlp | 
|  |  | 
|  | ;; continue the aquisition (already started) | 
|  | clr	IOA.2		; clear the DIN bit | 
|  | mov 	R2,#5		; five steps for the aquision | 
|  | clockaq:setb	IOA.0		; SCLK = 1 | 
|  | clr	IOA.0		; SCLK = 0 | 
|  | djnz    R2,clockaq	; loop | 
|  |  | 
|  | ;; read highbyte from the A/D-converter | 
|  | ;; and do the conversion | 
|  | mov	r4,#0 		; Highbyte goes into R4 | 
|  | mov	R2,#4		; COUNTER 4 data bits in the MSB | 
|  | mov	r5,#08h		; create bit-mask | 
|  | gethi:				; loop get the 8 highest bits from MSB downw | 
|  | setb	IOA.0		; SCLK = 1 | 
|  | clr	IOA.0		; SCLK = 0 | 
|  | mov	a,IOA		; from port A | 
|  | jnb	ACC.4,zerob	; the in-bit is zero | 
|  | mov	a,r4		; get the byte | 
|  | orl	a,r5		; or the bit to the result | 
|  | mov	r4,a		; save it again in r4 | 
|  | zerob:	mov	a,r5		; get r5 in order to shift the mask | 
|  | rr	a		; rotate right | 
|  | mov	r5,a		; back to r5 | 
|  | djnz	R2,gethi | 
|  | ;; read the lowbyte from the A/D-converter | 
|  | mov	r3,#0 		; Lowbyte goes into R3 | 
|  | mov	r2,#8		; COUNTER 8 data-bits in the LSB | 
|  | mov	r5,#80h		; create bit-mask | 
|  | getlo:				; loop get the 8 highest bits from MSB downw | 
|  | setb	IOA.0		; SCLK = 1 | 
|  | clr	IOA.0		; SCLK = 0 | 
|  | mov	a,IOA		; from port A | 
|  | jnb	ACC.4,zerob2	; the in-bit is zero | 
|  | mov	a,r3		; get the result-byte | 
|  | orl	a,r5		; or the bit to the result | 
|  | mov	r3,a		; save it again in r4 | 
|  | zerob2:	mov	a,r5		; get r5 in order to shift the mask | 
|  | rr	a		; rotate right | 
|  | mov	r5,a		; back to r5 | 
|  | djnz	R2,getlo | 
|  | setb	IOA.1		; set /CS to one | 
|  | ;; | 
|  | ret | 
|  |  | 
|  |  | 
|  |  | 
|  | ;;; aquires data from A/D channels and stores them in the EP6 buffer | 
|  | conv_ad: | 
|  | mov	AUTOPTRH1,#0F8H	; auto pointer on EP6 | 
|  | mov	AUTOPTRL1,#00H | 
|  | mov	AUTOPTRSETUP,#7 | 
|  | mov	r0,#CHANNELLIST	; points to the channellist | 
|  |  | 
|  | mov	a,@r0		; number of channels | 
|  | mov	r1,a		; counter | 
|  |  | 
|  | mov 	DPTR,#XAUTODAT1	; auto pointer | 
|  | convloop: | 
|  | inc	r0 | 
|  | mov 	a,@r0		; Channel | 
|  | lcall 	readAD | 
|  | mov 	a,R3		; | 
|  | movx 	@DPTR,A | 
|  | mov 	a,R4		; | 
|  | movx 	@DPTR,A | 
|  | djnz	r1,convloop | 
|  |  | 
|  | ret | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  | ;;; initilise the transfer | 
|  | ;;; It is assumed that the USB interface is in alternate setting 3 | 
|  | initeps: | 
|  | mov	dptr,#FIFORESET | 
|  | mov	a,#80H | 
|  | movx	@dptr,a		; reset all fifos | 
|  | mov	a,#2 | 
|  | movx	@dptr,a		; | 
|  | mov	a,#4 | 
|  | movx	@dptr,a		; | 
|  | mov	a,#6 | 
|  | movx	@dptr,a		; | 
|  | mov	a,#8 | 
|  | movx	@dptr,a		; | 
|  | mov	a,#0 | 
|  | movx	@dptr,a		; normal operat | 
|  |  | 
|  | mov	DPTR,#EP2CFG | 
|  | mov	a,#10010010b	; valid, out, double buff, iso | 
|  | movx	@DPTR,a | 
|  |  | 
|  | mov	dptr,#EP2FIFOCFG | 
|  | mov	a,#00000000b	; manual | 
|  | movx	@dptr,a | 
|  |  | 
|  | mov	dptr,#EP2BCL	; "arm" it | 
|  | mov	a,#00h | 
|  | movx	@DPTR,a		; can receive data | 
|  | lcall	syncdelay	; wait to sync | 
|  | movx	@DPTR,a		; can receive data | 
|  | lcall	syncdelay	; wait to sync | 
|  | movx	@DPTR,a		; can receive data | 
|  | lcall	syncdelay	; wait to sync | 
|  |  | 
|  | mov	DPTR,#EP1OUTCFG | 
|  | mov	a,#10100000b	; valid | 
|  | movx	@dptr,a | 
|  |  | 
|  | mov	dptr,#EP1OUTBC	; "arm" it | 
|  | mov	a,#00h | 
|  | movx	@DPTR,a		; can receive data | 
|  | lcall	syncdelay	; wait until we can write again | 
|  | movx	@dptr,a		; make shure its really empty | 
|  | lcall	syncdelay	; wait | 
|  |  | 
|  | mov	DPTR,#EP6CFG	; ISO data from here to the host | 
|  | mov	a,#11010010b	; Valid | 
|  | movx	@DPTR,a		; ISO transfer, double buffering | 
|  |  | 
|  | mov	DPTR,#EP8CFG	; EP8 | 
|  | mov	a,#11100000b	; BULK data from here to the host | 
|  | movx	@DPTR,a		; | 
|  |  | 
|  | mov	dptr,#EPIE	; interrupt enable | 
|  | mov	a,#10001000b	; enable irq for ep1out,8 | 
|  | movx	@dptr,a		; do it | 
|  |  | 
|  | mov	dptr,#EPIRQ	; clear IRQs | 
|  | mov	a,#10100000b | 
|  | movx	@dptr,a | 
|  |  | 
|  | ;; enable interrups | 
|  | mov     DPTR,#USBIE	; USB int enables register | 
|  | mov     a,#2            ; enables SOF (1ms/125us interrupt) | 
|  | movx    @DPTR,a         ; | 
|  |  | 
|  | mov	EIE,#00000001b	; enable INT2 in the 8051's SFR | 
|  | mov	IE,#80h		; IE, enable all interrupts | 
|  |  | 
|  | ret | 
|  |  | 
|  |  | 
|  | ;;; counter | 
|  | ;;; r0: DIOSTAT | 
|  | ;;; r1:	counter address | 
|  | ;;; r2:	up/down-mask | 
|  | ;;; r3:	reset-mask | 
|  | ;;; r4:	clock-mask | 
|  | counter: | 
|  | mov	a,IOB		; actual IOB input state | 
|  | mov	r5,a		; save in r5 | 
|  | anl	a,r3		; bit mask for reset | 
|  | jz	no_reset	; reset if one | 
|  | clr	a		; set counter to zero | 
|  | mov	@r1,a | 
|  | inc	r4 | 
|  | mov	@r1,a | 
|  | sjmp	ctr_end | 
|  | no_reset: | 
|  | mov	a,@r0		; get last state | 
|  | xrl	a,r5		; has it changed? | 
|  | anl	a,r5		; is it now on? | 
|  | anl	a,r4		; mask out the port | 
|  | jz	ctr_end		; no rising edge | 
|  | mov	a,r5		; get port B again | 
|  | anl	a,r2		; test if up or down | 
|  | jnz	ctr_up		; count up | 
|  | mov	a,@r1 | 
|  | dec	a | 
|  | mov	@r1,a | 
|  | cjne	a,#0ffh,ctr_end	; underflow? | 
|  | inc	r1		; high byte | 
|  | mov	a,@r1 | 
|  | dec	a | 
|  | mov	@r1,a | 
|  | sjmp	ctr_end | 
|  | ctr_up:				; count up | 
|  | mov	a,@r1 | 
|  | inc	a | 
|  | mov	@r1,a | 
|  | jnz	ctr_end | 
|  | inc	r1		; high byte | 
|  | mov	a,@r1 | 
|  | inc	a | 
|  | mov	@r1,a | 
|  | ctr_end: | 
|  | mov	a,r5 | 
|  | mov	@r0,a | 
|  | ret | 
|  |  | 
|  | ;;; implements two soft counters with up/down and reset | 
|  | timer0_isr: | 
|  | push	dps | 
|  | push	acc | 
|  | push	psw | 
|  | push	00h		; R0 | 
|  | push	01h		; R1 | 
|  | push	02h		; R2 | 
|  | push	03h		; R3 | 
|  | push	04h		; R4 | 
|  | push	05h		; R5 | 
|  |  | 
|  | mov	r0,#DIOSTAT0	; status of port | 
|  | mov	r1,#CTR0	; address of counter0 | 
|  | mov	a,#00000001b	; bit 0 | 
|  | mov	r4,a		; clock | 
|  | rl	a		; bit 1 | 
|  | mov	r2,a		; up/down | 
|  | rl	a		; bit 2 | 
|  | mov	r3,a		; reset mask | 
|  | lcall	counter | 
|  | inc	r0		; to DISTAT1 | 
|  | inc	r1		; to CTR1 | 
|  | inc	r1 | 
|  | mov	a,r3 | 
|  | rl	a		; bit 3 | 
|  | rl	a		; bit 4 | 
|  | mov	r4,a		; clock | 
|  | rl	a		; bit 5 | 
|  | mov	r2,a		; up/down | 
|  | rl	a		; bit 6 | 
|  | mov	r3,a		; reset | 
|  | lcall	counter | 
|  |  | 
|  | pop	05h		; R5 | 
|  | pop	04h		; R4 | 
|  | pop	03h		; R3 | 
|  | pop	02h		; R2 | 
|  | pop	01h		; R1 | 
|  | pop	00h		; R0 | 
|  | pop	psw | 
|  | pop	acc | 
|  | pop	dps | 
|  |  | 
|  | reti | 
|  |  | 
|  | ;;; interrupt-routine for SOF | 
|  | ;;; is for full speed | 
|  | sof_isr: | 
|  | push	dps | 
|  | push	dpl | 
|  | push	dph | 
|  | push	dpl1 | 
|  | push	dph1 | 
|  | push	acc | 
|  | push	psw | 
|  | push	00h		; R0 | 
|  | push	01h		; R1 | 
|  | push	02h		; R2 | 
|  | push	03h		; R3 | 
|  | push	04h		; R4 | 
|  | push	05h		; R5 | 
|  | push	06h		; R6 | 
|  | push	07h		; R7 | 
|  |  | 
|  | mov	a,EP2468STAT | 
|  | anl	a,#20H		; full? | 
|  | jnz	epfull		; EP6-buffer is full | 
|  |  | 
|  | lcall	conv_ad		; conversion | 
|  |  | 
|  | mov	DPTR,#EP6BCH	; byte count H | 
|  | mov	a,#0		; is zero | 
|  | lcall	syncdelaywr	; wait until we can write again | 
|  |  | 
|  | mov	DPTR,#EP6BCL	; byte count L | 
|  | mov	a,#10H		; is 8x word = 16 bytes | 
|  | lcall	syncdelaywr	; wait until we can write again | 
|  |  | 
|  | epfull: | 
|  | ;; do the D/A conversion | 
|  | mov	a,EP2468STAT | 
|  | anl	a,#01H		; empty | 
|  | jnz	epempty		; nothing to get | 
|  |  | 
|  | mov	dptr,#0F000H	; EP2 fifo buffer | 
|  | lcall	dalo		; conversion | 
|  |  | 
|  | mov	dptr,#EP2BCL	; "arm" it | 
|  | mov	a,#00h | 
|  | lcall	syncdelaywr	; wait for the rec to sync | 
|  | lcall	syncdelaywr	; wait for the rec to sync | 
|  |  | 
|  | epempty: | 
|  | ;; clear INT2 | 
|  | mov	a,EXIF		; FIRST clear the USB (INT2) interrupt request | 
|  | clr	acc.4 | 
|  | mov	EXIF,a		; Note: EXIF reg is not 8051 bit-addressable | 
|  |  | 
|  | mov	DPTR,#USBIRQ	; points to the SOF | 
|  | mov	a,#2		; clear the SOF | 
|  | movx	@DPTR,a | 
|  |  | 
|  | nosof: | 
|  | pop	07h | 
|  | pop	06h | 
|  | pop	05h | 
|  | pop	04h		; R4 | 
|  | pop	03h		; R3 | 
|  | pop	02h		; R2 | 
|  | pop	01h		; R1 | 
|  | pop	00h		; R0 | 
|  | pop	psw | 
|  | pop	acc | 
|  | pop	dph1 | 
|  | pop	dpl1 | 
|  | pop	dph | 
|  | pop	dpl | 
|  | pop	dps | 
|  | reti | 
|  |  | 
|  |  | 
|  | reset_ep8: | 
|  | ;; erase all data in ep8 | 
|  | mov	dptr,#FIFORESET | 
|  | mov	a,#80H		; NAK | 
|  | lcall	syncdelaywr | 
|  | mov	dptr,#FIFORESET | 
|  | mov	a,#8		; reset EP8 | 
|  | lcall	syncdelaywr | 
|  | mov	dptr,#FIFORESET | 
|  | mov	a,#0		; normal operation | 
|  | lcall	syncdelaywr | 
|  | ret | 
|  |  | 
|  |  | 
|  | reset_ep6: | 
|  | ;; throw out old data | 
|  | mov	dptr,#FIFORESET | 
|  | mov	a,#80H		; NAK | 
|  | lcall	syncdelaywr | 
|  | mov	dptr,#FIFORESET | 
|  | mov	a,#6		; reset EP6 | 
|  | lcall	syncdelaywr | 
|  | mov	dptr,#FIFORESET | 
|  | mov	a,#0		; normal operation | 
|  | lcall	syncdelaywr | 
|  | ret | 
|  |  | 
|  | ;;; interrupt-routine for ep1out | 
|  | ;;; receives the channel list and other commands | 
|  | ep1out_isr: | 
|  | push	dps | 
|  | push	dpl | 
|  | push	dph | 
|  | push	dpl1 | 
|  | push	dph1 | 
|  | push	acc | 
|  | push	psw | 
|  | push	00h		; R0 | 
|  | push	01h		; R1 | 
|  | push	02h		; R2 | 
|  | push	03h		; R3 | 
|  | push	04h		; R4 | 
|  | push	05h		; R5 | 
|  | push	06h		; R6 | 
|  | push	07h		; R7 | 
|  |  | 
|  | mov	dptr,#0E780h	; FIFO buffer of EP1OUT | 
|  | movx	a,@dptr		; get the first byte | 
|  | mov	r0,#CMD_FLAG	; pointer to the command byte | 
|  | mov 	@r0,a		; store the command byte for ep8 | 
|  |  | 
|  | mov	dptr,#ep1out_jmp; jump table for the different functions | 
|  | rl	a		; multiply by 2: sizeof sjmp | 
|  | jmp	@a+dptr		; jump to the jump table | 
|  | ;; jump table, corresponds to the command bytes defined | 
|  | ;; in usbdux.c | 
|  | ep1out_jmp: | 
|  | sjmp	storechannellist; a=0 | 
|  | sjmp	single_da	; a=1 | 
|  | sjmp	config_digital_b; a=2 | 
|  | sjmp	write_digital_b	; a=3 | 
|  | sjmp	storesglchannel	; a=4 | 
|  | sjmp	readcounter	; a=5 | 
|  | sjmp	writecounter	; a=6 | 
|  | sjmp	pwm_on		; a=7 | 
|  | sjmp	pwm_off		; a=8 | 
|  |  | 
|  | pwm_on: | 
|  | lcall	startPWM | 
|  | sjmp	over_da | 
|  |  | 
|  | pwm_off: | 
|  | lcall	stopPWM | 
|  | sjmp	over_da | 
|  |  | 
|  | ;; read the counter | 
|  | readcounter: | 
|  | lcall	reset_ep8	; reset ep8 | 
|  | lcall	ep8_ops		; fill the counter data in there | 
|  | sjmp	over_da		; jump to the end | 
|  |  | 
|  | ;; write zeroes to the counters | 
|  | writecounter: | 
|  | mov	dptr,#0e781h	; buffer | 
|  | mov	r0,#CTR0	; r0 points to counter 0 | 
|  | movx	a,@dptr		; channel number | 
|  | jz	wrctr0		; first channel | 
|  | mov	r1,a		; counter | 
|  | wrctrl: | 
|  | inc	r0		; next counter | 
|  | inc	r0		; next counter | 
|  | djnz	r1,wrctrl	; advance to the right counter | 
|  | wrctr0: | 
|  | inc	dptr		; get to the value | 
|  | movx	a,@dptr		; get value | 
|  | mov	@r0,a		; save in ctr | 
|  | inc	r0		; next byte | 
|  | inc	dptr | 
|  | movx	a,@dptr		; get value | 
|  | mov	@r0,a		; save in ctr | 
|  | sjmp	over_da		; jump to the end | 
|  |  | 
|  | storesglchannel: | 
|  | mov	r0,#SGLCHANNEL	; the conversion bytes are now stored in 80h | 
|  | mov	dptr,#0e781h	; FIFO buffer of EP1OUT | 
|  | movx	a,@dptr		; | 
|  | mov	@r0,a | 
|  |  | 
|  | lcall	reset_ep8	; reset FIFO | 
|  | ;; Save new A/D data in EP8. This is the first byte | 
|  | ;; the host will read during an INSN. If there are | 
|  | ;; more to come they will be handled by the ISR of | 
|  | ;; ep8. | 
|  | lcall	ep8_ops		; get A/D data | 
|  |  | 
|  | sjmp	over_da | 
|  |  | 
|  |  | 
|  | ;;; Channellist: | 
|  | ;;; the first byte is zero: | 
|  | ;;; we've just received the channel list | 
|  | ;;; the channel list is stored in the addresses from CHANNELLIST which | 
|  | ;;; are _only_ reachable by indirect addressing | 
|  | storechannellist: | 
|  | mov	r0,#CHANNELLIST	; the conversion bytes are now stored in 80h | 
|  | mov	r2,#9		; counter | 
|  | mov	dptr,#0e781h	; FIFO buffer of EP1OUT | 
|  | chanlloop: | 
|  | movx	a,@dptr		; | 
|  | mov	@r0,a | 
|  | inc	dptr | 
|  | inc	r0 | 
|  | djnz	r2,chanlloop | 
|  |  | 
|  | lcall	reset_ep6	; reset FIFO | 
|  |  | 
|  | ;; load new A/D data into EP6 | 
|  | ;; This must be done. Otherwise the ISR is never called. | 
|  | ;; The ISR is only called when data has _left_ the | 
|  | ;; ep buffer here it has to be refilled. | 
|  | lcall	ep6_arm		; fill with the first data byte | 
|  |  | 
|  | sjmp	over_da | 
|  |  | 
|  | ;;; Single DA conversion. The 2 bytes are in the FIFO buffer | 
|  | single_da: | 
|  | mov	dptr,#0e781h	; FIFO buffer of EP1OUT | 
|  | lcall	dalo		; conversion | 
|  | sjmp	over_da | 
|  |  | 
|  | ;;; configure the port B as input or output (bitwise) | 
|  | config_digital_b: | 
|  | mov	dptr,#0e781h	; FIFO buffer of EP1OUT | 
|  | movx	a,@dptr		; get the second byte | 
|  | mov	OEB,a		; set the output enable bits | 
|  | sjmp	over_da | 
|  |  | 
|  | ;;; Write one byte to the external digital port B | 
|  | ;;; and prepare for digital read | 
|  | write_digital_b: | 
|  | mov	dptr,#0e781h	; FIFO buffer of EP1OUT | 
|  | movx	a,@dptr		; get the second byte | 
|  | mov	OEB,a		; output enable | 
|  | inc	dptr		; next byte | 
|  | movx	a,@dptr		; bits | 
|  | mov	IOB,a		; send the byte to the I/O port | 
|  |  | 
|  | lcall	reset_ep8	; reset FIFO of ep 8 | 
|  |  | 
|  | ;; fill ep8 with new data from port B | 
|  | ;; When the host requests the data it's already there. | 
|  | ;; This must be so. Otherwise the ISR is not called. | 
|  | ;; The ISR is only called when a packet has been delivered | 
|  | ;; to the host. Thus, we need a packet here in the | 
|  | ;; first instance. | 
|  | lcall	ep8_ops		; get digital data | 
|  |  | 
|  | ;; | 
|  | ;; for all commands the same | 
|  | over_da: | 
|  | mov	dptr,#EP1OUTBC | 
|  | mov	a,#00h | 
|  | lcall	syncdelaywr	; arm | 
|  | lcall	syncdelaywr	; arm | 
|  | lcall	syncdelaywr	; arm | 
|  |  | 
|  | ;; clear INT2 | 
|  | mov	a,EXIF		; FIRST clear the USB (INT2) interrupt request | 
|  | clr	acc.4 | 
|  | mov	EXIF,a		; Note: EXIF reg is not 8051 bit-addressable | 
|  |  | 
|  | mov	DPTR,#EPIRQ	; | 
|  | mov	a,#00001000b	; clear the ep1outirq | 
|  | movx	@DPTR,a | 
|  |  | 
|  | pop	07h | 
|  | pop	06h | 
|  | pop	05h | 
|  | pop	04h		; R4 | 
|  | pop	03h		; R3 | 
|  | pop	02h		; R2 | 
|  | pop	01h		; R1 | 
|  | pop	00h		; R0 | 
|  | pop	psw | 
|  | pop	acc | 
|  | pop	dph1 | 
|  | pop	dpl1 | 
|  | pop	dph | 
|  | pop	dpl | 
|  | pop	dps | 
|  | reti | 
|  |  | 
|  |  | 
|  |  | 
|  | ;;; all channels | 
|  | dalo: | 
|  | movx	a,@dptr		; number of channels | 
|  | inc	dptr		; pointer to the first channel | 
|  | mov	r0,a		; 4 channels | 
|  | nextDA: | 
|  | movx	a,@dptr		; get the first low byte | 
|  | mov	r3,a		; store in r3 (see below) | 
|  | inc	dptr		; point to the high byte | 
|  | movx	a,@dptr		; get the high byte | 
|  | mov	r4,a		; store in r4 (for writeDA) | 
|  | inc	dptr		; point to the channel number | 
|  | movx	a,@dptr		; get the channel number | 
|  | inc	dptr		; get ready for the next channel | 
|  | lcall	writeDA		; write value to the DAC | 
|  | djnz	r0,nextDA	; next channel | 
|  | ret | 
|  |  | 
|  |  | 
|  |  | 
|  | ;;; D/A-conversion: | 
|  | ;;; control-byte in a, | 
|  | ;;; value in r3(low) and r4(high) | 
|  | writeDA:			; mask the control byte | 
|  | anl	a,#11000000b	; only the channel is left | 
|  | orl	a,#00110000b	; internal clock, bipolar mode, +/-5V | 
|  | orl	a,r4		; or the value of R4 to it | 
|  | ;; set CS to low | 
|  | clr	IOA.5		; set /CS to zero | 
|  | ;; send the first byte to the DA-converter | 
|  | mov 	R2,#8		; bit-counter | 
|  | DA1:    jnb     ACC.7,zeroda	; jump if Bit7 = 0? | 
|  | setb	IOA.2		; set the DIN bit | 
|  | sjmp	clkda		; continue with the clock | 
|  | zeroda: clr	IOA.2		; clear the DIN bit | 
|  | clkda:	setb	IOA.0		; SCLK = 1 | 
|  | clr	IOA.0		; SCLK = 0 | 
|  | rl      a               ; next Bit | 
|  | djnz    R2,DA1 | 
|  |  | 
|  |  | 
|  | ;; send the second byte to the DA-converter | 
|  | mov	a,r3		; low byte | 
|  | mov 	R2,#8		; bit-counter | 
|  | DA2:    jnb     ACC.7,zeroda2	; jump if Bit7 = 0? | 
|  | setb	IOA.2		; set the DIN bit | 
|  | sjmp	clkda2		; continue with the clock | 
|  | zeroda2:clr	IOA.2		; clear the DIN bit | 
|  | clkda2:	setb	IOA.0		; SCLK = 1 | 
|  | clr	IOA.0		; SCLK = 0 | 
|  | rl      a               ; next Bit | 
|  | djnz    R2,DA2 | 
|  | ;; | 
|  | setb	IOA.5		; set /CS to one | 
|  | ;; | 
|  | noDA:	ret | 
|  |  | 
|  |  | 
|  |  | 
|  | ;;; arm ep6 | 
|  | ep6_arm: | 
|  | lcall	conv_ad | 
|  |  | 
|  | mov	DPTR,#EP6BCH	; byte count H | 
|  | mov	a,#0		; is zero | 
|  | lcall	syncdelaywr	; wait until the length has arrived | 
|  |  | 
|  | mov	DPTR,#EP6BCL	; byte count L | 
|  | mov	a,#10H		; is one | 
|  | lcall	syncdelaywr	; wait until the length has been proc | 
|  | ret | 
|  |  | 
|  |  | 
|  |  | 
|  | ;;; converts one analog/digital channel and stores it in EP8 | 
|  | ;;; also gets the content of the digital ports B and D depending on | 
|  | ;;; the COMMAND flag | 
|  | ep8_ops: | 
|  | mov	dptr,#0fc01h	; ep8 fifo buffer | 
|  | clr	a		; high byte | 
|  | movx	@dptr,a		; set H=0 | 
|  | mov	dptr,#0fc00h	; low byte | 
|  | mov	r0,#CMD_FLAG | 
|  | mov	a,@r0 | 
|  | movx	@dptr,a		; save command byte | 
|  |  | 
|  | mov	dptr,#ep8_jmp	; jump table for the different functions | 
|  | rl	a		; multiply by 2: sizeof sjmp | 
|  | jmp	@a+dptr		; jump to the jump table | 
|  | ;; jump table, corresponds to the command bytes defined | 
|  | ;; in usbdux.c | 
|  | ep8_jmp: | 
|  | sjmp	ep8_err		; a=0, err | 
|  | sjmp	ep8_err		; a=1, err | 
|  | sjmp	ep8_err		; a=2, err | 
|  | sjmp	ep8_dio		; a=3, digital read | 
|  | sjmp	ep8_sglchannel	; a=4, analog A/D | 
|  | sjmp	ep8_readctr	; a=5, read counter | 
|  | sjmp	ep8_err		; a=6, write counter | 
|  |  | 
|  | ;; reads all counters | 
|  | ep8_readctr: | 
|  | mov	r0,#CTR0	; points to counter0 | 
|  | mov	dptr,#0fc02h	; ep8 fifo buffer | 
|  | mov	r1,#8		; transfer 4 16bit counters | 
|  | ep8_ctrlp: | 
|  | mov	a,@r0		; get the counter | 
|  | movx	@dptr,a		; save in the fifo buffer | 
|  | inc	r0		; inc pointer to the counters | 
|  | inc	dptr		; inc pointer to the fifo buffer | 
|  | djnz	r1,ep8_ctrlp	; loop until ready | 
|  |  | 
|  | sjmp	ep8_send	; send the data | 
|  |  | 
|  | ;; read one A/D channel | 
|  | ep8_sglchannel: | 
|  | mov	r0,#SGLCHANNEL	; points to the channel | 
|  | mov 	a,@r0		; Ch0 | 
|  |  | 
|  | lcall 	readAD		; start the conversion | 
|  |  | 
|  | mov 	DPTR,#0fc02h	; EP8 FIFO | 
|  | mov 	a,R3		; get low byte | 
|  | movx 	@DPTR,A		; store in FIFO | 
|  | inc	dptr		; next fifo entry | 
|  | mov 	a,R4		; get high byte | 
|  | movx 	@DPTR,A		; store in FIFO | 
|  |  | 
|  | sjmp	ep8_send	; send the data | 
|  |  | 
|  | ;; read the digital lines | 
|  | ep8_dio: | 
|  | mov 	DPTR,#0fc02h	; store the contents of port B | 
|  | mov	a,IOB		; in the next | 
|  | movx	@dptr,a		; entry of the buffer | 
|  |  | 
|  | inc	dptr | 
|  | clr	a		; high byte is zero | 
|  | movx	@dptr,a		; next byte of the EP | 
|  |  | 
|  | ep8_send: | 
|  | mov	DPTR,#EP8BCH	; byte count H | 
|  | mov	a,#0		; is zero | 
|  | lcall	syncdelaywr | 
|  |  | 
|  | mov	DPTR,#EP8BCL	; byte count L | 
|  | mov	a,#10H		; 16 bytes | 
|  | lcall	syncdelaywr	; send the data over to the host | 
|  |  | 
|  | ep8_err: | 
|  | ret | 
|  |  | 
|  |  | 
|  |  | 
|  | ;;; EP8 interrupt: gets one measurement from the AD converter and | 
|  | ;;; sends it via EP8. The channel # is stored in address 80H. | 
|  | ;;; It also gets the state of the digital registers B and D. | 
|  | ep8_isr: | 
|  | push	dps | 
|  | push	dpl | 
|  | push	dph | 
|  | push	dpl1 | 
|  | push	dph1 | 
|  | push	acc | 
|  | push	psw | 
|  | push	00h		; R0 | 
|  | push	01h		; R1 | 
|  | push	02h		; R2 | 
|  | push	03h		; R3 | 
|  | push	04h		; R4 | 
|  | push	05h		; R5 | 
|  | push	06h		; R6 | 
|  | push	07h		; R7 | 
|  |  | 
|  | lcall	ep8_ops | 
|  |  | 
|  | ;; clear INT2 | 
|  | mov	a,EXIF		; FIRST clear the USB (INT2) interrupt request | 
|  | clr	acc.4 | 
|  | mov	EXIF,a		; Note: EXIF reg is not 8051 bit-addressable | 
|  |  | 
|  | mov	DPTR,#EPIRQ	; | 
|  | mov	a,#10000000b	; clear the ep8irq | 
|  | movx	@DPTR,a | 
|  |  | 
|  | pop	07h | 
|  | pop	06h | 
|  | pop	05h | 
|  | pop	04h		; R4 | 
|  | pop	03h		; R3 | 
|  | pop	02h		; R2 | 
|  | pop	01h		; R1 | 
|  | pop	00h		; R0 | 
|  | pop	psw | 
|  | pop	acc | 
|  | pop	dph1 | 
|  | pop	dpl1 | 
|  | pop	dph | 
|  | pop	dpl | 
|  | pop	dps | 
|  | reti | 
|  |  | 
|  |  | 
|  | ;; need to delay every time the byte counters | 
|  | ;; for the EPs have been changed. | 
|  |  | 
|  | syncdelay: | 
|  | nop | 
|  | nop | 
|  | nop | 
|  | nop | 
|  | nop | 
|  | nop | 
|  | nop | 
|  | nop | 
|  | nop | 
|  | ret | 
|  |  | 
|  | syncdelaywr: | 
|  | movx	@dptr,a | 
|  | lcall	syncdelay | 
|  | ret | 
|  |  | 
|  |  | 
|  | .End | 
|  |  | 
|  |  |