Close

Serial I/O

A project log for Z80 Computer

An attempt to build a Z80 computer capable of running CP/M

techavtechav 06/22/2015 at 06:361 Comment

I never thought I'd be so happy to see a few characters slowly type out on screen. Serial communication has been the hardest part of this project so far. I'm using the Zilog Z8530 Serial Communication Controller (SCC). It's a versatile 2-channel programmable USART capable of speeds up to T1, and most notably used in the early Macintosh line for serial and LocalTalk.

With its versatility though, comes complexity. The SCC has sixteen registers for each channel (some shared though) that have to be programmed in the right order to properly configure the chip. Get the order wrong, and an internal race condition is likely. The manual warns of this possibility, but other than a few example assembly programs in the appendices, doesn't give much detail on what the proper order is. What finally worked for me was a combination of procedures found via Google, and old-fashioned trial-and-error.

...But configuring the chip was only one problem.

I have an old Mac Plus that didn't work when it was given to me fifteen years ago. I scavenged a few parts from it as a teenager, and it's been in storage since. I decided to pull the SCC from it, along with the MC68000 (next project). I don't have the best tools, but with time, a soldering iron, and a Radio Shack spring-loaded solder sucker, I was able to get most of the pins clear. Any pin that was tied to power or ground though, I couldn't get the solder to melt through. I'm assuming that the inner layers of the Plus motherboard are ground and power pours, and all that copper was just sucking up the heat. I finally got the chip free using a heat gun at work, but in the process got the chip far hotter than I felt was safe for it. Pretty sure I killed it there.

I tried it anyway - brought it home, wired it in, wrote and flashed a new program, aaaand ... nothing. Tried different combinations, different initialization orders, different clock speeds and baud rates ... nothing. Suspecting that I had killed the thing, I wired it in to my Arduino Mega, so I could try reading the registers to make sure they were even writing. Every register read back $00 every time. That settled it, I burned the chip trying to remove it.

Thankfully, Mouser still carries the Z85C30, and since they're practically in my back yard, by the next day I had a new one, along with a 3.6864 MHz crystal (the SCC's internal Baud Rate Generator can use the system clock, but a 3.6864 MHz clock will generate all common baud rates without jitter). So, replaced the old SCC with my new one in the Arduino test bed, and ... every register read back $00 every time. Nothing I did, no attempts at adjusting timing or sequence would read back anything other than 0 for any register.

Well, I seriously doubt the new chip is bad out of the box—surely it's just my Arduino sketch. So, I put the new SCC back in circuit with the Z80. Of course, nothing happened on the first try, but I changed a few parameters, re-burned my flash, and tried again ... and again ... and again ... and finally got gibberish.

At this point, garbage data was like gold. Garbage data means the chip is working. Garbage data means the SCC is properly configured for async, Tx is enabled, and it's accepting data. I pored over the settings, checking, rechecking every bit and its hexadecimal translation entered into my code. Still garbage. I changed the baud rate to something slower, surely 1200 should work fine. Still garbage. Finally I spot it—the careless mistake I had made, and confirmed, and checked over time after time ... baud rate source set to RTxCA instead of the Baud Rate Generator.

Sure, I had properly set up the BRG, I had set the BRG to use a crystal between RTxCA and SYNCA ... but then set channel A to use the raw clock on RTxCA, instead of the actual baud rate generated by the BRG from the RTxCA clock.

Did I mention the chip is a little complicated to set up properly?

After all this, finally I was able to reliably output a string of text every time the system was reset. After that though, nothing. My program was supposed to poll the SCC for data in the Rx buffer, and when data is available, read in and echo back. I'm certain the problem is my program, but this project is the first time I've ever used Assembly for anything more than just a few minor operations. Clearly I need more practice, but at this point, I just want the thing to work.

Enter Small Device C Compiler - SDCC. SDCC worked with no problems right out of the box. It does require you write your own getChar() and putChar() routines for stdio to work, but those were easy enough. So in no time at all, I had a new test program, in a language I could easily troubleshoot.

#include <stdio.h>

static __sfr __at 0x03 sccDatA;
static __sfr __at 0x02 sccDatB;
static __sfr __at 0x01 sccComA;
static __sfr __at 0x00 sccComB;
static __sfr __at 0xF0 flagsReg;

void boot() {
	__asm
		LD SP,#0xFF00
		CALL _main
	__endasm;
}

#define sccSetsCount 38
const unsigned char sccSets[] = {
	0x00,0x00,      //pointer reset
	0x09,0xC0,	//hardware reset
	0x04,0x04,	//1x clock, async, 1 stop, no par
	0x01,0x00,	//no dma, no interrupts
	0x02,0x00,	//clear int vector
	0x03,0xC0,	//rx 8 bits, disabled
	0x05,0x60,	//tx 8 bits, disabled
	0x09,0x01,	//status low, no interrupts
	0x0A,0x00,	//nrz encoding
	0x0B,0xD6,	//xtal, BRG for rxc, trxc output
	0x0C,0xfe,	//time constant low byte (1200)
	0x0D,0x05,	//time constant high byte(1200)
	0x0E,0x00,	//BRG source RTxC
	0x0E,0x80,	//clock source BRG
	0x0E,0x01,	//enable BRG
	0x0F,0x00,	//no ints
	0x10,0x10,	//reset interrupts
	0x03,0xC1,	//enable Rx
	0x05,0x68,	//enable Tx
	0x00,0x00	//overflow
};

void initSccA() {
	unsigned char i;
	for(i=0; i<sccSetsCount; i++){
		sccComA = sccSets[i];
	}
}

void main() {
	unsigned char c;
	flagsReg = 0x80;	//turn on LED
	initSccA();
	flagsReg = 0;		//turn off LED
	printf("SCC Init Complete");
	while(1){
		c = getchar();
		putchar(c);
	};
}

void putchar(char cin) {
	unsigned char c;
	//check status of tx buf, then output
	do{
		sccComA = 0;
		c = sccComA;
		c &= 4;
	}while(c == 0);
	sccDatA = cin;
}

char getchar() {
	unsigned char c;
	do{
		sccComA = 0;
		c = sccComA;
		c &= 1;
	}while(c != 1);
	c = sccDatA;
	return c;
}
By default, SDCC uses templates that make a lot of assumptions about the way the hardware is set up. In the case of the Z80, the template is built for a specific emulator. The quickest way to get my test program up and running was to disable the template, which once compiled will start executing with the first function listed. That's why I've inserted that boot() function at the beginning—it's the first thing executed, so it initializes the stack and calls main().

The result of this compiles to a 3KB binary, thanks to the included stdio library. New problem. I've been copying lines from a hex file into the Arduino control monitor to program the flash rom. Not a problem with a program that's under 100 bytes. Very tedious for 3K of code. I now know enough Python to access a serial port, open a file, and output the file over the serial port. Success!

So, now I have a Z80 computer with 64kB of RAM, up to 32kB of ROM, 2-way serial communication via SCC, an easy way to program it, and an easy way to burn a new ROM ... and I don't know what to do with it. I keep looking over the source code for CP/M, but CP/M is a disk-based system. Without disks, 90% of the code is no longer relevant.

Maybe it's time to start on a new system with a Motorola 68000 while I think about it.

Discussions

Eric Hertz wrote 06/22/2015 at 07:09 point

Hah, this was a fun read... I love those moments when "Garbage data" indicates an improvement :)

  Are you sure? yes | no