Interrupt on Rising and Falling on the same pin

Status
Not open for further replies.

skpang

Well-known member
I've got a pulse coming in on pin 0 and trying to measure the pulse width.

I'm using ISR on the falling and rising edge of the same pin but it looks like the ISR is only calling on the rising edge.

Can I use attachInterrupt() twice on the same pin on both FALLING and RISING ?

Code:
int led = 13;
int rx_pin = 0;

void setup() 
{
  pinMode(led, OUTPUT);    
  pinMode(rx_pin, INPUT); 

  attachInterrupt(digitalPinToInterrupt(rx_pin),fallingISR,FALLING);
  attachInterrupt(digitalPinToInterrupt(rx_pin),risingISR,RISING);

  Serial.println("Ready..");
}

void loop() 
{
    
}


void fallingISR()
{
  Serial.println("fallingISR");
  digitalWrite(led, HIGH);  

}

void risingISR()
{
  Serial.println("risingISR");
  digitalWrite(led, LOW);  
  
}
 
No, you can't since only one single interrupt vector can be assigned at a time. But you could use "BOTH" and check for the pin voltage (i.e. with digitalReadFast()) within your interrupt handler to check if it was rising or falling.

Another strategy could be to change the interrupt attachment within the interrupt handlers themselves. For example attach RISING in setup to have a defined starting point, attach FALLING within risingISR() and RISING within fallingISR().
 
I've changed it to "CHANGE" and test the voltage inside the ISR. It worked !!

Thank you for your help.
 
Is the syntax for changing the interrupt from RISING to FALLING inside the ISR() just calling the attachInterrupt() again with a different mode? Are there benefits to this over a digitalReadFast() in terms of time?

Similar to OP, I'm trying to measure a pulse width of a start bit on a 2Mbps serial signal. A digitalReadFast() seems to add about 70ns of delay to the ISR execution that I'd like to minimize.
 
In reply to my own question, the function attachInterrupt(pin, isr, mode) can be called from inside an interrupt service routine to change the "mode" of an interrupt already attached to that pin. It is not necessary to call detachInterrupt(pin) first, although I saw no added delay on the scope from this function.

Adding dual ISRs for rising and falling eliminated a digitalReadFast() from the ISR and saved 20ns (12 clock cycles, Teensy 4.1) from the whole ISR.

Code:
int PinInt1 = 15;
int PinOut1 = 13;

void setup() {
  pinMode(PinInt1, INPUT); 
  pinMode(PinOut1, OUTPUT); 
  Serial.begin(115200); 
  attachInterrupt(digitalPinToInterrupt(PinInt1), isrServiceRising, RISING);
}

void loop() {
  delay(1000);
  Serial.println("Running");
}

void isrServiceRising()
{
  digitalWriteFast(PinOut1, 1);
  attachInterrupt(digitalPinToInterrupt(PinInt1), isrServiceFalling, FALLING);
}

void isrServiceFalling()
{
  digitalWriteFast(PinOut1, 0);
  attachInterrupt(digitalPinToInterrupt(PinInt1), isrServiceRising, RISING);
}

Overall this code example reads pin 15 and echoes the result on pin 13. Similar to OP, I'm trying to measure pulse width to determine a start bit on a serial stream.

Overall skew between input and output signals sits around 103ns. Don't see much additional optimization potential besides DMA (not sure if that helps for timing related stuff).
 
Don't see much additional optimization potential besides DMA

The pin interrupts can be slow because there is only one interrupt per port. Thus, the ISR needs to check which pins actually caused the interrupt. If you don't need other pin interrupts you might think of using the port interrupt directly. If you only have one pin activated you know that a port interrupt can only come from this pin. No need to further check for others. Might save a few ns.
 
The pin interrupts can be slow because there is only one interrupt per port. Thus, the ISR needs to check which pins actually caused the interrupt. If you don't need other pin interrupts you might think of using the port interrupt directly. If you only have one pin activated you know that a port interrupt can only come from this pin. No need to further check for others. Might save a few ns.

