ATMEGA 328p PCINT-Resolution questions

Hello all. Is there anyone that can tell me the minimun time between two PCINT:s on Atmega 328p

If a resolution of 0+1 micros is needed between two or more interrupts on that controller, whats best practice?.

Q:S
When can and can i NOT use Delay exept for inside ISR of course:
Does delay and micros occasionally "mess up" with Timer1 values and if so when?
Can i read TCNT1 value inside an ISR just by calling it without setting up the timer and does that increment inside the ISR or not?

//Best NIls

/*Edit Version.  211227-1 (YYMMDD-nr)
  Auth: Nils.bruhner@gmail.com

  HARDWARE SETUP:
  Three sound-sensors of any kind may be attached to an Arduino with X meter CC between mics to test the PCINT "of the shelf" resolution.
  Soundwave-front coming in and digitally interrupt (I/0) the three mics in unspecific order will result in X microseconds between the three Interuppts on each vector.
  The difference in micros between the interrupts  may be used to investigate speed of sound in air and/or the electronics behaviour and resolution etc.
  Sketch is compatible and tested only on an Atmega 328p-UNO 16 mhz.






  pin A0 PortC                       pin7 PortD                                 pin8 PortB

   (Mic)                               (Mic)                                      (Mic)
  micLeft----------------------------micCenter-----------------------------------micRight

                X meter                               X meter

  common Ground -5 volt
  common DC     +5 volt

*/




// void StartQuickTimer() //Experimental timer for counting chrystal clocksyckles instead of micros. Not used.
//  {
// noInterrupts ();
// Reset Timer 1A and Timer 1B
// TCCR1A = 0;
// TCCR1B = 0;
// TCNT1 = 0;
// TIMSK1 = 0;
// TIFR1 = 0;//_BV(TOV1);
// TCCR1B = _BV(CS10);
// TCCR1B |= B0000001;//Set prescaler to 1
// TCCR1B |= B0000010;//Set prescaler to 8
// TCCR1B |= B0000011;//Set prescaler to 64
// TCCR1B |= B0000100;//Set prescaler to 256
// TCCR1B |= B0000101;//Set prescaler to 1024
//TIMSK1 = _BV(TOIE1);
//  interrupts ();

// }//END void StartQuickTimer




//____________________________________________________________________________________________________

int micLeft = A0;
int micCenter = 7;
int micRight = 8;

volatile int i0 = 0;//var i(x) for eventually count nr of trigs when debugging . Not used.
volatile int i1 = 0;
volatile int i2 = 0;

volatile char trigged = 0;// var trigged for detection if trigged I/0. Not used.
volatile unsigned long resetcount = 0; // var to increment a fake delay if one mic is accidentally trigged.

volatile unsigned long timeLeft = 0;// Var to store times in micros inside ISR:s. Therefore volatile.
volatile unsigned long timeCenter = 0;
volatile unsigned long timeRight = 0;

volatile int stopLeft = 0;   // Flags to handle the trigs. Used in ISR:S Therefore volatile.
volatile int stopCenter = 0;
volatile int stopRight = 0;


void setup() {
  // StartQuickTimer(); // Experimental timer for counting chrystal clocksyckles instead of micros. Not used.


  pinMode(micLeft, INPUT_PULLUP);
  pinMode(micCenter, INPUT_PULLUP);
  pinMode(micRight, INPUT_PULLUP);
  pinMode(11, OUTPUT);//Eg Buzzer for debugging if trigged when no monitor is used.

  Serial.begin(9600);

  //PCICR = PCIF2 + PCIF1 + PCIF0; //Also all PCINT-vectors/ports B,C and D.

  PCICR |= 0b00000111;    // Activate all three PCINT-vectors on atmega 328p
  PCIFR = 0;              // Zero all INT-Flags.
  PCMSK0 = B00000001;     // Activate only pin D8 for interrupts (PCINT0)  PortB
  PCMSK1 = B00000001;     // Activate only pin A0 (PCINT8) for interrupts PortC
  PCMSK2 = B10000000;     // Activate only pin D7 (PCINT23) for interrupts PortD

}

