1
\$\begingroup\$

I need to jump into PCINT ISR as fast as possible after signal appears on pin. Currently it takes about 20 clock cycles. I would like to decrease it to 10 cycles. I am using C. Is there a way? I found out the code optimisation has a big impact.

Edit: Code added

#include<avr/io.h>
#include<avr/interrupt.h>

ISR(PCINT3_vect) {

    PORTB &= ~ (1 << 0);

}


int main (void) {

    DDRB |= (1 << 0); 

    ///SET PIN CHANGE INTERRUPT
    PCICR |= (1 << PCIE3);
    PCMSK3 |= (1 << PCINT24);

    sei();

    while(1) {
        
        PORTB |= (1 << 0);
            
    }   
}

PORTB0 is wired to the input (PCINT24). I measured pulse width using scope and with 20MHz crystal it was 1us.

Edit 2:

Thanks for answers. Regarding the assembler I do not have much experience to do this stuff better than in C. However, I did another measuring setup and results are satisfied for my purpose. I generate a very short pulse of 5 Clk with period of 40 Clk using Fast PWM (at 20Mhz F_clk). The PWM is wired to PCINT pin. I read the PIN in ISR as fast as possible. With 6 cycles pulse the pin is still read as High. With 5 cycles pulse the pin reading become Low.

The Pulse: (0.2us/div , 1V/div) (poor scope probe)

enter image description here

Code:

#include<avr/io.h>
#include<avr/interrupt.h>


volatile unsigned char state_flag = 0;

ISR(PCINT3_vect) {


    static unsigned char port_old = 0;

    unsigned char port = PIND;
    
    if(   ((port & (1 << 0)) == 0)   && ((port_old & (1 << 0)) == 0)    ) {
    
        state_flag = 1;
    } else {
    
        state_flag = 2;
    }
        
    port_old = port;

}



int main (void) {


    DDRB |= (1 << 0);  //Led
    PORTB &= ~(1 << 0);

    
    DDRD &= ~(1 << 0);  //PCINT pin
    PORTD &= ~(1 << 0);  //pull-up OFF

    ///SET PIN CHANGE INTERRUPT
    PCICR |= (1 << PCIE3);
    PCMSK3 |= (1 << PCINT24);
    
    
    //SET PWM 
    DDRB |= (1 << 4);  //OC0B
    TCCR0A |= (1 << COM0B1);
    
    TCCR0A |= (1 << WGM00); //fast pwm
    TCCR0A |= (1 << WGM01);
    TCCR0B |= (1 << WGM02);
    TCCR0B |= (1 << CS00);  //prescaler fclk/1
    OCR0A = 40;  //top value
    OCR0B = 6;  //compare value

    sei();


    while(1) {
        
        if(state_flag == 1) {
            state_flag = 0;
            PORTB &= ~(1 << 0); 
        }
        
        if(state_flag == 2) {
            state_flag = 0;
            PORTB |= (1 << 0);
        }
            
    }
    
}
\$\endgroup\$
8
  • 1
    \$\begingroup\$ The jumping to run ISR takes only a couple of cycles, the C overhead before it runs your code may be significant. Why not post your C code, parameters used to compile it, and the resulting assembly lististings to give any ideas how to make it faster? \$\endgroup\$
    – Justme
    May 15, 2023 at 5:27
  • \$\begingroup\$ I added the code i am using for measurement. \$\endgroup\$
    – user339861
    May 15, 2023 at 5:49
  • \$\begingroup\$ If you are measuring the pulse width, then you are measuring how long it takes to exit the ISR and to run main loop again where it left off. Also both are read-modify-write operations so that alone is multiple opcodes. Which AVR is this? What supply voltage it uses? Are you sure it runs on the crystal, instead of some slower internal frequency? \$\endgroup\$
    – Justme
    May 15, 2023 at 5:56
  • \$\begingroup\$ It is ATmega164PU at 5V. Fuses are set to full swing. I am sure it runs of crystal because when I changed crystal to 8M and 13M one the frequency adequate changed. \$\endgroup\$
    – user339861
    May 15, 2023 at 6:09
  • 2
    \$\begingroup\$ You should look at the disassembly what assembly opcododes it executes. The datasheet says the interrupt response time is 4 cycles minimum, depending on what the MCU was executing as it has to finish an opcode. Interrupt return is also 4 cycles. The rest may be C overhead which you will see from the generated assembly listing. \$\endgroup\$
    – Justme
    May 15, 2023 at 6:40