How would I check the port vs. just the pin? This is my only interrupt and I'd be interested in seeing how quick I can get it.
 
Just saw that there is only on interrupt for all ports in the fast port mode. Basically I would do (untested, just ideas, have a look at the attachInterrupt sources for detailes...)

Code:
void myISR()
{   
    // logic to reset the ISR flag goes here!!
   
    // do whatever needs to be done in your ISR
}

 void setup() {

     attachInterrupt(15, myISR, RISING);  // use the stock function to do all the installation
     attachInterruptVector(IRQ_GPIO6789, myISR);  // use your own, optimized ISR instead. 
 }

Anyway, it might be much better to not use interrupts at all but use one of the counters instead. You can program them to only count at certain conditions (e.g. a pin state). Since this is done in hardware it will be very precise. IIRC FreqMeasure uses this method: https://www.pjrc.com/teensy/td_libs_FreqMeasure.html. Maybe you can use it directly. If not you can probably copy the relevant code from the library.
 
Just saw that there is only on interrupt for all ports in the fast port mode. Basically I would do (untested, just ideas, have a look at the attachInterrupt sources for detailes...)

Code:
void myISR()
{   
    // logic to reset the ISR flag goes here!!
   
    // do whatever needs to be done in your ISR
}

 void setup() {

     attachInterrupt(15, myISR, RISING);  // use the stock function to do all the installation
     attachInterruptVector(IRQ_GPIO6789, myISR);  // use your own, optimized ISR instead. 
 }

Anyway, it might be much better to not use interrupts at all but use one of the counters instead. You can program them to only count at certain conditions (e.g. a pin state). Since this is done in hardware it will be very precise. IIRC FreqMeasure uses this method: https://www.pjrc.com/teensy/td_libs_FreqMeasure.html. Maybe you can use it directly. If not you can probably copy the relevant code from the library.

luni, thanks much for the feedback. I'm rather new with all this so sorry if any of the questions are something I should really know already. My project requires only a single state change interrupt on one pin. This is intercepting a 2Mbps burst serial transmission and manually parsing it to decode (the serial is manchester encoded, so a normal serial port won't work). I need my code to handle other stuff in the background, so I am trying to optimize my interrupt routines to get-in and get-out as fast as possible. Acceptable timing variation is in the 250ns range and my pin change ISR calls are chewing up 100ns of that time, not leaving much to actually work with.

How would I locate my port interrupt name? I read through some of the linked posts and couldn't identify where I would find that. Paul commented to someone that you check the kinetis.h file, but I only see that in the GitHub files for Teensy 3.x. I'm using a Teensy 4.1. Specifically, I'm trying to find the port name for pin 15.

What command resets the ISR flag? I couldn't find this information.

Thanks!
 
Specifically, I'm trying to find the port name for pin 15.
You can find a list here: https://github.com/luni64/TeensyTimerTool/wiki/Avoid-PWM-timer-clashes
Pin 15 is bit 19 on GPIO6.

I remember doing some interrupt based Manchester decoder a couple of years ago: https://forum.pjrc.com/threads/46854-manchester-decoding-teensy-3-2-with-uart. It kind of worked at 500kBaud but it really was borderline. As already mentioned, today I'd go the counter/capture route instead.

You will find information about the capability and API of the counters in the manual: https://www.pjrc.com/teensy/datasheets.html

I'd use one of the TMR timers (chapter 54 in the manual). It says:

54.7.4 Usage of the Capture Register
The capture register stores a copy of the counter's value when an input edge (positive,
negative, or both) is detected. After a capture event occurs, no further updating of the
capture register will occur until the SCTRL[IEF] (input edge flag) is cleared by writing a
zero to the SCTRL[IEF].

