So I'll post a question here, something that's been stumping me. I discovered that on hardware TI-83+s, but not on PindurTI or WabbitEmu, the CALCnet interrupt eventually fails to continue running. I chalked it up to a mistake on my part in the interrupt itself, but careful examination eventually revealed that my iFastCopy routine is the culprit. If I replace iFastCopy with iFastCopy \ ei, then everything continues functioning happily ad infinitum. I believe the culprit is the code that I use to detect if interrupts are running:


Code:
iFastCopy:
   ld a,i
   push af
[...routine code...]
iFastCopyAllFinish:
      pop af
   ret po
   ei
   ret


Does anyone know why that would occasionally fail, and more importantly, have a suggestion for an alternative that is more reliable?
One of my implementations, which seems to differ greatly. Dunno if it works offhand, or even if I've tested it..

Code:
bufCopy:
    ld a,r
    push af
    di
[snip]
    pop af
    jp pe,_exit
    ei
_exit:
    ret
im not sure, but isn't there an undocumented command called RETI that does kinda what your trying to do? You could just...


Code:
IFastCopy:
  ld hl,code
  push hl
  reti
code:
  di
  [snip]

  ei
  ret
Actually, since you're just negating the logic using pe (parity even) instead of po (parity odd), the only difference is that you're loading from r instead of i. I'm not familiar with that method; perhaps I'm wrong in thinking it should be ld a,i?

Edit: Re reti: Not only is it not undocumented, it's for a totally different purpose. Smile But thanks for the thought.
hmm.well i know there's an undocmented RET command that synchronizes with the interrupt...Just can't remember which one.
According to the manual, both LD A,I and LD A,R copy IFF2 to P/V. It also mentions that "If an interrupt occurs during execution of this instruction, the Parity flag contains a 0", which is to be expected (IFF1 and IFF2 are automatically reset when a maskable interrupt is serviced).

I've never heard of an undocumented RET instruction that affects interrupts. RETI (used for daisy-chained interrupt sources, so is not much use on the TI-83+) and RETN (copies IFF2 to IFF1, so not much use here as IFF2 is cleared; it's handy for non-maskable interrupts, however, as only IFF1 is cleared when servicing them) are clearly documented.

I can't think of a way to retrieve the state of IFF1 short of having a bit of code in the ISR that sets a flag in RAM. That way you could check IFF2 (if set, interrupts are enabled) and then this flag (if set, interrupts are enabled) in case an interrupt is serviced during the LD A,I or LD A,R instruction.
Anakclusmos wrote:
hmm.well i know there's an undocmented RET command that synchronizes with the interrupt...Just can't remember which one.
If there is such a thing, that would be very good to know about. Smile
im pretty sure there is.i saw it when i was looking in the TASM command ref in the Learn TI83+ ASM in 28 Days Tutorial (i still make quick glances sometimes.Its helpful not to keep EVERYTHING in your head ^_^0
benryves wrote:
According to the manual, both LD A,I and LD A,R copy IFF2 to P/V. It also mentions that "If an interrupt occurs during execution of this instruction, the Parity flag contains a 0", which is to be expected (IFF1 and IFF2 are automatically reset when a maskable interrupt is serviced).
And I think that indeed is what I'm running into.

Quote:
I've never heard of an undocumented RET instruction that affects interrupts. RETI (used for daisy-chained interrupt sources, so is not much use on the TI-83+) and RETN (copies IFF2 to IFF1, so not much use here as IFF2 is cleared; it's handy for non-maskable interrupts, however, as only IFF1 is cleared when servicing them) are clearly documented.
Indeed. Sad

Quote:
I can't think of a way to retrieve the state of IFF1 short of having a bit of code in the ISR that sets a flag in RAM. That way you could check IFF2 (if set, interrupts are enabled) and then this flag (if set, interrupts are enabled) in case an interrupt is serviced during the LD A,I or LD A,R instruction.
That would indeed help, but that would mean that the only interrupt that I could properly deal with inside Doors CS would be the CALCnet2.2 one (not the TI-OS one, or any user-created interrupt). That's why I was hoping there might be some z80 tricks to detect the presence of an interrupt in an alternative manner, or to at least force the interrupt to trigger on a different instruction. Thought: what if I did something like ld a,r \ push af \ ld a,r \ , and then somehow cross-correlated the flags? I'd think it unlikely that the interrupt will trigger on both lds, unless it was about 1/110th of a second minus 12 cycles or so.
I think your idea to read from I or R twice is probably the most sensible one, though would still be possible to break (triggering another interrupt - such as a link or On key interrupt - during the ISR, for example).

