Skip to content

PLM1800 and CDOS – A Blast From The Past

Thanks to a ton of work by a bunch of people on the Cosmac Elf mailing list, we have resurrected an operating system and a couple of compilers from the early days of the 1802.  Just getting the files off the old 3.5″ diskettes was a feat in itself. I think David Schultz did the work for Microdos and BASIC and Hank Riley for PLM. David went on to work through the file format and adapt an emulator to support it – His work is here. Marcel Tongren integrated the diskette support into the Emma 02 Emulator.

The PLM compiler compiles a PL/1-like language and was meant to run on the RCA Microdos system with 60K of memory.  I’m running microdos in the Emma 02 emulator and it’s a treat with a full set of dos-type commands: DIR, DEL, COPY etc besides the BASIC and PLM compilers and the ASM8 assembler.

16-12-12-plm-compile
Compiling the program is a multi-step process. You run the compiler, merge the compiler output with the PLMLIB support routines, assemble the result with ASM8, then use a utility program CDSBIN to extract the binary code from the listing. The :1’s and :2’s are telling MicroDOS which drive the files are on :0 is the operating system, :1 is the PLM disk, and :2 is emulated on a windows directory.  The last couple of commands copy  the output .BIN and .LST files to the windows directory.

On windows I converted the raw binary in the .BIN file to intel hex and the uploaded it to the olduino with avrdude.  The blink program uses a PLM built-in routine TIME which is generates a delay of 90+10N where N is the parameter.  By happy coincidence 90+327670 cycles is around 1/2 sec on my 4mhz olduino so it makes a nice blink as you can see below. I think everyone working on the recovery of MicroDOS and the compilers has been working on emulators so I imagine this is the first time that PLM code has run on hardware since the 80’s.

The code, at first glance looks pretty basic. Seems to be mostly generated subroutine calls rather than inline code, maybe a stack machine underneath?

0409 ;             0160
0409 ;             0161 MAIN   ORG  $
0409 ;             0162 ..00001 01 DO;
0409 ;             0163 ..00002 02
  DECLARE T BYTE INITIAL(1);
0409 ;             0164 Z09000  EQU       $
0409 ;             0165        ORG       Z10000
046A 01;           0166          DC       00001
046B ;             0167        ORG       Z09000
0409 ;             0168 ..00003 02
  DO WHILE T>0;
0409 ;             0169 Z01000 ORG  $
0409 D40287;       0170        CALL LOAD1
040C 046A;         0171          DC      Z10000
040E D40255;       0172        CALL COMPOP
0411 044F;         0173          DC      Z00050
0413 CB041E;       0174        LBNF     $+00011
0416 D40063;       0175        CALL SMOP
0419 044F;         0176          DC      Z00050
041B D4026F;       0177        CALL TEST
041E C0044C;       0178        LBR       Z01001
0421 ;             0179 Z01002 ORG  $
0421 ;             0180 ..00004 03
    Q=1;
0421 D4022D;       0181        CALL LOADOP
0424 0451;         0182          DC      Z00051
0426 8F;           0183        GLO  RF
0427 F6;           0184        SHR
0428 CB042F;       0185        LBNF     $+00007
042B 7B;           0186        SEQ
042C C00430;       0187        LBR      $+00004
042F 7A;           0188        REQ
0430 ;             0189 ..00005 03
    CALL TIME(32767);
0430 D40236;       0190        CALL TIMDEL
0433 0455;         0191          DC      Z00053
0435 ;             0192 ..00006 03
    Q=0;
0435 D4022D;       0193        CALL LOADOP
0438 044F;         0194          DC      Z00050
043A 8F;           0195        GLO  RF
043B F6;           0196        SHR
043C CB0443;       0197        LBNF     $+00007
043F 7B;           0198        SEQ
0440 C00444;       0199        LBR      $+00004
0443 7A;           0200        REQ
0444 ;             0201 ..00007 03
    CALL TIME(32767);
0444 D40236;       0202        CALL TIMDEL
0447 0455;         0203          DC      Z00053
0449 ;             0204 ..00008 03
  END;
