Z88 Developers' Notes
Previous Contents Next

25. Manipulating the Blink Registers

It is generally programming practice to use the operating system calls provided rather than manipulating the hardware directly; it avoids compatibility problems with future releases, and treading on the toes of the operating system. We are therefore NOT going to encourage the reader to poke to the screen or disable the operating system! However we shall attempt to give as full a description of the hardware that exists.

Overview of the BLINK registers

The following table lists all the registers in the BLINK chip, which are accessed through the Z80 I/O ports. Not all of the registers are useful to programmers, but they are all included for completeness. Some are described in more detail later in this section. Note that because registers can only be written to or read from, but not both, it would be possible to lose track of register contents. Therefore the operating system use addresses $0400 to $04FF to store 'soft copies' of the values in the BLINK registers, with the low byte of the address being taken from the address of the I/O port. You can read these soft copies, to check out the contents of the registers, but most importantly you should always write to the soft copy for a register before writing to a register itself, for reasons explained below.

I/O port    WRITE                   READ
$70         PB0, pixel base reg.0   -
$71         PB1, pixel base reg.1   -
$72         PB2, pixel base reg.2   -
$73         PB3, pixel base reg.3   -
$74         SBR, screen base reg.   -

$B0         COM, command register   -
$B1         INT, mask & control     STA, interrupt status
$B2         -                       KBD, keyboard
$B3         EPR, EPROM programming  -
$B4         TACK, RTC ack.          -
$B5         TMK, RTC int. mask      TSTA, RTC int. status
$B6         ACK, main int. mask     -

$D0         SR0, segment reg. 0     TIM0, RTC 5ms counter
$D1         SR1, segment reg. 1     TIM1, RTC seconds counter (6 bits)
$D2         SR2, segment reg. 2     TIM2, RTC minutes counter
$D3         SR3, segment reg. 3     TIM3, RTC minutes/256 counter
$D4         -                       TIM4, RTC minutes/64K counter (5 bits)

$E0         -                       RXD, UART receive data register
$E1         -                       RXE, extended receiver data
$E2         RXC, receiver control   -
$E3         TXD, transmit data      -
$E4         TXC, transmit control   -
$E5         UMK, UART int. mask     UIT, UART int. status
$E6         UAK, UART int. mask     -

NOTE: RTC stands for "Real Time Clock", int. stands for "Interrupt", and ack. stands for "acknowledge".

The COM register ( COM, $B0)

This register is detailed first because it controls various diverse aspects of BLINK operations:

Bit         Name        Function
7           SRUN        Speaker source (0=SBIT, 1=TxD or 3200Khz)
6           SBIT        SRUN=0: 0=low, 1=high; SRUN=1: 0=3200 Khz, 1=TxD
5           OVERP       Set to overprogram EPROMs
4           RESTIM      Set to reset the RTC, clear to continue
3           PROGRAM     Set to enable EPROM programming
2           RAMS        Binding of lower 8K of segment 0: 0=bank 0, 1=bank 20
1           VPPON       Set to turn programming voltage ON
0           LCDON       Set to turn LCD ON, clear to turn LCD OFF

The two speaker control bits operate in the following fashion:

SRUN        SBIT        Effect
0           1           Speaker line low
0           1           Speaker line high
1           0           Speaker line oscillates at 3200 Khz
1           1           Speaker line attached to Tx data (Tx data still output
                        to comms. port)

RAMS is cleared on reset thus paging in ROM at logical addresses $0000 to $1FFF. This is necessary since the Z80 program counter is loaded with zero on reset. In normal operation RAMS is set and the lower 8K is the system RAM, which is also used for application static workspace and stack.

Bank Switching ( SR0-3, $D0-3 )

System calls inevitably require a significant time overhead while they decode their input parameters and perform general housekeeping. This could have an effect on some programs which need to switch banks around a great deal, so we present here the method for binding banks via hardware. It should not be necessary to use this code because of the existence of the fast code interface described in "Miscellaneous useful routines". This section is included to provide some explanations as how things work. If you want your code to be compatible with future versions of the machine you should only use the fast code interface.

To rebind one of the segments 0 to 3, the program writes the bank number into the relevant "segment register"; there are four segment registers, one for each segment. These are held in the BLINK chip and adressed via the Z80 I/O ports $D0 to $D3. Thus:

            ld   a,20               ; do not use this code...
            out  ($D3),a            ; under any circumstances

will bind bank 20 (internal RAM) to segment 3.

