serial.print vs interrupts priority

anbolge

Member
Dear Pjrc Forum,

Please kindly help with a dummy question.
I am counting 5mhz signal via digital interrupt pin at 600mhz on teensy 4.1. This process seems to work fine. I can even implement additional logic to do some math and also i can store the results to SD card mithout any major impact. However, what i can not seem to be able to get over is how to do a serial print so that it does not affect my count.
If i add one serial print like the one below (to communicate with nextion display) then i am loosing approx 10 counts (i.e. in 1 seconds teensy count 5mhz - 10 pulses). If i add 6 of them (to update various screen parameters) than it will be 60 (or even more).
I tried setting higher priority to the interrupt function as i found on this forum, however it does not seem to affect performance.
I tried increasing the speed of the chip, also did not have impact.
Next thing i could try is to half the signal frequency to 2,5mhz. But based on this forum "Counting interupt pulses on a teensy 4.1" even 2mhz signal seems to be affected by serial output.


Code:
#include <SoftwareSerial.h>

volatile long HZCount = 0;    
const byte Main_Pin = 34;                  //OCXO 5MHz
SoftwareSerial DisplaySerial(28,29);  // RX, TX

void setup() {
  DisplaySerial.begin(9600);
  pinMode(Main_Pin, INPUT_PULLUP);       //ocxo setup
  attachInterrupt(digitalPinToInterrupt(Main_Pin), HZ, RISING);
  // Set interrupt priority level to a higher value (lower priority)
  NVIC_SET_PRIORITY(IRQ_GPIO2_16_31, 0);
}

void HZ() {
  HZCount = HZCount + 1;
}
void loop() {
  char temp[12];
  if (HZCount > 5000000) {
    sprintf(temp, "%ld", HZCount);
    DisplaySerial.print(F("p2.x="));
    DisplaySerial.print(temp);
    DisplaySerial.print(F("\xFF\xFF\xFF"));
    HZCount = 0;
  }
}
 
Last edited:
The GPIO on T_4.x are moved from 'normal slow' ports to high speed during reset startup.

all of the GPIO - like GPIO2 fall under a single interrupt: IRQ_GPIO6789
perhaps: NVIC_SET_PRIORITY(IRQ_GPIO6789, 9);

Though the location of the "HZCount = 0;" leaves lots of time for the HZ() isr to fire and then have count(s) dropped.
This zero'ing isn't atomic and could still miss with added loop() code - but will close a big gap made by three .print()'s
Code:
  if (HZCount > 5000000) {
    long tmpHz = HZCount;
    HZCount = 0;
    sprintf(temp, "%ld", tmpHz);
    DisplaySerial.print(F("p2.x="));
    DisplaySerial.print(temp);
    DisplaySerial.print(F("\xFF\xFF\xFF"));
  }
 
Thanks for the reply @defragster. What i am actually doing is counting pulses to 5mil to know exactly when 1 second has passed. And in the example above what would happen is that the first iteration of the program will count correctly, however second iteration will not do it right, since it will be affected by the serial print below hzount loop..

Other alternatives could be to do something like addMemoryForWrite (not sure if it will do anything) or maybe use SerialTransfer.h
 
You might also want to use one of Hardware Serial's instead of using SoftwareSerial. Sure that won't help either. Note there are 8 Serial ports you can use if you have something attached.
 
You might also want to use one of Hardware Serial's instead of using SoftwareSerial. Sure that won't help either. Note there are 8 Serial ports you can use if you have something attached.
Skipped over 'SoftwareSerial' - but looking and of course pins 28/29 are native Serial7 that would work the same or perhaps better.

first iteration of the program will count correctly
Having the first correct would be expected - but with the handling as posted there will be some delay before the HZCount = 0; is done - and that could perhaps be where a 1/5,000,000 tick was missed with the intervening code as indicate p#2.
Does the suggested code in p#2 perform any better moving to Serial7 and doing the NVIC_SET_PRIORITY(IRQ_GPIO6789, 9);

Also 5 MHz _isr() is pretty taxing - cutting that down to 2.5 MHz or less would reduce the overhead.

