PCIFR update is not immediate in Atmega328P

Go To Last Post
11 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I was looking at Pin change interrupt in ATMEGA328P. I was working on Arduino nano (Low cost Chinese version)

Here is my code used for testing pin change interrupt flag on output change:

#ifndef F_CPU
#define F_CPU 16000000UL
#endif

#include <avr/io.h>
#include <util/delay.h>
void USART0SendOneByte(uint8_t data)
{
    while(!(UCSR0A&(1<<UDRE0)));
    UDR0=data;
}

int main(void)
{
    UCSR0B=0;  // Boot loader will disable normal port operation. So keep them as normal port pin.
    UBRR0=103; // 9600 baud, rest all parameters default
    //Enable the transmitter and receiver as applicable
    UCSR0B |= (1<<TXEN0);
    DDRB |=1<<PB1;  // Set LED pin as output port
    PCMSK0 = 1<<PCINT1; // Enable pin change interrupt on PB1
    while(1)
    {
        PORTB ^= (1<<PB1);
        //_delay_us(1);
        USART0SendOneByte(PCIFR & (1<<PCIF0));
        //while(!(UCSR0A&(1<<UDRE0)));
        //UDR0=PCIFR & (1<<PCIF0);
        PCIFR = (1<<PCIF0); // Clear the interrupt flag by writing 1
        USART0SendOneByte(PCIFR & (1<<PCIF0));
        _delay_ms(1000);
    }
}

Above code sends 00 00 on USART0. 

If I add 1 us delay by uncommenting _delay_us(1), controller sends 01 00 as expected.

Alternatively, below code also works fine.

while(1)
    {
        PORTB ^= (1<<PB1);
        //_delay_us(1);
        //USART0SendOneByte(PCIFR & (1<<PCIF0));
        while(!(UCSR0A&(1<<UDRE0)));
        UDR0=PCIFR & (1<<PCIF0);
        PCIFR = (1<<PCIF0); // Clear the interrupt flag by writing 1
        USART0SendOneByte(PCIFR & (1<<PCIF0));
        _delay_ms(1000);
    }

In this second code snippet, I am not using function call to send one byte. Instead directly waiting for UDRE0 flag to set.

 

So my conclusion is that in first code snippet, function is called right away by passing PCIFR & (1<<PCIF0) to the function. At this instance, PCIFR is not updated it looks. In second case it works because while loop introduces some delay anyway and by that time PCIFR gets updated.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Surely you could just assign a volatile variable to PCIFR.  e.g.

    PORTB ^= (1<<PB1);
    //asm("nop");
    PORTC = PCIFR;
    PCIFR = (1<<PCIF0);

Insert nop(s) as appropriate.   Observe the behaviour in the AS7.0 Simulator.  Or with a Logic Analyser.

 

I have not checked the datasheet.   I suspect that PCIFR will take one or two cycles.   And bear in mind that any ISR() response has considerably more cycles.

 

You will also find the Simulator invaluable for showing the Disassembly view.   And counting the cycles.

 

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I am not making use of ISR. Only PCIFR status flag is used. How a beginner should know if delay (nop) has to be introduced before reading the PCIFR or any other registers similar to this? I think processor should not execute next instruction without updating the PCIFR register. Is this a bug in processor by chance? I am just hobby programmer and do not have deep knowledge on programming and also no idea about logic analyzer or simulator.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I know that you are not using an ISR().   Polling PCIFR is much quicker.

 

Anyway,  you have intrigued me.  I will run it through the Simulator.   Determine how many "cycles" in the response.

And then test in real life with a Logic Analyser.

 

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I wrote a trivial program for ATmega328P

/*
* PCIFR_response.c
*
* Created: 03-Jun-21 14:46:41
* Author : David Prentice
*/

#include <avr/io.h>

int main(void)
{
    DDRB |=1<<PB1;  // Set LED pin as output port
    PCMSK0 = 1<<PCINT1; // Enable pin change interrupt on PB1
    while (1)
    {
        PORTB ^= (1<<PB1);
        asm("nop");
        asm("nop");
        asm("nop");
        PORTC = PCIFR;
        PCIFR = (1<<PCIF0);
    }
}

It required 3 NOPs to register the interrupt flag.

PB1 bit has just been changed at PC=0x4A

PCIFR register (0x1B) is read at PC=0x4D

 