Because the segment registers are write-only, the operating system needs 'soft copies' of the current bindings so it can restore the old bindings correctly after it has rebound banks at the start of OS calls or interrupt service routines. These soft copies are stored at machine locations $04D0 to $04D3. It is VITAL that the desired bindings are stored here BEFORE actually binding the bank; if the bank were bound first, then in the event of an interrupt occurring immediatly after the output instruction, the interrupt service routine would rebind the bank to the contents of the soft copy (assuming it does rebind the relevant segment), and the desired change would be lost. The equivalent of an OS_Mpb would be:

            ld   bc, $04D0 + segment            ; address of soft copy
            ld   a, bank                        ; bank number
            ld   (bc),a                         ; update the soft copy
            out  (c),a                          ; bind in the bank

Similarly, the equivalent of the OS_Mgb would be simply to read the soft copy:

            ld   a,($04D0 + segment)


No convenient system call is provided for blowing individual bytes to EPROM. Hence we detail a method for doing so by directly manipulating the gate array registers.

To write a byte to EPROM, the relevant physical address must be in slot 3, ie. banks $C0 to $FF. The user first sets up the correct programming signals for the type of EPROM concerned. This is done by writing to the BLINK register EPR, addressed by I/O port $B3.

EPR, EPROM programming register:

BIT         NAME        FUNCTION
7           PD1         Two bits representing the length of delay period
6           PD0
5           PGMD        State of program pulse during delay period
4           EOED        State of EOE during delay period
3           SE3D        State of slot 3 select during delay period
2           PGMP        State of program pulse during porch period
1           EOEP        State of EOE during porch period
0           SE3P        State of slot 3 select during porch period

NOTE: For 32K EPROMs write $48 (@01001000) to EPR and for 128K, 256K EPROMs use $69 (@01101001).

The delay periods are as follows, but note that during overprogramming (when OVERP is set in COM) the period is tripled. The porch period is not exact but is never less than 2.4us.

PD1         PD0         Period
0           0           4.88us
0           1           312.5us
1           0           2.5ms
1           1           10ms

To enter EPROM-programming mode, the user must set VPPON and PROGRAM in the COM register, addressed by I/O port $B0, and also switch off the LCD by clearing the LCDON bit. When this is done, programming can be achieved by writing to the relevant address (which obviously needs to be a physical address in slot 3 ie. banks $C0 to $FF) as though it were RAM. However, writing to slot 3 addresses in program mode causes BLINK to take over. BLINK will hold the data and address busses with the appropriate data and then do a porch cycle, followed by a delay cycle and then another porch cycle before returning control to the processor. Once a byte has been programmed, it needs to be verified. This is done by exiting programming mode - clear VPPON and PROGRAM - and reading the byte back to see if it has been successfully blown. No more than 75 attempts should be made to blow any byte, and if it has not been blown by that stage, something is clearly wrong. Note that a byte will usually be successfully blown in one attempt. Once a byte has been successfully programmed, OVERP (in COM) should be set and the byte overprogrammed. Each byte should be overprogrammed the same number of times as it first took to program.

A code fragment to blow a byte in register C to logical address (HL) - assumed to be bound to an EPROM address - is reproduced below. Note that when the Filer writes to the EPROM it does so by blowing blocks of bytes, rather than writing one byte at a time, and so operates a little faster than blowing each byte individually.

.start      ld   b, 75              ; Max. number of attempts
            ld   a, $48             ; for 32K EPROMs
            out  ($B3),a            ; Set EPROM programming signals

.proloop    ld   a, $0E
            out  ($B0),a            ; Set VPP and PROGRAM bits
            ld   (hl),c             ; write byte to EPROM (address)
            ld   a, $04
            out  ($B0),a            ; Reset VPP and PROGRAM bits
            ld   a,(hl)
            cp   c                  ; verify byte
            jr   z, byteok
            djnz proloop
; handle failure to write byte here...

.byte       ld   a, 76              ; one more than maximum number of attempts
            sub  b                  ; same number of times it took to program
            ld   a,b

.ovploop    ld   a, $2E             ; Reset VPP, PROGRAM
            out  ($B0),a            ; and OVERPROGRAM
            ld   (hl),c             ; write byte
            ld   a, $04
            out  ($B0),a            ; reset VPP, PROGRAM and OVERPROGRAM
            djnz ovploop

            ld   a, $05             ; set COM register to
            out  ($B0),a            ; turn screen back on

