Amstrad CPC driver/demo for TA Gabriele 9009, credited to K.H. Weiß in CPC Schneider International magazine, December 1985, pp88-89: https://www.cpcwiki.eu/imgs/6/6c/CPCAI-85-12_Page_88.jpg https://www.cpcwiki.eu/imgs/c/cf/CPCAI-85-12_Page_89.jpg Transcribed, roughly translated and amateurishly reversed by BALJ. E&OE, beware my schoolboy German skills, try not to set fire to your typewriter. Notes on what this thing is actually doing appear after the listing. 1 ' English translation, BALJ 2023-06-01 2 ' (Original German version at bottom of file) 5 ' Type table with strike strength and type width 10 DATA 00,0C,3E,64,2B,83,4C,C7,27,A7,43,E6,22,E7,0E,A1 11 DATA 3A,64,31,64,29,A4,0F,A3,02,62,03,82,01,61,3C,84 12 DATA 19,A5,10,A4,11,A5,12,A5,13,A5,14,A5,15,A5,16,A4 13 DATA 17,A6,18,A5,36,63,35,63,4A,A4,34,A4,0D,A6,2D,A5 14 DATA 40,A7,24,E7,1C,C7,2A,E6,2C,E7,1A,C6,1D,C5,45,E7 15 DATA 39,E6,2F,64,42,A5,3B,E6,26,C5,37,07,2E,E7,3D,E7 16 DATA 1E,C7,41,E7,28,E7,1F,A6,25,C5,30,E7,21,C5,32,07 17 DATA 3F,E5,23,E5,20,C6,4B,E7,49,E7,47,E7,0C,A2,33,A3 18 DATA 44,A1,5E,A5,62,A6,58,A4,60,A6,5D,A5,0B,84,59,A6 19 DATA 57,A5,5F,64,07,64,55,A5,05,64,06,06,5A,A5,63,A5 20 DATA 56,A6,4F,A6,5B,84,5C,85,4D,84,61,A5,04,A4,08,E5 21 DATA 4E,A4,54,A5,64,A4,53,A6,52,A6,51,A6,50,A5,0A,A6 22 DATA 46,83,09,A5,38,62,48,A4,50,A5,1B,A7,49 24 ' 25 ' Machine code routine: send 2 bytes at 4800 baud 30 DATA DD,4E,00,06,01,C5,06,F5,ED,78,17,17,38,FA,06,EF 31 DATA 3E,80,16,09,F3,1E,31,E6,80,ED,79,1D,20,FD,79,0F 32 DATA 4F,2F,15,20,F0,3E,00,1E,31,ED,79,1D,20,FD,06,F5 33 DATA ED,78,17,17,30,FA,FB,C1,DD,4E,01,05,28,C7,C9,134 99 ' 100 ' Read type table and machine code into string variables 110 typ$=STRING$(204, 0): send$=STRING$(63‚ 0): a=FRE("") 115 ' typ and send are the actual starting addresses of the table and routine 120 typ=PEEK(@typ$+1)+256*PEEK(@typ$+2): send=PEEK(@send$+1)+256*PEEK(@send$+2) 130 checksum=0 140 FOR I=1 TO 204 150 READ a$: a=VAL("&"+a$): MID$(typ$, ¡)=CHR$(a) 160 checksum=(checksum+a) AND 255 170 NEXT i 180 READ a: IF a<>checksum THEN PRINT "Data checksum error": STOP 190 checksum=0 200 FOR i=1 TO 63 210 READ a$: a=VAL("&"+a$): MID$(send$, ¡)=CHR$(a) 220 checksum=(checksum+a) AND 255 230 NEXT i 240 READ a: IF a<›checksum THEN PRINT "Code checksum error": STOP 249 ' 250 ' Test lines for demonstration: 260 a$="Line spacing, 1/1.5/2/2.5 lines:"+CHR$(4):GOSUB 300 261 a$="1 line"+CHR$(5):GOSUB 300 262 a$="1.5 lines"+CHR$(6):GOSUB 300 263 a$="2 lines"+CHR$(7):GOSUB 300 264 a$="2.5 lines"+CHR$(4):GOSUB 300 265 a$="Character spacing: "+CHR$(1)+"15 cpi "+CHR$(2)+"12 cpi" 266 a$=a$+CHR$(3)+" 10 cpi":GOSUB 300 267 a$="Examples of exponents: A=pi*r"+CHR$(8)+"2"+CHR$(9) 268 a$=a$+" and indicies: F=n*dX"+CHR$(9)+"¡"+CHR$(8):GOSUB 300 269 a$= "and finally, "+CHR$(10)+" underlining" +CHR$(10)+"of words." 270 GOSUB 300 271 ' Take typewriter offline and end the demo. 272 a$=CHR$(0):GOSUB 300:STOP 299 ' 300 ' Print output to Gabriele 9009 daisywheel typewriter 310 ' Line to be printed is in a$. 320 ' Control strings: a$=CHR$(0) Take machine offline 330 ' a$=...+CHR$(1-3)+... Set 15/12/10 characters per inch 340 ' a$=...+CHR$(4-7)+... Set 1/1.5/2/2.5 line spacing 350 ' a$=...+CHR$(8)+... Quarter line reverse feed (paper down) 360 ' a$=...+CHR$(9)+... Quarter line forward feed (paper up) 370 ' a$=...+CHR$(10)+... Toggle underline on/off 380 ' Control codes other than 0 can appear in the middle of a line. 390 ' Underline is automatically cancelled at the end of the line; 400 ' all other codes apply until changed again. 410 IF a$=CHR$(0) THEN CALL send, &A0: MID$(typ$, 1)=CHR$(0): RETURN 420 kPOS=0: IF LEFT$(typ$, 1)<>CHR$(0) THEN 460 430 MID$(typ$, 1)=CHR$(16): ' Underline and online flags, line spacing 440 CALL send, &A1: CALL send, &A4: CALL send, &A2: CALL send, &1F82: CALL send, &78C0 450 MID$(typ$, 2)=CHR$(12): ' Character spacing 460 FOR i=1 TO LEN(a$) 470 a=ASC(MID$(a$, i)) 480 IF a<33 THEN 600 ELSE a=a-32 490 IF a>100 THEN RETURN: ' Character out of range (not on the type wheel) 500 a=ASC(MID$(typ$, 2*a+1)) + 256*((ASC(MID$(typ$, 2*a+2)) AND 15) + &90) 510 IF ASC(LEFT$(typ$, 1)) AND 128 THEN CALL send, &1333: ' Do underline? 520 CALL send, a: kPOS = kPOS + ASC(MID$(typ$, 2)): ' Print character, update head position. 530 NEXT I 535 ' Carriage return and linefeed at end of line 540 MID$(typ$, 1)=CHR$(ASC(LEFT$(typ$, 1)) AND 127): ' Turn underlining off 550 kPOS = (INT(kPOS/256) OR &E0) + 256*(kPOS AND 255): ' Number of steps for carriage return 560 a = 256*(ASC(MID$(typ$, 1)) AND 127) + &D0: ' Number of steps for linefeed 570 CALL send, kPOS:CALL send, a:'Return-Linefeed 580 RETURN 600 ' Handle control codes 610 IF a<>32 THEN 630: ' Space? 620 a = &C0 + 256*ASC(MID$(typ$, 2)): GOTO 510 630 IF a<>10 THEN 650: ' Toggle underline 640 MID$(typ$, 1)=CHR$(128 XOR ASC(MID$(typ$, 1))): GOTO 530 650 IF a<>9 THEN 670: ' Roller to "Index" position? [BALJ - 1/4 feed forwards] 660 CALL send, &4D0: GOTO 530 670 IF a<>8 THEN 690: ' Roller to "Exponent" position? [BALJ - 1/4 feed backwards] 680 CALL send, &4F0: GOTO 530 690 IF a<4 THEN 710: ' Set line spacing? 700 MID$(typ$, 1)=CHR$(8*(a-4)+16): GOTO 530 710 IF a>10 OR a=0 THEN 800: ' Set character pitch? 711 ' [BALJ - original had "other codes, e.g. bold type", which is backwards in sense from the other comments!] 720 MID$(typ$, 2)=CHR$(2*(a-1)+8) 730 CALL send, &80 + 256*ASC(MID$(typ$, 2)): GOTO 530 800 ' Add more control codes here! [BALJ - "e.g. bold type", say] 801 GOTO 530: '(codes <1 or >10 are not implemented) The machine language routine disassembles as follows: entry: LD C,(IX+0) ; #DD,#4E,#00 ; get low byte of parameter into C LD B,#01 ; #06,#01 ; remember we have a byte left reentry: PUSH BC ; #C5 LD B,#F5 ; #06,#F5 ; F5xx = CPC PIO Port B busy_1: IN A,(C) ; #ED,#78 RLA ; #17 ; rotate bit 6 (printer BUSY) RLA ; #17 ; into carry flag JR C,busy_1 ; #38,#FA ; wait until it's clear LD B,#EF ; #06,#EF ; EFxx = CPC printer port output LD A,#80 ; #3E,#80 ; bit 7 -> #STROBE LD D,#09 ; #16,#09 ; send 9 bits, counting start bit DI ; #F3 ; interrupts off please! send_bit: LD E,#31 ; #1E,#31 ; #31 is delay constant for 4800 baud AND #80 ; #E6,#80 ; mask off next bit to send OUT (C),A ; #ED,#79 ; push it out the #STROBE line baud_1: DEC E ; #1D ; delay loop JR NZ,baud_1 ; #20,#FD LD A,C ; #79 ; get the byte we're sending back RRCA ; #0F ; next lowest bit round to the top LD C,A ; #4F ; and remember for next time CPL ; #2F ; invert (since #STROBE is inverted) DEC D ; #15 JR NZ,send_bit ; #20,#F0 ; send the bit unless we've run out LD A,#00 ; #3E,#00 ; now send the stop bit LD E,#31 ; #1E,#31 ; with another bit-time's delay loop OUT (C),A ; #ED,#79 baud_2: DEC E ; #1D JR NZ,baud_2 ; #20,#FD LD B,#F5 ; #06,#F5 ; wait for BUSY line to be clear again busy_2: IN A,(C) ; #ED,#78 RLA ; #17 RLA ; #17 JR NC,busy_2 ; #30,#FA EI ; #FB ; interrupts OK now POP BC ; #C1 ; retrieve our first-byte flag LD C,(IX+1) ; #DD,#4E,#01 ; get high byte of parameter DEC B ; #05 JR Z,reentry ; #28,#C7 ; and go a second time if needed RET ; #C9 ; ... or back to BASIC if not ... which appears to take a 16-bit parameter and sends it out of the printer port's STROBE line at 4800 baud, low byte first. For clarity, from hereon I'll quote 16-bit words byte-swapped so that they read in the order they're actually sent, i.e. where the BASIC looks to be sending '&C0 + 256*&12' that does indeed go on the wire as &C0 &12. This makes reasoning about the bitfields rather easier! Taking the machine offline appears to be done by sending &A000. It's brought online automatically when the first line is printed, by sending: &A100 &A400 &A200 &821F &C078 ... at which point the code resets its internal state to single line spacing, underline off, 10 characters per inch. Other commands sent by the driver appear to be: - &80xx - set character pitch to xx (8 => 1/15 inch, 10 => 1/12 inch, 12 => 1/10 inch, so units are 1/120") - &C0xx - move carriage forwards by xx (same units as &80xx) - &Exxx - move carriage backwards by xxx (same units as &80xx) - &D0xx - advance paper by xx (4 => 1/4 line, 16 => 1 line, 24 => 1.5 lines, so units are 1/16 line; manual suggests 6 lines per inch so 1/96"?) - &F0xx - retract paper by xx (same units as &D0xx) The &Exxx command is used for carriage-return at the end of the line; the kPOS variable keeps a running sum of all the character widths sent on the line so far, as determined from current pitch held in the second byte of typ$. A &D0xx command follows immediately afterwards as a line-feed, with spacing determined by the low seven bits of the first byte of typ$. The top bit of the first byte of typ$ holds the underline on/off flag. Printing characters: The 'typ' table maps ASCII character codes to values to be sent as follows: - table is organised as pairs of (hex) bytes, starting at ASCII code 32 (space) - the first pair is used as internal state since space is non-printing - the second pair therefore corresponds to ASCII 33, ! - the first byte in each pair is the low byte, the second the high - this means that it can be read directly in the order bytes go out on the wire, e.g. the second pair is 3E, 64 -> &3E64 (following my convention here) - but the top four bits of the second byte are masked off and replaced with &90 - so ! gets turned into &3E94 &3313 appears to be sent to print an underline; as a sanity check, ASCII 95 (_) would be the 63rd pair in the table, which is &33A3. After the masking and addition that give &3393. That implies that the &0080 bit indicates whether to advance the carriage after striking the character? None of the character codes have the &8000 bit set, which lines up with the commentary in the article that this indicates whether it's a character to be struck or a motor movement command. Plenty of character codes have data encoded in the nibble that's masked off, though, which is presumably something to do with the type widths mentioned in the comment; the article mentions proportional printing so perhaps clearing the &0080 bit and then advancing the carriage using &C0xx would achieve this, if we can work out how the width is encoded. Original listing in German follows, OCRed and fixed up from the scans above; 5 'Typentabelle mit Anschlgstaerke u. Typenbreite 10 DATA 00,0C,3E,64,2B,83,4C,C7,27,A7,43,E6,22,E7,0E,A1 11 DATA 3A,64,31,64,29,A4,0F,A3,02,62,03,82,01,61,3C,84 12 DATA 19,A5,10,A4,11,A5,12,A5,13,A5,14,A5,15,A5,16,A4 13 DATA 17,A6,18,A5,36,63,35,63,4A,A4,34,A4,0D,A6,2D,A5 14 DATA 40,A7,24,E7,1C,C7,2A,E6,2C,E7,1A,C6,1D,C5,45,E7 15 DATA 39,E6,2F,64,42,A5,3B,E6,26,C5,37,07,2E,E7,3D,E7 16 DATA 1E,C7,41,E7,28,E7,1F,A6,25,C5,30,E7,21,C5,32,07 17 DATA 3F,E5,23,E5,20,C6,4B,E7,49,E7,47,E7,0C,A2,33,A3 18 DATA 44,A1,5E,A5,62,A6,58,A4,60,A6,5D,A5,0B,84,59,A6 19 DATA 57,A5,5F,64,07,64,55,A5,05,64,06,06,5A,A5,63,A5 20 DATA 56,A6,4F,A6,5B,84,5C,85,4D,84,61,A5,04,A4,08,E5 21 DATA 4E,A4,54,A5,64,A4,53,A6,52,A6,51,A6,50,A5,0A,A6 22 DATA 46,83,09,A5,38,62,48,A4,50,A5,1B,A7,49 25 'm-code: 2 Bytes mit 4800 Baud senden 30 DATA DD,4E,00,06,01,C5,06,F5,ED,78,17,17,38,FA,06,EF 31 DATA 3E,80,16,09,F3,1E,31,E6,80,ED,79,1D,20,FD,79,0F 32 DATA 4F,2F,15,20,F0,3E,00,1E,31,ED,79,1D,20,FD,06,F5 33 DATA ED,78,17,17,30,FA,FB,C1,DD,4E,01,05,28,C7,C9,134 100 'Typentabelle und Maschinenprogramm in Stringvariablen laden: 110 typ$=STRING$(204,0):send$=STRING$(63‚0):a=FRE("") 115 'typ und send sind die aktuellen Anfangsadressen von Tabelle und Programm: 120 typ=PEEK(@typ$+1)+256*PEEK(@typ$+2):send=PEEK(@send$+1)+256*PEEK(@send$+2) 130 checksum=0 140 FOR I=1 TO 204 150 READ a$:a=VAL("&"+a$):MID$(typ$,¡)=CHR$(a) 160 checksum=(checksum+a)AND 255 170 NEXT i 180 READ a:IF a<>checksum THEN PRINT"Data-Fehler":STOP 190 checksum=0 200 FOR i=1 TO 63 210 READ a$:a=VAL("&"+a$):MID$(send$,¡)=CHR$(a) 220 checksum=(checksum+a)AND 255 230 NEXT i 240 READ a:IF a<›checksum THEN PRINT"Data-Fehler":STOP 250 'Testzeilen zur Demonstration: 260 a$="Zeilenabstand 1- 1.5- 2- 2.5- eilig:"+CHR$(4):GOSUB 300:' eilig[sic] - BALJ 261 a$="1-zeilig"+CHR$(5):GOSUB 300 262 a$="1.5-zeilig"+CHR$(6):GOSUB 300 263 a$="2-zeilig"+CHR$(7):GOSUB 300 264 a$="2.5-zeilig"+CHR$(4):GOSUB 300 265 a$="Zeichenabstand: "+CHR$(1)+"15 Zeichen/Zoll "+CHR$(2)+"12 Zeichen/Zoll" 266 a$=a$+CHR$(3)+" 10 Zeichen/Zoll":GOSUB 300 267 a$="Beispiel fuer Exponent: A=pi*r"+CHR$(8)+"2"+CHR$(9) 268 a$=a$+" und Index: F=n*dX"+CHR$(9)+"¡"+CHR$(8):GOSUB 300 269 a$= "und als letztes"+CHR$(10)+" Unterstreichen" +CHR$(10)+"von Worten." 270 GOSUB 300 271 'Maschine Offline schalten und Demo-Stop: 272 a$=CHR$(0):GOSUB 300:STOP 300 'Druckausgabe auf Typenradmaschine "Gabriele 9009" 310 'Zeile wird in a$ uebergeben. 320 'Steuerstrings: a$=chr$(0) Maschine Offline schalten 330 ' a$=...+CHR$(1-3)+... 15-12-10 Zeichen/Zoll 340 ' a$=...+CHR$(4-7)+... 1- 1.5- 2- 2.5- Zelig schreiben 350 ' a$=...+CHR$(8)+... Walze 1/4 Zeile zurueck 360 ' a$=...+CHR$(9)+... Walze 1/4 Vorwaerts drehen 370 ' a$=...+CHR$(10)+... Unterstreichen Ein/Ausschalten 380 'Die Steuercodes >0 koennen mitten in der Zeile stehen. Unterstreichen 390 'wird am Zeilenende automatisch wieder aufqehoben. Alle anderen Codes 400 'gelten bis zur erneuten Definition! 410 IF a$=CHR$(0) THEN CALL send, &A0:MID$(typ$,1)=CHR$(0):RETURN 420 kPOS=0:IF LEFT$(typ$,1)<>CHR$(0) THEN 460 430 MID$(typ$,1)=CHR$(16):'Zeilenabstand und On-Line-Flag 440 CALL send,&A1:CALL send,&A4:CALL send,&A2:CALL send,&1F82:CALL send,&78C0 450 MID$(typ$,2)=CHR$(12):'Zeichenabstand 460 FOR i=1 TO LEN(a$) 470 a=ASC(MID$(a$,i)) 480 IF a<33 THEN 600 ELSE a=a-32 490 IF a>100 THEN RETURN:'nicht mehr auf dem Typenrad! 500 a=ASC(MID$(typ$,2*a+1))+256*((ASC(MID$(typ$,2*a+2))AND 15)+&90) 510 IF ASC(LEFT$(typ$,1))AND 128 THEN CALL send, &1333: 'unterstreichen 520 CALL send, a:kPOS=kPOS+ASC(MID$(typ$, 2)):'Zeichenausg., neue Kopfpos. 530 NEXT I 535 'Druckkopf an den Anfang der Folgezeile bewegen: 540 MID$(typ$,1)=CHR$(ASC(LEFT$(typ$,1))AND 127):'Unterstreichen aus 550 kPOS=(INT(kPOS/256 )OR &E0)+256*(kPOS AND 255):'Schrittzahl fuer Ruecklauf 560 a=256*(ASC(MID$(typ$,1))AND 127)+&D0:'Schrittzahl fuer Linefeed 570 CALL send, kPOS:CALL send, a:'Return-Linefeed 580 RETURN 600 'Steuercodes auswerten: 610 IF a<>32 THEN 630:'leerzeichen 620 a=&C0+256*ASC(MID$(typ$,2)):GOTO 510 630 IF a<>10 THEN 650:'Unterstreichen ein-oder aus 640 MID$(typ$,1)=CHR$(128 XOR ASC(MID$(typ$,1))):GOTO 530 650 IF a<>9 THEN 670:'Walze in "Index-Stellung" 660 CALL send, &4D0:GOTO 530 670 IF a<>8 THEN 690:'Walze in "Exponenten-Stellung" 680 CALL send, &4F0:GOTO 530 690 IF a<4 THEN 710:'Zeilenabstand 700 MID$(typ$,1)=CHR$(8*(a-4)+16):GOTO 530 710 IF a>10 OR a=0 THEN 800:'weitere Codes, z. B. Fettdruck 720 MID$(typ$,2)=CHR$(2*(a-1)+8) 730 CALL send,&80+256*ASC(MID$(typ$,2)):GOTO 530 800 'Ab hier weitere Steuercodes anhaengen! 801 GOTO 530:'(<1 oder >10 Nicht implementiert)