moria.de Michael's home page Computing MCS-48 ECB SBC

MCS-48 ECB SBC hardware

There are quite some SBC projects on the net, but few offer more documentation than a GIF with the schematics. I enjoy free software and hope to support the thought of free hardware design by documenting my work in detail. I started with Xfig, but later switched to KiCAD, a free schematics CAD system. That way, everybody could continue working on the system.

Reset and battery backup

The reset circuit started out as the usual RC-combination with a diode for discharging. Talking to a friend, he pointed out that I risk crashs during brownouts that way, so I changed to a TLC7705, which has the nice property of generating both /RESET and RESET, saving an inverter. It would have stayed that way, but later I decided against a RTC with builtin battery, because I wanted to be able to change the battery. Using an external battery simply asked for a switch between main and battery power in order to use the battery for battery buffered RAM, too. I changed the prototype again, this time to a MAX690, and that's a truely great chip. It does not even need the usual electrolytic cap, freeing up board space.

Many designs save the switch and simply use a Schottky diode to connect a RAM to Vcc. Unfortunately, many RAM parts ask for signals not to exceed Vcc plus 0.3V to avoid getting damaged. In the real world, the diode works, but it is easily outside the spec. I want things to work as reliable as possible and the MAX690 has a ridiculous low voltage drop. Its /RESET is also perfect, which is important, because it is active on battery backup and fed to unpowered chips. You wouldn't want a regular low of up to 0.8V being fed to unpowered parts. There is one trap, though: Unless you need a very small fanout, you need a pullup resistor for /RESET, because the builtin resistor is pretty large.

PFI monitors the battery voltage. If PFI falls below 1.3 V, /PFO gets active. The RAM requires at least 2 V for not losing its content. To get an early warning, I set the alarm at 2.2 V. This is accomplished using a voltage divider: 1.3 V / 2.2 V = 0.6. Maxim suggests a divider of 20 MOhm total, which is reasonable, and results in using 7.5 MOhm and 12.5 MOhm. The latter is hard to get, so 7.5 MOhm and 5.1 MOhm are used in series, resulting in 12.6 MOhm - good enough.

Don't try to measure the divided voltage, unless you have a very good voltage meter, or the resistance of the meter will pull down the voltage. Instead, measure /PFO (pin 5). If the battery is fine, it is high. If the battery voltage drops, which can be simulated by shortening PFI (pin 4) to GND, /PFO changes to low. The MAX690 does not provide this signal during a power failure, thus not feeding unpowered chips. Did I already mention it is a truely great chip? /PFO is connected to /DCD on the UART, allowing to determine the battery status. DCD is checked by looking at bit 7 in register 6 of the UART with the base port address 0x8000. In Forth:

: battery-ok? 8006 PC@ 80 AND 0= ;

CPU and internal bus signals

The MCS-48 controllers can access external memory for program storage and I make use of that, because that way I can use any member of the family and I can run programs from RAM. Many people say this family has a Harvard architecture, but I don't think so. There is only one bus for both program and data access, and nothing enforces to split code and data. To me, it just uses two segments and tells the bus which one it accesses.

The MCS-48 family multiplexes A0-A7 and the data bus. A 74HCT573 latch is controlled by ALE and stores A0-A7.

Code addresses are 12 bit wide and data addresses are even shorter: Just 8 bit. That may suffice for small controlling application, but I wanted more. Extending the data address space is easy, because P20-P23 is used as A8-A11 during program access, but as port any time else and in particular during a data access, so simply use P20-P23 as A8-A11 without any latches and you get the data space extension from 8 to 12 bits for free.

Anything beyond requires banking, but since there are two segments, two independent bank selectors are needed. That asks for multiplexers, controlled by /PSEN. After reset, the multiplexer must be inactive. One solution would be a flipflop, triggered e.g. by a write access to ROM space to avoid the need for a port, but that means you could not get back to the state after reset. I decided to use P10, because it is high after a reset, just right to control /E of a multiplexer. P24-P27 are just natural as A12-A15 for data accesses, leaving P11-P17. I decided to use P14-P17 as A12-A15 for code accesses (just for symmetry), which leaves P11-P13. Three bits give me A16 and A17 for data, and A17 (no A16!) for code, because I figure I need more data than code. All in all, code can use P2 like a page pointer for data accesses, but P1 is a typical bank switching control port and accesses to it should be rare.