Alternatively, you could just do what everyone else has been doing and mention that certain routines disable interrupts and that they should re-enable them afterwards if need be. It's not as elegant, but it avoids these complications.

As a complete aside, does the CALCnet2.2 ISR clobber any registers?
benryves wrote:
I think your idea to read from I or R twice is probably the most sensible one, though would still be possible to break (triggering another interrupt - such as a link or On key interrupt - during the ISR, for example).
True, true. I think it's at least worth a try. The most interesting part is that I don't see this behavior on either PTI or WabbitEmu; I suppose neither emulates the clearing of the IFF-derived flags when the interrupt triggers.

Quote:
Alternatively, you could just do what everyone else has been doing and mention that certain routines disable interrupts and that they should re-enable them afterwards if need be. It's not as elegant, but it avoids these complications.
True, true. I suppose that wouldn't be the worst alternative. I'd run into problems with the DCS GUI, though, because I want it to continue operating interrupts properly while it has control, and it makes plenty of calls to interrupt-disabling things like FastCopy.

Quote:
As a complete aside, does the CALCnet2.2 ISR clobber any registers?
Nope, the only thing it plays with is its RAM buffers, unless I misunderstand the question.

Edit: I've been running the Cn2 speed test for several minutes now, and it works much better, but it still eventually reaches a state where presumably both loads returned the wrong value. Sad Here's what I'm using now:


Code:
iFastCopy:
   ld a,i
   push af
      ld a,i
      push af
         di
         [...function body...]
iFastCopyAllFinish:
         pop af
      jp pe,iFastCopyAllFinishEnablePop
      pop af
   ret po
   jr iFastCopyAllFinishEnable
iFastCopyAllFinishEnablePop:
      pop af
iFastCopyAllFinishEnable:
;#ifdef enablecn2eis
   ei
;#endif
   ret
KermMartian wrote:
Quote:
As a complete aside, does the CALCnet2.2 ISR clobber any registers?
Nope, the only thing it plays with is its RAM buffers, unless I misunderstand the question.
The TI-OS ISR clobbers the shadow registers, which is mildly irritating.

Quote:
Edit: I've been running the Cn2 speed test for several minutes now, and it works much better, but it still eventually reaches a state where presumably both loads returned the wrong value. Sad
I can't think of anything else to suggest, sorry, short of setting a flag in the ISR.
I beg your pardon, it does indeed clobber the shadow registers. In Doors CS I just di/ei around any section where I might be using the shadow registers anyway, so it's no biggie. I thought of one further problem slash solution that perhaps you might have insight into. The interrupt would most likely trigger twice in a row like that if the interrupt itself took over 1/110th of a second, right? Is there any way to clear the pending interrupt when the interrupt exits so that it won't immediately retrigger if it took, say, 1.001/110th of a second? Right now I load, in quick order, to port 3: %00001000, %00001010, %00000110. My notes indicate that that's ack&disable, set 1st timer active, set 110Hz.
KermMartian wrote:
I beg your pardon, it does indeed clobber the shadow registers. In Doors CS I just di/ei around any section where I might be using the shadow registers anyway, so it's no biggie.
Fair enough. Smile

Quote:
Is there any way to clear the pending interrupt when the interrupt exits so that it won't immediately retrigger if it took, say, 1.001/110th of a second?
/INT is level-triggered (unlike /NMI which is edge-triggered) so you'd need to ensure that something is not asserting /INT when you leave the ISR.

Quote:
Right now I load, in quick order, to port 3: %00001000, %00001010, %00000110. My notes indicate that that's ack&disable, set 1st timer active, set 110Hz.
Acknowledge the interrupt immediately before exiting the ISR rather than at the start of the ISR. I don't believe clearing the timer's interrupt flag causes it to reset (so if you clear it immediately before the next "tick" then you could still be caught out). You could manually poll the interrupt flag within in the ISR, then reset it, then exit. This would be a little slow, unfortunately.
Sorry, I should have been more clear - that's what I do at the end of the interrupt:


Code:
Cn2_Caller_Routine:
   ex af,af'                     ;switch to shadow registers               ;1
   exx                                                            ;1
#ifdef cn2_installerinflash
   in a, (2)                     ;                                 ;2
   and 80h                        ;%10000000 for fast, %00000000 for slow      ;2
   rlca                        ;%00000001 for fast, %00000000 for slow      ;1
   push af                                                         ;1
      xor a                     ;set speed to slow                     ;1
      out (20h), a               ;set the speed                        ;2

      in a,(6)                                                   ;2
      push af                                                      ;1