So, you should be able program the counter to copy its current value (i.e. a timestamp) to a capture register whenever it detects an edge on its input pin. Additionally it can trigger an interrupt after capturing the value.
In that interrupt routine you can simply copy the captured value to some array (ideally something like a ring buffer). You can then a-synchronically read out and analyze the timestamps from the ringbuffer. Accuracy would be 1/150MHz = 6.6ns

I can try to do some proof of principle code later today if this sounds all weird to you :)
 
Alright, I think I am following. Doing any calls directly to registers is somewhat new to me, but I think I understand where you are going. My signal is quite a bit faster than the ones in the example (2Mbps vs. 500kbps) and I think that's where I'm struggling a bit. Each instruction takes a significant and measurable amount of time compared to the signal I'm trying to read.

To summarize, my current method is to use cyclecounter (ARM_DWT_CYCCNT) values to measure the pulse width of the start bit, once identified I am use a periodic timer interrupt to read the signal 1/4 period before the state transition, per your post #9 on this forum (https://forum.pjrc.com/threads/46854-manchester-decoding-teensy-3-2-with-uart)

I think what you are suggesting is instead grabbing the cyclecounter at each rising or falling edge and doing math to compare the timing between the pulses. I think this is really elegant, but I am a bit unsure how to sync with the start bit using this method. If you were able to give a piece of example code, particularly just so I can see how you set up the capture timers and ring buffer, I'd really appreciate it. I have looked through the manual for this chip, but it is a bit like reading Egyptian hieroglyphics to me. I'll have to learn more. Again, I really appreciate your replies.

I spent a lot of time on this last night and using my method, I got the signal to sync and line up the read positions correctly. Instead of reading, I'm toggling a digital output so I can visualize it on my scope, but it should be a simple instruction swap. The periodic interrupt timer seems to give a very stable behavior once enabled, but I had to fudge with the first offset value to get everything to line up. Basically when I'm looking for the start bit, I arm the periodic interrupt timer on each rising edge. If a pulse which is too short (not a start bit) is measured, the periodic interrupt timer is cancelled before the ISR is called. If a start bit is found, the timer is allowed to trigger the ISR and begin a sequence of reads. After 21 cycles, an extra long timer interrupt is generated to delay past the end of the stop bit, then the sequence is disarmed.

In the picture below, the transitions of the blue lines represent the periodic ISR triggering. This is where I'd put a digitalReadFast(). The FRFR[1-3] measurement represents the sync between the sampling and the signal start bit, target value is 3.375us. This looks great.

image130.jpg

Unfortunately, if I put the scope in fast acquisition mode and turn on display persistence, I can see occasional blips where the sampling signal gets misaligned with the data. I've never gotten an honest capture of this on the scope, only the persistence.

image128.jpg

And the code I used to get to where I am. I am absolutely open to suggestions/optimizations. Every reduced instruction really makes a difference in the timing of this signal. The digitalWriteFast() calls will be replaced with a digitalReadFast().

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

int PinInt1 = 15;
int PinOut1 = 13;
volatile unsigned int clockCyclesRising = 0; //measure rising edge when looking for start bit
volatile unsigned int clockCyclesFalling = 0; //measure falling edge when looking for start bit
volatile unsigned int timerInterruptCount = 0; //count the number of samples completed in the periodic interrupt
unsigned int clockCyclesElapsed = 0; //calculated value of clock cycles
volatile bool intFlag = 0; //Flags when pulse has been captured by interrupts. Check if it is a start bit
volatile bool intPeriod = 0; //Flags when the periodic interrupt is running
IntervalTimer myTimer; 

void setup() {
  pinMode(PinInt1, INPUT);
  pinMode(PinOut1, OUTPUT);
  attachInterrupt(digitalPinToInterrupt(PinInt1), isrServiceRising, RISING);
  ARM_DEMCR |= ARM_DEMCR_TRCENA;
  ARM_DWT_CTRL |= ARM_DWT_CTRL_CYCCNTENA;
}