The MCS-48 family offers an internal clock generator, if you connect a crystal and two caps. That takes little board space and is cheap. Don't let yourself get fooled by the rather high frequency: Internally, it is first divided by three, and five resulting clock cycles make up one machine cycle, so at 6 MHz, one machine cycle takes 2.5 us, yielding a cycle frequency of 0,4 MHz - slow as a snail! The CPU can output the divided clock on T0 and I make use of that. A Z80 CPU, the ECB reference, starts /RD on a falling clock edge and ends it on a rising edge, letting /RD active for 1.5 clock cycles. The MCS-48 starts it on a rising edge and ends it on a falling egde with a duration of 2.5 clock cycles. For that reason, I inverted the clock to correct the phase. Perhaps nobody cares, but who knows.


Address decoding

Using a single 139 decoder, I get this address map:


The RAM is mapped to the top half of the address room. The upper half of the RAM can not be used as program storage, because A16 of the code segment selector is tied to ground for lack of port pins. The I/O area is not fully decoded and consists of four parts:

beginendusage
800080ffUART
810081ff--
820082ff--
830083ffECB

Although in theory ECB supports 16-bit port addresses, no I/O card I know of does, and that's not a surprise, because the 8080 only offers 8-bit port addresses. Just the Z80 allowed more. For that reason, the SBC maps 8300-83ff to the ECB addresses 00-ff.

The initial design needed more chip enable lines and this part probably could be improved now, but I doubt you can save a whole chip, so why bother?


Memory

The memory subsystem is very straight. ROM is a 32kx8 EPROM or EEPROM, the latter being easier to program, but more expensive. A jumper field allows to select which one you want to use. RAM is a 128kx8 low power SRAM. It is battery backed by connecting its power supply to battery backed power and CS2 to /RESET, saving a NVRAM. Both memories serve program and data accesses by combining /RD and /PSEN. Note that the MAX690 has about no voltage drop, which is important for the SRAM which only allows signals to be 0.3V above Vcc. A schottky diode may not suffice.


Talking about memory, it is a good time to check the timing. Let's start with the instruction fetch cycle:

Start: ALE and /PSEN are high
tLL later: ALE is low and A0-A7 are output
tLAFC2 later: /PSEN is low
tCC2 later: Data must be valid and the instruction will be read
tDR later: The data bus must be in tristate (released by memory)

A data read cycle looks a little different:

Start: ALE and /PSEN are high
tLL later: ALE is low and A0-A7 are output
tLAFC1 later: /PSEN is low
tCC1 later: Data must be valid and will be read
tDR later: The data bus must be in tristate (released by memory)

So let's calculate the delays, starting with the actual requirements set by the CPU. Running at the maximum speed of 11 MHz, the MCS-48 times are:

F = 11 MHz
tcy = 15/F = 1364 ns
tCC1 = 1/2 tcy - 200 ns = 482 ns
tCC2 = 2/5 tcy - 200 ns = 345 ns
tDR = 1/10 tcy - 30 ns = 106 ns

Since tCC2 is shorter, there are two questions: Will data be output soon enough once /PSEN is low? Will be bus be released early enough after /RD is high again?

The 74HCT157 multiplexers are switched by /PSEN, making switching propagation delay important for A12-A17:

tADDRESSHIGH = 46 ns

A17 is inverted for /RAM. The 74HCT08 and 74HCT03 gates are similar:

t/RAM = tADDRESSHIGH + 30 ns = 76 ns
t/CRD = 30 ns

At the same time, the 74HCT139 decoder computes /ROM. It is a little slower:

t/ROM = tADDRESSHIGH + 43 ns = 89 ns

The lower address lines are marginally faster than the higher ones, determined by the propagation delay of the 74HCT573 latch:

tADDRESSLOW = 44 ns

The ROM is usually available as 150ns part, whereas the usual RAM is much faster, so let's look at the ROM.

max(tADDRESSHIGH,tADDRESSLOW) + tACC (Adr to output delay) = 46 ns + 150 ns = 196 ns
t/CRD + tOE (/OE to output delay) = 30 ns + 70 ns = 100 ns
t/ROM + tCE (/CE to output delay) = 89 ns + 150 ns
tDF (output disable to output float) = 50 ns

Once /PSEN is low, 196 ns later there will be valid data on the bus. That's early enough for a tCC2 of 345 ns. The bus will be released within 50 ns, where 106 ns would suffice. That's what happens if you combine a current ROM with a CPU that is 30 years older. This design should work very reliable.

Serial console

Face the fact: Computers without a serial console suck. :) Unfortunately, there is little choice in UARTs these days, because they are usually integrated with other chips. The only UART that is easily available these days is the 16450/16550. Besides being usually cheap, you can often get used ones from garbage serial cards, but that's about the only good thing that can be said about it. It is awful to program, and worse, it has only one channel, but a DIL40 case. It expects a active high reset and the interrupt output is active high and not open collector. Did I say it's the only choice really already? The UART asks for more than just an external crystal and capacitors to generate its own clock signal. A small Ruby program determines if the system clock would suffice instead:

