6502 Pen plotter - Drawing by Lee Davison
[Back]
The actual drawing is performed by an interrupt service routine. This is called either by a software interrupt (BRK) if no drawing is taking place or by the completion of the previous draw command signalled by a timer countdown interrupt.Actions.For this description it doesn't matter how the routine was called, that it was is all that matters.
The mechanism can only do one of ten things, it can move in one of eight directions either along the step axes or at 45 degrees to them (step x and y together), lift the pen or drop the pen. Each of these actions takes a finite time and the maximum time for that action has to pass before a new action can be started. For movement this time is 3mS which, with a step size of 0.1mm gives a draw rate of 3.33cm/S. For lifting or dropping the pen the time needed is 8mS which means we can draw a maximum of 45 separate lines per second.Draw command format.This is by no means fast and the 6502 is quite adequate to fully control these actions.
The draw commands are fetched from the buffer one at a time and have the format.Executing the command.
Count low Low byte of step count word Count high High byte of step count word Nbyte Negative step byte for the mechanism Pbyte Positive step byte for the mechanism Nbyte and Pbyte have the following bit functions
Bit 1 0 7 Pen down Pen up 6 Unused 5 Half step Step 4 Disable motors Enable motors 3 Y direction positive Y direction negative 2 Step Y motor No Y step 1 X direction positive X direction negative 0 Step X motor No X step The only difference between them is that Nbyte always has the motor step bits, 0 and 2, cleared whereas Pbyte will have the bits set for the motor(s) to be stepped. While this may seem wastefull, Nbyte gan easily be generated from Pbyte by doing an AND #$FA, at the time the hardware was not finalised and it was better to have the bits defined in the higher level routines and leave this routine as dumb as possible.
Starting a new line.The first thing the draw routine does, after saving all the registers, is to check if it was called by a timer event. If not it jumps to start a new line.
DrawIRQ PHA ; save A TXA ; copy X PHA ; save X TYA ; copy Y PHA ; save Y LDA Timer2r ; read and clear counter timer chip status BPL Startnext ; branch if we were idle (i.e. not drawing)Next it checks the buffer and, if there is a draw command present, branches to execute the command.Startnext JSR Increadb ; increment pointer and read byte from buffer BCC Newline ; branch if line to doNow the draw command is copied to local variables and the motor and pen latch is set up. Also the drawing flag is blindly set, it is quicker to do this than check the old state first.Newline STA Cbytel ; save step count low byte JSR Increadb ; increment pointer and read byte from buffer STA Cbyteh ; save step count high byte LDA #$FF ; flag drawing in progress STA drawf ; set draw flag JSR Increadb ; increment pointer and read byte from buffer STA Nbyte ; save latch negative byte. this is needed here because ; the step input MUST be low when changing mode or ; direction to avoid erroneous stepping. STA Mport ; byte out to port (set pen & directions) JSR Increadb ; increment pointer and read byte from buffer STA Pbyte ; save latch positive byteNext the pen state for this command is compared with the current pen state. If the pen state has changed then the routine sets up the delay value for the pen to complete its action.AND #$80 ; mask pen status bit CMP Lastp ; compare current pen mode with last pen mode STA Lastp ; set pen byte to new state BEQ Decrstep ; branch if pen bits = (go do count-1) ; pen state has changed so set the pen up/down delay & exit LDY #$01 ; get count for 8mS pen delay low byte LDX #$F8 ; get count for 8mS pen delay high byte BNE Exitdraw ; start count & exit (LDX is never 0 so branch always)If the pen state hasn't changed then the step count is decremented, the motor(s) are stepped and the delay value is set up for the motor(s) to complete the step.Decrstep LDA Cbytel ; get step count low byte BNE Lowonly ; skip high byte decrement if <>0 LDA Cbyteh ; get step count low byte BEQ Startnext ; branch if done (low & high bytes = 0) Highnlow DEC Cbyteh ; else decrement step count high byte Lowonly DEC Cbytel ; decrement step count low byte LDA Pbyte ; get latch positive byte STA Mport ; byte out to port LDA Nbyte ; get latch negative byte STA Mport ; byte out to port LDY #$01 ; set count for 3mS step delay low byte LDX #$24 ; set count for 3mS step delay high byteFinally, the timer delay value is set, ..Exitdraw STX Timer2h ; save timer 2 count high byte STY Timer2l ; save timer 2 count low byte LDA #$E3 ; set single shot count down STA Timer2r ; set timer BNE ResEXIT ; this was an interrupt service so exit properly.. the registers are restored and the routine exits.ResEXIT PLA ; pull Y TAY ; restore it PLA ; pull X TAX ; restore it PLA ; restore A ExitINT RTI ; this was an interrupt service so exit properlyContinuing a line.The routine saves the registers, as before, but, as we are already drawing, this time it drops through to the motor step code.
DrawIRQ PHA ; save A TXA ; copy X PHA ; save X TYA ; copy Y PHA ; save Y LDA Timer2r ; read and clear counter timer chip status BPL Startnext ; branch if we were idle (i.e. not drawing) LDA Timer2h ; clear counter timer chip timer 2 interrupt ; we were drawing so decrement count Decrstep ...The step count is then decremented, the motor(s) are stepped, the delay value is set up for the motor(s) to complete the step, and the routine exits as it did in the last section.Ending a line.
This time, after saving the registers and clearing the timer, the step count has reached zero so the routine branches to check for a new line.
Decrstep LDA Cbytel ; get step count low byte BNE Lowonly ; skip high byte decrement if <>0 LDA Cbyteh ; get step count low byte BEQ Startnext ; branch if done (low & high bytes = 0)On attempting to read from the buffer there is no new data waiting so the newline branch is not taken.Startnext JSR Increadb ; increment pointer and read byte from buffer BCC Newline ; branch if line to doSo this time the drawing flag is cleared, the pen is released and the motors are disabled to reduce dissipation in the driver stages.LDA #$00 ; clear byte STA drawf ; clear draw flag STA Lastp ; save as last pen byte LDA #$10 ; turn off the motors and set pen up STA Mport ; byte out to portLastly the registers are restored and the routine exits a s before.ResEXIT PLA ; pull Y TAY ; restore it PLA ; pull X TAX ; restore it PLA ; restore A ExitINT RTI ; this was an interrupt service so exit properly