How Fast Can We Measure?

The simplest way to find out how quickly we can take a measurement is to perform a pulse width measurement using a busy wait. Apply in square wave to GPIO 4 i.e. pin 7 we can measure the time that the pulse is high using:

#include <stdio.h>
#include <stdlib.h>
#include <bcm2835.h>
#include <sched.h>
#include <sys/mman.h>

int main(int argc, char** argv) {
    const struct sched_param priority = {1};
    sched_setscheduler(0, SCHED_FIFO, &priority);
    mlockall(MCL_CURRENT | MCL_FUTURE);
        if (!bcm2835_init())
        return 1;
  
    bcm2835_gpio_fsel(RPI_GPIO_P1_07, BCM2835_GPIO_FSEL_INPT);

    volatile int i;
    while (1) {
        while (1 == bcm2835_gpio_lev(RPI_GPIO_P1_07));
        while (0 == bcm2835_gpio_lev(RPI_GPIO_P1_07));
        for (i = 0; i < 5000; i++) {
            if (0 == bcm2835_gpio_lev(RPI_GPIO_P1_07)) break;
        }
        printf("%d\n\r", i); fflush(stdout);
    }

    return (EXIT_SUCCESS);
}

This might look a little strange at first.

The inner while loops are responsible for getting us to the right point in the waveform. First we loop until the line goes low, then we loop until it goes high again and finally measure how long before it goes low. You might think that we simply have to wait for it to go high and then measure how long till it goes low but this misses the possibility that the signal might be part way though a high period when we first measure it.

If you run this program with different pulse widths the result are very regular:

(y axis loop count x axis pulse time in microseconds)

The equation relating time t and loop count n is:

t=0.12 n -1.28 microseconds

This works down to about 1 microsecond or slightly less. 

If you want to record the time in microseconds rather than using a loop count then you could use:

  uint64_t t;
  volatile int i;
  while (1) {
     while (1 == bcm2835_gpio_lev(RPI_GPIO_P1_07));
     while (0 == bcm2835_gpio_lev(RPI_GPIO_P1_07));
     t= bcm2835_st_read();    
     for (i = 0; i < 5000; i++) {
       if (0 == bcm2835_gpio_lev(RPI_GPIO_P1_07)) break;
        }
     t= bcm2835_st_read()-t;
     printf("%d,%ld\n\r",i,t); 
     fflush(stdout);
 }

This is accurate to around can measure down to around 1 microsecond with an accuracy of 0.5 microseconds e.g.

Pulse width System Time
1 5
2.5 18.2
5 40
10 84
25 214
50 440

 

Notice that in either case if you try measuring pulse widths much shorter than the lower limit that works you will get results that look like longer pulses are being applied. The reason is simply that the Pi will miss the first transition to zero but will detect a second or third or later transition. This is the digital equivalent to of the aliasing effect found in the Fourier Transform or general signal processing. 

Interrupts Considered Harmful?

There is a general feeling that realtime programming and interrupts go together and if you are not using an interrupt you are probably doing something wrong. In fact the truth is that if you are using an interrupt you probably are doing something wrong. Some organizations agree with the general idea that interrupts are dangerous so much that they are banned from being used at all - and this includes realtime software. 

Interrupts are only really useful when you have a low frequency condition that needs to be dealt with on a high priority basis. Their use can simplify the logic of your program but rarely does using an interrupt speed things up because the overhead involved in interrupt handling is usually quite high. 

For example suppose you want to react to a doorbell push button. You could write a polling loop that simply checks the button status repeatedly and forever - or you could write an interrupt service routine ISR  to respond to the doorbell. The processor would be free to get on with other things until the doorbell was pushed when it would then stop what it was doing and transfer its attention to the ISR. 

How good a design this is depends on how much the doorbell press has to interact with the rest of the program and how many doorbell pushes you are expecting. It takes time to respond to the doorbell push and then the ISR has to run to completion - what is going to happen if another doorbell push happens while the first push is still being processed? Some processors have provision for forming a queue of interrupts but it doesn't help with the fact that the process can only handle on interrupt at a time. Of course the same is true of a polling loop but if you can't handle the throughput of events with a polling loop you can't handle it using an interrupt because interrupts add the time to transfer to the ISR and back again. 

Finally before you dismiss the idea of having a processor  do nothing but ask repeatedly "is the doorbell pressed" - what else has it to do?  

If the answer is "not much" then a polling loop might well be your simplest option.

Also if the processor has multiple cores then the fastest way of dealing with any external event is to use one of the cores in a polling loop.

Despite their attraction interrupts are usually a poor choice. 

Interrupts And The bcm2816 Library

The bcm2816 library doesn't support interrupts and for all the reasons given  

The reason is that Linux doesn't support user mode interrupts. However it is possible to make interrupts work in user mode and this is something explained at the end of this chapter but it involves using SYSFS.

The bcm2816 GPIO hardware has a very sophisticated and impressive list of conditions that you can set to trigger and event. These are all made available via the bcm2816 library as a set of set and clear functions.

There are four groups of functions that work with any of high, low, rising edge or falling edge detection:

High Detect

void bcm2835_gpio_hen(uint8_t pin)
void bcm2835_gpio_clr_hen(uint8_t pin)

Low Detect 

void bcm2835_gpio_len(uint8_t pin)
void bcm2835_gpio_clr_len(uint8_t pin)

Falling Edge Detect

void bcm2835_gpio_fen(uint8_t pin)
void bcm2835_gpio_clr_fen(uint8_t pin)

Rising Edge Detect

void bcm2835_gpio_ren(uint8_t pin)
void bcm2835_gpio_clr_ren(uint8_t pin)

It is obvious that the rising and falling events occur when the input changes from low to high and high to low respectively but when do the high and low level events occur. The answer is that the high or low level event is triggered as soon as the line is high or low - i.e. perhaps when you enable the event detection. The event also stays set as long as the high or low status persists - trying to reset it has no effect. In practice edge detection is usually the most useful. 

There are also two groups that work with asynchronous version of rising and falling edge detection. These are simply faster versions of the previous two:

Asynchronous Falling Edge Detect

void bcm2835_gpio_afen(uint8_t pin)
void bcm2835_gpio_clr_afen(uint8_t pin)

Asynchronous Rising Edge Detect

void bcm2835_gpio_aren(uint8_t pin)
void bcm2835_gpio_clr_aren(uint8_t pin)

It is worth explaining that the asynchronous versions may be faster but they are not "debounced". 

Each of these events, if set and if triggered set bits in an event detection register. The setting of these status bits can also be set to cause an interrupt but there is no way of directly handling such an interrupt in user mode. The functions that let you test the status bits are:

Return or clear a specific bit

uint8_t bcm2835_gpio_eds(uint8_t pin)
void bcm2835_gpio_set_eds(uint8_t pin)

Return or clear a set of bits using a mask

uint32_t bcm2835_gpio_eds_multi(uint32_t mask)
void bcm2835_gpio_set_eds_multi(uint32_t mask)

Notice that after an event has been detected you have to clear the status bit for the event to be detected again. As already mentioned clearing bits for high or low events only works if the level isn't currently high or low respectively.