Hi all,

I'm currently trying to understand what the restart commando is used for. I understand it's being used for b_calls. b_call(_PutS) for example, is equivalent to:

Code:
RST 28H
.DW $450A

So, as soon as this part of the code is being reached, the PC jumps to $0028. But what happens simultaniously and after that?
I learned from the ASM in 28 Days tutorial that if you execute IM 1, a RST 28H instruction is performed about 140 times a second. So my question is... how do I use this? It executes some kind of b_call, I suppose, but there is no such thing as _PutS to determine which b_call it should execute. And furthermore, how do I use this feat of the TI-84+ calculator in something like making a cursor blink or whatever, in other words, keeping track of the number of RST 28Hs executed?

Greetings, Arriopolis.
A rst is just shorthand for a call. For example, rst 20h (rMov9ToOP1), behaves in exactly the same way as call 20h. The advantage is that the rst instruction is only 1 byte, while a call requires three bytes of storage for the code.

The rst vector at 28h is the bcall handler. The handler at 28h looks at the code following your rst instruction (which with a bcall is the address of the bcall) and uses that to find the routine you request. See day 14 of 83pa28d for an example of the sort of thing it's doing, "Passing parameters via the code stream".

The system interrupt (what gets called when the machine is in IM 1) is at 38h (not 28h), but it otherwise behaves in mostly the same way. The system automatically invokes that ~140 times per second, and that handler saves the state of all your registers, does what it wants, and returns control to whatever is running, and the original code doesn't even need to know it was ever interrupted.

To blink a cursor or some such, you could just store a counter somewhere in memory, and increment it every time the ISR is invoked, then do something when the counter hits a predefined limit. Refer to day 23 of 83pa28d for how you can mess with interrupts yourself.
The presence of the RST instructions makes more sense if you consider it in the context of the 8080 CPU, which the Z80 is based on - why do you need extra instructions that CALL a fixed address?

The 8080 only had one interrupt mode (which is implemented in the Z80 as IM 0), and in this mode the hardware requesting the interrupt needs to put a single-byte instruction on the data bus, which the CPU then executes. By putting a RST instruction on the data bus each interrupting device can get the CPU to run one of a number of different interrupt service routines. The Z80's IM 1 is a bit like IM 0 with an implied RST $38, which can simplify the hardware design (interrupting devices only need to pull /INT low, they don't need to then put an instruction on the data bus).

A useful property of the RST instructions is that they are smaller and faster than a regular CALL instruction. This property can be exploited by placing frequently-called system routines at RST-friendly addresses ($08, $10, $18, $20 etc) to improve the performance of a program. The TI-OS uses this for its BCALL handler and the rMov9ToOP1, for example.

The Z80 also added a third interrupt mode, IM 2. This one is the most interesting as it allows the interrupt service routine to be placed anywhere in memory, and as such allows you to override the TI-OS ISR with your own. Read "Day 23: Interrupts" in "Learn TI-83 Plus Assembly In 28 Days" for more information, but please be advised that the information about the value placed on the data bus following a particular pattern is wrong - the calculator does not place any value on the bus, and the selected address is, effectively, random.
"Day 23" is just the day I'm struggling with:). I actually know nothing about the internal architecture of the Z80 processor, and I've not worked with anything like that either, making this one big mess of information to me.Smile
So, if I'm right... there are 3 interrupt modes on the Z80:
0 is where hardware requests the interrupt by a single byte sent through the data bus;
1 is where the RST 38h (typo in ASMin28Days) is produced 140 times a second so the hardware only needs to read that byte from the data bus, instead of sending data through it. So how do I know one is sent through the data bus?
2 is where interrupts can be generated by the ON key (bit 0 of port 3), the hardware timer (bit 1 of port 3) and the link port (bit 4 of port 3), and when such an interrupt is executed, I have loaded the I register to create the upper 8 bits of the address of the vector table, which points to the Interrupt Service Routine.
On the TI-83+/84+, mode 0 is more or less equivalent to no interrupts at all, as far as I understand. I don't think that the ASIC generates any hardware interrupts in mode 0. Mode 1 is pretty much the "OS mode", and Mode 2 can be thought of as the "user mode". In Mode 1, you're stuck with the OS's own interrupt at $0038, wheres Mode 2 is almost exclusively used for overriding (or augmenting, if you post-jump to $0038) the TI-OS's interrupt. I'd be happy to share some very nicely-working interrupt code from CALCnet if you'd like.
Well, I tried the following:

Code:

.nolist
#include    "ti83plus.inc"
#define    ProgStart    $9D95
.list
.org    ProgStart - 2
    .DB t2ByteTok, tAsmCmp
;We wanna get a timer, which increments 140 times a second.
    b_call(_getKey)
    DI         ;Disable interrupts until everything's set up.
    LD A, $99      ;Load the address vector table into I.
    LD I, A
    LD HL, Interrupt   ;Set up the vector table.
    LD ($993F), HL
    LD ($997F), HL
    LD ($99BF), HL
    LD ($99FF), HL
    LD A, %00000010   ;I want it to be caused only by the internal timer
    OUT (3), A
    IM 2
    EI         ;Enable interrupts
CheckCounter:
    LD HL, (Counter)   ;Check if counter exceeds 50000
    LD DE, 50000
    OR A      ;Reset carry
    SBC HL, DE
    JP P, CheckCounter
    LD A, %00010011   ;Enable hardware
    OUT (3), A
    IM 1
    RET