void loop() {
    if (intFlag == 1){
      clockCyclesElapsed = clockCyclesFalling - clockCyclesRising; 
      if ((clockCyclesElapsed <= 1575) || (clockCyclesElapsed >= 1585)) //Pulse too short or too long to be a start pulse
      {
          myTimer.end(); //disarm periodic timer interrupt before it can call the ISR
          intFlag = 0;
          attachInterrupt(digitalPinToInterrupt(PinInt1), isrServiceRising, RISING); //arm interrupt looking for start bit
      }
      else //start bit is found, do nothing, periodic interrupt is already armed and counting down
      {
          intFlag = 0;
          intPeriod = 1; //Flag for periodic interrupt
      }
    }
    if ((intPeriod == 1) && (timerInterruptCount >= 22)) //Once 21 samples have been taken + a delay, end the timer interrupt, arm the start bit interrupt for the next sequence
    {
       myTimer.end();
       delayNanoseconds(500);
       intPeriod = 0;
       timerInterruptCount = 0;
       digitalWriteFast(PinOut1, 0);
       attachInterrupt(digitalPinToInterrupt(PinInt1), isrServiceRising, RISING);
    }
}

void isrServiceRising()
{
    clockCyclesRising = ARM_DWT_CYCCNT;
    myTimer.begin(isrPeriodic, 2.2);  //Arm Timer Interrupt to occur fixed value after rising edge. This must be cancelled if pulse is not start pulse.
    myTimer.update(1.0); //sets the next pulses to be 1us
    attachInterrupt(digitalPinToInterrupt(PinInt1), isrServiceFalling, FALLING); 
}

void isrServiceFalling()
{
    clockCyclesFalling = ARM_DWT_CYCCNT;
    intFlag = 1; //Flag that a pulse has been measured and needs to be checked if it is a start bit
    detachInterrupt(digitalPinToInterrupt(PinInt1));
}

void isrPeriodic()
{
  timerInterruptCount++;
    if (timerInterruptCount <22)
    {
      digitalToggleFast(PinOut1);
    }
}
 
That looks quite good. If you want to go the interrupt way and want to optimize you should have a look at the low level code in attachInterrupt and the corresponding ISR as mentioned in #8 and #9. You find the corresponding code in interrupt.c. Information about the GPIOS can be found in chap. 12 of the reference manual.

I'm very interested to see how good the capture method works for this kind of application. I try to do some simple test code and post it here, but it might take a bit (busy times).
 
Finally captured an anomalous event on the scope of the code from the previous post. They are exceedingly rare. Only found one in a 40k capture sequence.

Not entirely sure why this breaks down.

image132.jpg
 
Finally captured an anomalous event on the scope of the code from the previous post. They are exceedingly rare. Only found one in a 40k capture sequence.

Not entirely sure why this breaks down.

View attachment 27634

I edited the code a bit to make it so the digitalToggleFast() only exists inside the periodic timer routine. From the looks of it, every once in a while, the periodic interrupt does not trigger with the right delay. Not sure if this is something I am doing wrong, a glitch in the processor, an overflow of the timer, some sort of other interrupt (the periodic interrupt is the only one I have intentionally enabled in my code), of something in the underlying code of the IntervalTimer class. It is repeatable. And while I can use the CRC to detect these bad readings, I'd rather resolve the issue. I'd like to try the timer capture method discussed above. I'll try to research and watch for any example code posted.
 
More scoping makes me certain that intervalTimer, even when using myTimer.priority(0); or NVIC_SET_PRIORITY(IRQ_PIT, 0); will occasionally have pulses delayed 25us or more. This is rare in terms of occurrence, but when timing periods of 1us, this happens several times per second. I am not sure what is causing this, as this simple test does not have serial communications or any other interrupts enabled when the intervalTimer is running.

I think it is time to go to the timer capture method, although it does bother me not understanding why this method falls apart in some cases.
 