void loop() {

  //Listen for accidental trigs on each port. If so ZERO again...
  if (stopLeft + stopCenter + stopRight > 0 < 3) {
    resetcount ++;
    if (resetcount > 10000000) {
      stopLeft = 0;
      stopCenter = 0;
      stopRight = 0;
      timeLeft = 0;
      timeCenter = 0;
      timeRight = 0;
      resetcount = 0;
      PCICR |= 0b00000111;    // Activate all PCINT-vectors
      PCIFR = 0;              // Zero all INT-Flags.
      PCMSK0 = B00000001;     // Activate only pin D8 (PCINT0)  PortB UNO!
      PCMSK1 = B00000001;     // Activate only pin A0 (PCINT8)  PortC UNO!
      PCMSK2 = B10000000;     // Activate only pin D7 (PCINT23) PortD UNO!
    }//END IF
  }//END IF

  //If all mics trigged within X Clockcykles, then deactivate PCI Control and send data to serial. After sending data then reactivate PCI Control.
  if (stopLeft == 1 && stopCenter == 1 && stopRight == 1 ) {

    PCICR = B00000000; //De-Activates PCI_vectors 0, 1, 2 (To Re-activte again set last three bits to 1..("PCICR = B00000111;")

    //WRITE TO EXTERNAL EXE APPLICATION.  Notice!: Serial.print command may accidentaly activate PCI on Arduino?

    //Serial.println(",");
    Serial.print("A");
    //Serial.print(",");
    Serial.write((byte)0x00);
    //Serial.print(",");
    Serial.print(timeLeft);
    //Serial.print(",");
    Serial.write((byte)0x00);
    //Serial.print(",");
    Serial.print("B");
    //Serial.print(",");
    Serial.write((byte)0x00);
    //Serial.print(",");
    Serial.print(timeCenter);
    //Serial.print(",");
    Serial.write((byte)0x00);
    //Serial.print(",");
    Serial.print("C");
    //Serial.print(",");
    Serial.write((byte)0x00);
    // Serial.print(",");
    Serial.print(timeRight);
    // Serial.print(",");
    Serial.write((byte)0x00);
    // Serial.print(",");
    Serial.println();
    //END WRITE TO EXTERNAL EXE APPLICATION

    //WRITE TO ARDUINO IDE-SERIAL-MONITOR FOR DEBUGGING..
    Serial.println( timeCenter - timeLeft);
    Serial.println( timeRight - timeLeft);
    Serial.println();
    Serial.println(resetcount);

    // Make a buzzer noise for debugging
    digitalWrite(11, HIGH);
    delay(6000);
    digitalWrite(11, LOW);
    delay(500); //Debounce before PCICR and PCMSK Re-Activation.

    PCIFR = 0; //Zero all INT_Flags..
    PCICR = B00000111; //Re-Activates PCI-Possibility on vectors 0, 1, and 2...
    PCMSK0 = B00000001;     // Re-Activate only pin D8  (PCINT0)
    PCMSK1 = B00000001;     // Re-Activate only pin A0 (PCINT8)
    PCMSK2 = B10000000;     // Re-Activate only pin D7 (PCINT23)

    //Zero all volatile variables in ISR:s..
    timeLeft = 0;
    timeCenter = 0;
    timeRight = 0;
    stopLeft = 0;
    stopCenter = 0;
    stopRight = 0;

  }//END IF
}//END LOOP



//_____________________________Trig 1 Left mic pins-D8-D13-PortB UNO___________________
ISR (PCINT0_vect) {

  if (stopLeft == 0) {
    stopLeft = 1;
    timeLeft = micros();
    PCMSK0 = B00000000; //Deactivate all pins in PCINT0-vect..PortB

  } //END IF
} //END PCINT0_vect