0449 C00409;       0205        LBR       Z01000
044C ;             0206 Z09001  EQU       $
044C ;             0207        ORG       Z00053
0455 7FFF;         0208          DC       32767
0457 ;             0209        ORG       Z09001
044C ;             0210 Z01001 ORG  $
044C ;             0211 ..00009 02
END;
044C ;             0212 ..00010 01
EOF
044C C00409;       0213 Z00001 LBR        MAIN
044F 00;           0214 Z00050   DC 00H
0450 00;           0215          DC 0
0451 00;           0216 Z00051   DC 00H
0452 01;           0217          DC       00001
0453 00;           0218 Z00052   DC 00H
0454 02;           0219          DC       00002
0455 ;             0220 Z00053 ORG      $+00002
0457 ;             0221 Z00054 ORG      $+00002
0459 0000;         0222 Z00003   DC 0000H
045B 0000;         0223 Z00004   DC 0000H
045D 0000;         0224 Z00005   DC 0000H
045F 0000;         0225 Z00006   DC 0000H
0461 0000;         0226 Z00007   DC 0000H
0463 0000;         0227 Z00008   DC 0000H
0465 0000;         0228 Z00009   DC 0000H
0467 0000;         0229 Z00010   DC 0000H
0469 ;             0230 Z09999 ORG      $+00001
046A ;             0231 Z10000 ORG      $+00001
046B ;             0232        PAGE
0500 ;             0233        ORG      $+00255
05FF ;             0234 STACK  ORG      $+00001
0600 ;             0235 Z30000 ORG  $
0600 ;             0236 MEMORY ORG  $
0600 ;             0237        END 

 

Edited to change references to CDOS to MicroDOS. CDOS was an even earlier RCA operating system for the 1802 which did in fact use 8″ diskettes.

TP3465V 16 Bit Mode to the 2.2″ TFT LCD

The Elecfreaks LCD is very nice but each of the 320X240 pixels requires you to send 16 bits over SPI.  Even an arduino takes a noticeable length of time to clear the screen and for the olduino it can be quite long.  I got 16 bit mode working a while ago and finally worked it into a screen clearing routine today.  With an 8 bit transfer routine coded in assembler it took right around .9 seconds to clear the screen.  With a carefully crafted 16 bit clear routine it takes about .6 sec.  The fundamental reason for it is that I have to leave a nop gap between pairs of writes to the SPI chip so I chew up an extra instruction there.  Then, even though I’ve unrolled the loop there’s some overhead, and the timer interrupt steals some cycles each ms.  The result is that the best i can do is a bit over 2 mb/sec rather than the theoretical 4 mb.

The first routine below is an assembly language fill routine that uses 8 bit mode. It gets passed the count as well as the value to send. Knowing it’s always 2 bytes/pixel it does double up the loop payload. The second routine uses 16 bit mode and sends 8 bytes per loop pass.

 

void zfilla(unsigned int n, unsigned char c){ //send n copies of c
	__asm
	;prolog will have prepped ix
	ld	e,(ix+4)
	ld	d,(ix+5)
	srl d			;divide de
	rr	e		;by 2
; here count/2 is in de, value is at (ix+6)
; now we want lsb of count, msb+1(*) in d (* unless already 0)
	ld b,e          ; Number of loops is in DE
	dec de          ; Calculate DB value (destroys B, D and E)
	inc d
	ld a,(ix+6)
l_zfilla_00101:
		out (0x81),a
		nop
		out (0x81),a
	djnz l_zfilla_00101
	dec d
	jp nz,l_zfilla_00101
	;epilog will restore ix and return
	__endasm;
n;c; //refs to keep compiler happy
}
void zclear(char c){ //fill the lcd by sending 153600 c's
	__asm
	ld	de,#19200 ;153600/8

; here count is in de, value is at (ix+4)
; now we want lsb of count, msb+1(*) in d (* unless already 0)
	ld b,e          ; Number of loops is in DE
	dec de          ; Calculate DB value (destroys B, D and E)
	inc d

	ld	a,0xFF	;set channel 0 to 16 bits
	out (0x8c),a ;by writing to the MWM bit
	nop

	ld a,(ix+4)
l_zclear_00101:
		out (0x80),a ;second byte
		out (0x81),a ;first byte
		nop
		out (0x80),a ;second byte
		out (0x81),a ;first byte
		nop
		out (0x80),a ;second byte
		out (0x81),a ;first byte
		nop
		out (0x80),a ;second byte
		out (0x81),a ;first byte
	djnz l_zclear_00101
	dec d
	jp nz,l_zclear_00101

	ld	a,0x00	;set channel 0 to 8 bits
	out (0x8c),a

	;epilog will restore ix and return
	__endasm;
c; //refs to keep compiler happy
}

I always want the last word so that wordpress doesn’t eat my code!

TP3465V 16 Bit Mode Works Just Fine

16-08-26 16 bit working