Also note the 1062 in T_4.x's has a running active ARM_DWT_CYCCNT that increases with every clock cycle. It is a steady and easy to measure reference - though it doesn't actually seem to get to 600,000,000 before a second elapses - but if the count is calibrated to find the expected number it varies only somewhat from that with temp and other crystal changes. And the value is read in only about 3 cycles - allowing some time for testing or calibration without the overhead of 5M _isr()'s/sec.
 
What i am actually doing is counting pulses to 5mil to know exactly when 1 second has passed.
If that's the only purpose for counting the pulses, you're better off using one of the timer modules that can generate a single interrupt when the total is reached rather than 5 million individual interrupts.
 
SoftwareSerial - I am pretty sure on T4.x it detects if the pins passed in are HardwareSerial pins and if so uses the HardwareSerial code, with some
added overhead:
Code:
size_t SoftwareSerial::write(uint8_t b)
{
    elapsedMicros elapsed;
    uint32_t target;
    uint8_t mask;
    uint32_t begin_cycle;

    // use hardware serial, if possible
    if (port) return port->write(b);
...
It probably also brings in all 8 of the HardwareSerial objects into the code and data space...

You might be able to speed up the interrupt processing, although not sure how much that would help:
That is you could use that interrupt: IRQ_GPIO2_16_31
If you switch that pin back to slow mode:
Startup.c sets all of the pins to fast mode using:
Code:
// Use fast GPIO6, GPIO7, GPIO8, GPIO9
    IOMUXC_GPR_GPR26 = 0xFFFFFFFF;
    IOMUXC_GPR_GPR27 = 0xFFFFFFFF;
    IOMUXC_GPR_GPR28 = 0xFFFFFFFF;
    IOMUXC_GPR_GPR29 = 0xFFFFFFFF;
You could clear the appropriate bit in I believe IOMUXC_GPR_GPR27 to set your pin back to GPIO2.
You could then instead of using attachInterrupt() of your pin, you would use attachInterruptVector( IRQ_GPIO2_16_31...
You would have to do all of the stuff to change the registers to enable the interrupt on your pin, and you would need
to do what is necessary to clear the status in the ISR...

Which is a lot of downside. Upside is you don't have code that has to check the state of 4 GPIO ports to see which IRQs fired, that you have
configured to interrupt on...

I played around once doing this for teensy pin 0... Something like:
Code:
//---------------------------------------------
  // First lets setup pin 0 to slow mode and direct ISR...
  CCM_CCGR1 |= CCM_CCGR1_GPIO1(CCM_CCGR_ON);
  attachInterruptVector(IRQ_GPIO1_0_15, &pin_isr);
  NVIC_ENABLE_IRQ(IRQ_GPIO1_0_15);
  Serial.println("After Attach"); Serial.flush();
  // I think this will have GPIO1 handle its pin 3
  IOMUXC_GPR_GPR26 = 0xFFFFFFF7;
  Serial.println("After set IOMUXC"); Serial.flush();
  GPIO1_ICR1 = 0x00; // set to 0
  Serial.println("After set ICR1"); Serial.flush();
  GPIO1_GDIR &= ~0x08;  // Make sure set as input in GPIO1
  GPIO1_EDGE_SEL = 0x08; // set to 0
  GPIO1_ISR = 0xffff;
...
void pin_isr(void) {
  digitalWriteFast(ECHO_PIN, digitalReadFast(IRQ_PIN));
  irq_count++;
  GPIO1_ISR = 0x08; // clear the IRQ
  asm("dsb");
}
But that is specific to that pin...

At the time I was curious to play some more with this, especially on GPIO1 where there are individual interrupts defined for it pins 0-7
But on the T4.1 that only applies to pins 0 and 1 which are GPIO1 pins 3 and 2.
 
Thank you very much. No, NVIC_SET_PRIORITY(IRQ_GPIO6789, 9); does not do much either.

Probably the best way it to try a timer, where could I get some examples?
 
Thank you very much. No, NVIC_SET_PRIORITY(IRQ_GPIO6789, 9); does not do much either.

Probably the best way it to try a timer, where could I get some examples?
See the FreqCount library, which has an example for T4. This library only supports pulse counting on one pin, but you can also use the link below to a sketch that Paul wrote that supports pulse counting on many pins.

 
Back
Top