Pulse Width Measurement on beagle bone black

Hello Everyone,
I am new to beagle bone black and Linux. so please help me.
I want to Calculate Pulse width in microseconds. I want to measure pulse width approx 100 us. so how can I do that if anyone has a C code for that please help. I designed one code but it does not show proper output for microseconds of range. when I give it greater than 1 ms of pulse it shows fine output.

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <signal.h>
#include <sys/time.h>
#include <poll.h>

#define GPIO_PATH "/sys/class/gpio"
#define P8_9 "69"   // Change this to the GPIO pin you want to control

// Global variable for value_fd
int value_fd;

// Function declarations
void cleanup();

// Function to check if GPIO is already exported
int is_gpio_exported(const char *gpio_pin) {
    char path[255];
    sprintf(path, GPIO_PATH "/gpio%s", gpio_pin);
    return access(path, F_OK) != -1;
}

// Function to export a GPIO pin if not already exported
void export_gpio(const char *gpio_pin) {
    if (!is_gpio_exported(gpio_pin)) {
        FILE *export_file = fopen(GPIO_PATH "/export", "w");
        if (export_file == NULL) {
            perror("Error exporting GPIO");
            exit(EXIT_FAILURE);
        }
        fprintf(export_file, "%s", gpio_pin);
        fclose(export_file);
    }
}

// Function to set the direction of a GPIO pin
void set_gpio_direction(const char *gpio_pin, const char *direction) {
    char path[255];
    sprintf(path, GPIO_PATH "/gpio%s/direction", gpio_pin);
    FILE *direction_file = fopen(path, "w");
    if (direction_file == NULL) {
        perror("Error setting GPIO direction");
        exit(EXIT_FAILURE);
    }
    fprintf(direction_file, "%s", direction);
    fclose(direction_file);
}

// Function to set a GPIO pin high
void set_gpio_high(const char *gpio_pin) {
    char path[255];
    sprintf(path, GPIO_PATH "/gpio%s/value", gpio_pin);
    FILE *value_file = fopen(path, "w");
    if (value_file == NULL) {
        perror("Error setting GPIO high");
        exit(EXIT_FAILURE);
    }
    fprintf(value_file, "1");
    fclose(value_file);
}

// Function to set a GPIO pin low
void set_gpio_low(const char *gpio_pin) {
    char path[255];
    sprintf(path, GPIO_PATH "/gpio%s/value", gpio_pin);
    FILE *value_file = fopen(path, "w");
    if (value_file == NULL) {
        perror("Error setting GPIO low");
        exit(EXIT_FAILURE);
    }
    fprintf(value_file, "0");
    fclose(value_file);
}

// Function to unexport a GPIO pin
void unexport_gpio(const char *gpio_pin) {
    FILE *unexport_file = fopen(GPIO_PATH "/unexport", "w");
    if (unexport_file == NULL) {
        perror("Error unexporting GPIO");
        exit(EXIT_FAILURE);
    }
    fprintf(unexport_file, "%s", gpio_pin);
    fclose(unexport_file);
}

// Signal handler for SIGINT (Ctrl+C)
void signal_handler(int signo) {
    if (signo == SIGINT) {
        printf("\nReceived SIGINT. Cleaning up...\n");
        cleanup();
        exit(EXIT_SUCCESS);
    }
}

// Function to set GPIO low and unexport the pin
void cleanup() {
    set_gpio_low(P8_9);
    unexport_gpio(P8_9);
}


// Function to read the value of a GPIO pin
int read_gpio_value(const char *gpio_pin) {
    char path[255];
    sprintf(path, GPIO_PATH "/gpio%s/value", gpio_pin);
    FILE *value_file = fopen(path, "r");
    if (value_file == NULL) {
        perror("Error reading GPIO value");
        exit(EXIT_FAILURE);
    }
    int value;
    fscanf(value_file, "%d", &value);
    fclose(value_file);
    return value;
}

void measure_low_pulse_width(const char *gpio_pin) {
    struct timeval last_edge_time, current_time;

    // Open value file descriptor
    int value_fd = open(GPIO_PATH "/gpio" P8_9 "/value", O_RDONLY);
    if (value_fd == -1) {
        perror("Error opening value file");
        exit(EXIT_FAILURE);
    }

    // Register cleanup function
    if (atexit(cleanup) != 0) {
        perror("Error registering cleanup function");
        exit(EXIT_FAILURE);
    }

    while (1) {
        // Wait for low signal
        while (read_gpio_value(gpio_pin) == 1) {}

        // Measure low pulse width
        gettimeofday(&last_edge_time, NULL);

        // Wait for high signal
        while (read_gpio_value(gpio_pin) == 0) {}

        gettimeofday(&current_time, NULL);

        // Calculate and print pulse width in seconds
        double pulse_width = (current_time.tv_sec - last_edge_time.tv_sec) +
                             (current_time.tv_usec - last_edge_time.tv_usec) / 1.0e6;

        // Print only low pulse widths
        //if (pulse_width >= 0.5) {
           // printf("Low Pulse Width: %f seconds\n", pulse_width);
            printf("Low Pulse Width: %lf milliseconds\n", pulse_width * 1000);
           // printf("Low Pulse Width: %lf microseconds\n", pulse_width * 1.0e6);
       // }
    }
}


