New CP/M-80 computer: Z80, SD cards, VGA, USB keyboard and power... Teensy 4.1

tomicdesu

Well-known member
Thanks to the wonderful culture of folks' here I was able to put together a fully functional standalone CP/M-80 version 2.2 machine using Teensy 4.1. None of the host teensy code is visible, it boots Z80/8080 code like a 1978 machine, with all of the BIOS, boot, ROM monitor, written in Digital Research's MAC and ASM. Utilities (FORMAT, SYSGEN, ROM and keyboard map uploaders, etc) were written in BDC C 1.6, on the machine. It has a nice workflow as a CP/M machine.

v2-kbd-screen.jpg

I'd done CP/M installs professionally in the 70's and 80's, and I wanted a machine as good or better than anything then. Intentional anachronisms: SD cards as removable media; a modern USB keyboard; USB-C power. The rest is pure Z80/8080, there's no way to tell it's NOT a Z80. With the Teensy 4.1 running at stock 600 MHz I crudely estimate CPU speed as about 60 MHz; fast enough to be a real improvement but not silly-fast. So far I've gathered only a small amount of CP/M abandonware, while developing the project, and customized two text editors for the machine ("H19 terminal"); WordStar rel 4, and PMate 3.21. WordStar fetches screen resolution from the VGA device at runtime. Should run anything you can download.

Provided utilities move files between CP/M and the SD card's FAT filesystem.

Full description of the project is here: https://www.bleeve.me/eZ80/index.html The web page is a work in progress.

I only wanted one (or two) for myself, but I may make in quantity if there's interest. I made 5 prototypes, those boards are about to go to friends for getting hours on them and chase down bugs. The code is on the website above, imperfectly, this is going on now, if something's wrong/missing I'll fix ASAP.

Thanks to everyone who answered my annoying questions in various threads this past 6 months, and to wwatson for VGA_4bit.
 
This is really cool as I have been recently been going through my old SB180 FX SBC. I have the adaptec 4000A SCSI add on board with an ancient 10MB winchester hard drive. I have several 5.25 and 3.25 floppy drives as well and still have the original Micromint CPM disks with lots of CPM software on floppys. I am very interested in what you have done with the Teensy 4.1. I would be willing to do some testing with one of the prototypes. Great work :)

I revisited my VGA_T4 repo. looks like somebody else is using it as well and found some bugs. Need to setup and test...
 
I'd be totally flattered if you had one of my boards. I have 10 version 3 boards, with repaired LPT ports, Im working on the last missing feature (keyboard scan code maps) likely needed for people using not-my-keyboard, no big deal, done in a day or two.

I've ordered 10 4.1's but they're back ordered. But I can set one up, and ship you a v3 without a teensy on it, if that works.

I've got a scheme for distribution CP/M media, its just not written up yet. But I will ship with bootable cards and oothers with utils and sources. Turnkey is the goal.

The only bug I found in vga4bit is reverse scroll is AFU in some way or I misunderstand it. I implemented H19/H89 terminal, more CPM than the contemporaneous VT100! My lazy workaround is to not use it.

Slow scroll has not been a problem in use; WordStar, PMate, etc repaint, which is fast.

The only missing feetch is maybe a margin of empty scan lines and bits left snd right, my=cheap LCDs dont display the top pixel rows. I am thinking of partitioning off a character line, and 0th and Nth columns. Then I could steal another and use top of bot line for "status".

Your vga4bit worked first try and has never failed or glitched. Its pretty damn awesome.

SB180, nice machine!! A Z180?! Wow. I used some of Ciarcia's stuff..

There's a device called the Grease Weazel, another approach to reading old floppies. I bought one, found a 50 pin to 34 pin adapter on eBay, have a half height 8" drive, and plan to "soon" to try to ectract some stuff off my reamining old floppies. Rather than try to connect floppies to eZ80.
 
