Interrupt processing- one time or loop - Page 1
Forum section: electronics / general

author: cv007
author_rank: Frequent Contributor
timestamp: December 27, 2017, 03:19:30 am
content: Interrupt question.my code (incomplete, hitting datasheet a chapter at a time, now 'stuck' in usb)https://github.com/cv007/PIC32MM_Curiosity_CPPhttps://github.com/cv007/PIC32MM_Curiosity_CPP/blob/master/Usb.hpp- line 320(no defines or mchp headers, except I broke down to add defines/macros in Isr.hpp for easier ISR creation)The PIC32MM has interrupt levels and many irq sources, so is it best to just run through an interrupt once, or check it until no more (specific) irq flag, letting any other higher level irq interrupt as needed, or does it make no difference.original way, with just returning when done-Code:void USB_ISR(){Usb u; Irq ir;static Usb::state_t last_state; //keep track of usb state for resumesuint8_t flags = u.flags(); //get all irq flagsir.flag_clr(ir.USB); //clear usb irq flag before early returns//ATTACHED->POWERED if vbus_pin highif(u.state == u.ATTACHED){if(vbus_pin.ison()) u.state = u.POWERED;else { //no power (not sure how we would get irq with no vbus)u.eflags_clr(u.ALLEFLAGS);u.flags_clr(u.ERROR);return;}}//more code}if USB flag was set again while processing, we go through isr register restore, then backup registers again since we will be right back hereOR, something like this, where it loops until usb flag no longer set (replacing early returns with continue)-(I also changed flag getting/clearing so only needs to be done in one spot, one time- which I should do anyway)Code:void USB_ISR(){Usb u; Irq ir;static Usb::state_t last_state; //keep track of usb state for resumesfor(; ir.flag(ir.USB); ir.flag_clr(ir.USB)){ //added loopuint8_t flags = u.flags(); //get all irq flagsuint8_t eflags = u.eflags(); //get all irq error flagsu.flags_clr(flags); //clear only what we got (1=clear)u.eflags_clr(flags); //clear only what we got (1=clear)//ATTACHED->POWERED if vbus_pin highif(u.state == u.ATTACHED){if(vbus_pin.ison()) u.state = u.POWERED;else continue; //no power- how we get here with no vbus?}...} //for loop}Maybe the loop and flag check make it not worth it? or maybe its a good idea as maybe usb gets multiple consecutive irq's since the stat fifo is 4 long? The usb isr does seem to require a fair amount of code to run so most likely takes longer than normal isr's.For a other 8bit pic's I use (only 1 isr, which is processed differently of course), when checking the rx flag I usually use a loop that to get the rx buffer flushed right away.

author: NorthGuy
author_rank: Super Contributor
timestamp: December 27, 2017, 05:32:13 am
content: Probably doesn't matter for practical purposes. Presumably, you process your USB interrupts faster than they occur, so fishing for more interrupts in a loop is usually redundant.

