Raspberry Pi Variable Pulse Generator C Code Using libgpiod v2.2 With Timing Results!
Convert your Raspberry Pi to a Variable Speed Pulse Generator! This article was human reviewed and tested (the code was written by Grok 4). All internet articles should be explicit and transparent about their AI article state as responsible writers.
- We needed a bit-banger we could control from the keyboard, here it is.
- Please note this is for libgpiod v2.2+ the latest you may need to update your packages..
- We have included the new very-valuable prompt
Check your library before you ask Grok or an LLM to write the code - otherwise you will get possibly the wrong v1 API calls or etc.
For the following libraries write an application that toggles a gpio pins speed based on 'z' slow down 1%, x slow down .1% c slow down 1 us, v increase 1 us, b increase .1% n increase 1%, Ctrl-c graceful exit The user can pass the output gpio and the starting speed in us from the command line
Usage is as follows
- z : slow down 1%
- x: slow down .1 %
- c: slow down 1 us
- v: increase 1 us
- b: increase .1%
- n: increase 1%
- Ctrl-c graceful exit
./pulse_control 17 250000
# output gpio 17 at 250,000 us pulse durationInstall your Support Libraries: (note gpio is v2 API rated).
sudo apt install gpiod libgpiod-dev/*
* gpio_pulse_control.c
* Fixed for your libgpiod signature: gpiod_chip_request_lines(chip, req_cfg, line_cfg)
*/
#include <gpiod.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <termios.h>
#include <fcntl.h>
#include <signal.h>
#define CHIP_PATH "/dev/gpiochip0"
#define POLL_INTERVAL_NS 250000000LL
static struct gpiod_chip *chip = NULL;
static struct gpiod_line_request *req = NULL;
static struct termios orig_term;
static int gpio_offset;
static long pulse_duration_us = 500000;
static volatile sig_atomic_t running = 1;
static void cleanup(void)
{
if (req) gpiod_line_request_release(req);
if (chip) gpiod_chip_close(chip);
tcsetattr(STDIN_FILENO, TCSANOW, &orig_term);
printf("\nGPIO released. Terminal restored.\n");
}
static void signal_handler(int sig) { (void)sig; running = 0; }
static int read_key(void)
{
char ch;
return (read(STDIN_FILENO, &ch, 1) == 1) ? (unsigned char)ch : -1;
}
static void adjust_duration(int key)
{
switch (key) {
case 'z': pulse_duration_us = (long)(pulse_duration_us * 1.01 + 0.5); break;
case 'x': pulse_duration_us = (long)(pulse_duration_us * 1.001 + 0.5); break;
case 'c': pulse_duration_us += 1; break;
case 'v': if (pulse_duration_us > 1) pulse_duration_us -= 1; break;
case 'b': pulse_duration_us = (long)(pulse_duration_us * 0.999); break;
case 'n': pulse_duration_us = (long)(pulse_duration_us * 0.99); break;
default: return;
}
if (pulse_duration_us < 1) pulse_duration_us = 1;
printf("\rDuration: %6ld µs Freq: %.1f Hz ",
pulse_duration_us, 1000000.0 / (2.0 * pulse_duration_us));
fflush(stdout);
}
int main(int argc, char *argv[])
{
if (argc != 3) {
fprintf(stderr, "Usage: %s <gpio_offset> <initial_half_period_us>\n", argv[0]);
return 1;
}
gpio_offset = atoi(argv[1]);
pulse_duration_us = atol(argv[2]);
if (pulse_duration_us < 1) pulse_duration_us = 1;
chip = gpiod_chip_open(CHIP_PATH);
if (!chip) { perror("gpiod_chip_open"); return 1; }
struct gpiod_line_settings *settings = gpiod_line_settings_new();
gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_OUTPUT);
gpiod_line_settings_set_output_value(settings, 0);
struct gpiod_line_config *line_cfg = gpiod_line_config_new();
unsigned int offset_u = (unsigned int)gpio_offset;
gpiod_line_config_add_line_settings(line_cfg, &offset_u, 1, settings);
// Matches your header: req_cfg, then line_cfg (no consumer string)
req = gpiod_chip_request_lines(chip, NULL, line_cfg);
if (!req) {
perror("gpiod_chip_request_lines");
goto cleanup;
}
tcgetattr(STDIN_FILENO, &orig_term);
struct termios raw = orig_term;
raw.c_lflag &= ~(ECHO | ICANON);
tcsetattr(STDIN_FILENO, TCSANOW, &raw);
fcntl(STDIN_FILENO, F_SETFL, fcntl(STDIN_FILENO, F_GETFL) | O_NONBLOCK);
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
printf("Pulse generator on BCM %d\nInitial: %ld µs (%.1f Hz)\n",
gpio_offset, pulse_duration_us, 1000000.0 / (2.0 * pulse_duration_us));
printf("Keys: z/x/c slower v/b/n faster\n\n");
struct timespec last_poll = {0};
clock_gettime(CLOCK_MONOTONIC, &last_poll);
while (running) {
gpiod_line_request_set_value(req, 0, 1);
struct timespec high = {0, pulse_duration_us * 1000L};
nanosleep(&high, NULL);
gpiod_line_request_set_value(req, 0, 0);
struct timespec low = {0, pulse_duration_us * 1000L};
nanosleep(&low, NULL);
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
long long elapsed = (now.tv_sec - last_poll.tv_sec) * 1000000000LL +
(now.tv_nsec - last_poll.tv_nsec);
if (elapsed >= POLL_INTERVAL_NS) {
int key = read_key();
if (key != -1) adjust_duration(key);
last_poll = now;
}
}
cleanup:
cleanup();
return 0;
}Compilation & Usage
gcc -Wall -O2 -o gpio_pulse_control gpio_pulse_control.c -lgpiod
./gpio_pulse_control 17 100000Implementation Notes: (Author)
- We had to get Grok 4 to rewrite this multiple times, however this should work for libraries 2.2.
- Grok consistently did not pass the command-line parameter gpio-offset to the corresponding pin driver. So we had to manually change:
gpiod_line_request_set_value(req, 0, 1);
// to
gpiod_line_request_set_value(req, gpio_offset, 1);Output looks as:
Pulse generator on BCM 17
Initial: 100000 µs (5.0 Hz)
Keys: z/x/c slower v/b/n faster
Testing (Saelae Logic Generic Logic Analyzer to 24 Mhz Used)
- We noted in this article that pigpio(d) really misses timings - go read it, it's interesting.. Once we realized it was a overbuilt library that was opening port 8888 allowing for remote control, we went for a more simpler library
5.00 Hz (Very Good)
Pulse generator on BCM 14
Initial: 100000 µs (5.0 Hz)
Keys: z/x/c slower v/b/n faster
10.00 Hz (0.25ms out)
Pulse generator on BCM 14
Initial: 50000 µs (10.0 Hz)
Keys: z/x/c slower v/b/n faster
20.00 Hz (Returns 19.90 Hz 0.25 ms out)
Pulse generator on BCM 14
Initial: 25000 µs (20.0 Hz)
Keys: z/x/c slower v/b/n faster
40 Hz (Returns 39.583 Hz 0.26 ms out)
Pulse generator on BCM 14
Initial: 12500 µs (40.0 Hz)
Keys: z/x/c slower v/b/n faster
100 Hz (Returns 97.35 Hz)
Pulse generator on BCM 14
Initial: 5000 µs (100.0 Hz)
Keys: z/x/c slower v/b/n faster
250 Hz (Returns 228.3 Hz)
Pulse generator on BCM 14
Initial: 2000 µs (250.0 Hz)
Keys: z/x/c slower v/b/n faster
500 Hz (Returns 443.13Hz )
Pulse generator on BCM 14
Initial: 1000 µs (500.0 Hz)
Keys: z/x/c slower v/b/n fasterEtc...
Summary:
If you look at the code - it is a respectibly tight loop consider:
while (running) {
gpiod_line_request_set_value(req, gpio_offset, 1);
struct timespec high = {0, pulse_duration_us * 1000L};
nanosleep(&high, NULL);
gpiod_line_request_set_value(req, gpio_offset, 0);
struct timespec low = {0, pulse_duration_us * 1000L};
nanosleep(&low, NULL);
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
long long elapsed = (now.tv_sec - last_poll.tv_sec) * 1000000000LL +
(now.tv_nsec - last_poll.tv_nsec);
if (elapsed >= POLL_INTERVAL_NS) {
int key = read_key();
if (key != -1) adjust_duration(key);
last_poll = now;
}These tests were run on a 700 Mhz Raspberry Pi Zero, which incidentally has a universal 19.2 Mhz reference clock. Interrupts can be dimissed effectively as the pulses were clean and consistent.
- The general ideation is to simply manually tweak your us duration to get it as close as possible to a know reference.