//____________________________Trig 2 Center mic pins A0-A5-PortC UNO______________________
ISR (PCINT1_vect) {
  if (stopCenter == 0) {
    stopCenter = 1;
    timeCenter = micros();
    PCMSK1 = B00000000; //Deactivate all pins in PCINT1-vect..Port C

  } //END IF
} //END ISR PCINT1_vect


//____________________________Trig 3 Right mic pins D0-D7-PortD UNO_______________________
ISR (PCINT2_vect) {
  if (stopRight == 0) {
    stopRight = 1;
    timeRight = micros();
    PCMSK2 = B00000000; //Deactivate all pins in PCINT2-vect..PortD
  } //END IF
} //END ISR PCINT2_Vect

Can we ask that you read and follow the forum guidelines on how to post code? Read the forum guidelines to see how to properly post code and some hints on how to get the most from this forum.
Use the IDE autoformat tool (ctrl-t or Tools, Auto format) before posting code in code tags to make the code easier to read and follow.

Arduino timing depends on interrupts so if interrupts are disabled, like inside an ISR, the millis() will stop counting but will handle one pending interrupt when the interrupts are enabled. If interrupts are disabled long enough for a second interrupt to be pending, one of the two will be lost forever and millis() will be slow.

Unclear what you mean by "mess up". Timer 0 is used for the micros() value and both Timer1 and Timer 0 generally run off the same system clock.

Yes, you can use the value of TCNT1 any time you like. It is buffered so the high byte matches the low byte. The timers continue to count even with interrupts disabled.

For measuring precise time intervals, Timer1 has an Input Capture pin that can be used to capture the TCNT1 value on a rising or falling edge. The capture can generate an interrupt and the ISR can then read the register that was set.

Interesting.
I need a setup where three or more independent PCINT with a resulution of 1 or Max 4 micros in between. They just might trigger at the same time if so i want the Timestamp to show as little dev as possible,

They shall all delgiver expected trig within less than 2000 micros.

The trigs Will give me as an exact time of

interrupts as possible. Do u think its possible om arduino 328p?specifically its piezo sensors they shall detecting a soundwave. The after detecting the times Will be sent via serial but that seem to trigg the atmegas PCICR

You should get awfully close by using three pins on the same Pin Change Interrupt. On each interrupt, save the PIN register for the port and the TCNT1 register into a buffer. You can then post-process the buffer to determine the order of RISING or FALLING edges and the relative timing of the edges in 16ths of a microsecond. The timer will cover 65536 clock ticks or 4096 microseconds.

Is this something u feel u can freelance me for? I need professional guidande here. Math i do very well. Arduino and interrupts is though headache for me. But i need it to work to send TCNT1 values over serial without ”jumping” around and give me bad readings. Im stuck. :confused: that is, i need the code in this post above to work as discribed.

Try this. It will report the times for up to 16 pin changes.

//  Written for Atmega328P processor (UNO, Nano)
//
// We need all three input pins to be on the same hardware
// PORT so they all use the same Pin Change Interrupt
//
// Let's use Pins 5, 6, and 7 (all on PORTD)
//
const byte SignalAPin = 5; // PD5, PCINT21
const byte SignalBPin = 6; // PD6, PCINT22
const byte SignalCPin = 7; // PD7, PCINT23

#define SignalPortInputRegister PIND
#define SignalPortPCIMaskRegister PCMSK2
const byte SignalPortPCIFlag = _BV(PCIF2);
const byte SignalPortPCIEnable = _BV(PCIE2);

// This mapping of Arduino pins to PORT masks
// only works for PORTD.
// For PORTB, subtract 8 from the pin number.
// For PORTC (analog inputs), subtract A0 from the pin number
const byte SignalAPortMask = _BV(SignalAPin);
const byte SignalBPortMask = _BV(SignalBPin);
const byte SignalCPortMask = _BV(SignalCPin);

const byte MAX_INTERRUPTS = 16;
volatile byte InterruptCount = 0;
volatile uint16_t InterruptTime[MAX_INTERRUPTS];
volatile byte InterruptPins[MAX_INTERRUPTS];