3 Answers 3

2
\$\begingroup\$

Create an assembler version. Prologues and epilogues for ISR procedures differ in size for different compilers. For ISR procedures, they are by any means larger than for the same procedure with this call prototype. If you do not use arithmetic calculations, then just create an implementation of your actions in a separate *.s (asm) file. If you want to do this in C, you can use special attributes to set the function to disable the formation of prologue and epilogue, and place the function itself using attributes in a separate named section of the linker, then set the address of the interrupt vector of your ISR in the link file. It will be easier to do this on asm.

\$\endgroup\$
14
  • 1
    \$\begingroup\$ Before jumping right into making the ISR in assembly language, it is worth checking what kind of ISR the C compiler is making. You still need to push the registers you use to stack and pop them, there may not be much to gain by switching to assembly. \$\endgroup\$
    – Justme
    May 15, 2023 at 9:33
  • \$\begingroup\$ @justm It's always a good idea to check. But long experience tells me assembly is needed here. Some years ago i suggested a contest as a challenge to those who argued c compilers could closely equal a hand assembly coder. I decided to hamper the assembly coder challenge further by requiring that the code fit c prologue/epilogue conventions, as well. Months later and dozens if c compilers later, the assembly code i wrote was twice as fast and half the size. GCD algorithm, so short and giving still more handicapping to the c compilers. They aren't creative. I don't expect better today. \$\endgroup\$ May 15, 2023 at 9:54
  • 1
    \$\begingroup\$ The safe ISR procedure takes 18 cycles of prologue and epilogue code even in hand-written in assembly. You can't literally make it any faster, even if clearing the bit takes 3 cycles. Assuming you want it to be compatible with running any standard C code in main loop \$\endgroup\$
    – Justme
    May 15, 2023 at 10:01
  • 1
    \$\begingroup\$ @Lundin The last time I used AVR-GCC, it had already deprecated the cbi/sbi macros and it could generate cbi/sbi when applicable. \$\endgroup\$
    – Justme
    May 15, 2023 at 17:03
  • 1
    \$\begingroup\$ @periblepsis My experience with avr-gcc agrees with yours. I've written DSP code for example: I made a 6 tap reverb + 2x biquad filter at 16 bits x 1ch x 25kSps on a 32MHz XMEGA, which performed at about this level between native C vs. hand ASM. GCC synthesizes to a 16-bit intermediate representation, which isn't optimized on translating to native as I understand it. Hence a lot of time is wasted shuffling around register pairs, sign extensions, etc. when not needed. Library calls are also used for 16+ bit multiplication. (8x8 and 8x16 are inlined, however.) \$\endgroup\$ May 17, 2023 at 9:07
1
\$\begingroup\$

There is not much you can do make it faster.

The ISR code must store and restore all registers it uses, including the status register, in order to not disturb the main program which also uses the registers. It must be done to allow the main to run any arbitrary code and be able to interrupt it.

The ISR code approximately executes the following:

(4) enter ISR

(2) push tempregister

(1) in tempregister, SREG

(2) push tempregister

[your code starts]

(1) in tempregister, PORTB

(1) andi tempregister, 254

(1) out PORT, tempregister

[your code ends]

(2) pop tempregister

(1) out SREG, tempregister

(2) pop tempregister

(4) reti

So you see, it already takes at least 18 cycles minimum to enter and exit an interrupt, even if your code takes only 3 cycles to execute to change the IO pin low.

