initial checkin of Apple 410 doc and driver

This commit is contained in:
Adam Mayer 2017-11-21 12:09:54 -05:00
commit 5aa05091c7
13 changed files with 532 additions and 0 deletions

19
10print.py Executable file
View File

@ -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")

50
apple410.py Executable file
View File

@ -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())

153
doc/Apple 401 Plotter.md Normal file
View File

@ -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 %?)

54
moore.py Executable file
View File

@ -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

105
plot_scripts/self_text.scr Normal file
View File

@ -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

112
plot_to_svg.py Executable file
View File

@ -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)

BIN
roms/B9801YL.bin Normal file

Binary file not shown.

BIN
roms/B9801YM.bin Normal file

Binary file not shown.

BIN
roms/B9801YN.bin Normal file

Binary file not shown.

10
roms/README.md Normal file
View File

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

BIN
roms/ROM.bin Normal file

Binary file not shown.

20
roms/test_extractor.py Executable file
View File

@ -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'))

9
send_to_plotter.py Executable file
View File

@ -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)))