* Bubble sort a list called stepSeq, then seek to those locations using a stepper motor.
* Paul Main. October 2003.
* Tested on LaTrobe University's HC-COM
*
code    equ $2000    * place this code in RAM
eeprom  equ $B600    * place library code in eeprom
data    equ $4000    * place data in RAM also

TRUE  equ 1
FALSE equ 0

REGBAS EQU $1000        * Starting address for register block
PORTA EQU $00           * Port A INPUT:PA0..2, OUTPUT:PA3..6, & I/O PA7
*                       * Port Outputs can be used when not using timer
*                       * latches specified in TCTL1 (below)
PORTB EQU $04           * Output port B
CFORC EQU $0B           * Force Output Compare...
OC1M EQU $0C            * OClM7,OClM6,OClM5,OClM4*OClM3,-,-,-
OC1D EQU $0D            * OClD7,OClD6,OClD5,OClD4*OClD3,-,-,-
TCNT EQU $0E            * Free running counter (16 bit)
TIC1 EQU $10            * IC1 register (16 bit)
TOC1 EQU $16            * OC1 register (16 bit)
TOC2 EQU $18            * OC2 register (16 bit)
TOC3 EQU $1A            * OC3 register (16 bit)
TCTL1 EQU $20           * OM2,OL2,OM3,OL3*OM4,OL4,OM5,OL5
TCTL2 EQU $21           * -,-,EDGlB,EDGlA*EDG2B,EDG2A,EDG3B,EDG3A
TMSK1 EQU $22           * OC1I,OC2I,OC3I,OC4I*OC51,IC1I,IC2I,IC3I
TFLG1 EQU $23           * OC1F,OC2F,OC3F,OC4F*OC5F,IC1F,IC2F,IC3F
TMSK2 EQU $24           * TOI,RTII,PAOVI,PAII*-,-,PR1,PR0
TFLG2 EQU $25           * TOF,RTIF,PAOVF,PAIF*-,-,-,-
PACTL EQU $26           * DDRA7:PAEN:PAMOD:PEDGE:-:-:RTR1:RTR0
*                       DDRA7 - Data Direction Register for Port A bit 7
*                       PAEN - Pulse Accumulator ENable
*                       PAMOD - For Pulse Accumulator - See Section 11 of ref.man.
*                        PAMOD = 0 -> Event Count,
*                        PAMOD= 1 -> Gated Time Accumulation
*                       RTR1:RTR0 - Select Real Time Interrupt Rate
*BUFFALO Routine Addresses & Pseudo Vector Equates

.OUTA EQU $FFB8         * Print character in A-reg
.OUTCRL EQU $FFC4       * Output <cr><lf>
.OUTSTO EQU $FFCA       * Output Msg seg (no <cr,lf>)
.OUTSTR EQU $FFC7       * Output Msg w/leading <cr,lf>
OutChar equ $FFAF
InChar  equ $FFCD

PVTOF EQU $00D0         * EVB Pseudo Vector for TOF
PVOC2 EQU $00DC         * EVB Pseudo Vector for OC2

REGS    EQU     $2000           * Register stack
TOF     EQU     %10000000       * Timer overflow flag
N1      EQU     30             * times for one sec
OPTION  EQU     $39
ADCTL   EQU     $30
ADR1    EQU     $31
ADSET   EQU     %00000100       * A/D input on PE-4
CCF     EQU     %10000000       * Conversion complete flag
ADPU    EQU     %10000000       * A/D power up bit
* *                               * Monitor Equates
OUTLHF  EQU     $FFB2 Print left half
OUTRHF  EQU     $FFB5 Print right half
CRLF    EQU     $FFC4 Print CRLF

     org data
     jmp startHere

halfDelay RMB 2              Half-cycle delay (in 0.5mS increments)
ADCVAL RMB 1
stepDirn RMB 2

StepPtr   RMB   2
* Half stepping code sequence:
*StepTable FCB %0001000, %0101000, %0100000, %0110000,
*          FCB %0010000, %1010000, %1000000, %1001000
*
* Full step - single coil excitation sequence:
StepTable FCB %0001000, %0100000,
          FCB %0010000, %1000000,

* Full step - dual coil excitation sequence:
*StepTable FCB %0101000, %0110000,
*          FCB %1010000, %1001000
*
* Step End identifies the end of the table:
StepEnd   FCB #$00

nextPos  rmb 2                  * The next shaft position in Steps
currPos  rmb 2                  * The current shaft position in Steps