author: cv007
author_rank: Frequent Contributor
timestamp: December 27, 2017, 06:38:59 am
content: I think you may be right. I was trying to read the mchp usb code to get ideas how they do things (very difficult), and in the TRNIF processing (in isr) they keep that part in a loop to keep the stat fifo flushed. But, they also have the same usb code for every usb chip they ever made (it seems) including the most under-powered 8bit they have, where its probably a lot easier to bump up against the end of the usb fifo. The PIC32MM should not have any problem I guess, and an extra prologue/epilogue from time to time is not a big deal, also simpler code is better. Their usb code is a bugger to read- I have de-macro'd it, along with the original version, and grep things from the command line- still difficult.I don't know if I can get usb working with my own code, but I'll attempt it. At least I will probably learn something along the way. I started with an empty main for the pic32mm, took one chapter of datasheet, created a 'driver' for that peripheral, repeat. I'm also learning C++ along the way.Another question, here is my current code (for the moment)-Does it matter the order of clearing the flags? (usb irq flag vs the various usb flags)Code:void USB_ISR(){Usb u; Irq ir;static Usb::state_t last_state; //keep track of usb state for resumesuint8_t flags = u.flags(); //get all usb specific irq flagsuint8_t eflags = u.eflags(); //get all usb specific irq error flagsUsb::stat_t stat; //get stat reg BEFORE flags clearedu.stat(stat); //pass by referenceu.flags_clr(flags); //clear only what we got (1=clear)u.eflags_clr(eflags); //clear only what we got (1=clear)ir.flag_clr(ir.USB); //clear usb irq flag//ATTACHED->POWERED if vbus_pin highif(u.state == u.ATTACHED){if(vbus_pin.ison()) u.state = u.POWERED;else { //no power (not sure how we would get here with no vbus)return;}}//more code...edit-this may be one of those things that I know or thought I knew, but then I doubt that I really know and question it all over.Except for persistent flags (which does matter), I think it doesn't matter. Probably.(or I can deduce that the datasheet and reference manual don't say anything about it except for persistent flags)

author: voltsandjolts
author_rank: Unknown
timestamp: December 27, 2017, 09:04:35 am
content: ARM Cortex has tail-chaining, does PIC32 have a similar feature?

author: andersm
author_rank: Super Contributor
timestamp: December 27, 2017, 12:36:17 pm
content: Quote from: voltsandjolts on December 27, 2017, 09:04:35 amThose with cores that implement the MIPSMCU ASEhave something vaguely similar, but it's not supported by Microchip's toolchain (the features have to be enabled, and a special interrupt epilogue has to be generated). If you're noticing that you're spending most running time in ISRs there could be some advantage in using a loop, since MIPS ISRs have a fairly huge overhead compared to most architectures.

author: mikeselectricstuff
author_rank: Super Contributor
timestamp: December 27, 2017, 12:54:03 pm
content: You'd normally do all the ints you can in one call to save the time taken getting in & out of the int code. e.g. in a UART receive int you'd loop until you'd emptied the FIFO

author: danadak
author_rank: Super Contributor
timestamp: December 27, 2017, 01:33:01 pm
content: Just as general practice -1) Use pointers in ISR to minimize stack push.2) Set flags in ISR and handle processing outside ISR.3) Wherever possible use DMA to handle ISR events.Regards, Dana.

author: mikeselectricstuff
author_rank: Super Contributor
timestamp: December 27, 2017, 01:44:17 pm
content: Quote from: danadak on December 27, 2017, 01:33:01 pmI'd disagree with that unless you really need the performance, because DMA can be really hard to debug.With an ISR, you can twiddle IO bits in the ISR so you can tell exactly when ints are called, and use scope triggers to capture rare events and observe the sequence of evends. DMA just happens and you have very little visibility of what's going on.

author: nctnico
author_rank: Super Contributor
timestamp: December 27, 2017, 02:02:36 pm
content: Quote from: danadak on December 27, 2017, 01:33:01 pmThat is a very bad idea. Better do all the processing inside the ISR because that will prevent problems with sharing data between two asynchronous processes. Not to mention the extra overhead of setting & reading the flags. Also doing time critical (microseconds) processing in the main loop makes the main loop and whatever is related to it time critical while most of it won't be time very critical at all (tens of milliseconds or more). Most microcontrollers support nested interrupts so even if one interrupt takes long, you can still service a higher priority interrupt. Either way anything time critical will need an analysis regarding response time and available processing time.

author: cv007
author_rank: Frequent Contributor
timestamp: December 27, 2017, 02:18:28 pm
content: Maybe using the (only) shadow register set for this irq priority would be a good idea as it will be the most used I would guess. If I ever get to workable usb code, I could wiggle a pin in the usb irq to see how often back-to-back isr actually happens. I suppose one could also calculate how often its possible to get a transaction complete flag for whatever usb configuration is used, and go from there.

author: andyturk
author_rank: Frequent Contributor
timestamp: December 27, 2017, 03:25:36 pm
content: Quote from: danadak on December 27, 2017, 01:33:01 pmThis may not be necessary on ARM Cortex architectures because a second stack pointer is available for interrupt processing.QuoteAssuming you have a good way to communicate from the ISR to whatever is going to do the actual processing, and no hard realtime deadlines that will be missed. E.g., with an RTOS, you can use a Semaphore to wake a thread. In this case, it's nice to separate the handling of the interrupt from the processing.QuoteFor things like serial protocol handlers, using DMA usually means you end up with *fewer* interrupts, which is probably a good thing. I.e., instead of getting one interrupt per byte sent/received on your I2C driver, you have one interrupt from the DMA controller when it's all done. Fewer interrupts is almost always better than more.