\$\endgroup\$
4
  • \$\begingroup\$ If you can use SBI and CBI instructions for PORTB you don't need to preserve SREG and the tempregister. The code then needs only 10 cycles. \$\endgroup\$
    – Jens
    May 15, 2023 at 13:01
  • \$\begingroup\$ @Jens From what I could tell from the manual, there's a mandatory >11 cycle interrupt latency by the hardware. \$\endgroup\$
    – Lundin
    May 15, 2023 at 13:22
  • \$\begingroup\$ @Lundin Where the 11 cycles? It should take approximately 2 cycles from the IO synchronizer to see the change and trigger PCINT, then CPU should finish executing any multi-cycle instructions, then four cycles to push PC to stack and it's running the ISR code. Good idea from Jens to use just CBI but compiler optimizations should already use CBI/SBI where possible. Maybe here some assembly with naked ISR with only CBI and RETI would be good to test. \$\endgroup\$
    – Justme
    May 15, 2023 at 14:20
  • \$\begingroup\$ @Justme See the quote from the core manual in the answer I posted. \$\endgroup\$
    – Lundin
    May 15, 2023 at 14:28
1
\$\begingroup\$

Disclaimer: I haven't used AVR since these were still fashionable, meaning >15 years back. So my knowledge of the ISA is quite rusty.

The term you are looking for is often called interrupt latency and tends to refer to both hardware overhead as well as software overhead as per ABI/calling convention. To know the hardware overhead part, you need to read the friendly core manual, in this case for ATmega16:

Interrupt Response Time

The interrupt execution response for all the enabled AVR interrupts is four clock cycles minimum. After four clock cycles the program vector address for the actual interrupt handling routine is executed. During this four clock cycle period, the Program Counter is pushed onto the Stack. The vector is normally a jump to the interrupt routine, and this jump takes three clock cycles. If an interrupt occurs during execution of a multi-cycle instruction, this instruction is completed before the interrupt is served. If an interrupt occurs when the MCU is in sleep mode, the interrupt execution response time is increased by four clock cycles. This increase comes in addition to the start-up time from the selected sleep mode.

This means that the minimum overhead time enforced by the hardware is 4+3 cycles. Plus some extra depending on where the interrupt hit. Then adding the time to return, we have 4+3+4 = 11 absolute minimum.

Add to that anything mandated by the calling convention such as extra stacking of registers not done by the hardware. This will be mentioned in the compiler manual. It would seem PC + SP are handled by the hardware, but SREG pushing/popping is only parially handled by setting the I bit.

Add to that the time it takes to read, modify, write the register as well as extra overhead in case main() is interrupted in the middle of an instruction.

So there isn't much you can improve here...

However, note that PORTB &= ~ (1 << 0); in C comes with some quirks. PORTB is volatile so this C code enforces both a read and a write. Even if the core supports a bit set instruction, the C code still tells it to read the register first. This may or may not include a middle step in case the core loads it into a register (check the disassembly). It may be ever so slightly faster to use a bit set instruction if supported (SBI/BST?). This can't be done reliably from C code.

Additionally note that the actually GPIO port itself will have some latency, again written somewhere in the friendly manual, typically 10 to 100ns somewhere.

\$\endgroup\$
4
  • \$\begingroup\$ Lundin - Hi, Please remember the site rule for content copied into a post from elsewhere. In order to help, I found what I believe to be the missing source PDF link in this case and added it for you. For the future, please remember it's your responsibility to do that :) Thanks. \$\endgroup\$
    – SamGibson
    May 15, 2023 at 14:02
  • \$\begingroup\$ @SamGibson Maybe you should instead prompt the OP to include the MCU partno they are using as well as a link to the friendly manual as part of the actual question? \$\endgroup\$
    – Lundin
    May 15, 2023 at 14:04
  • 2
    \$\begingroup\$ Lundin - "Maybe you should instead prompt the OP" There is no "instead" to it - I just reminded you, politely, about where your post broke the site rules. I even helped you by finding & adding the link. And now you reply with snark that I didn't do something else, for someone who didn't break the site rules? That's not a productive reply. Remember to follow the Code of Conduct and stop the snark. || You also have the ability to edit the question or ask for more info. Mods are not expected to read and improve every question. \$\endgroup\$
    – SamGibson
    May 15, 2023 at 14:36
  • \$\begingroup\$ @SamGibson You are reading things between the lines that aren't there. There was no snark other than frustration that I had to dig up the datasheet in order to answer the question, because the OP didn't link it nor provide the necessary partno. I'll remember to avoid answering such questions in the future, I'll just down vote them and close vote as unclear. \$\endgroup\$
    – Lundin
    May 16, 2023 at 6:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.