diff --git a/Makefile b/Makefile index 59812ea..bc9fe7a 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ LIB = periphery.a -SRCS = src/gpio.c src/spi.c src/i2c.c src/mmio.c src/serial.c src/version.c +SRCS = src/gpio.c src/led.c src/spi.c src/i2c.c src/mmio.c src/serial.c src/version.c SRCDIR = src OBJDIR = obj diff --git a/README.md b/README.md index 3eb27ea..07a5684 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # c-periphery [![Build Status](https://travis-ci.org/vsergeev/c-periphery.svg?branch=master)](https://travis-ci.org/vsergeev/c-periphery) [![GitHub release](https://img.shields.io/github/release/vsergeev/c-periphery.svg?maxAge=7200)](https://github.com/vsergeev/c-periphery) [![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/vsergeev/c-periphery/blob/master/LICENSE) -## C Library for Linux Peripheral I/O (GPIO, SPI, I2C, MMIO, Serial) +## C Library for Linux Peripheral I/O (GPIO, LED, SPI, I2C, MMIO, Serial) -c-periphery is a small C library for GPIO, SPI, I2C, MMIO, and Serial peripheral I/O interface access in userspace Linux. c-periphery simplifies and consolidate the native Linux APIs to these interfaces. c-periphery is useful in embedded Linux environments (including Raspberry Pi, BeagleBone, etc. platforms) for interfacing with external peripherals. c-periphery is re-entrant, has no dependencies outside the standard C library and Linux, compiles into a static library for easy integration with other projects, and is MIT licensed. +c-periphery is a small C library for GPIO, LED, SPI, I2C, MMIO, and Serial peripheral I/O interface access in userspace Linux. c-periphery simplifies and consolidate the native Linux APIs to these interfaces. c-periphery is useful in embedded Linux environments (including Raspberry Pi, BeagleBone, etc. platforms) for interfacing with external peripherals. c-periphery is re-entrant, has no dependencies outside the standard C library and Linux, compiles into a static library for easy integration with other projects, and is MIT licensed. Using Python or Lua? Check out the [python-periphery](https://github.com/vsergeev/python-periphery) and [lua-periphery](https://github.com/vsergeev/lua-periphery) projects. @@ -60,6 +60,55 @@ int main(void) { [Go to GPIO documentation.](docs/gpio.md) +### LED + +``` c +#include +#include +#include + +#include "led.h" + +int main(void) { + led_t *led; + unsigned int max_brightness; + + led = led_new(); + + /* Open LED led0 */ + if (led_open(led, "led0") < 0) { + fprintf(stderr, "led_open(): %s\n", led_errmsg(led)); + exit(1); + } + + /* Turn on LED (set max brightness) */ + if (led_write(led, true) < 0) { + fprintf(stderr, "led_write(): %s\n", led_errmsg(led)); + exit(1); + } + + /* Get max brightness */ + if (led_get_max_brightness(led, &max_brightness) < 0) { + fprintf(stderr, "led_get_max_brightness(): %s\n", led_errmsg(led)); + exit(1); + } + + /* Set half brightness */ + if (led_set_brightness(led, max_brightness / 2) < 0) { + fprintf(stderr, "led_set_brightness(): %s\n", led_errmsg(led)); + exit(1); + } + + led_close(led); + + led_free(led); + + return 0; +} +``` + +[Go to LED documentation.](docs/led.md) + ### SPI ``` c @@ -327,7 +376,7 @@ $ ## Building c-periphery into another project -Include the header files in `src/` (e.g. `gpio.h`, `spi.h`, `i2c.h`, `mmio.h`, `serial.h`) and link in the `periphery.a` static library. +Include the header files in `src/` (e.g. `gpio.h`, `led.h`, `spi.h`, `i2c.h`, `mmio.h`, `serial.h`) and link in the `periphery.a` static library. ``` console $ gcc -I/path/to/periphery/src myprog.c /path/to/periphery/periphery.a -o myprog diff --git a/docs/led.md b/docs/led.md new file mode 100644 index 0000000..9970a61 --- /dev/null +++ b/docs/led.md @@ -0,0 +1,227 @@ +### NAME + +LED wrapper functions for Linux userspace sysfs LEDs. + +### SYNOPSIS + +``` c +#include + +/* Primary Functions */ +led_t *led_new(void); +int led_open(led_t *led, const char *name); +int led_read(led_t *led, bool *value); +int led_write(led_t *led, bool value); +int led_close(led_t *led); +void led_free(led_t *led); + +/* Getters */ +int led_get_brightness(led_t *led, unsigned int *brightness); +int led_get_max_brightness(led_t *led, unsigned int *max_brightness); + +/* Setters */ +int led_set_brightness(led_t *led, unsigned int brightness); + +/* Miscellaneous */ +int led_name(led_t *led, char *str, size_t len); +int led_tostring(led_t *led, char *str, size_t len); + +/* Error Handling */ +int led_errno(led_t *led); +const char *led_errmsg(led_t *led); +``` + +### DESCRIPTION + +``` c +led_t *led_new(void); +``` +Allocate an LED handle. + +Returns a valid handle on success, or NULL on failure. + +------ + +``` c +int led_open(led_t *led, const char *name); +``` +Open the sysfs LED with the specified name. + +`led` should be a valid pointer to an allocated LED handle structure. + +Returns 0 on success, or a negative [LED error code](#return-value) on failure. + +------ + +``` c +int led_read(led_t *led, bool *value); +``` +Read the state of the LED into `value`, where `true` is non-zero brightness, and `false` is zero brightness. + +`led` should be a valid pointer to an LED handle opened with `led_open()`. `value` should be a pointer to an allocated bool. + +Returns 0 on success, or a negative [LED error code](#return-value) on failure. + +------ + +``` c +int led_write(led_t *led, bool value); +``` +Write the state of the LED to `value`, where `true` is max brightness, and `false` is zero brightness. + +`led` should be a valid pointer to an LED handle opened with `led_open()`. + +Returns 0 on success, or a negative [LED error code](#return-value) on failure. + +------ + +``` c +int led_close(led_t *led); +``` +Close the LED. + +`led` should be a valid pointer to an LED handle opened with `led_open()`. + +Returns 0 on success, or a negative [LED error code](#return-value) on failure. + +------ + +``` c +void led_free(led_t *led); +``` +Free an LED handle. + +------ + +``` c +int led_get_brightness(led_t *led, unsigned int *brightness); +``` +Get the brightness of the LED. + +`led` should be a valid pointer to an LED handle opened with `led_open()`. + +Returns 0 on success, or a negative [LED error code](#return-value) on failure. + +------ + +``` c +int led_get_max_brightness(led_t *led, unsigned int *max_brightness); +``` +Get the max brightness of the LED. + +`led` should be a valid pointer to an LED handle opened with `led_open()`. + +Returns 0 on success, or a negative [LED error code](#return-value) on failure. + +------ + +``` c +int led_set_brightness(led_t *led, unsigned int brightness); +``` +Set the brightness of the LED. + +`led` should be a valid pointer to an LED handle opened with `led_open()`. + +Returns 0 on success, or a negative [LED error code](#return-value) on failure. + +------ + +``` c +int led_name(led_t *led, char *str, size_t len); +``` +Return the name of the sysfs LED. + +`led` should be a valid pointer to an LED handle opened with `led_open()`. + +Returns 0 on success, or a negative [LED error code](#return-value) on failure. + +------ + +``` c +int led_tostring(led_t *led, char *str, size_t len); +``` +Return a string representation of the LED handle. + +`led` should be a valid pointer to an LED handle opened with `led_open()`. + +This function behaves and returns like `snprintf()`. + +------ + +``` c +int led_errno(led_t *led); +``` +Return the libc errno of the last failure that occurred. + +`led` should be a valid pointer to an LED handle opened with `led_open()`. + +------ + +``` c +const char *led_errmsg(led_t *led); +``` +Return a human readable error message of the last failure that occurred. + +`led` should be a valid pointer to an LED handle opened with `led_open()`. + +### RETURN VALUE + +The periphery LED functions return 0 on success or one of the negative error codes below on failure. + +The libc errno of the failure in an underlying libc library call can be obtained with the `led_errno()` helper function. A human readable error message can be obtained with the `led_errmsg()` helper function. + +| Error Code | Description | +|-----------------------|-----------------------------------| +| `LED_ERROR_ARG` | Invalid arguments | +| `LED_ERROR_OPEN` | Opening LED | +| `LED_ERROR_QUERY` | Querying LED attributes | +| `LED_ERROR_IO` | Reading/writing LED brightness | +| `LED_ERROR_CLOSE` | Closing LED | + +### EXAMPLE + +``` c +#include +#include +#include + +#include "led.h" + +int main(void) { + led_t *led; + unsigned int max_brightness; + + led = led_new(); + + /* Open LED led0 */ + if (led_open(led, "led0") < 0) { + fprintf(stderr, "led_open(): %s\n", led_errmsg(led)); + exit(1); + } + + /* Turn on LED (set max brightness) */ + if (led_write(led, true) < 0) { + fprintf(stderr, "led_write(): %s\n", led_errmsg(led)); + exit(1); + } + + /* Get max brightness */ + if (led_get_max_brightness(led, &max_brightness) < 0) { + fprintf(stderr, "led_get_max_brightness(): %s\n", led_errmsg(led)); + exit(1); + } + + /* Set half brightness */ + if (led_set_brightness(led, max_brightness / 2) < 0) { + fprintf(stderr, "led_set_brightness(): %s\n", led_errmsg(led)); + exit(1); + } + + led_close(led); + + led_free(led); + + return 0; +} +``` + diff --git a/src/led.c b/src/led.c new file mode 100644 index 0000000..ed57902 --- /dev/null +++ b/src/led.c @@ -0,0 +1,215 @@ +/* + * c-periphery + * https://github.com/vsergeev/c-periphery + * License: MIT + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "led.h" + +#define P_PATH_MAX 256 + +struct led_handle { + char name[64]; + unsigned int max_brightness; + + struct { + int c_errno; + char errmsg[96]; + } error; +}; + +static int _led_error(led_t *led, int code, int c_errno, const char *fmt, ...) { + va_list ap; + + led->error.c_errno = c_errno; + + va_start(ap, fmt); + vsnprintf(led->error.errmsg, sizeof(led->error.errmsg), fmt, ap); + va_end(ap); + + /* Tack on strerror() and errno */ + if (c_errno) { + char buf[64]; + strerror_r(c_errno, buf, sizeof(buf)); + snprintf(led->error.errmsg+strlen(led->error.errmsg), sizeof(led->error.errmsg)-strlen(led->error.errmsg), ": %s [errno %d]", buf, c_errno); + } + + return code; +} + +led_t *led_new(void) { + led_t *led = calloc(1, sizeof(led_t)); + if (led == NULL) + return NULL; + + return led; +} + +int led_open(led_t *led, const char *name) { + char led_path[P_PATH_MAX]; + int fd, ret; + + snprintf(led_path, sizeof(led_path), "/sys/class/leds/%s/brightness", name); + + if ((fd = open(led_path, O_RDWR)) < 0) + return _led_error(led, LED_ERROR_OPEN, errno, "Opening LED: opening 'brightness'"); + + close(fd); + + strncpy(led->name, name, sizeof(led->name)); + + if ((ret = led_get_max_brightness(led, &led->max_brightness)) < 0) + return ret; + + return 0; +} + +int led_read(led_t *led, bool *value) { + int ret; + unsigned int brightness; + + if ((ret = led_get_brightness(led, &brightness)) < 0) + return ret; + + *value = brightness != 0; + + return 0; +} + +int led_write(led_t *led, bool value) { + return led_set_brightness(led, value ? led->max_brightness : 0); +} + +int led_close(led_t *led) { + return 0; +} + +void led_free(led_t *led) { + free(led); +} + +int led_get_brightness(led_t *led, unsigned int *brightness) { + char led_path[P_PATH_MAX]; + char buf[16]; + int fd, ret; + + snprintf(led_path, sizeof(led_path), "/sys/class/leds/%s/brightness", led->name); + + if ((fd = open(led_path, O_RDONLY)) < 0) + return _led_error(led, LED_ERROR_IO, errno, "Opening LED 'brightness'"); + + if ((ret = read(fd, buf, sizeof(buf))) < 0) { + int errsv = errno; + close(fd); + return _led_error(led, LED_ERROR_IO, errsv, "Reading LED 'brightness'"); + } + + if (close(fd) < 0) + return _led_error(led, LED_ERROR_IO, errno, "Closing LED 'brightness'"); + + /* Null-terminate over newline */ + buf[ret] = '\0'; + + *brightness = strtoul(buf, NULL, 10); + + return 0; +} + +int led_get_max_brightness(led_t *led, unsigned int *max_brightness) { + char led_path[P_PATH_MAX]; + char buf[16]; + int fd, ret; + + snprintf(led_path, sizeof(led_path), "/sys/class/leds/%s/max_brightness", led->name); + + if ((fd = open(led_path, O_RDONLY)) < 0) + return _led_error(led, LED_ERROR_QUERY, errno, "Opening LED 'max_brightness'"); + + if ((ret = read(fd, buf, sizeof(buf))) < 0) { + int errsv = errno; + close(fd); + return _led_error(led, LED_ERROR_QUERY, errsv, "Reading LED 'max_brightness'"); + } + + if (close(fd) < 0) + return _led_error(led, LED_ERROR_QUERY, errno, "Closing LED 'max_brightness'"); + + /* Null-terminate over newline */ + buf[ret] = '\0'; + + led->max_brightness = strtoul(buf, NULL, 10); + + *max_brightness = led->max_brightness; + + return 0; +} + +int led_set_brightness(led_t *led, unsigned int brightness) { + char led_path[P_PATH_MAX]; + char buf[16]; + int fd, len; + + if (brightness > led->max_brightness) + return _led_error(led, LED_ERROR_ARG, 0, "Brightness out of bounds (max is %u)", led->max_brightness); + + snprintf(led_path, sizeof(led_path), "/sys/class/leds/%s/brightness", led->name); + + if ((fd = open(led_path, O_WRONLY)) < 0) + return _led_error(led, LED_ERROR_IO, errno, "Opening LED 'brightness'"); + + len = snprintf(buf, sizeof(buf), "%u\n", brightness); + + if (write(fd, buf, len) < 0) { + int errsv = errno; + close(fd); + return _led_error(led, LED_ERROR_IO, errsv, "Writing LED 'brightness'"); + } + + if (close(fd) < 0) + return _led_error(led, LED_ERROR_IO, errno, "Closing LED 'brightness'"); + + return 0; +} + +int led_name(led_t *led, char *str, size_t len) { + strncpy(str, led->name, len); + return 0; +} + +int led_tostring(led_t *led, char *str, size_t len) { + unsigned int brightness; + char brightness_str[16]; + unsigned int max_brightness; + char max_brightness_str[16]; + + if (led_get_brightness(led, &brightness) < 0) + strncpy(brightness_str, "", sizeof(brightness_str)); + else + snprintf(brightness_str, sizeof(brightness_str), "%u", brightness); + + if (led_get_max_brightness(led, &max_brightness) < 0) + strncpy(max_brightness_str, "", sizeof(max_brightness_str)); + else + snprintf(max_brightness_str, sizeof(max_brightness_str), "%u", max_brightness); + + return snprintf(str, len, "LED %s (brightness=%s, max_brightness=%s)", led->name, brightness_str, max_brightness_str); +} + +int led_errno(led_t *led) { + return led->error.c_errno; +} + +const char *led_errmsg(led_t *led) { + return led->error.errmsg; +} diff --git a/src/led.h b/src/led.h new file mode 100644 index 0000000..f0bd8e7 --- /dev/null +++ b/src/led.h @@ -0,0 +1,56 @@ +/* + * c-periphery + * https://github.com/vsergeev/c-periphery + * License: MIT + */ + +#ifndef _PERIPHERY_LED_H +#define _PERIPHERY_LED_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +enum led_error_code { + LED_ERROR_ARG = -1, /* Invalid arguments */ + LED_ERROR_OPEN = -2, /* Opening LED */ + LED_ERROR_QUERY = -3, /* Querying LED attributes */ + LED_ERROR_IO = -4, /* Reading/writing LED brightness */ + LED_ERROR_CLOSE = -5, /* Closing LED */ +}; + +typedef struct led_handle led_t; + +/* Primary Functions */ +led_t *led_new(void); +int led_open(led_t *led, const char *name); +int led_read(led_t *led, bool *value); +int led_write(led_t *led, bool value); +int led_close(led_t *led); +void led_free(led_t *led); + +/* Getters */ +int led_get_brightness(led_t *led, unsigned int *brightness); +int led_get_max_brightness(led_t *led, unsigned int *max_brightness); + +/* Setters */ +int led_set_brightness(led_t *led, unsigned int brightness); + +/* Miscellaneous */ +int led_name(led_t *led, char *str, size_t len); +int led_tostring(led_t *led, char *str, size_t len); + +/* Error Handling */ +int led_errno(led_t *led); +const char *led_errmsg(led_t *led); + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/tests/test_led.c b/tests/test_led.c new file mode 100644 index 0000000..4ccdc4d --- /dev/null +++ b/tests/test_led.c @@ -0,0 +1,174 @@ +/* + * c-periphery + * https://github.com/vsergeev/c-periphery + * License: MIT + */ + +#include "test.h" + +#include +#include +#include +#include + +#include "../src/led.h" + +const char *device; + +void test_arguments(void) { + ptest(); + + /* No real argument validation needed in the LED wrapper */ +} + +void test_open_config_close(void) { + led_t *led; + char name[16]; + unsigned int max_brightness; + unsigned int brightness; + bool value; + + ptest(); + + /* Allocate LED */ + led = led_new(); + passert(led != NULL); + + /* Open non-existent LED */ + passert(led_open(led, "nonexistent") == LED_ERROR_OPEN); + + /* Open legitimate LED */ + passert(led_open(led, device) == 0); + + /* Check properties */ + passert(led_name(led, name, sizeof(name)) == 0); + passert(strcmp(name, device) == 0); + + /* Check max brightness */ + passert(led_get_max_brightness(led, &max_brightness) == 0); + passert(max_brightness > 0); + + /* Check setting invalid brightness */ + passert(led_set_brightness(led, max_brightness + 1) == LED_ERROR_ARG); + + /* Write true, read true, check brightness is max */ + passert(led_write(led, true) == 0); + usleep(10000); + passert(led_read(led, &value) == 0); + passert(value == true); + passert(led_get_brightness(led, &brightness) == 0); + passert(brightness == max_brightness); + + /* Write false, read false, check brightness is zero */ + passert(led_write(led, false) == 0); + usleep(10000); + passert(led_read(led, &value) == 0); + passert(value == false); + passert(led_get_brightness(led, &brightness) == 0); + passert(brightness == 0); + + /* Set brightness to 1, check brightness */ + passert(led_set_brightness(led, 1) == 0); + usleep(10000); + passert(led_get_brightness(led, &brightness) == 0); + passert(brightness >= 1); + + /* Set brightness to 0, check brightness */ + passert(led_set_brightness(led, 0) == 0); + usleep(10000); + passert(led_get_brightness(led, &brightness) == 0); + passert(brightness == 0); + + passert(led_close(led) == 0); + + /* Free LED */ + led_free(led); +} + +void test_loopback(void) { + ptest(); +} + +bool getc_yes(void) { + char buf[4]; + fgets(buf, sizeof(buf), stdin); + return (buf[0] == 'y' || buf[0] == 'Y'); +} + +void test_interactive(void) { + char str[256]; + led_t *led; + + ptest(); + + /* Allocate LED */ + led = led_new(); + passert(led != NULL); + + passert(led_open(led, device) == 0); + + printf("Starting interactive test...\n"); + printf("Press enter to continue...\n"); + getc(stdin); + + /* Check tostring */ + passert(led_tostring(led, str, sizeof(str)) > 0); + printf("LED description: %s\n", str); + printf("LED description looks OK? y/n\n"); + passert(getc_yes()); + + /* Turn LED off */ + passert(led_write(led, false) == 0); + printf("LED is off? y/n\n"); + passert(getc_yes()); + + /* Turn LED on */ + passert(led_write(led, true) == 0); + printf("LED is on? y/n\n"); + passert(getc_yes()); + + /* Turn LED off */ + passert(led_write(led, false) == 0); + printf("LED is off? y/n\n"); + passert(getc_yes()); + + /* Turn LED on */ + passert(led_write(led, true) == 0); + printf("LED is on? y/n\n"); + passert(getc_yes()); + + passert(led_close(led) == 0); + + /* Free LED */ + led_free(led); +} + +int main(int argc, char *argv[]) { + if (argc < 2) { + fprintf(stderr, "Usage: %s \n\n", argv[0]); + fprintf(stderr, "[1/4] Arguments test: No requirements.\n"); + fprintf(stderr, "[2/4] Open/close test: LED should be real.\n"); + fprintf(stderr, "[3/4] Loopback test: No test.\n"); + fprintf(stderr, "[4/4] Interactive test: LED should be observed.\n\n"); + fprintf(stderr, "Hint: for Raspberry Pi 3, disable triggers for led1:\n"); + fprintf(stderr, " $ echo none > /sys/class/leds/led1/trigger\n"); + fprintf(stderr, "Observe led1 (red power LED), and run this test:\n"); + fprintf(stderr, " %s led1\n\n", argv[0]); + exit(1); + } + + device = argv[1]; + + test_arguments(); + printf(" " STR_OK " Arguments test passed.\n\n"); + test_open_config_close(); + printf(" " STR_OK " Open/close test passed.\n\n"); + test_loopback(); + printf(" " STR_OK " Loopback test passed.\n\n"); + test_interactive(); + printf(" " STR_OK " Interactive test passed.\n\n"); + + printf("All tests passed!\n"); + return 0; +} +