void setup()
{
  Serial.begin(115200);
  delay(200);

  // Set the signal pins to inputs
  pinMode(SignalAPin, INPUT_PULLUP);
  pinMode(SignalBPin, INPUT_PULLUP);
  pinMode(SignalCPin, INPUT_PULLUP);

  // Enable pin change interrupts for each signal pin.
  SignalPortPCIMaskRegister |= SignalAPortMask;
  SignalPortPCIMaskRegister |= SignalBPortMask;
  SignalPortPCIMaskRegister |= SignalCPortMask;

  // Clear the interupt flag (Yes, by writing 1 to it.)
  PCIFR = SignalPortPCIFlag;

  // Enable the interrupt
  PCICR |= SignalPortPCIEnable;

  // Start Timer1 counting over and over at 16 MHz
  TCCR1A = 0;
  TCCR1B = 1;
}

ISR(PCINT2_vect)
{
  if (InterruptCount < MAX_INTERRUPTS)
  {
    InterruptTime[InterruptCount] = TCNT1;
    InterruptPins[InterruptCount] = SignalPortInputRegister;
    InterruptCount++;
  }
}

void loop()
{
  InterruptCount = 0;

  // Clear the interupt flag (Yes, by writing 1 to it.)
  PCIFR = SignalPortPCIFlag;

  // Enable the interrupt
  PCICR |= SignalPortPCIEnable;

  Serial.println(F("Ready for signal"));
  Serial.flush();

  while (InterruptCount == 0);
  delay(5);  // Allow 5 milliseconds for the rest of the signals

  // Disable the interrupt
  PCICR &= ~SignalPortPCIEnable;

  Serial.println(F("Signal complete"));

  uint16_t startTime = InterruptTime[0];
  
  for (byte i = 0; i < InterruptCount; i++)
  {
    Serial.print(F("At time "));
    Serial.print(InterruptTime[i] - startTime);
    Serial.print(F(" pins "));
    if (InterruptPins[i] & SignalAPortMask)
      Serial.print('A');
    if (InterruptPins[i] & SignalBPortMask)
      Serial.print('B');
    if (InterruptPins[i] & SignalCPortMask)
      Serial.print('C');
    Serial.println(F(" are on."));
  }
  Serial.println(F("Report done."));
}

Amazing.
Will try in a couple of days. Only have Mail and Phone Were i am.

I learn stuff every day though. :slight_smile:

In my world till now every pin on the same port on arduino share the same ISR, but just by reading this on my Phone u mean its possible to know what PIn caused the interrupt if more than 1 on same port?

There is one Pin Change Interrupt per PORT. You can select which pins on the port will cause an interrupt when they change. You can tell which pin(s) changed by looking at the difference between the pin states on the previous interrupt and the pin states on the current interrupt.

There is a Pin Change Interrupt library that lets you treat each pin as a separate RISING, FALLING, or CHANGE interrupt but since you need precise timing of the order of edges I chose to use the hardware directly rather than use the library. If you just need a bunch of interrupt pins, use the library.

Hardware seems to be exactly what this project needs.
I Very much appricciate your help.
If i in any way can help u back let me know by email. Gonna study ultrasonic sound for Quite a While now so im very much open for a way to pay for your microcontroller skill and time.

Vänligen

Nils Bruhner

Hello again. and since u r the xpert....just curious.
Whats the benefit of using the same vector at all interrupts?

My thoughts are that by NOT using different vectors, there is the other hand a new need for polling inside the ISR:s of what pin caused the interrupt ? wich also halt the exec time. No?

So the actual q must be...

  1. How much time is used to jump to a ISR on a different port than was reg on the last interrupt.
    and
  2. how much time is used to poll 1 single state on a pin pins inside the ISR,

From there i can draw my own conclusions...

Its a nerdy q but very interesting to me... ist eg about if Atmega328 is on the border to not fit a specific need..

The benefit of using the same port at all pins of interest is that if all of the pins of interest are in the same port they can all be read together as one byte. The pins in a port also share a Pin Change Interrupt.