This has been quietly bugging me for months.  I tried 16 bit mode on the TP3465V spi/microwire chip and just saw munged bits output.  I thought about timing issues but it just seemed unlikely that it wouldn’t work.  I went back to the data sheet and re-wrote simpler code and discovered my error: I was writing the bytes in the wrong sequence! You have to “Write the LOW data byte in the SMB register and then write the HIGH data byte into the FMB byte location. All 16 data bits are shifted out after the trailing edge of the Write strobe for the FMB register”

I’ll have to go back to my lcd screen fill code to see if it makes much difference in application.


void bit16(){
__asm

ld a,0xFF ;set channel 0 to 16 bits
out (0x8c),a ;by writing to the MWM bit

ld a,0x34
out (0x80),a ;second byte

ld a,0x12
out (0x81),a ;first

ld a,0x00 ;set channel 0 to 8 bits
out (0x8c),a
__endasm;
}

I always want the last word so that wordpress doesn’t eat my code!

A bit disappointed in the Z80 SPI speed

Clearing the TFT LCD just takes a lot of data transfer.  For each of the 240*320 pixels you have to send TWO 8 bit bytes. so that’s 1.2MB.  At 4mhz then, the best you could hope for is 1/3 second or so.  I can’t get anywhere near that though.  The naive fill routine written in Z80 assembler looks like uses a two instruction inner loop(OUT (0x81),a; djnz…).  This adds about 4us between each 2us burst of clock so it takes almost a second to clear the whole screen.  You can improve that a bit by doubling up the OUT’s and halving the count.

I remembered something Matt Millman had said about running the TP3465V in 16 bit mode but I still seem to need a nop between writes or it mungs the bits somehow and, in the end, it’s not much faster.

//16 bit fill routine
void zfillb(unsigned int n, unsigned char c){ //send n copies of c
	__asm
	;prolog will have prepped ix=sp
	ld	e,(ix+4)
	ld	d,(ix+5)
; here length is in de, value is at (ix+6)
	srl d
	rr	e
	ld	a,0xFF	;set channel 0 to 16 bits
	out (0x8c),a ;by writing to the MWM bit

	ld b,e          ; Number of loops is in DE
	dec de          ; Calculate DB value (destroys B, D and E)
	inc d
	ld a,(ix+6)
l_zfillb_00101:
		out (0x81),a	;first byte
		nop
		out (0x80),a	;second the same
	djnz l_zfillb_00101
	dec d
	jp nz,l_zfillb_00101
	;epilog will restore ix and return
	ld	a,0x00	;set channel 0 to 8 bits
	out (0x8c),a
	__endasm;
}
//8 bit follows
void zfilla(unsigned int n, unsigned char c){ //send n copies of c
	__asm
	;prolog will have prepped ix
	ld	e,(ix+4)
	ld	d,(ix+5)
	srl d
; here length is in de, value is at (ix+6)
; now we want lsb of count, msb+1(*) in d (* unless already 0)
	ld b,e          ; Number of loops is in DE
	dec de          ; Calculate DB value (destroys B, D and E)
	inc d
	ld a,(ix+6)
l_zfilla_00101:
		out (0x81),a
		nop
		out (0x81),a
	djnz l_zfilla_00101
	dec d
	jp nz,l_zfilla_00101
	;epilog will restore ix and return
	__endasm;
}

I always want the last word so that wordpress doesn’t eat my code!

Close But No Cigar

Something a bit wonky about the z80 version of the character rendering routine!

Olduino/Z with the Colour LCD

 

This is just the same code I ran on the 1802 for this LCD but I think it’s noteworthy that it IS the same code.  Not exactly, of course, there are some assembly language helpers in each case but that’s almost all in the hspi3 library and controlled by the __CPUTYPE defs.  The other (I think) neat thing is how compatible the hardware is.  In the two left pictures below I’ve just moved the signal and power leads from the 1802 Olduino to exactly the same spots on the Olduino/Z.  I may not be brilliant but I am doggedly persistent.

I’m going to work at getting a web client and text output working bilingually on the two processors and use it for the hack-a-day retro edition.

