Z80 testing – Simple program to exercise the UART

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.

z80_uarttest
Logic Analyzer monitoring the Z80 while starting the UARTtest program
 ; 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"

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s