I spent some time experimenting with the capture mode. Here some feasibility results which might give you a head start. The code measures the time between detected edges. It uses the capture capabilities of the TMR timers. Since the capture is done in hardware it doesn't matter if the edge interrupt is delayed a few ns. You will always read out the captured time. Of course you now need to reconstruct the sent bits from the recorded times, but this should not be too difficult. If you need to reduce the load generated by the ISR you can think of storing the captured values using DMA instead of the interrupt. Then it should run without significant load on the processor.

I used my manchester generator from the link in #10 and increased the clock frequency to 1MHz. For the test I send the letter A every 24µs. This generates the following waveform:
Screenshot 2022-02-24 201653.jpg

Theoretical times between edges are (µs)

3.5 1.0 1.0 1.0 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 1.0

The code measures the following times which look pretty good I'd say:

Code:
3.52 1.05 1.06 1.05 0.53 0.53 0.52 0.53 0.53 0.52 0.53 0.52 1.07 
3.52 1.05 1.06 1.05 0.53 0.53 0.52 0.53 0.53 0.52 0.53 0.52 1.07 
3.52 1.05 1.06 1.05 0.53 0.53 0.52 0.53 0.53 0.52 0.53 0.52 1.07 
3.52 1.05 1.06 1.05 0.53 0.53 0.52 0.53 0.53 0.52 0.53 0.52 1.07 
3.52 1.05 1.06 1.05 0.53 0.53 0.52 0.53 0.53 0.52 0.53 0.52 1.07 
3.52 1.05 1.06 1.05 0.53 0.53 0.52 0.53 0.53 0.52 0.53 0.52 1.07 
3.52 1.05 1.06 1.05 0.53 0.53 0.52 0.53 0.53 0.52 0.53 0.52 1.07 
3.52 1.05 1.06 1.05 0.53 0.53 0.52 0.53 0.52 0.53 0.53 0.52 1.07 
3.52 1.05 1.06 1.05 0.53 0.53 0.52 0.53 0.52 0.53 0.53 0.52 1.07 
3.51 1.05 1.07 1.05 0.53 0.52 0.53 0.53 0.52 0.53 0.53 0.52 1.07 
3.52 1.05 1.07 1.05 0.53 0.52 0.53 0.52 0.53 0.53 0.52 0.53 1.07 
3.52 1.05 1.07 1.05 0.53 0.52 0.53 0.52 0.53 0.53 0.52 0.53 1.06 
3.52 1.05 1.07 1.05 0.53 0.52 0.53 0.52 0.53 0.53 0.52 0.53 1.06 
3.52 1.05 1.07 1.05 0.53 0.52 0.53 0.52 0.53 0.53 0.52 0.53 1.06 
3.52 1.05 1.07 1.05 0.53 0.52 0.53 0.52 0.53 0.53 0.52 0.53 1.06 
3.52 1.05 1.07 1.05 0.53 0.52 0.53 0.52 0.53 0.53 0.52 0.53 1.06 
3.52 1.05 1.07 1.05 0.53 0.52 0.53 0.52 0.53 0.53 0.52 0.53 1.06 
3.52 1.05 1.07 1.05 0.52 0.53 0.53 0.52 0.53 0.53 0.52 0.53 1.06 
3.52 1.05 1.07 1.05 0.52 0.53 0.53 0.52 0.53 0.52 0.53 0.53 1.06 
3.52 1.05 1.07 1.05 0.52 0.53 0.53 0.52 0.53 0.52 0.53 0.53 1.06 
3.52 1.05 1.07 1.05 0.52 0.53 0.53 0.52 0.53 0.52 0.53 0.53 1.06 
3.52 1.05 1.07 1.05 0.52 0.53 0.53 0.52 0.53 0.52 0.53 0.53 1.06 
3.52 1.05 1.06 1.05 0.52 0.53 0.53 0.52 0.53 0.52 0.53 0.53 1.06 
3.52 1.05 1.06 1.05 0.52 0.53 0.53 0.52 0.53 0.52 0.53 0.53 1.06 
3.52 1.05 1.06 1.05 0.53 0.53 0.52 0.53 0.53 0.52 0.53 0.52 1.07 
3.52 1.05 1.06 1.05 0.53 0.53 0.52 0.53 0.53 0.52 0.53 0.52 1.07 
3.52 1.05 1.06 1.05 0.53 0.53 0.52 0.53 0.52 0.53 0.53 0.52 1.07 
3.52 1.05 1.06 1.05 0.53 0.53 0.52 0.53 0.52 0.53 0.53 0.52 1.07 
3.51 1.05 1.07 1.05 0.53 0.52 0.53 0.53 0.52 0.53 0.53 0.52 1.07 
3.51 1.05 1.07 1.05 0.53 0.52 0.53 0.53 0.52 0.53 0.53 0.52 1.07 
3.51 1.05 1.07 1.05 0.53 0.52 0.53 0.53 0.52 0.53 0.53 0.52 1.07