#!/usr/bin/ruby

baudrates = [ 50,75,110,134.5,150,300,600,1200,1800,2000,2400,3600,4800,7200,9600,19200,38400,57600,115200 ]
freq1 = 11e6/3
freq2 = 6e6/3
baudrates.each {|baud|
  d1 = (freq1/16/baud+0.5).truncate
  d2 = (freq2/16/baud+0.5).truncate
  printf("%d\t%5.2f\t%5.2f\n",baud,100*(1-(baud*d1)/(freq1/16)),100*(1-(baud*d2)/(freq2/16)))
}

Fortunately, it is odd enough that it can be used:

BaudrateError at 11 MHz in %Error at 6 MHz in %
500.010
75-0.01-0.02
1100.020.03
134.5-0.010.04
150-0.010.04
300-0.01-0.08
600-0.010.16
1200-0.010.16
18000.240.64
2000-0.36-0.8
24000.510.16
3600-0.54-0.8
4800-0.540.16
7200-0.542.08
9600-0.540.16
19200-0.54-7.52
38400-0.547.84
57600-0.547.84
115200-0.547.84

As you can see, with 6 MHz baudrates above 9600 will not work, but that suffices. The level shifter is the usual MAX232 application, but with a MAX232ACPE to avoid electrolytic capacitors that take space and don't last forever. The connector is not DSUB, as you might expect, but RJ45 with Cisco pinout, which is made to connect any two devices with always the same simple 180 degree twisted flat cable - a console cable. Serial connections never were easier.

The drivers are wired to allow RTS/CTS handshaking, which is supposed to avoid overruns. In fact, with modern PCs as communication partner, it does not, because the common 16550 does not support hardware handshaking and common software implementations are braindead enough to check CTS when filling the transmitter FIFO, which means although the peer asserts RTS, the FIFO will continue transmitting characters. If the SBC can't receive characters at line rate, all it can do is to using an UART with a FIFO of at least 16 bytes, because that's what the 16550s in PCs use. And that's why the MCS-48 SBC uses a 16550.

A few unused UART lines allow to build a I2C/2-wire interface. The transistors work open collector with 4k7 resistors as bus pullups.


The real time clock and temperature sensor

Initially, I didn't want to do bitbanging for an I2C interface, which ruled out a bunch RTCs. Additionally, I strongly dislike internal batteries that can not be changed without cracking the module package, and there is absolutely no reason for a wide DIP28 case for a RTC. The RTC72421 appeared to be the ideal solution: External battery, traditional bus interface and a DIP18 package. When I wrote the driver, things didn't look just as good anymore. What looked like a Y2K issue at first could be solved, but the registers are not double buffered, so you end up doing bitbanging anyway. Finally it annoyed me enough to implement an I2C interface and using a nice DS1307 RTC. The code is way better now, although the Y2K hacks are still required.

Every system should know how warm it is. There are various sensors on the market, but the TSic206 is unique by having a TO92 package, being very precise, already calibrated and outputting a digital signal in a simple, self-clocking code. To make things even better, it needs only very little power. The downside is the price. The chip justs asks for being connected to T1 on the CPU. At 6 MHz, the code is not trivial to write, but works.

When I changed the design to I2C for the RTC, I felt using an I2C temperature sensor would be sensible. The DS1621 is available as DIP8, although as expensive as the TSic206 and not as precise. If you want to save some money, then solder a LM75 to an adapter and use that instead: Their pinout is compatible, just the software needs some small changes. Older socket 7 boards usually contain a LM75 below the CPU and that's where I got mine, too. If you want more precision, use the LM75AD from Philips, which offers 11 bit instead of 9, or the ADT75 from Analog Devices, which offers even 12 bit.


The ECB interface

The MCS-48 family offers neither any kind of advanced interrupt facilities nor DMA. Although the latter could be emulated by letting the CPU run on one memory chip while decoupling the other for external access, it didn't seem worth the effort.

The entire address room is filled with memory onboard and the ECB interface is only used for external I/O accesses. As already mentioned, the range 8300-83ff is mapped to external I/O addresses 00-ff. A8-A15 on the ECB interface are tied to GND.

The clock signal is buffered by two parallel buffers to ensure a low resistance. The /RESET signal is distributed to the bus, but reset input is not used, because I never saw a card generating it and it would have made the reset circuit more complicated. The interrupt line is pulled up, but not buffered.

One buffer is used to drive a low current LED. A regular LED would almost double the power requirement of the board for nothing. All in all, the ECB interface is very simple.