; 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 ; 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