Now that our system has a stack it will be much easier to test code out. One such test is to get interrupts to work between the Z80 microprocessor and the UART. This will free up the system from polling so that it can dedicate processing time to other tasks.
Interrupts will allow the UART to signal to the Z80 when an event has happened. One such event is when the user has pushed a key on the serial terminal emulator software. This is a very powerful feature because before we would have to have the Z80 process and wait in tight loops (polling) to make sure it would catch everything that a slow peripheral was trying to tell it. With interrupts however the Z80 is free to do other tasks until it is interrupted. To interrupt the Z80 microprocessor a peripheral needs to pull the /INT (pin 16) logic-low. When this happens the Z80 will respond with whichever interrupt mode it is set to. There are three interrupt modes the Z80 can work with, and we are using interrupt mode 1.
In interrupt mode 1 when the /INT pin is pulled logic-low and interrupts are enabled using the EI instruction the Z80 will stop what it is doing and jump to the address 0038H. Using interrupt mode 1 the Z80 will always jump to this memory location. At this location an interrupt service routine should reside to handle the interrupt that was generated. This is a very simple interrupt scheme and should be used for simple interrupt implementations like we are using. In our circuit the only device that will be interrupting the Z80 will be the UART. To enable multiple devices to interrupt the Z80 they should be wire-OR to the /INT pin. I have not done this in my schematic since I only have one interrupting device so I used a direct connection. In the case of multiple interrupting devices using interrupt mode 1 you will need software to be able to identify the type of interrupt that was generated since the /INT pin only indicates that an interrupt was generated and does not indicate which interrupt was generated. Luckily in the 16550 UART there is an interrupt identification register that can be read after an interrupt to find out which type of interrupt was generated.
Since 0038H is such a low-address and the Z80 starts processing instructions at address 0000H, a jump should be implemented to skip over the interrupt service routine to a main routine. An example of this would be the following code,
.ORG $0000 START: DI JP MAIN ;Jump to the MAIN routine .ORG $0100 MAIN: LD SP,RAMTOP IM 1 ;Use interrupt mode 1 EI ;Enable interrupts END_PROGRAM: HALT JP END_PROGRAM .ORG $0038 MODE1_INTERRUPT: DI ;Disable interrupts EX AF,AF' ;Save register states EXX ;Save register states CALL UART_INTERRUPT_ROUTINE EXX ;Restore register states EX AF,AF' ;Restore register states EI ;Enable interrupts RET .END
In the above example we immediately jump over the first 0100H addresses to MAIN and then with the .ORG directive in the TASM assembler we can place our interrupt service routine right where it belongs at address 0038H. Notice below main how there is END_PROGRAM which basically issues HALT and has a JP instruction to itself after. This odd bit of code is because HALT can be exited by an interrupt. After the interrupt is serviced and a RET is issued the program resumes at the instruction after HALT. Jumping back to itself will leave the system idle until an interrupt is generated. The interrupt will be serviced and then the system will go back to being idle due to the JP and HALT which is exactly the behavior we want for our test program.
The test program that I have implemented demonstrates two Z80 assembly subjects, interrupts and strings. First the program prints out a few strings to form a header on the terminal screen, much like many of the ROM monitors in older computers. Second the program halts and waits for an interrupt to be generated. The system is setup for interrupts to be generated when it sees a character being received. When this happens the system runs a routine to echo the character received back to the terminal, then goes idle waiting for the next interrupt to occur. Obviously during this waiting period the Z80 could be doing other things, but for simplicity I just had it halt so that I could monitor the LED I placed on the Z80’s HALT pin toggling when a HALT instruction is issued.
From here I think I am going to either work more on producing a ROM monitor application or try and implement a BASIC interpreter as per suggestions I have received in the past. Either way I am itching to get my design off of the breadboard and onto a real PCB. I actually ran into a lot of grief during this test because of loose connections on the breadboard. My code was acting in very strange ways unlike during the stack tests. It was only after my cat started playing with my multi-meter’s probes that I considered testing the breadboard for continuity and sure enough D4 was not connected between the data bus and the RAM, but just happened to work during my last test.
All Files from this project can be downloaded from my GitHub Repository,