seqPtr  RMB 2
* The sequence to use to test position seek time:
stepSeq  fcb 254, 2, 251, 3, 252, 4, 253, 5, 254, 0

myString fcc 'THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG!'
         fcb 0

promptString fcc 'Position: (4 digit hex):'
         fcb 0
ADCprompt fcc 'ADC:'
         fcb 0
POSprompt fcc ', POS:'
         fcb 0
DonePrompt fcc 'Done'
         fcb 7, 0


* string passed to InStringXB
readString fcc 'HERE IS A STRING THAT CAN BE OVERWRITTEN BY CONSOLE INPUT'
         fcb 0

crlf     fcb $0d, 0             * CR
beep     fcb 7, 0               * ASCII BELL
sorted   rmb 1                  * boolean flag to indicate when list is sorted

        org code

startHere:
        LDX  #stepSeq
        jsr  bubbleSortX

        LDD  #$0
        STD  stepDirn           * If StepDirection = 0 => no stepper movement

        LDD  #$2000
        STD  halfDelay          * Half Cycle delay - One phase step time

        LDD  #stepSeq           * point to the beginning of the sequence table
        STD  seqPtr

        ldd  #10                 * Initialise curr position & next position
        std  nextPos
        ldd  #10
        std  currPos
        ldd  #0
        std  stepDirn           * step back to 0

        LDAA #$7E               * Jump (extended) Opcode
        STAA PVOC2              * Pseudo Vector see manual text
        LDX  #OC2ISR            * Address of OC2 service routine
        STX  PVOC2+1            * Finish jump instruc to TOF svc

        LDX  #REGBAS            * Point to register block
        LDAA #%0000000          *
        STAA OC1M,x            * Output Compare Mask = 0 for all OCs
        LDAA #%0000000          * OM2:OL2 = 0:0 - Disable timer pin functions
        STAA TCTL1,X            * All timers OCx outs not connected to outputs

        LDAA #%1000000          * OC2F - Output Compare 2 Flag - Bit Mask
        STAA TFLG1,X            * Clear any pending,OC2F
        STAA TMSK1,X            * Enable OC2 interrupts

        LDX  #StepTable
        STX  StepPtr            * StepPtr -> Start of stepper table values

        LDX  StepPtr
        LDAA 0,x
        LDAA #$FF               * For debugging set to all ones
        LDX  #REGBAS
        STAA PORTA,x           * Store initial Step position

        LDAA #%11111000
        STAA OC1D,x
        STAA CFORC,x

* All initialisations complets - let the stepping begin
        CLI                     * Enable Interrupts

        BSET    OPTION,x ADPU   * Power up the ADC
        JSR     delay100us      * wait till the charge pump is stabilised

FOREVER:
* Beginning of WHILE loop
        jsr slewWait         * wait for stepper motor to complete slewing

* READ NEXT BYTE INTEGER FROM THE STRING.
        ldx     seqPtr
        ldab    0,x        * get the byte from the string
        inx
        stx     seqPtr      * save ptr++
        cmpb    #0          * is this the end of the string?
        beq     stopSeq     * yes then send a prompt to say so
        ldaa    #0          * set a = 0
* work out which direction to step.
        std     nextPos     * save the next position
        cpd     currPos     * compare with current position
        beq     noStep      * if same value, dont step.
        bhi     stepFwd     * if higher, step forwards
* else less than so step backwards
        ldd     #-1         * if backwards then step backwards
        bra     continue
noStep:
        ldd     #0          * if equal dont step anywhere
        bra     continue
stepFwd:                    
* if forwards, then increment
        ldd     #1
        bra     continue

continue:
        std     stepDirn    * save step direction

        BRA     FOREVER * loop forever

stopSeq:
        ldx    beep
        jsr    outStringX

        ldx    DonePrompt
        jsr    outStringX

        ldx    #crlf
        jsr    outStringX

        swi



* ----------------------------------------------------------------
slewWait:
        psha
        pshb
        pshx

slewMore:
        ldd     stepDirn   * are we still stepping?
        cpd     #0         * Compare direction with 0 - signals completed stepping
        beq     doneStepping * If 0, we have stepped to the requested position

        ldx     #POSprompt
        jsr     outStringX
        ldd     currPos   * show current position
        jsr     outHexD
        JSR     CRLF      * output carriage return - line feed

        bra     slewMore

doneStepping:
        ldx     #beep        * beep to indicate slewing complete
        jsr     outStringX
        jsr     outStringX
        jsr     outStringX
        jsr     outStringX
        jsr     outStringX
        jsr     outStringX

        pulx
        pulb
        pula
        rts

