initial checkin of Apple 410 doc and driver
This commit is contained in:
commit
5aa05091c7
|
@ -0,0 +1,19 @@
|
|||
#!/usr/bin/python3
|
||||
|
||||
import apple410
|
||||
import random
|
||||
import math
|
||||
|
||||
lineht=100
|
||||
# 2394 x 1700
|
||||
w=2394
|
||||
h=1700
|
||||
|
||||
a = apple410.Apple410('/dev/ttyUSB0')
|
||||
a.send("LS{}".format(lineht))
|
||||
ydim = math.floor(h/lineht)
|
||||
xdim = math.floor(w/lineht)
|
||||
for y in range(ydim):
|
||||
a.send("MA0,{}".format(lineht*y))
|
||||
a.send("PL"+''.join(map(lambda _:random.choice('\\/'),range(xdim))))
|
||||
a.send("CH")
|
|
@ -0,0 +1,50 @@
|
|||
#!/usr/bin/python3
|
||||
import serial
|
||||
import time
|
||||
import sys
|
||||
|
||||
class Apple410:
|
||||
"""A simple class for queing up commands for the Apple 410"""
|
||||
def __init__(self, portname, baud=1200):
|
||||
self.serial = serial.Serial(portname, baud, rtscts=True, dsrdtr=True, timeout=0.1)
|
||||
self.pos = (0,0)
|
||||
self.wd = self.vp = (0,0,2394,1759)
|
||||
|
||||
def sendchar(self, c):
|
||||
self.serial.flush()
|
||||
while not self.serial.cts:
|
||||
time.sleep(0.1)
|
||||
while not self.serial.dsr:
|
||||
time.sleep(0.1)
|
||||
self.serial.write(c.encode('ascii'))
|
||||
|
||||
def send(self, command):
|
||||
for c in command:
|
||||
self.sendchar(c)
|
||||
self.sendchar('\x03')
|
||||
|
||||
def move_to(self, coords):
|
||||
self.send('MA{},{}'.format(coords[0],coords[1]))
|
||||
self.pos = coords
|
||||
|
||||
def draw_to(self, coords):
|
||||
self.send('DA{:.2f},{:.2f}'.format(coords[0],coords[1]))
|
||||
self.pos = coords
|
||||
|
||||
def pen_select(self, index):
|
||||
self.send('PS{}'.format(index))
|
||||
|
||||
if __name__ == '__main__':
|
||||
scr = 'test_script.cmds'
|
||||
if len(sys.argv) > 1:
|
||||
scr = sys.argv[1]
|
||||
print("Running file {}".format(scr))
|
||||
a = Apple410('/dev/ttyUSB0')
|
||||
if scr == "-":
|
||||
f = sys.stdin
|
||||
else:
|
||||
f = open(scr)
|
||||
for line in f.readlines():
|
||||
print("SENDING: {}".format(line.strip()))
|
||||
a.send(line.strip())
|
||||
|
|
@ -0,0 +1,153 @@
|
|||
# The Apple 401 Color Plotter
|
||||
|
||||
## General concepts
|
||||
|
||||
The way the 401 interprets a coordinate at any given time depends on the current
|
||||
viewport and window settings. The "viewport" is a rectangle specified in hardware
|
||||
absolute coordinates and defines the physical area in which coordinates are resolved.
|
||||
The "window" defines the virtual coordinates of the drawing area, which are then
|
||||
mapped to the viewport.
|
||||
|
||||
Commands sent to the 401 are terminated by the ASCII "end of text" delimiter, 0x03.
|
||||
|
||||
### Serial connection
|
||||
|
||||
The 8 DIP switches (SW1-SW8) on the back of the plotter are used to configure the
|
||||
serial interface.
|
||||
|
||||
SW1 selects the data bits. ON = 7 bits, OFF = 8 bits.
|
||||
|
||||
SW2 selects whether parity is used. ON = no parity bit, OFF = use a parity bit.
|
||||
If parity is selected, SW3 determines the type of parity bit. ON = odd parity,
|
||||
OFF = even parity.
|
||||
|
||||
SW4 and SW5 select the number of stop bits:
|
||||
| SW4 | SW5 | Stop bits |
|
||||
|------|------|---------------|
|
||||
| OFF | OFF | 2 stop bits |
|
||||
| ON | OFF | 1.5 stop bits |
|
||||
| OFF | ON | 1 stop bit |
|
||||
| ON | ON | invalid |
|
||||
|
||||
SW6, SW7, and SW8 select the baud rate:
|
||||
| SW6 | SW7 | SW8 | Baud |
|
||||
|-----|-----|-----|------------|
|
||||
| OFF | OFF | OFF | 75 baud |
|
||||
| OFF | OFF | ON | 150 baud |
|
||||
| OFF | ON | OFF | 300 baud |
|
||||
| OFF | ON | ON | 600 baud |
|
||||
| ON | OFF | OFF | 1200 baud |
|
||||
| ON | OFF | ON | 2400 baud |
|
||||
| ON | ON | OFF | 4800 baud |
|
||||
| ON | ON | ON | 9600 baud |
|
||||
|
||||
**The 401 uses hardware handshaking.** It never returns data to the computer
|
||||
over the serial line and sets DSR when its internal buffer is full. Sending even
|
||||
one byte after the DSR line has been set will corrupt the 401's command buffer
|
||||
and trigger an error. Most USB to RS232 cables will attempt to bundle up several
|
||||
bytes before transmission, so unless you're sure that your cable can handle it
|
||||
I recommend flushing the serial connection after every byte and checking the DSR
|
||||
line yourself.
|
||||
|
||||
## Command Reference
|
||||
|
||||
### Move Absolute (MA)
|
||||
|
||||
```
|
||||
MAx,y
|
||||
```
|
||||
|
||||
Raise the pen and move the plotter head to the position specified by the x,y coordinates.
|
||||
|
||||
Params: 2
|
||||
|
||||
### ??Move relative (MR)??
|
||||
|
||||
```
|
||||
MRx,y
|
||||
```
|
||||
|
||||
Raise the pen and move the plotter head to a position offset x,y from its
|
||||
current position.
|
||||
|
||||
Params: 2
|
||||
### Draw absolute (DA)
|
||||
|
||||
```
|
||||
DAx,y(,x,y..)
|
||||
```
|
||||
|
||||
Lower the pen and draw a line from the current position to the position
|
||||
specified by the x,y coordinates. Continues
|
||||
|
||||
Params: arb.
|
||||
|
||||
==??Draw relative (DR)??==
|
||||
|
||||
DRx,y(,x,y)*
|
||||
|
||||
lower pen and draw line from current position to an x,y offset. Multiple
|
||||
offsets may be specified in a single command.
|
||||
|
||||
Params: arb.
|
||||
|
||||
==Circle (CA)==
|
||||
|
||||
CAr,x,y
|
||||
|
||||
lower pen and draw a circle of radius r centered at x,y.
|
||||
|
||||
Params: 3
|
||||
|
||||
==Letter size (LS)==
|
||||
|
||||
LSs
|
||||
|
||||
sets the font size to s
|
||||
|
||||
==Letter rotation (LR)==
|
||||
|
||||
LRtheta
|
||||
|
||||
draw following text rotated by theta
|
||||
in degrees, clockwise
|
||||
|
||||
==Print letters (PL)==
|
||||
|
||||
PLtext
|
||||
|
||||
lower pen and draw specified text at current position
|
||||
|
||||
==Pen select (PS)==
|
||||
|
||||
PSi
|
||||
|
||||
select pen i (where i is in 1-4)
|
||||
|
||||
|
||||
LT - Line type -
|
||||
1 solid
|
||||
2 ??
|
||||
3 ??
|
||||
4 ??
|
||||
5 ?? param
|
||||
6 ??
|
||||
|
||||
XT - ? xticks ? -- 5 params
|
||||
YT - ? yticks ? -- 5 params
|
||||
CH - ??? -- no params
|
||||
PM - Point Marking -- 1 param
|
||||
PS - pen select
|
||||
PV - ??? - 1 param
|
||||
WD - window - 4 params
|
||||
AC - arc - 5 params
|
||||
LI - ??? - no params/arb ???
|
||||
IM - ??? - 2 params
|
||||
PK - ??? - no params/arb
|
||||
RS - ??? - ?0x80 as params?
|
||||
VP - Viewport - 4 params
|
||||
UL - ??? - no params/arb ? 1 param 0-9?
|
||||
SP - ??? - 1 param ??
|
||||
LF - ??? - 1 param ? 0-9
|
||||
SL - ??? - 1 param -- Slanted lettering (by %?)
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
#!/usr/bin/python3
|
||||
from itertools import chain
|
||||
from math import sin,cos,pi
|
||||
import apple410
|
||||
|
||||
axiom = list("LFL+F+LFL")
|
||||
|
||||
rules = { 'L' : list("-RF+LFL+FR-"),
|
||||
'R' : list("+LF-RFR-FL+") }
|
||||
|
||||
def L_repl(c):
|
||||
global rules
|
||||
r = rules.get(c)
|
||||
if r:
|
||||
return r
|
||||
return [c]
|
||||
|
||||
def L_iter(system):
|
||||
a = map(L_repl,system)
|
||||
return list(chain.from_iterable(a))
|
||||
|
||||
def L_sys(system,depth):
|
||||
l = axiom
|
||||
for i in range(depth):
|
||||
l = L_iter(l)
|
||||
return l
|
||||
|
||||
seq=L_sys(axiom,5)
|
||||
|
||||
direction = 0.0
|
||||
theta = (pi/2)
|
||||
distance = 16
|
||||
location = (452.0,451.0)
|
||||
|
||||
def move(pos,angle,distance):
|
||||
nx = pos[0] + sin(angle)*distance
|
||||
ny = pos[1] + cos(angle)*distance
|
||||
return (nx,ny)
|
||||
|
||||
a = apple410.Apple410('/dev/ttyUSB0')
|
||||
a.pen_select(2)
|
||||
a.move_to(location)
|
||||
|
||||
for e in seq:
|
||||
if e == 'F':
|
||||
nl = move(location, direction, distance)
|
||||
a.draw_to(nl)
|
||||
print("DA {}".format(nl))
|
||||
location = nl
|
||||
elif e == '+':
|
||||
direction = direction + theta
|
||||
elif e == '-':
|
||||
direction = direction - theta
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
MA0,0
|
||||
DA2394,0,2394,1700,0,1700,0,0
|
||||
PS2
|
||||
WD0,0,150,140
|
||||
LS4
|
||||
MA7,6.6
|
||||
PLApple
|
||||
LS2
|
||||
PS3
|
||||
MA20,19.2
|
||||
LR30
|
||||
PLApple
|
||||
PS4
|
||||
MA30,28.8
|
||||
LR60
|
||||
PLApple
|
||||
PS1
|
||||
MA40,38.4
|
||||
LR90
|
||||
PLApple
|
||||
PS2
|
||||
MA50,48
|
||||
LR120
|
||||
PLApple
|
||||
PS3
|
||||
MA60,57.6
|
||||
LR150
|
||||
PLApple
|
||||
PS4
|
||||
MA70,67.2
|
||||
LR180
|
||||
PLApple
|
||||
PS1
|
||||
MA80,76.8
|
||||
LR210
|
||||
PLApple
|
||||
PS2
|
||||
MA90,86.4
|
||||
LR240
|
||||
PLApple
|
||||
PS3
|
||||
MA100,96
|
||||
LR270
|
||||
PLApple
|
||||
PS4
|
||||
MA110,105.6
|
||||
LR300
|
||||
PLApple
|
||||
PS1
|
||||
MA120,115.2
|
||||
LR330
|
||||
PLApple
|
||||
LS4
|
||||
PS2
|
||||
MA127,121.8
|
||||
LR360
|
||||
PLApple
|
||||
VP1500,250,2300,1050
|
||||
WD0,0,100,100
|
||||
PS1
|
||||
CA50,50,50
|
||||
CA10,50,50
|
||||
PS2
|
||||
CA25,75,50
|
||||
CA25,25,50
|
||||
AC25,0,180,50,75
|
||||
AC25,180,360,50,25
|
||||
PS3
|
||||
CA15,85,50
|
||||
CA15,50,15
|
||||
CA15,15,50
|
||||
CA15,50,85
|
||||
PS4
|
||||
CA10,90,50
|
||||
CA10,50,10
|
||||
CA10,10,50
|
||||
CA 10,50,90
|
||||
VP0,821,1197,1700
|
||||
WD0,0,140,100
|
||||
MA10,50
|
||||
XT3,120,12,2,2
|
||||
MA10,10
|
||||
YT1,80,8,4,0
|
||||
PS1
|
||||
LT5,10
|
||||
MA20,60
|
||||
DA30,90,40,20,50,20,60,10,70,80
|
||||
LT6
|
||||
PS2
|
||||
DA80,90,90,40,100,60,120,60,130,70
|
||||
VP900,100,1400,600
|
||||
WD0,0,100,100
|
||||
PS3
|
||||
LT1
|
||||
MA0,0
|
||||
DA0,100,10,100,0,0,20,100,30,100,0,0,40,100,50,100,0,0
|
||||
DA60,100,70,100,0,0,80,100,90,100,0,0,100,100,100,90
|
||||
DA0,0,100,80,100,70,0,0,100,60,100,50,0,0,100,40,100,30
|
||||
DA0,0,100,20,100,10,0,0,100,0
|
||||
VP0,0,2394,1759
|
||||
WD0,0,2394,1759
|
||||
MA1550,110
|
||||
LS30
|
||||
PLApple Color Plotter Test
|
||||
CH
|
|
@ -0,0 +1,112 @@
|
|||
#!/usr/bin/python3
|
||||
import sys
|
||||
import svgwrite
|
||||
import math
|
||||
|
||||
d = svgwrite.Drawing(profile='tiny')
|
||||
|
||||
|
||||
def getnum(line):
|
||||
pass
|
||||
|
||||
def coordlist(line,expected=-1):
|
||||
l = list(map(float,line.split(',')))
|
||||
if expected > -1:
|
||||
assert len(l) == expected
|
||||
return l
|
||||
|
||||
pens = [
|
||||
svgwrite.rgb(0, 0, 0, '%'),
|
||||
svgwrite.rgb(100, 0, 0, '%'),
|
||||
svgwrite.rgb(0, 100, 0, '%'),
|
||||
svgwrite.rgb(0, 0, 100, '%') ]
|
||||
|
||||
pennum = 0
|
||||
pos = (0.0,0.0)
|
||||
text_rotation = 0.0
|
||||
text_size = 1
|
||||
# "viewport"? part of the page to plot on
|
||||
vp = (0.0,0.0,2394,1759)
|
||||
# virtual size for viewport
|
||||
wd = (0.0,0.0,2394,1759)
|
||||
|
||||
def tcoord(x, vpl, vph, wdl, wdh):
|
||||
vpw = vph-vpl
|
||||
wdw = wdh-wdl
|
||||
nx = vpw*(x/wdw)
|
||||
return nx + vpl
|
||||
|
||||
def transform1(x):
|
||||
vpw = vp[2]-vp[0]
|
||||
wdw = wd[2]-wd[0]
|
||||
nx = vpw*(x/wdw)
|
||||
return nx
|
||||
|
||||
def transform(x,y):
|
||||
nx = tcoord(x,vp[0],vp[2],wd[0],wd[2])
|
||||
ny = tcoord(y,vp[1],vp[3],wd[1],wd[3])
|
||||
return nx, ny
|
||||
|
||||
for line in sys.stdin.readlines():
|
||||
line = line.strip()
|
||||
cmd = line[0:2]
|
||||
line = line[2:].strip()
|
||||
if cmd == 'MA':
|
||||
l=coordlist(line,2)
|
||||
pos=transform(l[0],l[1])
|
||||
elif cmd == 'VP': #viewport?
|
||||
l=coordlist(line,4)
|
||||
vp=(l[0],l[1],l[2],l[3])
|
||||
elif cmd == 'WD':
|
||||
l=coordlist(line,4)
|
||||
wd=(l[0],l[1],l[2],l[3])
|
||||
elif cmd == 'DA':
|
||||
l=coordlist(line)
|
||||
while len(l) > 0:
|
||||
nextp = transform(l[0],l[1])
|
||||
l = l[2:]
|
||||
d.add(d.line(pos,nextp,stroke=pens[pennum]))
|
||||
pos = nextp
|
||||
elif cmd == 'CA': #circle
|
||||
l=coordlist(line)
|
||||
r=transform1(l[0])
|
||||
p=transform(l[1],l[2])
|
||||
c=d.circle(center=p,r=r)
|
||||
c.fill(opacity=0)
|
||||
c.stroke(color=pens[pennum],opacity=100,width=2)
|
||||
d.add(c)
|
||||
elif cmd == 'AC': #arc
|
||||
l=coordlist(line)
|
||||
r=transform1(l[0])
|
||||
p=transform(l[3],l[4])
|
||||
t2, t1 = math.radians(l[1]),math.radians(l[2])
|
||||
x1, y1 = p[0] + (r*math.cos(t1)), p[1] + (r*math.sin(t1))
|
||||
x2, y2 = p[0] + (r*math.cos(t2)), p[1] + (r*math.sin(t2))
|
||||
ds="M {} {} A {} {} 0 0 0 {} {}".format(x1,y1,r,r,x2,y2)
|
||||
path=d.path(
|
||||
d=ds,
|
||||
stroke=pens[pennum])
|
||||
path.fill(opacity=0)
|
||||
d.add(path)
|
||||
elif cmd == 'LR':
|
||||
text_rotation = float(line)
|
||||
elif cmd == 'LS':
|
||||
text_size = float(line)
|
||||
elif cmd == 'PS':
|
||||
pennum = int(line) - 1
|
||||
#print("Switching to pen {}".format(pennum))
|
||||
elif cmd == 'PL':
|
||||
t = d.text(line,insert=pos)
|
||||
t.rotate(text_rotation,center=pos)
|
||||
d.add(t)
|
||||
pass
|
||||
else:
|
||||
#print("Command {}".format(cmd))
|
||||
pass
|
||||
|
||||
d.write(sys.stdout)
|
||||
|
||||
|
||||
|
||||
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,10 @@
|
|||
## Apple 410 ROM dumps
|
||||
|
||||
This directory contains the raw ROM dumps from the Apple 410 Color Plotter.
|
||||
|
||||
`B9801Y[LMN].bin` are images the three 8K 27C64 chips installed on the board. `ROM.bin` is the
|
||||
consolidated ROM image built by concatenating the three individual roms.
|
||||
|
||||
`test_extractor.py` is a script for extracting the self-test commands from the consolidated
|
||||
ROM file.
|
||||
|
Binary file not shown.
|
@ -0,0 +1,20 @@
|
|||
#!/usr/bin/python3
|
||||
import sys
|
||||
|
||||
st_start = 0x94b
|
||||
|
||||
assert len(sys.stdin.buffer.read(st_start)) == st_start
|
||||
|
||||
while 1:
|
||||
cmdlen = ord(sys.stdin.buffer.read(1))
|
||||
if cmdlen == 0:
|
||||
break
|
||||
cmd = sys.stdin.buffer.read(cmdlen)
|
||||
if cmd[-1] != 3:
|
||||
print("Invalid entry")
|
||||
break
|
||||
else:
|
||||
print(cmd[0:-1].decode('ascii'))
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
#!/usr/bin/python3
|
||||
import serial
|
||||
|
||||
print("opening port")
|
||||
s = serial.Serial("/dev/ttyUSB0", 9600, timeout=1)
|
||||
print("opened")
|
||||
s.write(b'PS4\x03')
|
||||
print("Sent pen select\n")
|
||||
print("recv {}\n".format(s.read(100)))
|
Loading…
Reference in New Issue