I recently came across this piece of code in an interrupt service routine (ISR):
#define MAX_CHANNELS 4
static uint16_t volatile* ADCVALS[MAX_CHANNELS] = {
&ADC1BUF0, &ADC1BUF1, &ADC1BUF2, &ADC1BUF3
};
static uint8_t CHANNELS = 0;
static uint16_t volatile* volatile BUFFER_IDX[MAX_CHANNELS];
void __attribute__((interrupt, no_auto_psv)) _AD1Interrupt(void) {
*(BUFFER_IDX[0]++) = *ADCVALS[0];
if (CHANNELS >= 1) {
*(BUFFER_IDX[1]++) = *ADCVALS[1];
if (CHANNELS >= 2) {
*(BUFFER_IDX[2]++) = *ADCVALS[2];
if (CHANNELS >= 3) {
*(BUFFER_IDX[3]++) = *ADCVALS[3];
}
}
}
}
It copies between 1-4 register values into memory, depending on the value of CHANNELS
, which is a value between 0-3 which is set elsewhere in the program via a setter function.
I found the nested if's extremely ugly and changed it to this:
int i;
for (i = 0; i <= CHANNELS; i++) {
*(BUFFER_IDX[i]++) = *ADCVALS[i];
}
which promptly broke the ISR. This is an embedded system, PIC24 architecture, 64 MHz clock. The ISR is severely time constrained and must finish within 1 µs. The for loop is apparently too slow, while the nested if is fast enough.
My question, then, is two-fold:
- Is there a less ugly way to do what the nested if clauses do, without slowing down the ISR?
- Why is the for loop so much slower? I would have expected the compiler (xc16) to be smart enough to generate similar asm for both (at
-O2
).
i <= CHANNELS
is almost always wrong. Usually something likei < CHANNELS
is correct. – DarnleyCHANNELS
? Is it a macro (common convention is to use all-uppercase words for macros, not for variables) or an enumeration? – DarnleyCHANNELS
is a value between 0-3, where 0 means copy one value, and 3 means copy four.BUFFER_IDX
is an array of pointers to a preallocated memory region. The region is split into 1-4 sections, depending on the value ofCHANNELS
. Soi <= CHANNELS
is correct, since the loop should run forCHANNELS == 3
but not forCHANNELS == 4
. – Etoileif (CHANNELS == 3)
instead ofif (CHANNELS >= 3)
, since the only valid values forCHANNELS
is 0-3. In that case, I believe the two code examples are equivalent, no? – EtoileBUFFER_IDX
andCHANNELS
and so on. Start with disassembling the original code and take it from there. The only thing I can tell you from this code is that you shouldn't be using naive types such asint
when coding embedded systems. In case of PIC, usingint
may make the code needlessly slow for nothing gained and the compiler might not be able to optimize away such basic mistakes. – Insiderint
is slow, it's pretty obvious. It's an 8-bit MCU so swap it foruint8_t
. – Insider