Here the receiver code:
Code:
#include "Arduino.h"

constexpr unsigned maxEdges = 14;
uint32_t  cap_index;
volatile uint16_t cap_vals[maxEdges];

IMXRT_TMR_CH_t& ch = IMXRT_TMR1.CH[2]; // TMR1_2 -> input pin 11

bool recording = false;

void onCapture()
{
    ch.SCTRL &= ~(TMR_SCTRL_IEF); // no need to check which flag was set since we only enabled IEF
    if (recording)
    {
        cap_vals[cap_index++] = ch.CAPT;  // store the captured value
        if (cap_index >= maxEdges)
        {
            recording = false;
            cap_index = 0;
        }
    }
    asm volatile("dsb"); // wait for clear  memory barrier
}

void initTimer()
{
    cap_vals[0] = 0;
    cap_index   = 0;

    *(portConfigRegister(11)) = 1;        // Alt1, use pin11 as input to TMR1_2
                                          //
    ch.CTRL  = 0;                         // stop timer
    ch.SCTRL = TMR_SCTRL_CAPTURE_MODE(3); // capture at rising and falling edges 
    ch.SCTRL |= TMR_SCTRL_IEFIE;          // enable input edge flag interrupt
    ch.LOAD = 0;

    attachInterruptVector(IRQ_QTIMER1, onCapture);
    NVIC_ENABLE_IRQ(IRQ_QTIMER1);

    ch.CTRL = TMR_CTRL_CM(1) | TMR_CTRL_PCS(8 + 0) | TMR_CTRL_SCS(2); // source: peripheral clock, prescaler 0, use counter 2 input pin for capture
}

void setup()
{   
    initTimer();
    recording = true;
}

void loop()
{
    if (!recording)
    {
        for (unsigned i = 0; i < maxEdges - 1; i++)
        {
            uint16_t x;
            if (cap_vals[i + 1] > cap_vals[i])
            {
                x = (cap_vals[i + 1] - cap_vals[i]);
            }
            else
            {
                x = (uint16_t)0xFFFF - (cap_vals[i] - cap_vals[i + 1]);
            }
            Serial.printf("%.2f ", 1E6 * x / F_BUS_ACTUAL);
        }
        Serial.println();
    }
    recording = true;
}

And, for the sake of completeness, the sender:

Code:
#include "Arduino.h"

