I decided to add-on the 82C51A just to try some Z80 coding. I figured that I already had the 82C51A UART and its smaller in size (DIP-28) than an 82C55 PIO (DIP-40). I added it to the solder-less bread board and checked all my connections for (proper) continuity. All tested well for continuity, so off to coding.
As I mentioned earlier, its been a few decades since I worked with the Z80. My main focus for the last two decades has been on the ATMEL AVR8 series of MCU’s (http://www.atmel.com/products/microcontrollers/avr/default.aspx).
Of course, writing a program and not having an assembler or compiler to generate the native machine code makes it all for naught. Fortunately, I had been successful in getting the Z88DK software development environment to compile and install to completion.
Since my strength is in assembly language programming and not so much in “C”, I set out to learn the syntax of the assembler being used in the Z88DK suite, which is Z80ASM by Paulo Custodio.
I had used some software posted by Scott Lawrence from the RC2014 GOOGLE group (https://groups.google.com/forum/#!forum/rc2014-z80). Scott is using the ASZ80 assembler from the ASXXXX Cross Assembler package by Alan R. Baldwin. Here’s the link for it: http://shop-pdp.net/ashtml/ I did notice that Scott’s package uses an older version (v1.85) of the ASZ80 assembler but did not think about updating the software (to v5.10) until after I did a little programming and successful assembling of said program. The update to the Makefile is to simply add the asz80 options of “-s -o“.
As I stated, its been a while since I programmed Z80. I had to find the Z80 Reference Manual, which I found here: z80.info/zip/z80cpu_um.pdf. I also found some “pocket references” listing all the instructions, their syntax, various operands and the flags affected, which was very helpful to see the variations in the assembly syntax.
I have to admit that I was impressed with both the sheer simplicity and the sheer complexity of the Z80 instructions. The sheer simplicity of their use and the sheer complexity of the actual operation of each. I then realized that I was looking at the instruction set of a CISC (Complex Instruction Set Computing) microprocessor. I had gotten so used to the AVR’s RISC (Reduced Instruction Set Computing) architecture that I had forgotten the meaning in the difference of the two. It was a wonderful revelation and brought me further back to the days of the infancy of the micro-computing industry of the 1970’s.
I have no external Z80 RAM available on my functional test board, so I had to be sure not to use any subroutines nor stack operations. After writing the test code, I programmed it into the AT28C256 EEPROM and well, it didn’t work. I spent the next few hours looking for some sample programming code and info on the 82C51A. Now, I can read datasheets just fine and I can usually figure out how to program peripherals in short order using the datasheets but something I found while searching was a sample program in Scott Lawrence’s package, where he used a sequence of six (6) bytes to force the 82C51A into a “software reset mode” and then programmed the mode byte into it. I thought, “well maybe I missed something” so I added that sequence to my code and tried it to no avail. Note: I had been working from the OKI SEMICONDUCTOR datasheet since that’s the part I have but I happened to pull up the INTEL datasheet and noticed a statement on “page 2-12”; “8251A Internal Reset on Power-Up: When power is first applied, the 8251A may come up in the mode, SYN character or command format. It is safest to execute the worst-case initialization sequence (SYNC mode with two SYN characters). Loading three 00H consecutively into the device with C/#D = 1. An internal reset command (40H) may then be issued to return the device to mode word. The mode word must then be issued, and followed by the command word.” Well, that explains the six (6) byte sequence, which isn’t in the OKI SEMICONDUCTOR datasheet, so I can safely assume that issue has been corrected in teh OKI parts. I mean why have a RESET pin on the part if it will not reset the part into a startup known state correctly, yes? I thought it prudent though, to leave the code in. Even so, the code would still not function. That’s when I started having the thoughts about loathing and NOT missing the days of the external address, data and control buses and the days of the internal DEBUG registers and real-time hardware access to the internal memory, registers and I/O ports became MUCH more appealing. LOL Okay, enough of that. I had to figure out what was the problem and I needed a way to determine what was going on at the program level. This is not a microcontroller where I can just toggle an I/O port pin when I get to somewhere in my code. I really needed to “see” what was happening. Break out the trusty logic analyzer!
My logic analyzer is only sixteen channels. What to do when I have 16 address bits, 8 data bits and at least 5 control bits to watch? Upgrade to the 32 channel unit? No, drop the address bus monitoring and trigger on the Z80’s high-to-low M1 transition while monitoring the data bus! After wiring the logic analyzer in and setting up the channel assignment, “grouping” the data bus so I can read it as a hexadecimal number and triggering , I hit the hardware RESET button and let it go. After the logic analyzer was finished capturing, I could see the databus contents change. I compared the databus contents to the assembly listing of my code and I could follow each instruction sequence, byte for byte. Now I am making some progress. In following along, I could see that when the 82C51A’s status register was being polled, the value returned was 0x85. I thought that strange because I am looking for the “TXEmpty” bit to go active before I stuff a byte into the UART. I thought that was bit position 2. Upon looking at my code, I saw that I had mistakenly been looking at bit position 1, which is the “RXReady” bit. No surprise at what was happening. I corrected the code and still no joy. Some more tracing led me to find that a “jnz” instruction was being executed when a “jz” instruction should have been. Well, that was it and the program started to function properly.
The following image is a screen print from the logic analyzer. The trigger is the Z80’s M1 signal. One can see that the “DATABUS” shows hexadecimal values changing. Following those changing states, one can read through the assembly listing and trace the program flow. A partial assembly listing follows the image.
Toward the right side of the image is the sequence “0xED 0xB3 0X00“. The”0xED 0xB3” are the opcodes for the”otir” instruction and the “0x00” is the data written to the 82C51A via the “otir” instruction. The “iorq” and “PIOcs” signals can be seen dropping to a low-level, which selects the 82C51A. The “rd” and “wr” signals are not shown but that sequence is a “write” to the 82C51A.
; Initial entry point ;====================================================================== 0000 .org 0x0000 ;start at 0x0000 0000 Reset: 0000 F3 ;disable interrupts 0001 31 FF FF ld sp, #STACK ;setup the stack 0004 ED 56 im 1 ;interrupt mode 1 0006 C3 00 01 jp Main ;jump to main routine 0100 .org 0x0100 0100 Main: 0100 Init8251: 0100 21 60 01 ld hl,#res8251 ;Reset sequence for 8251 0103 06 06 ld b,#res8251end-res8251 ;Table length 0105 0E 11 ld c,#TermStatus ;Point to control port 0107 ED B3 otir ;OUT and loop until done
Once again, I learned by doing, which is the only way that I ever really learned anything in life (and electronics/programming). I could read all the databooks and instruction guides I wanted but I never learned a thing until I sat down and started to learn-by-doing.
The final 82C51A UART test code is posted below. There are actually two files the code is contained within, UARTtest.asm and UARTtest.h. The contents of both are posted but the contents can be combined into one file, just be sure to add the UARTtest.h contents BEFORE the UARTtest.asm contents. I am not sure how to correct WORDPRESS and keep if from converting tabs to spaces as the WORDPRESS editor really screws up the formatting and makes it a mess. I know it is the HTML codes that cause some of the issues but I am not sure how to get around that.
I am thinking that the next step in the design process will be to either start the PC board layout process, which will give me a full-blown system to start the AVR host software development on … OR … to work on some code for UART emulation using my current functional test bread board. If I decide to use the current functional test bread board then I will have to remove the 82C51A UART and the ATmega328P from the breadboard and mount the TEENSY 2.0++. Although, I think I do not really need the 74HC299 yet, I may be able to leave the 82C51A UART. The ATmega328P does not have enough I/O pins available to monitor the databus and some of the control lines (IORQ, WR, RD and A0 to A3). The TEENSY 2.0++, which is the eventual target, has all the pins I need. However, I need access to the JTAG pins (port pins F4 to F7) and I need access to the PORTA pins, which are in the middle of the TEENSY 2.0++ board and not on the outside pins. This is a potentially an “ugly” hack on the solder-less bread board.
Honestly, I am not looking forward to more wiring on the solder-less bread board. I have lots of 24AWG single-stand wire since the telephone guy who was repairing the DSL pole-to-house wiring a year ago left me about 50 feet of wire, so abundance of wire is not an issue, just the number of wires required to make the various bus connections. Sigh…
;============================================================================= ; Test for the UART I/O (and mini-monitor) ; Version 1.00 ; (C) 2016 - Quest, Johnny ; File name: UARTtest.h ;== Defines ================================================================== ; AVR ports AVRData = 0x00 ;Data on AVR for testing AVRStatus = 0x01 ;Status on AVR for testing ; 82C51A port addresses TermData = 0x10 ;Data on 82C51A for terminal comms TermStatus = 0x11 ;Status on 82C51A for terminal comms ;82C51A - MODE bit definitions SB1 = 7 SB0 = 6 ;stop bits EP = 5 ;parity PEN = 4 ;parity enable L1 = 3 L0 = 2 ;character length B1 = 1 B0 = 0 ;baud clock divisor ;Initial Mode - Async, 8N1 @ 1x baud clock INIT82C51A = (1< ;82C51A - COMMAND bit position definitions EH = 7 ;Hunt mode IR = 6 ;Internal reset RTS = 5 ;RTS state ER = 4 ;Reset Error Flag SBRK = 3 ;Send BREAK char RXE = 2 ;RX enable DTR = 1 ;DTR state TXEN = 0 ;TX enable ;Enable TX/RX, RTS/DTR active and clear errors CMD82C51A = (1< ; 82C51A - Status Register bit definitions DSR = 7 ;DSR pin status BD = 6 ;BREAK detected FE = 5 ;Framing Error OE = 4 ;Data Overrun Error PE = 3 ;Parity Error TXEmpty = 2 ;TX register empty bit position in STATUS RXReady = 1 ;RX ready bit position in STATUS TXReady = 0 ;TX ready bit position in STATUS;============================================================================ ; Test for the UART I/O (and mini-monitor) ; Version 1.00 ; (C) 2016 - Quest, Johnny ; File name: UARTtest.asm ;============================================================================= .module UARTTEST .area .CODE (ABS) .include "Include/UARTtest.h" ;define come constants ;============================================================================= ; We are operating from ROM, so no RAM available for calls or ; stack operations. STACK = 0xFFFF BUFFER = 0x8000 ;UART buffer address ;============================================================================= ; Initial entry point ;============================================================================= .org 0x0000 ;start at 0x0000 Reset: di ;disable interrupts ld sp, #STACK ;setup the stack im 1 ;interrupt mode 1 jp Main ;jump to main routine ;============================================================================= ; INT - Interrupt handler (not used) ;============================================================================= .org 0x0038 jp CMD_halt ;halt - for INT response testing reti ;============================================================================= ; NMI - Interrupt handler ;============================================================================= .org 0x0066 reti ;============================================================================= .org 0x0100 Main: ;Don't assume a hardware RESET just ocurred. Attempt to force the ; 82C51A into a reset condition. Init8251: ld hl,#res8251 ;Reset sequence for 8251 ld b,#res8251end-res8251 ;Table length ld c,#TermStatus ;Point to control port otir ;OUT and loop until done ;Wait for UART to be ready for TX and RX functions TXnotrdy: in a, (TermStatus) ;fetch the status bit TXEmpty,a ;test TXEmpty bit is set jr z, TXnotrdy ;loop till TX is ready ; send out a newline seroutNL: ld hl, #str_crlf ; send out the newline string seroutNL1: ld a, (hl) ;get the next character cp #0 ;is it a NULL? jr z, seroutNL9 ;if yes, we're done here. seroutNL2: ;wait for TXrdy in b, (c) ;fetch the status bit TXEmpty,b ;test TXEmpty bit is set jr z, seroutNL2 ;loop till TX is ready out (TermData), a ;send it out. inc hl ;go to the next character jr seroutNL1 ;loop till EOS seroutNL9: ;Send out the sign-on message seroutSC: ld hl, #str_splash ; send out the newline string seroutSC1: ld a, (hl) ;get the next character cp #0 ;is it a NULL? jr z, seroutSC9 ;if yes, we're done here. seroutSC2: ;send the splash screen in b, (c) ;fetch the status bit TXEmpty,b ;test TXEmpty bit is set jr z, seroutSC2 ;loop till TX is ready out (TermData), a ;send it out. inc hl ;go to the next character jr seroutSC1 ;loop till EOS seroutSC9: RXloop: ;echo all characters received in b, (c) ;fetch UART status bit RXReady,b ;test RXReady bit is set jr z, RXloop ;loop till RX ready RXloop1: in a, (TermData) ;fetch the character cp #"H" ;was it an "H"? jr z,CMD_halt ;yes, halt cp #"R" ;was it an "R"? jr z,Main ;yes, soft reset cp #"A" ;was it an "A"? jr z,CMD_AVR ;yes, exercise the AVR port RXloop2: ;wait for TXrdy in b, (c) ;fetch the status bit TXEmpty,b ;test TXEmpty bit is set jr z, RXloop2 ;loop till TX is ready out (TermData), a jr RXloop ;continue looping ;============================================================================= CMD_AVR: ;continually strobe the AVR I/O port address ld hl,#avr_test ;byte sequence for the AVR ld b,#avr_test_end-avr_test ;Table length ld c,#AVRData ;Point to AVR port otir ;OUT and loop until done jr CMD_AVR ;continual loop until RESET ;============================================================================= CMD_halt: ;halt the CPU until NMI received di ;disable interrupts halt ;enter Z80 halt mode ;=== Strings ================================================================= ;82C51A reset sequence res8251: ;we can jamb these bytes out full speed .byte 0x00, 0x00, 0x00, 0x40, 0x4D, 0x37 res8251end: avr_test: .byte 0, 1, 2, 4, 8, 16, 32, 64, 128, 255 avr_test_end: str_splash: ;Splash Screen .ascii "82C51A UART Tester - 2016 Johnny Quest\r\n" .ascii " Press 'H' to HALT\r\n" .ascii " Press 'R' to RESET\r\n" .ascii " Press 'A' to strobe AVR\r\n" .byte 0x00 str_crlf: ;CR/LF combo .byte 0x0d, 0x0a, 0x00 ; "\r\n"