Event triggered by Interrupt - Page 1
Forum section: electronics / general

author: T3sl4co1l
author_rank: Super Contributor
timestamp: June 09, 2018, 12:27:31 pm
content: I'm thinking about a method, which works fine on a lot of platforms, but which happens to be kind of actively defeated by the XMEGA hardware.So, the general dilemma I have is: either I need to escalate my methods (hack around the limitation), or, probably more wisely -- I need to find an alternative method that's more common and general.The exact situation is this: I have a periodic timer interrupt. Every, say, 256th interrupt, I want to fire an additional low-priority operation.The operation could potentially run for a long time, so:1. Interrupts are enabled during it. (Including the timer interrupt, in which case it momentarily ends up on the stack twice.)2. It's blocked with an "in-function" flag.The heavy-weight method would be a full task switch, so it runs as an alternate main(), and the various low-priority tasks (including main() itself proper) run whenever. But I'd rather not write or import an RTOS here, that's way overkill...So what's the matter? The XMEGA has an interrupt controller which apparently cannot be defeated (the status registers are read only). All it's watching for, is that RETI instruction at the end of the ISR.Simply enabling interrupts, works perfectly fine on an ATMEGA, which has simple interrupt control. It even works on a PC*, give or take what commands you have to send to the PIC to acknowledge the interrupt.*Old PCs, as in MS-DOS era. Embedded MCUs have more than eclipsed the old PC in capabilities now; I think of vintage PCs more as historic examples of quirky, bulky embedded systems, that happen to have a couple fairly rich peripherals on them (notably, relatively large RAM, graphical output, and disk I/O).What approach to follow?It seems to me, a tail call would do just fine. Push the destination function address on the stack, then RETI. The interrupt flag clears, the destination is jumped to, and any interrupt can fire. I don't think this is something I can convince GCC to do, though... sure, it'll do that optimization automatically in a function, but in an ISR?I could write the ASM, no problem. Not worried about that. But that's admitting I need a hack. As far as I know, this is an uncommon method/problem/situation, but it doesn't feel so uncommon that that kind of hack should be required to solve it.I don't want to set a flag and check it in main(), because that requires a polling loop. If I ever execute blocking functions in main(), that goes in the crapper. And I already intend on doing just that, and I'd rather not muck up those blocking functions with polling thrown in everywhere...I could trigger a low-priority interrupt; if there were software interrupts, that'd have potential. There's a dozen peripherals I'm not using at the moment, which could serve as an interrupt source; but that's not very general. What if I end up needing one?Same goes for pin change interrupts. I do happen to have free GPIOs I could use for this, but that seems hacky as well.Tim

author: ajb
author_rank: Super Contributor
timestamp: June 09, 2018, 05:56:10 pm
content: Sounds like you've managed to talk yourself out of all of the available options on an AVR, sooo... switch to an ARM and use PendSV? Atmel's SAM D (M0+) parts are quite nice as a step up from the XMEGA class of AVRs.Really, though, the kind of task switching you're talking about is RTOS territory. If you don't want the "hack" of writing your own pseudo context switching, then the other option is to not write blocking functions, or at least split them up into state machines so that they can yield while blocked and let your main polling loop run fast enough to hit the longer task however promptly you need. Actually, since you mentioned other tasks still needing to run in the new context, rolling your own task switching could get messy unless you can guarantee that an in-progress task can complete atomically before the switch is made, and at that point you're not much better off than a superloop.

author: NorthGuy
author_rank: Super Contributor
timestamp: June 09, 2018, 10:26:22 pm
content: Quote from: T3sl4co1l on June 09, 2018, 12:27:31 pmIf you used RTOS, it would have assembler routines for context switching. So, you get the "hack" either way.

author: cv007
author_rank: Frequent Contributor
timestamp: June 10, 2018, 01:31:53 am
content: If you inline asm the call to long task/reti as you indicate, the compiler will not know about it and may not have saved all the caller-saved registers.