void setup()
{
    constexpr unsigned dataPin  = 0;
    constexpr unsigned clockPin = 1; // clock output not necessary but useful for debugging

    pinMode(dataPin, OUTPUT);
    digitalWriteFast(dataPin, LOW);

    pinMode(clockPin, OUTPUT);
    digitalWriteFast(clockPin, LOW);

    char buf[100];

    // noInterrupts();  // need a clean signal here

    unsigned i = 0;

    while (1)
    {
        //snprintf(buf, 100, "Hello Manchester %u", i++);
        snprintf(buf, 100, "A");

        digitalWriteFast(dataPin, HIGH);
        delayNanoseconds(3500);
        digitalWriteFast(dataPin, LOW);
        delayNanoseconds(500);

        for (unsigned byteCnt = 0; byteCnt < strlen(buf); byteCnt++) // loop over chars in buffer
        {
            uint8_t current = buf[byteCnt];
            for (int bitCnt = 0; bitCnt < 8; bitCnt++) // loop over bits in current char
            {
                uint8_t isSet = (current & 0x80) != 0x80;  // get MSB
                digitalWriteFast(clockPin, HIGH);          // clock output not necessary but useful for debugging
                digitalWriteFast(dataPin, (isSet ^ HIGH)); // data XOR clock
                delayNanoseconds(500);                     // we want a ~1MHz clock

                current <<= 1;                            // prepare next bit
                digitalWriteFast(clockPin, LOW);          // clock
                digitalWriteFast(dataPin, (LOW ^ isSet)); // data XOR clock
                delayNanoseconds(500);
            }
        }
         digitalWriteFast(dataPin, LOW);
        delayMicroseconds(24);
    }
}
void loop() {}
 
Last edited:
I spent some time experimenting with the capture mode. Here some feasibility results which might give you a head start.

Thanks so much! I uploaded this and verified it works exactly as you programmed it. I am doing my due diligence to understand the code in its entirety, as well as adapt it to my situation (locate start bit, variable number of edges, etc.). I'll write back in a day or so once I've worked out the kinks or gotten stuck.

Thanks again for your time and help.
 
Thanks again for your time and help.

That was quite some fun. So: win-win :)
I played even further, made everything more usable and pushed the sources to GitHub: https://github.com/luni64/manchesterCapture in case someone is interested.

There are 2 classes

  1. An EdgeProvider class (edgeprovider.h & .cpp) which runs in the background and pushes all detected edge timestamps together with its type (up/down) into a ring-buffer (I used this one: https://github.com/Locoduino/RingBuffer). The EdgeProvider uses more or less the edge detection code shown above.
  2. A decoder class (decoder.h & .cpp) which is fed with the stored edges, decodes them and stores the decoded bytes in a result ring buffer. I used the algorithm from @victornpb's manch_decode repo: https://github.com/victornpb/manch_decode. However, I stripped everything down to the bare minimum.
    Note: the decoder needs to be ticked regularly. The ticking interval is not super critical since once ticked it processes all edges found in the edge buffer.

Usage is simple:
Code:
#include "Arduino.h"
#include "decoder.h"

Manchester::Decoder decoder;

void setup()
{
    pinMode(9, OUTPUT); // monitor time in ISR

    while (!Serial) {}
    Serial.println("start --------------");

    decoder.begin();
}

void loop()
{
    char buf[1000];
    size_t i = decoder.read(buf, 1000); // copy the decoded chars from the decoder to some buffer
    Serial.write(buf, i);               // write this buffer to Serial (or do whatever you need to do with it)
                                        //
    delay(100);                         // do something else, make sure you don't spend too much time otherwise the result buffer might overflow and you loose data
}

void yield()
{
    decoder.tick();  // tick the decoder from yield to get ticks even during delays and other time consuming tasks...
}

The sender sends a running number surrounded by '*' and a trailing \n. Clock 1MHz, delay between strings: 10µs.
The receiver sketch prints:
Code:
...
*23698*
*23699*
*23700*
*23701*
*23702*
*23703*
*23704*
*23705*
*23706*
*23707*
*23708*
*23709*
*23710*
*23711*
*23712*
*23713*
*23714*
*23715*
*23716*
*23717*
*23718*
*23719*
*23720*
*23721*
*23722*
*23723*
*23724*
*23725*
*23726*
*23727*
*23728*
*23729*
*23730*
*23731*
*23732*
*23733*
*23734*
*23735*
*23736*
*23737*
*23738*
*23739*
...
I didn't observe any errors. I also tried with much longer strings which also works flawlessly. Here the manchester stream together with an indicator showing the time spent in the edge-isr:

Screenshot 2022-02-26 215208.png

Zoomed in:

Screenshot 2022-02-26 215613.jpg

You see that the code eats up significant time in the edge ISR (~250ns per edge). This may or may not be a problem for the rest of your code since it requires a large part of the available processor power. It should be possible to push the timestamps using DMA which would reduce the processor load significantly. Would be interesting to see how fast one can go with this method...

Adapting the Decoder class to your custom encoding should be straight forward.
 
  1. An EdgeProvider class (edgeprovider.h & .cpp) which runs in the background and pushes all detected edge timestamps together with its type (up/down) into a ring-buffer (I used this one: https://github.com/Locoduino/RingBuffer). The EdgeProvider uses more or less the edge detection code shown above.
  2. A decoder class (decoder.h & .cpp) which is fed with the stored edges, decodes them and stores the decoded bytes in a result ring buffer. I used the algorithm from @victornpb's manch_decode repo: https://github.com/victornpb/manch_decode. However, I stripped everything down to the bare minimum.
    Note: the decoder needs to be ticked regularly. The ticking interval is not super critical since once ticked it processes all edges found in the edge buffer.

Wow, this is so awesome! :)
 
Very nice. You could shorten the ISR by just storing the capture value (and edge), and do the calculation of delta time between edges in the code that extracts values from the ring buffer, i.e. the decoder.

The existing FreqMeasureMulti library does a very nice job of reading and storing input capture values to a ring buffer, but I think only for FTM and not for QuadTimer. Your code provides a nice template for extending FreqMeasureMulti to also support QuadTimer.

I wonder if the simpler ring buffer in FreqMeasureMulti would be faster than the template-based RingBuffer library?
 
Wow, this is so awesome! :)
joepasquareillo said:
Very nice.
Thanks, much appreciated.

