Omnimaga

Calculator Community => TI Calculators => ASM => Topic started by: Excelseo on August 26, 2016, 09:08:25 am

Title: Interrupt Routine not RETurning
Post by: Excelseo on August 26, 2016, 09:08:25 am
So, I've been trying to learn assembly as of late, and a few days ago I started messing around with interrupts. However, I could never get the programs to work as intended, and corrupted the calculator's system routines accidentally whilst trying to do so. (I'm on vacation without my link cable and writing the code on-calc with Mimas)  Forced to use direct output rather than putS to figure out what was going on, I wrote this program.
Code: [Select]
  di
  ld a,$99
;setting up interrupt
  ld i,a
  ld hl,Interrupt
  ld ($993F),hl
  ld ($997F),hl
  ld ($99BF),hl
  ld ($99FF),hl
  im 2
  ld e,$FF
  ei
Loop:
;Infinite loop o' ram clears
  jr Loop
Interrupt:
  ld a,26
;setting column
  out ($10),a
  call WasteTime
;my equivalent of _LCD_BUSY_QUICK
  ld a,$96
;setting row
  out ($10),a
  call WasteTime
  ld a,e
;displaying e as row of pixels
  out ($11),a
  dec e
  reti
WasteTime:
  ld l,10
WasteLoop:
  nop
;344 T-states, just to be safe
  nop
  nop
  nop
  dec l
  jr nz,WasteLoop
  ret
The expectation here is that every 140th of a second, the row of pixels specified would update to reflect e. I understand that the LCD has a refresh rate of 60 fps, but the row of pixels should update on some interval. However, instead, only a solid row of pixels (reflecting e's original $FF) is displayed, which never changes. Since the pixels were displayed at the correct coordinates as intended, I'm reasonably certain that the interrupt setup is working, and that WasteTime is functional as well. That narrows it down to these two lines:
Code: [Select]
dec e
reti
And I'm pretty sure I know how dec works :). Is there something I don't understand about reti? Or is there something entirely different wrong with my understanding of assembly? Sorry for the long post, I wanted to give as much information as possible.

Thanks in advance!


Edit Sorunome: added code tags

 
 
Title: Re: Interrupt Routine not RETurning
Post by: Geekboy1011 on August 26, 2016, 09:25:42 am
I am afraid I have no code to help you right now but I have some suggestions.

It looks like you are not servicing the interrupt call so it can run again. SO in essence your never re-enabling it.
I would recommend following this page on how to setup your interrupt as well http://wikiti.brandonw.net/index.php?title=83Plus:Ports:03 (http://wikiti.brandonw.net/index.php?title=83Plus:Ports:03)
Your initialization is not 100% guaranteed to work on all calculator models!

also reti is rather useless on the 8X series we work with. Due to how it does interrupts a simple ret will usually suffice (we have no user properly nestable interrupts so it is more tricky then abusing reti)
Title: Re: Interrupt Routine not RETurning
Post by: Excelseo on August 26, 2016, 09:31:09 am
You are a speed demon, my good sir.
Title: Re: Interrupt Routine not RETurning
Post by: Geekboy1011 on August 26, 2016, 09:34:35 am
no problems ask questions as needed please :D i'll help with what I can. (I am at work right now so I don't have all my code to help me. Else I would have just handed you a re-written set of code already lol) :D
Title: Re: Interrupt Routine not RETurning
Post by: Sorunome on August 26, 2016, 10:45:17 am
On the calculator interrupts are unfortunately a bit more complex than that.

The reason being is that TI didn't care about im2 mode, and thus the hardware is returning a random address to jump to.

What does this mean? You will need to place your interrupt handler at an address where the high and the low bytes are the same and your interrupt vector needs to be 257 bytes large.

Here's an example with the instruction tale at $8000 (https://www.omnimaga.org/index.php?page=ram#addr8000) (note that with this way you will have to disable interrupts before archiving stuff and re-create your jump table as it uses up the first byte of ramCode (https://www.omnimaga.org/index.php?page=ram#addr8100))
The interrupt handler is located at $8585 (https://www.omnimaga.org/index.php?page=ram#addr8508), we will jump to some other location straight away
Code: [Select]
; disable interrupts and set interrupt mode 2
di
im 2

; set the upper byte of the jumptable to $80
ld a,$80
ld i,a

; create the vector table (do this every time before you enable interrupts, be sure to disable interrupts before archiving / unarchiving stuff)
ld hl, $8000
ld de, $8001
ld (hl), $85
ld bc,256
ldir

; setting up the jump handler at $8585
ld hl,$8585
ld (hl),$c3 ; this is the jp instruction
ld de,InterruptHandler ; replace this with whatever label you are jumping to, where you have your interrupt handler
inc hl
ld (hl),e
inc hl
ld (hl),d

; time to enable interrupts
ei
Keep in mind that interrupts keep on disable themself, you will have to call "ei" at the end of your interrupt routine so that it'll trigger again.

You could also set up your interrupt table at $9900, just adjust setting the riger i and the vector table accordingly, that would allow you to do whatever flash operations while running the interrupt, however that means you can't use appBakUpScrn (https://www.omnimaga.org/index.php?page=ram#addr9872)


If you are creating an app your interrupt handler will need to be in a ram location and you need to make sure you swap in the correct flash pages (or, alternatively, you may not use any bcalls at all, custom ones or system ones).


Here's the code for an app:
Note that it also uses $8584 and keyToStr (https://www.omnimaga.org/index.php?page=ram#addr9D76)
Code: [Select]
#define basepage $8584
Run:
; disable interrupts and set interrupt mode 2
di
im 2

; save whichever page we are on so that we know where to go
in a,(6)
ld (basepage),a

; set the upper byte of the jumptable to $80
ld a,$80
ld i,a

; now we copy our interrupt loader to ram, we only have to do this once
ld hl,LoadInterrupt
ld de,keyToStr
ld bc,LoadInterruptSize
ldir

; create the vector table (do this every time before you enable interrupts, be sure to disable interrupts before archiving / unarchiving stuff)
ld hl, $8000
ld de, $8001
ld (hl), $85
ld bc,256
ldir

; setting up the jump handler at $8585
ld hl,$8585
ld (hl),$c3 ; this is the jp instruction
ld de,InterruptHandler ; replace this with whatever label you are jumping to, where you have your interrupt handler
inc hl
ld (hl),e
inc hl
ld (hl),d

; time to enable interrupts
ei

; we will copy this to ram, it'll swap us the correct page in and swap back whichever page we had before
LoadInterrupt:
push af
in a,(6)
push af
ld a,(basepage)
out (6),a
call Interrupt
pop af
out (6),a
pop af
ei
ret
LoadInterruptSize = $ - LoadInterrupt

; now we have our actual interrupt handler
Interrupt:
; we can do whatever here, since LoadInterrupt already does ei we don't have to do it in here
ret

Why does the Interrupt address has to be at a place where high and low bytes match?
Well, let's say you set i to $80 so that the interrupt jump-tabe starts at $8000. Now, let's say you want to have the handler at, like $9876
For that you'd want to set these bytes starting at $8000:
Code: [Select]
.db $76,$98 ; reverse order due to little endian-ness
.db $76,$98
.db $76,$98
.db $76,$98
.db $76,$98

etc.
Now, let's say the interrupt fires and the hardware reports as lower byte 0, so we take the address at $8000, which is $9876.
If the hardware reports 2 we take the address at $8002, which is $9876

That all seems fine. However, the addresses the hardware reports are seemingly random, so let's say the hardware reports 1
Now we take the address at $8001 which is $7698. Woops, that shouldn't happen.
You overgo this by only using addresses where the high and the low bytes match

EDIT: As Runer112 pointed out, you don't actually need the upper and lower bytes to match, you'd need to have two handlers then, though, in this example one at $9876 and one at $7698 (while the later is actually not possible as that isn't ram, which just shows how much easier it is if you have the upper and lower bytes matched)
Why must the interrupt vector be 257 bytes large?
Especially, why not 256 bytes?
Well, 256 bytes would be sufficiant if the hardware could only reply 0 - $FE
However, if the hardware replies $FF, then we are looking for the address at $80FF which consists of one byte at $80FF and one byte at $8100, thus we need a 257-bytes large vector table
Title: Re: Interrupt Routine not RETurning
Post by: Excelseo on August 26, 2016, 12:43:49 pm
Thank you Sorunome, but I'm afraid that's not the problem. I got the interrupt setup method I used from http://tutorials.eeems.ca/ASMin28Days/lesson/day23.html (http://tutorials.eeems.ca/ASMin28Days/lesson/day23.html), which explains that the lowest 6 bits of the data bus are always set when calling a mode 2 interrupt. There may be a catch to this, but the fact that the pixels were displayed as intended in my original program shows that the interrupt was called correctly.
Title: Re: Interrupt Routine not RETurning
Post by: Sorunome on August 26, 2016, 12:47:30 pm
Thank you Sorunome, but I'm afraid that's not the problem. I got the interrupt setup method I used from http://tutorials.eeems.ca/ASMin28Days/lesson/day23.html (http://tutorials.eeems.ca/ASMin28Days/lesson/day23.html), which explains that the lowest 6 bits of the data bus are always set when calling a mode 2 interrupt. There may be a catch to this, but the fact that the pixels were displayed as intended in my original program shows that the interrupt was called correctly.
Unfortunately the 28 days tutorial is wrong in some aspects, including how interrupts are working. What the tutorial states may be true for some calculatores with certain revision numbers, but definitally not for all calculators. I'd strongly recommend setting up a full jump-table like i stated above.
Title: Re: Interrupt Routine not RETurning
Post by: Excelseo on August 26, 2016, 12:58:49 pm
Spent a little while tinkering with the program this morning, and I have two things that are definitely not solutions.

1. I output %00001011 to port 3 during setup, output %00000000 at the beginning of the interrupt routine, and reverted it to %00001011 at the end, which I think is what Geekboy was suggesting. I changed reti to ret as well, but I wouldn't really call that a proposed solution, just an optimization.

2. I added two "srl a"s before "out ($11),a". This way, the pixels to be displayed only change every 4 interrupts i.e. 35 times a second, which is well within the LCD's refresh rate. The problem persisted, and I didn't expect it to, as I don't think the interrupt routine is running a second time.

I might try some more solutions later but I'm out of ideas at the moment.

EDIT: changing the interrupt setup to a full table now



Ok, I replaced the interrupt setup with the following code, similar to Sorunome's, but with a slight optimization using ix instead of hl, and moving the table to $9900 since I was more familiar with that area of RAM.

ld a,$99
ld i,a
ld hl,$9900
ld de, $9901
ld (hl),$9A
ld bc,256
ldir
ld ix,$9A9A
ld (ix+0),$C3
ld hl,Interrupt
ld (ix+1),l
ld (ix+2),h
im 2

Unfortunately, it had the same result. The specified 8 pixels set, and no updates.


Edit (Eeems): Merged double post
Title: Re: Interrupt Routine not RETurning
Post by: Sorunome on August 26, 2016, 01:27:13 pm
Do you run "ei" before returning from your interrupt? I did mention it in my huge post but you might have simpely over-read it ^.^
Title: Re: Interrupt Routine not RETurning
Post by: Excelseo on August 26, 2016, 01:27:47 pm
Yes.
Title: Re: Interrupt Routine not RETurning
Post by: Sorunome on August 26, 2016, 01:28:07 pm
What about re-outputting your interrupt mask to port 03?
Title: Re: Interrupt Routine not RETurning
Post by: Excelseo on August 26, 2016, 01:40:30 pm
Tried that as well.
Title: Re: Interrupt Routine not RETurning
Post by: Sorunome on August 26, 2016, 01:42:07 pm
Mind posting the whole code you currently have? The thing is, I can't see any port 03 access in your top post.
Title: Re: Interrupt Routine not RETurning
Post by: Excelseo on August 26, 2016, 01:45:39 pm
Yeah I didn't mention adding it until my 2nd reply. I'll post the whole code as it is currently in half an hour or so, so stay tuned. :)
Title: Re: Interrupt Routine not RETurning
Post by: Eeems on August 26, 2016, 02:12:18 pm
FYI about 28 days. If you want to use an updated version of the guide I would highly recommend looking here: https://bitbucket.org/tari/83pa28d
Here is a direct link to day 23 (https://bitbucket.org/tari/83pa28d/src/75a384e95da9a48ef71f28ba2601f0d86c5791b5/lesson/day23.md?at=default&fileviewer=file-view-default)
Title: Re: Interrupt Routine not RETurning
Post by: Excelseo on August 26, 2016, 02:31:40 pm
Code: [Select]
  di
  ld a,$99
  ld i,a
  ld hl,$9900
  ld de, $9901
  ld (hl),$9A
  ld bc,256
  ldir
  ld ix,$9A9A
  ld (ix+0),$C3
  ld hl,Interrupt
  ld (ix+1),l
  ld (ix+2),h
  im 2
  ld a,%00001011
  out (3),a
  ld e,$FF
  ei
Loop:
  jr Loop
Interrupt:
  di
  xor a
  out (3),a
  ld a,$26
  out ($10),a
  call WasteTime
  ld a,$9C
  out ($10),a
  call WasteTime
  ld a,e
  srl a
  srl a
  out ($11),a
  dec e
  ld a,%00001011
  out (3),a
  ei
  ret
WasteTime:
  ld l,10
WasteLoop:
  nop
  nop
  nop
  nop
  dec l
  jr nz,WasteLoop
  ret

My code as it is currently, by request of Sorunome. Thanks Eems for the link.

Edit Sorunome: added code-tags
Title: Re: Interrupt Routine not RETurning
Post by: Sorunome on August 26, 2016, 03:06:11 pm
So, I just assembled your code into program with spasm-ng and it does what expected, outputs an 8-pixel block which are keep on changing because of dec e

In my other programs i also called once, before starting interrupts:
Code: [Select]
xor a
out (4),a
That basically sets the speed of the hardware timers. Maybe adding that would work?

EDIT: this is what it is looking like for me:
(https://img.ourl.ca/test-6.gif)
Title: Re: Interrupt Routine not RETurning
Post by: Excelseo on August 26, 2016, 03:41:18 pm
Thanks! It's working now. The output to port 4 finally did it. Thank you all for the help!
Title: Re: Interrupt Routine not RETurning
Post by: Sorunome on August 26, 2016, 03:45:46 pm
In case you need any more information on port 4 you can find it here: http://wikiti.brandonw.net/index.php?title=83Plus:Ports:04