/*==========================================================================
The LCD connection is the same as Nokia LCD5110 and  is a“8 Bit Pant Demo“

Just for ElecFreaks TFT01-2.2SP, which use SPI serial port and 240x320 pixel.
The driver is ILI9341.

by Elecfreaks

16-04-12 adapting for olduino/Z
==========================================================================*/
#include <cpuz80spd4port40.h>
#include <olduino.h>
#include <stdio.h>
#include <hspi3.h> //Z
#define LCD_WR    4-3   //clock
#define LCD_RS    3-3   //data/mosi
#define LCD_DC    6-3   //data/command
#define LCD_REST  5-3   //RESET
#define LCD_CS    7-3   //slave select
void lcdfiller(unsigned int, unsigned char);
void LCD_Writ_Bus(unsigned char value){
  digitalWrite(LCD_CS,LOW);
  spiSend(value);
  digitalWrite(LCD_CS,HIGH);
}

void LCD_Write_COM(unsigned char VL)
{
  digitalWrite(LCD_DC,LOW);
  LCD_Writ_Bus(VL);
}

void LCD_Write_DATA(unsigned char VL)
{
  digitalWrite(LCD_DC,HIGH);
  LCD_Writ_Bus(VL);
}

void Address_set(unsigned int x1,unsigned int y1,unsigned int x2,unsigned int y2)
{//hah - this is already set up properly for endian-independance!
   printf("aset...");
   LCD_Write_COM(0x2a);
   LCD_Write_DATA(x1>>8);
   LCD_Write_DATA(x1);
   LCD_Write_DATA(x2>>8);
   LCD_Write_DATA(x2);

   LCD_Write_COM(0x2b);
   LCD_Write_DATA(y1>>8);
   LCD_Write_DATA(y1);
   LCD_Write_DATA(y2>>8);
   LCD_Write_DATA(y2);

   LCD_Write_COM(0x2C);
   printf("done\n");
}

void LCD_Init(void)
{
    digitalWrite(LCD_REST,LOW);
	delay(10);
    digitalWrite(LCD_REST,HIGH);
        LCD_Write_COM(0xCB);
        LCD_Write_DATA(0x39);
        LCD_Write_DATA(0x2C);
        LCD_Write_DATA(0x00);
        LCD_Write_DATA(0x34);
        LCD_Write_DATA(0x02);

        LCD_Write_COM(0xCF);
        LCD_Write_DATA(0x00);
        LCD_Write_DATA(0XC1);
        LCD_Write_DATA(0X30);

        LCD_Write_COM(0xE8);
        LCD_Write_DATA(0x85);
        LCD_Write_DATA(0x00);
        LCD_Write_DATA(0x78);

        LCD_Write_COM(0xEA);
        LCD_Write_DATA(0x00);
        LCD_Write_DATA(0x00);

        LCD_Write_COM(0xED);
        LCD_Write_DATA(0x64);
        LCD_Write_DATA(0x03);
        LCD_Write_DATA(0X12);
        LCD_Write_DATA(0X81);

        LCD_Write_COM(0xF7);
        LCD_Write_DATA(0x20);

        LCD_Write_COM(0xC0);    //Power control
        LCD_Write_DATA(0x23);   //VRH[5:0]

        LCD_Write_COM(0xC1);    //Power control
        LCD_Write_DATA(0x10);   //SAP[2:0];BT[3:0]

        LCD_Write_COM(0xC5);    //VCM control
        LCD_Write_DATA(0x3e);   //Contrast
        LCD_Write_DATA(0x28);

        LCD_Write_COM(0xC7);    //VCM control2
        LCD_Write_DATA(0x86);   //--

        LCD_Write_COM(0x36);    // Memory Access Control
        LCD_Write_DATA(0x48);   //was 48 C8	   //48 68??//28 E8 ??

        LCD_Write_COM(0x3A);
        LCD_Write_DATA(0x55);

        LCD_Write_COM(0xB1);
        LCD_Write_DATA(0x00);
        LCD_Write_DATA(0x18);

        LCD_Write_COM(0xB6);    // Display Function Control
        LCD_Write_DATA(0x08);
        LCD_Write_DATA(0x82);
        LCD_Write_DATA(0x27);
        LCD_Write_COM(0x11);    //Exit Sleep
        delay(120);

        LCD_Write_COM(0x29);    //Display on
        LCD_Write_COM(0x2c);
printf("init done\n");
}

void hwspilcdasm(){ //asm routines for hardware spi lcd
/*
	asm("	align 16\n"		//make sure lcdclearer jumps will fit on page
		"_lcdfiller:\n" 	//fills R12 bytes of lcd with R13.0
		"$$clrloop:\n"		//come back here for more
			"	dec 2\n	glo 13\n	str 2\n	out 6\n"	//send a fill byte
			"	dec R12\n"	//decrease counter
			"	glo R12\n	bnz $$clrloop\n" //back for more
			"	ghi R12\n	bnz $$clrloop\n" //til done
		"	cretn\n");	//and we're done
*/
}
void zfill(unsigned int n, unsigned char c){ //send n copies of c
	while (n-->0){
		spiSend(c);
	}
}

