Best CircularBuffer for an ISR

I know that there are many CircularBuffer posts already. But they mostly cover the concept and several basic ways of doing it, even some classes (what I would normally do). But in a ISR they are Very SLOW mostly requiring the use of multiple of memory accesses and non-cpu-supported division.

In a ISR (Interrupt Service Request) we need to be fast, very fast.

The best I could think of was to use a Buffer of size that's a power of 2

Then my index incrementor is

Buffer_Pos_Write=(Buffer_Pos_Write+1)&(BUFFSIZE-1);

BUFFSIZE being a Precompiler #Define, is just a constant.
a Constant - a constant (1), gcc should just replace with a constant

so in the case of BUFFSIZE = 8

The code should reduce to
Buffer_Pos_Write=(Buffer_Pos_Write+1)&(7);

Then to
Memory fetch for "Buffer_Pos_Write" (X Cycles)
Inc (1 cycle, all AVR devices)
ANDI (1 cycle, all AVR devices)
Memory write for "Buffer_Pos_Write" (X Cycles)

That gets me a Memory read, Memory write + 2 Cpu cycles, for a CircularBuffer incrementor.

My question to all you is, is there a better way? Maybe some way to keep a ISR variable in a register between requests... etc

That is the usual choice for many practical implementations.

1 Like

Don't forget, some code must be used, somewhere, to remove entries from the buffer at least as fast as they are being put into the buffer.

IMNSO, people underestimate:

if (++Buffer_Pos_Write > BUFFSIZE) {  Buffer_Pos_Write = 0; }

Fetch
Increment
Compare
conditional rewrite
Store.

The conditional (and maybe a second write) may add a cycle or two SOMETIMES, but you gain complete freedom of choice for BUFFSIZE.

Not

      if (++Buffer_Pos_Write >= BUFFSIZE) {  Buffer_Pos_Write = 0; }

?

a7

This noisy isr signal and circular buffer works fast enough for me, would be even faster with a clean isr signal ...

Hello dlloyd

Post the sketch in code tags for mobile users.

1 Like
/* 
Measuring a Noisy Signal using CHANGE Mode Interrupt
David Lloyd, May 2023.
*/

const uint8_t inputPin = 2;
const uint8_t bufferSize = 8;
const uint16_t stablePeriod = 120;
float bufferA[bufferSize], bufferB[bufferSize];
uint32_t pulsePeriod, pulseWidth, t00, t11, t22;
volatile uint32_t us, stableUs, t0, t1, t2; // isr
uint8_t inputLevel;
bool ready;
float hz, duty, rpm;

// functions
void input_ISR();
float smoothA(float val);
float smoothB(float val);
bool timer(uint32_t ms);

void setup() {
  Serial.begin(115200);
  pinMode(inputPin, INPUT); //Interrupt
  attachInterrupt(digitalPinToInterrupt(inputPin), input_ISR, CHANGE);
}

void loop() {
  if (timer(50)) {
    if (ready) {
      noInterrupts();
      t22 = t2;
      t11 = t1;
      t00 = t0;
      t2 = 0;
      t1 = 0;
      t0 = 0;
      ready = false;
      interrupts();

      pulsePeriod = t22 - t00;
      pulseWidth = t11 - t00;
      duty = 100.0 * pulseWidth / pulsePeriod;
      duty = smoothA(duty);
      hz = 1000000.0 / pulsePeriod;
      hz = smoothB(hz);
      rpm = hz * 60.0;
      Serial.print("  Duty = ");
      Serial.print(duty, 0);
      Serial.print("  Hz = ");
      Serial.print(hz, 0);
      Serial.print("  RPM = ");
      Serial.print(rpm, 0);
      Serial.println();
    }
  }
}

void input_ISR() {
  us = micros();
  if (us - stableUs > stablePeriod) {
    inputLevel = digitalRead(inputPin);
    if (inputLevel == HIGH) {
      if (!t0 && !t1 && !t2) t0 = us;
      else {
        if (t0 && t1 && !t2) {
          t2 = us;
          ready = true;
        }
      }
    } else {
      if (t0 && !t1 && !t2) t1 = us;
    }
  }
  stableUs = us;
}

float smoothA(float val) {
  float dataSum = 0.0;
  float dataAverage = 0.0;
  static uint8_t currentIndex = 0;
  bufferA[currentIndex] = val;
  currentIndex = (currentIndex + 1) % bufferSize;
  for (uint8_t j = 0; j < bufferSize; j++) dataSum += bufferA[j];
  return dataAverage = dataSum / (float)bufferSize;
}

float smoothB(float val) {
  float dataSum = 0.0;
  float dataAverage = 0.0;
  static uint8_t currentIndex = 0;
  bufferB[currentIndex] = val;
  currentIndex = (currentIndex + 1) % bufferSize;
  for (uint8_t j = 0; j < bufferSize; j++) dataSum += bufferB[j];
  return dataAverage = dataSum / (float)bufferSize;
}

bool timer(uint32_t ms) {
  volatile uint32_t prevMs, now;
  now = millis();
  if ((now - prevMs) >= ms) {
    prevMs = now;
    return true;
  }
  return false;
}

Note: The circular buffer isn't required for the isr function to work, it just gives the initial ramp-up of the return value and adds some averaging to provide a response that's closer to that of an analog guage.

I used the math operator X+1 instead of the C/C++ increment Operators ++X and X++, since both of those imply a immedate memory write, which I don't want to spend time doing, as I still need to apply the mask., after the increment

I'm running a ISR on the µs scale. where my relevant bit pulses are 600µs wide. so thus using the micros() timing function.

Yes. Being able to just add mask is the beautsy of using a power of two as the buffer size.

The other lines (methods) were offered for any buffer size, one was not quite right so I justposted what seemed to work in a test I did.

Using the modulo operator always seemed like overkill for something you can pretty well know is incremented past a point where it needs to be wrapped around.

a7

Actually no, my read is much slower, as I only check to read all buffer data every 500ms or so. As for me data packets are small-ish and far between. so as long as the buffer can hold it, reads don't need to be fast, that's why there's a buffer in the first place. Its the ISR than I need on the 4µs range.

Ok, but do NOT read the very last circular buffer entry as the interrupts may not be finished with it!

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.