[Return to top]

6502 Pen plotter - Drawing by Lee Davison
[Back]


Drawing.
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.

For this description it doesn't matter how the routine was called, that it was is all that matters.

Actions.
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.

This is by no means fast and the 6502 is quite adequate to fully control these actions.

Draw command format.
The draw commands are fetched from the buffer one at a time and have the format.

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
7Pen downPen up
6Unused
5Half stepStep
4Disable motorsEnable motors
3Y direction positiveY direction negative
2Step Y motorNo Y step
1X direction positiveX direction negative
0Step X motorNo 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.

Executing the command.
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 do
Now 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 byte
Next 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 byte
Finally, 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 properly
Continuing 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 do
So 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 port
Lastly 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

e-mail me [e-mail]
Last page update: 2nd May, 2002.