You could shorten the ISR by just storing the capture value (and edge), and do the calculation of delta time between edges in the code that extracts values from the ring buffer, i.e. the decoder.

Hm, not subtracting two integers on a superscalar 600MHz processor won't save much I assume. Having a more streamlined ring buffer might be more effective. On the other hand, the write to the buffer takes about 40ns only.

Please note: due to the mismatch in the clock frequencies of the core and the peripherals there is a lower limit to the execution time of an ISR. If it gets too short you need to wait until the reset of the ISR flag propagated to the timer module. I.e., if you leave the ISR too early you'll get double calls. So, instead of waiting for the flag one can as well do something useful :).

If I find some time I'll try a DMA based approach. Unfortunately this will loose the edge information (AFAIK one can only transfer the capture register). Are there any examples showing how to use the DMA ring buffer?
 
If I find some time I'll try a DMA based approach. Unfortunately this will loose the edge information (AFAIK one can only transfer the capture register).

Maybe DMA channel linking could be used, so the DMA transfer which collects the capture register triggers another DMA channel which collects a GPIO register only a short time later?


Are there any examples showing how to use the DMA ring buffer?

The audio library ADC input collects raw ADC measurements into a circular buffer which is a couple hundred samples larger than the amount the update function copies at regular intervals. The extra space is so that new incoming data won't overwrite while we're doing the copy, and to allow for some variability on the timing of when the update function grabs a new set of samples.
 
If I find some time I'll try a DMA based approach. Unfortunately this will loose the edge information (AFAIK one can only transfer the capture register). Are there any examples showing how to use the DMA ring buffer?

I hope this is not considered off-topic, but I've always wondered, for FreqMeasureMulti, if there was something specific in the details of the implementation of the ring buffer write/read that guarantees that it is "safe" to read from a ring buffer at non-ISR level when the ring buffer is being written from ISR level. In this new application, you are doing it via the RingBuffer library. Can you say what are the characteristics of the ring buffer implementation that guarantee this is safe?
 
Status
Not open for further replies.
Back
Top