* ----------------------------------------------------------------------


getADC:
        LDX     REGBAS
        LDAA    #ADSET  * Select ADC input from Port E-4
        STAA    ADCTL,x * Signal ADC to start conversion

* Busy-Wait until ADC conversion done
convwait:
        BRCLR   ADCTL,x CCF convwait

* Conversion now done, so get the ADC input and save it in ADCVAL
        LDAA    ADR1,x * Get ADC converted input voltage value
        STAA    ADCVAL  * Store for later use

        rts

* ----------------------------------------------------------------------

bubbleSortX:                     
* bubble sorts the ASCIIZ string pointed to by  X

        psha
        pshb
        pshx                    * save list start on stack

        ldaa    0,x            * if first character = 0, then return.
        cmpa    #0              * are we at 0 terminator?
        bne     bubbleList      * else sort the data.
        rts                     * list is too short to sort

bubbleList:                     
* listStart = Start of null terminated list
        ldaa    #TRUE
        staa    sorted          * sorted = TRUE*

        pulx                    * get list start from stack,
        pshx                    * then save it again. x = pointer to list_start*

*        jsr     outStringX      * show the changes as they happen, pass by pass
*        pshx
*        jsr     InChar
*        pulx


bubbleItem:
        ldaa    1,x
        cmpa    #0              * are we at 0 terminator?
        beq     next_pass       * then break - we don’t want to bubble NULL

        cmpa    0,x            * comparing adjacent items here.
        bhs     dont_swap       * if (item[x] > item [x+1])
        bsr     byteSwapX       * { swap        items. Sorted = FALSE }
dont_swap:

        inx                     * x ++*
        bra     bubbleItem

next_pass:
        ldaa    sorted          * if (sorted <> TRUE) => not sorted
        cmpa    #TRUE
        bne     bubbleList      * not sorted, so keep bubbling.

        pulx
        pulb
        pula
        rts                     * else the list is sorted.


* -------------------------------------------------------------------
* Swap bytes pointed to at by Index register X and X+1.
* -------------------------------------------------------------------
byteSwapX:
        ldab    0,x             * b = mem[x+0]
        ldaa    1,x             * a = mem[x+1]
        staa    0,x             * mem[x+0] = a
        stab    1,x             * mem[x+1] = b
        clr     sorted           * sorted = FALSE*
        rts


* -------------------------------------------------------------------
* Console Output - an ASCIIZ string pointed to by Index register X.
* -------------------------------------------------------------------
outStringX:
        pshx
again:
        ldaa    0,x            * get the character pointed to by X
        cmpa    #0              * Compare with null - character 0
        beq     foundNull       * found null character so break out of loop
        jsr     OutChar         * output character in Acc. A
        inx
        bra     again

foundNull:
        pulx
        rts


* -------------------------------------------------------------------
* Console Input inStringXB
* -------------------------------------------------------------------
* Reads an ASCIIZ string pointed to by Index register X.
* Maximum length to read is specified by Accumulator B.
* Pressing carriage return also terminates input, placing a 0 at the end of the string.
* -------------------------------------------------------------------
inStringXB:
        pshx                    * Save pointer to string
        pshb                    * B = max # chars to read
        psha                    * Save Acc A

getAgain:
        pshb                    * Preserve B
        jsr     InChar          * get character into Acc. A
        pulb                    * Recover B

        cmpa    #13             * is is Carriage Return (CR)?
        beq     foundEnter      * CR character? - yes: break out of loop
        staa    0,x            * no: save read char in buffer
        inx                     * point to next buffer position
        decb                    * decrement the limit counter
        bne     getAgain        * max count not = 0, loop again

foundEnter:
        clr     0,x            * ensure string is terminated

        pula                    * recover Acc A
        pulb                    * recover Acc B
        pulx                    * recover pointer to string
        rts


* -------------------------------------------------------------------
* SUBROUTINE ASCII to INT16
* -------------------------------------------------------------------
* Pass a Null terminated ASCII HEX string to this routine, and it will
* return a 16 bit number - the last 4 hex-digits converted in the D register
*
* Upon entry Index Register X points to the beginning of the ASCIIZ string to convert
*
* Upon return:
*        Accumulator D holds the converted 16 bit Integer
*
* Note: Garbage In-Garbage Out :
* - If the provided string does not contain HEX digits,
*   then the returned integer value is undefined.
* ---------------------------------------------------------------------
atoi:
        PSHY            * SAVE Y REGISTER
        pshx

        LDD     #0      * Start with 0 as our interim result in accumulator D