author: NorthGuy
author_rank: Super Contributor
timestamp: December 27, 2017, 03:36:15 pm
content: Quote from: cv007 on December 27, 2017, 02:18:28 pmIf your transfer size is 64 bytes, then the time between interrupts cannot possibly be more than 50 us. However, it depends on your protocol. For example, if you use HID, you get only one IN transfer every millisecond (or even less frequently).You have full control and you won't receive anything until you arm your endpoint, so you can go at your own pace. Therefore, USB is a good candidate for main loop processing.

author: cv007
author_rank: Frequent Contributor
timestamp: December 27, 2017, 06:32:42 pm
content: The ultimate goal will be basic audio via usb to a dac, and I 'think' audio is done every frame, so 1000 irq's/sec from usb. Actual data amount probably not too important in this case as dma will do most of the work moving incoming audio out to dac.24,000 clocks (1ms, 24MHz clock), less whatever the usb isr code uses, is a lot of time for the pic32mm to do other things so the original question now seems unimportant.

author: westfw
author_rank: Super Contributor
timestamp: December 28, 2017, 07:55:32 am
content: There are two parts of the overhead for servicing an interruptThe inherent overhead of servicing any interrupt - saving and restoring the PC and other registers, jumping to the ISR, possibly invalidating cache and memory management, and etc. Hardware (shadow register sets, tail chaining, tightly coupled memory and/or special cache/mmu settings, etc) can minimize this, but it's always going to be there.The overhead that the ISR function needs to set up the particular context for whatever device it is actually servicing. If you're doing a USART interrupt, this usually means setting up pointers to the UART registers, and other pointers to a memory buffer somewhere, and things like that. It could be a relatively significant amount of code.BOTH of these are likely to involve "many" more cycles than looping in the ISR to check for more conditions, so for maximum efficiency, you probably want to loop. Carried to extremes, that would mean you should just poll instead of using ISRs... You'd normally avoid (or limit) looping if you are particularly concerned with latencies that the interrupt priorities don't address, or if you're pretty sure there will never be multiple things to service... Thinking about extremes ("what happens if for some reason the system is so loaded that I DO have multiple things to service even though I think I shouldn't?") is a good idea, but I'm not sure that there's always something useful that you can figure out!

author: tggzzz
author_rank: Super Contributor
timestamp: December 28, 2017, 10:51:55 am
content: Quote from: westfw on December 28, 2017, 07:55:32 amThat's a good summary.Those problems are avoided by using the XMOS xCORE processors, since you dedicate one core (of up to 32) to a single I/O port, and sleep until the specified condition on the I/O port occurs. The hardware wakes up the core with a latency of 10ns (XMOS states <100ns). Add "FPGA-like" I/O, delete caches, and you have an excellent basis forhardrealtime processing.All that would be useless without tight integration with the software. The xC language, based on CSP, is the excellent solution to that.

author: danadak
author_rank: Super Contributor
timestamp: December 31, 2017, 12:28:10 am
content: nctnico,QuoteThat's an excellent point. My application did not have async processes operating on the same data,so that issue in the design not of concern.QuoteDesign was Cortex M3, GNU compiler shows 1 cycle to set / clear a bit. That's not using bit banding,just C.QuoteIn this design the main loop was the primary loop.Regards, Dana.

author: westfw
author_rank: Super Contributor
timestamp: December 31, 2017, 12:56:26 am
content: QuoteI find that difficult to believe; do you have an example? (1 cycle to set a bit in a CPU register, perhaps. But that's of pretty limited use by itself...)

author: danadak
author_rank: Super Contributor
timestamp: December 31, 2017, 02:36:16 am
content: I declared a bit struc, outside of main(), then set the bit, then cleared itinside of main().lst file shows a single ORR, for setting, acting on one of the registers.Reg load apparently done during initialization. So bit permanently assigned tothe register.To your point in a more fully implemented design that reg would have to be loadeddepending on compiler decisions.I am not a C expert by any stretch, so I could be in error....."limited use", discussion is setting a flag in an ISR then leaving ISR and testing/processingbit / thread in main().Regards, Dana.

author: westfw
author_rank: Super Contributor
timestamp: December 31, 2017, 07:38:44 am
content: QuoteOK; that makes sense, for a sufficiently tiny program...Thanks for providing the details!