Multifunction ISRs - Page 1
Forum section: electronics / general

author: ajb
author_rank: Super Contributor
timestamp: March 01, 2015, 12:53:26 am
content: I'm finally trying to get my occasional MCU projects out of the 8 bit world and into ARM, and as I'm porting over some communications stuff I'm trying to do so with an eye toward better code reuse, which is something I've never really been good about. I generally know enough C to get myself into trouble--andusuallyout of it, eventually--so I hope some more experienced folks can offer some advice here.Say I'm working on a protocol driver that uses a UART at 250K and does some basic start/end checking in the ISR, but otherwise just chucks incoming data into a buffer. That's all fine and good. Now say I want to be able to use that same UART for a second protocol that requires a different ISR, and freely switch between the two during run time--and perhaps use the same protocol(s) on more than one UART simultaneously. It seems like the most efficient way to do this would be to reassign the interrupt vector to the appropriate handler function, but as far as I can see there's no way to do this during runtime, at least in the M0-M4 range. (I'm using Atmel's SAMD21 (M0+) and SAM4S (M4), for what it's worth).Performance wise, the next best thing I expect would be to create a combined ISR that takes the appropriate actions depending on which protocol is active. However that means spreading code from what would ideally be two separate libraries across their respective files and the main application, which isn't terribly elegant.The ideal option from a reusability standpoint would be to have protocol-specific handling functions that are selectively called from the peripheral's ISR. I know the prevailing wisdom at least in the 8-bit world is that ISRs should never call functions if at all possible, since that will increase stack usage and execution time. But I don't know to what extent that's a real concern with the increased speed and RAM of something in the ARM family, or to what extent I can rely on the compiler to optimize away that overhead. I'm already using ASF API functions for most hardware accesses anyway. I don'tintendto push the limits of execution time and stack size, but I also don't want to waste resources unnecessarily. I could declare the handler functions as inline, but as far as I know that requires them to be defined locally, which again would require mixing library code into application code (maybe that's just my lack of C knowledge).The other complication is the ability to handle different hardware. Atmel's ARMs have both UARTs and USARTs on some parts, and multiple SERCOMs on others, all of which require different configurations to do the same thing. Ideally I think what I'd like to do is have a single handler function for each protocol, and selectively call the correct handler from each interface's ISR. The call to the handler would need to pass a struct to tell the handler which hardware interface to interact with and which RX/TX buffers to use, etc. That seems like the most elegant solution, but I don't know if it's asking for performance trouble. I also don't know at the moment how tricky it will be to deal with reentrancy with multiple ISRs potentially calling the same function--presumably one UART/USART/SERCOM interrupt would never preempt another, but I haven't looked into that very deeply yet.Or is there anything else I'm not thinking of, or am I just overthinking this whole thing?