The Z88 Assembler Workbench application Zprom uses the above algorithm in principle, but blows to EPROM in blocks of max. 16K in one go (depending of the user defined range) by using the standard Z80 instruction LDIR.

UART Receive registers

RXD ($E0), receive data register

7  6  5  4  3  2  1  0
D7 D6 D5 D4 D3 D2 D1 D0

RXE ($E1), extended receive data register

BIT         NAME        Function
7           -           Always clear
6           -           Always clear
5           FE          Frame error, set if error
4           RXD         Value of RXD pin on BLINK
3           TCLK        Internal transmit clock
2           RCLK        Internal receince clock
1           PAR         Parity or first stop bit
0           START       Start bit (should be zero)

RXC ($E2), receive control register

BIT         NAME        Function
7           SHTW        Set to select short word mode
6           LOOP        Set to connect transmit to receive (used for testing)
5           UART        Set to hold UART in RESET
4           ARTS        Auto RTS mode
3           IRTS        Invert RTS
2           BAUD 2      These three bits define receiver baud rate
1           BAUD 1
0           BAUD 0

SHTW is used to align RXC register with the incoming data such that the byte you want ends up in RXC and you do not get stuck with parity or stop bits. We can see the effect of SHTW by showing how an incoming data stream is mapped onto RXC and RXE. Suppose the incoming data is

--> S2 S1 D7 D6 D5 D4 D3 D2 D1 D0 ST --> Z88

BITS        If SHTW=0               If SHTW=1
S2          FE (inverted)           Not clocked in
S1          PAR                     FE (inverted)
D7          D7                      PAR
D7-D0       D7-D0                   D7-D0
ST          START                   START

When ARTS is set then RTS will be asserted every time a new character is received and cleared when the receive register has been read. Manual control of RTS can be achieved by using IRTS to change the current state of the line.

The baud rates set by BAUD 2 to BAUD 0 are as follows:

Value       (Baud2-0)   Baud rate
0           000         75
1           001         300
2           010         600
3           011         1200
4           100         2400
5           101         9600
6           110         19200
7           111         38400

UART Transmit registers

The transmit data registers is 11 bits wide with the upper 3 bits being written by the address lines A8-A10 during the OUT instruction. The register looks like this:

TXD ($E3), Transmit data register

            10          9           8           7-0
            STOP2       STOP1       START       D7 - D0

If SHTW (in RXC) is set then STOP2 will not be sent out and only 10 bits will be sent in all. To load the register you must use a code sequence like:

            ld   a, data            ; 8 bits to send
            ld   b, @00000110       ; 2 stops, 1 start
            ld   c, TXD             ; I/O port $B3
            out  (a),a              ; transmit data

TXC ($E4), Transmit control register

BIT         NAME        Function
7           UTEST       Set fast baud rate for receive and transmit
6           IDCD        If set, DCD interrupts when low (else when high)
5           ICTS        If set, CTS interrupts when low (else when high)
4           ATX         Auto transmit mode
3           ITX         Invert Tx data output pin
2           BAUD2       These three bits define transmit baud reate
1           BAUD1       (See above table for baud rates)
0           BAUD0

When ATX is set the data in TXD will not be sent until CTS is asserted. Manual control of the TxD pin can be achieved by using ITX to change the current state of the line.

UART interrupts and status

UIT ($E5), UART interrupt status register

BIT         NAME        Function
7           RSRD        Receive shift register full (cleared when read)
6           DCDI        DCD interrupt
5           CTSI        CTS interrupt
4           TDRE        Transmit data register empty
3           -           -
2           RDRF        Receive data register full
1           DCD         State of DCD line
0           CTS         State of CTS line

The registers UMK and UAK control the enabling and acknowledging of the UART interrupts. CTS and DCD can both be enabled to cause interrupts and the interrupts can be set to occur on either state of the line (IDCD and ICTS in the TXC register). To clear a DCD interrupt the state of IDCD needs to be changed (since the interrupt is level and not edge triggered) and then the DCD bit should be set in the interrupt acknowledge register UAK. CTS works in exactly the same way as DCD.

UMK ($E5), UART interrupt mask register

BIT         NAME        Function
7           -           -
6           DCD         If set, DCD interrupts are enabled
5           CTS         If set, CTS interrupts are enabled
4           TDRE        If set, transmit data register empty interrupt enabled
3           -           -
2           RDRF        If set, receive data register empty interrupt enabled
1           -           -

UAK ($E6), UART interrupt acknowledge register

BIT         NAME        Function
7           -           -
6           DCD         Set to acknowledge DCD interrupt
5           CTS         Set to acknowledge CTS interrupt
4 - 0       -           -

