commit 5aa05091c7b081a89e1a942d0de6e40c9888daed Author: Adam Mayer Date: Tue Nov 21 12:09:54 2017 -0500 initial checkin of Apple 410 doc and driver diff --git a/10print.py b/10print.py new file mode 100755 index 0000000..a4b3ef9 --- /dev/null +++ b/10print.py @@ -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") diff --git a/apple410.py b/apple410.py new file mode 100755 index 0000000..7482588 --- /dev/null +++ b/apple410.py @@ -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()) + diff --git a/doc/Apple 401 Plotter.md b/doc/Apple 401 Plotter.md new file mode 100644 index 0000000..3f7ffbc --- /dev/null +++ b/doc/Apple 401 Plotter.md @@ -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 %?) + diff --git a/moore.py b/moore.py new file mode 100755 index 0000000..cc29928 --- /dev/null +++ b/moore.py @@ -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 + diff --git a/plot_scripts/self_text.scr b/plot_scripts/self_text.scr new file mode 100644 index 0000000..e64404d --- /dev/null +++ b/plot_scripts/self_text.scr @@ -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 diff --git a/plot_to_svg.py b/plot_to_svg.py new file mode 100755 index 0000000..27bd77d --- /dev/null +++ b/plot_to_svg.py @@ -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) + + + + + diff --git a/roms/B9801YL.bin b/roms/B9801YL.bin new file mode 100644 index 0000000..3fb0127 Binary files /dev/null and b/roms/B9801YL.bin differ diff --git a/roms/B9801YM.bin b/roms/B9801YM.bin new file mode 100644 index 0000000..0388029 Binary files /dev/null and b/roms/B9801YM.bin differ diff --git a/roms/B9801YN.bin b/roms/B9801YN.bin new file mode 100644 index 0000000..37c10a9 Binary files /dev/null and b/roms/B9801YN.bin differ diff --git a/roms/README.md b/roms/README.md new file mode 100644 index 0000000..740be97 --- /dev/null +++ b/roms/README.md @@ -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. + diff --git a/roms/ROM.bin b/roms/ROM.bin new file mode 100644 index 0000000..74873a2 Binary files /dev/null and b/roms/ROM.bin differ diff --git a/roms/test_extractor.py b/roms/test_extractor.py new file mode 100755 index 0000000..2c82ea4 --- /dev/null +++ b/roms/test_extractor.py @@ -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')) + + + diff --git a/send_to_plotter.py b/send_to_plotter.py new file mode 100755 index 0000000..ea65809 --- /dev/null +++ b/send_to_plotter.py @@ -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)))