author: westfw
author_rank: Super Contributor
timestamp: June 10, 2018, 07:53:23 am
content: QuoteAre you sure? The status register is read-only, but there's a separate control register that claims to have enable/disable bits for the different levels of interrupts...You said "every 256 timer cycles", but you also said the low-level process runs for multiple timer ticks, so I'd guess that isn't a critical ratio. Unless you're really using all the myriad timers (including RTC and anything else that might be re-interpreted as a timer) it seems like it would be a lot easier to simply set up a separate timer (at low priority) for the less frequent process. Driving everything from a single clock tick is swell, but it's really just an admission that it would have been nice to have more hardware timers.I guess you've mentioned this, but...It seems like the multi-level interrupt system should make this easier; run your normal interrupts at medium or high level, and when you want to run your low-priority process, have the medium level ISR trigger a low-level interrupt. When the normal ISR returns, the low-level interrupt will trigger, and it will be interruptable by the high/medium level interrupts. (or, I think it's possible to have this all happen via the event system, though I'm not familiar with it, so I'm not sure exactly how.)I think that in the absence of "software interrupt" triggers or something like ARM's PENDSV, I think "hacks" are your only choice.

author: T3sl4co1l
author_rank: Super Contributor
timestamp: June 10, 2018, 09:39:53 am
content: Quote from: westfw on June 10, 2018, 07:53:23 amYeah, but I don't want to disable it, that's the whole point.Unless disabling it resets the flag, and enabling it again leaves none the wiser? Hmm, maybe worth a try.QuoteI'd like some well-timed operations at the front, then whatever for the rest of the routine, so it should still start ~synchronously.Well. I suppose by definition, I should put those in the primary timer routine...Point taken about the timers; and the XMEGA are better stocked on them than the old kind, including more compare-capture registers which I'm certainly not using all of on the timer channel. I could disable that most of the time except during the special 256th cycle, that would work well.QuoteYeah, that's why I'm interested, it feels so close to possible, and would be reasonably convenient.It looks like you can strobe the hardware event system from software, but there is no interrupt source from the system. (That would've been cool, but I guess they figured it wasn't necessary.)Tim

author: cv007
author_rank: Frequent Contributor
timestamp: June 10, 2018, 01:43:29 pm
content: __attribute((naked)) void enable_this_irqlevel(){ asm("reti"); }

author: T3sl4co1l
author_rank: Super Contributor
timestamp: June 11, 2018, 10:42:42 am
content: Quote from: cv007 on June 10, 2018, 01:43:29 pmGave this a try, I'm getting random dropped characters from the serial output. Thoughts?Tim

author: cv007
author_rank: Frequent Contributor
timestamp: June 11, 2018, 02:16:29 pm
content: QuoteI'll assume isr is something like-ISR(some_timer){static uint8_t counter;static uint8_t long_task_busy;clear_timer_flag_here();if(++counter || long_task_busy) return;long_task_busy = 1;enable_this_irqlevel();long_task();long_task_busy = 0;}you can check the asm output to make sure 'enable_this_irqlevel()' compiles to a call to a single reti - I don't think it would be optimized away since it has an asm statement, but you have to check.if it is correct- a call to a reti, then you got what you asked fortypical thread- (attempt at humor)q: how do I take a wheel off of my car?a: why do you want to take the wheel off? what engine does it have? is the car inside the garage or outdoors? do you have the manual? what color is the car?me: jack up the car until the wheel is off the ground, remove the wheel nutsq: ok, I got the wheels off, but why is my air conditioner still not working?

author: IanB
author_rank: Super Contributor
timestamp: June 11, 2018, 02:22:52 pm
content: I'm having trouble understanding the difficulty.If you were running on bare hardware without an OS, then I think you would have a main loop that can test flags and branch appropriately when a flag is set. Yes, it would be polling, but what else does it have to do?If you were running with an OS, then I think you would use a software interrupt. The SWI would run at lower priority than the hardware interrupts and would not block them. An RTOS should usually have interrupt priorities and would be designed for just this situation.

author: T3sl4co1l
author_rank: Super Contributor
timestamp: June 11, 2018, 11:40:31 pm
content: Quote from: cv007 on June 11, 2018, 02:16:29 pmYes, except clear_flag is automatic thanks to the hardware. That's basically verbatim what it is now, before I expand it with whatever the project actually needs.QuoteFrom the .lss:Code:00000c96 <enable_this_irqlevel>:c96:18 95retiQuoteYeah, having replied to many X-Y Problems myself, I'm keenly aware of the X-Y Problem potential of this situation!Unfortunately, based on the expert opinions given here, it seems that I have, in fact, exhausted all the possibilities already, and will have to compromise somehow.Tim

author: cv007
author_rank: Frequent Contributor
timestamp: June 12, 2018, 01:05:06 am
content: QuoteI would also make sure the call to that function is also taking place- the call is still under compiler control (I'm not sure if the optimizer would even attempt to eliminate that call). Although I guess it would probably be obvious if the timer irq is being re-enabled or not.

author: Hypernova
author_rank: Unknown
timestamp: June 12, 2018, 01:16:01 am
content: Is there any reason you can't just use two timers with different priorities with the slower one at /256'th rate of higher one? It's been 7 years since I last touched a XMEGA but I don't recall multi level nested ISR being that difficult to do.

author: T3sl4co1l
author_rank: Super Contributor
timestamp: June 12, 2018, 01:21:32 am
content: Quote from: cv007 on June 12, 2018, 01:05:06 amWell like I said, it has effects. Serial is dropping characters when it's present, and flawless when commented out.Tim

author: T3sl4co1l
author_rank: Super Contributor
timestamp: June 12, 2018, 01:25:26 am
content: Quote from: Hypernova on June 12, 2018, 01:16:01 amRight now, no, but if there's an application that requires a strict sequence, it would be nice to know of a solution.Tim

author: obiwanjacobi
author_rank: Super Contributor
timestamp: June 12, 2018, 06:34:42 am
content: Not sure if I understand the problem correctly but it sounds like you have a low prio background task that is dormant until it's triggered from an (256th) interrupt...So the mainloop simply checks the 'run-background-task-is-active' flag - set by the interrupt handler with its timing/counting logic- and then processes slices of that task in the main loop.Having an RTOS would help,but my low level task macro'swill also work...Hope it helps

author: NivagSwerdna
author_rank: Super Contributor
timestamp: June 12, 2018, 06:55:43 am
content: Sounds like you want to make your IRQ routine re-entrant. That should be feasible. Don't return but instead just enable the interrupt within the interrupt routine and use status flags to detect if an interrupt is currently active within the IRQ routine?

author: westfw
author_rank: Super Contributor
timestamp: June 12, 2018, 08:06:02 am
content: If your timer function is based on an output compare interrupt, there's the interesting (hacky) possibility of setting the timer count to MAX-1 instead of 0, so that you (shortly) get a timer overflow interrupt (at a different priority) in addition to the usual OC match interrupts... (but I guess the reset to 0 is automatic, and this would cause a bit of skew...)

author: T3sl4co1l
author_rank: Super Contributor
timestamp: June 12, 2018, 09:06:46 am
content: Quote from: obiwanjacobi on June 12, 2018, 06:34:42 amNah, that would work fine. The problem is that I want to avoid doing that because it makes everything else more complicated...Tim

author: T3sl4co1l
author_rank: Super Contributor
timestamp: June 12, 2018, 09:07:17 am
content: Quote from: NivagSwerdna on June 12, 2018, 06:55:43 amAs mentioned in OP, that works on devices without this PMIC.Tim

author: cv007
author_rank: Frequent Contributor
timestamp: June 12, 2018, 12:30:06 pm
content: QuoteI assume the 'it' is the call to the reti function. If the compiled size is changing more than 4 bytes (or whatever the call is) then there is more the compiler is up to. In any case I would see what the compiler generates for the isr with/without the call. Of course the other difference, which is what you are after, is now the irq level is enabled while your long task is working (where your serial output takes place?- we don't know).

author: T3sl4co1l
author_rank: Super Contributor
timestamp: June 12, 2018, 02:46:09 pm
content: Yes, here's the result with it in:Code:c66:80 91 5a 21ldsr24, 0x215Ac6a:81 11cpser24, r1c6c:0a c0rjmp.+20; 0xc82 <__vector_14+0x56>c6e:81 e0ldir24, 0x01; 1c70:80 93 5a 21sts0x215A, r24c74:0e 94 15 06call0xc2a; 0xc2a <enable_this_irqlevel>c78:78 94seic7a:0e 94 1a 01call0x234; 0x234 <do256HeartBeat>c7e:10 92 5a 21sts0x215A, r1And without:Code:c64:80 91 5a 21ldsr24, 0x215Ac68:81 11cpser24, r1c6a:08 c0rjmp.+16; 0xc7c <__vector_14+0x52>c6c:81 e0ldir24, 0x01; 1c6e:80 93 5a 21sts0x215A, r24c72:78 94seic74:0e 94 1a 01call0x234; 0x234 <do256HeartBeat>c78:10 92 5a 21sts0x215A, r1(enable_this_irqlevel is not called, but isn't removed from the output. *Checking*, ah, link-time optimization removes it when commented out, I see.)

author: Marco
author_rank: Super Contributor
timestamp: June 12, 2018, 07:08:05 pm
content: There already is a standard mechanism in GCC for this, the bog standard by default reentrant interrupt routine. It's the XMEGA which fucks it up with its weird hardware. So despite the problem being common, it seems unlikely there is some non hacky solution to it. Just manipulate the stack and call RETI in assembly and see if GCC handles the return from your non interrupt function back to main correctly, if not hack some more.Hell, I see you have to jump assembly hoops to do reentrant interrupt routines in ARM as well ... if you can't do it cleanly there, it seems unlikely for XMEGA.

author: T3sl4co1l
author_rank: Super Contributor
timestamp: June 12, 2018, 10:08:06 pm
content: Oh yeah, so, the problem would be the offendingsecondRETI after when the 256-function eventually returns.So I need a regular RET (not RETI, and not GOTO __epilogue__ i.e. continuing the normal program flow through the ISR's default RETI).Which means the dropped serial char is when enough ISRs happen to be on the stack that one gets dropped by the double-RETI. Or maybe it's when the serial transmit just dings ready, then gets immediately deasserted and skipped by the RETI.Tim