author: westfw
author_rank: Super Contributor
timestamp: March 01, 2015, 01:15:15 am
content: Many ARMs have a relocatable vector table, so that you can transfer the whole vector table to a location in RAM rather than in flash. See the "Vector Table Offset Register" (VTOR) on M3, for example. (I'm not quite sure whether this is part of the ARM NVIC definition, or something added by vendors.)It's common for os-like applications to move vector tables into RAM. (You can take the startup vector table in Flash and simply memcpy() to a RAM location, and thereafter have changeable vectors.)(This was also common back in the 68000 days, and with other CPUs whose start address (that you wanted to be in ROM) and vector table defaulted to the same area of memory.)

author: ajb
author_rank: Super Contributor
timestamp: March 01, 2015, 01:48:04 am
content: Interesting, thanks! The SAM4S datasheet does indicate that the VTOR can point to RAM or ROM, so that's worth looking into. The SAMD21 datasheet lists the VTOR as a "configurable option" that is present. I guess they mean it's an optional feature of the M0+ core. No further info on its functionality. So it sounds like it won't be universally available across the ARM ecosystem.

author: andersm
author_rank: Super Contributor
timestamp: March 01, 2015, 03:08:36 am
content: If you don't have RAM vector tables you can always use function pointers. It seems what you're actually doing is defining a device driver model.

author: ajb
author_rank: Super Contributor
timestamp: March 01, 2015, 04:17:41 am
content: I'm not clear on how I'd use function pointers to redirect interrupts if the interrupt table isn't already in RAM. Or are you suggesting that the ISR calls the appropriate protocol handling function via pointer?While redirecting the interrupt vectors would solve the multiple protocols on one interface case, it doesn't help the case where the same protocol is implemented on multiple interfaces. For that case just calling a function from the ISR and passing it interface-specific parameters would be cleaner.Quote from: andersm on March 01, 2015, 03:08:36 amIs that what I'm doing? I'm not enough of a programmer to knowI just want to write better code so I don't have to rewrite it as often.

author: andersm
author_rank: Super Contributor
timestamp: March 01, 2015, 04:28:50 am
content: Quote from: ajb on March 01, 2015, 04:17:41 amYes. It's a technique that will work on almost all hardware.QuoteHaving a table of function pointers and context pointers is no more complex than having just the function pointers.

author: ajb
author_rank: Super Contributor
timestamp: March 01, 2015, 04:57:58 am
content: So I take it the bottom line is that calling a function from an ISR isn't actually that big a deal? That's what I was really wondering about, so if that's the case, then yeah, it's a no brainer.Thanks!

author: hans
author_rank: Super Contributor
timestamp: March 01, 2015, 09:51:12 am
content: The point of an ISR is to keep it as short as possible. When you're dealing with complete protocols that could be tough. If you're doing protocol handling inside an ISR I recommended sticking it to a simple state machine, e.g. header sync detection + payload grabber (+ maybe a rolling CRC check).But short != function calls. You could iterate through your complete payload buffer to do a CRC check and that may take hundreds of microseconds. You are not calling any functions - but it will stall the peripheral if you can overflow the FIFO (if it has any) within that period.Function pointers are quite efficient on any non 8-bit platform I know off. The call won't take much longer than a statically compiled call (ideally 1 instruction extra, to load the address from RAM).However, do consider stack size. On some RTOS you can allocate a specific stack size for interrupt handlers (the RTOS then usually babysits the ISR enter/leave), while others may not (so any stack growth is directly added to your maximum stack size). Always make sure it's decently sized - otherwise you might be chasing weird bugs and crashes for weeks.However, I would always write a dedicated UART device driver and offload any protocol handling outside of it. That would also solve your USART/UART problem, because even if the register I/O is different to use that should become transparent for your protocol handler. That is the whole idea of abstractions and creating layers of software; trick is to keep the abstractions as efficient and unbloated as possible.

author: Jeroen3
author_rank: Super Contributor
timestamp: March 01, 2015, 04:28:23 pm
content: Have your interrupt routine write the data to a pointer of a fifo buffer. Depending on the active protocol, you can change which fifo buffer.This reduces interrupt time significantly, and you are able to run your protocol processing on normal stack instead of interrupt stack. This is different on ARM, something to be aware of.It does take some extra memory, but you should have plenty.

author: westfw
author_rank: Super Contributor
timestamp: March 02, 2015, 08:08:10 am
content: QuoteIt depends on how much additional context saving code the ISR code needs to execute to meet the needs of the ABI used. On ARM, this is "not a big deal." With avr-gcc (as a counter-example), it turns out to be quite significant.QuoteThis code (From Arduino :-)) is typical:Code:void attachInterrupt(uint8_t interruptNum, void (*userFunc)(void), int mode) {if(interruptNum < EXTERNAL_NUM_INTERRUPTS) {intFunc = userFunc; // Set secondary vectorswitch (interruptNum) {// : (lots more cases)case 1: // actually enable the appropriate interruptEICRA = (EICRA & ~((1 << ISC10) | (1 << ISC11))) | (mode << ISC10);EIMSK |= (1 << INT1);break;// : (more cases)}}}// Actual ISR code (loaded in flash)ISR(INT1_vect) {if(intFunc)intFunc();}

author: mikerj
author_rank: Super Contributor
timestamp: March 02, 2015, 12:13:43 pm
content: Quote from: ajb on March 01, 2015, 04:57:58 amCalling functions from an ISR is fine, providing you deal with the usual re-entrancy issues. You don't have the restrictive stack limitations that you have with e.g. a PIC, and you can pass 16 or 32 bit values or de-reference pointers to data structures very quickly.

author: dannyf
author_rank: Super Contributor
timestamp: March 02, 2015, 03:03:46 pm
content: I cannot say that I have read every line but letting the isr process an interrupt differently based on differing context is not a big deal. The function pointer would be one way to do it - I actually implement most of my isr via function pointers - low-end PICs are the exception there.QuoteIt is a big deal most of the time. A big factor here is the re-entrancy.However, there are solutions even if that's the case -> like not calling that same function from somewhere else but that can be hard at times.

author: nctnico
author_rank: Super Contributor
timestamp: March 02, 2015, 03:27:07 pm
content: Quote from: Jeroen3 on March 01, 2015, 04:28:23 pmI wouldn't do that. Using a buffer just adds extra overhead and headaches with sharing data between 2 asynchronous processes. Handle as much as possible inside the interrupts unless latency for other interrupts gets problematic. Try to see the interrupt controller as a hardware timeslicing OS. Focussing on keeping interrupts short is prone to creating something very complicated. Some of my firmware spends close to 100% of the CPU time in interrupts but that doesn't matter because an interrupt is just another parallel process.

author: ajb
author_rank: Super Contributor
timestamp: March 02, 2015, 07:12:32 pm
content: Quote from: westfw on March 02, 2015, 08:08:10 amOkay, this is really the crux of my original problem. The vast majority of the previous programming I've done has been on AVRs, so moving to ARM has required some mental recalibration.Quote from: Jeroen3 on March 01, 2015, 04:28:23 pmThat gets tricky when the protocol must interact with the hardware interface for things like break detection or flow control. Or with addressed protocols, it requires buffering messages that may be intended for another node. Sure there may be plenty of memory, but if the ISR can quickly determine if the received data is valid and relevant to this node before buffering it, then the program becomes much more efficient and easier to maintain. For things like message interpretation that require more intensive processing, yeah, that's going to be handed off to another process via FIFO.Re-entrancy shouldn't be an issue for the things I have in mind, as long as the interrupts in question don't pre-empt each other, and that shouldn't be a problem.

author: Jeroen3
author_rank: Super Contributor
timestamp: March 03, 2015, 06:39:17 am
content: With fifo's you're hardware independent. You can change the interface to any other byte stream, for example Ethernet (tcp) or SPI instead or UART.But this is up to the implementer and his preference for making a highly abstracted structure. Which does include performance penalty.I've implemented a single shell process to be able to run on a TCP connection or UART or Virtual COM over USB. Only thing passing to the shell is a pointer to any (bidirectional) fifo.Unfortunately this requires the cpu to copy the data twice. (in and out of the fifo) But is was easier than having the shell work hardcoded on 3 different api's.The above is easier if you have a rtos with api's for such abstractions.

author: westfw
author_rank: Super Contributor
timestamp: March 03, 2015, 07:30:03 am
content: When I did SLIP, I created a state machine called from the ISR. Out of the device and into the packet (works fine for "virtual" devices), no extra copies, and it's processing you have to do anyway (so doing it in the ISR can increase latency for 'stuff', but doesn't increase overall processing burden.) Having short and simple ISRs is swell, but don't "cut off your nose to to spite your face."

author: miguelvp
author_rank: Super Contributor
timestamp: March 03, 2015, 07:48:59 am
content: Quote from: westfw on March 03, 2015, 07:30:03 amOne of my 25+ years ago signatures was:Don't SLIP while you PPP

author: nctnico
author_rank: Super Contributor
timestamp: March 03, 2015, 01:59:20 pm
content: Quote from: Jeroen3 on March 03, 2015, 06:39:17 amEven easier is to have a shell which can handle multiple streams/contexts where the data is pushed into the shell so the shell doesn't need to poll the fifos.

author: langwadt
author_rank: Super Contributor
timestamp: March 04, 2015, 01:56:11 pm
content: Quote from: westfw on March 03, 2015, 07:30:03 amthe old trick is to add a stack frame that points to you processing routine and do an RTI