Cn2_Caller_HomePage:
         ld a,0                  ;SMC field                           ;2
         out (6),a                                                ;2
#endif ;cn2_installerinflash

         call Cn2_Int_Start                                          ;3
#ifdef cn2_installerinflash
         pop af                                                   ;1
      out (6),a                                                   ;2
      pop af                                                      ;1
   out (20h),a                     ;restore the speed!                     ;2
#endif ;cn2_installerinflash
   ld a,%00001000                  ;Acknowledge and disable               ;2
   out (3),a                                                      ;2
   ld a,%00001010                  ;Set 1st timer active                  ;2
   out (3),a                                                      ;2
   ld a,%00000110                  ;Slowest frequency, ~110hz               ;2
   out (4),a                                                      ;2
   exx                                                            ;1
   ex af,af'                                                      ;1
   ei                                                            ;1
   reti                        ;-1-8- -1-9- -9- 42 bytes total now         ;1
Cn2_Caller_RoutineDone:

How would I manually poll the interrupt flag to reset it, why would that be slow?
Port $04 indicates which device triggered the interrupt. What I'm suggesting is that you poll port 4 within the ISR until bit 1 (the timer 1 flag) is set. At this point, acknowledge interrupts (as you are above) and exit the ISR. This would be slow as you'd have to wait up to 1/110th of a second before exiting the ISR, unfortunately, but I don't know if there's a way to reset the counter and avoid these spurious interrupts otherwise.

Edit: You could temporarily set the timer to run at the fastest possible rate to reduce this wait. You'd still have enough time to reset it.
Are you therefore suggesting that any retriggering that's occurring in my current interrupt is because the timer expired right after the interrupt exited, not because it expired some time before the interrupt finished? Or do I misunderstand? Either way, I could set Timer 1 to 560Hz, as you're suggesting, then clear and ack the interrupt immediately, adding a maximum wait of 2ms, which isn't terrible if you take a look at some of the guard pauses within the Cn2.2 protocol (in case you glanced at the whitepaper). That's definitely well-worth a try!
KermMartian wrote:
Are you therefore suggesting that any retriggering that's occurring in my current interrupt is because the timer expired right after the interrupt exited, not because it expired some time before the interrupt finished?
It shouldn't matter if an interrupt occurs during the ISR as you're clearing its flag at the end of the ISR anyway. My assumption is that the timer is triggering an interrupt some time between the time that you clear its flag (right at the end of the interrupt) and an instruction or two after the ISR has returned, which would be possible if it was about to trigger an interrupt shortly before you reset it. It's a complete shot in the dark, sorry, as I'm not 100% sure how the timer hardware works.
benryves wrote:
KermMartian wrote:
Are you therefore suggesting that any retriggering that's occurring in my current interrupt is because the timer expired right after the interrupt exited, not because it expired some time before the interrupt finished?
It shouldn't matter if an interrupt occurs during the ISR as you're clearing its flag at the end of the ISR anyway. My assumption is that the timer is triggering an interrupt some time between the time that you clear its flag (right at the end of the interrupt) and an instruction or two after the ISR has returned, which would be possible if it was about to trigger an interrupt shortly before you reset it. It's a complete shot in the dark, sorry, as I'm not 100% sure how the timer hardware works.
As you can unfortunately probably tell, I'm quite vague on the mechanism myself, and since it's an esoteric, rarely-documented feature of an esoteric, rarely-documented field and device, not too many others are un-vague on it. Sad I'll give that a try and see if it succeeds in doing what we're hoping. Actually, that'll clear up a few other hacks and workarounds I needed to add to prevent the Cn2.2 interrupt from consuming the entire CPU time, if it works.
Here is what I think may be a foolproof method for checking IFF2, even if an interrupt is triggered during the LD A,I instruction:

Code:
;Push a zero byte to the stack and pop it
   xor a
   push af
   pop af
   ld a,i
   jp pe,interrupts_on
   ;See if an interrupt triggered. If so, the byte on the stack will not be 0 anymore
   ;unless this code is executing from $0000-$00FF area, which would only happen if you are writing an OS
   dec sp
   dec sp
   pop af
   or a
   ;Parity is even if A was zero
   jr z,interrupts_on
   ;Reset overflow
   sub a
interrupts_on:
  
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 2
» 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