Does it implement the undocumented IX/IY instructions? That might be one way.
Oh, I remember using those! Although they tended to be a bit slow (in terms of T states per instruction) so there was no real gan from using them.
This was on a Sinclair ZX Spectrum 48k (HiSoft Pascal, loaded from tape), and then an Amstrad PCW 8512 running CPM+(FTL Modula 2, loaded from 3" disk - such luxury)
 
Last edited:
Received the board today:D I have a fresh T41 in the package that I can solder the pins to tomorrow. Then start working with your board...
 
@tomicdesu - I have your board up and running now. Nice work man(y)
eZ80-3-1.jpg

Works with wireless keyboard and probably mouse. Kind of through me off at first after programming the T41 I plugged in the USB C power cable and things lit up but the T41 did not. Then I noticed the button by the green LED pressed that and it booted up. Have read the docs yet:D
Here is a better shot of the VGA screen (not so steady with the camera):
eZ80-3-2.jpg


Once I have read the manuals I will begin testing playing with things. I have not tried using a USB HUB yet but it looks like it will probably work.
Again very nice work...
 
Neato!! Thanks!

Lol I always fear I send one to someone-not-me and it catches on fire or runs out the door or something. I'm in the Great Middle of the project -- most things work as intended, but I'm whacking bugs daily. As you know, working on H19 terminal emulation. Of course it being 1975 in there there's no compatibility between keyboard features (arrow keys, DEL vs backspace) but I've got it working nicely between CCP, WordStar, Pmate and SuperCalc2. PMATE can be configured.

The manuals are at https://www.bleeve.me/eZ80/index.html, you probably know. DOCS has the operating and programming manuals. There's an ABOUT CPM section about the sorts of assumptions made in 1975 that are NOT OBVIOUS, lol.

The software distribution is klunky, and will be fixed in coming days.

Feel free to pile on with bug reports!
 
To address your mentioning of the SD card error in the VGA thread...

There is a sporadic problem when two SD cards are inserted at once. The mere presence of the second (on the SPI buss) card seems to cause problems with the first. I think I have a strategy to investigate, and I'll do so in the coming week. It's annoying as hell when it happens.

It appears no one is doing what I'm doing with SD cards, using them as removable media. I've got nearly all of it solved otherwise.

Either SdFat is maintaining some undocumented (or obscure) state, or there's an issue when rapidly selecting between "adjacent" disks. I have some ideas to pursue and I think there may be a hardware solution to isolating them better. OR of course, I'm making some stupid misteek! Which is most likely.
 
Last edited:
All's going well with the machine, and back from Tear Down, besides fixing bugs generally I am concentrating on SD cards.

I wrote a disk (SD) tester, runs on the Z80 side, to stress test my code (blocking/deblocking and multidrive and buffering etc) and that test code has been a big help. I did a two-day close bench read of my disk code and found some marginal things, now fixed, having to do with writing out dirty buffered slices (4 ea CP/M sectors per SD card block). I haven't had the ACDM41 error in a week.

Paul will be happy to know SdFat seems to be working perfectly, all of my problems were my own!

I'll need to revise the board to include tristate isolation for each card. Inserting a card, even if ignored by the code (as it should) "does something" to the SPI buss, obviously reactive loading of the SPI lines at minimum. My code already interleaves eg. reads of drive B:, then mounts of drive C:, waiting for operations to complete before starting the next. With added tristate buffers, you will be able to insert a card while writes are underway on the other card; right now mere insertion of the other card interferes.

So for now, wait until there's no card activity before inserting or removing the other card. Luckily this isn't too much of a limitation.

I wrote a disk test program (Z80 side, so I can test the whole stack and hardware) that can test on one or both cards. I put a test pattern (track and sector) in each CP/M sector and check that after CP/M sector read. The meaningful tests are random track/sector read, and random track/sector random operation (read/write).

* Run on one card nearly all of the cards, $2 AliExpresss or genuine from-Sandisk Sandisk, run all tests without error (no SdFat error report, all sector pattern data match (pattern is track and sector)). 100K passes at a time.

(Keep in mind this is over a 32 MB tiny portion of each card.)

* Run on two drives, the destructo test is random drive (B:/C:), random track/sector read, and random track/sector random operation (read/write). Here things get strange! The CHEAPEST AliExpress cards run 100K with no error. The SanDisks-from-Sandisk fail at less than 1000 passes (often, SD_CARD_ERROR_CMD18). (But I'm not doing multiple block reads.) so I assume that's SPI buss corruption. Once again pointing to physical isolation as a Good Thing.

Less strenuous writing, PIP B:=A:*.*, with both B and C inserted, gets occasional errors but Retry usually fixes it. With only B inserted, it "always" copies without error.

So I think I have a handle on this now.

Gonna do a big software update "soon" (days).


And since I'm using so little of the card, even the cheapest cards seem fine!
 
Sorry for the slow response.I have been working with the SD cards as well and found that the only time I get the ACDM41 error is with a card formatted as FAT16. Also copying files between drives is hit and miss with all three cards plugged in. If I just use two cards everything seems to copy properly and consistently. I think you are on the right track with a possible buss loading issue. Another possibility is a power issue as these cards can draw a lot of current during certain operations.
The only other thing I ran into was with "VGA.COM". The cursor type selection is reversed in the menu. This:
Code:
     printf ("  T/n    cursor type 0=block 1=underline\n");
should be:
Code:
     printf ("  T/n    cursor type 0=underline 1=block\n");
It ain't no biggie but it confused me for a bit:LOL:

I am working with the 1-JUL-25 distribution that was available. A quick question, Did you compile the the utility programs such as vga.c in CPM on the board or externally cross compile them? Which compiler did you use? I tried using the BD c compiler but getting symbol table out of space error. Have not had a chance to relearn and fix yet...
 
Oh, no hurry at all!
the only time I get the ACDM41 error is with a card formatted as FAT16

Aha! I have not been paying attention to the filesystem type at all. I'll go look at mine.
Also copying files between drives is hit and miss with all three cards plugged in. If I just use two cards everything seems to copy properly and consistently.
Exactly that. A: is the 4-bit SDIO so besides faster, it's electrically isolated.

I didn't pay enough attention to the SD card electronics, but I'll revise the board shortly.

I'll revise VGA for the cursor business! Yeah confusing.

Did you compile the the utility programs such as vga.c in CPM on the board or externally cross compile them? Which compiler did you use? I tried using the BD c compiler but getting symbol table out of space error. Have not had a chance to relearn and fix yet...

I use BDS C 1.60 -- but the symbols in include file EZ80IO.H fill up the symbol table.

A> CC VGA -R15
A> CLINK VGA -N

-R nn sets symbol table size to nn K bytes. Most of the utilities include it so you need to add to all CC invokes. There's a SUBMIT file you can use but I've never used it. -N on CLINK tells VGA to return to CP/M, not warm boot. With the newer disk drivers you have warm boot is almost imperceptable.

All CP/M code is assembled or compiled on the machine itself. I did bootstrap with David Lee's ntvcm, but as soon as I got it to boot I switched over. That provides sufficient inducement to make everything work like a real machine!
 
Last edited:
The one thing I seriously miss editing and compiling, not available on CP/M, is an edit window and a test/debug window. Constantly quitting and restarting is tedious. And a 768x1024 screen is just big enough to split...

With Teensy handling the screen, it occurs to me that single-seat MP/M-80, with 100 MHz CPU and bank switched memory, might actually be a nice usable system.
 
Oh, no hurry at all!


Aha! I have not been paying attention to the filesystem type at all. I'll go look at mine.

Exactly that. A: is the 4-bit SDIO so besides faster, it's electrically isolated.

I didn't pay enough attention to the SD card electronics, but I'll revise the board shortly.

I'll revise VGA for the cursor business! Yeah confusing.



I use BDS C 1.60 -- but the symbols in include file EZ80IO.H fill up the symbol table.

A> CC VGA -R15
A> CLINK VGA -N

-R nn sets symbol table size to nn K bytes. Most of the utilities include it so you need to add to all CC invokes. There's a SUBMIT file you can use but I've never used it. -N on CLINK tells VGA to return to CP/M, not warm boot. With the newer disk drivers you have warm boot is almost imperceptable.
Thanks, I did find the -r option definition and strangely enough tried -r 20 which did not work and then -r15 which did work. Did not know about the -N option in clink.com. Been a long time since I used this stuff :D I am now playing with getting zde16 working. It has an install program with various terminal types as options. One of them is a Heath terminal which I believe is a hazeltine 1500. It has a couple of issues so I will try them all and see if one of them will work. Did you ever get the ins/del line working?
 
I noticed that you are using what appears to be a message buss and port numbers to communicate with different physical devices. Am I correct?
 
Not sure what you're asking?

The Z80 sees everything as I/O ports, true. The z80 emulator calls Devices::inPort (n) and outPort (n, val) and the Teensy handles it from there.

The teensy side code uses an event model. Each .h tab is a single object, with setup() and loop() entry points. The main loop calls each task loop's loop() in turn.

A Z80 command OUT port (reg A=command) arrives at Devices::eek:utPort (port, command). That gets written to a message, then returns, freeing the Z80.

To check completion, the Z80 does IN port, which the emulator dispatches to Devices::inPort, which returns the contents of the function's result message.

In between, the task loop (that handles that command) eventually runs, reads the message, does the work, and writes the result to the result message, for the Z80 to pick up. (When status message is read, it's cleared to zero.)

The average task response time is about 4 microseconds, worse case generally 100 uS.
 
Sorry to be so vague. You answered my question. I think I understand how this is all working now (y) I have been testing things in the H19Out() function in the "devices.h" file.
The one thing I seriously miss editing and compiling, not available on CP/M, is an edit window and a test/debug window. Constantly quitting and restarting is tedious. And a 768x1024 screen is just big enough to split...
I Agree. I can lose track real easy that way. I do have a simple windowing example in the examples folder of VGA_4Bit_T4. But I think that would take a lot of work to setup...
 
SOOOOoooo.... I just read all the MP/M II docs... and it would not be that hard to implement. It does real preemptive multitasking! On an 8080! The shop I worked at in the late 70s brought one up, MP/M 1, not II (2), with the off-application intent of doing what I am thinking now (which is what everyone but DR was thinking, then...) one-seat multitasking.

A 4MHz 8080 was too slow. But a 100 MHz Z80 isn't. And there's enough RAM to page/bank in. I'd need to add "bank" address (offset) to every memory ref in the emulator. A day's work.

Console switching is done by Control-D in the MP/M CCP, but I find it hard to believe it wouldn't be able to assign a special key combo that generates no code to do this (shift meta cokebottle "1" and "2", say) so that you could be editing text on console A, then switch to B to compile load go.

MP/M has a cron equiv!

MP/M has a lot of odd features, but they seem very nice. There's a simple way to run MP/M as a CP/M program, with non-banked memory, one console. It doesn't seem too hard to accomplish so I'm gonna probably do that to play with. The docs are typical good stuff from DR.

The catch is, and why I'm riffing on this in your thread, is that the screen is the gating item. In 1024 x 768 mode, there's *almost* enough screen space to window it, for now imagine a vertical split. 48 x 128 would become two 48 line, 64 column windows; or four ways, eg. two large at 36 x 64, with two 12 line 64 col mini-consoles, four total. Or asymmetrical, 48 x 80, the other side 48 wide, etc. It's all just rectangles.

Or -- totally sexy -- runtime configurable window size. Edit large, compile small. Vaguely similar to a constrained X/Wayland window manager, like ratpoison or sway.

This could be done with your windowing, or I could even do it in h19Out(), really it's just rectangles. There's so much possibility here I think I'm gonna pursue it. I will go RTFM your windowing scheme. That might be all it needs.

I am not trying to rope you into my hare-brained project, just thinking out loud. Any insights you have would be appreciated. Now that I've typed it out I'm thinking the current scheme broken into 4 rectangles is probably the way to go.
 
Last edited:
Give me a little time to experiment with this and develop some examples that build on this idea. Right now each window is based on an instance of:
Code:
typedef struct {
  int handle = -1;
  bool isOpen = false;
  bool active = false;
  uint8_t x1;
  uint8_t y1;
  uint8_t x2;
  uint8_t y2;
  uint8_t fgc;
  uint8_t bgc;
  uint8_t fgcSave;
  uint8_t bgcSave;
  uint8_t frameColor;
  uint8_t frameType;
  bool shadowEnable = false;
  uint8_t shadowPattern;
  uint8_t shadowColor;
  uint8_t shadowBGColor;
  uint8_t titleColor;
  const char *winTitle;
  uint8_t *winbuf = NULL;
} vgawin_t;
The whole thing is based on grabbing the size of the window, borders (if any) and shadows (if any). The latter two are optional. Then saving the contents of that part of the screen to malloc'd memory. Then drawing the window and finally restoring the previous contents of the screen and freeing malloce'd memory when closing the window. So basically on a 1024x768 broken into 2 or 4 windows requires another 786432 / 2 = 393216 bytes of memory (2 pixels/byte). Right now the VGA buffer is using DMAMEM and with the ez80 library we only have 101728 bytes * 2 = 203456 left. We are 393216 - 203456 = 189760 short on DMAMEM. So I need to figure out other memory options as well. PSRAM for mallocing maybe?
The only thing that could be challenging would be dynamic window resizing. But that's the kind off thing I love doing:D
 
Another way might be to leave the DMA'd portion of the design as-is to shuffle pixels to the screen, which it's already very good at; and do all windowing at the character level. Currently-active window could be the one with the cursor. Lines and cols is inherently rectangular, and within the character paradigm pixel level control brings no advantage.

Borders or different background color would be awfully useful; it could be done with characters but that's wasteful, though the simplicity is very seductive. And lighting up the active window could be very quick. Box-drawing characters would do it all.

It would be very CP/M-like to, within a given window, run a utility to adjust or select window size, then run the desired program. I've already got WordStar patched to read lines/cols from input ports; it reads at startup. So that already works.

Also bank switching CP/M TPA will need 32K to 48K per bank; so two to four of those will be needed, 128K - 160K.


OMG it just occurred to me, with the screen broken into four and only four windows, resize would simply be grabbing the "+" in the center and shifting it left/right up/down, simple arrow key motion. With fixed keys to jump the current window to "regulation" size, 24x80 etc.

Oh crap, 2nd edit: if every "console" screen was virtually 24x80, "fixed", resizing by the above method could reveal/draw off-screen text, and it would be stored as text, so each virtual window would be character (8) bg and fg colors (8) so 24 x 80 * 2 bytes of RAM per screen. Four of those. On the Teensy side, above the vga4bit library. Or within, I'm happy to leave you to coding it! lol! Seriously though I think it would be easiest, control- and storage-wise, to put in "my" code, its the right layer of abstraction I think.

3rd edit: With 24x80 CP/M every CP/M program could be configured for stock H19, 24 x 80, regardless of what's shown on screen. This means it would work as-is with 1024x768, or 800x600; the amount of character space on screen would change, only.

I'm liking this virtual screen thingie.
 
Last edited:
May I ask two question?
I have some asm code from the 80s (on paper). So, I simply can type it in and is should work, right?
Also, I recall that one of the nice features of the Z80 was that it had 2 independent register sets, so that context switching was fast. Is that also implemented?
 
Back
Top