To clear TDRE and RDRE interrupts you write to the transmit data register or read from the receive data register respectively.

UART examples

It is sometimes desirable to explicitly manipulate the RTS line of the serial interface for example when auto-dialing modems. This may be done by manipulating the IRTS bit in the RXC register. In programming terms this is bit 3 of I/O port $E2. It is important that the soft copy is updated first.

Bit 3 is XOR'ed with the normal state of the RTS line, as defined by RDRF in UIT (receive data register full) and ARTS (auto RTS) in RXC.

The logic is RTS=[[RDRD AND ARTS] XOR IRTS]. This bit therefore toggles the RTS line. A code fragment to achieve this might be:

            ld   bc, $04E2          ; address of soft copy
            ld   a,(bc)             ; fetch old value
            or   8                  ; or AND 247 to reset
            ld   (bc),a             ; update soft copy
            out  (c),a              ; update hardware

The TxD (transmit data) line can be inverted by changing the state of ITX in TXC (the transmit control register) using bit 3 of I/O port $E4 (soft copy address $04E4) in a similar way to the RTS line. The value of the RxD line (receive data) can be found by reading RXDB in RXE (extended receive data register, using bit 4 of I/O port $E1 (soft copy $04E1 - the soft copy will not necessarily be up to date).

The KeyboardKBD, $B2 )

The keyboard is read by outputting a row on the address line A8 to A15 and reading the KBD register in BLINK. The keyboard matrix looks like this:

         | D7     D6      D5      D4      D3      D2      D1      D0