Interrupt:
    DI
    EX AF, AF'
    EXX
    XOR A
    OUT (3), A
    LD (CurRow), A
    LD (CurCol), A
    LD HL, (Counter)
    INC HL
    LD (Counter), HL
    LD A, %00000010
    OUT (3), A
    EXX
    EX AF, AF'
    EI
    RET
Counter:    .DW 0
.end
.end

and I get a RAM-clear.
I hope one of you can help me out. Thanks in advance.
I suggest you read the Z80 CPU user manual to get a good handle on how interrupts work on the Z80. IM 1 does not magically interrupt X times per second - this is a function of the calculator's timer hardware.

KermMartian wrote:
On the TI-83+/84+, mode 0 is more or less equivalent to no interrupts at all, as far as I understand. I don't think that the ASIC generates any hardware interrupts in mode 0.

The external hardware doesn't know which interrupt mode the Z80 is operating in. On the TI-83+/84+, mode 0 is more or less equivalent to a crash, as the Z80 will attempt to execute random opcodes every time an interrupt is requested.

arriopolis wrote:
Well, I tried the following:

*snip*

and I get a RAM-clear.
I hope one of you can help me out. Thanks in advance.

As I mentioned earlier, "Learn TI-83 Plus Assembly In 28 Days" is incorrect in its assertion that the value placed on the data bus during interrupt requests follows a particular pattern. You need a full 257-byte vector table for your interrupt service routine rather than just storing the address at $993F, $997F, $99BF and $99FF. You will also need to store your interrupt service routine at an address that has the same least-significant and most-significant byte - such as $9898.
Ok, I now have this:

Code:

;We wanna get a timer, which increments 140 times a second.
    JR Start
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
ISR:         ;$9D9D
    DI
    EX AF, AF'
    EXX
    XOR A
    OUT (3), A
    LD (CurRow), A
    LD (CurCol), A
    LD HL, (Counter)
    INC HL
    LD (Counter), HL
    b_call(_DispHL)
    LD A, %00000010
    OUT (3), A
    EXX
    EX AF, AF'
    EI
    RET
Start:
    b_call(_getKey)
    DI         ;Disable interrupts until everything's set up.
    LD A, $99      ;Load the address vector table into I.
    LD I, A
    LD DE, InterruptNumber   ;Set up the vector table.
    LD HL, $9900
    LD BC, 256
    LDIR
    LD A, %00000010   ;I want it to be caused only by the internal timer
    OUT (3), A
    IM 2
    EI         ;Enable interrupts
CheckCounter:
    LD HL, (Counter)   ;Check if counter exceeds 50000
    LD DE, 50000
    OR A      ;Reset carry
    SBC HL, DE
    JP P, CheckCounter
    LD A, %00010011   ;Enable hardware
    OUT (3), A
    IM 1
    RET
Counter:            .DW 0
InterruptNumber:    .DW ISR

but I still get the exact same result, RAMClear.
What's still wrong?
Your problem is that you're not setting up the table correctly. What you need to do is:

Code:
    LD HL,$9900
    LD DE,$9901
    LD BC,256
    LD (HL),InterruptNumber & $FF
    LDIR


Also, your counter check loop should have a JP C, not a JP P.
LDIR doesn't work like that - it copies BC bytes from address HL to address DE. You would need something like this (generating a table 257 bytes long):

Code:
    ld hl,$9900
    ld (hl),$9D
    ld d,h
    ld e,1 ; Yes, "one", not L.
    ld bc,256
    ldir


Note that there's no need to DI at the start of your ISR as interrupts are automatically disabled when serviced (they are not, however, automatically re-enabled unless you're using RETN at the end of a non-maskable interrupt, so keep using EI \ RET). You will also need to acknowledge interrupts through port 3, and I would recommend against calling BCALLs inside the ISR in case they have nasty side-effects (like re-enabling interrupts) - keep your ISR as simple as possible. Here's a simple ISR from one of my projects that simply increments a counter named Ticks:


Code:
ISR:
    push af
   
    ld a,%01000 ; Acknowledge all interrupts, keep calculator on.
    out (3),a
   
    push hl
Ticks = $+1
    ld hl,0
    inc hl
    ld (Ticks),hl
    pop hl
   
    ld a,%01010 ; Enable timer 1 interrupt, keep calculator on.
    out (3),a
   
    pop af
    ei
    reti

I don't use the shadow registers in my ISR as it's a waste of perfectly good registers - I prefer to push the registers to save onto the stack. If you are augmenting (rather than replacing) the TI-OS ISR then there is no way around this, sadly.
calc84maniac wrote:
Your problem is that you're not setting up the table correctly.

Ah, stupid mistake, thanks.
calc84maniac wrote:
Also, your counter check loop should have a JP C, not a JP P.

What's wrong with P?
benryves wrote:

Code:
    ld hl,$9900 
    ld (hl),$9D 
    ld d,h 
    ld e,1 ; Yes, "one", not L. 
    ld bc,256 
    ldir

Doesn't LDIR do (DE) -> (HL) in stead of (HL) -> (DE)?
Edit: Oh, nvm Razz
It's working! YES! Very Happy
  
Register to Join the Conversation
Have your own thoughts to add to this or any other topic? Want to ask a question, offer a suggestion, share your own programs and projects, upload a file to the file archives, get help with calculator and computer programming, or simply chat with like-minded coders and tech and calculator enthusiasts via the site-wide AJAX SAX widget? Registration for a free Cemetech account only takes a minute.

» Go to Registration page
Page 1 of 1
» All times are UTC - 5 Hours
 
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum

 

Advertisement