i.e. it takes three cycles.   If I looked in a logic Analyser,  PB1 is changed @ 0x4A and PC0 is written @ 0x4F

        PORTB ^= (1<<PB1);
00000046 95.b1                IN R25,0x05		In from I/O location
00000047 82.e0                LDI R24,0x02		Load immediate
00000048 89.27                EOR R24,R25		Exclusive OR
00000049 85.b9                OUT 0x05,R24		Out to I/O location
        asm("nop");
0000004A 00.00                NOP 		No operation
        asm("nop");
0000004B 00.00                NOP 		No operation
        asm("nop");
0000004C 00.00                NOP 		No operation
        PORTC = PCIFR;
0000004D 8b.b3                IN R24,0x1B		In from I/O location
0000004E 88.b9                OUT 0x08,R24		Out to I/O location
        PCIFR = (1<<PCIF0);
0000004F 81.e0                LDI R24,0x01		Load immediate

David.

Last Edited: Thu. Jun 3, 2021 - 02:15 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thank you David for working on it. But I may not be able to conclude since I am at level 0 in uC programming. Do I need to wait in while loop until PCIF0 in PCIFR is set before sending the status on USART0? Better code than giving delay I guess.   

Does datasheet document this delay somewhere or is it a general rule for most uCs?  

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0


joneewheelock wrote:
Does datasheet document this delay somewhere or is it a general rule for most uCs?  

DS shows:

and says:

there is more in that paragraph, ask if you still have questions.

 

Jim

 

 

Keys to wealth:

Invest for cash flow, not capital gains!

Wealth is attracted, not chased! 

Income is proportional to how many you serve!

If you want something you've never had...

...you must be willing to do something you've never done!

Lets go Brandon!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You always have to read and write via an AVR register e.g. reading PCIFR into R24 and writing R24 into UDR0.

 

It is a fixed response time i.e. 3 cycles.   You don't need to poll.  It is quicker to insert 3 NOPs.

This is what happens if you poll:

        while ((PCIFR & (1<<PCIF0)) == 0) ;
0000004A d8.9b                SBIS 0x1B,0		Skip if bit in I/O register set
0000004B fe.cf                RJMP PC-0x0001		Relative jump

It fails on the first pass.  And succeeds on the second pass.  i.e. 1 + 2 + 2

So instead of wasting 3 cycles the successful poll takes 5 cycles.

 

David.

 

Edit.   As an exercise for the reader.   Try to Simulate the INT0 response.   I suspect that it is 2 cycles instead of 3.

Last Edited: Thu. Jun 3, 2021 - 02:52 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks all for giving good info. smileyWe need smart compilers in this hi-tech era, wherein compiler takes care of such important issues by warning the programmer and if opted, introducing nops automatically rather than user adding required code. People (most having little patience today) wants to get things done without reading in detail. May be such compilers are coming soon!!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

joneewheelock wrote:
People (most having little patience today) wants to get things done without reading in detail.

Hmm, the devil is in the details devil     in the details, lay the rewards!  You may want to re-think that!!!

 

 

Keys to wealth:

Invest for cash flow, not capital gains!

Wealth is attracted, not chased! 

Income is proportional to how many you serve!

If you want something you've never had...

...you must be willing to do something you've never done!

Lets go Brandon!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

My guess was correct.

int main(void)
{
    DDRD |= 1<<PD2;  // Set INT0 pin as output port
    EICRA = 1<<ISC00; // Enable pin change interrupt on INT0
    EIMSK = (1<<INT0);
    while (1)
    {
        PORTD ^= (1<<PD2);
        asm("nop");
        asm("nop");
        //asm("nop");
        PORTC = EIFR;
        EIFR = (1<<INTF0);
    }
}

The EXT_INT0 operates in 2 cycles.  i.e. marginally faster than PCINT0

 

Regarding "automatic insertion" of NOPs would really set a cat among the pigeons.

 

Think about it.   This example would never be used in real-life.  The only occasion might be to implement a"SWI".

In which case the hardware vectors to the appropriate ISR().   The 2-cycles are just part of the "interrupt response".

 

Incidentally,  many microcontrollers require a 1-cycle delay between writing to a PORTx and reading the PINx register.

You also get the "polling anomaly".   You poll with SBIS but when SBIS "succeeds" the SBIS opcode takes 2 cycles to do the skip.

 

David.