int main() {
    // Register the signal handler
    if (signal(SIGINT, signal_handler) == SIG_ERR) {
        perror("Error registering signal handler");
        exit(EXIT_FAILURE);
    }

    // Export the GPIO pin
    export_gpio(P8_9);

    // Set the GPIO direction to out
    set_gpio_direction(P8_9, "in");

    // Measure low pulse width
    measure_low_pulse_width(P8_9);

    return 0;
}

Might not be too many ā€œsimpleā€ solutions.

  1. Run the counter on the PRU core and process the data on the big core.
    2)Rapsberry pi pico, use that for the counter and send the data to your BBB via i2c.
  2. Using the sys/class interface you might not have much accuracy
  3. So much of this depends on your end use and how much time you want to put into coding it.
    Sorry, not what you are looking for, maybe the other community members on here will have a better solution.

Iā€™m quite sure that you can not get this resolution from linux user space on the humble 1Ghz (max) BBB.

Why did you post this to the ā€˜pruā€™ topic? That is the right track, using the pru which is NOT PREEMPTIVE, you can write an ā€˜asmā€™ program (or maybe a ā€˜cā€™ program) that will (easily) output a stable 100us duration pwmā€¦ What duty cycle are you thinking of implementing?

If this MUST be ā€˜cā€™, and it MUST be on the BBB, then I think someone is trying to steer you to the PRU.

if this doesnā€™t have to be ā€˜cā€™, but you only have a BBB, then Iā€™d do it with the pwm peripheral built in to the system. See the TRM - SPRUH73P ā€“ October 2011 ā€“ Revised March 2017 ā€” chapter 15

or modify the details of my post here: Help with Device Tree, Trying to expose kernel pps - #28 by gomer

(but that would be cheating if this is a school project, wouldnā€™t it?)

good luck,
gomer

OOOPSā€¦ since my reply, I have taken a closer look at your code and realize that you are not trying to generate a PWM signal, but that you are trying to measure an existing PWM signal. (my fault for responding too quickly).

HOWEVER, my suggestion for familiarizing yourself with Chapter 15 of the TRM still might be fruitful. ā€¦ in fact, if I was doing this in ASM on the PRU, I would use a higher resolution PWM signal as an application clock to assign the time to the signal under test.

in this post:

i describe a possible solution to a similar problem using a pwm signal as an application clock. If YOUR pwm signal is very high resolution, say 1us, this may be the best way to measure time between pulses of the signal under test.

good luck,
gomer

Thank you for your response.
but as I said I am a new user of beagle bone black so I donā€™t have any idea related to pru and other stuff. and I didnā€™t understand what are you saying about.
also, I donā€™t want to generate pwm signal but I want to measure the pulse width of the signal that I getting on the Gpio of the beagle bone.

yet you posted your question to the pru topicā€¦ hmmm

yes I understand that now, I acknowledged that in my edit here:

Iā€™m left a little confused, but if your code works on a signal at 1ms on a 1Ghz processor (pre emptive), my rough estimate is that you would need a 10Ghz processor to measure a 100us signal.

maybe a real time (RT) linux is where you should look, others may have experience in this arena, I do not.

good luck
gomer

You wonā€™t reach microseconds accuracy by low level code running on the ARM.

But itā€™s simple to reach your target by using hardware to count the pulse train. The AM335x CPU contains three PWM subsystems, each one has a CAP module, but only two lines are connected at the header pins.

The library libpruio makes it easy to read the CAP values. Find example C code in example pwm_cap. The CAP part reads at P9_42 a self generated pulse train and evaluates the frequency and the duty cycle. Itā€™s trivial to convert to ms.

Regards

1 Like

actually, I read that Pru is able to access Gpio directly so it can able to work fast that main processor. so I add this in Pru topic. and I donā€™t have idea about how use it in real time.

ā€œRealtimeā€ is not going to happen with linux unless you are measuring a pulse duration that is longer than the the cumulative latency of the system . You will need to design hardware using a FPGA to handle the actual pulse measurement then send that data to BBB at a rate that it can digest without missing any data sent.