A15 (#7) | RSH    SQR     ESC     INDEX   CAPS    .       /      
A14 (#6) | HELP   LSH     TAB     DIA     MENU    ,       ;       '
A13 (#5) | [      SPACE   1       Q       A       Z       L       0
A12 (#4) | ]      LFT     2       W       S       X       M       P
A11 (#3) | -      RGT     3       E       D       C       K       9
A10 (#2) | =      DWN     4       R       F       V       J       O
A9  (#1) | \      UP      5       T       G       B       U       I
A8  (#0) | DEL    ENTER   6       Y       H       N       7       8

DIA         <DIAMOND> key
SQR         <SQUARE> key
LSH         Left <SHIFT> key
RSH         Right <SHIFT> key
LFT         <LEFT> arrow key
RGT         <RIGHT> arrow key
DWN         <DOWN> arrow key
UP          <UP> arror key

The keyboard can be read directly using a piece of code like below, although note that while reading the keyboard, BLINK will stop the processor for up to 40us:

            ld   c, $B2             ; I/O port $B2
            ld   b, row             ; one row of A15-8
            in   a, (c)             ; get column data in A

To check for the escape key being pressed.

            ld   c, $B2                                               
            ld   b, @01111111       ;Detect keys in matrix top row.
            in   a, (c)                                               
            cp   @11011111          ;Bit pattern returned if only the escape key is pressed.

If KWAIT (in INT) is set then performing a key read read will send the machine into Snooze state. When a key press is made the machine will wake up and if KEY (in INT) is set then a keyboard interrupt will occur.


The INT ($B1) register controls which interrupts are enabled:

BIT         NAME        Function
7           KWAIT       If set, reading the keyboard will Snooze
6           A19         If set, an active high on A19 will exit Coma
5           FLAP        If set, flap interrupts are enabled
4           UART        If set, UART interrupts are enabled
3           BTL         If set, battery low interrupts are enabled
2           KEY         If set, keyboard interrupts (Snooze or Coma) are enabl.
1           TIME        If set, RTC interrupts are enabled
0           GINT        If clear, no interrupts get out of blink

The ACK ($B6) register is used to acknowledge and thus clear an interrupt:

BIT         NAME        Function
7           -           -
6           A19         Acknowledge A19 interrupt
5           FLAP        Acknowledge FLAP interrupt
4           -           -
3           BTL         Acknowledge battery low interrupt
2           KEY         Acknowledge keyboard interrupt
1           -           -
0           -           -

The STA ($B1) register provides information about which interrupt has actually occurred:

BIT         NAME        Function
7           FLAPOPEN    If set, flap open else flap closed
6           A19         If set, high level on A19 occurred during coma
5           FLAP        If set, positive edge has occurred on FLAPOPEN
4           UART        If set, an enabled UART interrupt is active
3           BTL         If set, battery low pin is active
2           KEY         If set, a column has gone low in snooze (or coma)
1           -           -
0           TIME        If set, an enabled TIME interrupt is active

Coma and Snooze

The COMA state is entered via a HALT instruction. Any interrupt can cause a wake up, providing there is enough power to run the machine and the interrupt is enabled. If all interrupts are disabled withing BLINK (regardless of whether they are enabled in Z80 - the interrupt line to the Z80 comes from BLINK) before a HALT instruction, then the machine will never be able to wake up again. One special way of waking the machine is by ensuring KEY and KWAIT (in INT) are set before executing HALT. During HALT the upper 8 address bits hold the contents of the Z80 I (Interrupt) register. If this is set appropriately, a special key sequence can cause an interrupt to occur, waking the machine. This is how the pressing the two <SHIFT> keys wakes up the machine. The I register is loaded with @00111111 before HALT is executed. The two <SHIFT> keys occur in the two left hand columns of the keyboard matrix, so they can be sensed with the address lines in this state. Note that the minimum time between entering COMA and waking up again is 5ms, so the COMA state would not be suitable for waiting for regular interrupts. Finally note that the UART will not work in COMA because all the UART clocks are shut down.

During snooze the Z80 clock is stopped, but all the other system clocks continue to run. Therefore it is possible to use the UART from snooze. Any keyboard column going low will cause a wakeup from snooze, even if the keyboard interrupts are disabled. Note that the keyboard data returned after wakeup will not necessarily be valid, so the keyboard should definitely be read again.

Real Time Clock (TIM0-4, $D0-4)

Register    Period      Counts to   Width in bits
TIM0        5ms         199         8
TIM1        1 second    59          6
TIM2        1 minute    255         8
TIM3        256 minutes 255         8
TIM4        64K minutes 31          5

The clock is reset to zero by settings RESTIM in the COM register. The clock will be held in reset, ie. it won't count until RESTIM is cleared again. In order to guarantee a valid result from any of these registers, software needs to read until two equal values are returned. This should always occur within three read cycles.

There are three interrupts that the real time clock can generate and these are TICK (100 times a second), SEC (once a second) and MIN (once a minute). Three registers are associated with these interrupts:

TSTA ($B5), Timer interrupt status

BIT         NAME        Function
7 - 3       -           -
2           MIN         Set if minute interrupt has occurred
1           SEC         Set if second interrupt has occurred
0           TICK        Set if tick interrupt has occurred

TMK ($B5), Timer interrupt mask

BIT         NAME        Function
7 - 3       -           -
2           MIN         Set to enable minute interrupt
1           SEC         Set to enable second interrupt
0           TICK        Set to enable tick interrupt

TACK ($B4), Timer interrupt acknowledge

BIT         NAME        Function
7 - 3       -           -
2           MIN         Set to acknowledge minute interrupt
1           SEC         Set to acknowledge second interrupt
0           TICK        Set to acknowledge tick interrupt

Unused I/O ports

The I/O ports $F0 to $FF are not used by the system and so are available to hardware developers. The soft copy page $0400 is entirely reserve for I/O port use and should be used. You must not use this memory for any other purpose. Remember that when updating a soft copy, it must always be done before updating the port because of the possibility of an interrupt occurring and receiving bogus information about the state of a port.

 Screen registers

The addresses of the screen files are held in BLINK registers PB0 to PB3 and SBR. Their width determine the file granularity in a bank. For example, the LORES1 can be stored at 0, 4, 8 or 12K. Their softcopies are stored at ($046x) for the most significant bits and ($047x) for the 8 right bits. The role of these registers is as follows:

Register   Name    Role    Lentgh      Granularity Width    ($046x)
PB0 ($70)  SC_LR0  LORES0  512 bytes   512 bytes   13 bits  5 bits
PB1 ($71)  SC_LR1  LORES1  3.5K bytes  4K bytes    10 bits  8 bits
PB2 ($72)  SC_HR0  HIRES0  6K bytes    8K bytes     9 bits  1 bit
PB3 ($73)  SC_HR1  HIRES1  2K bytes    2K bytes    11 bits  3 bits
SBR ($74)  SC_SBR  SBF     2K bytes    2K bytes    11 bits  3 bits

When reading or writing to these registers IN or OUT (C),A instructions are used. B always contains the left bits of data (more than 8), A the data, C the port. So, A is ($047x) and B is ($046x). Please, refer to the Screen Files chapter for more about their structure.

Previous Contents Next
BBC BASIC & in-line assembler Manipulating the Blink Registers Z88 Motherboard Hardware