Times, backlights and off-page calls

Published September 14, 2008
Advertisement
Dates, times and backlights

I'm using a DS1307 real-time clock to provide the computer with real-time date and time functions. It's a great little chip - all it needs is power, two lines for I2C communications, a 32768Hz crystal between two pins and a back-up battery to keep it ticking when main power is removed and it's happy. That accounts for seven pins; the last remaining pin can be used as a one-bit output (you can set it to a high or low state in software) or it can be configured to output a square wave at 1Hz, ~4kHz, ~8kHz or ~32kHz.


BBC BASIC can access the clock via the TIME$ pseudo-variable. This string variable returns the date and time in the format Sun,14 Sep 2008.15:20:00, and you can set the clock by assigning to the variable. When setting the clock you can specify either the date, the time, or both. Parsing the string has been an interesting exercise in Z80 programming, as it's not something I've ever attempted without regular expressions before!


The only hardware modification since last time is a very poorly implemented software control of the backlight. The fifth bit of the control port specifies whether the backlight is on or off, and it can be toggled with the *BACKLIGHT command. I say "poorly implemented" as the transistor driver I'm using to interface the hardware port with the backlight LEDs results in a much dimmer backlight than when I had the LEDs hooked up directly to the power supply (on the positive side, at least the 5V regulator's heatsink is cool enough to touch - the backlight draws a lot of current).

Calling off-page functions

Now that I have access to all eight 16KB "pages" that make up the 128KB OS ROM, it may help to explain how one can use all of this memory. After all, if page 1 is swapped in and you wish to call a function on page 2, a regular Z80 call isn't going to work as you need to swap page 2 before calling the function then swap page 1 back in afterwards.

The trick is to exploit the way that the Z80 handles calling subroutines. There is a 16-bit register, PC, which stores the address of the next instruction to execute. When you call a subroutine, the Z80 pushes PC onto the stack then sets PC to the address of the subroutine. When you return from a subroutine (via the ret instruction) the Z80 simply pops the value it previously pushed onto the stack and copies this back to PC. Instead of calling the target subroutine directly, you call a special handler that is available on every page. Following your call is 16-bit identifier for the off-page function you wish to call. This handler then (prematurely) pops off the return address from the stack, reads the 16-bit value that follows it (which is the indentifier of the function you wish to call), looks up the page and address of the target function, swaps in the correct page and calls it as normal. When the function returns, the handler then swaps back the calling page and jumps back to the return address.

The Z80 has a series of rst instructions that call fixed addresses within the first 256 bytes of memory. These instructions are useful as they're small (one byte vs three bytes for a regular call) and fast, so I'm using rst $28 to call the off-page call handler (for no other reason than it's the same as the handler on the TI-83+).

As an example, let's say you had this function call at address $2B00:
$2B00:    rst $28$2B01:    .dw $30F0$2B03:    ; We'd return here.


When the Z80 executed that rst $28 it would push $2B01 (address of the next instruction) to the stack then jump to $28. The handler at $28 would do something like this:
    pop hl    ; hl is a 16-bit register and would now contain $2B01    ld e,(hl) ; Read "e" from address pointed to by hl, now equals $F0    inc hl    ; hl = $2B02    ld d,(hl) ; Read "d" from address pointed to by hl, now equals $30    inc hl    ; hl = $2B03 ("real" return address)    push hl   ; push hl back on the stack so when we return from here we end up in the correct place.

Now, de is $30F0 - this is the identifier of the function we're calling. In my case, the identifier points to a function table on page 0. Each entry in the table is three bytes - one byte for the page index and two bytes for the address of the function on the that page. We'd need to do something like this:
    in a,(Page)  ; Read the current page into A.    push af      ; Push A and F to the stack for later retrieval.    and ~7       ; Mask out the lower three bits of the address.    out (Page),a ; Sets current ROM page to 0.    ex de,hl     ; Exchanges de and hl, so hl now points to the function identifier.    or (hl)      ; ORs contents of memory at (hl) (ie, page number) with a, to set the target page.    inc hl    ld e,(hl)    ; e = LSB of target address    inc hl    ld d,(hl)    ; d = MSB of target address    ex de,hl     ; hl = target address.    out (Page),a ; Swaps in the correct page.
At this point, the correct page is swapped in and hl points to the address of the function to call. All we need to do now is call it!
    ld de,ReturnFromHandler ; Address to return to.    push de ; Store on stack.    jp (hl) ; Set pc = hl.ReturnFromHandler    ; Swap back the original page which was pushed earlier...    pop af    out (Page),a    ret ; ...and return to the calling page!

A further advantage of using rst $28 to replace call is that both are the same size, so the assembler can check if you're calling an address on the same page or a different one and insert the regular (and much faster) Z80 call in places where you don't need to swap the page.


Click for video (578KB XviD MPEG-4 AVI)

Finally, the obligatory video, this time showing a clock that toggles the backlight once a second.
0 likes 2 comments

Comments

nolongerhere
Its lookin good! I have a question though, I was wondering if that is the only way you can do the bank switching or did you just choose to do it that way?

I was thinking you would do it by setting a register to a certain value corresponding to a particular page and then maybe make a call. Or is that not even possible?

Your the one who knows what theyre doing, I was just curious though :-p
September 14, 2008 10:48 PM
benryves
Quote:Original post by Falling Sky
I was thinking you would do it by setting a register to a certain value corresponding to a particular page and then maybe make a call. Or is that not even possible?
In either circumstance you'd need to call some handler or perform the switching yourself. Performing the switching yourself is difficult, as ld a,PageNumber \ out (Page),a \ call AddressOnPage doesn't work (before you get to the call the current page has been swapped out). This is why the handler lurks in the first 256 bytes of ROM, which in my case contains exactly the same code on each page, so you can switch as much as you like in that area.

As for doing something like ld hl,FunctionIdentifier \ rst $28, that would work but is a byte larger and would prevent you from passing hl to the function. Sticking data you wish to pass to a function immediately after the call is a fairly useful trick, and can allow for easier to follow code.
; Here's the usual way of doing it.
SomeFunction
    ; code
    ld hl,String
    call Console.PutString
    ; code
    ret
.db "Some String",0

; Using an inline string instead.
SomeFunction
    ; code
    call Console.PutString
    .db "Some String",0
    ; code
    ret
September 15, 2008 05:10 AM
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Advertisement