C-Periphery: Simplifying Linux Embedded Peripheral Device Operations!

Introduction

Have you ever encountered difficulties in Linux embedded development when trying to directly manipulate hardware interfaces such as GPIO, LED, PWM, SPI, I2C, MMIO, Serial, etc., only to find that the native APIs are complex, cumbersome, and hard to maintain? Or do you find yourself writing a lot of low-level code every time, making debugging particularly troublesome? If these issues resonate with you, then you must know about the C library called c-periphery, which can help you easily handle these hardware interfaces!

Today, I will provide a detailed introduction to this “magic tool”—c-periphery. What is it? What pain points does it solve? How do you use it? What are its pros and cons? Let’s explore!

In simple terms, c-periphery is a small C library designed specifically for Linux embedded platforms (such as Raspberry Pi, BeagleBone, NanoPi, etc.) to operate various hardware peripheral interfaces. It encapsulates the low-level interfaces of Linux, allowing you to easily control devices like GPIO, LED, PWM, SPI, I2C, MMIO, Serial, etc., with just a few lines of code.

Unlike those complex low-level drivers, you don’t need to write cumbersome IOCTL calls or deal with device files. It wraps all of these into simple APIs, allowing you to focus on your logical development.

In short, c-periphery is your hardware “butler” toolbox, making operations on these hardware interfaces as simple as building blocks.

What Pain Points Does It Solve?

  1. 1. Complex Low-Level APIs: The GPIO, SPI, I2C, and other interfaces in Linux are very low-level, with cumbersome APIs that are hard to get started with quickly. After encapsulation, c-periphery offers clear and concise APIs.
  2. 2. Poor Cross-Platform Compatibility: The low-level differences between different hardware platforms are significant. With c-periphery, you hardly need to worry about these differences; just call the unified interface.
  3. 3. Many Dependencies: There are no other complex dependencies; it only relies on the standard C library and Linux, making it lightweight and easy to integrate.
  4. 4. Poor Usability: Without encapsulated APIs, writing low-level code can easily lead to errors, making debugging frustrating. c-periphery provides friendly error messages, simplifying debugging.
  5. 5. Multi-Interface Support: A single set of APIs can handle multiple hardware interfaces, saving development time.
  6. 6. Multi-Language Extensions: In addition to C, there are encapsulated versions for Python, Lua, Java, Dart, etc., to meet various development needs.

What Can You Do With It? Practical Examples!

GPIO (Pin Control)

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include "gpio.h"

int main() {
    gpio_t* gpio_in = gpio_new();
    gpio_t* gpio_out = gpio_new();

    // Open GPIO pin, line number 10, set as input
    if (gpio_open(gpio_in, "/dev/gpiochip0", 10, GPIO_DIR_IN) < 0) {
        fprintf(stderr, "Failed to open input GPIO: %s\n", gpio_errmsg(gpio_in));
        return 1;
    }

    // Open GPIO line, line number 12, set as output
    if (gpio_open(gpio_out, "/dev/gpiochip0", 12, GPIO_DIR_OUT) < 0) {
        fprintf(stderr, "Failed to open output GPIO: %s\n", gpio_errmsg(gpio_out));
        return 1;
    }

    bool val;
    if (gpio_read(gpio_in, &val) < 0) {
        fprintf(stderr, "Failed to read GPIO: %s\n", gpio_errmsg(gpio_in));
        return 1;
    }
    printf("Read input value: %d\n", val);

    // Write the inverse to the output pin
    if (gpio_write(gpio_out, !val) < 0) {
        fprintf(stderr, "Failed to write GPIO: %s\n", gpio_errmsg(gpio_out));
        return 1;
    }

    gpio_close(gpio_in);
    gpio_close(gpio_out);
    gpio_free(gpio_in);
    gpio_free(gpio_out);
    return 0;
}

LED Control

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include "led.h"

int main() {
    led_t* led = led_new();
    if (led_open(led, "led0") < 0) {
        fprintf(stderr, "Failed to open LED: %s\n", led_errmsg(led));
        return 1;
    }

    // Turn on LED
    led_write(led, true);
    unsigned int max_brightness;
    led_get_max_brightness(led, &max_brightness);
    printf("Max brightness: %u\n", max_brightness);

    // Set to half brightness
    led_set_brightness(led, max_brightness / 2);
    led_close(led);
    led_free(led);
    return 0;
}

PWM Speed Control

#include <stdio.h>
#include <stdlib.h>
#include "pwm.h"

int main() {
    pwm_t *pwm = pwm_new();

    if (pwm_open(pwm, 0, 10) < 0) {
        fprintf(stderr, "Failed to open PWM: %s\n", pwm_errmsg(pwm));
        return 1;
    }

    pwm_set_frequency(pwm, 1000); // 1kHz
    pwm_set_duty_cycle(pwm, 0.75); // 75%
    pwm_enable(pwm);

    // Adjust duty cycle
    pwm_set_duty_cycle(pwm, 0.5); // 50%
    pwm_close(pwm);
    pwm_free(pwm);
    return 0;
}

SPI Communication

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include "spi.h"

int main() {
    spi_t* spi = spi_new();
    uint8_t buf[4] = {0xAA, 0xBB, 0xCC, 0xDD};

    if (spi_open(spi, "/dev/spidev1.0", 0, 1000000) < 0) {
        fprintf(stderr, "Failed to open SPI: %s\n", spi_errmsg(spi));
        return 1;
    }

    if (spi_transfer(spi, buf, buf, sizeof(buf)) < 0) {
        fprintf(stderr, "SPI transfer failed: %s\n", spi_errmsg(spi));
        return 1;
    }

    printf("Returned data: %02X %02X %02X %02X\n", buf[0], buf[1], buf[2], buf[3]);
    spi_close(spi);
    spi_free(spi);
    return 0;
}

These examples are just the tip of the iceberg; c-periphery also supports I2C, MMIO, Serial, and other interfaces to meet your various hardware needs.

Pros and Cons Revealed

Advantages:

  • Lightweight: Only standard C dependencies, small package size, fast execution
  • Well-Encapsulated: Clear API design, simple operations
  • Cross-Platform: Supports multiple hardware platforms
  • No Dependencies: No additional dependencies, easy to integrate
  • Multi-Language Support: Encapsulated versions available for Python, Lua, Java, Dart
  • Open Source MIT License: Freely usable, no pressure for secondary development

Disadvantages:

  • Relatively Basic Functionality: Lacks advanced features like professional drivers
  • Performance May Be Limited: For scenarios with extreme performance requirements, low-level APIs may be better
  • Only Applicable in User Space: Cannot directly manipulate kernel drivers, limited to user space

Conclusion

If you are looking for a “user-friendly, simple, and well-encapsulated” hardware interface library for embedded Linux development, c-periphery is definitely a great choice! It helps you encapsulate cumbersome low-level operations into simple APIs, allowing you to focus on developing logic and saving a lot of debugging time.

It supports various peripheral interfaces, is easy to integrate, has no dependencies, and is suitable for various hardware platforms, especially for developers of single-board computers like Raspberry Pi and BeagleBone. Whether you are working on IoT, robotics, or various hardware projects, c-periphery can help you achieve more with less effort.

In short, do you want to make Linux hardware operations so easy? Try c-periphery!

If you want to learn more, you can visit the project address: https://github.com/vsergeev/c-periphery

Leave a Comment