void Pant(unsigned char VL)
{
  int i;
  Address_set(0,0,240,320);
  digitalWrite(LCD_DC,HIGH);
  digitalWrite(LCD_CS,LOW);
  for(i=0;i<160;i++)
  {
  	zfill(480,VL);
  }
  digitalWrite(LCD_CS,HIGH);
}

void main(){
	//PIN4=0;
	LCD_Init();
	while(1){
		Pant(0xFF); //white
	  	Pant(0xE0);	//red
		Pant(0x18);	//blue
		Pant(0x07);	//green
		Pant(0xE7);	//yellow
		Pant(0x00);	//black
	}
}

#include <olduino.c>
#include <hspi3.c>

I always get the last word.

Olduino/Z Arduino Compatability

ooh, pretty! I wanted to document the connections to the arduino headers and I had seen these ascii pinouts so i tried adapting one from busyducks. I’m quite pleased with the result -it may even be useful!
On the extreme left and right are the labels for the arduino header positions, on the inside are labels for the two 30 pin connectors that go down to the Z80 CPU board.

The Arduino header positions are chosen to fit most shields that will work with an Arduino Uno including Ethernet, SD card, and prototype shields.

  • At the top left is parallel input pin 7 and output 7 which are used for serial I/O
  • below that is a spare chip select which can be used as a bidirectional I/O pin
  • next are output bits 0 to 6 – beside O6 there’s a wired in LED attached to that pin
  • next is the dedicated chip select 0 from the TP3465V SPI chip followed by MOSI, MISO, and SCK from the same chip
  • next is a ground
  • on the top right are five parallel input bits I5 down to I0 where the arduino would have its analog inputs
  • below that is a block of power connections.
		               /RST
           GND CTS VCC TXD RXD RTS
         +-------------------------+
         | [ ] [ ] [ ] [ ] [ ] [ ] |
        +                          +_____________________+
        |                          [RST]                 |
        |     [ ] A11              [BTN]      O0 [ ]     |
     I7 | [ ] [ ] A12                         O1 [ ] [ ] | I5
     O7 | [ ] [ ] A13                         O2 [ ] [ ] | I4
    CS1 | [ ] [ ] A14                         O3 [ ] [ ] | I3
     O0 | [ ] [+] A15                         O4 [+] [ ] | I2
     O1 | [ ] [ ] D4                          O5 [ ] [ ] | I1
     O2 | [ ] [ ] D3    +---------+ [ ]CS2    O6 [ ] [ ] | I0
     O3 | [ ] [ ] D5    |         | [ ]CS3    O7 [ ]     |
     O4 | [ ] [ ] D6    | TP3465V |          OUT [ ] [ ] | N/C
        |     [1] VCC   |         |          GND [1] [ ] | GND
     O5 | [ ] [ ] D2    |         |           I0 [ ] [ ] | GND5
 (*) O6 | [ ] [ ] D7    +---------+           I1 [ ] [ ] | VCC
    CS0 | [ ] [ ] D0                          I2 [ ] [ ] | N/C
   MOSI | [ ] [ ] D1                          I3 [ ]     |
   MISO | [ ] [+] /MREQ                       I4 [+]     |
    SCK | [ ] [ ] /IO                         I5 [ ]     |
    GND | [ ] [ ] /WE                         I6 [ ]     |
     NC | [ ] [ ] /OE                         I7 [ ]     |
        |     [ ] A10                        VCC [ ]     |
        |     [2] A9                         VIN [2]     |
        |     [ ] A8                      /RESET [ ]     |
        |     [ ] A7                         /IN [ ]     |
        |     [ ] A6                        /INT [ ]     |
        |     [ ] A5                       CLOCK [ ]     |
        |     [+] A4                       /WAIT [+]     |
        |     [ ] A3                        /RAM [ ]     |
        |     [ ] A2                        /ROM [ ]     |
        |     [ ] A1                        HALT [ ]     |
        |     [ ] A0                        /NMI [ ]     |
        |     [3] GND                        /M1 [3]     |
        |                                                |
        +------------------------------------------------+  

Thanks to	http://busyducks.com/ascii-art-arduinos

The actual schematic for the Olduino/Z is here, the Z80 CPU board schematic is in this manual.