Programming the NC100’s IO Ports
The Amstrad NC100 I/O Specification lists a series of IO ‘ports’ which can be used to read or write Notepad hardware status and configuration information. These ports are accessed through the Z80 microprocessor, which provides 256 unique addresses for system IO devices along with means to send data to those devices and to receive data from those them.
The Z80 Port System
The Z80’s IO ports - and therefore those of the NC100 - are accessed using the IN and OUT assembly language mnemonics. These commands cause the microprocessor’s IOREQ pin to be asserted instead of its MEMREQ pin. IO devices use this as an enablement trigger and determine whether they are the device being targeted by sampling the Z80’s A0-7 pins (or a subset of them depending on the design of the system and the specific IO devices that are part of it). Data to be output to the port is read by device from the Z80 data bus; input data is placed on the data bus.
This is entirely identical to the way the Z80 communicates with memory devices: MEMREQ is asserted and connected memory devices sample the address bus to find out which one of them is to service the operation. Data is received or written to the processor data bus. The only difference is the use of the MEMREQ signal pin for memory operations and the IOREQ signal pin for IO operations. The address and data buses are common to both, though only the lower eight address lines are relevant for IO (though the Z80 can make use internally of the upper lines). What determines which of these two signal pins are asserted? The use of memory commands, such as LD, or the use of IO commands, such as IN and OUT.
The NC100’s Ports
The ports used by the NC100 are listed in the Amstrad NC100 IO Specification. They provide access to many aspects of the Notepad’s hardware, such as its uPD71051 UART chip (ports 0xC0 and 0xC1), the TM8521 real-time clock chip (ports 0xD0-DF) and keyboard (ports 0xB0-B9), but are most commonly used to switch banks RAM and ROM into the Z80’s 64KB address space, via ports 0x10-13.
For example, you need to switch in the NC100’s 16KB of video RAM (though only the top 4KB are actually used for this purpose; the remainder is reserved) into a high portion of the Z80 address space in order to write directly to the bitmap present on screen. Once you’ve done writing to the screen RAM, you page the video RAM block out again, restoring the system to its state at the start of the operation.
For more information on memory bank switching on the NC100, see the Amstrad NC100 I/O Specification pages 3-6, and the Amstrad Notepad Advanced User Guide Section 2, Chapter 3.
How to Program the NC100’s Ports
Accessing the NC100’s ports is performed in machine code, typically via a routine installed using BBC Basic’s built-in assembler. For example, the following code can be used to read the NC100’s memory card status byte.
.carddata DEFB &A0 ; The memory card data port
PUSH AF ; Preserve the A register
IN A,(carddata) ; Read port &A0 into A
LD (value),A ; Store the port value for access
; from Basic
POP AF ; Restore A
RET ; Return from subroutine
.value 0
In Basic, this would be rendered as:
10 DIM MC% 16
20 PROCasm
30 CALL MC%
40 V%=?value
50 CLS:PRINT "NC100 State ("+STR$(V%)+")":PRINT
60 PRINT "AA batteries ";
70 IF (V% AND &08)=0 THEN PRINT "good" ELSE PRINT "weak"
80 PRINT "Li battery ";
90 IF (V% AND &04)=0 THEN PRINT "good" ELSE PRINT "weak"
100 IF (V% AND &10)>0 THEN PRINT "Power level is good"
110 PRINT "Memory card ";
120 IF (V% AND &80)=0 THEN PRINT "present ";
130 IF (V% AND &40)>0 PRINT "(write protected, "; ELSE PRINT "(writeable, ";
140 IF (V% AND &10)>0 PRINT "battery good)" ELSE PRINT "battery weak)"
150 IF (V% AND &80)>0 PRINT "not present"
152 PRINT:PRINT "PRESS ANY KEY TO EXIT";:G%=GET
160 CLS:END
1000 DEF PROCasm
1010 FOR PA%=0 TO 2 STEP 2
1020 P%=MC%
1030 [
1040 OPT PA%
1050 :
1060 .carddata DEFB &A0
1070 PUSH AF
1080 IN A,(carddata)
1090 LD (value),A
1100 POP AF
1110 RET
1190 .value
1200 ]
1210 :
1220 NEXT
1230 ENDPROC
Paging Video Memory
The NC100 contains 64KB of RAM and 256KB of ROM. Together these exceed the total memory space addressable by the Z80 microprocessor’s 16 address lines. How did Amstrad make the combined 320KB of NC100 memory fit into the 64KB Z80 memory map? Circuitry in the Notepad’s ASIC maps the actually memory, arranged in units of 16KB, called “blocks” or “pages”, to the four pages within the Z80 memory space. So when, say, the Z80 retrieves the value of the byte at 0x4000 (the start address of the second 16KB page in its memory map), that byte may actually reside at any of the 20 pages in the real memory — it just depends which of these blacks has been “paged in” to the Z80 page 2.
For example, when the NC100 is being used for word processing, 32KB of spelling checker code within the ROM is mapped to the first two Z80 pages. The third page is mapped to RAM, and the remaining page is mapped to the Protext application code, also in ROM.
It doesn’t stay this way. Every time the dictionary is used, that gets temporarily paged in in place of Protext. Every time the screen is written to, the video RAM that holds the data read by the LCD circuitry is paged in in place of Protext and/or the dictionary. Only 4KB are required for the video bitmap, but because the page size if 16KB, the remaining 12KB of the video RAM is paged in too, but is reserved by the system.
Paging the video memory in (and back out again when screen writing is done) takes place every time the screen is written to or read by Protext or the system, which is pretty much any time you hit a key to switch menus to type a character. Your own programs will need to do the same if they write directly to the screen rather than do so via Basic. The Basic memory map has pages 0-2 mapped to RAM and page 3 mapped to Basic in ROM.
The following assembly code (portion) shows how to page in the video memory in a neighbourly way, and restore the page state when you’re done. The address passed via OUT is 0x13 the port that controls what is paged to Z80 page 3 (0xC000-FFFF). The first value output to it, 67, is 01000011 in binary. Bits 7 and 6 (01) indicate we’re paging in internal RAM. Bits 5 to 0 (000011) determine which page of the type specified by bits 7 and 6 should be used, in this case the fourth page of the internal RAM, which is where the video RAM is stored.
Memory address 0xB003 is one of four bytes of system memory that is used to record the current state of the paging system. All code should use this as a source of truth and keep it updated as memory is paged in and out. This ensures that if a processor interrupt causes your code’s execution to be suspended while the interrupt service routine completes, both the ISR and your code can trust that pages remain where they expect to be. It that’s not the case, the system will certainly crash sooner or later, probably sooner.
.start LD A,(&B003) ; Retrieve the block currently paged into
; Z80 page 0xC000-FFFF...
PUSH AF ; ...and store it on the stack
LD A,67 ; Set the page number holding the video RAM...
LD (&B003),A ; ...write it to the system record...
OUT (&13),A ; ...and output the new page number to page
; the video memory into Z80 page 0xC000-FFFF
. . .
.restore POP AF ; Retrieve the stored page ID
LD (&B003),A ; Update the system record
OUT (&13),A ; Output the restored page number to page it
; back into Z80 page 0xC000-FFFF
RET