blob: 38f5f0e84008d76628bb90a65afee8b1385680c9 [file] [log] [blame]
; 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