Jump to content
  • Advertisement
  • entries
    222
  • comments
    606
  • views
    592867

Times, backlights and off-page calls

Sign in to follow this  
benryves

1438 views

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.
Sign in to follow this  


2 Comments


Recommended Comments

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

Share this comment


Link to comment
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

Share this comment


Link to comment

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!