| ;   usbdux_firmware.asm | 
 | ;   Copyright (C) 2010,2015 Bernd Porr, mail@berndporr.me.uk | 
 | ;   For usbduxsigma.c 0.5+ | 
 | ; | 
 | ;   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: usbduxsigma_firmware.asm for usbduxsigma.c | 
 | ; Description: University of Stirling USB DAQ & INCITE Technology Limited | 
 | ; Devices: [ITL] USB-DUX-SIGMA (usbduxsigma.ko) | 
 | ; Author: Bernd Porr <mail@berndporr.me.uk> | 
 | ; Updated: 20 Jul 2015 | 
 | ; Status: testing | 
 | ; | 
 | ;;; | 
 | ;;; | 
 | ;;; | 
 | 	 | 
 | 	.inc	fx2-include.asm | 
 |  | 
 | ;;; a couple of flags in high memory | 
 | 	.equ	CMD_FLAG,80h	; flag for the next in transfer | 
 | 	.equ	PWMFLAG,81h	; PWM on or off? | 
 | 	.equ	MAXSMPL,82H	; maximum number of samples, n channellist | 
 | 	.equ	MUXSG0,83H	; content of the MUXSG0 register | 
 | 	.equ	INTERVAL,88h	; uframe/frame interval | 
 | 	.equ	INTCTR,89h	; interval counter | 
 | 	.equ	DABUFFER,0F0h	; buffer with DA values | 
 |  | 
 | ;;; in precious low memory but accessible within one clock cycle | 
 | 	.equ	DPTRL,70H | 
 | 	.equ	DPTRH,71h | 
 | 	.equ	ASYNC_ON,72h | 
 | 	.equ	SMPLCTR,73h | 
 |  | 
 | ;;; actual code | 
 | 	.org	0000h		; after reset the processor starts here | 
 | 	ljmp	main		; jump to the main loop | 
 |  | 
 | 	.org	0003h | 
 | 	ljmp	isr0		; external interrupt 0: /DRY | 
 |  | 
 | 	.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:	 | 
 | 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 | 
 |  | 
 |  | 
 | ep1in_isr:	 | 
 | 	push	dps | 
 | 	push	dpl | 
 | 	push	dph | 
 | 	push	dpl1 | 
 | 	push	dph1 | 
 | 	push	acc | 
 | 	push	psw | 
 | 		 | 
 | 	mov	dptr,#0E7C0h	; EP1in | 
 | 	mov	a,IOB		; get DIO D | 
 | 	movx	@dptr,a		; store it | 
 | 	inc	dptr		; next byte | 
 | 	mov	a,IOC		; get DIO C | 
 | 	movx	@dptr,a		; store it | 
 | 	inc	dptr		; next byte | 
 | 	mov	a,IOD		; get DIO B | 
 | 	movx	@dptr,a		; store it | 
 | 	inc	dptr		; next byte | 
 | 	mov	a,#0		; just zero | 
 | 	movx	@dptr,a		; pad it up | 
 |  | 
 | 	;; 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,#00000100b	; clear the ep1in | 
 | 	movx	@DPTR,a | 
 |  | 
 | 	pop	psw | 
 | 	pop	acc  | 
 | 	pop	dph1  | 
 | 	pop	dpl1 | 
 | 	pop	dph  | 
 | 	pop	dpl  | 
 | 	pop	dps | 
 | 	reti | 
 |  | 
 |  | 
 |  | 
 | ;;; this is triggered when DRY goes low | 
 | isr0:	 | 
 | 	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,ASYNC_ON | 
 | 	jz	noepsubmit | 
 |  | 
 | 	mov	DPS,#0 | 
 | 	mov	dpl,DPTRL | 
 | 	mov	dph,DPTRH | 
 |  | 
 | 	lcall	readADCch	; read one channel | 
 |  | 
 | 	mov	DPTRL,dpl | 
 | 	mov	DPTRH,dph | 
 |  | 
 | 	mov	a,SMPLCTR | 
 | 	dec	a | 
 | 	mov	SMPLCTR,a | 
 | 	jnz	noepsubmit | 
 |  | 
 | 	mov	ASYNC_ON,#0 | 
 |  | 
 | 	clr	IOA.7		; START = 0 | 
 | 	 | 
 | 	;; arm the endpoint and send off the data | 
 | 	mov	DPTR,#EP6BCH	; byte count H | 
 | 	mov	a,#0		; is zero | 
 | 	lcall	syncdelaywr	; wait until we can write again | 
 | 	 | 
 | 	mov	r0,#MAXSMPL	; number of samples to transmit | 
 | 	mov	a,@r0		; get them | 
 | 	rl	a		; a=a*2 | 
 | 	rl	a		; a=a*2 | 
 | 	add	a,#4		; four bytes for DIO | 
 | 	mov	DPTR,#EP6BCL	; byte count L | 
 | 	lcall	syncdelaywr	; wait until we can write again | 
 |  | 
 | noepsubmit: | 
 | 	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 | 
 |  | 
 | 	 | 
 | 		 | 
 | ;;; 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	dptr,#INTSETUP	; IRQ setup register | 
 | 	mov	a,#08h		; enable autovector | 
 | 	lcall	syncdelaywr | 
 |  | 
 | 	mov	dptr,#PORTCCFG | 
 | 	mov	a,#0 | 
 | 	lcall	syncdelaywr | 
 |  | 
 | 	mov	IP,#01H		; int0 has highest interrupt priority | 
 | 	mov	EIP,#0		; all USB interrupts have low priority | 
 |  | 
 | 	lcall	initAD		; init the ports to the converters | 
 |  | 
 | 	lcall	initeps		; init the isochronous data-transfer | 
 |  | 
 | ;;; main loop, rest is done as interrupts | 
 | 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 | 
 |  | 
 |  | 
 | ;;; initialise the ports for the AD-converter | 
 | initAD: | 
 | 	mov	r0,#MAXSMPL	; length of channellist | 
 | 	mov	@r0,#0		; we don't want to accumlate samples | 
 |  | 
 | 	mov	ASYNC_ON,#0	; async enable | 
 |  | 
 | 	mov	r0,#DABUFFER | 
 | 	mov	@r0,#0 | 
 |  | 
 | 	mov	OEA,#11100000b	; PortA7,A6,A5 Outputs | 
 | 	mov	IOA,#01100000b	; /CS = 1 and START = 0 | 
 | 	mov	dptr,#IFCONFIG	; switch on clock on IFCLK pin | 
 | 	mov	a,#10100000b	; gpif, 30MHz, internal IFCLK -> 15MHz for AD | 
 | 	lcall	syncdelaywr | 
 |  | 
 | 	mov	SCON0,#013H	; ser rec en, TX/RX: stop, 48/12MHz=4MHz clock | 
 | 	 | 
 | 	mov	dptr,#PORTECFG | 
 | 	mov	a,#00001000b	; special function for port E: RXD0OUT | 
 | 	lcall	syncdelaywr | 
 |  | 
 | 	ret | 
 |  | 
 |  | 
 | ;;; send a byte via SPI | 
 | ;;; content in a, dptr1 is changed | 
 | ;;; the lookup is done in dptr1 so that the normal dptr is not affected | 
 | ;;; important: /cs needs to be reset to 1 by the caller: IOA.5 | 
 | sendSPI: | 
 | 	inc	DPS | 
 | 	 | 
 | 	;; bit reverse | 
 | 	mov	dptr,#swap_lut	; lookup table | 
 | 	movc 	a,@a+dptr	; reverse bits | 
 |  | 
 | 	;; clear interrupt flag, is used to detect | 
 | 	;; successful transmission | 
 | 	clr	SCON0.1		; clear interrupt flag | 
 |  | 
 | 	;; start transmission by writing the byte | 
 | 	;; in the transmit buffer | 
 | 	mov	SBUF0,a		; start transmission | 
 |  | 
 | 	;; wait for the end of the transmission | 
 | sendSPIwait: | 
 | 	mov	a,SCON0		; get transmission status | 
 | 	jnb     ACC.1,sendSPIwait	; loop until transmitted | 
 |  | 
 | 	inc	DPS | 
 | 	 | 
 | 	ret | 
 |  | 
 |  | 
 |  | 
 | 	 | 
 | ;;; receive a byte via SPI | 
 | ;;; content in a, dptr is changed | 
 | ;;; the lookup is done in dptr1 so that the normal dptr is not affected | 
 | ;;; important: the /CS needs to be set to 1 by the caller via "setb IOA.5" | 
 | recSPI: | 
 | 	inc	DPS | 
 | 	 | 
 | 	clr	IOA.5		; /cs to 0	 | 
 |  | 
 | 	;; clearning the RI bit starts reception of data | 
 | 	clr	SCON0.0 | 
 |  | 
 | recSPIwait: | 
 | 	;; RI goes back to 1 after the reception of the 8 bits | 
 | 	mov	a,SCON0		; get receive status | 
 | 	jnb	ACC.0,recSPIwait; loop until all bits received | 
 |  | 
 | 	;; read the byte from the buffer | 
 | 	mov	a,SBUF0		; get byte | 
 | 	 | 
 | 	;; lookup: reverse the bits | 
 | 	mov	dptr,#swap_lut	; lookup table | 
 | 	movc 	a,@a+dptr	; reverse the bits | 
 |  | 
 | 	inc	DPS | 
 | 	 | 
 | 	ret | 
 |  | 
 |  | 
 |  | 
 | 	 | 
 | ;;; reads a register | 
 | ;;; register address in a | 
 | ;;; returns value in a | 
 | registerRead: | 
 | 	anl	a,#00001111b	; mask out the index to the register | 
 | 	orl	a,#01000000b	; 010xxxxx indicates register read | 
 | 	clr	IOA.5		; ADC /cs to 0 | 
 | 	lcall	sendSPI		; send the command over | 
 | 	lcall	recSPI		; read the contents back | 
 | 	setb	IOA.5		; ADC /cs to 1 | 
 | 	ret | 
 |  | 
 |  | 
 |  | 
 | ;;; writes to a register | 
 | ;;; register address in a | 
 | ;;; value in r0 | 
 | registerWrite: | 
 | 	push	acc | 
 | 	anl	a,#00001111b	; mask out the index to the register | 
 | 	orl	a,#01100000b	; 011xxxxx indicates register write | 
 |  | 
 | 	clr	IOA.5		; ADC /cs to 0	 | 
 |  | 
 | 	lcall	sendSPI		; | 
 | 	mov	a,r0 | 
 | 	lcall	sendSPI | 
 |  | 
 | 	setb	IOA.5		; ADC /cs to 1 | 
 | 	pop	acc | 
 |  | 
 | 	lcall	registerRead	; check if the data has arrived in the ADC | 
 | 	mov	0f0h,r0		; register B | 
 | 	cjne	a,0f0h,registerWrite ; something went wrong, try again | 
 | 	 | 
 | 	ret | 
 |  | 
 |  | 
 |  | 
 | ;;; initilise the endpoints | 
 | 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,#PORTACFG | 
 | 	mov	a,#1		; interrupt on pin A0 | 
 | 	lcall	syncdelaywr | 
 |  | 
 | 	;; enable interrupts | 
 | 	mov	dptr,#EPIE	; interrupt enable | 
 | 	mov	a,#10001100b	; enable irq for ep1out,8,ep1in | 
 | 	movx	@dptr,a		; do it | 
 |  | 
 | 	mov	dptr,#EPIRQ	; clear IRQs | 
 | 	mov	a,#10001100b | 
 | 	movx	@dptr,a | 
 | 	 | 
 |         mov     DPTR,#USBIE	; USB int enables register | 
 |         mov     a,#2            ; enables SOF (1ms/125us interrupt) | 
 |         movx    @DPTR,a         ;  | 
 |  | 
 | 	setb	TCON.0		; make INT0 edge triggered, falling edge | 
 |  | 
 | 	mov	EIE,#00000001b	; enable INT2/USBINT in the 8051's SFR | 
 | 	mov	IE,#81h		; IE, enable all interrupts and INT0 | 
 |  | 
 | 	ret | 
 |  | 
 |  | 
 | ;;; Reads one ADC channel from the converter and stores | 
 | ;;; the result at dptr | 
 | readADCch: | 
 | 	;; reading data is done by just dropping /CS and start reading and | 
 | 	;; while keeping the IN signal to the ADC inactive | 
 | 	clr	IOA.5		; /cs to 0 | 
 | 	 | 
 | 	;; 1st byte: STATUS | 
 | 	lcall	recSPI		; index | 
 | 	movx	@dptr,a		; store the byte | 
 | 	inc	dptr		; increment pointer | 
 |  | 
 | 	;; 2nd byte: MSB | 
 | 	lcall	recSPI		; data | 
 | 	movx	@dptr,a | 
 | 	inc	dptr | 
 |  | 
 | 	;; 3rd byte: MSB-1 | 
 | 	lcall	recSPI		; data | 
 | 	movx	@dptr,a | 
 | 	inc	dptr | 
 |  | 
 | 	;; 4th byte: LSB | 
 | 	lcall	recSPI		; data | 
 | 	movx	@dptr,a | 
 | 	inc	dptr | 
 | 	 | 
 | 	;; got all bytes | 
 | 	setb	IOA.5		; /cs to 1 | 
 | 	 | 
 | 	ret | 
 |  | 
 | 	 | 
 |  | 
 | ;;; interrupt-routine for SOF | 
 | 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	r0,#INTCTR	; interval counter | 
 | 	mov	a,@r0		; get the value | 
 | 	dec	a		; decrement | 
 | 	mov	@r0,a		; save it again | 
 | 	jz	sof_adc		; we do ADC functions | 
 | 	ljmp	epfull		; we skip all adc functions | 
 | 	 | 
 | sof_adc: | 
 | 	mov	r1,#INTERVAL	; get the interval | 
 | 	mov	a,@r1		; get it | 
 | 	mov	@r0,a		; save it in the counter | 
 | 	mov	a,EP2468STAT | 
 | 	anl	a,#20H		; full? | 
 | 	jnz	epfull		; EP6-buffer is full | 
 |  | 
 | 	mov	a,IOA		; conversion running? | 
 | 	jb	ACC.7,epfull | 
 |  | 
 | 	;; make sure that we are starting with the first channel | 
 | 	mov	r0,#MUXSG0	; | 
 | 	mov	a,@r0		; get config of MUXSG0 | 
 | 	mov	r0,a | 
 | 	mov	a,#04H		; MUXSG0 | 
 | 	lcall	registerWrite	; this resets the channel sequence | 
 |  | 
 | 	setb	IOA.7		; start converter, START = 1 | 
 | 	 | 
 | 	mov	dptr,#0f800h	; EP6 buffer | 
 | 	mov	a,IOD		; get DIO D | 
 | 	movx	@dptr,a		; store it | 
 | 	inc	dptr		; next byte | 
 | 	mov	a,IOC		; get DIO C | 
 | 	movx	@dptr,a		; store it | 
 | 	inc	dptr		; next byte | 
 | 	mov	a,IOB		; get DIO B | 
 | 	movx	@dptr,a		; store it | 
 | 	inc	dptr		; next byte | 
 | 	mov	a,#0		; just zero | 
 | 	movx	@dptr,a		; pad it up | 
 | 	inc	dptr		; algin along a 32 bit word | 
 | 	mov	DPTRL,dpl | 
 | 	mov	DPTRH,dph | 
 |  | 
 | 	mov	r0,#MAXSMPL | 
 | 	mov	a,@r0 | 
 | 	mov	SMPLCTR,a | 
 |  | 
 | 	mov	ASYNC_ON,#1 | 
 |  | 
 | 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: | 
 | 	mov	a,IOA		; conversion running? | 
 | 	jb	ACC.7,sofend | 
 |  | 
 | 	lcall	DAsend | 
 |  | 
 | sofend:	 | 
 | 	;; 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 | 
 |  | 
 |  | 
 | ;;; configure the ADC converter | 
 | ;;; the dptr points to the init data: | 
 | ;;; CONFIG 0,1,3,4,5,6 | 
 | ;;; note that CONFIG2 is omitted | 
 | configADC:	 | 
 | 	clr	IOA.7		; stops ADC: START line of ADC = L | 
 | 	setb	IOA.5		; ADC /cs to 1 | 
 |  | 
 | 	;; just in case something has gone wrong | 
 | 	nop | 
 | 	nop | 
 | 	nop | 
 |  | 
 | 	mov	a,#11000000b	; reset	the ADC | 
 | 	clr	IOA.5		; ADC /cs to 0	 | 
 | 	lcall	sendSPI | 
 | 	setb	IOA.5		; ADC /cs to 1	 | 
 |  | 
 | 	movx	a,@dptr		; | 
 | 	inc	dptr | 
 | 	mov	r0,a | 
 | 	mov	a,#00H		; CONFIG0 | 
 | 	lcall	registerWrite | 
 |  | 
 | 	movx	a,@dptr		; | 
 | 	inc	dptr | 
 | 	mov	r0,a | 
 | 	mov	a,#01H		; CONFIG1 | 
 | 	lcall	registerWrite | 
 |  | 
 | 	movx	a,@dptr		; | 
 | 	inc	dptr | 
 | 	mov	r0,a | 
 | 	mov	a,#03H		; MUXDIF | 
 | 	lcall	registerWrite | 
 |  | 
 | 	movx	a,@dptr		; | 
 | 	inc	dptr | 
 | 	mov	r0,#MUXSG0 | 
 | 	mov	@r0,a		; store it for reset purposes | 
 | 	mov	r0,a | 
 | 	mov	a,#04H		; MUXSG0 | 
 | 	lcall	registerWrite | 
 | 	 | 
 | 	movx	a,@dptr		; | 
 | 	inc	dptr | 
 | 	mov	r0,a | 
 | 	mov	a,#05H		; MUXSG1 | 
 | 	lcall	registerWrite | 
 | 	 | 
 | 	movx	a,@dptr		; | 
 | 	inc	dptr | 
 | 	mov	r0,a | 
 | 	mov	a,#06H		; SYSRED | 
 | 	lcall	registerWrite | 
 |  | 
 | 	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	startadc	; a=0 | 
 | 	sjmp	single_da	; a=1 | 
 | 	sjmp	config_digital_b; a=2 | 
 | 	sjmp	write_digital_b	; a=3 | 
 | 	sjmp	initsgADchannel	; a=4 | 
 | 	sjmp	nothing		; a=5 | 
 | 	sjmp	nothing		; a=6 | 
 | 	sjmp	pwm_on		; a=7 | 
 | 	sjmp	pwm_off		; a=8 | 
 | 	sjmp	startadcint	; a=9 | 
 |  | 
 | nothing: | 
 | 	ljmp	over_da | 
 |  | 
 | pwm_on: | 
 | 	lcall	startPWM | 
 | 	sjmp	over_da | 
 |  | 
 | pwm_off: | 
 | 	lcall	stopPWM | 
 | 	sjmp	over_da | 
 |  | 
 | initsgADchannel: | 
 | 	mov	ASYNC_ON,#0 | 
 | 	 | 
 | 	mov	dptr,#0e781h	; FIFO buffer of EP1OUT | 
 | 	lcall	configADC	; configures the ADC esp sel the channel | 
 |  | 
 | 	lcall	reset_ep8	; reset FIFO: get rid of old bytes | 
 | 	;; 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 | 
 |  | 
 | startadcint: | 
 | 	mov	dptr,#0e781h	; FIFO buffer of EP1OUT from 2nd byte | 
 |  | 
 | 	movx	a,@dptr		; interval is the 1st byte | 
 | 	inc	dptr		; data pointer | 
 | 	sjmp	startadc2	; the other paramters as with startadc | 
 | 	 | 
 | ;;; config AD: | 
 | ;;; we write to the registers of the A/D converter | 
 | startadc: | 
 | 	mov	dptr,#0e781h	; FIFO buffer of EP1OUT from 2nd byte | 
 |  | 
 | 	mov	a,#1		; interval is 1 here all the time | 
 | startadc2:	 | 
 | 	mov	r0,#INTERVAL	; set it | 
 | 	mov	@r0,a | 
 | 	mov	r0,#INTCTR	; the counter is also just one | 
 | 	mov	@r0,a | 
 |  | 
 | 	movx	a,@dptr		; get length of channel list | 
 | 	inc	dptr | 
 | 	mov	r0,#MAXSMPL | 
 | 	mov	@r0,a 		; length of the channel list | 
 | 	mov	SMPLCTR,a | 
 |  | 
 | 	lcall	configADC	; configures all registers | 
 |  | 
 | 	mov	ASYNC_ON,#1	; async enable | 
 |  | 
 | 	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 dummy data | 
 | 	 | 
 | 	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 | 
 | 	inc	dptr | 
 | 	mov	OEB,a		; set the output enable bits | 
 | 	movx	a,@dptr		; get the second byte | 
 | 	inc	dptr | 
 | 	mov	OEC,a | 
 | 	movx	a,@dptr		; get the second byte | 
 | 	inc	dptr | 
 | 	mov	OED,a | 
 | 	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		; command[1] | 
 | 	inc	dptr | 
 | 	mov	OEB,a		; output enable | 
 | 	movx	a,@dptr		; command[2] | 
 | 	inc	dptr | 
 | 	mov	OEC,a | 
 | 	movx	a,@dptr		; command[3] | 
 | 	inc	dptr | 
 | 	mov	OED,a  | 
 | 	movx	a,@dptr		; command[4] | 
 | 	inc	dptr | 
 | 	mov	IOB,a		; | 
 | 	movx	a,@dptr		; command[5] | 
 | 	inc	dptr | 
 | 	mov	IOC,a | 
 | 	movx	a,@dptr		; command[6] | 
 | 	inc	dptr | 
 | 	mov	IOD,a | 
 |  | 
 | 	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 | 
 |  | 
 |  | 
 | 	 | 
 | ;;; save all DA channels from the endpoint buffer in a local buffer | 
 | dalo: | 
 | 	movx	a,@dptr		; number of bytes to send out | 
 | 	inc	dptr		; pointer to the first byte | 
 | 	mov	r1,#DABUFFER	; buffer for DA values | 
 | 	mov	@r1,a		; save it | 
 | 	inc	r1		; inc pointer to local buffer | 
 | 	mov	r0,a		; counter | 
 | nextDAlo:	 | 
 | 	movx	a,@dptr		; get the byte | 
 | 	inc	dptr		; point to the high byte | 
 | 	mov	@r1,a		; save it in the buffer | 
 | 	inc	r1 | 
 | 	movx	a,@dptr		; get the channel number | 
 | 	inc	dptr		; get ready for the next channel | 
 | 	mov	@r1,a		; save it | 
 | 	inc	r1 | 
 | 	djnz	r0,nextDAlo	; next channel | 
 | 	ret | 
 |  | 
 |  | 
 | ;;; write to the DA converter | 
 | DAsend: | 
 | 	mov	r1,#DABUFFER	; buffer of the DA values | 
 | 	mov	a,@r1		; get the channel count | 
 | 	jz	DAret		; nothing to do | 
 | 	inc	r1		; pointer to the first byte | 
 | 	mov	r0,a		; counter | 
 | nextDA:	 | 
 | 	mov	a,@r1		; get the byte | 
 | 	inc	r1		; point to the high byte | 
 | 	mov	r3,a		; store in r3 for writeDA | 
 | 	mov	a,@r1		; get the channel number | 
 | 	inc	r1		; get ready for the next channel | 
 | 	push	1		; is modified in the subroutine | 
 | 	lcall	writeDA		; write value to the DAC | 
 | 	pop	1		; get the pointer back | 
 | 	djnz	r0,nextDA	; next channel | 
 | DAret:	 | 
 | 	ret | 
 |  | 
 |  | 
 |  | 
 | ;;; D/A-conversion: | 
 | ;;; channel number in a | 
 | ;;; value in r3 | 
 | writeDA: | 
 | 	anl	a,#00000011b	; 4 channels | 
 | 	mov	r1,#6		; the channel number needs to be shifted up | 
 | writeDA2: | 
 | 	rl	a		; bit shift to the left | 
 | 	djnz	r1,writeDA2	; do it 6 times | 
 | 	orl	a,#00010000b	; update outputs after write | 
 | 	mov	r2,a		; backup | 
 | 	mov	a,r3		; get byte | 
 | 	anl	a,#11110000b	; get the upper nibble | 
 | 	mov	r1,#4		; shift it up to the upper nibble | 
 | writeDA3: | 
 | 	rr	a		; shift to the upper to the lower | 
 | 	djnz	r1,writeDA3 | 
 | 	orl	a,r2		; merge with the channel info | 
 | 	clr	IOA.6		; /SYNC (/CS) of the DA to 0 | 
 | 	lcall	sendSPI		; send it out to the SPI | 
 | 	mov	a,r3		; get data again | 
 | 	anl	a,#00001111b	; get the lower nibble | 
 | 	mov	r1,#4		; shift that to the upper | 
 | writeDA4: | 
 | 	rl	a | 
 | 	djnz	r1,writeDA4 | 
 | 	anl	a,#11110000b	; make sure that's empty | 
 | 	lcall	sendSPI | 
 | 	setb	IOA.6		; /SYNC (/CS) of the DA to 1 | 
 | noDA:	ret | 
 | 	 | 
 |  | 
 |  | 
 | ;;; arm ep6: this is just a dummy arm to get things going | 
 | ep6_arm: | 
 | 	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,#1		; 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,C 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_err		; a=5, err | 
 | 	sjmp	ep8_err		; a=6, err | 
 |  | 
 | 	;; read one A/D channel | 
 | ep8_sglchannel: | 
 | 	setb	IOA.7		; start converter, START = 1 | 
 | 	;; we do polling: we wait until DATA READY is zero | 
 | sglchwait:	 | 
 | 	mov	a,IOA		; get /DRDY | 
 | 	jb	ACC.0,sglchwait	; wait until data ready (DRDY=0) | 
 | 	mov 	DPTR,#0fc01h	; EP8 FIFO | 
 | 	lcall	readADCch	; get one reading | 
 | 	clr	IOA.7		; stop the converter, START = 0 | 
 |  | 
 | 	sjmp	ep8_send	; send the data | 
 |  | 
 | 	;; read the digital lines | 
 | ep8_dio:	 | 
 | 	mov 	DPTR,#0fc01h	; store the contents of port B | 
 | 	mov	a,IOB		; in the next | 
 | 	movx	@dptr,a		; entry of the buffer | 
 | 	inc	dptr | 
 | 	mov	a,IOC		; port C | 
 | 	movx	@dptr,a		; next byte of the EP | 
 | 	inc	dptr | 
 | 	mov	a,IOD | 
 | 	movx	@dptr,a		; port D | 
 | 	 | 
 | 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, bec it's such a great number... | 
 | 	lcall	syncdelaywr	; send the data over to the host | 
 |  | 
 | ep8_err:	 | 
 | 	ret | 
 |  | 
 |  | 
 |  | 
 | ;;; EP8 interrupt is the endpoint which sends data back after a command | 
 | ;;; The actual command fills the EP buffer already | 
 | ;;; but for INSNs we need to deliver more data if the count > 1 | 
 | 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 | 
 |  | 
 |  | 
 |  | 
 | ;;; 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,#10100000b	; gpif, 30MHz, internal IFCLK | 
 | 	lcall	syncdelaywr | 
 | 	ret | 
 | 	 | 
 |  | 
 | ;;; init PWM | 
 | startPWM: | 
 | 	mov	dptr,#IFCONFIG	; switch on IFCLK signal | 
 | 	mov	a,#10100010b	; 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 | 
 |  | 
 | 	 | 
 |  | 
 | ;; 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 | 
 |  | 
 |  | 
 |  | 
 | 	.org	1F00h		; lookup table at the end of memory | 
 |  | 
 | swap_lut: | 
 | .db 0,128,64,192,32,160,96,224,16,144,80,208,48,176,112,240,8,136 | 
 | .db 72,200,40,168,104,232,24,152,88,216,56,184,120,248,4,132,68,196,36,164,100 | 
 | .db 228,20,148,84,212,52,180,116,244,12,140,76,204,44,172,108,236,28,156,92,220 | 
 | .db 60,188,124,252,2,130,66,194,34,162,98,226,18,146,82,210,50,178,114,242,10 | 
 | .db 138,74,202,42,170,106,234,26,154,90,218,58,186,122,250,6,134,70,198,38,166 | 
 | .db 102,230,22,150,86,214,54,182,118,246,14,142,78,206,46,174,110,238,30,158,94 | 
 | .db 222,62,190,126,254,1,129,65,193,33,161,97,225,17,145,81,209,49,177,113,241,9 | 
 | .db 137,73,201,41,169,105,233,25,153,89,217,57,185,121,249,5,133,69,197,37,165 | 
 | .db 101,229,21,149,85,213,53,181,117,245,13,141,77,205,45,173,109,237,29,157,93 | 
 | .db 221,61,189,125,253,3,131,67,195,35,163,99,227,19,147,83,211,51,179,115,243,11 | 
 | .db 139,75,203,43,171,107,235,27,155,91,219,59,187,123,251,7,135,71,199,39,167 | 
 | .db 103,231,23,151,87,215,55,183,119,247,15,143,79,207,47,175,111,239,31,159,95 | 
 | .db 223,63,191,127,255 | 
 |  | 
 |  | 
 |  | 
 | 	 | 
 | .End | 
 |  | 
 |  |