MoreChar:
        XGDY            * Swap D with Y* Y now contains the interim result

        LDAB    0,x    * Get the character pointed to by the X register
        CMPB    #0      * Compare with the String Terminator (Carriage Return)
        BEQ     AllConverted    * Finished conversion
*
* now convert the ASCII letters/numbers -> binary
        SUBB    #$30    * '0' - $30 => 0, '1' - $30 => 1, ... '9'-$30 -> 9, 'A'-$30->17
        CMPB    #9      * is result in range 0..9?
        BLE     IsNumeric * If it is 0..9 then Character is now converted to integer
IsAlphabetic:
        SUBB    #7      * else assume it was letter & convert letters A..F to binary
IsNumeric:
        ANDB    #$0F    * Mask any bits in top nybble of byte - there should be none!

        XGDY            * Swap the interim result back into D

        ASLD            * Multiply our interim result by 16 by shifting left 4 times
        ASLD
        ASLD
        ASLD

        XGDY            * Swap D with Y* Y now contains the interim result
        ABY             * IY = IY + B = append the binary value to our interim result
        XGDY            * Swap the interim result back into D

        INX             * X = X + 1 - point to the next character in the string
        BRA     MoreChar * loop for more characters to convert

AllConverted:
        XGDY            * Return result in D register

        pulx
        PULY            * RESTORE Y REGISTER

        RTS             * Return from subroutine



* -------------------------------------------------------------------
* Output 2 hex digit version of Accumulator A
* -------------------------------------------------------------------
outHexA:
       psha
       pshb
       pshx
       pshy

       TAB
       JSR     OUTLHF  * output high nybble
       TBA             * restore it to A
       JSR     OUTRHF  * output low nybble

       puly
       pulx
       pulb
       pula
       rts


* -------------------------------------------------------------------
* Output 4 hex digit version of Accumulator D
* -------------------------------------------------------------------
outHexD:
       psha
       pshb

       pshb
       jsr     outHexA
       pula
       jsr     outHexA

       JSR     CRLF    * output carriage return - line feed

       pulb
       pula
       rts


* ------------------------------------------------------------------------------------
* This subroutine delays (a little over) 100 micro-seconds
* ------------------------------------------------------------------------------------
* Busy-wait a number of clock cycles
* ------------------------------------------------------------------------------------
delay100us:
        psha            * save Accumulator A
*                       * Generate a "short" delay > 100 microsec
        LDAA    #40     * 40 loops for 200 clock cycles at 0.5 micro seconds/clock
DELAYLOOP:
        DECA            *
        BNE     DELAYLOOP
        pula            * restore Accumulator A
        RTS             *



* -------------------------------------------------------------------
* OC2ISR - Output Compare 2 interrupt service routine
*
* This routine is executed on each OC2 interrupt.
* -------------------------------------------------------------------
OC2ISR
        LDX  #REGBAS            * X now points to the register base

        LDD  halfDelay          * Schedule Next TOC2 interrupt
        ADDD TOC2,X             * Add halfDelay to last compare value
        STD  TOC2,X             * Update OC2 (schedule next edge)
        BCLR TFLG1,X $BF        * Clear OC2F

        LDD  StepPtr            * X = Stepper Motor Table Pointer address
        ADDD stepDirn           * 1, 0 or -1 (a 16 bit value)
        XGDX

        CPX  #StepEnd           * If at end of table, then wrap to start
        BLO  NoWrap             * Is the index still pointing within the table?
*                                * BLO = Unsigned Branch if lower
        LDX  #StepTable         * NO: Re-Load the starting address of table
        BRA  StorePtr
NoWrap:                         
* The index still points less than the table end

        CPX  #StepTable         * If before start of table, then wrap back to end
        BHS  NoWrapBack         * Is index >= start? Then still in table range
*                                * BHS=Branch if Higher or Same.
        LDX  #StepEnd-1         * NO: Load the last address within the table
NoWrapBack:

StorePtr:
        STX  StepPtr            * Save next Stepper Motor Table Pointer address
        LDAA 0,x               * Acc A = Next Stepper Value

        STAA REGBAS+PORTA       * Write to the port to activate next stepper coil

        LDD  currPos            * Get Current Position
        ADDD stepDirn           * 1, 0 or -1
        std  currPos
        cpd  nextPos
        bne  keepStepping
        ldd  #0                 * Stop stepping
        STD  stepDirn           * Setting Step Direction to 0 means "Stop Stepping"
                                
* The ISR will keep interrupting, but no action will result.
keepStepping

        RTI                     * Return from OC2 Interrupt service routine


