; ; Bret Victor: bret@ugcs.caltech.edu ; GRASP lab: University of Pennsylvania ; August, 2000 ; ; Data collection unit controller for DAX rotation sensor ; Source code for SX18AC microcontroller ; version 1.0 ; ; Port map: Port A: bit 3: output: gyro RS232 xmit ; bit 2: input: gyro RS232 receive ; bit 1: output: host RS232 xmit ; bit 0: input: host RS232 receive ; Port B: bit 7: output: EEPROM 3 /CS ; bit 6: output: EEPROM 2 /CS ; bit 5: output: EEPROM 1 /CS ; bit 4: output: EEPROM SCLK ; bit 3: output: EEPROM SI ; bit 2: input: EEPROM SO ; bit 1: output: LED (active low) ; bit 0: input: button (active low) ; ; Comment out the following line to assemble with SASM instead of SX-Key SX_KEY ;;;;;;;;;;;;;;;;; ;; ;; device options ;; IFDEF SX_KEY device sx18l,turbo,stackx_optionx freq 22_118_000 ELSE device pins18,banks8,oschs3,turbo,optionx ENDIF id 'DCU v1.0' reset ResetEntry ;;;;;;;;;;;;;;; ;; ;; port mapping ;; serialg_Tx equ ra.3 ; output: gyro RS232 xmit serialg_Rx equ ra.2 ; input: gyro RS232 receive serialh_Tx equ ra.1 ; output: host RS232 xmit serialh_Rx equ ra.0 ; input: host RS232 receive eeprom_cs3 equ rb.7 ; output: eeprom #3 chip-select eeprom_cs2 equ rb.6 ; output: eeprom #2 chip-select eeprom_cs1 equ rb.5 ; output: eeprom #1 chip-select eeprom_sclk equ rb.4 ; output: eeprom serial clock eeprom_si equ rb.3 ; output: eeprom serial input eeprom_so equ rb.2 ; input: eeprom serial output led equ rb.1 ; output: pretty light (active low) button equ rb.0 ; input: pretty button (active low) eeprom_cs_mask equ %11100000 led_mask equ %00000010 serialg_Tx_mask equ %1000 serialg_Rx_mask equ %0100 serialh_Tx_mask equ %0010 serialh_Rx_mask equ %0001 PortA_ResetValue equ %0000 PortAdir_ResetValue equ %0101 PortB_ResetValue equ %11100010 PortBdir_ResetValue equ %00000101 ;;;;;;;;;;;;;;;;; ;; ;; memory mapping ;; InterruptPage equ $0 ; interrupt handler, reset entry, and event loop UiPage equ $2 ; serial, UI, and everything else routines ;;;;;;;;;; ;; ;; equates ;; VersionMajor equ $01 ; major version VersionMinor equ $00 ; minor version InterruptRate equ 144 ; 22.118 Mhz clock with 1/2 prescalar: ; interrupt every 13 us, or 8 x 9600 Khz SerialDivider equ 8 ; every 8 interrupts, send a bit SerialDivider2 equ 12 ; 1.5 x SerialDivider EventLoopDelay equ 6 ; time between samples, divided by 3.3 ms UiAck equ $80 ; code for generic OK in non-verbose mode UiNak equ $f0 ; code for unrecognized command in non-verbose mode DcuPrefGyro equ $01 ; user preference bits for gyro mode DcuPrefInt equ $02 ; user preference bits for rotation mode DcuMaxAddrHi equ $2f ; hibyte of the maximum memory address EepromRead equ $03 ; eeprom instruction code to read data EepromWrite equ $02 ; eeprom instruction code to write data EepromEnablewrite equ $06 ; eeprom instruction code to set write enable latch EepromReadstatus equ $05 ; eeprom_instruction code to read status register GyrocmdAutoSpeed0 equ '0' ; commands to the gyro boards GyrocmdAutoSpeed1 equ '1' GyrocmdAutoRead1 equ 'A' GyrocmdAutoRead2 equ 'a' GyrocmdZero1 equ 'Z' GyrocmdZero2 equ 'z' GyrocmdQuiet1 equ 'Q' GyrocmdQuiet2 equ 'q' GyrocmdNoVerbose equ 'v' GyrocmdCalGyro1 equ 'G' GyrocmdCalGyro2 equ 'g' GyrocmdCalInt1 equ 'I' GyrocmdCalInt2 equ 'i' GyrorespAck equ $80 ; response codes from the gyro board GyrorespAck1 equ $81 GyrorespAck2 equ $82 GyrorespChannel1 equ $01 GyrorespChannel2 equ $02 ButtonDebounceCount equ 5 ; minimum amount of time button must be held down LedFlashDelay equ 20 ; amount of time (x3.3ms) between toggling light ;;;;;;;;;;;; ;; ;; variables ;; ; globals org $8 stashi ds 1 ; stash variable for interrupt routine stash1 ds 1 ; stash variable for main code (higher level) stash2 ds 1 ; stash variable for main code (subroutines) statusbits ds 1 ; global status bits mastercounter ds 1 ; increments every 13 us eventloop_timer ds 1 dcu_readrecord ds 1 ; bank 1 org $10 BankSerial = $ serial_rxgetptr ds 1 ; offset in buffer to read the next byte serial_rxputptr ds 1 ; offset in buffer to put the next byte serial_rxlength ds 1 ; how many bytes are in the buffer serial_txgetptr ds 1 ; offset in buffer to read the next byte serial_txputptr ds 1 ; offset in buffer to put the next byte serial_txlength ds 1 ; how many bytes are in the buffer serial_txbyte ds 1 ; the byte that is being transmitted serial_txcount ds 1 ; how many bits we have left to transmit serial_txdivide ds 1 ; how many more interrupt periods to wait serial_rxbyte ds 1 ; the byte that is being received serial_rxcount ds 1 ; how many bits we have left to receive serial_rxdivide ds 1 ; how many more interrupt periods to wait serial_rxmask ds 1 serial_txmask ds 1 serial_stash ds 1 ; stash register for calculations serial_stash2 ds 1 ; stash register for calculations ; bank 3 org $30 BankRxBuf = $ ; serial receive buffer ; bank 5 org $50 BankTxBuf = $ ; serial transmit buffer ; bank 7 org $70 BankStream = $ BankButton = $ BankUI = $ stream_addresslo ds 1 ; lobyte of eeprom address to read or write stream_addresshi ds 1 ; hibyte of eeprom address to read or write stream_startlo ds 1 ; lobyte of record starting address stream_starthi ds 1 ; hibyte of record starting address stream_lengthlo ds 1 ; lobyte of bytes written or bytes left to read stream_lengthhi ds 1 ; hibyte of bytes written or bytes left to read stream_bufpointer ds 1 ; where the next byte will be written in buffer stream_flushptr ds 1 ; what buffer data has not been flushed button_debounce ds 1 ; counter for button debouncing ui_stash ds 1 ; just a stash ; bank 9 org $90 BankStreamBuf = $ stream_buffer ds 16 ; buffer for writing to the stream ;;;;;;;;;;;;;; ;; ;; status bits ;; ; global status bits serialTxActive equ statusbits.0 ; true if a byte is being transmitted serialRxActive equ statusbits.1 ; true if a byte is being received serialRxAvail equ statusbits.2 ; true if the receive buf is not empty uiLowercase equ statusbits.3 ; true if a received cmd was lowercase uiRecord1 equ statusbits.4 ; true if channel 1 should record uiRecord2 equ statusbits.5 ; true if channel 2 should record buttonActive equ statusbits.6 ; true if button is expected to be down statusbits_resetvalue equ %1000000 ;;;;;;;;; ;; ;; macros ;; ;; ;; comparison macros ;; ; _sweq, _swne, _swgt, _swlte myreg (modify w) ; skip next instruction if w is equal to, not equal to, greater than, ; or less than or equal to the specified register, respectively. _sweq MACRO 1 xor w, \1 ; see if they are equal sz ; if so, skip next instruction ENDM _swne MACRO 1 xor w, \1 ; see if they are equal snz ; if not, skip next instruction ENDM _swgt MACRO 1 mov w, \1-w ; w = reg - w snc ; c = 0 if w > reg ENDM _swlte MACRO 1 mov w, \1-w ; w = reg - w sc ; c = 1 if w <= reg ENDM ; _srgtel, _srltl, _srgtl, _srltel (modify w) ; skip next instruction if register is >=, <, >, <= the specified literal. _srgtel MACRO 2 mov w, #\2 _swlte \1 ENDM _srltl MACRO 2 mov w, #\2 _swgt \1 ENDM _srgtl MACRO 2 mov w, #(\2)-1 _swlte \1 ENDM _srltel MACRO 2 mov w, #(\2)-1 _swgt \1 ENDM ;; ;; serial port macros ;; ; _incptr mybufptr (modifies w) ; increments mybufptr, wrapping to 0 if greater than 15. _incptr MACRO 1 mov w, ++\1 ; increment buffer pointer, and w, #$0f ; wrapping appropriately mov \1, w ENDM ; _getbuf mybufptr, BankBuffer (modifies w, fsr) ; gets a byte from the specified buffer and leaves it in w. ; The bank is left as BankSerial. _getbuf MACRO 2 mov w, #\2 ; get the bank add w, \1 ; add the "get pointer" mov fsr, w ; get ready for indirect addressing mov w, indf ; get our byte from the buffer bank BankSerial ; switch back to our bank ENDM ; _putbuf mybufptr, BankBuffer, globalvar (modifies w, fsr) ; puts the byte in globalvar into the specified buffer. globalvar must ; be a global variable (duh). The bank is left as BankSerial. _putbuf MACRO 3 mov w, #\2 ; get the bank add w, \1 ; add in the "put pointer" mov fsr, w ; get ready for indirect addressing mov w, \3 ; get the received byte mov indf, w ; put it in its place bank BankSerial ; switch back to our bank ENDM ;;;;;;;;;;;;;;;; ;; ;; ;; Page 0 ;; ;; ;; ;;;;;;;;;;;;;;;; org $0 ;;;;;;;;;;;;;;;;; ;; ;; Interrupt code ;; ; This interrupt simply maintains a counter and a UART. ; The UART checks if there is data to be transmitted, and then receives some ; data or waits for a start bit. There is no handshaking, so any bytes ; received while the buffer is full are ignored (gracefully). This UART ; has the ability to send or receive from multiple channels (but not at ; the same time). serial_rxmask and serial_txmask indicate which are the ; current send and receive pins. InterruptRoutine ; ;; Counter stuff ; inc mastercounter ; increment master counter snz ; overflow? dec eventloop_timer ; if so, decrement the countdown ; ;; Serial stuff ; bank BankSerial ; do our UART thang decsz serial_txdivide ; only xmit once every 8 ints jmp :sendend mov w, #SerialDivider ; reset divider mov serial_txdivide, w test serial_txlength ; is there anything in the xmit buffer? snz jmp :sendend ; if not, go receive something. snb serialTxActive ; are we already xmitting a byte? jmp :sending ; if so, continue with that :sendfirstbit _getbuf serial_txgetptr, BankTxBuf ; get a byte from the buffer mov serial_txbyte, w ; put it into the variable setb serialTxActive ; set the flag that says we're xmitting mov w, #9 ; nine more bits to go mov serial_txcount, w mov w, serial_txmask ; put start bit on pin or ra, w jmp :sendend ; all done :sending decsz serial_txcount ; decrement bit counter jmp :sendbit ; was that the last data bit? :sendlastbit dec serial_txlength ; if so, dec buffer length _incptr serial_txgetptr ; inc buf pointer, with wrapping clrb serialTxActive ; clear the flag mov w, /serial_txmask ; put stop bit on pin and ra, w jmp :sendend ; we outtee :sendbit rr serial_txbyte ; put next bit into carry snc ; is it 0? jmp :sendone :sendzero mov w, serial_txmask ; if so, raise the pin or ra, w jmp :sendend :sendone mov w, /serial_txmask ; if 1, lower the pin and ra, w ; that's all for that :sendend :recvstart mov w, ra ; read the pins and w, serial_rxmask ; isolate our current receive pin clc snz ; and copy inverse to carry bit stc snb serialRxActive ; are we already receiving a byte? jmp :recving ; if so, continue with that snc ; is it a start bit? jmp :recvend ; if not, nothin' else to do! mov w, #9 ; if so, let's get 9 bits. mov serial_rxcount, w setb serialRxActive ; set the flag mov w, #SerialDivider2 ; get the next bit in the middle mov serial_rxdivide, w ; of its slot jmp :recvend :recving decsz serial_rxdivide ; receive bit once every 8 ints jmp :recvend mov w, #SerialDivider ; restore divider mov serial_rxdivide, w decsz serial_rxcount ; still more bits to receive jmp :recvbit ; if so, go get a bit :recvlastbit mov w, #15 ; shouldn't put more than 15 in the buf mov w, serial_rxlength-w ; see how much is already there... snc ; are we full? jmp :recvignore ; if so, ignore this byte. mov w, serial_rxbyte ; if not, get the received byte mov stashi, w ; put it into a global _putbuf serial_rxputptr, BankRxBuf, stashi ; put it into the buffer _incptr serial_rxputptr ; inc buffer pointer, with wrapping inc serial_rxlength ; say there's one more byte in the buf setb serialRxAvail ; set the "rx data available" flag :recvignore clrb serialRxActive ; clear the active flag skip ; it's all good :recvbit rr serial_rxbyte ; rotate carry into the recv'd byte :recvend mov w, #-InterruptRate retiw ;;;;;;;;;;;;;; ;; ;; Reset entry ;; ResetEntry mov w, #PortA_ResetValue ; initialize port pins mov ra, w mov w, #PortB_ResetValue mov rb, w mov w, #PortAdir_ResetValue ; set up input and output pins mov !ra, w mov w, #PortBdir_ResetValue mov !rb, w mov w, #statusbits_resetvalue mov statusbits, w ; initialize global variables clr dcu_readrecord bank BankSerial ; initialize serial variables clr serial_txlength ; nothing in the xmit buffer clr serial_txgetptr clr serial_txputptr clr serial_rxlength ; nothing in the recv buffer clr serial_rxgetptr clr serial_rxputptr mov w, #serialh_rx_mask ; set the masks to communicate mov serial_rxmask, w ; with the host mov w, #serialh_tx_mask mov serial_txmask, w page PageUI ; pause a bit to make sure bank BankUI ; everything is fully plugged in call Delay_FullLong mov w, #%00000000 ; okay, we're ready for the interrupt mov !option, w ; prescalar 1/2, let 'er rip clr eventloop_timer ; reset the eventloop timer ;;;;;;;;;;;;;; ;; ;; Event Loop ;; EventLoop mov w, #EventLoopDelay ; go through the event loop once add eventloop_timer, w ; every EventLoopDelay x 3.3 ms. page PageButton call ButtonVector_Check ; was there a button hit? sb wreg.7 jmp UI_Record ; if so, let's record something page EventLoop :wait sb serialRxAvail ; receive anything on the serial port? jmp :noserial page PageUI call UiVector_ProcessInput ; if so, deal with the command. clr eventloop_timer page EventLoop :noserial sb eventloop_timer.7 ; did the eventloop countdown roll over? jmp :wait ; if not, keep waiting jmp EventLoop ; if so, start over. ;;;;;;;;;;;;;;;;;;;;; ;; ;; ;; Pages 2 and 3 ;; ;; ;; ;;;;;;;;;;;;;;;;;;;;; org UiPage*256 PageDelay equ $ PageButton equ $ PageUI equ $ PageSerial equ $ ;;;;;;;;;;;;;;; ;; ;; Jump vectors ;; ButtonVector_Check jmp Button_Check UiVector_ProcessInput jmp UI_ProcessInput ;;;;;;;;;;;;;;;;; ;; ;; Delay routines ;; ; Delay_FullLong (modifies w, stash2) ; waits for about 11.9 ms ; ; Delay_Long (modifies w, stash2) ; waits for about 47 us x w Delay_FullLong mov w, #0 Delay_Long mov stash2, w mov w, #0 :loop decsz wreg jmp :loop decsz stash2 jmp :loop JumpRet ret ; Delay_10 (modifies nothing) ; waits for 10 cycles (about 452 ns), including the call ; ; Delay_9 (modifies nothing) ; waits for 9 cycles (about 407 ns), including the call Delay_10 nop Delay_9 jmp JumpRet ;;;;;;;;;;;;;;;;; ;; ;; UI subroutines ;; ; UI_SendAck (modfies w, stash2) ; sends an generic OK byte UI_SendAck mov w, #UiAck ; get ACK character jmp Serial_SendByte ; send it out ; UI_FlashLedUntilAck (modifies w, stash2) ; flashes the led until an ACK of any type is received UI_FlashLedUntilAck clr eventloop_timer ; init the timer countdown :loop mov w, #led_mask ; toggle the light xor rb, w mov w, #LedFlashDelay ; reset the timer add eventloop_timer, w :wait sb serialRxAvail ; serial data received? jmp :noserial call Serial_RecvByte ; if so, get it and w, #$fc ; clear two lowest bits xor w, #GyrorespAck ; is it any ack? snz ; if not, ignore it jmp :win :noserial sb eventloop_timer.7 ; check if the timer has rolled over jmp :wait ; if not, wait for serial data jmp :loop ; if so, toggle the light :win setb led ; turn off the light ret ; and leave ; UI_WaitForResponse (modifies w, stash2, stash1) ; waits until the desired response byte is received. The byte ; is passed in w. UI_WaitForResponse mov stash1, w ; stash our response byte :wait call Serial_RecvByte ; get a byte xor w, stash1 ; compare it to the desired response sz ; match? jmp :wait ; if not, keep waiting ret ; if so, we're done ;;;;;;;;;;;;;;;;;;;;;;; ;; ;; Serial port routines ;; ; Serial_RecvByte (modifies w, fsr, stash2) ; pulls a byte out of the receive buffer, waiting for one if the buffer ; is empty. The byte is returned in w. Serial_RecvByte bank BankSerial :loop sb serialRxAvail ; is there data to be gotten? jmp :loop ; if not, wait for it. _getbuf serial_rxgetptr, BankRxBuf ; get a byte from the buffer mov stash2, w ; stash it _incptr serial_rxgetptr ; update buffer pointer clrb serialRxAvail ; update avail flag and buf length, in decsz serial_rxlength ; such a way that it is safe for an setb serialRxAvail ; interrupt to occur in the middle. mov w, stash2 ; get back byte bank BankUI ret ; and return with it. ; Serial_SendByte (modifies w, fsr, stash2) ; puts a byte into the transmit buffer. If the buffer is full, it waits ; until it isn't. The byte is passed in w. Serial_SendByte bank BankSerial mov stash2, w ; stash the byte to xmit :loop mov w, #15 ; shouldn't put more than 15 in the buf mov w, serial_txlength-w ; how much is already there? snc ; carry is clear if txlength < 15 jmp :loop ; otherwise, wait for buf to clear _putbuf serial_txputptr, BankTxBuf, stash2 ; put a byte in the buffer _incptr serial_txputptr ; update buffer pointer inc serial_txlength ; update buffer size bank BankUI ret ; that's enough. ;;;;;;;;; ;; ;; EEPROM stuff ;; ; Eeprom_ClockHighLow (modifies nothing) ; brings the eeprom serial clock line high and low Eeprom_ClockHighLow setb eeprom_sclk ; clock high call Delay_9 clrb eeprom_sclk ; clock low jmp JumpRet ; Eeprom_SendByte (modifies w, stash2) ; clocks a byte out serially to the eeprom. Not the same as WriteByte. ; Byte is passed in w. Eeprom_SendByte clr stash2 ; put 8 in stash2 setb stash2.3 :sendbit rl wreg ; put hibit in carry sc clrb eeprom_si ; put carry onto serial input pin snc setb eeprom_si call Eeprom_ClockHighLow ; clock it in decsz stash2 ; dec the counter jmp :sendbit ; if we haven't done 8 bits, keep at it ret ; Eeprom_RecvByte (modifies w) ; clocks a byte in serially from the eeprom. Not the same as ReadByte. ; Byte is returned in w. Eeprom_RecvByte mov w, #$01 :getbit clc ; put serial output pin into carry snb eeprom_so stc rl wreg ; rotate into result byte call Eeprom_ClockHighLow ; clock it out sc jmp :getbit ; if we haven't done 8 bits, keep at it ret ; Eeprom_CsLow (modifies nothing) ; lowers the appropriate chip-select for the current stream_addresshi. Eeprom_CsLow sb stream_addresshi.5 ; is it $2xxx? jmp :no3 clrb eeprom_cs3 ; if so, hit eeprom 3 ret :no3 snb stream_addresshi.4 ; is it $1xxx? clrb eeprom_cs2 ; if so, hit eeprom 2 sb stream_addresshi.4 ; is it $0xxx? clrb eeprom_cs1 ; if so, hit eeprom 1 ret ; Eeprom_CsHigh (modifies nothing) ; raises any chip select that is low. Eeprom_CsHigh setb eeprom_cs1 ; raise all three cs lines setb eeprom_cs2 setb eeprom_cs3 ret ; Eeprom_WaitForNotBusy (modifies w, stash2) ; continually reads the eeprom status register until the write-in-progress ; bit is clear. Eeprom_WaitForNotBusy :wait call Eeprom_CsLow ; lower chip-select mov w, #EepromReadstatus ; send the "read status register" call Eeprom_SendByte ; instruction call Eeprom_RecvByte ; get the result call Eeprom_CsHigh ; raise the chip-select snb wreg.0 ; check the write-in-progress bit jmp :wait ; if set, keep looping ret ; if clear, we're done ; Eeprom_EnableWrite (modifies w, stash2) ; waits until the eeprom is not busy, and then issues the command to ; turn off write protection. Must be done before every write. Eeprom_EnableWrite call Eeprom_WaitForNotBusy ; wait until any write is done call Eeprom_CsLow ; lower chip-select mov w, #EepromEnablewrite ; send the "turn off the write call Eeprom_SendByte ; protect latch" instruction call Eeprom_CsHigh ; raise chip-select ret ; Eeprom_ReadByte (modifies w, stash2) ; reads the byte from the eeprom that is pointed to by stream_addresslo/hi ; and returns it in w. stream_addresslo/hi is also incremented. The carry ; is returned clear. Eeprom_ReadByte call Eeprom_WaitForNotBusy ; wait until any write is done call Eeprom_CsLow ; lower chip-select mov w, #EepromRead ; send "read data" instruction call Eeprom_SendByte mov w, stream_addresshi ; send address hibyte call Eeprom_SendByte mov w, stream_addresslo ; send address lobyte call Eeprom_SendByte call Eeprom_RecvByte ; get back our data call Eeprom_CsHigh ; raise chip-select inc stream_addresslo ; increment address lobyte snz ; roll-over? inc stream_addresshi ; if so, increment hibte clc ; make things nice for Stream_Read ret ; and leave ; Eeprom_WriteByte (modifies w, stash2, stash1) ; writes a byte to the eeprom into stream_addresslo/hi. The byte is ; passed in w. stream_addresslo/hi is also incremented. This is a ; fairly inefficient write, and is not used by Stream_Flush. Eeprom_WriteByte mov stash1, w ; stash the byte to write call Eeprom_EnableWrite ; enable eeprom writes call Eeprom_CsLow mov w, #EepromWrite ; send "write data" instruction call Eeprom_SendByte mov w, stream_addresshi ; send address hibyte call Eeprom_SendByte mov w, stream_addresslo ; send address lobyte call Eeprom_SendByte mov w, stash1 ; get back data byte call Eeprom_SendByte ; send it call Eeprom_CsHigh ; raise chip-select inc stream_addresslo ; increment address lobyte snz ; roll-over? inc stream_addresshi ; if so, increment hibte ret ; Eeprom_EraseRecords (modifies w, stash2, stash1) ; writes an end-of-data marker ($ff) into the high byte of the ; length word of the first record, indicating that there are no ; valid records. Eeprom_EraseRecords mov w, #01 ; set address to $0001, which is mov stream_addresslo, w ; the hibyte of the length word clr stream_addresshi ; of the first record mov w, #$ff ; write the end-of-data byte jmp Eeprom_WriteByte ; Eeprom_WritePrefs (modifies w, stash2, stash1) ; writes a byte into the user preferences byte at the end of memory. ; The byte is passed in w. Eeprom_WritePrefs mov stash1, w ; stash the new prefs value mov w, #$ff ; set address to $2fff, which is mov stream_addresslo, w ; the last byte of memory mov w, #DcuMaxAddrHi mov stream_addresshi, w mov w, stash1 ; get back prefs value jmp Eeprom_WriteByte ; write it ; Eeprom_ReadPrefs (modifies w, stash2) ; reads the user preferences byte at the end of memory. The byte is ; returned in w. Eeprom_ReadPrefs mov w, #$ff ; set address to $2fff, which is mov stream_addresslo, w ; the last byte of memory mov w, #DcuMaxAddrHi mov stream_addresshi, w jmp Eeprom_ReadByte ; read the byte ; Stream_OpenForWrite (modifies w, stash2, stash1) ; prepares a new record to be written. This must be called before ; any calls to Stream_Write. Stream_OpenForWrite mov w, #-1 ; get the next available address call Stream_OpenForRead ; in memory (plus two) mov w, stream_addresslo ; get the lobyte and w, #$0f ; mask off the low nibble or w, #stream_buffer ; add pointer to buffer in RAM mov stream_bufpointer, w ; store as the new bufpointer mov stream_flushptr, w ; nothing has been flushed yet mov w, #-2 ; subtract two from the address add w, stream_addresslo ; in order to get where we should mov stream_startlo, w ; write the length word when the mov w, stream_addresshi ; stream is closed sc dec wreg mov stream_starthi, w clr stream_lengthlo ; clear the length word clr stream_lengthhi ret ; and we're ready to write ; Stream_Write (modifies w, stash2) ; writes data to the write buffer, flushing it to the eeprom when the buffer ; fills up. The byte to write is passed in w. Stream_WriteNoLength can be ; called instead to avoid incrementing stream_lengthlo/hi. Stream_Write inc stream_lengthlo ; increment the length snz inc stream_lengthhi Stream_WriteNoLength mov stash2, w ; stash the byte to write mov w, stream_bufpointer ; put bufptr in fsr mov fsr, w mov w, stash2 ; get back byte to write mov indf, w ; and put it into the buffer bank BankStream inc stream_bufpointer ; inc buf pointer mov w, stream_bufpointer ; check if it overflowed and w, #$0f ; isolate lo nibble sz ; is it zero? ret ; if not, we're done call Stream_Flush ; if zero, flush the buffer mov w, #stream_buffer ; reset the pointer to the start mov stream_bufpointer, w ; of the buffer mov stream_flushptr, w ; as well as the flush pointer ret ; Stream_Flush (modifies w, stash2) ; physically writes the contents of the buffer to the eeprom, and updates ; the pointers to indicate the portion of the buffer that has been written. ; Because it uses the eeprom's "page write" mode, it amortizes the eeprom ; write time over all of the data being flushed. Thus, even though a write ; cycle is 5 ms, data can effectively be written at 3200 bytes per second. ; Although this writes data to the eeprom, the existence of the new record ; will not be recognized until Stream_CloseWrite. Thus, if the power goes ; out in the middle of a recording, the record will be lost but the filesystem ; integrity will not. Stream_Flush call Eeprom_EnableWrite ; make sure we can write call Eeprom_CsLow ; lower chip-select mov w, #EepromWrite ; send "write data" instruction call Eeprom_SendByte mov w, stream_addresshi ; send address hibyte call Eeprom_SendByte mov w, stream_addresslo ; send address lobyte call Eeprom_SendByte mov w, stream_flushptr ; subtract flush ptr from buffer ptr mov w, stream_bufpointer-w ; to get number of bytes to flush add stream_addresslo, w ; increment the addresss by that much snc ; (which we can do now that we've inc stream_addresshi ; already sent the address) :writeloop mov w, stream_flushptr ; get pointer to byte to flush xor w, stream_bufpointer ; is it at the end of the buffer data? snz jmp Eeprom_CsHigh ; if so, raise cs and leave mov w, stream_flushptr ; if not, put the flushptr in fsr mov fsr, w mov w, indf ; get the byte from the buffer bank BankStream ; switch back to our bank call Eeprom_SendByte ; send that byte to the eeprom inc stream_flushptr ; increment the flush jmp :writeloop ; keep on flushing ; Stream_CloseWrite (modifies w, stash2) ; flushes any remaining data in the buffer, and writes the record length ; at the beginning of the record, effectively making the record visible. ; After this call, Stream_Write should not be called until Stream_OpenForWrite ; has been called again. Stream_CloseWrite call Stream_WriteNoLength ; write dummy lobyte for next record mov w, #$ff ; put end-of-data marker for hibyte call Stream_WriteNoLength ; of next record call Stream_Flush ; write any data remaining in the buffer mov w, stream_startlo ; set the address to where our length mov stream_addresslo, w ; word should go mov w, stream_starthi mov stream_addresshi, w mov w, stream_lengthlo ; get lobyte of number of bytes written call Eeprom_WriteByte ; write it mov w, stream_lengthhi ; get hibyte of number of bytes written jmp Eeprom_WriteByte ; write it ; Stream_Read (modifies w, stash2) ; reads a byte from a stream that was opened with Stream_OpenForRead. ; If there is no more data to read, the carry is returned set. Otherwise, ; the carry is clear and the data is returned in w. The number of bytes ; left in the record is available in stream_lengthlo/hi. Stream_Read test stream_lengthlo ; we want to decrement length snz ; is the lobyte zero? dec stream_lengthhi ; if so, decrement the hibyte dec stream_lengthlo ; either way, decrement the lobyte sb stream_lengthhi.7 ; is the length negative now? jmp Eeprom_ReadByte ; if not, read a byte and clear carry stc ; if so, set carry setb stream_lengthlo.0 ; make sure the length doesn't keep ret ; decrementing ; Stream_OpenForRead (modifies w, stash2, stash1) ; prepares a record to be read using Stream_Read. The record number ; is passed in w. If an invalid record number is specified, then ; calls to Stream_Read will simply return carry set. If w is -1 ; then the negative of the total number of records is available ; in stash1, and then next available memory address plus 2 is in ; stream_addresslo/hi. If the filesystem does not appear to be ; set up correctly, it will initialize it using Eeprom_EraseRecords. ; It is not necessary to close read streams. Stream_OpenForRead mov stash1, w ; put the record number in stash1 inc stash1 ; increment it clr stream_addresshi ; go to the start of memory clr stream_addresslo :readlength call Eeprom_ReadByte ; read a byte mov stream_lengthlo, w ; that's the lobyte of the length word call Eeprom_ReadByte ; read another byte mov stream_lengthhi, w ; that's the hibyte of the length word snb wreg.7 ; is it negative? ret ; if so, that's the end of data dec stash1 ; if not, decrement the counter snz ; was that our desired record? ret ; if so, we're ready to go mov w, stream_lengthlo ; if not, add the length to the current add stream_addresslo, w ; address (which is now 2 plus the mov w, stream_lengthhi ; starting address because ReadByte snc ; increments it) inc wreg add stream_addresshi, w ; and that's our new address _srgtl stream_addresshi, DcuMaxAddrHi ; is the hibyte past the end jmp :readlength ; of memory? call Eeprom_EraseRecords ; if so, initialize the memory clr stash1 ; and say there are no records ret ;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; User Interface routines ;; ; UI_ProcessInput (modifies w, stash2, stash1) ; receives a serial character, examines it to see if it's a valid command, ; and if so, jumps to the appropriate handler. UI_ProcessInput call Serial_RecvByte ; get the command snb wreg.7 ; high bit set? jmp UI_BadCommand ; if so, it's wrong mov stash2, w ; save our command xor w, #'?' ; version query? snz jmp UI_Version ; if so, go do that clrb uiLowercase ; set the lowercase bit if snb stash2.5 ; the received character is setb uiLowercase ; lowercase clrb stash2.5 ; force it to uppercase mov w, #'N' ; was it an N? xor w, stash2 snz jmp UI_NumRecords ; if so, go do that mov w, #'R' ; was it an R? xor w, stash2 snz jmp UI_Read ; if so, go do that mov w, #'E' ; was it an E? xor w, stash2 snz jmp UI_Erase ; if so, go do that mov w, #'G' ; was it a G? xor w, stash2 snz jmp UI_SwitchGyro ; if so, go do that mov w, #'I' ; was it an I? xor w, stash2 snz jmp UI_SwitchInt ; if so, go do that mov w, #'O' ; was it an O? xor w, stash2 snz jmp UI_SwitchOff ; if so, go do that mov w, #'H' ; was it an H? xor w, stash2 snz jmp UI_HalfRate ; if so, go do that UI_BadCommand mov w, #UiNak ; none of those, so jmp Serial_SendByte ; send out the NAK char ; UI_Read ; reads a record UI_Read call UI_SendAck ; send an ack mov w, dcu_readrecord ; get the record to read call Stream_OpenForRead ; open the record for reading call Stream_Read ; get the prefs byte snc ; past the end of the file? jmp :badrec ; if so, we're out of records mov stash1, w ; stash the prefs byte mov w, ++dcu_readrecord ; send the record number call Serial_SendByte ; (starting with 1) inc dcu_readrecord ; increment for next time mov w, stash1 ; get back the prefs byte call Serial_SendByte ; and send it mov w, stream_lengthlo ; get the number of bytes left call Serial_SendByte ; and send it mov w, stream_lengthhi call Serial_SendByte :loop call Stream_Read ; get a byte snc ; is carry set? ret ; if so, we're done call Serial_SendByte ; if not, send out the byte jmp :loop ; and keep sending until we run out :badrec mov w, #$ff ; say that we've exceeded the end jmp Serial_SendByte ; UI_NumRecords ; retrieves the number of stored records and resets the counter to the ; beginning UI_NumRecords clr dcu_readrecord ; reset the read counter call UI_SendAck ; send an ack mov w, #-1 ; get the negative of the number of call Stream_OpenForRead ; records in stash1 not stash1 ; negate it and put it in w mov w, ++stash1 jmp Serial_SendByte ; send it ; UI_Erase ; erases the current records UI_Erase clr dcu_readrecord ; reset the read counter call Eeprom_EraseRecords ; erase them jmp UI_SendAck ; say that we cleared it ; UI_Version ; displays version information UI_Version call UI_SendACK ; send an ACK mov w, #VersionMinor ; and the minor version byte call Serial_SendByte mov w, #VersionMajor ; and the major version byte jmp Serial_SendByte ; UI_SwitchGyro ; sets one of the channels to angular velocity mode ; ; UI_SwitchInt ; sets one of the channels to rotation mode ; ; UI_SwitchOff ; sets one of the channels to off mode UI_SwitchGyro mov w, #DcuPrefGyro ; get the pref code for gyro jmp uiSwitch ; go switch UI_SwitchInt mov w, #DcuPrefInt ; get the pref code for int skip ; go switch UI_SwitchOff clr wreg ; pref code for off is zero uiSwitch mov stash1, w call Eeprom_ReadPrefs ; get the prefs byte snb uiLowercase ; was the command uppercase? swap wreg ; if not, work on the high nibble and w, #$fc ; clear the bits or w, stash1 ; set the bits we want snb uiLowercase ; was the command uppercase? swap wreg ; if not, make the byte right again call Eeprom_WritePrefs ; write it into the eeprom jmp UI_SendAck ; say we did it ; UI_HalfRate ; turns on or off the reduced sample rate mode UI_HalfRate call Eeprom_ReadPrefs ; get the prefs byte clrb wreg.7 ; clear the halfrate bit sb uiLowercase ; unless it's uppercase setb wreg.7 ; in which case, set it call Eeprom_WritePrefs ; write it back jmp UI_SendAck ; say we did it ; UI_Record (does not return) ; called when the button is hit. Sets up serial communication with the ; gyro board, tells it to start sending samples, opens a new record, ; writes the prefs byte, and records incoming data, stopping when the ; button is hit. The record is then closed, and the code jumps to ; ResetEntry. UI_Record bank BankSerial ; muss with serial vars mov w, #serialg_rx_mask ; switch the receive bit to the mov serial_rxmask, w ; gyro board :waittx test serial_txlength ; wait for the transmit buffer to empty sz ; is it empty? jmp :waittx ; if not, keep waiting call Delay_FullLong ; wait a little bit mov w, #serialg_tx_mask ; switch the transmit bit to gyro mov serial_txmask, w ; board clr serial_rxlength ; empty the receive buffer clr serial_rxgetptr clr serial_rxputptr bank BankUI mov w, #GyrocmdNoVerbose ; make sure the board is in non-verbose mode call Serial_SendByte mov w, #GyrorespAck ; wait for the ack call UI_WaitForResponse mov w, #GyrocmdQuiet1 ; tell it not to autoread (for now) on either call Serial_SendByte ; channel mov w, #GyrocmdQuiet2 call Serial_SendByte mov w, #GyrorespAck1 ; wait for the proper responses (this is actually call UI_WaitForResponse ; more to make sure the responses are synchronized mov w, #GyrorespAck2 ; with what I'm sending) call UI_WaitForResponse call Eeprom_ReadPrefs ; read our preferences byte mov ui_stash, w ; and store it test wreg ; are we not recording anything? snz ; if both channels are off, don't jmp :skiprec ; do the record mov w, #GyrocmdAutoSpeed0 ; tell it to give us every sample snb ui_stash.7 ; unless the halfrate bit is set mov w, #GyrocmdAutoSpeed1 ; in which case, give us every other sample call Serial_SendByte mov w, #GyrorespAck ; wait for the ack call UI_WaitForResponse clrb uiRecord2 ; say not to record channel 2 for now mov w, ui_stash ; get the prefs byte and w, #$30 ; isolate the channel 2 prefs snz ; is it zero? jmp :no2 ; if so, don't record it setb uiRecord2 ; if not, say to record 2 mov w, #GyrocmdCalGyro2 ; gyro or int? sb wreg.4 mov w, #GyrocmdCalInt2 ; get the right calibration command call Serial_SendByte ; send it call UI_FlashLedUntilAck ; flash the led while it calibrates :no2 clrb uiRecord1 ; say not to record channel 1 for now mov w, ui_stash and w, #$03 ; isolate the channel 1 prefs snz ; is it zero? jmp :no1 ; if so, don't record it setb uiRecord1 ; if not, say to record 1 mov w, #GyrocmdCalGyro1 ; gyro or int? sb wreg.0 mov w, #GyrocmdCalInt1 ; get the right calibration command call Serial_SendByte ; send it call UI_FlashLedUntilAck ; flash the led while it calibrates :no1 call Stream_OpenForWrite ; open a record for writing _srltl stream_addresshi, DcuMaxAddrHi ; is memory full? jmp :skiprec ; if so, don't record anything! mov w, ui_stash ; write the preferences byte call Stream_Write mov w, #GyrocmdAutoRead1 ; if we are recording channel 1, snb uiRecord1 ; turn on its autoread call Serial_SendByte mov w, #GyrocmdAutoRead2 ; if we are recording channel 2, snb uiRecord2 ; turn on its autoread call Serial_SendByte :rec1 sb uiRecord1 ; recording channel 1? jmp :rec1done ; if not, skip this mov w, #GyrorespChannel1 ; wait for a channel 1 reading call UI_WaitForResponse call Serial_RecvByte ; get a data byte call Stream_Write ; write it call Serial_RecvByte ; get a data byte call Stream_Write ; write it :rec1done :rec2 sb uiRecord2 ; recording channel 2? jmp :rec2done ; if not, skip this mov w, #GyrorespChannel2 ; wait for a channel 2 reading call UI_WaitForResponse call Serial_RecvByte ; get a data byte call Stream_Write ; write it call Serial_RecvByte ; get a data byte call Stream_Write ; write it :rec2done clrb led ; turn on the led _srgtel stream_addresshi, DcuMaxAddrHi jmp :nooutofspace ; check if we've run out of memory _srltel stream_addresslo, $e0 jmp :stoprec :nooutofspace call ButtonVector_Check ; check for a button press snb wreg.7 ; is there one? jmp :rec1 ; if not, keep recording :stoprec setb led ; turn off the light call Stream_CloseWrite ; close the record mov w, #GyrocmdQuiet1 ; tell the gyro board to shut up call Serial_SendByte mov w, #GyrocmdQuiet2 call Serial_SendByte :skiprec bank BankSerial ; wait for the transmit buf to empty :waittx2 test serial_txlength sz jmp :waittx2 mov w, #%01000000 ; turn off interrupts mov !option, w call Delay_FullLong ; wait a bit page ResetEntry ; and (almost) reset the board jmp ResetEntry ;;;;;;;;;;;;;;;;;; ;; ;; Button routines ;; ; Button_Check (modifies w, fsr) ; checks the state of the button and performs debouncing. Returns ; $00 in w if there has been a button-down event, $ff otherwise. Button_Check sb button ; check the button (active low) jmp :down ; if down, take care of it :up clr button_debounce ; if up, clear the counter clrb buttonActive ; clear the active flag retw $ff ; return with nothing :down snb buttonActive ; still down from a previous event? retw $ff ; if so, return with nothing inc button_debounce ; if not, increment counter mov w, #ButtonDebounceCount _swlte button_debounce ; is it past the threshold? retw $ff ; if not, return with nothing setb buttonActive ; if so, set the active flag retw $00 ; and say we got a hit.