├── .gitignore ├── src ├── libperiphery.pc.in ├── version.c ├── version.h ├── led.h ├── i2c.h ├── mmio.h ├── pwm.h ├── spi.h ├── serial.h ├── i2c.c ├── gpio_internal.h ├── gpio.h ├── gpio.c ├── mmio.c ├── led.c └── pwm.c ├── .github └── workflows │ ├── build.yml │ └── buildroot.yml ├── tests ├── test.h ├── test_i2c.c ├── test_led.c ├── test_mmio.c ├── test_spi.c ├── test_pwm.c ├── test_gpio_sysfs.c └── test_serial.c ├── LICENSE ├── Makefile ├── CMakeLists.txt ├── docs ├── i2c.md ├── led.md ├── mmio.md ├── pwm.md ├── spi.md └── serial.md ├── CHANGELOG.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | obj/ 3 | *.a 4 | *.so* 5 | *.sh 6 | tests/* 7 | !tests/*.c 8 | !tests/*.h 9 | /*build/* 10 | -------------------------------------------------------------------------------- /src/libperiphery.pc.in: -------------------------------------------------------------------------------- 1 | prefix=@CMAKE_INSTALL_PREFIX@ 2 | exec_prefix=${prefix} 3 | libdir=${exec_prefix}/@CMAKE_INSTALL_LIBDIR@ 4 | includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@/@PROJECT_NAME@ 5 | 6 | Name: libperiphery 7 | Description: Library for peripheral I/O (GPIO, LED, PWM, SPI, I2C, MMIO, Serial) in Linux 8 | Version: @VERSION@ 9 | Libs: -L${libdir} -lperiphery 10 | Cflags: -I${includedir} 11 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | jobs: 8 | build: 9 | strategy: 10 | matrix: 11 | cc: [/usr/bin/gcc, /usr/bin/clang] 12 | 13 | env: 14 | CC: ${{ matrix.cc }} 15 | 16 | runs-on: ubuntu-24.04 17 | 18 | steps: 19 | - uses: actions/checkout@v4 20 | 21 | - name: Build with make 22 | run: make all tests 23 | 24 | - name: Build with cmake 25 | run: | 26 | mkdir build 27 | cd build 28 | cmake .. 29 | make 30 | -------------------------------------------------------------------------------- /tests/test.h: -------------------------------------------------------------------------------- 1 | /* 2 | * c-periphery 3 | * https://github.com/vsergeev/c-periphery 4 | * License: MIT 5 | */ 6 | 7 | #include 8 | #include 9 | 10 | #define STR_OK "[\x1b[1;32m OK \x1b[0m]" 11 | #define STR_FAIL "[\x1b[1;31mFAIL\x1b[0m]" 12 | 13 | #define passert(c) \ 14 | do { \ 15 | int r = (c); \ 16 | if (r) \ 17 | printf(" " STR_OK " %s %s():%d %s\n", __FILE__, __func__, __LINE__, #c); \ 18 | else \ 19 | printf(" " STR_FAIL " %s %s():%d %s\n", __FILE__, __func__, __LINE__, #c); \ 20 | assert(r); \ 21 | } while(0) 22 | 23 | #define ptest() \ 24 | printf("\nStarting test %s():%d\n", __func__, __LINE__) 25 | 26 | -------------------------------------------------------------------------------- /src/version.c: -------------------------------------------------------------------------------- 1 | /* 2 | * c-periphery 3 | * https://github.com/vsergeev/c-periphery 4 | * License: MIT 5 | */ 6 | 7 | #include "version.h" 8 | 9 | const char *periphery_version(void) { 10 | #define _STRINGIFY(s) #s 11 | #define STRINGIFY(s) _STRINGIFY(s) 12 | return "v" STRINGIFY(PERIPHERY_VERSION_MAJOR) "." STRINGIFY(PERIPHERY_VERSION_MINOR) "." STRINGIFY(PERIPHERY_VERSION_PATCH); 13 | } 14 | 15 | const periphery_version_t *periphery_version_info(void) { 16 | static const periphery_version_t version = { 17 | .major = PERIPHERY_VERSION_MAJOR, 18 | .minor = PERIPHERY_VERSION_MINOR, 19 | .patch = PERIPHERY_VERSION_PATCH, 20 | .commit_id = PERIPHERY_VERSION_COMMIT, 21 | }; 22 | return &version; 23 | } 24 | -------------------------------------------------------------------------------- /src/version.h: -------------------------------------------------------------------------------- 1 | /* 2 | * c-periphery 3 | * https://github.com/vsergeev/c-periphery 4 | * License: MIT 5 | */ 6 | 7 | #ifndef _PERIPHERY_VERSION_H 8 | #define _PERIPHERY_VERSION_H 9 | 10 | #ifdef __cplusplus 11 | extern "C" { 12 | #endif 13 | 14 | #define PERIPHERY_VERSION_MAJOR 2 15 | #define PERIPHERY_VERSION_MINOR 5 16 | #define PERIPHERY_VERSION_PATCH 0 17 | 18 | #ifndef PERIPHERY_VERSION_COMMIT 19 | #define PERIPHERY_VERSION_COMMIT "unknown" 20 | #endif 21 | 22 | typedef struct periphery_version { 23 | unsigned int major; 24 | unsigned int minor; 25 | unsigned int patch; 26 | const char *commit_id; 27 | } periphery_version_t; 28 | 29 | const char *periphery_version(void); 30 | 31 | const periphery_version_t *periphery_version_info(void); 32 | 33 | #ifdef __cplusplus 34 | } 35 | #endif 36 | 37 | #endif 38 | 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-2025 vsergeev / Ivan (Vanya) A. Sergeev 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /src/led.h: -------------------------------------------------------------------------------- 1 | /* 2 | * c-periphery 3 | * https://github.com/vsergeev/c-periphery 4 | * License: MIT 5 | */ 6 | 7 | #ifndef _PERIPHERY_LED_H 8 | #define _PERIPHERY_LED_H 9 | 10 | #ifdef __cplusplus 11 | extern "C" { 12 | #endif 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | enum led_error_code { 19 | LED_ERROR_ARG = -1, /* Invalid arguments */ 20 | LED_ERROR_OPEN = -2, /* Opening LED */ 21 | LED_ERROR_QUERY = -3, /* Querying LED attributes */ 22 | LED_ERROR_IO = -4, /* Reading/writing LED brightness */ 23 | LED_ERROR_CLOSE = -5, /* Closing LED */ 24 | }; 25 | 26 | typedef struct led_handle led_t; 27 | 28 | /* Primary Functions */ 29 | led_t *led_new(void); 30 | int led_open(led_t *led, const char *name); 31 | int led_read(led_t *led, bool *value); 32 | int led_write(led_t *led, bool value); 33 | int led_close(led_t *led); 34 | void led_free(led_t *led); 35 | 36 | /* Getters */ 37 | int led_get_brightness(led_t *led, unsigned int *brightness); 38 | int led_get_max_brightness(led_t *led, unsigned int *max_brightness); 39 | int led_get_trigger(led_t *led, char *str, size_t len); 40 | int led_get_triggers_entry(led_t *led, unsigned int index, char *str, size_t len); 41 | int led_get_triggers_count(led_t *led, unsigned int *count); 42 | 43 | /* Setters */ 44 | int led_set_brightness(led_t *led, unsigned int brightness); 45 | int led_set_trigger(led_t *led, const char *trigger); 46 | 47 | /* Miscellaneous */ 48 | int led_name(led_t *led, char *str, size_t len); 49 | int led_tostring(led_t *led, char *str, size_t len); 50 | 51 | /* Error Handling */ 52 | int led_errno(led_t *led); 53 | const char *led_errmsg(led_t *led); 54 | 55 | #ifdef __cplusplus 56 | } 57 | #endif 58 | 59 | #endif 60 | 61 | -------------------------------------------------------------------------------- /src/i2c.h: -------------------------------------------------------------------------------- 1 | /* 2 | * c-periphery 3 | * https://github.com/vsergeev/c-periphery 4 | * License: MIT 5 | */ 6 | 7 | #ifndef _PERIPHERY_I2C_H 8 | #define _PERIPHERY_I2C_H 9 | 10 | #ifdef __cplusplus 11 | extern "C" { 12 | #endif 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | #include 20 | 21 | enum i2c_error_code { 22 | I2C_ERROR_ARG = -1, /* Invalid arguments */ 23 | I2C_ERROR_OPEN = -2, /* Opening I2C device */ 24 | I2C_ERROR_QUERY = -3, /* Querying I2C device attributes */ 25 | I2C_ERROR_NOT_SUPPORTED = -4, /* I2C not supported on this device */ 26 | I2C_ERROR_TRANSFER = -5, /* I2C transfer */ 27 | I2C_ERROR_CLOSE = -6, /* Closing I2C device */ 28 | }; 29 | 30 | typedef struct i2c_handle i2c_t; 31 | 32 | /* Primary Functions */ 33 | i2c_t *i2c_new(void); 34 | int i2c_open(i2c_t *i2c, const char *path); 35 | int i2c_transfer(i2c_t *i2c, struct i2c_msg *msgs, size_t count); 36 | int i2c_close(i2c_t *i2c); 37 | void i2c_free(i2c_t *i2c); 38 | 39 | /* Miscellaneous */ 40 | int i2c_fd(i2c_t *i2c); 41 | int i2c_tostring(i2c_t *i2c, char *str, size_t len); 42 | 43 | /* Error Handling */ 44 | int i2c_errno(i2c_t *i2c); 45 | const char *i2c_errmsg(i2c_t *i2c); 46 | 47 | /* struct i2c_msg from : 48 | 49 | struct i2c_msg { 50 | __u16 addr; 51 | __u16 flags; 52 | #define I2C_M_TEN 0x0010 53 | #define I2C_M_RD 0x0001 54 | #define I2C_M_STOP 0x8000 55 | #define I2C_M_NOSTART 0x4000 56 | #define I2C_M_REV_DIR_ADDR 0x2000 57 | #define I2C_M_IGNORE_NAK 0x1000 58 | #define I2C_M_NO_RD_ACK 0x0800 59 | #define I2C_M_RECV_LEN 0x0400 60 | __u16 len; 61 | __u8 *buf; 62 | }; 63 | */ 64 | 65 | #ifdef __cplusplus 66 | } 67 | #endif 68 | 69 | #endif 70 | 71 | -------------------------------------------------------------------------------- /src/mmio.h: -------------------------------------------------------------------------------- 1 | /* 2 | * c-periphery 3 | * https://github.com/vsergeev/c-periphery 4 | * License: MIT 5 | */ 6 | 7 | #ifndef _PERIPHERY_MMIO_H 8 | #define _PERIPHERY_MMIO_H 9 | 10 | #ifdef __cplusplus 11 | extern "C" { 12 | #endif 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | enum mmio_error_code { 19 | MMIO_ERROR_ARG = -1, /* Invalid arguments */ 20 | MMIO_ERROR_OPEN = -2, /* Opening MMIO */ 21 | MMIO_ERROR_CLOSE = -3, /* Closing MMIO */ 22 | }; 23 | 24 | typedef struct mmio_handle mmio_t; 25 | 26 | /* Primary Functions */ 27 | mmio_t *mmio_new(void); 28 | int mmio_open(mmio_t *mmio, uintptr_t base, size_t size); 29 | int mmio_open_advanced(mmio_t *mmio, uintptr_t base, size_t size, const char *path); 30 | void *mmio_ptr(mmio_t *mmio); 31 | int mmio_read64(mmio_t *mmio, uintptr_t offset, uint64_t *value); 32 | int mmio_read32(mmio_t *mmio, uintptr_t offset, uint32_t *value); 33 | int mmio_read16(mmio_t *mmio, uintptr_t offset, uint16_t *value); 34 | int mmio_read8(mmio_t *mmio, uintptr_t offset, uint8_t *value); 35 | int mmio_read(mmio_t *mmio, uintptr_t offset, uint8_t *buf, size_t len); 36 | int mmio_write64(mmio_t *mmio, uintptr_t offset, uint64_t value); 37 | int mmio_write32(mmio_t *mmio, uintptr_t offset, uint32_t value); 38 | int mmio_write16(mmio_t *mmio, uintptr_t offset, uint16_t value); 39 | int mmio_write8(mmio_t *mmio, uintptr_t offset, uint8_t value); 40 | int mmio_write(mmio_t *mmio, uintptr_t offset, const uint8_t *buf, size_t len); 41 | int mmio_close(mmio_t *mmio); 42 | void mmio_free(mmio_t *mmio); 43 | 44 | /* Miscellaneous */ 45 | uintptr_t mmio_base(mmio_t *mmio); 46 | size_t mmio_size(mmio_t *mmio); 47 | int mmio_tostring(mmio_t *mmio, char *str, size_t len); 48 | 49 | /* Error Handling */ 50 | int mmio_errno(mmio_t *mmio); 51 | const char *mmio_errmsg(mmio_t *mmio); 52 | 53 | #ifdef __cplusplus 54 | } 55 | #endif 56 | 57 | #endif 58 | 59 | -------------------------------------------------------------------------------- /src/pwm.h: -------------------------------------------------------------------------------- 1 | /* 2 | * c-periphery 3 | * https://github.com/vsergeev/c-periphery 4 | * License: MIT 5 | */ 6 | 7 | #ifndef _PERIPHERY_PWM_H 8 | #define _PERIPHERY_PWM_H 9 | 10 | #ifdef __cplusplus 11 | extern "C" { 12 | #endif 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | enum pwm_error_code { 19 | PWM_ERROR_ARG = -1, /* Invalid arguments */ 20 | PWM_ERROR_OPEN = -2, /* Opening PWM */ 21 | PWM_ERROR_QUERY = -3, /* Querying PWM attributes */ 22 | PWM_ERROR_CONFIGURE = -4, /* Configuring PWM attributes */ 23 | PWM_ERROR_CLOSE = -5, /* Closing PWM */ 24 | }; 25 | 26 | typedef enum pwm_polarity { 27 | PWM_POLARITY_NORMAL, /* Normal polarity */ 28 | PWM_POLARITY_INVERSED, /* Inversed polarity */ 29 | } pwm_polarity_t; 30 | 31 | typedef struct pwm_handle pwm_t; 32 | 33 | /* Primary Functions */ 34 | pwm_t *pwm_new(void); 35 | int pwm_open(pwm_t *pwm, unsigned int chip, unsigned int channel); 36 | int pwm_enable(pwm_t *pwm); 37 | int pwm_disable(pwm_t *pwm); 38 | int pwm_close(pwm_t *pwm); 39 | void pwm_free(pwm_t *pwm); 40 | 41 | /* Getters */ 42 | int pwm_get_enabled(pwm_t *pwm, bool *enabled); 43 | int pwm_get_period_ns(pwm_t *pwm, uint64_t *period_ns); 44 | int pwm_get_duty_cycle_ns(pwm_t *pwm, uint64_t *duty_cycle_ns); 45 | int pwm_get_period(pwm_t *pwm, double *period); 46 | int pwm_get_duty_cycle(pwm_t *pwm, double *duty_cycle); 47 | int pwm_get_frequency(pwm_t *pwm, double *frequency); 48 | int pwm_get_polarity(pwm_t *pwm, pwm_polarity_t *polarity); 49 | 50 | /* Setters */ 51 | int pwm_set_enabled(pwm_t *pwm, bool enabled); 52 | int pwm_set_period_ns(pwm_t *pwm, uint64_t period_ns); 53 | int pwm_set_duty_cycle_ns(pwm_t *pwm, uint64_t duty_cycle_ns); 54 | int pwm_set_period(pwm_t *pwm, double period); 55 | int pwm_set_duty_cycle(pwm_t *pwm, double duty_cycle); 56 | int pwm_set_frequency(pwm_t *pwm, double frequency); 57 | int pwm_set_polarity(pwm_t *pwm, pwm_polarity_t polarity); 58 | 59 | /* Miscellaneous */ 60 | unsigned int pwm_chip(pwm_t *pwm); 61 | unsigned int pwm_channel(pwm_t *pwm); 62 | int pwm_tostring(pwm_t *pwm, char *str, size_t len); 63 | 64 | /* Error Handling */ 65 | int pwm_errno(pwm_t *pwm); 66 | const char *pwm_errmsg(pwm_t *pwm); 67 | 68 | #ifdef __cplusplus 69 | } 70 | #endif 71 | 72 | #endif 73 | 74 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | VERSION = 2.5.0 2 | SO_VERSION = 2.5 3 | 4 | STATIC_LIB = periphery.a 5 | SHARED_LIB = libperiphery.so 6 | 7 | SRCS = src/gpio.c src/gpio_cdev_v2.c src/gpio_cdev_v1.c src/gpio_sysfs.c src/led.c src/pwm.c src/spi.c src/i2c.c src/mmio.c src/serial.c src/version.c 8 | 9 | SRCDIR = src 10 | OBJDIR = obj 11 | 12 | TEST_PROGRAMS = $(basename $(wildcard tests/*.c)) 13 | 14 | ########################################################################### 15 | 16 | OBJECTS = $(patsubst $(SRCDIR)/%.c,$(OBJDIR)/%.o,$(SRCS)) 17 | 18 | NULL := $(if $(filter Windows_NT,$(OS)),NUL,/dev/null) 19 | GPIO_CDEV_V1_SUPPORT := $(shell ! env printf "\x23include \n\x23ifndef GPIO_GET_LINEEVENT_IOCTL\n\x23error\n\x23endif" | $(CC) -E - >$(NULL) 2>&1; echo $$?) 20 | GPIO_CDEV_V2_SUPPORT := $(shell ! env printf "\x23include \nint main(void) { GPIO_V2_LINE_FLAG_EVENT_CLOCK_REALTIME; return 0; }" | $(CC) -x c -fsyntax-only - >$(NULL) 2>&1; echo $$?) 21 | GPIO_CDEV_SUPPORT = $(if $(filter 1,$(GPIO_CDEV_V2_SUPPORT)),2,$(if $(filter 1,$(GPIO_CDEV_V1_SUPPORT)),1,0)) 22 | 23 | COMMIT_ID := $(shell git describe --abbrev --always --tags --dirty 2>$(NULL) || echo "") 24 | 25 | OPT ?= -O3 26 | CFLAGS += -std=gnu99 -pedantic 27 | CFLAGS += $(OPT) 28 | CFLAGS += -Wall -Wextra -Wno-stringop-truncation $(DEBUG) -fPIC 29 | CFLAGS += -DPERIPHERY_VERSION_COMMIT=\"$(COMMIT_ID)\" -DPERIPHERY_GPIO_CDEV_SUPPORT=$(GPIO_CDEV_SUPPORT) 30 | LDFLAGS += 31 | 32 | ifdef CROSS_COMPILE 33 | CC = $(CROSS_COMPILE)gcc 34 | AR = $(CROSS_COMPILE)ar 35 | endif 36 | 37 | ########################################################################### 38 | 39 | .PHONY: all 40 | all: $(STATIC_LIB) 41 | 42 | .PHONY: shared 43 | shared: $(SHARED_LIB) 44 | 45 | .PHONY: tests 46 | tests: $(TEST_PROGRAMS) 47 | 48 | .PHONY: clean 49 | clean: 50 | rm -rf $(STATIC_LIB) $(SHARED_LIB) $(SHARED_LIB).$(SO_VERSION) $(SHARED_LIB).$(VERSION) $(OBJDIR) $(TEST_PROGRAMS) 51 | 52 | ########################################################################### 53 | 54 | tests/%: tests/%.c $(STATIC_LIB) 55 | $(CC) $(CFLAGS) $(LDFLAGS) $< $(STATIC_LIB) -o $@ -lpthread 56 | 57 | ########################################################################### 58 | 59 | $(OBJECTS): | $(OBJDIR) 60 | 61 | $(OBJDIR): 62 | mkdir $(OBJDIR) 63 | 64 | $(STATIC_LIB): $(OBJECTS) 65 | $(AR) rcs $(STATIC_LIB) $(OBJECTS) 66 | 67 | $(SHARED_LIB): $(OBJECTS) 68 | $(CC) $(CFLAGS) $(LDFLAGS) -shared -Wl,-soname,$(SHARED_LIB).$(SO_VERSION) -o $(SHARED_LIB).$(VERSION) $(OBJECTS) 69 | ln -s $(SHARED_LIB).$(VERSION) $(SHARED_LIB).$(SO_VERSION) 70 | ln -s $(SHARED_LIB).$(SO_VERSION) $(SHARED_LIB) 71 | 72 | $(OBJDIR)/%.o: $(SRCDIR)/%.c 73 | $(CC) $(CFLAGS) $(LDFLAGS) -c $< -o $@ 74 | -------------------------------------------------------------------------------- /.github/workflows/buildroot.yml: -------------------------------------------------------------------------------- 1 | # Provide CI which utilize buildroot and the test-pkg utility to build c-periphery 2 | # against the following toolchains: 3 | # - br-arm-full 4 | # ARM toolchain with uClibc 5 | # - br-arm-cortex-a9-glibc 6 | # ARM toolchain with glibc and a very recent gcc version 7 | # - br-arm-cortex-m4-full. 8 | # ARM noMMU toolchain with no dynamic library support 9 | # - br-x86-64-musl 10 | # x86-64 musl toolchain 11 | # - br-arm-full-static 12 | # ARM toolchain which is fully static (doesn't support dynamic libraries) 13 | # - sourcery-arm 14 | # ARM toolchain with an old gcc version (gcc 4.8) 15 | # 16 | # Build logs are upload as part of the last step 17 | 18 | name: Buildroot 19 | 20 | # Only run on MRs or push to non-master branches (for development) 21 | on: 22 | push: 23 | branches-ignore: [master] 24 | pull_request: 25 | branches: [master] 26 | 27 | env: 28 | # Version of buildroot to use to perform build tests 29 | BR_VER: 2024.02.11 30 | 31 | jobs: 32 | build: 33 | runs-on: ubuntu-24.04 34 | 35 | steps: 36 | - uses: actions/checkout@v4 37 | 38 | # Need to perform clone/build tests outside of workspace 39 | - name: Create Build Directory 40 | shell: bash 41 | run: mkdir -p ~/build 42 | 43 | # Clone Buildroot and create file(s) for building c-periphery with buildroot 44 | # to utilize different toolchain types 45 | - name: Check-out/Setup Buildroot 46 | shell: bash 47 | run: | 48 | cd ~/build 49 | git clone -b $BR_VER git://git.buildroot.net/buildroot 50 | cd buildroot 51 | echo "BR2_PACKAGE_C_PERIPHERY=y" > c-periphery.config 52 | 53 | # Run buildroot's test-pkg script to perform builds with different toolchains 54 | - name: Buildroot 'test-pkg' Build 55 | shell: bash 56 | env: 57 | # Utilize the current checkout of c-periphery with buildroot instead of released version 58 | # https://buildroot.org/downloads/manual/manual.html#_advanced_usage 59 | C_PERIPHERY_OVERRIDE_SRCDIR: ${{github.workspace}} 60 | run: | 61 | cd ~/build/buildroot 62 | # Remove hash file to disable hash checks 63 | rm -f package/c-periphery/c-periphery.hash 64 | # Runs builds against the 6 toolchains above and places artifacts in ~/build/br-test-pkg 65 | ./utils/test-pkg -c c-periphery.config -p c-periphery -d "$(pwd)/../br-test-pkg" 66 | 67 | # Store build logs from test-pkg as artifacts 68 | - name: Upload Buildroot Logfiles 69 | continue-on-error: true 70 | uses: actions/upload-artifact@v4 71 | with: 72 | name: logfiles 73 | path: ~/build/br-test-pkg/**/logfile 74 | -------------------------------------------------------------------------------- /src/spi.h: -------------------------------------------------------------------------------- 1 | /* 2 | * c-periphery 3 | * https://github.com/vsergeev/c-periphery 4 | * License: MIT 5 | */ 6 | 7 | #ifndef _PERIPHERY_SPI_H 8 | #define _PERIPHERY_SPI_H 9 | 10 | #ifdef __cplusplus 11 | extern "C" { 12 | #endif 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | enum spi_error_code { 19 | SPI_ERROR_ARG = -1, /* Invalid arguments */ 20 | SPI_ERROR_OPEN = -2, /* Opening SPI device */ 21 | SPI_ERROR_QUERY = -3, /* Querying SPI device attributes */ 22 | SPI_ERROR_CONFIGURE = -4, /* Configuring SPI device attributes */ 23 | SPI_ERROR_TRANSFER = -5, /* SPI transfer */ 24 | SPI_ERROR_CLOSE = -6, /* Closing SPI device */ 25 | SPI_ERROR_UNSUPPORTED = -7, /* Unsupported attribute or operation */ 26 | }; 27 | 28 | typedef enum spi_bit_order { 29 | MSB_FIRST, 30 | LSB_FIRST, 31 | } spi_bit_order_t; 32 | 33 | typedef struct spi_msg { 34 | const uint8_t *txbuf; 35 | uint8_t *rxbuf; 36 | size_t len; 37 | bool deselect; 38 | uint16_t deselect_delay_us; 39 | uint8_t word_delay_us; 40 | } spi_msg_t; 41 | 42 | typedef struct spi_handle spi_t; 43 | 44 | /* Primary Functions */ 45 | spi_t *spi_new(void); 46 | int spi_open(spi_t *spi, const char *path, unsigned int mode, 47 | uint32_t max_speed); 48 | int spi_open_advanced(spi_t *spi, const char *path, unsigned int mode, 49 | uint32_t max_speed, spi_bit_order_t bit_order, 50 | uint8_t bits_per_word, uint8_t extra_flags); 51 | int spi_open_advanced2(spi_t *spi, const char *path, unsigned int mode, 52 | uint32_t max_speed, spi_bit_order_t bit_order, 53 | uint8_t bits_per_word, uint32_t extra_flags); 54 | int spi_transfer(spi_t *spi, const uint8_t *txbuf, uint8_t *rxbuf, size_t len); 55 | int spi_transfer_advanced(spi_t *spi, const spi_msg_t *msgs, size_t count); 56 | int spi_close(spi_t *spi); 57 | void spi_free(spi_t *spi); 58 | 59 | /* Getters */ 60 | int spi_get_mode(spi_t *spi, unsigned int *mode); 61 | int spi_get_max_speed(spi_t *spi, uint32_t *max_speed); 62 | int spi_get_bit_order(spi_t *spi, spi_bit_order_t *bit_order); 63 | int spi_get_bits_per_word(spi_t *spi, uint8_t *bits_per_word); 64 | int spi_get_extra_flags(spi_t *spi, uint8_t *extra_flags); 65 | int spi_get_extra_flags32(spi_t *spi, uint32_t *extra_flags); 66 | 67 | /* Setters */ 68 | int spi_set_mode(spi_t *spi, unsigned int mode); 69 | int spi_set_max_speed(spi_t *spi, uint32_t max_speed); 70 | int spi_set_bit_order(spi_t *spi, spi_bit_order_t bit_order); 71 | int spi_set_bits_per_word(spi_t *spi, uint8_t bits_per_word); 72 | int spi_set_extra_flags(spi_t *spi, uint8_t extra_flags); 73 | int spi_set_extra_flags32(spi_t *spi, uint32_t extra_flags); 74 | 75 | /* Miscellaneous */ 76 | int spi_fd(spi_t *spi); 77 | int spi_tostring(spi_t *spi, char *str, size_t len); 78 | 79 | /* Error Handling */ 80 | int spi_errno(spi_t *spi); 81 | const char *spi_errmsg(spi_t *spi); 82 | 83 | #ifdef __cplusplus 84 | } 85 | #endif 86 | 87 | #endif 88 | 89 | -------------------------------------------------------------------------------- /src/serial.h: -------------------------------------------------------------------------------- 1 | /* 2 | * c-periphery 3 | * https://github.com/vsergeev/c-periphery 4 | * License: MIT 5 | */ 6 | 7 | #ifndef _PERIPHERY_SERIAL_H 8 | #define _PERIPHERY_SERIAL_H 9 | 10 | #ifdef __cplusplus 11 | extern "C" { 12 | #endif 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | enum serial_error_code { 19 | SERIAL_ERROR_ARG = -1, /* Invalid arguments */ 20 | SERIAL_ERROR_OPEN = -2, /* Opening serial port */ 21 | SERIAL_ERROR_QUERY = -3, /* Querying serial port attributes */ 22 | SERIAL_ERROR_CONFIGURE = -4, /* Configuring serial port attributes */ 23 | SERIAL_ERROR_IO = -5, /* Reading/writing serial port */ 24 | SERIAL_ERROR_CLOSE = -6, /* Closing serial port */ 25 | }; 26 | 27 | typedef enum serial_parity { 28 | PARITY_NONE, 29 | PARITY_ODD, 30 | PARITY_EVEN, 31 | } serial_parity_t; 32 | 33 | typedef struct serial_handle serial_t; 34 | 35 | /* Primary Functions */ 36 | serial_t *serial_new(void); 37 | int serial_open(serial_t *serial, const char *path, uint32_t baudrate); 38 | int serial_open_advanced(serial_t *serial, const char *path, 39 | uint32_t baudrate, unsigned int databits, 40 | serial_parity_t parity, unsigned int stopbits, 41 | bool xonxoff, bool rtscts); 42 | int serial_read(serial_t *serial, uint8_t *buf, size_t len, int timeout_ms); 43 | int serial_write(serial_t *serial, const uint8_t *buf, size_t len); 44 | int serial_flush(serial_t *serial); 45 | int serial_input_waiting(serial_t *serial, unsigned int *count); 46 | int serial_output_waiting(serial_t *serial, unsigned int *count); 47 | int serial_poll(serial_t *serial, int timeout_ms); 48 | int serial_close(serial_t *serial); 49 | void serial_free(serial_t *serial); 50 | 51 | /* Getters */ 52 | int serial_get_baudrate(serial_t *serial, uint32_t *baudrate); 53 | int serial_get_databits(serial_t *serial, unsigned int *databits); 54 | int serial_get_parity(serial_t *serial, serial_parity_t *parity); 55 | int serial_get_stopbits(serial_t *serial, unsigned int *stopbits); 56 | int serial_get_xonxoff(serial_t *serial, bool *xonxoff); 57 | int serial_get_rtscts(serial_t *serial, bool *rtscts); 58 | int serial_get_vmin(serial_t *serial, unsigned int *vmin); 59 | int serial_get_vtime(serial_t *serial, float* vtime); 60 | 61 | /* Setters */ 62 | int serial_set_baudrate(serial_t *serial, uint32_t baudrate); 63 | int serial_set_databits(serial_t *serial, unsigned int databits); 64 | int serial_set_parity(serial_t *serial, enum serial_parity parity); 65 | int serial_set_stopbits(serial_t *serial, unsigned int stopbits); 66 | int serial_set_xonxoff(serial_t *serial, bool enabled); 67 | int serial_set_rtscts(serial_t *serial, bool enabled); 68 | int serial_set_vmin(serial_t *serial, unsigned int vmin); 69 | int serial_set_vtime(serial_t *serial, float vtime); 70 | 71 | /* Miscellaneous */ 72 | int serial_fd(serial_t *serial); 73 | int serial_tostring(serial_t *serial, char *str, size_t len); 74 | 75 | /* Error Handling */ 76 | int serial_errno(serial_t *serial); 77 | const char *serial_errmsg(serial_t *serial); 78 | 79 | #ifdef __cplusplus 80 | } 81 | #endif 82 | 83 | #endif 84 | 85 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.19) 2 | project(periphery C) 3 | 4 | option(BUILD_TESTS "Build test programs" ON) 5 | 6 | if(NOT CMAKE_BUILD_TYPE) 7 | set(CMAKE_BUILD_TYPE Release) 8 | endif() 9 | 10 | # Check Linux kernel header files for character device GPIO support 11 | include(CheckSourceCompiles) 12 | check_source_compiles(C "#include \nint main(void) { GPIO_V2_LINE_FLAG_EVENT_CLOCK_REALTIME; return 0; }" HAVE_GPIO_CDEV_V2) 13 | include(CheckSymbolExists) 14 | check_symbol_exists(GPIO_GET_LINEEVENT_IOCTL linux/gpio.h HAVE_GPIO_CDEV_V1) 15 | if(HAVE_GPIO_CDEV_V2) 16 | set(GPIO_CDEV_SUPPORT 2) 17 | elseif(HAVE_GPIO_CDEV_V1) 18 | set(GPIO_CDEV_SUPPORT 1) 19 | else() 20 | set(GPIO_CDEV_SUPPORT 0) 21 | message(WARNING "Missing character device GPIO support in Linux kernel header files. c-periphery will be built with legacy sysfs GPIO support only.") 22 | endif() 23 | add_definitions(-DPERIPHERY_GPIO_CDEV_SUPPORT=${GPIO_CDEV_SUPPORT}) 24 | 25 | # Library version 26 | set(VERSION "2.5.0") 27 | set(SOVERSION "2.5") 28 | 29 | # Glob sources, headers, tests 30 | file(GLOB_RECURSE periphery_SOURCES src/*.c) 31 | file(GLOB_RECURSE periphery_HEADERS src/*.h) 32 | file(GLOB_RECURSE periphery_TESTS tests/*.c) 33 | 34 | # Expose git commit id into COMMIT_ID variable 35 | execute_process( 36 | COMMAND git --git-dir="${CMAKE_CURRENT_SOURCE_DIR}/.git" describe --abbrev --always --tags --dirty 37 | WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" 38 | OUTPUT_VARIABLE COMMIT_ID 39 | ERROR_QUIET 40 | OUTPUT_STRIP_TRAILING_WHITESPACE) 41 | 42 | # Define C flags and include directories 43 | add_definitions(-DPERIPHERY_VERSION_COMMIT="${COMMIT_ID}") 44 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu99 -pedantic -Wall -Wextra -Wno-stringop-truncation -fPIC") 45 | set(CMAKE_C_FLAGS_DEBUG "-g") 46 | set(CMAKE_C_FLAGS_RELEASE "-O3") 47 | 48 | # Declare library target 49 | add_library(periphery ${periphery_SOURCES} ${periphery_HEADERS}) 50 | set_target_properties(periphery PROPERTIES VERSION ${VERSION} SOVERSION ${SOVERSION}) 51 | target_include_directories(periphery PUBLIC 52 | $ 53 | $ 54 | ) 55 | 56 | include(GNUInstallDirs) 57 | 58 | # Generate pkg-config pc file 59 | configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/libperiphery.pc.in ${CMAKE_CURRENT_BINARY_DIR}/libperiphery.pc @ONLY) 60 | 61 | # Declare install targets 62 | install(TARGETS periphery DESTINATION ${CMAKE_INSTALL_LIBDIR}) 63 | install(FILES ${periphery_HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}) 64 | install(FILES ${CMAKE_CURRENT_BINARY_DIR}/libperiphery.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) 65 | 66 | install( 67 | TARGETS ${PROJECT_NAME} 68 | EXPORT ${PROJECT_NAME}-config 69 | RUNTIME DESTINATION ${CMAKE_INSTALL_LIBDIR} 70 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 71 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} 72 | ) 73 | 74 | install( 75 | EXPORT ${PROJECT_NAME}-config 76 | NAMESPACE ${PROJECT_NAME}:: 77 | DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME} 78 | ) 79 | 80 | # Declare test targets if enabled 81 | if(BUILD_TESTS) 82 | foreach(TEST_SOURCE ${periphery_TESTS}) 83 | get_filename_component(TEST_PROGRAM ${TEST_SOURCE} NAME_WE) 84 | add_executable(${TEST_PROGRAM} ${TEST_SOURCE}) 85 | target_link_libraries(${TEST_PROGRAM} periphery pthread) 86 | set(TEST_PROGRAMS ${TEST_PROGRAMS} ${TEST_PROGRAM}) 87 | endforeach() 88 | add_custom_target(tests DEPENDS periphery ${TEST_PROGRAMS}) 89 | endif() 90 | -------------------------------------------------------------------------------- /src/i2c.c: -------------------------------------------------------------------------------- 1 | /* 2 | * c-periphery 3 | * https://github.com/vsergeev/c-periphery 4 | * License: MIT 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include 20 | #include 21 | #include 22 | 23 | #include "i2c.h" 24 | 25 | struct i2c_handle { 26 | int fd; 27 | 28 | struct { 29 | int c_errno; 30 | char errmsg[96]; 31 | } error; 32 | }; 33 | 34 | static int _i2c_error(i2c_t *i2c, int code, int c_errno, const char *fmt, ...) { 35 | va_list ap; 36 | 37 | i2c->error.c_errno = c_errno; 38 | 39 | va_start(ap, fmt); 40 | vsnprintf(i2c->error.errmsg, sizeof(i2c->error.errmsg), fmt, ap); 41 | va_end(ap); 42 | 43 | /* Tack on strerror() and errno */ 44 | if (c_errno) { 45 | char buf[64] = {0}; 46 | strerror_r(c_errno, buf, sizeof(buf)); 47 | snprintf(i2c->error.errmsg+strlen(i2c->error.errmsg), sizeof(i2c->error.errmsg)-strlen(i2c->error.errmsg), ": %s [errno %d]", buf, c_errno); 48 | } 49 | 50 | return code; 51 | } 52 | 53 | i2c_t *i2c_new(void) { 54 | i2c_t *i2c = calloc(1, sizeof(i2c_t)); 55 | if (i2c == NULL) 56 | return NULL; 57 | 58 | i2c->fd = -1; 59 | 60 | return i2c; 61 | } 62 | 63 | void i2c_free(i2c_t *i2c) { 64 | free(i2c); 65 | } 66 | 67 | int i2c_open(i2c_t *i2c, const char *path) { 68 | unsigned long supported_funcs; 69 | 70 | memset(i2c, 0, sizeof(i2c_t)); 71 | 72 | /* Open device */ 73 | if ((i2c->fd = open(path, O_RDWR)) < 0) 74 | return _i2c_error(i2c, I2C_ERROR_OPEN, errno, "Opening I2C device \"%s\"", path); 75 | 76 | /* Query supported functions */ 77 | if (ioctl(i2c->fd, I2C_FUNCS, &supported_funcs) < 0) { 78 | int errsv = errno; 79 | close(i2c->fd); 80 | i2c->fd = -1; 81 | return _i2c_error(i2c, I2C_ERROR_QUERY, errsv, "Querying I2C functions"); 82 | } 83 | 84 | if (!(supported_funcs & I2C_FUNC_I2C)) { 85 | close(i2c->fd); 86 | i2c->fd = -1; 87 | return _i2c_error(i2c, I2C_ERROR_NOT_SUPPORTED, 0, "I2C not supported on %s", path); 88 | } 89 | 90 | return 0; 91 | } 92 | 93 | int i2c_transfer(i2c_t *i2c, struct i2c_msg *msgs, size_t count) { 94 | struct i2c_rdwr_ioctl_data i2c_rdwr_data; 95 | 96 | /* Prepare I2C transfer structure */ 97 | memset(&i2c_rdwr_data, 0, sizeof(struct i2c_rdwr_ioctl_data)); 98 | i2c_rdwr_data.msgs = msgs; 99 | i2c_rdwr_data.nmsgs = count; 100 | 101 | /* Transfer */ 102 | if (ioctl(i2c->fd, I2C_RDWR, &i2c_rdwr_data) < 0) 103 | return _i2c_error(i2c, I2C_ERROR_TRANSFER, errno, "I2C transfer"); 104 | 105 | return 0; 106 | } 107 | 108 | int i2c_close(i2c_t *i2c) { 109 | if (i2c->fd < 0) 110 | return 0; 111 | 112 | /* Close fd */ 113 | if (close(i2c->fd) < 0) 114 | return _i2c_error(i2c, I2C_ERROR_CLOSE, errno, "Closing I2C device"); 115 | 116 | i2c->fd = -1; 117 | 118 | return 0; 119 | } 120 | 121 | int i2c_tostring(i2c_t *i2c, char *str, size_t len) { 122 | return snprintf(str, len, "I2C (fd=%d)", i2c->fd); 123 | } 124 | 125 | const char *i2c_errmsg(i2c_t *i2c) { 126 | return i2c->error.errmsg; 127 | } 128 | 129 | int i2c_errno(i2c_t *i2c) { 130 | return i2c->error.c_errno; 131 | } 132 | 133 | int i2c_fd(i2c_t *i2c) { 134 | return i2c->fd; 135 | } 136 | 137 | -------------------------------------------------------------------------------- /src/gpio_internal.h: -------------------------------------------------------------------------------- 1 | /* 2 | * c-periphery 3 | * https://github.com/vsergeev/c-periphery 4 | * License: MIT 5 | */ 6 | 7 | #ifndef _PERIPHERY_GPIO_INTERNAL_H 8 | #define _PERIPHERY_GPIO_INTERNAL_H 9 | 10 | #include 11 | 12 | #include "gpio.h" 13 | 14 | /*********************************************************************************/ 15 | /* Operations table and handle structure */ 16 | /*********************************************************************************/ 17 | 18 | struct gpio_ops { 19 | int (*read)(gpio_t *gpio, bool *value); 20 | int (*write)(gpio_t *gpio, bool value); 21 | int (*read_event)(gpio_t *gpio, gpio_edge_t *edge, uint64_t *timestamp); 22 | int (*poll)(gpio_t *gpio, int timeout_ms); 23 | int (*close)(gpio_t *gpio); 24 | int (*get_direction)(gpio_t *gpio, gpio_direction_t *direction); 25 | int (*get_edge)(gpio_t *gpio, gpio_edge_t *edge); 26 | int (*get_event_clock)(gpio_t *gpio, gpio_event_clock_t *event_clock); 27 | int (*get_debounce_us)(gpio_t *gpio, uint32_t *debounce_us); 28 | int (*get_bias)(gpio_t *gpio, gpio_bias_t *bias); 29 | int (*get_drive)(gpio_t *gpio, gpio_drive_t *drive); 30 | int (*get_inverted)(gpio_t *gpio, bool *inverted); 31 | int (*set_direction)(gpio_t *gpio, gpio_direction_t direction); 32 | int (*set_edge)(gpio_t *gpio, gpio_edge_t edge); 33 | int (*set_event_clock)(gpio_t *gpio, gpio_event_clock_t event_clock); 34 | int (*set_debounce_us)(gpio_t *gpio, uint32_t debounce_us); 35 | int (*set_bias)(gpio_t *gpio, gpio_bias_t bias); 36 | int (*set_drive)(gpio_t *gpio, gpio_drive_t drive); 37 | int (*set_inverted)(gpio_t *gpio, bool inverted); 38 | unsigned int (*line)(gpio_t *gpio); 39 | int (*fd)(gpio_t *gpio); 40 | int (*name)(gpio_t *gpio, char *str, size_t len); 41 | int (*label)(gpio_t *gpio, char *str, size_t len); 42 | int (*chip_fd)(gpio_t *gpio); 43 | int (*chip_name)(gpio_t *gpio, char *str, size_t len); 44 | int (*chip_label)(gpio_t *gpio, char *str, size_t len); 45 | int (*tostring)(gpio_t *gpio, char *str, size_t len); 46 | }; 47 | 48 | struct gpio_handle { 49 | const struct gpio_ops *ops; 50 | 51 | union { 52 | struct { 53 | unsigned int line; 54 | int line_fd; 55 | int chip_fd; 56 | gpio_direction_t direction; 57 | gpio_edge_t edge; 58 | gpio_event_clock_t event_clock; 59 | uint32_t debounce_us; 60 | gpio_bias_t bias; 61 | gpio_drive_t drive; 62 | bool inverted; 63 | char label[32]; 64 | } cdev; 65 | struct { 66 | unsigned int line; 67 | int line_fd; 68 | bool exported; 69 | } sysfs; 70 | } u; 71 | 72 | /* error state */ 73 | struct { 74 | int c_errno; 75 | char errmsg[96]; 76 | } error; 77 | }; 78 | 79 | /*********************************************************************************/ 80 | /* Common error formatting function */ 81 | /*********************************************************************************/ 82 | 83 | inline static int _gpio_error(gpio_t *gpio, int code, int c_errno, const char *fmt, ...) { 84 | va_list ap; 85 | 86 | gpio->error.c_errno = c_errno; 87 | 88 | va_start(ap, fmt); 89 | vsnprintf(gpio->error.errmsg, sizeof(gpio->error.errmsg), fmt, ap); 90 | va_end(ap); 91 | 92 | /* Tack on strerror() and errno */ 93 | if (c_errno) { 94 | char buf[64] = {0}; 95 | strerror_r(c_errno, buf, sizeof(buf)); 96 | snprintf(gpio->error.errmsg+strlen(gpio->error.errmsg), sizeof(gpio->error.errmsg)-strlen(gpio->error.errmsg), ": %s [errno %d]", buf, c_errno); 97 | } 98 | 99 | return code; 100 | } 101 | 102 | #endif 103 | 104 | -------------------------------------------------------------------------------- /src/gpio.h: -------------------------------------------------------------------------------- 1 | /* 2 | * c-periphery 3 | * https://github.com/vsergeev/c-periphery 4 | * License: MIT 5 | */ 6 | 7 | #ifndef _PERIPHERY_GPIO_H 8 | #define _PERIPHERY_GPIO_H 9 | 10 | #ifdef __cplusplus 11 | extern "C" { 12 | #endif 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | enum gpio_error_code { 19 | GPIO_ERROR_ARG = -1, /* Invalid arguments */ 20 | GPIO_ERROR_OPEN = -2, /* Opening GPIO */ 21 | GPIO_ERROR_NOT_FOUND = -3, /* Line name not found */ 22 | GPIO_ERROR_QUERY = -4, /* Querying GPIO attributes */ 23 | GPIO_ERROR_CONFIGURE = -5, /* Configuring GPIO attributes */ 24 | GPIO_ERROR_UNSUPPORTED = -6, /* Unsupported attribute or operation */ 25 | GPIO_ERROR_INVALID_OPERATION = -7, /* Invalid operation */ 26 | GPIO_ERROR_IO = -8, /* Reading/writing GPIO */ 27 | GPIO_ERROR_CLOSE = -9, /* Closing GPIO */ 28 | }; 29 | 30 | typedef enum gpio_direction { 31 | GPIO_DIR_IN, /* Input */ 32 | GPIO_DIR_OUT, /* Output, initialized to low */ 33 | GPIO_DIR_OUT_LOW, /* Output, initialized to low */ 34 | GPIO_DIR_OUT_HIGH, /* Output, initialized to high */ 35 | } gpio_direction_t; 36 | 37 | typedef enum gpio_edge { 38 | GPIO_EDGE_NONE, /* No interrupt edge */ 39 | GPIO_EDGE_RISING, /* Rising edge 0 -> 1 */ 40 | GPIO_EDGE_FALLING, /* Falling edge 1 -> 0 */ 41 | GPIO_EDGE_BOTH /* Both edges X -> !X */ 42 | } gpio_edge_t; 43 | 44 | typedef enum gpio_event_clock { 45 | GPIO_EVENT_CLOCK_REALTIME, /* Realtime */ 46 | GPIO_EVENT_CLOCK_MONOTONIC, /* Monotonic */ 47 | GPIO_EVENT_CLOCK_HTE /* Hardware Timestamping Engine */ 48 | } gpio_event_clock_t; 49 | 50 | typedef enum gpio_bias { 51 | GPIO_BIAS_DEFAULT, /* Default line bias */ 52 | GPIO_BIAS_PULL_UP, /* Pull-up */ 53 | GPIO_BIAS_PULL_DOWN, /* Pull-down */ 54 | GPIO_BIAS_DISABLE, /* Disable line bias */ 55 | } gpio_bias_t; 56 | 57 | typedef enum gpio_drive { 58 | GPIO_DRIVE_DEFAULT, /* Default line drive (push-pull) */ 59 | GPIO_DRIVE_OPEN_DRAIN, /* Open drain */ 60 | GPIO_DRIVE_OPEN_SOURCE, /* Open source */ 61 | } gpio_drive_t; 62 | 63 | /* Configuration structure for gpio_open_*advanced() functions */ 64 | typedef struct gpio_config { 65 | gpio_direction_t direction; 66 | gpio_edge_t edge; 67 | gpio_event_clock_t event_clock; 68 | uint32_t debounce_us; 69 | gpio_bias_t bias; 70 | gpio_drive_t drive; 71 | bool inverted; 72 | const char *label; /* Can be NULL for default consumer label */ 73 | } gpio_config_t; 74 | 75 | typedef struct gpio_handle gpio_t; 76 | 77 | /* Primary Functions */ 78 | gpio_t *gpio_new(void); 79 | int gpio_open(gpio_t *gpio, const char *path, unsigned int line, gpio_direction_t direction); 80 | int gpio_open_name(gpio_t *gpio, const char *path, const char *name, gpio_direction_t direction); 81 | int gpio_open_advanced(gpio_t *gpio, const char *path, unsigned int line, const gpio_config_t *config); 82 | int gpio_open_name_advanced(gpio_t *gpio, const char *path, const char *name, const gpio_config_t *config); 83 | int gpio_open_sysfs(gpio_t *gpio, unsigned int line, gpio_direction_t direction); 84 | int gpio_read(gpio_t *gpio, bool *value); 85 | int gpio_write(gpio_t *gpio, bool value); 86 | int gpio_poll(gpio_t *gpio, int timeout_ms); 87 | int gpio_close(gpio_t *gpio); 88 | void gpio_free(gpio_t *gpio); 89 | 90 | /* Read Event (for character device GPIOs) */ 91 | int gpio_read_event(gpio_t *gpio, gpio_edge_t *edge, uint64_t *timestamp); 92 | 93 | /* Poll Multiple */ 94 | int gpio_poll_multiple(gpio_t **gpios, size_t count, int timeout_ms, bool *gpios_ready); 95 | 96 | /* Getters */ 97 | int gpio_get_direction(gpio_t *gpio, gpio_direction_t *direction); 98 | int gpio_get_edge(gpio_t *gpio, gpio_edge_t *edge); 99 | int gpio_get_event_clock(gpio_t *gpio, gpio_event_clock_t *event_clock); 100 | int gpio_get_debounce_us(gpio_t *gpio, uint32_t *debounce_us); 101 | int gpio_get_bias(gpio_t *gpio, gpio_bias_t *bias); 102 | int gpio_get_drive(gpio_t *gpio, gpio_drive_t *drive); 103 | int gpio_get_inverted(gpio_t *gpio, bool *inverted); 104 | 105 | /* Setters */ 106 | int gpio_set_direction(gpio_t *gpio, gpio_direction_t direction); 107 | int gpio_set_edge(gpio_t *gpio, gpio_edge_t edge); 108 | int gpio_set_event_clock(gpio_t *gpio, gpio_event_clock_t event_clock); 109 | int gpio_set_debounce_us(gpio_t *gpio, uint32_t debounce_us); 110 | int gpio_set_bias(gpio_t *gpio, gpio_bias_t bias); 111 | int gpio_set_drive(gpio_t *gpio, gpio_drive_t drive); 112 | int gpio_set_inverted(gpio_t *gpio, bool inverted); 113 | 114 | /* Miscellaneous Properties */ 115 | unsigned int gpio_line(gpio_t *gpio); 116 | int gpio_fd(gpio_t *gpio); 117 | int gpio_name(gpio_t *gpio, char *str, size_t len); 118 | int gpio_label(gpio_t *gpio, char *str, size_t len); 119 | int gpio_chip_fd(gpio_t *gpio); 120 | int gpio_chip_name(gpio_t *gpio, char *str, size_t len); 121 | int gpio_chip_label(gpio_t *gpio, char *str, size_t len); 122 | int gpio_tostring(gpio_t *gpio, char *str, size_t len); 123 | 124 | /* Error Handling */ 125 | int gpio_errno(gpio_t *gpio); 126 | const char *gpio_errmsg(gpio_t *gpio); 127 | 128 | #ifdef __cplusplus 129 | } 130 | #endif 131 | 132 | #endif 133 | 134 | -------------------------------------------------------------------------------- /tests/test_i2c.c: -------------------------------------------------------------------------------- 1 | /* 2 | * c-periphery 3 | * https://github.com/vsergeev/c-periphery 4 | * License: MIT 5 | */ 6 | 7 | #include "test.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | #include "../src/i2c.h" 19 | 20 | #define I2C_EEPROM_ADDRESS 0x51 21 | 22 | const char *i2c_bus_path; 23 | 24 | void test_arguments(void) { 25 | ptest(); 26 | 27 | /* No real argument validation needed in the i2c wrapper */ 28 | } 29 | 30 | void test_open_config_close(void) { 31 | i2c_t *i2c; 32 | 33 | ptest(); 34 | 35 | /* Allocate I2C */ 36 | i2c = i2c_new(); 37 | passert(i2c != NULL); 38 | 39 | /* Open invalid i2c bus */ 40 | passert(i2c_open(i2c, "/foo/bar") == I2C_ERROR_OPEN); 41 | 42 | /* Open legitimate i2c bus */ 43 | passert(i2c_open(i2c, i2c_bus_path) == 0); 44 | passert(i2c_close(i2c) == 0); 45 | 46 | /* Free I2C */ 47 | i2c_free(i2c); 48 | } 49 | 50 | void test_loopback(void) { 51 | i2c_t *i2c; 52 | uint8_t vector[32]; 53 | uint8_t buf[2 + 32]; 54 | unsigned int i; 55 | struct i2c_msg msgs[2]; 56 | 57 | ptest(); 58 | 59 | /* Allocate I2C */ 60 | i2c = i2c_new(); 61 | passert(i2c != NULL); 62 | 63 | passert(i2c_open(i2c, i2c_bus_path) == 0); 64 | 65 | /* Generate random byte vector */ 66 | srandom(1234); 67 | for (i = 0; i < sizeof(vector); i++) { 68 | vector[i] = (uint8_t)random(); 69 | } 70 | 71 | /* Write bytes to 0x100 */ 72 | /* S [ 0x51 W ] [ 0x01 ] [ 0x00 ] [ Data... ] P */ 73 | buf[0] = 0x01; 74 | buf[1] = 0x00; 75 | memcpy(buf + 2, vector, sizeof(vector)); 76 | msgs[0].addr = I2C_EEPROM_ADDRESS; 77 | msgs[0].flags = 0; /* Write */ 78 | msgs[0].len = sizeof(buf); 79 | msgs[0].buf = buf; 80 | passert(i2c_transfer(i2c, msgs, 1) == 0); 81 | 82 | /* Wait for Write Cycle */ 83 | usleep(10000); 84 | 85 | /* Read bytes from 0x100 */ 86 | /* S [ 0x51 W ] [ 0x01 ] [ 0x00 ] S [ 0x51 R ] [ Data... ] P */ 87 | buf[0] = 0x01; 88 | buf[1] = 0x00; 89 | memset(buf + 2, 0, sizeof(vector)); 90 | msgs[0].addr = I2C_EEPROM_ADDRESS; 91 | msgs[0].flags = 0; /* Write */ 92 | msgs[0].len = 2; 93 | msgs[0].buf = buf; 94 | msgs[1].addr = I2C_EEPROM_ADDRESS; 95 | msgs[1].flags = I2C_M_RD; /* Read */ 96 | msgs[1].len = sizeof(vector); 97 | msgs[1].buf = buf + 2; 98 | passert(i2c_transfer(i2c, msgs, 2) == 0); 99 | 100 | /* Verify bytes */ 101 | passert(memcmp(buf + 2, vector, sizeof(vector)) == 0); 102 | 103 | /* Close I2C */ 104 | passert(i2c_close(i2c) == 0); 105 | 106 | /* Free I2C */ 107 | i2c_free(i2c); 108 | } 109 | 110 | bool getc_yes(void) { 111 | char buf[4]; 112 | fgets(buf, sizeof(buf), stdin); 113 | return (buf[0] == 'y' || buf[0] == 'Y'); 114 | } 115 | 116 | void test_interactive(void) { 117 | char str[256]; 118 | i2c_t *i2c; 119 | uint8_t msg1[] = { 0xaa, 0xbb, 0xcc, 0xdd }; 120 | struct i2c_msg msgs[1]; 121 | 122 | ptest(); 123 | 124 | /* Allocate I2C */ 125 | i2c = i2c_new(); 126 | passert(i2c != NULL); 127 | 128 | passert(i2c_open(i2c, i2c_bus_path) == 0); 129 | 130 | printf("Starting interactive test. Get out your logic analyzer, buddy!\n"); 131 | printf("Press enter to continue...\n"); 132 | getc(stdin); 133 | 134 | /* Check tostring */ 135 | passert(i2c_tostring(i2c, str, sizeof(str)) > 0); 136 | printf("I2C description: %s\n", str); 137 | printf("I2C description looks OK? y/n\n"); 138 | passert(getc_yes()); 139 | 140 | /* There isn't much we can do without assuming a device on the other end, 141 | * because I2C needs an acknowledgement bit on each transferred byte. 142 | * 143 | * But we can send a transaction and expect it to time out. */ 144 | 145 | /* S [ 0x7a W ] [0xaa] [0xbb] [0xcc] [0xdd] */ 146 | msgs[0].addr = 0x7a; 147 | msgs[0].flags = 0; /* Write */ 148 | msgs[0].len = sizeof(msg1); 149 | msgs[0].buf = msg1; 150 | 151 | printf("Press enter to start transfer..."); 152 | getc(stdin); 153 | passert(i2c_transfer(i2c, msgs, 1) < 0); 154 | passert(i2c_errno(i2c) > 0); 155 | printf("I2C transfer occurred? y/n\n"); 156 | passert(getc_yes()); 157 | 158 | passert(i2c_close(i2c) == 0); 159 | 160 | /* Free I2C */ 161 | i2c_free(i2c); 162 | } 163 | 164 | int main(int argc, char *argv[]) { 165 | if (argc < 2) { 166 | fprintf(stderr, "Usage: %s \n\n", argv[0]); 167 | fprintf(stderr, "[1/4] Arguments test: No requirements.\n"); 168 | fprintf(stderr, "[2/4] Open/close test: I2C device should be real.\n"); 169 | fprintf(stderr, "[3/4] Loopback test: Expects 24XX32 EEPROM (or similar) at address 0x51.\n"); 170 | fprintf(stderr, "[4/4] Interactive test: I2C bus should be observed with an oscilloscope or logic analyzer.\n\n"); 171 | fprintf(stderr, "Hint: for Raspberry Pi 3, enable I2C1 with:\n"); 172 | fprintf(stderr, " $ echo \"dtparam=i2c_arm=on\" | sudo tee -a /boot/firmware/config.txt\n"); 173 | fprintf(stderr, " $ sudo reboot\n"); 174 | fprintf(stderr, "Use pins I2C1 SDA (header pin 2) and I2C1 SCL (header pin 3),\n"); 175 | fprintf(stderr, "and run this test with:\n"); 176 | fprintf(stderr, " %s /dev/i2c-1\n\n", argv[0]); 177 | exit(1); 178 | } 179 | 180 | i2c_bus_path = argv[1]; 181 | 182 | test_arguments(); 183 | printf(" " STR_OK " Arguments test passed.\n\n"); 184 | test_open_config_close(); 185 | printf(" " STR_OK " Open/close test passed.\n\n"); 186 | test_loopback(); 187 | printf(" " STR_OK " Loopback test passed.\n\n"); 188 | test_interactive(); 189 | printf(" " STR_OK " Interactive test passed.\n\n"); 190 | 191 | printf("All tests passed!\n"); 192 | return 0; 193 | } 194 | 195 | -------------------------------------------------------------------------------- /docs/i2c.md: -------------------------------------------------------------------------------- 1 | ### NAME 2 | 3 | I2C wrapper functions for Linux userspace `i2c-dev` devices. 4 | 5 | ### SYNOPSIS 6 | 7 | ``` c 8 | #include 9 | 10 | /* Primary Functions */ 11 | i2c_t *i2c_new(void); 12 | int i2c_open(i2c_t *i2c, const char *device); 13 | int i2c_transfer(i2c_t *i2c, struct i2c_msg *msgs, size_t count); 14 | int i2c_close(i2c_t *i2c); 15 | void i2c_free(i2c_t *i2c); 16 | 17 | /* Miscellaneous */ 18 | int i2c_fd(i2c_t *i2c); 19 | int i2c_tostring(i2c_t *i2c, char *str, size_t len); 20 | 21 | /* Error Handling */ 22 | int i2c_errno(i2c_t *i2c); 23 | const char *i2c_errmsg(i2c_t *i2c); 24 | 25 | /* struct i2c_msg from : 26 | 27 | struct i2c_msg { 28 | __u16 addr; 29 | __u16 flags; 30 | #define I2C_M_TEN 0x0010 31 | #define I2C_M_RD 0x0001 32 | #define I2C_M_STOP 0x8000 33 | #define I2C_M_NOSTART 0x4000 34 | #define I2C_M_REV_DIR_ADDR 0x2000 35 | #define I2C_M_IGNORE_NAK 0x1000 36 | #define I2C_M_NO_RD_ACK 0x0800 37 | #define I2C_M_RECV_LEN 0x0400 38 | __u16 len; 39 | __u8 *buf; 40 | }; 41 | */ 42 | ``` 43 | 44 | ### DESCRIPTION 45 | 46 | ``` c 47 | i2c_t *i2c_new(void); 48 | ``` 49 | Allocate an I2C handle. 50 | 51 | Returns a valid handle on success, or NULL on failure. 52 | 53 | ------ 54 | 55 | ``` c 56 | int i2c_open(i2c_t *i2c, const char *device); 57 | ``` 58 | Open the `i2c-dev` device at the specified path (e.g. "/dev/i2c-1"). 59 | 60 | `i2c` should be a valid pointer to an allocated I2C handle structure. 61 | 62 | Returns 0 on success, or a negative [I2C error code](#return-value) on failure. 63 | 64 | ------ 65 | 66 | ``` c 67 | int i2c_transfer(i2c_t *i2c, struct i2c_msg *msgs, size_t count); 68 | ``` 69 | Transfer `count` number of `struct i2c_msg` I2C messages. 70 | 71 | `i2c` should be a valid pointer to an I2C handle opened with `i2c_open()`. `msgs` should be a pointer to an array of `struct i2c_msg` (defined in linux/i2c.h). 72 | 73 | Each I2C message structure (see [above](#synopsis)) specifies the transfer of a consecutive number of bytes to a slave address. The slave address, message flags, buffer length, and pointer to a byte buffer should be specified in each message. The message flags specify whether the message is a read (I2C_M_RD) or write (0) transaction, as well as additional options selected by the bitwise OR of their bitmasks. 74 | 75 | Returns 0 on success, or a negative [I2C error code](#return-value) on failure. 76 | 77 | ------ 78 | 79 | ``` c 80 | int i2c_close(i2c_t *i2c); 81 | ``` 82 | Close the `i2c-dev` device. 83 | 84 | `i2c` should be a valid pointer to an I2C handle opened with `i2c_open()`. 85 | 86 | Returns 0 on success, or a negative [I2C error code](#return-value) on failure. 87 | 88 | ------ 89 | 90 | ``` c 91 | void i2c_free(i2c_t *i2c); 92 | ``` 93 | Free an I2C handle. 94 | 95 | ------ 96 | 97 | ``` c 98 | int i2c_fd(i2c_t *i2c); 99 | ``` 100 | Return the file descriptor (for the underlying `i2c-dev` device) of the I2C handle. 101 | 102 | `i2c` should be a valid pointer to an I2C handle opened with `i2c_open()`. 103 | 104 | This function is a simple accessor to the I2C handle structure and always succeeds. 105 | 106 | ------ 107 | 108 | ``` c 109 | int i2c_tostring(i2c_t *i2c, char *str, size_t len); 110 | ``` 111 | Return a string representation of the I2C handle. 112 | 113 | `i2c` should be a valid pointer to an I2C handle opened with `i2c_open()`. 114 | 115 | This function behaves and returns like `snprintf()`. 116 | 117 | ------ 118 | 119 | ``` c 120 | int i2c_errno(i2c_t *i2c); 121 | ``` 122 | Return the libc errno of the last failure that occurred. 123 | 124 | `i2c` should be a valid pointer to an I2C handle opened with `i2c_open()`. 125 | 126 | ------ 127 | 128 | ``` c 129 | const char *i2c_errmsg(i2c_t *i2c); 130 | ``` 131 | Return a human readable error message of the last failure that occurred. 132 | 133 | `i2c` should be a valid pointer to an I2C handle opened with `i2c_open()`. 134 | 135 | ### RETURN VALUE 136 | 137 | The periphery I2C functions return 0 on success or one of the negative error codes below on failure. 138 | 139 | The libc errno of the failure in an underlying libc library call can be obtained with the `i2c_errno()` helper function. A human readable error message can be obtained with the `i2c_errmsg()` helper function. 140 | 141 | | Error Code | Description | 142 | |---------------------------|-----------------------------------| 143 | | `I2C_ERROR_ARG` | Invalid arguments | 144 | | `I2C_ERROR_OPEN` | Opening I2C device | 145 | | `I2C_ERROR_QUERY` | Querying I2C device attribtues | 146 | | `I2C_ERROR_NOT_SUPPORTED` | I2C not supported on this device | 147 | | `I2C_ERROR_TRANSFER` | I2C transfer | 148 | | `I2C_ERROR_CLOSE` | Closing I2C device | 149 | 150 | ### EXAMPLE 151 | 152 | ``` c 153 | #include 154 | #include 155 | #include 156 | 157 | #include "i2c.h" 158 | 159 | #define EEPROM_I2C_ADDR 0x50 160 | 161 | int main(void) { 162 | i2c_t *i2c; 163 | 164 | i2c = i2c_new(); 165 | 166 | /* Open the i2c-0 bus */ 167 | if (i2c_open(i2c, "/dev/i2c-0") < 0) { 168 | fprintf(stderr, "i2c_open(): %s\n", i2c_errmsg(i2c)); 169 | exit(1); 170 | } 171 | 172 | /* Read byte at address 0x100 of EEPROM */ 173 | uint8_t msg_addr[2] = { 0x01, 0x00 }; 174 | uint8_t msg_data[1] = { 0xff, }; 175 | struct i2c_msg msgs[2] = 176 | { 177 | /* Write 16-bit address */ 178 | { .addr = EEPROM_I2C_ADDR, .flags = 0, .len = 2, .buf = msg_addr }, 179 | /* Read 8-bit data */ 180 | { .addr = EEPROM_I2C_ADDR, .flags = I2C_M_RD, .len = 1, .buf = msg_data}, 181 | }; 182 | 183 | /* Transfer a transaction with two I2C messages */ 184 | if (i2c_transfer(i2c, msgs, 2) < 0) { 185 | fprintf(stderr, "i2c_transfer(): %s\n", i2c_errmsg(i2c)); 186 | exit(1); 187 | } 188 | 189 | printf("0x%02x%02x: %02x\n", msg_addr[0], msg_addr[1], msg_data[0]); 190 | 191 | i2c_close(i2c); 192 | 193 | i2c_free(i2c); 194 | 195 | return 0; 196 | } 197 | ``` 198 | 199 | -------------------------------------------------------------------------------- /tests/test_led.c: -------------------------------------------------------------------------------- 1 | /* 2 | * c-periphery 3 | * https://github.com/vsergeev/c-periphery 4 | * License: MIT 5 | */ 6 | 7 | #include "test.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "../src/led.h" 15 | 16 | const char *device; 17 | 18 | void test_arguments(void) { 19 | ptest(); 20 | 21 | /* No real argument validation needed in the LED wrapper */ 22 | } 23 | 24 | void test_open_config_close(void) { 25 | led_t *led; 26 | char name[64]; 27 | char trigger[64]; 28 | unsigned int max_brightness; 29 | unsigned int brightness; 30 | unsigned int triggers_count; 31 | bool value; 32 | 33 | ptest(); 34 | 35 | /* Allocate LED */ 36 | led = led_new(); 37 | passert(led != NULL); 38 | 39 | /* Open non-existent LED */ 40 | passert(led_open(led, "nonexistent") == LED_ERROR_OPEN); 41 | 42 | /* Open legitimate LED */ 43 | passert(led_open(led, device) == 0); 44 | 45 | /* Check properties */ 46 | passert(led_name(led, name, sizeof(name)) == 0); 47 | passert(strcmp(name, device) == 0); 48 | 49 | /* Check max brightness */ 50 | passert(led_get_max_brightness(led, &max_brightness) == 0); 51 | passert(max_brightness > 0); 52 | 53 | /* Check setting invalid brightness */ 54 | passert(led_set_brightness(led, max_brightness + 1) == LED_ERROR_ARG); 55 | 56 | /* Read trigger */ 57 | passert(led_get_trigger(led, trigger, sizeof(trigger)) == 0); 58 | /* Read triggers count */ 59 | passert(led_get_triggers_count(led, &triggers_count) == 0); 60 | passert(triggers_count > 0); 61 | /* Read each trigger */ 62 | for (unsigned int i = 0; i < triggers_count; i++) { 63 | passert(led_get_triggers_entry(led, i, trigger, sizeof(trigger)) == 0); 64 | } 65 | 66 | /* Write true, read true, check brightness is non-zero */ 67 | passert(led_write(led, true) == 0); 68 | usleep(10000); 69 | passert(led_read(led, &value) == 0); 70 | passert(value == true); 71 | passert(led_get_brightness(led, &brightness) == 0); 72 | passert(brightness > 0); 73 | 74 | /* Write false, read false, check brightness is zero */ 75 | passert(led_write(led, false) == 0); 76 | usleep(10000); 77 | passert(led_read(led, &value) == 0); 78 | passert(value == false); 79 | passert(led_get_brightness(led, &brightness) == 0); 80 | passert(brightness == 0); 81 | 82 | /* Set brightness to 1, check brightness */ 83 | passert(led_set_brightness(led, 1) == 0); 84 | usleep(10000); 85 | passert(led_get_brightness(led, &brightness) == 0); 86 | passert(brightness >= 1); 87 | 88 | /* Set brightness to 0, check brightness */ 89 | passert(led_set_brightness(led, 0) == 0); 90 | usleep(10000); 91 | passert(led_get_brightness(led, &brightness) == 0); 92 | passert(brightness == 0); 93 | 94 | /* Set trigger to default, check isn't none */ 95 | passert(led_set_trigger(led, "default") == 0); 96 | passert(led_get_trigger(led, trigger, sizeof(trigger)) == 0); 97 | passert(strcmp(trigger, "none") != 0); 98 | 99 | /* Set trigger to none, check trigger */ 100 | passert(led_set_trigger(led, "none") == 0); 101 | passert(led_get_trigger(led, trigger, sizeof(trigger)) == 0); 102 | passert(strcmp(trigger, "none") == 0); 103 | 104 | passert(led_close(led) == 0); 105 | 106 | /* Free LED */ 107 | led_free(led); 108 | } 109 | 110 | void test_loopback(void) { 111 | ptest(); 112 | } 113 | 114 | bool getc_yes(void) { 115 | char buf[4]; 116 | fgets(buf, sizeof(buf), stdin); 117 | return (buf[0] == 'y' || buf[0] == 'Y'); 118 | } 119 | 120 | void test_interactive(void) { 121 | char str[256]; 122 | char trigger[64]; 123 | unsigned int triggers_count; 124 | led_t *led; 125 | 126 | ptest(); 127 | 128 | /* Allocate LED */ 129 | led = led_new(); 130 | passert(led != NULL); 131 | 132 | passert(led_open(led, device) == 0); 133 | 134 | printf("Starting interactive test...\n"); 135 | printf("Press enter to continue...\n"); 136 | getc(stdin); 137 | 138 | /* Check tostring */ 139 | passert(led_tostring(led, str, sizeof(str)) > 0); 140 | printf("LED description: %s\n", str); 141 | printf("LED description looks OK? y/n\n"); 142 | passert(getc_yes()); 143 | 144 | /* Check triggers */ 145 | passert(led_get_trigger(led, trigger, sizeof(trigger)) == 0); 146 | passert(led_get_triggers_count(led, &triggers_count) == 0); 147 | printf("LED active trigger: %s\n", trigger); 148 | printf("LED available triggers (%d): ", triggers_count); 149 | for (unsigned int i = 0; i < triggers_count; i++) { 150 | assert(led_get_triggers_entry(led, i, trigger, sizeof(trigger)) == 0); 151 | printf("%s ", trigger); 152 | } 153 | printf("\nLED triggers look OK? y/n\n"); 154 | passert(getc_yes()); 155 | 156 | /* Turn LED off */ 157 | passert(led_write(led, false) == 0); 158 | printf("LED is off? y/n\n"); 159 | passert(getc_yes()); 160 | 161 | /* Turn LED on */ 162 | passert(led_write(led, true) == 0); 163 | printf("LED is on? y/n\n"); 164 | passert(getc_yes()); 165 | 166 | /* Turn LED off */ 167 | passert(led_write(led, false) == 0); 168 | printf("LED is off? y/n\n"); 169 | passert(getc_yes()); 170 | 171 | /* Turn LED on */ 172 | passert(led_write(led, true) == 0); 173 | printf("LED is on? y/n\n"); 174 | passert(getc_yes()); 175 | 176 | /* Restore default trigger */ 177 | passert(led_set_trigger(led, "default") == 0); 178 | 179 | passert(led_close(led) == 0); 180 | 181 | /* Free LED */ 182 | led_free(led); 183 | } 184 | 185 | int main(int argc, char *argv[]) { 186 | if (argc < 2) { 187 | fprintf(stderr, "Usage: %s \n\n", argv[0]); 188 | fprintf(stderr, "[1/4] Arguments test: No requirements.\n"); 189 | fprintf(stderr, "[2/4] Open/close test: LED should be real.\n"); 190 | fprintf(stderr, "[3/4] Loopback test: No test.\n"); 191 | fprintf(stderr, "[4/4] Interactive test: LED should be observed.\n\n"); 192 | fprintf(stderr, "Hint: for Raspberry Pi 3, disable triggers for PWR:\n"); 193 | fprintf(stderr, " $ echo none > /sys/class/leds/PWR/trigger\n"); 194 | fprintf(stderr, "Observe PWR (red power LED), and run this test:\n"); 195 | fprintf(stderr, " %s PWR\n\n", argv[0]); 196 | exit(1); 197 | } 198 | 199 | device = argv[1]; 200 | 201 | test_arguments(); 202 | printf(" " STR_OK " Arguments test passed.\n\n"); 203 | test_open_config_close(); 204 | printf(" " STR_OK " Open/close test passed.\n\n"); 205 | test_loopback(); 206 | printf(" " STR_OK " Loopback test passed.\n\n"); 207 | test_interactive(); 208 | printf(" " STR_OK " Interactive test passed.\n\n"); 209 | 210 | printf("All tests passed!\n"); 211 | return 0; 212 | } 213 | 214 | -------------------------------------------------------------------------------- /src/gpio.c: -------------------------------------------------------------------------------- 1 | /* 2 | * c-periphery 3 | * https://github.com/vsergeev/c-periphery 4 | * License: MIT 5 | */ 6 | 7 | #define _XOPEN_SOURCE 600 /* for POLLRDNORM */ 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include 20 | 21 | #include "gpio.h" 22 | #include "gpio_internal.h" 23 | 24 | extern const struct gpio_ops gpio_cdev_ops; 25 | extern const struct gpio_ops gpio_sysfs_ops; 26 | 27 | gpio_t *gpio_new(void) { 28 | gpio_t *gpio = calloc(1, sizeof(gpio_t)); 29 | if (gpio == NULL) 30 | return NULL; 31 | 32 | #if PERIPHERY_GPIO_CDEV_SUPPORT 33 | gpio->ops = &gpio_cdev_ops; 34 | gpio->u.cdev.line_fd = -1; 35 | gpio->u.cdev.chip_fd = -1; 36 | #else 37 | gpio->ops = &gpio_sysfs_ops; 38 | gpio->u.sysfs.line_fd = -1; 39 | #endif 40 | 41 | return gpio; 42 | } 43 | 44 | int gpio_read(gpio_t *gpio, bool *value) { 45 | return gpio->ops->read(gpio, value); 46 | } 47 | 48 | int gpio_write(gpio_t *gpio, bool value) { 49 | return gpio->ops->write(gpio, value); 50 | } 51 | 52 | int gpio_poll(gpio_t *gpio, int timeout_ms) { 53 | return gpio->ops->poll(gpio, timeout_ms); 54 | } 55 | 56 | int gpio_close(gpio_t *gpio) { 57 | return gpio->ops->close(gpio); 58 | } 59 | 60 | void gpio_free(gpio_t *gpio) { 61 | free(gpio); 62 | } 63 | 64 | int gpio_read_event(gpio_t *gpio, gpio_edge_t *edge, uint64_t *timestamp) { 65 | return gpio->ops->read_event(gpio, edge, timestamp); 66 | } 67 | 68 | int gpio_poll_multiple(gpio_t **gpios, size_t count, int timeout_ms, bool *gpios_ready) { 69 | struct pollfd fds[count]; 70 | int ret; 71 | 72 | /* Setup pollfd structs */ 73 | for (size_t i = 0; i < count; i++) { 74 | fds[i].fd = gpio_fd(gpios[i]); 75 | fds[i].events = (gpios[i]->ops == &gpio_sysfs_ops) ? 76 | (POLLPRI | POLLERR) : (POLLIN | POLLRDNORM); 77 | if (gpios_ready) 78 | gpios_ready[i] = false; 79 | } 80 | 81 | /* Poll */ 82 | if ((ret = poll(fds, count, timeout_ms)) < 0) 83 | return GPIO_ERROR_IO; 84 | 85 | /* Event occurred */ 86 | if (ret) { 87 | for (size_t i = 0; i < count; i++) { 88 | /* Set ready GPIOs */ 89 | if (gpios_ready) 90 | gpios_ready[i] = fds[i].revents != 0; 91 | 92 | /* Rewind GPIO if it is a sysfs GPIO */ 93 | if (gpios[i]->ops == &gpio_sysfs_ops) { 94 | if (lseek(gpios[i]->u.sysfs.line_fd, 0, SEEK_SET) < 0) 95 | return GPIO_ERROR_IO; 96 | } 97 | } 98 | 99 | return ret; 100 | } 101 | 102 | /* Timed out */ 103 | return 0; 104 | } 105 | 106 | int gpio_get_direction(gpio_t *gpio, gpio_direction_t *direction) { 107 | return gpio->ops->get_direction(gpio, direction); 108 | } 109 | 110 | int gpio_get_edge(gpio_t *gpio, gpio_edge_t *edge) { 111 | return gpio->ops->get_edge(gpio, edge); 112 | } 113 | 114 | int gpio_get_event_clock(gpio_t *gpio, gpio_event_clock_t *event_clock) { 115 | return gpio->ops->get_event_clock(gpio, event_clock); 116 | } 117 | 118 | int gpio_get_bias(gpio_t *gpio, gpio_bias_t *bias) { 119 | return gpio->ops->get_bias(gpio, bias); 120 | } 121 | 122 | int gpio_get_debounce_us(gpio_t *gpio, uint32_t *debounce_us) { 123 | return gpio->ops->get_debounce_us(gpio, debounce_us); 124 | } 125 | 126 | int gpio_get_drive(gpio_t *gpio, gpio_drive_t *drive) { 127 | return gpio->ops->get_drive(gpio, drive); 128 | } 129 | 130 | int gpio_get_inverted(gpio_t *gpio, bool *inverted) { 131 | return gpio->ops->get_inverted(gpio, inverted); 132 | } 133 | 134 | int gpio_set_direction(gpio_t *gpio, gpio_direction_t direction) { 135 | return gpio->ops->set_direction(gpio, direction); 136 | } 137 | 138 | int gpio_set_edge(gpio_t *gpio, gpio_edge_t edge) { 139 | return gpio->ops->set_edge(gpio, edge); 140 | } 141 | 142 | int gpio_set_event_clock(gpio_t *gpio, gpio_event_clock_t event_clock) { 143 | return gpio->ops->set_event_clock(gpio, event_clock); 144 | } 145 | 146 | int gpio_set_bias(gpio_t *gpio, gpio_bias_t bias) { 147 | return gpio->ops->set_bias(gpio, bias); 148 | } 149 | 150 | int gpio_set_debounce_us(gpio_t *gpio, uint32_t debounce_us) { 151 | return gpio->ops->set_debounce_us(gpio, debounce_us); 152 | } 153 | 154 | int gpio_set_drive(gpio_t *gpio, gpio_drive_t drive) { 155 | return gpio->ops->set_drive(gpio, drive); 156 | } 157 | 158 | int gpio_set_inverted(gpio_t *gpio, bool inverted) { 159 | return gpio->ops->set_inverted(gpio, inverted); 160 | } 161 | 162 | unsigned int gpio_line(gpio_t *gpio) { 163 | return gpio->ops->line(gpio); 164 | } 165 | 166 | int gpio_fd(gpio_t *gpio) { 167 | return gpio->ops->fd(gpio); 168 | } 169 | 170 | int gpio_name(gpio_t *gpio, char *str, size_t len) { 171 | return gpio->ops->name(gpio, str, len); 172 | } 173 | 174 | int gpio_label(gpio_t *gpio, char *str, size_t len) { 175 | return gpio->ops->label(gpio, str, len); 176 | } 177 | 178 | int gpio_chip_fd(gpio_t *gpio) { 179 | return gpio->ops->chip_fd(gpio); 180 | } 181 | 182 | int gpio_chip_name(gpio_t *gpio, char *str, size_t len) { 183 | return gpio->ops->chip_name(gpio, str, len); 184 | } 185 | 186 | int gpio_chip_label(gpio_t *gpio, char *str, size_t len) { 187 | return gpio->ops->chip_label(gpio, str, len); 188 | } 189 | 190 | int gpio_tostring(gpio_t *gpio, char *str, size_t len) { 191 | return gpio->ops->tostring(gpio, str, len); 192 | } 193 | 194 | int gpio_errno(gpio_t *gpio) { 195 | return gpio->error.c_errno; 196 | } 197 | 198 | const char *gpio_errmsg(gpio_t *gpio) { 199 | return gpio->error.errmsg; 200 | } 201 | 202 | #if !PERIPHERY_GPIO_CDEV_SUPPORT 203 | 204 | int gpio_open(gpio_t *gpio, const char *path, unsigned int line, gpio_direction_t direction) { 205 | (void)path; 206 | (void)line; 207 | (void)direction; 208 | return _gpio_error(gpio, GPIO_ERROR_UNSUPPORTED, 0, "c-periphery library built without character device GPIO support."); 209 | } 210 | 211 | int gpio_open_name(gpio_t *gpio, const char *path, const char *name, gpio_direction_t direction) { 212 | (void)gpio; 213 | (void)path; 214 | (void)name; 215 | (void)direction; 216 | return _gpio_error(gpio, GPIO_ERROR_UNSUPPORTED, 0, "c-periphery library built without character device GPIO support."); 217 | } 218 | 219 | int gpio_open_advanced(gpio_t *gpio, const char *path, unsigned int line, const gpio_config_t *config) { 220 | (void)path; 221 | (void)line; 222 | (void)config; 223 | return _gpio_error(gpio, GPIO_ERROR_UNSUPPORTED, 0, "c-periphery library built without character device GPIO support."); 224 | } 225 | 226 | int gpio_open_name_advanced(gpio_t *gpio, const char *path, const char *name, const gpio_config_t *config) { 227 | (void)path; 228 | (void)name; 229 | (void)config; 230 | return _gpio_error(gpio, GPIO_ERROR_UNSUPPORTED, 0, "c-periphery library built without character device GPIO support."); 231 | } 232 | 233 | #endif 234 | 235 | -------------------------------------------------------------------------------- /src/mmio.c: -------------------------------------------------------------------------------- 1 | /* 2 | * c-periphery 3 | * https://github.com/vsergeev/c-periphery 4 | * License: MIT 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include "mmio.h" 20 | 21 | struct mmio_handle { 22 | uintptr_t base, aligned_base; 23 | size_t size, aligned_size; 24 | void *ptr; 25 | 26 | struct { 27 | int c_errno; 28 | char errmsg[96]; 29 | } error; 30 | }; 31 | 32 | static int _mmio_error(mmio_t *mmio, int code, int c_errno, const char *fmt, ...) { 33 | va_list ap; 34 | 35 | mmio->error.c_errno = c_errno; 36 | 37 | va_start(ap, fmt); 38 | vsnprintf(mmio->error.errmsg, sizeof(mmio->error.errmsg), fmt, ap); 39 | va_end(ap); 40 | 41 | /* Tack on strerror() and errno */ 42 | if (c_errno) { 43 | char buf[64] = {0}; 44 | strerror_r(c_errno, buf, sizeof(buf)); 45 | snprintf(mmio->error.errmsg+strlen(mmio->error.errmsg), sizeof(mmio->error.errmsg)-strlen(mmio->error.errmsg), ": %s [errno %d]", buf, c_errno); 46 | } 47 | 48 | return code; 49 | } 50 | 51 | mmio_t *mmio_new(void) { 52 | return calloc(1, sizeof(mmio_t)); 53 | } 54 | 55 | void mmio_free(mmio_t *mmio) { 56 | free(mmio); 57 | } 58 | 59 | int mmio_open(mmio_t *mmio, uintptr_t base, size_t size) { 60 | return mmio_open_advanced(mmio, base, size, "/dev/mem"); 61 | } 62 | 63 | int mmio_open_advanced(mmio_t *mmio, uintptr_t base, size_t size, const char *path) { 64 | int fd; 65 | 66 | memset(mmio, 0, sizeof(mmio_t)); 67 | mmio->base = base; 68 | mmio->size = size; 69 | mmio->aligned_base = mmio->base - (mmio->base % sysconf(_SC_PAGESIZE)); 70 | mmio->aligned_size = mmio->size + (mmio->base - mmio->aligned_base); 71 | 72 | /* Open memory */ 73 | if ((fd = open(path, O_RDWR | O_SYNC)) < 0) 74 | return _mmio_error(mmio, MMIO_ERROR_OPEN, errno, "Opening %s", path); 75 | 76 | /* Map memory */ 77 | if ((mmio->ptr = mmap(0, mmio->aligned_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, mmio->aligned_base)) == MAP_FAILED) { 78 | int errsv = errno; 79 | close(fd); 80 | return _mmio_error(mmio, MMIO_ERROR_OPEN, errsv, "Mapping memory"); 81 | } 82 | 83 | /* Close memory */ 84 | if (close(fd) < 0) { 85 | int errsv = errno; 86 | munmap(mmio->ptr, mmio->aligned_size); 87 | mmio->ptr = 0; 88 | return _mmio_error(mmio, MMIO_ERROR_OPEN, errsv, "Closing %s", path); 89 | } 90 | 91 | return 0; 92 | } 93 | 94 | void *mmio_ptr(mmio_t *mmio) { 95 | return (void *)((uint8_t *)mmio->ptr + (mmio->base - mmio->aligned_base)); 96 | } 97 | 98 | /* WARNING: These functions may trigger a bus fault on some CPUs if an 99 | * unaligned address is accessed! */ 100 | 101 | int mmio_read64(mmio_t *mmio, uintptr_t offset, uint64_t *value) { 102 | offset += (mmio->base - mmio->aligned_base); 103 | if ((offset+8) > mmio->aligned_size) 104 | return _mmio_error(mmio, MMIO_ERROR_ARG, 0, "Offset out of bounds"); 105 | 106 | *value = *(volatile uint64_t *)(((volatile uint8_t *)mmio->ptr) + offset); 107 | return 0; 108 | } 109 | 110 | int mmio_read32(mmio_t *mmio, uintptr_t offset, uint32_t *value) { 111 | offset += (mmio->base - mmio->aligned_base); 112 | if ((offset+4) > mmio->aligned_size) 113 | return _mmio_error(mmio, MMIO_ERROR_ARG, 0, "Offset out of bounds"); 114 | 115 | *value = *(volatile uint32_t *)(((volatile uint8_t *)mmio->ptr) + offset); 116 | return 0; 117 | } 118 | 119 | int mmio_read16(mmio_t *mmio, uintptr_t offset, uint16_t *value) { 120 | offset += (mmio->base - mmio->aligned_base); 121 | if ((offset+2) > mmio->aligned_size) 122 | return _mmio_error(mmio, MMIO_ERROR_ARG, 0, "Offset out of bounds"); 123 | 124 | *value = *(volatile uint16_t *)(((volatile uint8_t *)mmio->ptr) + offset); 125 | return 0; 126 | } 127 | 128 | int mmio_read8(mmio_t *mmio, uintptr_t offset, uint8_t *value) { 129 | offset += (mmio->base - mmio->aligned_base); 130 | if ((offset+1) > mmio->aligned_size) 131 | return _mmio_error(mmio, MMIO_ERROR_ARG, 0, "Offset out of bounds"); 132 | 133 | *value = *(volatile uint8_t *)(((volatile uint8_t *)mmio->ptr) + offset); 134 | return 0; 135 | } 136 | 137 | int mmio_read(mmio_t *mmio, uintptr_t offset, uint8_t *buf, size_t len) { 138 | offset += (mmio->base - mmio->aligned_base); 139 | if ((offset+len) > mmio->aligned_size) 140 | return _mmio_error(mmio, MMIO_ERROR_ARG, 0, "Offset out of bounds"); 141 | 142 | memcpy((void *)buf, (const void *)(((volatile uint8_t *)mmio->ptr) + offset), len); 143 | return 0; 144 | } 145 | 146 | int mmio_write64(mmio_t *mmio, uintptr_t offset, uint64_t value) { 147 | offset += (mmio->base - mmio->aligned_base); 148 | if ((offset+8) > mmio->aligned_size) 149 | return _mmio_error(mmio, MMIO_ERROR_ARG, 0, "Offset out of bounds"); 150 | 151 | *(volatile uint64_t *)(((volatile uint8_t *)mmio->ptr) + offset) = value; 152 | return 0; 153 | } 154 | 155 | int mmio_write32(mmio_t *mmio, uintptr_t offset, uint32_t value) { 156 | offset += (mmio->base - mmio->aligned_base); 157 | if ((offset+4) > mmio->aligned_size) 158 | return _mmio_error(mmio, MMIO_ERROR_ARG, 0, "Offset out of bounds"); 159 | 160 | *(volatile uint32_t *)(((volatile uint8_t *)mmio->ptr) + offset) = value; 161 | return 0; 162 | } 163 | 164 | int mmio_write16(mmio_t *mmio, uintptr_t offset, uint16_t value) { 165 | offset += (mmio->base - mmio->aligned_base); 166 | if ((offset+2) > mmio->aligned_size) 167 | return _mmio_error(mmio, MMIO_ERROR_ARG, 0, "Offset out of bounds"); 168 | 169 | *(volatile uint16_t *)(((volatile uint8_t *)mmio->ptr) + offset) = value; 170 | return 0; 171 | } 172 | 173 | int mmio_write8(mmio_t *mmio, uintptr_t offset, uint8_t value) { 174 | offset += (mmio->base - mmio->aligned_base); 175 | if ((offset+1) > mmio->aligned_size) 176 | return _mmio_error(mmio, MMIO_ERROR_ARG, 0, "Offset out of bounds"); 177 | 178 | *(volatile uint8_t *)(((volatile uint8_t *)mmio->ptr) + offset) = value; 179 | return 0; 180 | } 181 | 182 | int mmio_write(mmio_t *mmio, uintptr_t offset, const uint8_t *buf, size_t len) { 183 | offset += (mmio->base - mmio->aligned_base); 184 | if ((offset+len) > mmio->aligned_size) 185 | return _mmio_error(mmio, MMIO_ERROR_ARG, 0, "Offset out of bounds"); 186 | 187 | memcpy((void *)(((volatile uint8_t *)mmio->ptr) + offset), (const void *)buf, len); 188 | return 0; 189 | } 190 | 191 | int mmio_close(mmio_t *mmio) { 192 | if (!mmio->ptr) 193 | return 0; 194 | 195 | /* Unmap memory */ 196 | if (munmap(mmio->ptr, mmio->aligned_size) < 0) 197 | return _mmio_error(mmio, MMIO_ERROR_CLOSE, errno, "Unmapping memory"); 198 | 199 | mmio->ptr = 0; 200 | 201 | return 0; 202 | } 203 | 204 | int mmio_tostring(mmio_t *mmio, char *str, size_t len) { 205 | return snprintf(str, len, "MMIO 0x%08zx (ptr=%p, size=%zu)", mmio->base, mmio->ptr, mmio->size); 206 | } 207 | 208 | const char *mmio_errmsg(mmio_t *mmio) { 209 | return mmio->error.errmsg; 210 | } 211 | 212 | int mmio_errno(mmio_t *mmio) { 213 | return mmio->error.c_errno; 214 | } 215 | 216 | uintptr_t mmio_base(mmio_t *mmio) { 217 | return mmio->base; 218 | } 219 | 220 | size_t mmio_size(mmio_t *mmio) { 221 | return mmio->size; 222 | } 223 | 224 | -------------------------------------------------------------------------------- /docs/led.md: -------------------------------------------------------------------------------- 1 | ### NAME 2 | 3 | LED wrapper functions for Linux userspace sysfs LEDs. 4 | 5 | ### SYNOPSIS 6 | 7 | ``` c 8 | #include 9 | 10 | /* Primary Functions */ 11 | led_t *led_new(void); 12 | int led_open(led_t *led, const char *name); 13 | int led_read(led_t *led, bool *value); 14 | int led_write(led_t *led, bool value); 15 | int led_close(led_t *led); 16 | void led_free(led_t *led); 17 | 18 | /* Getters */ 19 | int led_get_brightness(led_t *led, unsigned int *brightness); 20 | int led_get_max_brightness(led_t *led, unsigned int *max_brightness); 21 | int led_get_trigger(led_t *led, char *str, size_t len); 22 | int led_get_triggers_entry(led_t *led, unsigned int index, char *str, size_t len); 23 | int led_get_triggers_count(led_t *led, unsigned int *count); 24 | 25 | /* Setters */ 26 | int led_set_brightness(led_t *led, unsigned int brightness); 27 | int led_set_trigger(led_t *led, const char *trigger); 28 | 29 | /* Miscellaneous */ 30 | int led_name(led_t *led, char *str, size_t len); 31 | int led_tostring(led_t *led, char *str, size_t len); 32 | 33 | /* Error Handling */ 34 | int led_errno(led_t *led); 35 | const char *led_errmsg(led_t *led); 36 | ``` 37 | 38 | ### DESCRIPTION 39 | 40 | ``` c 41 | led_t *led_new(void); 42 | ``` 43 | Allocate an LED handle. 44 | 45 | Returns a valid handle on success, or NULL on failure. 46 | 47 | ------ 48 | 49 | ``` c 50 | int led_open(led_t *led, const char *name); 51 | ``` 52 | Open the sysfs LED with the specified name. 53 | 54 | `led` should be a valid pointer to an allocated LED handle structure. 55 | 56 | Returns 0 on success, or a negative [LED error code](#return-value) on failure. 57 | 58 | ------ 59 | 60 | ``` c 61 | int led_read(led_t *led, bool *value); 62 | ``` 63 | Read the state of the LED into `value`, where `true` is non-zero brightness, and `false` is zero brightness. 64 | 65 | `led` should be a valid pointer to an LED handle opened with `led_open()`. `value` should be a pointer to an allocated bool. 66 | 67 | Returns 0 on success, or a negative [LED error code](#return-value) on failure. 68 | 69 | ------ 70 | 71 | ``` c 72 | int led_write(led_t *led, bool value); 73 | ``` 74 | Write the state of the LED to `value`, where `true` is max brightness, and `false` is zero brightness. 75 | 76 | `led` should be a valid pointer to an LED handle opened with `led_open()`. 77 | 78 | Returns 0 on success, or a negative [LED error code](#return-value) on failure. 79 | 80 | ------ 81 | 82 | ``` c 83 | int led_close(led_t *led); 84 | ``` 85 | Close the LED. 86 | 87 | `led` should be a valid pointer to an LED handle opened with `led_open()`. 88 | 89 | Returns 0 on success, or a negative [LED error code](#return-value) on failure. 90 | 91 | ------ 92 | 93 | ``` c 94 | void led_free(led_t *led); 95 | ``` 96 | Free an LED handle. 97 | 98 | ------ 99 | 100 | ``` c 101 | int led_get_brightness(led_t *led, unsigned int *brightness); 102 | ``` 103 | Get the brightness of the LED. 104 | 105 | `led` should be a valid pointer to an LED handle opened with `led_open()`. 106 | 107 | Returns 0 on success, or a negative [LED error code](#return-value) on failure. 108 | 109 | ------ 110 | 111 | ``` c 112 | int led_get_max_brightness(led_t *led, unsigned int *max_brightness); 113 | ``` 114 | Get the max brightness of the LED. 115 | 116 | `led` should be a valid pointer to an LED handle opened with `led_open()`. 117 | 118 | Returns 0 on success, or a negative [LED error code](#return-value) on failure. 119 | 120 | ------ 121 | 122 | ``` c 123 | int led_get_trigger(led_t *led, char *str, size_t len); 124 | ``` 125 | Get the active trigger for the LED. 126 | 127 | `led` should be a valid pointer to an LED handle opened with `led_open()`. 128 | 129 | Returns 0 on success, or a negative [LED error code](#return-value) on failure. 130 | 131 | ------ 132 | 133 | ``` c 134 | int led_get_triggers_entry(led_t *led, unsigned int index, char *str, size_t len); 135 | ``` 136 | Get the trigger name at position `index` of triggers available for the LED. 137 | 138 | `led` should be a valid pointer to an LED handle opened with `led_open()`. 139 | 140 | Returns 0 on success, or a negative [LED error code](#return-value) on failure. 141 | 142 | ------ 143 | 144 | ``` c 145 | int led_get_triggers_count(led_t *led, unsigned int *count); 146 | ``` 147 | Get the count of triggers available for the LED. 148 | 149 | `led` should be a valid pointer to an LED handle opened with `led_open()`. 150 | 151 | Returns 0 on success, or a negative [LED error code](#return-value) on failure. 152 | 153 | ------ 154 | 155 | ``` c 156 | int led_set_brightness(led_t *led, unsigned int brightness); 157 | ``` 158 | Set the brightness of the LED. 159 | 160 | `led` should be a valid pointer to an LED handle opened with `led_open()`. 161 | 162 | Returns 0 on success, or a negative [LED error code](#return-value) on failure. 163 | 164 | ------ 165 | 166 | ``` c 167 | int led_set_trigger(led_t *led, const char *trigger); 168 | ``` 169 | Set the active trigger for the LED. 170 | 171 | `led` should be a valid pointer to an LED handle opened with `led_open()`. 172 | `trigger` should be a null-terminated string. 173 | 174 | Returns 0 on success, or a negative [LED error code](#return-value) on failure. 175 | 176 | ------ 177 | 178 | ``` c 179 | int led_name(led_t *led, char *str, size_t len); 180 | ``` 181 | Return the name of the sysfs LED. 182 | 183 | `led` should be a valid pointer to an LED handle opened with `led_open()`. 184 | 185 | Returns 0 on success, or a negative [LED error code](#return-value) on failure. 186 | 187 | ------ 188 | 189 | ``` c 190 | int led_tostring(led_t *led, char *str, size_t len); 191 | ``` 192 | Return a string representation of the LED handle. 193 | 194 | `led` should be a valid pointer to an LED handle opened with `led_open()`. 195 | 196 | This function behaves and returns like `snprintf()`. 197 | 198 | ------ 199 | 200 | ``` c 201 | int led_errno(led_t *led); 202 | ``` 203 | Return the libc errno of the last failure that occurred. 204 | 205 | `led` should be a valid pointer to an LED handle opened with `led_open()`. 206 | 207 | ------ 208 | 209 | ``` c 210 | const char *led_errmsg(led_t *led); 211 | ``` 212 | Return a human readable error message of the last failure that occurred. 213 | 214 | `led` should be a valid pointer to an LED handle opened with `led_open()`. 215 | 216 | ### RETURN VALUE 217 | 218 | The periphery LED functions return 0 on success or one of the negative error codes below on failure. 219 | 220 | 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. 221 | 222 | | Error Code | Description | 223 | |-----------------------|-----------------------------------| 224 | | `LED_ERROR_ARG` | Invalid arguments | 225 | | `LED_ERROR_OPEN` | Opening LED | 226 | | `LED_ERROR_QUERY` | Querying LED attributes | 227 | | `LED_ERROR_IO` | Reading/writing LED brightness | 228 | | `LED_ERROR_CLOSE` | Closing LED | 229 | 230 | ### EXAMPLE 231 | 232 | ``` c 233 | #include 234 | #include 235 | #include 236 | 237 | #include "led.h" 238 | 239 | int main(void) { 240 | led_t *led; 241 | unsigned int max_brightness; 242 | 243 | led = led_new(); 244 | 245 | /* Open LED led0 */ 246 | if (led_open(led, "led0") < 0) { 247 | fprintf(stderr, "led_open(): %s\n", led_errmsg(led)); 248 | exit(1); 249 | } 250 | 251 | /* Turn on LED (set max brightness) */ 252 | if (led_write(led, true) < 0) { 253 | fprintf(stderr, "led_write(): %s\n", led_errmsg(led)); 254 | exit(1); 255 | } 256 | 257 | /* Get max brightness */ 258 | if (led_get_max_brightness(led, &max_brightness) < 0) { 259 | fprintf(stderr, "led_get_max_brightness(): %s\n", led_errmsg(led)); 260 | exit(1); 261 | } 262 | 263 | /* Set half brightness */ 264 | if (led_set_brightness(led, max_brightness / 2) < 0) { 265 | fprintf(stderr, "led_set_brightness(): %s\n", led_errmsg(led)); 266 | exit(1); 267 | } 268 | 269 | led_close(led); 270 | 271 | led_free(led); 272 | 273 | return 0; 274 | } 275 | ``` 276 | 277 | -------------------------------------------------------------------------------- /docs/mmio.md: -------------------------------------------------------------------------------- 1 | ### NAME 2 | 3 | MMIO wrapper functions for the Linux userspace `/dev/mem` device. 4 | 5 | ### SYNOPSIS 6 | 7 | ``` c 8 | #include 9 | 10 | /* Primary Functions */ 11 | mmio_t *mmio_new(void); 12 | int mmio_open(mmio_t *mmio, uintptr_t base, size_t size); 13 | int mmio_open_advanced(mmio_t *mmio, uintptr_t base, size_t size, const char *path); 14 | void *mmio_ptr(mmio_t *mmio); 15 | int mmio_read64(mmio_t *mmio, uintptr_t offset, uint64_t *value); 16 | int mmio_read32(mmio_t *mmio, uintptr_t offset, uint32_t *value); 17 | int mmio_read16(mmio_t *mmio, uintptr_t offset, uint16_t *value); 18 | int mmio_read8(mmio_t *mmio, uintptr_t offset, uint8_t *value); 19 | int mmio_read(mmio_t *mmio, uintptr_t offset, uint8_t *buf, size_t len); 20 | int mmio_write64(mmio_t *mmio, uintptr_t offset, uint64_t value); 21 | int mmio_write32(mmio_t *mmio, uintptr_t offset, uint32_t value); 22 | int mmio_write16(mmio_t *mmio, uintptr_t offset, uint16_t value); 23 | int mmio_write8(mmio_t *mmio, uintptr_t offset, uint8_t value); 24 | int mmio_write(mmio_t *mmio, uintptr_t offset, const uint8_t *buf, size_t len); 25 | int mmio_close(mmio_t *mmio); 26 | void mmio_free(mmio_t *mmio); 27 | 28 | /* Miscellaneous */ 29 | uintptr_t mmio_base(mmio_t *mmio); 30 | size_t mmio_size(mmio_t *mmio); 31 | int mmio_tostring(mmio_t *mmio, char *str, size_t len); 32 | 33 | /* Error Handling */ 34 | int mmio_errno(mmio_t *mmio); 35 | const char *mmio_errmsg(mmio_t *mmio); 36 | ``` 37 | 38 | ### DESCRIPTION 39 | 40 | ``` c 41 | mmio_t *mmio_new(void); 42 | ``` 43 | Allocate a MMIO handle. 44 | 45 | Returns a valid handle on success, or NULL on failure. 46 | 47 | ------ 48 | 49 | ``` c 50 | int mmio_open(mmio_t *mmio, uintptr_t base, size_t size); 51 | ``` 52 | Map the region of physical memory specified by the `base` physical address and `size` size in bytes, using the default `/dev/mem` memory character device. 53 | 54 | `mmio` should be a valid pointer to an allocated MMIO handle structure. Neither `base` nor `size` need be aligned to a page boundary. 55 | 56 | Returns 0 on success, or a negative [MMIO error code](#return-value) on failure. 57 | 58 | ------ 59 | 60 | ``` c 61 | int mmio_open_advanced(mmio_t *mmio, uintptr_t base, size_t size, const char *path); 62 | ``` 63 | Map the region of physical memory specified by the `base` physical address and `size` size in bytes, using the specified memory character device. This open function can be used with sandboxed memory character devices, e.g. `/dev/gpiomem`. 64 | 65 | `mmio` should be a valid pointer to an allocated MMIO handle structure. Neither `base` nor `size` need be aligned to a page boundary. 66 | 67 | Returns 0 on success, or a negative [MMIO error code](#return-value) on failure. 68 | 69 | ------ 70 | 71 | ``` c 72 | void *mmio_ptr(mmio_t *mmio); 73 | ``` 74 | Return the pointer to the mapped physical memory. 75 | 76 | This function is a simple accessor to the MMIO handle structure and always succeeds. 77 | 78 | ------ 79 | 80 | ``` c 81 | int mmio_read64(mmio_t *mmio, uintptr_t offset, uint64_t *value); 82 | int mmio_read32(mmio_t *mmio, uintptr_t offset, uint32_t *value); 83 | int mmio_read16(mmio_t *mmio, uintptr_t offset, uint16_t *value); 84 | int mmio_read8(mmio_t *mmio, uintptr_t offset, uint8_t *value); 85 | int mmio_read(mmio_t *mmio, uintptr_t offset, uint8_t *buf, size_t len); 86 | ``` 87 | Read 64-bits, 32-bits, 16-bits, 8-bits, or an array of bytes, respectively, from mapped physical memory, starting at the specified byte offset, relative to the base address the MMIO handle was opened with. 88 | 89 | `mmio` should be a valid pointer to an MMIO handle opened with one of the `mmio_open*()` functions. 90 | 91 | Returns 0 on success, or a negative [MMIO error code](#return-value) on failure. 92 | 93 | ------ 94 | 95 | ``` c 96 | int mmio_write32(mmio_t *mmio, uintptr_t offset, uint64_t value); 97 | int mmio_write32(mmio_t *mmio, uintptr_t offset, uint32_t value); 98 | int mmio_write16(mmio_t *mmio, uintptr_t offset, uint16_t value); 99 | int mmio_write8(mmio_t *mmio, uintptr_t offset, uint8_t value); 100 | int mmio_write(mmio_t *mmio, uintptr_t offset, const uint8_t *buf, size_t len); 101 | ``` 102 | Write 64-bits, 32-bits, 16-bits, 8-bits, or an array of bytes, respectively, to mapped physical memory, starting at the specified byte offset, relative to the base address the MMIO handle was opened with. 103 | 104 | `mmio` should be a valid pointer to an MMIO handle opened with one of the `mmio_open*()` functions. 105 | 106 | Returns 0 on success, or a negative [MMIO error code](#return-value) on failure. 107 | 108 | ------ 109 | 110 | ``` c 111 | int mmio_close(mmio_t *mmio); 112 | ``` 113 | Unmap mapped physical memory. 114 | 115 | `mmio` should be a valid pointer to an MMIO handle opened with one of the `mmio_open*()` functions. 116 | 117 | Returns 0 on success, or a negative [MMIO error code](#return-value) on failure. 118 | 119 | ------ 120 | 121 | ``` c 122 | void mmio_free(mmio_t *mmio); 123 | ``` 124 | Free a MMIO handle. 125 | 126 | ------ 127 | 128 | ``` c 129 | uintptr_t mmio_base(mmio_t *mmio); 130 | ``` 131 | Return the base address the MMIO handle was opened with. 132 | 133 | `mmio` should be a valid pointer to an MMIO handle opened with one of the `mmio_open*()` functions. 134 | 135 | This function is a simple accessor to the MMIO handle structure and always succeeds. 136 | 137 | ------ 138 | 139 | ``` c 140 | size_t mmio_size(mmio_t *mmio); 141 | ``` 142 | Return the size the MMIO handle was opened with. 143 | 144 | `mmio` should be a valid pointer to an MMIO handle opened with one of the `mmio_open*()` functions. 145 | 146 | This function is a simple accessor to the MMIO handle structure and always succeeds. 147 | 148 | ------ 149 | 150 | ``` c 151 | int mmio_tostring(mmio_t *mmio, char *str, size_t len); 152 | ``` 153 | Return a string representation of the MMIO handle. 154 | 155 | `mmio` should be a valid pointer to an MMIO handle opened with one of the `mmio_open*()` functions. 156 | 157 | This function behaves and returns like `snprintf()`. 158 | 159 | ------ 160 | 161 | ``` c 162 | int mmio_errno(mmio_t *mmio); 163 | ``` 164 | Return the libc errno of the last failure that occurred. 165 | 166 | `mmio` should be a valid pointer to an MMIO handle opened with one of the `mmio_open*()` functions. 167 | 168 | ------ 169 | 170 | ``` c 171 | const char *mmio_errmsg(mmio_t *mmio); 172 | ``` 173 | Return a human readable error message of the last failure that occurred. 174 | 175 | `mmio` should be a valid pointer to an MMIO handle opened with one of the `mmio_open*()` functions. 176 | 177 | ### RETURN VALUE 178 | 179 | The periphery MMIO functions return 0 on success or one of the negative error codes below on failure. 180 | 181 | The libc errno of the failure in an underlying libc library call can be obtained with the `mmio_errno()` helper function. A human readable error message can be obtained with the `mmio_errmsg()` helper function. 182 | 183 | | Error Code | Description | 184 | |-----------------------|-----------------------| 185 | | `MMIO_ERROR_ARG` | Invalid arguments | 186 | | `MMIO_ERROR_OPEN` | Opening MMIO | 187 | | `MMIO_ERROR_CLOSE` | Closing MMIO | 188 | 189 | ### EXAMPLE 190 | 191 | ``` c 192 | #include 193 | #include 194 | #include 195 | 196 | #include "mmio.h" 197 | 198 | struct am335x_rtcss_registers { 199 | uint32_t seconds; /* 0x00 */ 200 | uint32_t minutes; /* 0x04 */ 201 | uint32_t hours; /* 0x08 */ 202 | /* ... */ 203 | }; 204 | 205 | int main(void) { 206 | mmio_t *mmio; 207 | uint32_t mac_id0_lo, mac_id0_hi; 208 | volatile struct am335x_rtcss_registers *regs; 209 | 210 | mmio = mmio_new(); 211 | 212 | /* Open control module */ 213 | if (mmio_open(mmio, 0x44E10000, 0x1000) < 0) { 214 | fprintf(stderr, "mmio_open(): %s\n", mmio_errmsg(mmio)); 215 | exit(1); 216 | } 217 | 218 | /* Read lower 2 bytes of MAC address */ 219 | if (mmio_read32(mmio, 0x630, &mac_id0_lo) < 0) { 220 | fprintf(stderr, "mmio_read32(): %s\n", mmio_errmsg(mmio)); 221 | exit(1); 222 | } 223 | 224 | /* Read upper 4 bytes of MAC address */ 225 | if (mmio_read32(mmio, 0x634, &mac_id0_hi) < 0) { 226 | fprintf(stderr, "mmio_read32(): %s\n", mmio_errmsg(mmio)); 227 | exit(1); 228 | } 229 | 230 | printf("MAC address: %04x%08x\n", mac_id0_lo, mac_id0_hi); 231 | 232 | mmio_close(mmio); 233 | 234 | /* Open RTC subsystem */ 235 | if (mmio_open(mmio, 0x44E3E000, 0x1000) < 0) { 236 | fprintf(stderr, "mmio_open(): %s\n", mmio_errmsg(mmio)); 237 | exit(1); 238 | } 239 | 240 | regs = mmio_ptr(mmio); 241 | 242 | /* Read current RTC time */ 243 | printf("hours: %02x minutes: %02x seconds %02x\n", regs->hours, regs->minutes, regs->seconds); 244 | 245 | mmio_close(mmio); 246 | 247 | mmio_free(mmio); 248 | 249 | return 0; 250 | } 251 | ``` 252 | 253 | -------------------------------------------------------------------------------- /tests/test_mmio.c: -------------------------------------------------------------------------------- 1 | /* 2 | * c-periphery 3 | * https://github.com/vsergeev/c-periphery 4 | * License: MIT 5 | */ 6 | 7 | #include "test.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "../src/mmio.h" 15 | 16 | #define PAGE_SIZE 4096 17 | 18 | #define CONTROL_MODULE_BASE 0x44e10000 19 | #define USB_VID_PID_OFFSET 0x7f4 20 | #define USB_VID_PID 0x04516141 21 | 22 | #define RTCSS_BASE 0x44e3e000 23 | #define RTC_SCRATCH2_REG_OFFSET 0x68 24 | 25 | void test_arguments(void) { 26 | ptest(); 27 | 28 | /* Check offset out of bounds in test_open_config_close() */ 29 | } 30 | 31 | void test_open_config_close(void) { 32 | mmio_t *mmio; 33 | uintptr_t address; 34 | uint32_t value32; 35 | 36 | ptest(); 37 | 38 | /* Allocate MMIO */ 39 | mmio = mmio_new(); 40 | passert(mmio != NULL); 41 | 42 | /* Open aligned base */ 43 | passert(mmio_open(mmio, CONTROL_MODULE_BASE, PAGE_SIZE) == 0); 44 | passert(mmio_base(mmio) == CONTROL_MODULE_BASE); 45 | passert(mmio_size(mmio) == PAGE_SIZE); 46 | passert(mmio_ptr(mmio) != NULL); 47 | 48 | struct mmio_handle { 49 | uintptr_t base, aligned_base; 50 | size_t size, aligned_size; 51 | void *ptr; 52 | 53 | struct { 54 | int c_errno; 55 | char errmsg[96]; 56 | } error; 57 | }; 58 | 59 | /* Check alignment math */ 60 | passert(((struct mmio_handle *)mmio)->base == CONTROL_MODULE_BASE); 61 | passert(((struct mmio_handle *)mmio)->size == PAGE_SIZE); 62 | passert(((struct mmio_handle *)mmio)->aligned_base == CONTROL_MODULE_BASE); 63 | passert(((struct mmio_handle *)mmio)->aligned_size == PAGE_SIZE); 64 | passert(mmio_ptr(mmio) == ((struct mmio_handle *)mmio)->ptr); 65 | 66 | passert(mmio_read32(mmio, PAGE_SIZE-3, &value32) == MMIO_ERROR_ARG); 67 | passert(mmio_read32(mmio, PAGE_SIZE-2, &value32) == MMIO_ERROR_ARG); 68 | passert(mmio_read32(mmio, PAGE_SIZE-1, &value32) == MMIO_ERROR_ARG); 69 | passert(mmio_read32(mmio, PAGE_SIZE, &value32) == MMIO_ERROR_ARG); 70 | passert(mmio_close(mmio) == 0); 71 | 72 | /* Open unaligned base */ 73 | address = CONTROL_MODULE_BASE + 123; 74 | passert(mmio_open(mmio, address, PAGE_SIZE) == 0); 75 | passert(mmio_base(mmio) == address); 76 | passert(mmio_size(mmio) == PAGE_SIZE); 77 | passert(mmio_ptr(mmio) != NULL); 78 | 79 | /* Check alignment math */ 80 | passert(((struct mmio_handle *)mmio)->base == address); 81 | passert(((struct mmio_handle *)mmio)->size == PAGE_SIZE); 82 | passert(((struct mmio_handle *)mmio)->aligned_base == (address - (address % sysconf(_SC_PAGESIZE)))); 83 | passert(((struct mmio_handle *)mmio)->aligned_size == (PAGE_SIZE + (address % sysconf(_SC_PAGESIZE)))); 84 | passert((size_t)((uint8_t *)mmio_ptr(mmio) - (uint8_t *)((struct mmio_handle *)mmio)->ptr) == 85 | (size_t)(((struct mmio_handle *)mmio)->base - ((struct mmio_handle *)mmio)->aligned_base)); 86 | 87 | passert(mmio_read32(mmio, PAGE_SIZE-3, &value32) == MMIO_ERROR_ARG); 88 | passert(mmio_read32(mmio, PAGE_SIZE-2, &value32) == MMIO_ERROR_ARG); 89 | passert(mmio_read32(mmio, PAGE_SIZE-1, &value32) == MMIO_ERROR_ARG); 90 | passert(mmio_read32(mmio, PAGE_SIZE, &value32) == MMIO_ERROR_ARG); 91 | passert(mmio_close(mmio) == 0); 92 | 93 | /* Free MMIO */ 94 | mmio_free(mmio); 95 | } 96 | 97 | void test_loopback(void) { 98 | mmio_t *mmio; 99 | uint32_t value32; 100 | uint8_t data[4]; 101 | uint8_t vector[] = { 0xaa, 0xbb, 0xcc, 0xdd }; 102 | 103 | ptest(); 104 | 105 | /* Allocate MMIO */ 106 | mmio = mmio_new(); 107 | passert(mmio != NULL); 108 | 109 | /* Read USB VID/PID */ 110 | passert(mmio_open(mmio, CONTROL_MODULE_BASE, PAGE_SIZE) == 0); 111 | passert(mmio_read32(mmio, USB_VID_PID_OFFSET, &value32) == 0); 112 | passert(value32 == USB_VID_PID); 113 | passert(mmio_close(mmio) == 0); 114 | 115 | /* Read USB VID/PID via byte read */ 116 | passert(mmio_open(mmio, CONTROL_MODULE_BASE, PAGE_SIZE) == 0); 117 | passert(mmio_read(mmio, USB_VID_PID_OFFSET, data, 4) == 0); 118 | passert(data[0] == (USB_VID_PID & 0xff)); 119 | passert(data[1] == ((USB_VID_PID >> 8) & 0xff)); 120 | passert(data[2] == ((USB_VID_PID >> 16) & 0xff)); 121 | passert(data[3] == ((USB_VID_PID >> 24) & 0xff)); 122 | passert(mmio_close(mmio) == 0); 123 | 124 | /* Write/Read RTC Scratch2 Register */ 125 | passert(mmio_open(mmio, RTCSS_BASE, PAGE_SIZE) == 0); 126 | passert(mmio_write32(mmio, RTC_SCRATCH2_REG_OFFSET, 0xdeadbeef) == 0); 127 | passert(mmio_read32(mmio, RTC_SCRATCH2_REG_OFFSET, &value32) == 0); 128 | passert(value32 == 0xdeadbeef); 129 | passert(mmio_close(mmio) == 0); 130 | 131 | /* Write/Read RTC Scratch2 Register via byte write */ 132 | passert(mmio_open(mmio, RTCSS_BASE, PAGE_SIZE) == 0); 133 | passert(mmio_write(mmio, RTC_SCRATCH2_REG_OFFSET, vector, 4) == 0); 134 | passert(mmio_read32(mmio, RTC_SCRATCH2_REG_OFFSET, &value32) == 0); 135 | passert(value32 == 0xddccbbaa); 136 | passert(mmio_read(mmio, RTC_SCRATCH2_REG_OFFSET, data, 4) == 0); 137 | passert(memcmp(data, vector, 4) == 0); 138 | passert(mmio_close(mmio) == 0); 139 | 140 | /* Free MMIO */ 141 | mmio_free(mmio); 142 | } 143 | 144 | struct rtc_ss { 145 | volatile uint32_t seconds; /* 0x00 */ 146 | volatile uint32_t minutes; /* 0x04 */ 147 | volatile uint32_t hours; /* 0x08 */ 148 | volatile uint32_t days; /* 0x0C */ 149 | volatile uint32_t months; /* 0x10 */ 150 | volatile uint32_t years; /* 0x14 */ 151 | volatile uint32_t weeks; /* 0x18 */ 152 | 153 | volatile uint32_t reserved1; /* 0x1C */ 154 | 155 | volatile uint32_t alarm_seconds; /* 0x20 */ 156 | volatile uint32_t alarm_minutes; /* 0x24 */ 157 | volatile uint32_t alarm_hours; /* 0x28 */ 158 | volatile uint32_t alarm_days; /* 0x2C */ 159 | volatile uint32_t alarm_months; /* 0x30 */ 160 | volatile uint32_t alarm_years; /* 0x34 */ 161 | 162 | volatile uint32_t reserved2; /* 0x38 */ 163 | volatile uint32_t reserved3; /* 0x3C */ 164 | 165 | volatile uint32_t rtc_ctrl; /* 0x40 */ 166 | volatile uint32_t rtc_status; /* 0x44 */ 167 | volatile uint32_t rtc_interrupts;/* 0x48 */ 168 | }; 169 | 170 | #define BCD_HI(x) (((x) >> 4) & 0xf) 171 | #define BCD_LO(x) ((x) & 0xf) 172 | #define BCD2DEC(x) (10*BCD_HI(x) + BCD_LO(x)) 173 | 174 | void test_interactive(void) { 175 | mmio_t *mmio; 176 | uint32_t rtc_start, rtc_stop; 177 | time_t start, stop; 178 | struct rtc_ss *rtc; 179 | 180 | ptest(); 181 | 182 | /* Allocate MMIO */ 183 | mmio = mmio_new(); 184 | passert(mmio != NULL); 185 | 186 | passert(mmio_open(mmio, RTCSS_BASE, PAGE_SIZE) == 0); 187 | rtc = (struct rtc_ss *)mmio_ptr(mmio); 188 | 189 | printf("Waiting for seconds ones digit to reset to 0...\n"); 190 | 191 | start = time(NULL); 192 | /* Wait until seconds low go to 0, so we don't have to deal with overflows 193 | * in comparing times */ 194 | while (BCD_LO(rtc->seconds) != 0) { 195 | usleep(500000); 196 | passert((time(NULL) - start) < 12); 197 | } 198 | 199 | start = time(NULL); 200 | rtc_start = rtc->seconds; 201 | 202 | printf("Date: %04d-%02d-%02d\n", 2000 + BCD2DEC(rtc->years), BCD2DEC(rtc->months), BCD2DEC(rtc->days)); 203 | printf("Time: %02d:%02d:%02d %s\n", BCD2DEC(rtc->hours & 0x7f), BCD2DEC(rtc->minutes), BCD2DEC(rtc->seconds), (rtc->hours & 0x80) ? "PM" : "AM"); 204 | 205 | sleep(3); 206 | 207 | printf("Date: %02d-%02d-%02d\n", 2000 + BCD2DEC(rtc->years), BCD2DEC(rtc->months), BCD2DEC(rtc->days)); 208 | printf("Time: %02d:%02d:%02d %s\n", BCD2DEC(rtc->hours & 0x7f), BCD2DEC(rtc->minutes), BCD2DEC(rtc->seconds), (rtc->hours & 0x80) ? "PM" : "AM"); 209 | 210 | rtc_stop = rtc->seconds; 211 | stop = time(NULL); 212 | 213 | /* Check that time has passed */ 214 | passert((stop - start) > 2); 215 | passert((rtc_stop - rtc_start) > 2); 216 | 217 | passert(mmio_close(mmio) == 0); 218 | 219 | /* Free MMIO */ 220 | mmio_free(mmio); 221 | } 222 | 223 | int main(void) { 224 | printf("WARNING: This test suite assumes a BeagleBone Black (AM335x) host!\n"); 225 | printf("Other systems may experience unintended and dire consequences!\n"); 226 | printf("Press enter to continue!\n"); 227 | getc(stdin); 228 | 229 | test_arguments(); 230 | printf(" " STR_OK " Arguments test passed.\n\n"); 231 | test_open_config_close(); 232 | printf(" " STR_OK " Open/close test passed.\n\n"); 233 | test_loopback(); 234 | printf(" " STR_OK " Loopback test passed.\n\n"); 235 | test_interactive(); 236 | printf(" " STR_OK " Interactive test passed.\n\n"); 237 | 238 | printf("All tests passed!\n"); 239 | return 0; 240 | } 241 | 242 | -------------------------------------------------------------------------------- /tests/test_spi.c: -------------------------------------------------------------------------------- 1 | /* 2 | * c-periphery 3 | * https://github.com/vsergeev/c-periphery 4 | * License: MIT 5 | */ 6 | 7 | #include "test.h" 8 | 9 | #include 10 | #include 11 | 12 | #include "../src/spi.h" 13 | 14 | const char *device; 15 | 16 | void test_arguments(void) { 17 | spi_t *spi; 18 | 19 | ptest(); 20 | 21 | /* Allocate SPI */ 22 | spi = spi_new(); 23 | passert(spi != NULL); 24 | 25 | /* Invalid mode */ 26 | passert(spi_open(spi, device, 4, 1e6) == SPI_ERROR_ARG); 27 | /* Invalid bit order */ 28 | passert(spi_open_advanced(spi, device, 0, 1e6, LSB_FIRST+1, 8, 0) == SPI_ERROR_ARG); 29 | 30 | /* Free SPI */ 31 | spi_free(spi); 32 | } 33 | 34 | void test_open_config_close(void) { 35 | spi_t *spi; 36 | unsigned int mode; 37 | spi_bit_order_t bit_order; 38 | uint8_t bits_per_word; 39 | uint32_t max_speed; 40 | 41 | ptest(); 42 | 43 | /* Allocate SPI */ 44 | spi = spi_new(); 45 | passert(spi != NULL); 46 | 47 | passert(spi_open(spi, device, 0, 100000) == 0); 48 | 49 | /* Confirm bit_order = MSB first, bits_per_word = 8 */ 50 | passert(spi_get_bit_order(spi, &bit_order) == 0); 51 | passert(bit_order == MSB_FIRST); 52 | passert(spi_get_bits_per_word(spi, &bits_per_word) == 0); 53 | passert(bits_per_word == 8); 54 | 55 | /* Not going to try different bit order or bits per word, because not all 56 | * SPI controllers support them */ 57 | 58 | /* Try modes 1,2,3,0 */ 59 | passert(spi_set_mode(spi, 1) == 0); 60 | passert(spi_get_mode(spi, &mode) == 0); 61 | passert(mode == 1); 62 | passert(spi_set_mode(spi, 2) == 0); 63 | passert(spi_get_mode(spi, &mode) == 0); 64 | passert(mode == 2); 65 | passert(spi_set_mode(spi, 3) == 0); 66 | passert(spi_get_mode(spi, &mode) == 0); 67 | passert(mode == 3); 68 | passert(spi_set_mode(spi, 0) == 0); 69 | passert(spi_get_mode(spi, &mode) == 0); 70 | passert(mode == 0); 71 | 72 | /* Try 100KHz, 500KHz, 1MHz */ 73 | passert(spi_set_max_speed(spi, 100000) == 0); 74 | passert(spi_get_max_speed(spi, &max_speed) == 0); 75 | passert(max_speed == 100000); 76 | passert(spi_set_max_speed(spi, 500000) == 0); 77 | passert(spi_get_max_speed(spi, &max_speed) == 0); 78 | passert(max_speed == 500000); 79 | passert(spi_set_max_speed(spi, 1000000) == 0); 80 | passert(spi_get_max_speed(spi, &max_speed) == 0); 81 | passert(max_speed == 1000000); 82 | 83 | passert(spi_close(spi) == 0); 84 | 85 | /* Free SPI */ 86 | spi_free(spi); 87 | } 88 | 89 | void test_loopback(void) { 90 | spi_t *spi; 91 | uint8_t buf[32]; 92 | uint8_t rxbuf1[32], rxbuf2[32]; 93 | spi_msg_t msgs[3] = { 94 | { .txbuf = buf, .rxbuf = rxbuf1, .len = sizeof(buf), .deselect = true }, 95 | { .txbuf = buf, .rxbuf = rxbuf2, .len = sizeof(buf), .deselect = false }, 96 | }; 97 | unsigned int i; 98 | 99 | ptest(); 100 | 101 | /* Allocate SPI */ 102 | spi = spi_new(); 103 | passert(spi != NULL); 104 | 105 | passert(spi_open(spi, device, 0, 100000) == 0); 106 | 107 | for (i = 0; i < sizeof(buf); i++) 108 | buf[i] = i; 109 | 110 | passert(spi_transfer(spi, buf, buf, sizeof(buf)) == 0); 111 | 112 | for (i = 0; i < sizeof(buf); i++) 113 | passert(buf[i] == i); 114 | 115 | passert(spi_transfer_advanced(spi, msgs, 3) == 0); 116 | 117 | for (i = 0; i < sizeof(rxbuf1); i++) { 118 | passert(rxbuf1[i] == i); 119 | passert(rxbuf2[i] == i); 120 | } 121 | 122 | passert(spi_close(spi) == 0); 123 | 124 | /* Free SPI */ 125 | spi_free(spi); 126 | } 127 | 128 | bool getc_yes(void) { 129 | char buf[4]; 130 | fgets(buf, sizeof(buf), stdin); 131 | return (buf[0] == 'y' || buf[0] == 'Y'); 132 | } 133 | 134 | void test_interactive(void) { 135 | char str[256]; 136 | spi_t *spi; 137 | uint8_t buf[] = { 0x55, 0xaa, 0x0f, 0xf0 }; 138 | spi_msg_t msgs[3] = { 139 | { .txbuf = buf, .rxbuf = NULL, .len = 4, .deselect = true }, 140 | { .txbuf = buf, .rxbuf = NULL, .len = 4, .deselect = false }, 141 | { .txbuf = buf, .rxbuf = NULL, .len = 4, .deselect = false }, 142 | }; 143 | 144 | ptest(); 145 | 146 | /* Allocate SPI */ 147 | spi = spi_new(); 148 | passert(spi != NULL); 149 | 150 | passert(spi_open(spi, device, 0, 100000) == 0); 151 | 152 | printf("Starting interactive test. Get out your logic analyzer, buddy!\n"); 153 | printf("Press enter to continue...\n"); 154 | getc(stdin); 155 | 156 | /* Check tostring */ 157 | passert(spi_tostring(spi, str, sizeof(str)) > 0); 158 | printf("SPI description: %s\n", str); 159 | printf("SPI description looks OK? y/n\n"); 160 | passert(getc_yes()); 161 | 162 | /* Mode 0 transfer */ 163 | printf("Press enter to start transfer..."); 164 | getc(stdin); 165 | passert(spi_transfer(spi, buf, NULL, sizeof(buf)) == 0); 166 | printf("SPI data 0x55, 0xaa, 0x0f, 0xf0\n"); 167 | printf("SPI transfer speed <= 100KHz, mode 0 occurred? y/n\n"); 168 | passert(getc_yes()); 169 | 170 | /* Mode 1 transfer */ 171 | passert(spi_set_mode(spi, 1) == 0); 172 | printf("Press enter to start transfer..."); 173 | getc(stdin); 174 | passert(spi_transfer(spi, buf, NULL, sizeof(buf)) == 0); 175 | printf("SPI data 0x55, 0xaa, 0x0f, 0xf0\n"); 176 | printf("SPI transfer speed <= 100KHz, mode 1 occurred? y/n\n"); 177 | passert(getc_yes()); 178 | 179 | /* Mode 2 transfer */ 180 | passert(spi_set_mode(spi, 2) == 0); 181 | printf("Press enter to start transfer..."); 182 | getc(stdin); 183 | passert(spi_transfer(spi, buf, NULL, sizeof(buf)) == 0); 184 | printf("SPI data 0x55, 0xaa, 0x0f, 0xf0\n"); 185 | printf("SPI transfer speed <= 100KHz, mode 2 occurred? y/n\n"); 186 | passert(getc_yes()); 187 | 188 | /* Mode 3 transfer */ 189 | passert(spi_set_mode(spi, 3) == 0); 190 | printf("Press enter to start transfer..."); 191 | getc(stdin); 192 | passert(spi_transfer(spi, buf, NULL, sizeof(buf)) == 0); 193 | printf("SPI data 0x55, 0xaa, 0x0f, 0xf0\n"); 194 | printf("SPI transfer speed <= 100KHz, mode 3 occurred? y/n\n"); 195 | passert(getc_yes()); 196 | 197 | passert(spi_set_mode(spi, 0) == 0); 198 | 199 | /* Multiple transfer */ 200 | printf("Press enter to start transfer..."); 201 | getc(stdin); 202 | passert(spi_transfer_advanced(spi, msgs, 3) == 0); 203 | printf("SPI data 0x55, 0xaa, 0x0f, 0xf0\n"); 204 | printf("SPI transfer of three messages, with deselect after first message occurred? y/n\n"); 205 | passert(getc_yes()); 206 | 207 | /* 500KHz transfer */ 208 | passert(spi_set_max_speed(spi, 500000) == 0); 209 | printf("Press enter to start transfer..."); 210 | getc(stdin); 211 | passert(spi_transfer(spi, buf, NULL, sizeof(buf)) == 0); 212 | printf("SPI data 0x55, 0xaa, 0x0f, 0xf0\n"); 213 | printf("SPI transfer speed <= 500KHz, mode 0 occurred? y/n\n"); 214 | passert(getc_yes()); 215 | 216 | /* 1MHz transfer */ 217 | passert(spi_set_max_speed(spi, 1000000) == 0); 218 | printf("Press enter to start transfer..."); 219 | getc(stdin); 220 | passert(spi_transfer(spi, buf, NULL, sizeof(buf)) == 0); 221 | printf("SPI data 0x55, 0xaa, 0x0f, 0xf0\n"); 222 | printf("SPI transfer speed <= 1MHz, mode 0 occurred? y/n\n"); 223 | passert(getc_yes()); 224 | 225 | passert(spi_close(spi) == 0); 226 | 227 | /* Free SPI */ 228 | spi_free(spi); 229 | } 230 | 231 | int main(int argc, char *argv[]) { 232 | if (argc < 2) { 233 | fprintf(stderr, "Usage: %s \n\n", argv[0]); 234 | fprintf(stderr, "[1/4] Arguments test: No requirements.\n"); 235 | fprintf(stderr, "[2/4] Open/close test: SPI device should be real.\n"); 236 | fprintf(stderr, "[3/4] Loopback test: SPI MISO and MOSI should be connected with a wire.\n"); 237 | fprintf(stderr, "[4/4] Interactive test: SPI MOSI, CLK, CS should be observed with an oscilloscope or logic analyzer.\n\n"); 238 | fprintf(stderr, "Hint: for Raspberry Pi 3, enable SPI0 with:\n"); 239 | fprintf(stderr, " $ echo \"dtparam=spi=on\" | sudo tee -a /boot/firmware/config.txt\n"); 240 | fprintf(stderr, " $ sudo reboot\n"); 241 | fprintf(stderr, "Use pins SPI0 MOSI (header pin 19), SPI0 MISO (header pin 21), SPI0 SCLK (header pin 23),\n"); 242 | fprintf(stderr, "connect a loopback between MOSI and MISO, and run this test with:\n"); 243 | fprintf(stderr, " %s /dev/spidev0.0\n\n", argv[0]); 244 | exit(1); 245 | } 246 | 247 | device = argv[1]; 248 | 249 | test_arguments(); 250 | printf(" " STR_OK " Arguments test passed.\n\n"); 251 | test_open_config_close(); 252 | printf(" " STR_OK " Open/close test passed.\n\n"); 253 | test_loopback(); 254 | printf(" " STR_OK " Loopback test passed.\n\n"); 255 | test_interactive(); 256 | printf(" " STR_OK " Interactive test passed.\n\n"); 257 | 258 | printf("All tests passed!\n"); 259 | return 0; 260 | } 261 | 262 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | * v2.5.0 - 11/02/2025 2 | * MMIO 3 | * Add `read64()` and `write64()` APIs. 4 | * SPI 5 | * Add `spi_transfer_advanced()` API for transferring multiple messages, 6 | with optional deselect between messages, deselect delay, and word 7 | delay. 8 | * GPIO 9 | * Add open configuration, getters, and setters for event clock. 10 | * Add open configuration, getters, and setters for debounce period. 11 | * LED 12 | * Add getter and setter for trigger. 13 | * Add `led_get_triggers_entry()` and `led_get_triggers_count()` APIs 14 | for querying triggers list. 15 | * Build 16 | * Add shared library target to Makefile. 17 | * Use . for shared library SONAME under CMake. 18 | 19 | * v2.4.3 - 02/28/2025 20 | * Fix memory safety with some older `strerror_r()` implementations in error 21 | formatters for all modules. 22 | * Build 23 | * Fix character device GPIO support tests in Makefile for alternate 24 | shells and older versions of make. 25 | * Fix cross-compilation in Makefile from Windows. 26 | * Fix CMake minimum required version. 27 | * Add CMake package generation. 28 | * Contributors 29 | * Ryan Barnett, @rjbarnet - ec31b39 30 | * javalikescript, @javalikescript - 024a25d 31 | * HopeCollector, @HopeCollector - aca6815, b5e53e6 32 | 33 | * v2.4.2 - 07/05/2023 34 | * GPIO 35 | * Fix building under Linux kernel headers missing realtime event 36 | timestamp support in the gpio-cdev v2 ABI. 37 | 38 | * v2.4.1 - 04/21/2023 39 | * GPIO 40 | * Fix realtime timestamp reporting for line events in gpio-cdev v2 41 | implementation. 42 | 43 | * v2.4.0 - 03/31/2023 44 | * GPIO 45 | * Add support for gpio-cdev v2 ABI. 46 | 47 | * v2.3.1 - 01/05/2021 48 | * SPI 49 | * Fix compilation error and unused variable/parameter warnings when 50 | building under Linux kernel headers without 32-bit SPI mode flags 51 | support. 52 | * Contributors 53 | * Ryan Barnett, @rjbarnet - 708f7fe, 21c1b7a 54 | 55 | * v2.3.0 - 12/16/2020 56 | * MMIO 57 | * Add advanced open function with device path for use with 58 | alternate memory character devices (e.g. `/dev/gpiomem`). 59 | * SPI 60 | * Add getter and setter for 32-bit extra flags. 61 | * Add advanced open function with 32-bit extra flags. 62 | * Build 63 | * Enable unused parameter warning. 64 | * Contributors 65 | * Rémy Dziemiaszko, @remdzi - b8adb42 66 | 67 | * v2.2.5 - 11/19/2020 68 | * GPIO 69 | * Add direction checks for improved error reporting to `gpio_write()`, 70 | `gpio_read_event()`, and `gpio_poll()` for character device GPIOs. 71 | * Improve string handling in `gpio_open()` and in getters for 72 | sysfs and character device GPIOs. 73 | * LED 74 | * Improve string handling in `led_open()` and `led_name()`. 75 | * Build 76 | * Add default optimization to CFLAGS in Makefile. 77 | * Add debug and release CFLAGS to CMakeLists.txt. 78 | 79 | * v2.2.4 - 09/11/2020 80 | * Fix future spurious close caused by uncleared handle state after an error 81 | during open in GPIO, I2C, SPI, Serial, and MMIO modules. 82 | 83 | * v2.2.3 - 09/03/2020 84 | * GPIO 85 | * Disable character device GPIO support when building with older Linux 86 | kernel headers missing line event support in the gpio-cdev ABI. 87 | * SPI 88 | * Fix formatted bits per word truncation in `spi_tostring()`. 89 | * Build 90 | * Add test for character device GPIO support in Linux kernel headers to 91 | Makefile. 92 | * Contributors 93 | * Fabrice Fontaine, @ffontaine - 5b81b89 94 | 95 | * v2.2.2 - 07/24/2020 96 | * GPIO 97 | * Add conditional compilation of character device GPIO support to allow 98 | build under older Linux kernel headers. 99 | * Increase feature test macro version to fix missing definition 100 | warnings. 101 | * Build 102 | * Fix directory paths for pkg-config pc file generation and 103 | installation under CMake. 104 | * Fix COMMIT_ID identification when building within a parent git 105 | repository under CMake. 106 | * Add CMake build option for tests. 107 | * Contributors 108 | * oficsu, @oficsu - 80bc63d 109 | * Ryan Barnett, @rjbarnet - ea1e0da, 05262e6, 50fcd0a 110 | * Fabrice Fontaine, @ffontaine - caadb46 111 | 112 | * v2.2.1 - 05/31/2020 113 | * GPIO 114 | * Add feature test macro for POLLRDNORM flag to fix build with uClibc. 115 | * Fix argument name in prototype for `gpio_set_bias()`. 116 | * Contributors 117 | * Joris Offouga, @jorisoffouga - cfc722e 118 | 119 | * v2.2.0 - 05/29/2020 120 | * GPIO 121 | * Add `gpio_poll_multiple()` function. 122 | * Add getter for line consumer label. 123 | * Add getters and setters for line bias, line drive, and inverted 124 | properties. 125 | * Add advanced open functions with additional properties for character 126 | device GPIOs. 127 | * Only unexport GPIO in `gpio_close()` if exported in 128 | `gpio_open_sysfs()` for sysfs GPIOs. 129 | * Add retry loop to direction write after export to accommodate delayed 130 | udev permission rule application in `gpio_open_sysfs()` for sysfs 131 | GPIOs. 132 | * Improve wording and fix typos in documentation. 133 | * Serial 134 | * Add getters and setters for vmin and vtime termios settings. 135 | * Add support for termios timeout with `serial_read()`. 136 | * Improve wording in documentation. 137 | * Build 138 | * Add CMake build support. 139 | * Add pkg-config pc file generation. 140 | * Contributors 141 | * Joris Offouga, @jorisoffouga - 952e1e9, 671e618 142 | 143 | * v2.1.0 - 01/07/2020 144 | * Add LED module. 145 | * Add PWM module. 146 | * Clean up internal string handling in SPI and GPIO modules. 147 | 148 | * v2.0.1 - 10/08/2019 149 | * Initialize handle state in new functions of all modules. 150 | * Fix performance of blocking read in `serial_read()`. 151 | * Return error on unexpected empty read in `serial_read()`, which may be 152 | caused by a serial port disconnect. 153 | * Improve formatting of `spi_tostring()`. 154 | * Fix typo in GPIO module documentation. 155 | * Fix cross-compilation support in Makefile to allow override of CC 156 | variable. 157 | 158 | * v2.0.0 - 09/30/2019 159 | * Add support for character device GPIOs (`gpio-cdev`) to the GPIO module. 160 | * Remove support for preserve direction in `gpio_open()`. 161 | * Remove problematic dummy read with sysfs GPIOs from `gpio_poll()`. 162 | * Unexport sysfs GPIOs in `gpio_close()`. 163 | * Migrate to opaque handles with new/free functions in all modules. 164 | * Simplify error codes for MMIO, I2C, and Serial modules. 165 | * Fix typos in GPIO module documentation. 166 | * Update tests with running hints for Raspberry Pi 3. 167 | * Improve cross-compilation support in Makefile. 168 | * Contributors 169 | * longsky, @wangqiang1588 - d880ef7 170 | * jhlim, @johlim - 742d983 171 | 172 | * v1.1.3 - 04/28/2018 173 | * Fix data's most significant bit getting stripped when opening a serial 174 | port with parity enabled in `serial_open_advanced()`. 175 | * Contributors 176 | * Ryan Barnett, @rjbarnet - 537eeac 177 | 178 | * v1.1.2 - 04/01/2018 179 | * Add handling for delayed pin directory export on some platforms in 180 | `gpio_open()`. 181 | * Fix supported functions query for 64-bit in `i2c_open()`. 182 | * Add support for building with C++. 183 | * Contributors 184 | * Jared Bents, @jmbents - 304faf4 185 | * Ryan Barnett, @rjbarnet - 82ebb4f 186 | 187 | * v1.1.1 - 04/25/2017 188 | * Fix blocking `gpio_poll()` for some platforms. 189 | * Add library version macros and functions. 190 | * Contributors 191 | * Михаил Корнилов, @iTiky - 0643fe9 192 | 193 | * v1.1.0 - 09/27/2016 194 | * Add support for preserving pin direction to `gpio_open()`. 195 | * Fix enabling input parity check in `serial_set_parity()`. 196 | * Fix enabling hardware flow control in `serial_set_rtscts()`. 197 | * Include missing header to fix build with musl libc. 198 | * Omit unsupported serial baudrates to fix build on SPARC. 199 | * Contributors 200 | * Thomas Petazzoni - 27a9552, 114c715 201 | 202 | * v1.0.3 - 05/25/2015 203 | * Fix portability of serial baud rate set/get with termios-provided baud rate functions. 204 | * Fix unlikely bug in `spi_tostring()` formatting. 205 | * Clean up integer argument signedness in serial API. 206 | 207 | * v1.0.2 - 01/31/2015 208 | * Fix `gpio_supports_interrupts()` so it does not return an error if interrupts are not supported. 209 | * Fix `errno` preservation in a few error paths, mostly in the open functions. 210 | 211 | * v1.0.1 - 12/26/2014 212 | * Improve Makefile. 213 | * Fix _BSD_SOURCE compilation warnings. 214 | 215 | * v1.0.0 - 05/15/2014 216 | * Initial release. 217 | -------------------------------------------------------------------------------- /docs/pwm.md: -------------------------------------------------------------------------------- 1 | ### NAME 2 | 3 | PWM wrapper functions for Linux userspace sysfs PWMs. 4 | 5 | ### SYNOPSIS 6 | 7 | ``` c 8 | #include 9 | 10 | /* Primary Functions */ 11 | pwm_t *pwm_new(void); 12 | int pwm_open(pwm_t *pwm, unsigned int chip, unsigned int channel); 13 | int pwm_enable(pwm_t *pwm); 14 | int pwm_disable(pwm_t *pwm); 15 | int pwm_close(pwm_t *pwm); 16 | void pwm_free(pwm_t *pwm); 17 | 18 | /* Getters */ 19 | int pwm_get_enabled(pwm_t *pwm, bool *enabled); 20 | int pwm_get_period_ns(pwm_t *pwm, uint64_t *period_ns); 21 | int pwm_get_duty_cycle_ns(pwm_t *pwm, uint64_t *duty_cycle_ns); 22 | int pwm_get_period(pwm_t *pwm, double *period); 23 | int pwm_get_duty_cycle(pwm_t *pwm, double *duty_cycle); 24 | int pwm_get_frequency(pwm_t *pwm, double *frequency); 25 | int pwm_get_polarity(pwm_t *pwm, pwm_polarity_t *polarity); 26 | 27 | /* Setters */ 28 | int pwm_set_enabled(pwm_t *pwm, bool enabled); 29 | int pwm_set_period_ns(pwm_t *pwm, uint64_t period_ns); 30 | int pwm_set_duty_cycle_ns(pwm_t *pwm, uint64_t duty_cycle_ns); 31 | int pwm_set_period(pwm_t *pwm, double period); 32 | int pwm_set_duty_cycle(pwm_t *pwm, double duty_cycle); 33 | int pwm_set_frequency(pwm_t *pwm, double frequency); 34 | int pwm_set_polarity(pwm_t *pwm, pwm_polarity_t polarity); 35 | 36 | /* Miscellaneous */ 37 | unsigned int pwm_chip(pwm_t *pwm); 38 | unsigned int pwm_channel(pwm_t *pwm); 39 | int pwm_tostring(pwm_t *pwm, char *str, size_t len); 40 | 41 | /* Error Handling */ 42 | int pwm_errno(pwm_t *pwm); 43 | const char *pwm_errmsg(pwm_t *pwm); 44 | ``` 45 | 46 | ### ENUMERATIONS 47 | 48 | * `pwm_polarity_t` 49 | * `PWM_POLARITY_NORMAL`: Normal polarity 50 | * `PWM_POLARITY_INVERSED`: Inversed polarity 51 | 52 | ### DESCRIPTION 53 | 54 | ``` c 55 | pwm_t *pwm_new(void); 56 | ``` 57 | Allocate a PWM handle. 58 | 59 | Returns a valid handle on success, or NULL on failure. 60 | 61 | ------ 62 | 63 | ``` c 64 | int pwm_open(pwm_t *pwm, unsigned int chip, unsigned int channel); 65 | ``` 66 | Open the sysfs PWM with the specified chip and channel. 67 | 68 | `pwm` should be a valid pointer to an allocated PWM handle structure. 69 | 70 | Returns 0 on success, or a negative [PWM error code](#return-value) on failure. 71 | 72 | ------ 73 | 74 | ``` c 75 | int pwm_enable(pwm_t *pwm); 76 | ``` 77 | Enable the PWM output. 78 | 79 | `pwm` should be a valid pointer to a PWM handle opened with `pwm_open()`. 80 | 81 | Returns 0 on success, or a negative [PWM error code](#return-value) on failure. 82 | 83 | ------ 84 | 85 | ``` c 86 | int pwm_disable(pwm_t *pwm); 87 | ``` 88 | Disable the PWM output. 89 | 90 | `pwm` should be a valid pointer to a PWM handle opened with `pwm_open()`. 91 | 92 | Returns 0 on success, or a negative [PWM error code](#return-value) on failure. 93 | 94 | ------ 95 | 96 | ``` c 97 | int pwm_close(pwm_t *pwm); 98 | ``` 99 | Close the PWM. 100 | 101 | `pwm` should be a valid pointer to a PWM handle opened with `pwm_open()`. 102 | 103 | Returns 0 on success, or a negative [PWM error code](#return-value) on failure. 104 | 105 | ------ 106 | 107 | ``` c 108 | void pwm_free(pwm_t *pwm); 109 | ``` 110 | Free a PWM handle. 111 | 112 | ------ 113 | 114 | ``` c 115 | int pwm_get_enabled(pwm_t *pwm, bool *enabled); 116 | ``` 117 | Get the output state of the PWM. 118 | 119 | `pwm` should be a valid pointer to a PWM handle opened with `pwm_open()`. 120 | 121 | Returns 0 on success, or a negative [PWM error code](#return-value) on failure. 122 | 123 | ------ 124 | 125 | ``` c 126 | int pwm_get_period_ns(pwm_t *pwm, uint64_t *period_ns); 127 | int pwm_get_duty_cycle_ns(pwm_t *pwm, uint64_t *duty_cycle_ns); 128 | ``` 129 | Get the period in nanoseconds or duty cycle in nanoseconds, respectively, of the PWM. 130 | 131 | `pwm` should be a valid pointer to a PWM handle opened with `pwm_open()`. 132 | 133 | Returns 0 on success, or a negative [PWM error code](#return-value) on failure. 134 | 135 | ------ 136 | 137 | ``` c 138 | int pwm_get_period(pwm_t *pwm, double *period); 139 | int pwm_get_duty_cycle(pwm_t *pwm, double *duty_cycle); 140 | int pwm_get_frequency(pwm_t *pwm, double *frequency); 141 | ``` 142 | Get the period in seconds, duty cycle as a ratio between 0.0 to 1.0, or frequency in Hz, respectively, of the PWM. 143 | 144 | `pwm` should be a valid pointer to a PWM handle opened with `pwm_open()`. 145 | 146 | Returns 0 on success, or a negative [PWM error code](#return-value) on failure. 147 | 148 | ------ 149 | 150 | ``` c 151 | int pwm_get_polarity(pwm_t *pwm, pwm_polarity_t *polarity); 152 | ``` 153 | Get the output polarity of the PWM. 154 | 155 | `pwm` should be a valid pointer to a PWM handle opened with `pwm_open()`. 156 | 157 | Returns 0 on success, or a negative [PWM error code](#return-value) on failure. 158 | 159 | ------ 160 | 161 | ``` c 162 | int pwm_set_enabled(pwm_t *pwm, bool enabled); 163 | ``` 164 | Set the output state of the PWM. 165 | 166 | `pwm` should be a valid pointer to a PWM handle opened with `pwm_open()`. 167 | 168 | Returns 0 on success, or a negative [PWM error code](#return-value) on failure. 169 | 170 | ------ 171 | 172 | ``` c 173 | int pwm_set_period_ns(pwm_t *pwm, uint64_t period_ns); 174 | int pwm_set_duty_cycle_ns(pwm_t *pwm, uint64_t duty_cycle_ns); 175 | ``` 176 | Set the period in nanoseconds or duty cycle in nanoseconds, respectively, of the PWM. 177 | 178 | `pwm` should be a valid pointer to a PWM handle opened with `pwm_open()`. 179 | 180 | Returns 0 on success, or a negative [PWM error code](#return-value) on failure. 181 | 182 | ------ 183 | 184 | ``` c 185 | int pwm_set_period(pwm_t *pwm, double period); 186 | int pwm_set_duty_cycle(pwm_t *pwm, double duty_cycle); 187 | int pwm_set_frequency(pwm_t *pwm, double frequency); 188 | ``` 189 | Set the period in seconds, duty cycle as a ratio between 0.0 to 1.0, or frequency in Hz, respectively, of the PWM. 190 | 191 | `pwm` should be a valid pointer to a PWM handle opened with `pwm_open()`. 192 | 193 | Returns 0 on success, or a negative [PWM error code](#return-value) on failure. 194 | 195 | ------ 196 | 197 | ``` c 198 | int pwm_set_polarity(pwm_t *pwm, pwm_polarity_t *polarity); 199 | ``` 200 | Set the output polarity of the PWM. 201 | 202 | `pwm` should be a valid pointer to a PWM handle opened with `pwm_open()`. 203 | 204 | Returns 0 on success, or a negative [PWM error code](#return-value) on failure. 205 | 206 | ------ 207 | 208 | ``` c 209 | unsigned int pwm_chip(pwm_t *pwm); 210 | ``` 211 | Return the chip number of the PWM handle. 212 | 213 | `pwm` should be a valid pointer to a PWM handle opened with `pwm_open()`. 214 | 215 | This function is a simple accessor to the PWM handle structure and always succeeds. 216 | 217 | ------ 218 | 219 | ``` c 220 | unsigned int pwm_channel(pwm_t *pwm); 221 | ``` 222 | Return the channel number of the PWM handle. 223 | 224 | `pwm` should be a valid pointer to a PWM handle opened with `pwm_open()`. 225 | 226 | This function is a simple accessor to the PWM handle structure and always succeeds. 227 | 228 | ------ 229 | 230 | ``` c 231 | int pwm_tostring(pwm_t *pwm, char *str, size_t len); 232 | ``` 233 | Return a string representation of the PWM handle. 234 | 235 | `pwm` should be a valid pointer to a PWM handle opened with `pwm_open()`. 236 | 237 | This function behaves and returns like `snprintf()`. 238 | 239 | ------ 240 | 241 | ``` c 242 | int pwm_errno(pwm_t *pwm); 243 | ``` 244 | Return the libc errno of the last failure that occurred. 245 | 246 | `pwm` should be a valid pointer to a PWM handle opened with `pwm_open()`. 247 | 248 | ------ 249 | 250 | ``` c 251 | const char *pwm_errmsg(pwm_t *pwm); 252 | ``` 253 | Return a human readable error message of the last failure that occurred. 254 | 255 | `pwm` should be a valid pointer to a PWM handle opened with `pwm_open()`. 256 | 257 | ### RETURN VALUE 258 | 259 | The periphery PWM functions return 0 on success or one of the negative error codes below on failure. 260 | 261 | The libc errno of the failure in an underlying libc library call can be obtained with the `pwm_errno()` helper function. A human readable error message can be obtained with the `pwm_errmsg()` helper function. 262 | 263 | | Error Code | Description | 264 | |-----------------------|-----------------------------------| 265 | | `PWM_ERROR_ARG` | Invalid arguments | 266 | | `PWM_ERROR_OPEN` | Opening PWM | 267 | | `PWM_ERROR_QUERY` | Querying PWM attributes | 268 | | `PWM_ERROR_CONFIGURE` | Configuring PWM attributes | 269 | | `PWM_ERROR_CLOSE` | Closing PWM | 270 | 271 | ### EXAMPLE 272 | 273 | ``` c 274 | #include 275 | #include 276 | 277 | #include "pwm.h" 278 | 279 | int main(void) { 280 | pwm_t *pwm; 281 | 282 | pwm = pwm_new(); 283 | 284 | /* Open PWM chip 0, channel 10 */ 285 | if (pwm_open(pwm, 0, 10) < 0) { 286 | fprintf(stderr, "pwm_open(): %s\n", pwm_errmsg(pwm)); 287 | exit(1); 288 | } 289 | 290 | /* Set frequency to 1 kHz */ 291 | if (pwm_set_frequency(pwm, 1e3) < 0) { 292 | fprintf(stderr, "pwm_set_frequency(): %s\n", pwm_errmsg(pwm)); 293 | exit(1); 294 | } 295 | 296 | /* Set duty cycle to 75% */ 297 | if (pwm_set_duty_cycle(pwm, 0.75) < 0) { 298 | fprintf(stderr, "pwm_set_duty_cycle(): %s\n", pwm_errmsg(pwm)); 299 | exit(1); 300 | } 301 | 302 | /* Enable PWM */ 303 | if (pwm_enable(pwm) < 0) { 304 | fprintf(stderr, "pwm_enable(): %s\n", pwm_errmsg(pwm)); 305 | exit(1); 306 | } 307 | 308 | /* Change duty cycle to 50% */ 309 | if (pwm_set_duty_cycle(pwm, 0.50) < 0) { 310 | fprintf(stderr, "pwm_set_duty_cycle(): %s\n", pwm_errmsg(pwm)); 311 | exit(1); 312 | } 313 | 314 | pwm_close(pwm); 315 | 316 | pwm_free(pwm); 317 | 318 | return 0; 319 | } 320 | ``` 321 | 322 | -------------------------------------------------------------------------------- /src/led.c: -------------------------------------------------------------------------------- 1 | /* 2 | * c-periphery 3 | * https://github.com/vsergeev/c-periphery 4 | * License: MIT 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | #include "led.h" 19 | 20 | #define P_PATH_MAX 256 21 | 22 | struct led_handle { 23 | char name[64]; 24 | unsigned int max_brightness; 25 | 26 | struct { 27 | int c_errno; 28 | char errmsg[96]; 29 | } error; 30 | }; 31 | 32 | static int _led_error(led_t *led, int code, int c_errno, const char *fmt, ...) { 33 | va_list ap; 34 | 35 | led->error.c_errno = c_errno; 36 | 37 | va_start(ap, fmt); 38 | vsnprintf(led->error.errmsg, sizeof(led->error.errmsg), fmt, ap); 39 | va_end(ap); 40 | 41 | /* Tack on strerror() and errno */ 42 | if (c_errno) { 43 | char buf[64] = {0}; 44 | strerror_r(c_errno, buf, sizeof(buf)); 45 | snprintf(led->error.errmsg+strlen(led->error.errmsg), sizeof(led->error.errmsg)-strlen(led->error.errmsg), ": %s [errno %d]", buf, c_errno); 46 | } 47 | 48 | return code; 49 | } 50 | 51 | led_t *led_new(void) { 52 | led_t *led = calloc(1, sizeof(led_t)); 53 | if (led == NULL) 54 | return NULL; 55 | 56 | return led; 57 | } 58 | 59 | int led_open(led_t *led, const char *name) { 60 | char led_path[P_PATH_MAX]; 61 | int fd, ret; 62 | 63 | snprintf(led_path, sizeof(led_path), "/sys/class/leds/%s/brightness", name); 64 | 65 | if ((fd = open(led_path, O_RDWR)) < 0) 66 | return _led_error(led, LED_ERROR_OPEN, errno, "Opening LED: opening 'brightness'"); 67 | 68 | close(fd); 69 | 70 | strncpy(led->name, name, sizeof(led->name) - 1); 71 | led->name[sizeof(led->name) - 1] = '\0'; 72 | 73 | if ((ret = led_get_max_brightness(led, &led->max_brightness)) < 0) 74 | return ret; 75 | 76 | return 0; 77 | } 78 | 79 | int led_read(led_t *led, bool *value) { 80 | int ret; 81 | unsigned int brightness; 82 | 83 | if ((ret = led_get_brightness(led, &brightness)) < 0) 84 | return ret; 85 | 86 | *value = brightness != 0; 87 | 88 | return 0; 89 | } 90 | 91 | int led_write(led_t *led, bool value) { 92 | return led_set_brightness(led, value ? led->max_brightness : 0); 93 | } 94 | 95 | int led_close(led_t *led) { 96 | (void)led; 97 | return 0; 98 | } 99 | 100 | void led_free(led_t *led) { 101 | free(led); 102 | } 103 | 104 | int led_get_brightness(led_t *led, unsigned int *brightness) { 105 | char led_path[P_PATH_MAX]; 106 | char buf[16]; 107 | int fd, ret; 108 | 109 | snprintf(led_path, sizeof(led_path), "/sys/class/leds/%s/brightness", led->name); 110 | 111 | if ((fd = open(led_path, O_RDONLY)) < 0) 112 | return _led_error(led, LED_ERROR_IO, errno, "Opening LED 'brightness'"); 113 | 114 | if ((ret = read(fd, buf, sizeof(buf) - 1)) < 0) { 115 | int errsv = errno; 116 | close(fd); 117 | return _led_error(led, LED_ERROR_IO, errsv, "Reading LED 'brightness'"); 118 | } 119 | 120 | buf[ret] = '\0'; 121 | 122 | if (close(fd) < 0) 123 | return _led_error(led, LED_ERROR_IO, errno, "Closing LED 'brightness'"); 124 | 125 | *brightness = strtoul(buf, NULL, 10); 126 | 127 | return 0; 128 | } 129 | 130 | int led_get_max_brightness(led_t *led, unsigned int *max_brightness) { 131 | char led_path[P_PATH_MAX]; 132 | char buf[16]; 133 | int fd, ret; 134 | 135 | snprintf(led_path, sizeof(led_path), "/sys/class/leds/%s/max_brightness", led->name); 136 | 137 | if ((fd = open(led_path, O_RDONLY)) < 0) 138 | return _led_error(led, LED_ERROR_QUERY, errno, "Opening LED 'max_brightness'"); 139 | 140 | if ((ret = read(fd, buf, sizeof(buf) - 1)) < 0) { 141 | int errsv = errno; 142 | close(fd); 143 | return _led_error(led, LED_ERROR_QUERY, errsv, "Reading LED 'max_brightness'"); 144 | } 145 | 146 | buf[ret] = '\0'; 147 | 148 | if (close(fd) < 0) 149 | return _led_error(led, LED_ERROR_QUERY, errno, "Closing LED 'max_brightness'"); 150 | 151 | led->max_brightness = strtoul(buf, NULL, 10); 152 | 153 | *max_brightness = led->max_brightness; 154 | 155 | return 0; 156 | } 157 | 158 | int led_get_trigger(led_t *led, char *str, size_t len) { 159 | char led_path[P_PATH_MAX]; 160 | FILE *fp; 161 | int c; 162 | enum { SCANNING, TRIGGER } state = SCANNING; 163 | size_t i = 0; 164 | 165 | if (!len) 166 | return 0; 167 | 168 | snprintf(led_path, sizeof(led_path), "/sys/class/leds/%s/trigger", led->name); 169 | 170 | if ((fp = fopen(led_path, "r")) == NULL) 171 | return _led_error(led, LED_ERROR_QUERY, errno, "Opening LED 'trigger'"); 172 | 173 | while ((c = fgetc(fp)) != EOF && i < len - 1) { 174 | if (c == '[' && state == SCANNING) { 175 | state = TRIGGER; 176 | } else if (c == ']' && state == TRIGGER) { 177 | break; 178 | } else if (state == TRIGGER) { 179 | str[i++] = (char)c; 180 | } 181 | } 182 | 183 | if (state != TRIGGER) 184 | return _led_error(led, LED_ERROR_QUERY, 0, "Active LED trigger not found"); 185 | 186 | /* Null-terminate */ 187 | str[i] = '\0'; 188 | 189 | if (fclose(fp) != 0) 190 | return _led_error(led, LED_ERROR_QUERY, errno, "Closing LED 'trigger'"); 191 | 192 | return 0; 193 | } 194 | 195 | int led_get_triggers_entry(led_t *led, unsigned int index, char *str, size_t len) { 196 | char led_path[P_PATH_MAX]; 197 | FILE *fp; 198 | int c; 199 | unsigned int _count = 0; 200 | size_t i = 0; 201 | 202 | if (!len) 203 | return 0; 204 | 205 | snprintf(led_path, sizeof(led_path), "/sys/class/leds/%s/trigger", led->name); 206 | 207 | if ((fp = fopen(led_path, "r")) == NULL) 208 | return _led_error(led, LED_ERROR_QUERY, errno, "Opening LED 'trigger'"); 209 | 210 | /* Scan for trigger index */ 211 | while (_count < index && (c = fgetc(fp)) != EOF) { 212 | if (c == ' ' || c == '\n') { 213 | _count++; 214 | } 215 | } 216 | 217 | if (_count != index) 218 | return _led_error(led, LED_ERROR_QUERY, 0, "Index %u not found in LED triggers", index); 219 | 220 | /* Copy trigger */ 221 | while ((c = fgetc(fp)) != EOF && i < len - 1) { 222 | if (c == ' ' || c == '\n') { 223 | break; 224 | } else if (c != '[' && c != ']') { 225 | str[i++] = c; 226 | } 227 | } 228 | 229 | /* Null-terminate */ 230 | str[i] = '\0'; 231 | 232 | if (fclose(fp) != 0) 233 | return _led_error(led, LED_ERROR_QUERY, errno, "Closing LED 'trigger'"); 234 | 235 | return 0; 236 | } 237 | 238 | int led_get_triggers_count(led_t *led, unsigned int *count) { 239 | char led_path[P_PATH_MAX]; 240 | FILE *fp; 241 | int c; 242 | enum { SCANNING, TRIGGER } state = SCANNING; 243 | unsigned int _count = 0; 244 | 245 | snprintf(led_path, sizeof(led_path), "/sys/class/leds/%s/trigger", led->name); 246 | 247 | if ((fp = fopen(led_path, "r")) == NULL) 248 | return _led_error(led, LED_ERROR_IO, errno, "Opening LED 'trigger'"); 249 | 250 | while ((c = fgetc(fp)) != EOF) { 251 | if ((c == ' ' || c == '\n') && state == TRIGGER) { 252 | _count++; 253 | state = SCANNING; 254 | } else if (state == SCANNING) { 255 | state = TRIGGER; 256 | } 257 | } 258 | 259 | if (fclose(fp) != 0) 260 | return _led_error(led, LED_ERROR_IO, errno, "Closing LED 'trigger'"); 261 | 262 | *count = _count; 263 | 264 | return 0; 265 | } 266 | 267 | int led_set_brightness(led_t *led, unsigned int brightness) { 268 | char led_path[P_PATH_MAX]; 269 | char buf[16]; 270 | int fd, len; 271 | 272 | if (brightness > led->max_brightness) 273 | return _led_error(led, LED_ERROR_ARG, 0, "Brightness out of bounds (max is %u)", led->max_brightness); 274 | 275 | snprintf(led_path, sizeof(led_path), "/sys/class/leds/%s/brightness", led->name); 276 | 277 | if ((fd = open(led_path, O_WRONLY)) < 0) 278 | return _led_error(led, LED_ERROR_IO, errno, "Opening LED 'brightness'"); 279 | 280 | len = snprintf(buf, sizeof(buf), "%u\n", brightness); 281 | 282 | if (write(fd, buf, len) < 0) { 283 | int errsv = errno; 284 | close(fd); 285 | return _led_error(led, LED_ERROR_IO, errsv, "Writing LED 'brightness'"); 286 | } 287 | 288 | if (close(fd) < 0) 289 | return _led_error(led, LED_ERROR_IO, errno, "Closing LED 'brightness'"); 290 | 291 | return 0; 292 | } 293 | 294 | int led_set_trigger(led_t *led, const char *trigger) { 295 | char led_path[P_PATH_MAX]; 296 | FILE *fp; 297 | 298 | snprintf(led_path, sizeof(led_path), "/sys/class/leds/%s/trigger", led->name); 299 | 300 | if ((fp = fopen(led_path, "w")) == NULL) 301 | return _led_error(led, LED_ERROR_QUERY, errno, "Opening LED 'trigger'"); 302 | 303 | if (fputs(trigger, fp) == EOF) 304 | return _led_error(led, LED_ERROR_QUERY, errno, "Writing LED 'trigger'"); 305 | 306 | if (fputc('\n', fp) == EOF) 307 | return _led_error(led, LED_ERROR_QUERY, errno, "Writing LED 'trigger'"); 308 | 309 | if (fclose(fp) != 0) 310 | return _led_error(led, LED_ERROR_QUERY, errno, "Closing LED 'trigger'"); 311 | 312 | return 0; 313 | } 314 | 315 | int led_name(led_t *led, char *str, size_t len) { 316 | if (!len) 317 | return 0; 318 | 319 | strncpy(str, led->name, len - 1); 320 | str[len - 1] = '\0'; 321 | 322 | return 0; 323 | } 324 | 325 | int led_tostring(led_t *led, char *str, size_t len) { 326 | unsigned int brightness; 327 | char brightness_str[16]; 328 | unsigned int max_brightness; 329 | char max_brightness_str[16]; 330 | char trigger_str[64]; 331 | 332 | if (led_get_brightness(led, &brightness) < 0) 333 | strcpy(brightness_str, ""); 334 | else 335 | snprintf(brightness_str, sizeof(brightness_str), "%u", brightness); 336 | 337 | if (led_get_max_brightness(led, &max_brightness) < 0) 338 | strcpy(max_brightness_str, ""); 339 | else 340 | snprintf(max_brightness_str, sizeof(max_brightness_str), "%u", max_brightness); 341 | 342 | if (led_get_trigger(led, trigger_str, sizeof(trigger_str)) < 0) 343 | strcpy(trigger_str, ""); 344 | 345 | return snprintf(str, len, "LED %s (brightness=%s, max_brightness=%s, trigger=%s)", led->name, brightness_str, max_brightness_str, trigger_str); 346 | } 347 | 348 | int led_errno(led_t *led) { 349 | return led->error.c_errno; 350 | } 351 | 352 | const char *led_errmsg(led_t *led) { 353 | return led->error.errmsg; 354 | } 355 | -------------------------------------------------------------------------------- /docs/spi.md: -------------------------------------------------------------------------------- 1 | ### NAME 2 | 3 | SPI wrapper functions for Linux userspace `spidev` devices. 4 | 5 | ### SYNOPSIS 6 | 7 | ``` c 8 | #include 9 | 10 | /* Primary Functions */ 11 | spi_t *spi_new(void); 12 | int spi_open(spi_t *spi, const char *path, unsigned int mode, uint32_t max_speed); 13 | int spi_open_advanced(spi_t *spi, const char *path, unsigned int mode, uint32_t max_speed, 14 | spi_bit_order_t bit_order, uint8_t bits_per_word, uint8_t extra_flags); 15 | int spi_open_advanced2(spi_t *spi, const char *path, unsigned int mode, uint32_t max_speed, 16 | spi_bit_order_t bit_order, uint8_t bits_per_word, uint32_t extra_flags); 17 | int spi_transfer(spi_t *spi, const uint8_t *txbuf, uint8_t *rxbuf, size_t len); 18 | int spi_transfer_advanced(spi_t *spi, const spi_msg_t *msgs, size_t count); 19 | int spi_close(spi_t *spi); 20 | void spi_free(spi_t *spi); 21 | 22 | /* Getters */ 23 | int spi_get_mode(spi_t *spi, unsigned int *mode); 24 | int spi_get_max_speed(spi_t *spi, uint32_t *max_speed); 25 | int spi_get_bit_order(spi_t *spi, spi_bit_order_t *bit_order); 26 | int spi_get_bits_per_word(spi_t *spi, uint8_t *bits_per_word); 27 | int spi_get_extra_flags(spi_t *spi, uint8_t *extra_flags); 28 | int spi_get_extra_flags32(spi_t *spi, uint32_t *extra_flags); 29 | 30 | /* Setters */ 31 | int spi_set_mode(spi_t *spi, unsigned int mode); 32 | int spi_set_max_speed(spi_t *spi, uint32_t max_speed); 33 | int spi_set_bit_order(spi_t *spi, spi_bit_order_t bit_order); 34 | int spi_set_bits_per_word(spi_t *spi, uint8_t bits_per_word); 35 | int spi_set_extra_flags(spi_t *spi, uint8_t extra_flags); 36 | int spi_set_extra_flags32(spi_t *spi, uint32_t extra_flags); 37 | 38 | /* Miscellaneous */ 39 | int spi_fd(spi_t *spi); 40 | int spi_tostring(spi_t *spi, char *str, size_t len); 41 | 42 | /* Error Handling */ 43 | int spi_errno(spi_t *spi); 44 | const char *spi_errmsg(spi_t *spi); 45 | ``` 46 | 47 | ### ENUMERATIONS 48 | 49 | * `spi_bit_order_t` 50 | * `MSB_FIRST`: Most significant bit first transfer (typical) 51 | * `LSB_FIRST`: Least significant bit first transfer 52 | 53 | ### DESCRIPTION 54 | 55 | ``` c 56 | spi_t *spi_new(void); 57 | ``` 58 | Allocate a SPI handle. 59 | 60 | Returns a valid handle on success, or NULL on failure. 61 | 62 | ------ 63 | 64 | ``` c 65 | int spi_open(spi_t *spi, const char *path, unsigned int mode, uint32_t max_speed); 66 | ``` 67 | Open the `spidev` device at the specified path (e.g. "/dev/spidev1.0"), with the specified SPI mode, specified max speed in hertz, and the defaults of `MSB_FIRST` bit order, and 8 bits per word. 68 | 69 | `spi` should be a valid pointer to an allocated SPI handle structure. SPI mode can be 0, 1, 2, or 3. 70 | 71 | Returns 0 on success, or a negative [SPI error code](#return-value) on failure. 72 | 73 | ------ 74 | 75 | ``` c 76 | int spi_open_advanced(spi_t *spi, const char *path, unsigned int mode, uint32_t max_speed, 77 | spi_bit_order_t bit_order, uint8_t bits_per_word, uint8_t extra_flags); 78 | ``` 79 | Open the `spidev` device at the specified path, with the specified SPI mode, max speed in hertz, bit order, bits per word, and extra flags. 80 | 81 | `spi` should be a valid pointer to an allocated SPI handle structure. SPI mode can be 0, 1, 2, or 3. Bit order can be `MSB_FIRST` or `LSB_FIRST`, as defined [above](#enumerations). Bits per word specifies the transfer word size. Extra flags specified additional flags bitwise-ORed with the SPI mode. 82 | 83 | Returns 0 on success, or a negative [SPI error code](#return-value) on failure. 84 | 85 | ------ 86 | 87 | ``` c 88 | int spi_open_advanced2(spi_t *spi, const char *path, unsigned int mode, uint32_t max_speed, 89 | spi_bit_order_t bit_order, uint8_t bits_per_word, uint32_t extra_flags); 90 | ``` 91 | Open the `spidev` device at the specified path, with the specified SPI mode, max speed in hertz, bit order, bits per word, and extra flags. This open function is the same as `spi_open_advanced()`, except that `extra_flags` can be 32-bits. 92 | 93 | `spi` should be a valid pointer to an allocated SPI handle structure. SPI mode can be 0, 1, 2, or 3. Bit order can be `MSB_FIRST` or `LSB_FIRST`, as defined [above](#enumerations). Bits per word specifies the transfer word size. Extra flags specified additional flags bitwise-ORed with the SPI mode. 94 | 95 | Returns 0 on success, or a negative [SPI error code](#return-value) on failure. 96 | 97 | ------ 98 | 99 | ``` c 100 | int spi_transfer(spi_t *spi, const uint8_t *txbuf, uint8_t *rxbuf, size_t len); 101 | ``` 102 | Shift out `len` word counts of the `txbuf` buffer, while shifting in `len` word counts to the `rxbuf` buffer. 103 | 104 | `spi` should be a valid pointer to an SPI handle opened with `spi_open()` or `spi_open_advanced()`. 105 | 106 | `rxbuf` may be NULL. `txbuf` and `rxbuf` may point to the same buffer. 107 | 108 | Returns 0 on success, or a negative [SPI error code](#return-value) on failure. 109 | 110 | ------ 111 | 112 | ``` c 113 | typedef struct spi_msg { 114 | const uint8_t *txbuf; 115 | uint8_t *rxbuf; 116 | size_t len; 117 | bool deselect; 118 | uint16_t deselect_delay_us; 119 | uint8_t word_delay_us; 120 | } spi_msg_t; 121 | 122 | int spi_transfer_advanced(spi_t *spi, const spi_msg_t *msgs, size_t count); 123 | ``` 124 | Transfer messages, shifting out `len` word counts of the `txbuf` buffer, while shifting in `len` word counts to the `rxbuf` buffer for each message. If `deselect` is true, deselect the device before reselecting it for the following transfer. 125 | 126 | `deselect_delay_us` specifies a delay in microseconds before deselection when `deselect` is true. `word_delay_us` specifies a delay in microseconds between words within a transfer. Note that `deselect_delay_us` and `word_delay_us` may not by supported by all SPI controllers and may be silently ignored. 127 | 128 | `spi` should be a valid pointer to an SPI handle opened with `spi_open()` or `spi_open_advanced()`. 129 | 130 | `rxbuf` may be NULL. `txbuf` and `rxbuf` may point to the same buffer. 131 | 132 | Returns 0 on success, or a negative [SPI error code](#return-value) on failure. 133 | 134 | ------ 135 | 136 | ``` c 137 | int spi_close(spi_t *spi); 138 | ``` 139 | Close the `spidev` device. 140 | 141 | `spi` should be a valid pointer to an SPI handle opened with `spi_open()` or `spi_open_advanced()`. 142 | 143 | Returns 0 on success, or a negative [SPI error code](#return-value) on failure. 144 | 145 | ------ 146 | 147 | ``` c 148 | void spi_free(spi_t *spi); 149 | ``` 150 | Free a SPI handle. 151 | 152 | ------ 153 | 154 | ``` c 155 | int spi_get_mode(spi_t *spi, unsigned int *mode); 156 | int spi_get_max_speed(spi_t *spi, uint32_t *max_speed); 157 | int spi_get_bit_order(spi_t *spi, spi_bit_order_t *bit_order); 158 | int spi_get_bits_per_word(spi_t *spi, uint8_t *bits_per_word); 159 | int spi_get_extra_flags(spi_t *spi, uint8_t *extra_flags); 160 | int spi_get_extra_flags32(spi_t *spi, uint32_t *extra_flags); 161 | ``` 162 | Get the mode, max speed, bit order, bits per word, or extra flags, respectively, of the underlying `spidev` device. 163 | 164 | `spi` should be a valid pointer to a SPI handle opened with `spi_open()` or `spi_open_advanced()`. 165 | 166 | Returns 0 on success, or a negative [SPI error code](#return-value) on failure. 167 | 168 | ------ 169 | 170 | ``` c 171 | int spi_set_mode(spi_t *spi, unsigned int mode); 172 | int spi_set_max_speed(spi_t *spi, uint32_t max_speed); 173 | int spi_set_bit_order(spi_t *spi, spi_bit_order_t bit_order); 174 | int spi_set_bits_per_word(spi_t *spi, uint8_t bits_per_word); 175 | int spi_set_extra_flags(spi_t *spi, uint8_t extra_flags); 176 | int spi_set_extra_flags32(spi_t *spi, uint32_t extra_flags); 177 | ``` 178 | Set the mode, max speed, bit order, bits per word, or extra flags, respectively, on the underlying `spidev` device. 179 | 180 | `spi` should be a valid pointer to a SPI handle opened with `spi_open()` or `spi_open_advanced()`. 181 | 182 | Returns 0 on success, or a negative [SPI error code](#return-value) on failure. 183 | 184 | ------ 185 | 186 | ``` c 187 | int spi_fd(spi_t *spi); 188 | ``` 189 | Return the file descriptor (for the underlying `spidev` device) of the SPI handle. 190 | 191 | `spi` should be a valid pointer to a SPI handle opened with `spi_open()` or `spi_open_advanced()`. 192 | 193 | This function is a simple accessor to the SPI handle structure and always succeeds. 194 | 195 | ------ 196 | 197 | ``` c 198 | int spi_tostring(spi_t *spi, char *str, size_t len); 199 | ``` 200 | Return a string representation of the SPI handle. 201 | 202 | `spi` should be a valid pointer to a SPI handle opened with `spi_open()` or `spi_open_advanced()`. 203 | 204 | This function behaves and returns like `snprintf()`. 205 | 206 | ------ 207 | 208 | ``` c 209 | int spi_errno(spi_t *spi); 210 | ``` 211 | Return the libc errno of the last failure that occurred. 212 | 213 | `spi` should be a valid pointer to a SPI handle opened with `spi_open()` or `spi_open_advanced()`. 214 | 215 | ------ 216 | 217 | ``` c 218 | const char *spi_errmsg(spi_t *spi); 219 | ``` 220 | Return a human readable error message of the last failure that occurred. 221 | 222 | `spi` should be a valid pointer to a SPI handle opened with `spi_open()` or `spi_open_advanced()`. 223 | 224 | ### RETURN VALUE 225 | 226 | The periphery SPI functions return 0 on success or one of the negative error codes below on failure. 227 | 228 | The libc errno of the failure in an underlying libc library call can be obtained with the `spi_errno()` helper function. A human readable error message can be obtained with the `spi_errmsg()` helper function. 229 | 230 | | Error Code | Description | 231 | |-----------------------|-----------------------------------| 232 | | `SPI_ERROR_ARG` | Invalid arguments | 233 | | `SPI_ERROR_OPEN` | Opening SPI device | 234 | | `SPI_ERROR_QUERY` | Querying SPI device attributes | 235 | | `SPI_ERROR_CONFIGURE` | Configuring SPI device attributes | 236 | | `SPI_ERROR_TRANSFER` | SPI transfer | 237 | | `SPI_ERROR_CLOSE` | Closing SPI device | 238 | 239 | ### EXAMPLE 240 | 241 | ``` c 242 | #include 243 | #include 244 | #include 245 | 246 | #include "spi.h" 247 | 248 | int main(void) { 249 | spi_t *spi; 250 | uint8_t buf[4] = { 0xaa, 0xbb, 0xcc, 0xdd }; 251 | 252 | spi = spi_new(); 253 | 254 | /* Open spidev1.0 with mode 0 and max speed 1MHz */ 255 | if (spi_open(spi, "/dev/spidev1.0", 0, 1000000) < 0) { 256 | fprintf(stderr, "spi_open(): %s\n", spi_errmsg(spi)); 257 | exit(1); 258 | } 259 | 260 | /* Shift out and in 4 bytes */ 261 | if (spi_transfer(spi, buf, buf, sizeof(buf)) < 0) { 262 | fprintf(stderr, "spi_transfer(): %s\n", spi_errmsg(spi)); 263 | exit(1); 264 | } 265 | 266 | printf("shifted in: 0x%02x 0x%02x 0x%02x 0x%02x\n", buf[0], buf[1], buf[2], buf[3]); 267 | 268 | spi_close(spi); 269 | 270 | spi_free(spi); 271 | 272 | return 0; 273 | } 274 | ``` 275 | 276 | -------------------------------------------------------------------------------- /tests/test_pwm.c: -------------------------------------------------------------------------------- 1 | /* 2 | * c-periphery 3 | * https://github.com/vsergeev/c-periphery 4 | * License: MIT 5 | */ 6 | 7 | #include "test.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "../src/pwm.h" 15 | 16 | unsigned int chip; 17 | unsigned int channel; 18 | 19 | void test_arguments(void) { 20 | ptest(); 21 | 22 | /* No real argument validation needed in the PWM wrapper */ 23 | } 24 | 25 | static double fabs(double x) { 26 | return (x < 0) ? -x : x; 27 | } 28 | 29 | void test_open_config_close(void) { 30 | pwm_t *pwm; 31 | uint64_t period_ns; 32 | uint64_t duty_cycle_ns; 33 | double period; 34 | double frequency; 35 | double duty_cycle; 36 | pwm_polarity_t polarity; 37 | bool enabled; 38 | 39 | ptest(); 40 | 41 | /* Allocate PWM */ 42 | pwm = pwm_new(); 43 | passert(pwm != NULL); 44 | 45 | /* Open non-existent PWM chip */ 46 | passert(pwm_open(pwm, 9999, channel) == PWM_ERROR_OPEN); 47 | 48 | /* Open non-existent PWM channel */ 49 | passert(pwm_open(pwm, chip, 9999) == PWM_ERROR_OPEN); 50 | 51 | /* Open legitimate PWM chip/channel */ 52 | passert(pwm_open(pwm, chip, channel) == 0); 53 | 54 | /* Check properties */ 55 | passert(pwm_chip(pwm) == chip); 56 | passert(pwm_channel(pwm) == channel); 57 | 58 | /* Initialize period and duty cycle */ 59 | passert(pwm_set_period(pwm, 5e-3) == 0); 60 | passert(pwm_set_duty_cycle(pwm, 0) == 0); 61 | 62 | /* Set period, check period, check period_ns, check frequency */ 63 | passert(pwm_set_period(pwm, 1e-3) == 0); 64 | passert(pwm_get_period(pwm, &period) == 0); 65 | passert(fabs(period - 1e-3) < 1e-4); 66 | passert(pwm_get_period_ns(pwm, &period_ns) == 0); 67 | passert(fabs(period_ns - 1000000) < 1e5); 68 | passert(pwm_get_frequency(pwm, &frequency) == 0); 69 | passert(fabs(frequency - 1000) < 100); 70 | 71 | passert(pwm_set_period(pwm, 5e-4) == 0); 72 | passert(pwm_get_period(pwm, &period) == 0); 73 | passert(fabs(period - 5e-4) < 1e-5); 74 | passert(pwm_get_period_ns(pwm, &period_ns) == 0); 75 | passert(fabs(period_ns - 500000) < 1e4); 76 | passert(pwm_get_frequency(pwm, &frequency) == 0); 77 | passert(fabs(frequency - 2000) < 100); 78 | 79 | /* Set frequency, check frequency, check period, check period_ns */ 80 | passert(pwm_set_frequency(pwm, 1000) == 0); 81 | passert(pwm_get_frequency(pwm, &frequency) == 0); 82 | passert(fabs(frequency - 1000) < 100); 83 | passert(pwm_get_period(pwm, &period) == 0); 84 | passert(fabs(period - 1e-3) < 1e-4); 85 | passert(pwm_get_period_ns(pwm, &period_ns) == 0); 86 | passert(fabs(period_ns - 1000000) < 1e5); 87 | 88 | passert(pwm_set_frequency(pwm, 2000) == 0); 89 | passert(pwm_get_frequency(pwm, &frequency) == 0); 90 | passert(fabs(frequency - 2000) < 100); 91 | passert(pwm_get_period(pwm, &period) == 0); 92 | passert(fabs(period - 5e-4) < 1e-5); 93 | passert(pwm_get_period_ns(pwm, &period_ns) == 0); 94 | passert(fabs(period_ns - 500000) < 1e4); 95 | 96 | /* Set period_ns, check period_ns, check period, check frequency */ 97 | passert(pwm_set_period_ns(pwm, 1000000) == 0); 98 | passert(pwm_get_period_ns(pwm, &period_ns) == 0); 99 | passert(fabs(period_ns - 1000000) < 1e5); 100 | passert(pwm_get_period(pwm, &period) == 0); 101 | passert(fabs(period - 1e-3) < 1e-4); 102 | passert(pwm_get_frequency(pwm, &frequency) == 0); 103 | passert(fabs(frequency - 1000) < 100); 104 | 105 | passert(pwm_set_period_ns(pwm, 500000) == 0); 106 | passert(pwm_get_period_ns(pwm, &period_ns) == 0); 107 | passert(fabs(period_ns - 500000) < 1e4); 108 | passert(pwm_get_period(pwm, &period) == 0); 109 | passert(fabs(period - 5e-4) < 1e-5); 110 | passert(pwm_get_frequency(pwm, &frequency) == 0); 111 | passert(fabs(frequency - 2000) < 100); 112 | 113 | passert(pwm_set_period_ns(pwm, 1000000) == 0); 114 | 115 | /* Set duty cycle, check duty cycle, check duty_cycle_ns */ 116 | passert(pwm_set_duty_cycle(pwm, 0.25) == 0); 117 | passert(pwm_get_duty_cycle(pwm, &duty_cycle) == 0); 118 | passert(fabs(duty_cycle - 0.25) < 1e-3); 119 | passert(pwm_get_duty_cycle_ns(pwm, &duty_cycle_ns) == 0); 120 | passert(fabs(duty_cycle_ns - 250000) < 1e4); 121 | 122 | passert(pwm_set_duty_cycle(pwm, 0.50) == 0); 123 | passert(pwm_get_duty_cycle(pwm, &duty_cycle) == 0); 124 | passert(fabs(duty_cycle - 0.50) < 1e-3); 125 | passert(pwm_get_duty_cycle_ns(pwm, &duty_cycle_ns) == 0); 126 | passert(fabs(duty_cycle_ns - 500000) < 1e4); 127 | 128 | passert(pwm_set_duty_cycle(pwm, 0.75) == 0); 129 | passert(pwm_get_duty_cycle(pwm, &duty_cycle) == 0); 130 | passert(fabs(duty_cycle - 0.75) < 1e-3); 131 | passert(pwm_get_duty_cycle_ns(pwm, &duty_cycle_ns) == 0); 132 | passert(fabs(duty_cycle_ns - 750000) < 1e4); 133 | 134 | /* Set duty_cycle_ns, check duty_cycle_ns, check duty_cycle */ 135 | passert(pwm_set_duty_cycle_ns(pwm, 250000) == 0); 136 | passert(pwm_get_duty_cycle_ns(pwm, &duty_cycle_ns) == 0); 137 | passert(fabs(duty_cycle_ns - 250000) < 1e4); 138 | passert(pwm_get_duty_cycle(pwm, &duty_cycle) == 0); 139 | passert(fabs(duty_cycle - 0.25) < 1e-3); 140 | 141 | passert(pwm_set_duty_cycle_ns(pwm, 500000) == 0); 142 | passert(pwm_get_duty_cycle_ns(pwm, &duty_cycle_ns) == 0); 143 | passert(fabs(duty_cycle_ns - 500000) < 1e4); 144 | passert(pwm_get_duty_cycle(pwm, &duty_cycle) == 0); 145 | passert(fabs(duty_cycle - 0.50) < 1e-3); 146 | 147 | passert(pwm_set_duty_cycle_ns(pwm, 750000) == 0); 148 | passert(pwm_get_duty_cycle_ns(pwm, &duty_cycle_ns) == 0); 149 | passert(fabs(duty_cycle_ns - 750000) < 1e4); 150 | passert(pwm_get_duty_cycle(pwm, &duty_cycle) == 0); 151 | passert(fabs(duty_cycle - 0.75) < 1e-3); 152 | 153 | /* Set polarity, check polarity */ 154 | passert(pwm_set_polarity(pwm, PWM_POLARITY_NORMAL) == 0); 155 | passert(pwm_get_polarity(pwm, &polarity) == 0); 156 | passert(polarity == PWM_POLARITY_NORMAL); 157 | 158 | passert(pwm_set_polarity(pwm, PWM_POLARITY_INVERSED) == 0); 159 | passert(pwm_get_polarity(pwm, &polarity) == 0); 160 | passert(polarity == PWM_POLARITY_INVERSED); 161 | 162 | /* Set enabled, check enabled */ 163 | passert(pwm_set_enabled(pwm, true) == 0); 164 | passert(pwm_get_enabled(pwm, &enabled) == 0); 165 | passert(enabled == true); 166 | 167 | passert(pwm_set_enabled(pwm, false) == 0); 168 | passert(pwm_get_enabled(pwm, &enabled) == 0); 169 | passert(enabled == false); 170 | 171 | /* Use pwm_enable()/pwm_disable(), check enabled */ 172 | passert(pwm_enable(pwm) == 0); 173 | passert(pwm_get_enabled(pwm, &enabled) == 0); 174 | passert(enabled == true); 175 | 176 | passert(pwm_disable(pwm) == 0); 177 | passert(pwm_get_enabled(pwm, &enabled) == 0); 178 | passert(enabled == false); 179 | 180 | /* Set invalid polarity */ 181 | passert(pwm_set_polarity(pwm, 123) == PWM_ERROR_ARG); 182 | 183 | passert(pwm_close(pwm) == 0); 184 | 185 | /* Free PWM */ 186 | pwm_free(pwm); 187 | } 188 | 189 | void test_loopback(void) { 190 | ptest(); 191 | } 192 | 193 | bool getc_yes(void) { 194 | char buf[4]; 195 | fgets(buf, sizeof(buf), stdin); 196 | return (buf[0] == 'y' || buf[0] == 'Y'); 197 | } 198 | 199 | void test_interactive(void) { 200 | char str[256]; 201 | pwm_t *pwm; 202 | 203 | ptest(); 204 | 205 | /* Allocate PWM */ 206 | pwm = pwm_new(); 207 | passert(pwm != NULL); 208 | 209 | passert(pwm_open(pwm, chip, channel) == 0); 210 | 211 | printf("Starting interactive test. Get out your oscilloscope, buddy!\n"); 212 | printf("Press enter to continue...\n"); 213 | getc(stdin); 214 | 215 | /* Set initial parameters and enable PWM */ 216 | passert(pwm_set_duty_cycle(pwm, 0.0) == 0); 217 | passert(pwm_set_frequency(pwm, 1e3) == 0); 218 | passert(pwm_set_polarity(pwm, PWM_POLARITY_NORMAL) == 0); 219 | passert(pwm_enable(pwm) == 0); 220 | 221 | /* Check tostring */ 222 | passert(pwm_tostring(pwm, str, sizeof(str)) > 0); 223 | printf("PWM description: %s\n", str); 224 | printf("PWM description looks OK? y/n\n"); 225 | passert(getc_yes()); 226 | 227 | /* Set 1 kHz frequency, 0.25 duty cycle */ 228 | passert(pwm_set_frequency(pwm, 1e3) == 0); 229 | passert(pwm_set_duty_cycle(pwm, 0.25) == 0); 230 | printf("Frequency is 1 kHz, duty cycle is 25%%? y/n\n"); 231 | passert(getc_yes()); 232 | 233 | /* Set 1 kHz frequency, 0.50 duty cycle */ 234 | passert(pwm_set_frequency(pwm, 1e3) == 0); 235 | passert(pwm_set_duty_cycle(pwm, 0.50) == 0); 236 | printf("Frequency is 1 kHz, duty cycle is 50%%? y/n\n"); 237 | passert(getc_yes()); 238 | 239 | /* Set 2 kHz frequency, 0.25 duty cycle */ 240 | passert(pwm_set_frequency(pwm, 2e3) == 0); 241 | passert(pwm_set_duty_cycle(pwm, 0.25) == 0); 242 | printf("Frequency is 2 kHz, duty cycle is 25%%? y/n\n"); 243 | passert(getc_yes()); 244 | 245 | /* Set 2 kHz frequency, 0.50 duty cycle */ 246 | passert(pwm_set_frequency(pwm, 2e3) == 0); 247 | passert(pwm_set_duty_cycle(pwm, 0.50) == 0); 248 | printf("Frequency is 2 kHz, duty cycle is 50%%? y/n\n"); 249 | passert(getc_yes()); 250 | 251 | passert(pwm_set_duty_cycle(pwm, 0.0) == 0); 252 | passert(pwm_disable(pwm) == 0); 253 | 254 | passert(pwm_close(pwm) == 0); 255 | 256 | /* Free PWM */ 257 | pwm_free(pwm); 258 | } 259 | 260 | int main(int argc, char *argv[]) { 261 | if (argc < 2) { 262 | fprintf(stderr, "Usage: %s \n\n", argv[0]); 263 | fprintf(stderr, "[1/4] Arguments test: No requirements.\n"); 264 | fprintf(stderr, "[2/4] Open/close test: PWM channel should be real.\n"); 265 | fprintf(stderr, "[3/4] Loopback test: No test.\n"); 266 | fprintf(stderr, "[4/4] Interactive test: PWM channel should be observed with an oscilloscope or logic analyzer.\n\n"); 267 | fprintf(stderr, "Hint: for Raspberry Pi 3, enable PWM0 and PWM1 with:\n"); 268 | fprintf(stderr, " $ echo \"dtoverlay=pwm-2chan,pin=18,func=2,pin2=13,func2=4\" | sudo tee -a /boot/firmware/config.txt\n"); 269 | fprintf(stderr, " $ sudo reboot\n"); 270 | fprintf(stderr, "Monitor GPIO 18 (header pin 12), and run this test with:\n"); 271 | fprintf(stderr, " %s 0 0\n", argv[0]); 272 | fprintf(stderr, "or, monitor GPIO 13 (header pin 33), and run this test with:\n"); 273 | fprintf(stderr, " %s 0 1\n\n", argv[0]); 274 | exit(1); 275 | } 276 | 277 | chip = strtoul(argv[1], NULL, 10); 278 | channel = strtoul(argv[2], NULL, 10); 279 | 280 | test_arguments(); 281 | printf(" " STR_OK " Arguments test passed.\n\n"); 282 | test_open_config_close(); 283 | printf(" " STR_OK " Open/close test passed.\n\n"); 284 | test_loopback(); 285 | printf(" " STR_OK " Loopback test passed.\n\n"); 286 | test_interactive(); 287 | printf(" " STR_OK " Interactive test passed.\n\n"); 288 | 289 | printf("All tests passed!\n"); 290 | return 0; 291 | } 292 | 293 | -------------------------------------------------------------------------------- /tests/test_gpio_sysfs.c: -------------------------------------------------------------------------------- 1 | /* 2 | * c-periphery 3 | * https://github.com/vsergeev/c-periphery 4 | * License: MIT 5 | */ 6 | 7 | #include "test.h" 8 | 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "../src/gpio.h" 18 | 19 | unsigned int pin_input, pin_output; 20 | 21 | void test_arguments(void) { 22 | gpio_t *gpio; 23 | 24 | ptest(); 25 | 26 | /* Allocate GPIO */ 27 | gpio = gpio_new(); 28 | passert(gpio != NULL); 29 | 30 | /* Invalid direction */ 31 | passert(gpio_open_sysfs(gpio, pin_input, 5) == GPIO_ERROR_ARG); 32 | 33 | /* Free GPIO */ 34 | gpio_free(gpio); 35 | } 36 | 37 | void test_open_config_close(void) { 38 | gpio_t *gpio; 39 | bool value; 40 | gpio_direction_t direction; 41 | gpio_edge_t edge; 42 | gpio_bias_t bias; 43 | gpio_drive_t drive; 44 | bool inverted; 45 | 46 | ptest(); 47 | 48 | /* Allocate GPIO */ 49 | gpio = gpio_new(); 50 | passert(gpio != NULL); 51 | 52 | /* Open non-existent GPIO -- export should fail with EINVAL */ 53 | passert(gpio_open_sysfs(gpio, 9999, GPIO_DIR_IN) == GPIO_ERROR_OPEN); 54 | passert(gpio_errno(gpio) == EINVAL); 55 | 56 | /* Open legitimate GPIO */ 57 | passert(gpio_open_sysfs(gpio, pin_output, GPIO_DIR_IN) == 0); 58 | 59 | /* Check properties */ 60 | passert(gpio_line(gpio) == pin_output); 61 | passert(gpio_fd(gpio) >= 0); 62 | 63 | /* Invalid direction */ 64 | passert(gpio_set_direction(gpio, 5) == GPIO_ERROR_ARG); 65 | /* Invalid interrupt edge */ 66 | passert(gpio_set_edge(gpio, 5) == GPIO_ERROR_ARG); 67 | /* Unsupported setting bias */ 68 | passert(gpio_set_bias(gpio, GPIO_BIAS_PULL_UP) == GPIO_ERROR_UNSUPPORTED); 69 | passert(gpio_get_bias(gpio, &bias) == GPIO_ERROR_UNSUPPORTED); 70 | /* Unsupported setting drive */ 71 | passert(gpio_set_drive(gpio, GPIO_DRIVE_OPEN_DRAIN) == GPIO_ERROR_UNSUPPORTED); 72 | passert(gpio_get_drive(gpio, &drive) == GPIO_ERROR_UNSUPPORTED); 73 | /* Unsupported property */ 74 | passert(gpio_chip_fd(gpio) == GPIO_ERROR_UNSUPPORTED); 75 | /* Unsupported method */ 76 | passert(gpio_read_event(gpio, &edge, NULL) == GPIO_ERROR_UNSUPPORTED); 77 | 78 | /* Set direction out, check direction out, check value low */ 79 | passert(gpio_set_direction(gpio, GPIO_DIR_OUT) == 0); 80 | passert(gpio_get_direction(gpio, &direction) == 0); 81 | passert(direction == GPIO_DIR_OUT); 82 | passert(gpio_read(gpio, &value) == 0); 83 | passert(value == false); 84 | /* Set direction out low, check direction out, check value low */ 85 | passert(gpio_set_direction(gpio, GPIO_DIR_OUT_LOW) == 0); 86 | passert(gpio_get_direction(gpio, &direction) == 0); 87 | passert(direction == GPIO_DIR_OUT); 88 | passert(gpio_read(gpio, &value) == 0); 89 | passert(value == false); 90 | /* Set direction out high, check direction out, check value high */ 91 | passert(gpio_set_direction(gpio, GPIO_DIR_OUT_HIGH) == 0); 92 | passert(gpio_get_direction(gpio, &direction) == 0); 93 | passert(direction == GPIO_DIR_OUT); 94 | passert(gpio_read(gpio, &value) == 0); 95 | passert(value == true); 96 | 97 | /* Set inverted true, check inverted */ 98 | passert(gpio_set_inverted(gpio, true) == 0); 99 | passert(gpio_get_inverted(gpio, &inverted) == 0); 100 | passert(inverted == true); 101 | /* Set inverted false, check inverted */ 102 | passert(gpio_set_inverted(gpio, false) == 0); 103 | passert(gpio_get_inverted(gpio, &inverted) == 0); 104 | passert(inverted == false); 105 | 106 | /* Set direction in, check direction in */ 107 | passert(gpio_set_direction(gpio, GPIO_DIR_IN) == 0); 108 | passert(gpio_get_direction(gpio, &direction) == 0); 109 | passert(direction == GPIO_DIR_IN); 110 | passert(gpio_read(gpio, &value) == 0); 111 | 112 | /* Set edge none, check edge none */ 113 | passert(gpio_set_edge(gpio, GPIO_EDGE_NONE) == 0); 114 | passert(gpio_get_edge(gpio, &edge) == 0); 115 | passert(edge == GPIO_EDGE_NONE); 116 | /* Set edge rising, check edge rising */ 117 | passert(gpio_set_edge(gpio, GPIO_EDGE_RISING) == 0); 118 | passert(gpio_get_edge(gpio, &edge) == 0); 119 | passert(edge == GPIO_EDGE_RISING); 120 | /* Set edge falling, check edge falling */ 121 | passert(gpio_set_edge(gpio, GPIO_EDGE_FALLING) == 0); 122 | passert(gpio_get_edge(gpio, &edge) == 0); 123 | passert(edge == GPIO_EDGE_FALLING); 124 | /* Set edge both, check edge both */ 125 | passert(gpio_set_edge(gpio, GPIO_EDGE_BOTH) == 0); 126 | passert(gpio_get_edge(gpio, &edge) == 0); 127 | passert(edge == GPIO_EDGE_BOTH); 128 | /* Set edge none, check edge none */ 129 | passert(gpio_set_edge(gpio, GPIO_EDGE_NONE) == 0); 130 | passert(gpio_get_edge(gpio, &edge) == 0); 131 | passert(edge == GPIO_EDGE_NONE); 132 | 133 | /* Close GPIO */ 134 | passert(gpio_close(gpio) == 0); 135 | 136 | /* Free GPIO */ 137 | gpio_free(gpio); 138 | } 139 | 140 | /* Threaded poll helper functions */ 141 | 142 | typedef struct { 143 | sem_t *sem; 144 | gpio_t *gpio; 145 | int timeout_ms; 146 | } gpio_poll_args_t; 147 | 148 | void *gpio_poll_thread(void *arg) { 149 | gpio_poll_args_t *args = (gpio_poll_args_t *)arg; 150 | gpio_t *gpio = args->gpio; 151 | int timeout_ms = args->timeout_ms; 152 | 153 | assert(sem_post(args->sem) == 0); 154 | 155 | intptr_t ret = gpio_poll(gpio, timeout_ms); 156 | 157 | return (void *)ret; 158 | } 159 | 160 | void gpio_poll_start(pthread_t *thread, gpio_t *gpio, int timeout_ms) { 161 | sem_t sem; 162 | gpio_poll_args_t args = {.sem = &sem, .gpio = gpio, .timeout_ms = timeout_ms}; 163 | 164 | assert(sem_init(&sem, 0, 0) == 0); 165 | assert(pthread_create(thread, NULL, &gpio_poll_thread, &args) == 0); 166 | assert(sem_wait(&sem) == 0); 167 | } 168 | 169 | int gpio_poll_join(pthread_t thread) { 170 | void *ret; 171 | 172 | assert(pthread_join(thread, &ret) == 0); 173 | 174 | return (intptr_t)ret; 175 | } 176 | 177 | void test_loopback(void) { 178 | gpio_t *gpio_in, *gpio_out; 179 | pthread_t poll_thread; 180 | bool value; 181 | 182 | ptest(); 183 | 184 | /* Allocate GPIO */ 185 | gpio_in = gpio_new(); 186 | passert(gpio_in != NULL); 187 | gpio_out = gpio_new(); 188 | passert(gpio_out != NULL); 189 | 190 | /* Open in and out pins */ 191 | passert(gpio_open_sysfs(gpio_in, pin_input, GPIO_DIR_IN) == 0); 192 | passert(gpio_open_sysfs(gpio_out, pin_output, GPIO_DIR_OUT) == 0); 193 | 194 | /* Drive out low, check in low */ 195 | passert(gpio_write(gpio_out, false) == 0); 196 | passert(gpio_read(gpio_in, &value) == 0); 197 | passert(value == false); 198 | 199 | /* Drive out high, check in high */ 200 | passert(gpio_write(gpio_out, true) == 0); 201 | passert(gpio_read(gpio_in, &value) == 0); 202 | passert(value == true); 203 | 204 | /* Check poll falling 1 -> 0 interrupt */ 205 | passert(gpio_set_edge(gpio_in, GPIO_EDGE_FALLING) == 0); 206 | gpio_poll_start(&poll_thread, gpio_in, 1000); 207 | passert(gpio_write(gpio_out, false) == 0); 208 | passert(gpio_poll_join(poll_thread) == 1); 209 | passert(gpio_read(gpio_in, &value) == 0); 210 | passert(value == false); 211 | 212 | /* Check poll rising 0 -> 1 interrupt */ 213 | passert(gpio_set_edge(gpio_in, GPIO_EDGE_RISING) == 0); 214 | gpio_poll_start(&poll_thread, gpio_in, 1000); 215 | passert(gpio_write(gpio_out, true) == 0); 216 | passert(gpio_poll_join(poll_thread) == 1); 217 | passert(gpio_read(gpio_in, &value) == 0); 218 | passert(value == true); 219 | 220 | /* Check poll timeout */ 221 | passert(gpio_poll(gpio_in, 1000) == 0); 222 | 223 | /* Test gpio_poll_multiple() API with one GPIO */ 224 | gpio_t *gpios[1] = {gpio_in}; 225 | bool gpios_ready[1] = {false}; 226 | 227 | passert(gpio_set_edge(gpio_in, GPIO_EDGE_BOTH) == 0); 228 | 229 | /* Check poll falling 1 -> 0 interrupt */ 230 | passert(gpio_write(gpio_out, false) == 0); 231 | passert(gpio_poll_multiple(gpios, 1, 1000, gpios_ready) == 1); 232 | passert(gpios_ready[0] == true); 233 | passert(gpio_read(gpio_in, &value) == 0); 234 | passert(value == false); 235 | 236 | /* Check poll rising 0 -> 1 interrupt */ 237 | passert(gpio_write(gpio_out, true) == 0); 238 | passert(gpio_poll_multiple(gpios, 1, 1000, gpios_ready) == 1); 239 | passert(gpios_ready[0] == true); 240 | passert(gpio_read(gpio_in, &value) == 0); 241 | passert(value == true); 242 | 243 | /* Check poll timeout */ 244 | passert(gpio_poll_multiple(gpios, 1, 1000, gpios_ready) == 0); 245 | passert(gpios_ready[0] == false); 246 | 247 | passert(gpio_close(gpio_in) == 0); 248 | passert(gpio_close(gpio_out) == 0); 249 | 250 | /* Free GPIO */ 251 | gpio_free(gpio_in); 252 | gpio_free(gpio_out); 253 | } 254 | 255 | bool getc_yes(void) { 256 | char buf[4]; 257 | fgets(buf, sizeof(buf), stdin); 258 | return (buf[0] == 'y' || buf[0] == 'Y'); 259 | } 260 | 261 | void test_interactive(void) { 262 | char str[256]; 263 | gpio_t *gpio; 264 | 265 | ptest(); 266 | 267 | /* Allocate GPIO */ 268 | gpio = gpio_new(); 269 | passert(gpio != NULL); 270 | 271 | passert(gpio_open_sysfs(gpio, pin_output, GPIO_DIR_OUT) == 0); 272 | 273 | printf("Starting interactive test. Get out your multimeter, buddy!\n"); 274 | printf("Press enter to continue...\n"); 275 | getc(stdin); 276 | 277 | /* Check tostring */ 278 | passert(gpio_tostring(gpio, str, sizeof(str)) > 0); 279 | printf("GPIO description: %s\n", str); 280 | printf("GPIO description looks OK? y/n\n"); 281 | passert(getc_yes()); 282 | 283 | /* Drive GPIO out low */ 284 | passert(gpio_write(gpio, false) == 0); 285 | printf("GPIO out is low? y/n\n"); 286 | passert(getc_yes()); 287 | 288 | /* Drive GPIO out high */ 289 | passert(gpio_write(gpio, true) == 0); 290 | printf("GPIO out is high? y/n\n"); 291 | passert(getc_yes()); 292 | 293 | /* Drive GPIO out low */ 294 | passert(gpio_write(gpio, false) == 0); 295 | printf("GPIO out is low? y/n\n"); 296 | passert(getc_yes()); 297 | 298 | passert(gpio_close(gpio) == 0); 299 | 300 | /* Free GPIO */ 301 | gpio_free(gpio); 302 | } 303 | 304 | int main(int argc, char *argv[]) { 305 | if (argc < 3) { 306 | fprintf(stderr, "Usage: %s \n\n", argv[0]); 307 | fprintf(stderr, "[1/4] Argument test: No requirements.\n"); 308 | fprintf(stderr, "[2/4] Open/close test: GPIO #2 should be real.\n"); 309 | fprintf(stderr, "[3/4] Loopback test: GPIOs #1 and #2 should be connected with a wire.\n"); 310 | fprintf(stderr, "[4/4] Interactive test: GPIO #2 should be observed with a multimeter.\n\n"); 311 | fprintf(stderr, "Hint: for Raspberry Pi 3,\n"); 312 | fprintf(stderr, "Use GPIO 17 (header pin 11) and GPIO 27 (header pin 13),\n"); 313 | fprintf(stderr, "connect a loopback between them, and run this test with:\n"); 314 | fprintf(stderr, " %s 17 27\n\n", argv[0]); 315 | exit(1); 316 | } 317 | 318 | pin_input = strtoul(argv[1], NULL, 10); 319 | pin_output = strtoul(argv[2], NULL, 10); 320 | 321 | test_arguments(); 322 | printf(" " STR_OK " Arguments test passed.\n\n"); 323 | test_open_config_close(); 324 | printf(" " STR_OK " Open/close test passed.\n\n"); 325 | test_loopback(); 326 | printf(" " STR_OK " Loopback test passed.\n\n"); 327 | test_interactive(); 328 | printf(" " STR_OK " Interactive test passed.\n\n"); 329 | 330 | printf("All tests passed!\n"); 331 | return 0; 332 | } 333 | 334 | -------------------------------------------------------------------------------- /tests/test_serial.c: -------------------------------------------------------------------------------- 1 | /* 2 | * c-periphery 3 | * https://github.com/vsergeev/c-periphery 4 | * License: MIT 5 | */ 6 | 7 | #include "test.h" 8 | 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | 15 | #include "../src/serial.h" 16 | 17 | #define ABS(x) ((x < 0) ? -x : x) 18 | 19 | const char *device; 20 | 21 | void test_arguments(void) { 22 | serial_t *serial; 23 | 24 | ptest(); 25 | 26 | /* Allocate serial */ 27 | serial = serial_new(); 28 | passert(serial != NULL); 29 | 30 | /* Invalid data bits (4 and 9) */ 31 | passert(serial_open_advanced(serial, device, 115200, 4, PARITY_NONE, 1, false, false) == SERIAL_ERROR_ARG); 32 | passert(serial_open_advanced(serial, device, 115200, 9, PARITY_NONE, 1, false, false) == SERIAL_ERROR_ARG); 33 | /* Invalid parity */ 34 | passert(serial_open_advanced(serial, device, 115200, 8, PARITY_EVEN+1, 1, false, false) == SERIAL_ERROR_ARG); 35 | /* Invalid stopbits */ 36 | passert(serial_open_advanced(serial, device, 115200, 8, PARITY_NONE, 0, false, false) == SERIAL_ERROR_ARG); 37 | passert(serial_open_advanced(serial, device, 115200, 8, PARITY_NONE, 3, false, false) == SERIAL_ERROR_ARG); 38 | 39 | /* Everything else is fair game, although termios might not like it. */ 40 | 41 | /* Free serial */ 42 | serial_free(serial); 43 | } 44 | 45 | void test_open_config_close(void) { 46 | serial_t *serial; 47 | uint32_t baudrate; 48 | unsigned int databits; 49 | serial_parity_t parity; 50 | unsigned int stopbits; 51 | bool xonxoff; 52 | bool rtscts; 53 | unsigned int vmin; 54 | float vtime; 55 | 56 | ptest(); 57 | 58 | /* Allocate serial */ 59 | serial = serial_new(); 60 | passert(serial != NULL); 61 | 62 | passert(serial_open(serial, device, 115200) == 0); 63 | 64 | /* Check default settings */ 65 | passert(serial_get_baudrate(serial, &baudrate) == 0); 66 | passert(baudrate == 115200); 67 | passert(serial_get_databits(serial, &databits) == 0); 68 | passert(databits == 8); 69 | passert(serial_get_parity(serial, &parity) == 0); 70 | passert(parity == PARITY_NONE); 71 | passert(serial_get_stopbits(serial, &stopbits) == 0); 72 | passert(stopbits == 1); 73 | passert(serial_get_xonxoff(serial, &xonxoff) == 0); 74 | passert(xonxoff == false); 75 | passert(serial_get_rtscts(serial, &rtscts) == 0); 76 | passert(rtscts == false); 77 | passert(serial_get_vmin(serial, &vmin) == 0); 78 | passert(vmin == 0); 79 | passert(serial_get_vtime(serial, &vtime) == 0); 80 | passert(vtime == 0); 81 | 82 | /* Change some stuff around */ 83 | passert(serial_set_baudrate(serial, 4800) == 0); 84 | passert(serial_get_baudrate(serial, &baudrate) == 0); 85 | passert(baudrate == 4800); 86 | passert(serial_set_baudrate(serial, 9600) == 0); 87 | passert(serial_get_baudrate(serial, &baudrate) == 0); 88 | passert(baudrate == 9600); 89 | passert(serial_set_databits(serial, 7) == 0); 90 | passert(serial_get_databits(serial, &databits) == 0); 91 | passert(databits == 7); 92 | passert(serial_set_parity(serial, PARITY_ODD) == 0); 93 | passert(serial_get_parity(serial, &parity) == 0); 94 | passert(parity == PARITY_ODD); 95 | passert(serial_set_stopbits(serial, 2) == 0); 96 | passert(serial_get_stopbits(serial, &stopbits) == 0); 97 | passert(stopbits == 2); 98 | passert(serial_set_xonxoff(serial, true) == 0); 99 | passert(serial_get_xonxoff(serial, &xonxoff) == 0); 100 | passert(xonxoff == true); 101 | #if 0 /* Test serial port may not support rtscts */ 102 | passert(serial_set_rtscts(serial, true) == 0); 103 | passert(serial_get_rtscts(serial, &rtscts) == 0); 104 | passert(rtscts == true); 105 | #endif 106 | passert(serial_set_vmin(serial, 50) == 0); 107 | passert(serial_get_vmin(serial, &vmin) == 0); 108 | passert(vmin == 50); 109 | passert(serial_set_vtime(serial, 15.3) == 0); 110 | passert(serial_get_vtime(serial, &vtime) == 0); 111 | passert(ABS(vtime - 15.3) < 0.1); 112 | 113 | passert(serial_close(serial) == 0); 114 | 115 | /* Free serial */ 116 | serial_free(serial); 117 | } 118 | 119 | void test_loopback(void) { 120 | serial_t *serial; 121 | unsigned int count; 122 | time_t start, stop; 123 | uint8_t lorem_ipsum[] = "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."; 124 | uint8_t lorem_hugesum[4096*3]; 125 | uint8_t buf[sizeof(lorem_hugesum)]; 126 | 127 | ptest(); 128 | 129 | /* Allocate serial */ 130 | serial = serial_new(); 131 | passert(serial != NULL); 132 | 133 | passert(serial_open(serial, device, 115200) == 0); 134 | 135 | /* Test write/flush/read */ 136 | passert(serial_write(serial, lorem_ipsum, sizeof(lorem_ipsum)) == sizeof(lorem_ipsum)); 137 | passert(serial_flush(serial) == 0); 138 | passert(serial_read(serial, buf, sizeof(lorem_ipsum), -1) == sizeof(lorem_ipsum)); 139 | passert(memcmp(lorem_ipsum, buf, sizeof(lorem_ipsum)) == 0); 140 | 141 | /* Test poll/write/flush/poll/input waiting/read */ 142 | passert(serial_poll(serial, 500) == 0); /* Should timeout */ 143 | passert(serial_write(serial, lorem_ipsum, sizeof(lorem_ipsum)) == sizeof(lorem_ipsum)); 144 | passert(serial_flush(serial) == 0); 145 | passert(serial_poll(serial, 500) == 1); 146 | usleep(500000); 147 | passert(serial_input_waiting(serial, &count) == 0); 148 | passert(count == sizeof(lorem_ipsum)); 149 | passert(serial_read(serial, buf, sizeof(lorem_ipsum), -1) == sizeof(lorem_ipsum)); 150 | passert(memcmp(lorem_ipsum, buf, sizeof(lorem_ipsum)) == 0); 151 | 152 | /* Test non-blocking poll */ 153 | passert(serial_poll(serial, 0) == 0); 154 | 155 | /* Test a very large read-write (likely to exceed internal buffer size (~4096)) */ 156 | memset(lorem_hugesum, 0xAA, sizeof(lorem_hugesum)); 157 | passert(serial_write(serial, lorem_hugesum, sizeof(lorem_hugesum)) == sizeof(lorem_hugesum)); 158 | passert(serial_flush(serial) == 0); 159 | passert(serial_read(serial, buf, sizeof(lorem_hugesum), -1) == sizeof(lorem_hugesum)); 160 | passert(memcmp(lorem_hugesum, buf, sizeof(lorem_hugesum)) == 0); 161 | 162 | /* Test read timeout */ 163 | start = time(NULL); 164 | passert(serial_read(serial, buf, sizeof(buf), 2000) == 0); 165 | stop = time(NULL); 166 | passert((stop - start) > 1); 167 | 168 | /* Test non-blocking read */ 169 | start = time(NULL); 170 | passert(serial_read(serial, buf, sizeof(buf), 0) == 0); 171 | stop = time(NULL); 172 | /* Assuming we weren't context switched out for a second and weren't on a 173 | * thin time boundary ;) */ 174 | passert((stop - start) == 0); 175 | 176 | /* Test blocking read with vmin=5 termios timeout */ 177 | passert(serial_set_vmin(serial, 5) == 0); 178 | /* Write 5, read back 5 (== vmin) */ 179 | passert(serial_write(serial, lorem_ipsum, 5) == 5); 180 | passert(serial_flush(serial) == 0); 181 | passert(serial_read(serial, buf, sizeof(buf), -1) == 5); 182 | passert(memcmp(lorem_ipsum, buf, 5) == 0); 183 | 184 | /* Test blocking read with vmin=5, vtime=2 termios timeout */ 185 | passert(serial_set_vtime(serial, 2) == 0); 186 | /* Write 3, read back 3 (< vmin, but > vtime interbyte timeout) */ 187 | passert(serial_write(serial, lorem_ipsum, 3) == 3); 188 | passert(serial_flush(serial) == 0); 189 | start = time(NULL); 190 | passert(serial_read(serial, buf, sizeof(buf), -1) == 3); 191 | stop = time(NULL); 192 | passert(memcmp(lorem_ipsum, buf, 3) == 0); 193 | passert((stop - start) > 1); 194 | 195 | passert(serial_close(serial) == 0); 196 | 197 | /* Free serial */ 198 | serial_free(serial); 199 | } 200 | 201 | bool getc_yes(void) { 202 | char buf[4]; 203 | fgets(buf, sizeof(buf), stdin); 204 | return (buf[0] == 'y' || buf[0] == 'Y'); 205 | } 206 | 207 | void test_interactive(void) { 208 | char str[256]; 209 | serial_t *serial; 210 | uint8_t buf[] = "Hello World"; 211 | 212 | ptest(); 213 | 214 | /* Allocate serial */ 215 | serial = serial_new(); 216 | passert(serial != NULL); 217 | passert(serial_open(serial, device, 4800) == 0); 218 | 219 | printf("Starting interactive test. Get out your logic analyzer, buddy!\n"); 220 | printf("Press enter to continue...\n"); 221 | getc(stdin); 222 | 223 | /* Check tostring */ 224 | passert(serial_tostring(serial, str, sizeof(str)) > 0); 225 | printf("Serial description: %s\n", str); 226 | printf("Serial description looks OK? y/n\n"); 227 | passert(getc_yes()); 228 | 229 | printf("Press enter to start transfer..."); 230 | getc(stdin); 231 | passert(serial_write(serial, buf, sizeof(buf)) == sizeof(buf)); 232 | printf("Serial transfer baudrate 4800, 8n1 occurred? y/n\n"); 233 | passert(getc_yes()); 234 | 235 | passert(serial_set_baudrate(serial, 9600) == 0); 236 | 237 | printf("Press enter to start transfer..."); 238 | getc(stdin); 239 | passert(serial_write(serial, buf, sizeof(buf)) == sizeof(buf)); 240 | printf("Serial transfer baudrate 9600, 8n1 occurred? y/n\n"); 241 | passert(getc_yes()); 242 | 243 | passert(serial_set_baudrate(serial, 115200) == 0); 244 | 245 | printf("Press enter to start transfer..."); 246 | getc(stdin); 247 | passert(serial_write(serial, buf, sizeof(buf)) == sizeof(buf)); 248 | printf("Serial transfer baudrate 115200, 8n1 occurred? y/n\n"); 249 | passert(getc_yes()); 250 | 251 | passert(serial_close(serial) == 0); 252 | 253 | /* Free serial */ 254 | serial_free(serial); 255 | } 256 | 257 | int main(int argc, char *argv[]) { 258 | if (argc < 2) { 259 | fprintf(stderr, "Usage: %s \n\n", argv[0]); 260 | fprintf(stderr, "[1/4] Arguments test: No requirements.\n"); 261 | fprintf(stderr, "[2/4] Open/close test: Serial port device should be real.\n"); 262 | fprintf(stderr, "[3/4] Loopback test: Serial TX and RX should be connected with a wire.\n"); 263 | fprintf(stderr, "[4/4] Interactive test: Serial TX should be observed with an oscilloscope or logic analyzer.\n\n"); 264 | fprintf(stderr, "Hint: for Raspberry Pi 3, enable UART0 with:\n"); 265 | fprintf(stderr, " $ echo \"enable_uart=1\" | sudo tee -a /boot/firmware/config.txt\n"); 266 | fprintf(stderr, " $ echo \"dtoverlay=pi3-disable-bt\" | sudo tee -a /boot/firmware/config.txt\n"); 267 | fprintf(stderr, " $ sudo systemctl disable hciuart\n"); 268 | fprintf(stderr, " $ sudo reboot\n"); 269 | fprintf(stderr, " (Note that this will disable Bluetooth)\n"); 270 | fprintf(stderr, "Use pins UART0 TXD (header pin 8) and UART0 RXD (header pin 10),\n"); 271 | fprintf(stderr, "connect a loopback between TXD and RXD, and run this test with:\n"); 272 | fprintf(stderr, " %s /dev/ttyAMA0\n\n", argv[0]); 273 | exit(1); 274 | } 275 | 276 | device = argv[1]; 277 | 278 | test_arguments(); 279 | printf(" " STR_OK " Arguments test passed.\n\n"); 280 | test_open_config_close(); 281 | printf(" " STR_OK " Open/close test passed.\n\n"); 282 | test_loopback(); 283 | printf(" " STR_OK " Loopback test passed.\n\n"); 284 | test_interactive(); 285 | printf(" " STR_OK " Interactive test passed.\n\n"); 286 | 287 | printf("All tests passed!\n"); 288 | return 0; 289 | } 290 | 291 | -------------------------------------------------------------------------------- /docs/serial.md: -------------------------------------------------------------------------------- 1 | ### NAME 2 | 3 | Serial wrapper functions for Linux userspace termios `tty` devices. 4 | 5 | ### SYNOPSIS 6 | 7 | ``` c 8 | #include 9 | 10 | /* Primary Functions */ 11 | serial_t *serial_new(void); 12 | int serial_open(serial_t *serial, const char *path, uint32_t baudrate); 13 | int serial_open_advanced(serial_t *serial, const char *path, uint32_t baudrate, 14 | unsigned int databits, serial_parity_t parity, 15 | unsigned int stopbits, bool xonxoff, bool rtscts); 16 | int serial_read(serial_t *serial, uint8_t *buf, size_t len, int timeout_ms); 17 | int serial_write(serial_t *serial, const uint8_t *buf, size_t len); 18 | int serial_flush(serial_t *serial); 19 | int serial_input_waiting(serial_t *serial, unsigned int *count); 20 | int serial_output_waiting(serial_t *serial, unsigned int *count); 21 | int serial_poll(serial_t *serial, int timeout_ms); 22 | int serial_close(serial_t *serial); 23 | void serial_free(serial_t *serial); 24 | 25 | /* Getters */ 26 | int serial_get_baudrate(serial_t *serial, uint32_t *baudrate); 27 | int serial_get_databits(serial_t *serial, unsigned int *databits); 28 | int serial_get_parity(serial_t *serial, serial_parity_t *parity); 29 | int serial_get_stopbits(serial_t *serial, unsigned int *stopbits); 30 | int serial_get_xonxoff(serial_t *serial, bool *xonxoff); 31 | int serial_get_rtscts(serial_t *serial, bool *rtscts); 32 | 33 | /* Setters */ 34 | int serial_set_baudrate(serial_t *serial, uint32_t baudrate); 35 | int serial_set_databits(serial_t *serial, unsigned int databits); 36 | int serial_set_parity(serial_t *serial, enum serial_parity parity); 37 | int serial_set_stopbits(serial_t *serial, unsigned int stopbits); 38 | int serial_set_xonxoff(serial_t *serial, bool enabled); 39 | int serial_set_rtscts(serial_t *serial, bool enabled); 40 | 41 | /* Miscellaneous */ 42 | int serial_fd(serial_t *serial); 43 | int serial_tostring(serial_t *serial, char *str, size_t len); 44 | 45 | /* Error Handling */ 46 | int serial_errno(serial_t *serial); 47 | const char *serial_errmsg(serial_t *serial); 48 | ``` 49 | 50 | ### ENUMERATIONS 51 | 52 | * `serial_parity_t` 53 | * `PARITY_NONE`: No parity 54 | * `PARITY_ODD`: Odd parity 55 | * `PARITY_EVEN`: Even parity 56 | 57 | ### DESCRIPTION 58 | 59 | ``` c 60 | serial_t *serial_new(void); 61 | ``` 62 | Allocate a Serial handle. 63 | 64 | Returns a valid handle on success, or NULL on failure. 65 | 66 | ------ 67 | 68 | ``` c 69 | int serial_open(serial_t *serial, const char *path, uint32_t baudrate); 70 | ``` 71 | Open the `tty` device at the specified path (e.g. "/dev/ttyUSB0"), with the specified baudrate, and the defaults of 8 data bits, no parity, 1 stop bit, software flow control (xonxoff) off, hardware flow control (rtscts) off. 72 | 73 | `serial` should be a valid pointer to an allocated Serial handle structure. 74 | 75 | Returns 0 on success, or a negative [Serial error code](#return-value) on failure. 76 | 77 | ------ 78 | 79 | ``` c 80 | int serial_open_advanced(serial_t *serial, const char *path, uint32_t baudrate, 81 | unsigned int databits, serial_parity_t parity, 82 | unsigned int stopbits, bool xonxoff, bool rtscts); 83 | ``` 84 | Open the `tty` device at the specified path (e.g. "/dev/ttyUSB0"), with the specified baudrate, data bits, parity, stop bits, software flow control (xonxoff), and hardware flow control (rtscts) settings. 85 | 86 | `serial` should be a valid pointer to an allocated Serial handle structure. `databits` can be 5, 6, 7, or 8. `parity` can be `PARITY_NONE`, `PARITY_ODD`, or `PARITY_EVEN` as defined [above](#enumerations). `stopbits` can be 1 or 2. 87 | 88 | Returns 0 on success, or a negative [Serial error code](#return-value) on failure. 89 | 90 | ------ 91 | 92 | ``` c 93 | int serial_read(serial_t *serial, uint8_t *buf, size_t len, int timeout_ms); 94 | ``` 95 | Read up to `len` number of bytes from the serial port into the `buf` buffer with the specified millisecond timeout. `timeout_ms` can be positive for a blocking read with a timeout in milliseconds, zero for a non-blocking read, or negative for a blocking read that will block until `length` number of bytes are read. 96 | 97 | For a non-blocking or timeout-bound read, `serial_read()` may return less than the requested number of bytes. 98 | 99 | For a blocking read with the VMIN setting configured, `serial_read()` will block until at least VMIN bytes are read. For a blocking read with both VMIN and VTIME settings configured, `serial_read()` will block until at least VMIN bytes are read or the VTIME interbyte timeout expires after the last byte read. In either case, `serial_read()` may return less than the requested number of bytes. 100 | 101 | `serial` should be a valid pointer to a Serial handle opened with `serial_open()` or `serial_open_advanced()`. `timeout_ms` can be positive for a blocking read with a timeout in milliseconds, zero for a non-blocking read, or negative for a blocking read. 102 | 103 | Returns the number of bytes read on success, 0 on timeout, or a negative [Serial error code](#return-value) on failure. 104 | 105 | ------ 106 | 107 | ``` c 108 | int serial_write(serial_t *serial, const uint8_t *buf, size_t len); 109 | ``` 110 | Write `len` number of bytes from the `buf` buffer to the serial port. 111 | 112 | `serial` should be a valid pointer to a Serial handle opened with `serial_open()` or `serial_open_advanced()`. 113 | 114 | Returns the number of bytes written on success, or a negative [Serial error code](#return-value) on failure. 115 | 116 | ------ 117 | 118 | ``` c 119 | int serial_flush(serial_t *serial); 120 | ``` 121 | Flush the write buffer of the serial port (i.e. force its write immediately). 122 | 123 | `serial` should be a valid pointer to a Serial handle opened with `serial_open()` or `serial_open_advanced()`. 124 | 125 | Returns 0 on success, or a negative [Serial error code](#return-value) on failure. 126 | 127 | ------ 128 | 129 | ``` c 130 | int serial_input_waiting(serial_t *serial, unsigned int *count); 131 | ``` 132 | Get the number of bytes waiting to be read from the serial port. 133 | 134 | `serial` should be a valid pointer to a Serial handle opened with `serial_open()` or `serial_open_advanced()`. 135 | 136 | Returns 0 on success, or a negative [Serial error code](#return-value) on failure. 137 | 138 | ------ 139 | 140 | ``` c 141 | int serial_output_waiting(serial_t *serial, unsigned int *count); 142 | ``` 143 | Get the number of bytes waiting to be written to the serial port. 144 | 145 | `serial` should be a valid pointer to a Serial handle opened with `serial_open()` or `serial_open_advanced()`. 146 | 147 | Returns 0 on success, or a negative [Serial error code](#return-value) on failure. 148 | 149 | ------ 150 | 151 | ``` c 152 | bool serial_poll(serial_t *serial, int timeout_ms); 153 | ``` 154 | Poll for data available for reading from the serial port. 155 | 156 | `serial` should be a valid pointer to a Serial handle opened with `serial_open()` or `serial_open_advanced()`. `timeout_ms` can be positive for a timeout in milliseconds, zero for a non-blocking poll, or negative for a blocking poll. 157 | 158 | Returns 1 on success (data available for reading), 0 on timeout, or a negative [Serial error code](#return-value) on failure. 159 | 160 | ------ 161 | 162 | ``` c 163 | int serial_close(serial_t *serial); 164 | ``` 165 | Close the `tty` device. 166 | 167 | `serial` should be a valid pointer to a Serial handle opened with `serial_open()` or `serial_open_advanced()`. 168 | 169 | Returns 0 on success, or a negative [Serial error code](#return-value) on failure. 170 | 171 | ------ 172 | 173 | ``` c 174 | void serial_free(serial_t *serial); 175 | ``` 176 | Free a Serial handle. 177 | 178 | ------ 179 | 180 | ``` c 181 | int serial_get_baudrate(serial_t *serial, uint32_t *baudrate); 182 | int serial_get_databits(serial_t *serial, unsigned int *databits); 183 | int serial_get_parity(serial_t *serial, serial_parity_t *parity); 184 | int serial_get_stopbits(serial_t *serial, unsigned int *stopbits); 185 | int serial_get_xonxoff(serial_t *serial, bool *xonxoff); 186 | int serial_get_rtscts(serial_t *serial, bool *rtscts); 187 | ``` 188 | Get the baudrate, data bits, parity, stop bits, software flow control (xonxoff), or hardware flow control (rtscts), respectively, of the underlying `tty` device. 189 | 190 | `serial` should be a valid pointer to a Serial handle opened with `serial_open()` or `serial_open_advanced()`. 191 | 192 | Returns 0 on success, or a negative [Serial error code](#return-value) on failure. 193 | 194 | ------ 195 | 196 | ``` c 197 | int serial_set_baudrate(serial_t *serial, uint32_t baudrate); 198 | int serial_set_databits(serial_t *serial, unsigned int databits); 199 | int serial_set_parity(serial_t *serial, enum serial_parity parity); 200 | int serial_set_stopbits(serial_t *serial, unsigned int stopbits); 201 | int serial_set_xonxoff(serial_t *serial, bool enabled); 202 | int serial_set_rtscts(serial_t *serial, bool enabled); 203 | ``` 204 | Set the baudrate, data bits, parity, stop bits, software flow control (xonxoff), or hardware flow control (rtscts), respectively, on the underlying `tty` device. 205 | 206 | `serial` should be a valid pointer to a Serial handle opened with `serial_open()` or `serial_open_advanced()`. 207 | 208 | Returns 0 on success, or a negative [Serial error code](#return-value) on failure. 209 | 210 | ------ 211 | 212 | ``` c 213 | int serial_get_vmin(serial_t *serial, unsigned int *vmin); 214 | int serial_get_vtime(serial_t *serial, float *vtime); 215 | int serial_set_vmin(serial_t *serial, unsigned int vmin); 216 | int serial_set_vtime(serial_t *serial, float vtime); 217 | ``` 218 | Get or set the termios VMIN and VTIME settings, respectively, of the underlying `tty` device. 219 | 220 | VMIN specifies the minimum number of bytes returned from a blocking read. VTIME specifies the timeout in seconds of a blocking read. 221 | 222 | When both VMIN and VTIME settings are configured, VTIME acts as an interbyte timeout that restarts on every byte received, and a blocking read will block until either VMIN bytes are read or the VTIME timeout expires after the last byte read. See the `termios` man page for more information. 223 | 224 | `serial` should be a valid pointer to a Serial handle opened with `serial_open()` or `serial_open_advanced()`. `vmin` can be between 0 and 255. `vtime` can be between 0 and 25.5 seconds, with a resolution of 0.1 seconds. 225 | 226 | Returns 1 on success, or a negative [Serial error code](#return-value) on failure. 227 | 228 | ------ 229 | 230 | ``` c 231 | int serial_fd(serial_t *serial); 232 | ``` 233 | Return the file descriptor (for the underlying `tty` device) of the Serial handle. 234 | 235 | `serial` should be a valid pointer to a Serial handle opened with `serial_open()` or `serial_open_advanced()`. 236 | 237 | This function is a simple accessor to the Serial handle structure and always succeeds. 238 | 239 | ------ 240 | 241 | ``` c 242 | int serial_tostring(serial_t *serial, char *str, size_t len); 243 | ``` 244 | Return a string representation of the Serial handle. 245 | 246 | `serial` should be a valid pointer to a Serial handle opened with `serial_open()` or `serial_open_advanced()`. 247 | 248 | This function behaves and returns like `snprintf()`. 249 | 250 | ------ 251 | 252 | ``` c 253 | int serial_errno(serial_t *serial); 254 | ``` 255 | Return the libc errno of the last failure that occurred. 256 | 257 | `serial` should be a valid pointer to a Serial handle opened with `serial_open()` or `serial_open_advanced()`. 258 | 259 | ------ 260 | 261 | ``` c 262 | const char *serial_errmsg(serial_t *serial); 263 | ``` 264 | Return a human readable error message of the last failure that occurred. 265 | 266 | `serial` should be a valid pointer to a Serial handle opened with `serial_open()` or `serial_open_advanced()`. 267 | 268 | ### RETURN VALUE 269 | 270 | The periphery Serial functions return 0 on success or one of the negative error codes below on failure. 271 | 272 | The libc errno of the failure in an underlying libc library call can be obtained with the `serial_errno()` helper function. A human readable error message can be obtained with the `serial_errmsg()` helper function. 273 | 274 | | Error Code | Description | 275 | |---------------------------|---------------------------------------| 276 | | `SERIAL_ERROR_ARG` | Invalid arguments | 277 | | `SERIAL_ERROR_OPEN` | Opening serial port | 278 | | `SERIAL_ERROR_QUERY` | Querying serial port attributes | 279 | | `SERIAL_ERROR_CONFIGURE` | Configuring serial port attributes | 280 | | `SERIAL_ERROR_IO` | Reading/writing serial port | 281 | | `SERIAL_ERROR_CLOSE` | Closing serial port | 282 | 283 | ### EXAMPLE 284 | 285 | ``` c 286 | #include 287 | #include 288 | 289 | #include "serial.h" 290 | 291 | int main(void) { 292 | serial_t *serial; 293 | const char *s = "Hello World!"; 294 | char buf[128]; 295 | int ret; 296 | 297 | serial = serial_new(); 298 | 299 | /* Open /dev/ttyUSB0 with baudrate 115200, and defaults of 8N1, no flow control */ 300 | if (serial_open(serial, "/dev/ttyUSB0", 115200) < 0) { 301 | fprintf(stderr, "serial_open(): %s\n", serial_errmsg(serial)); 302 | exit(1); 303 | } 304 | 305 | /* Write to the serial port */ 306 | if (serial_write(serial, s, strlen(s)) < 0) { 307 | fprintf(stderr, "serial_write(): %s\n", serial_errmsg(serial)); 308 | exit(1); 309 | } 310 | 311 | /* Read up to buf size or 2000ms timeout */ 312 | if ((ret = serial_read(serial, buf, sizeof(buf), 2000)) < 0) { 313 | fprintf(stderr, "serial_read(): %s\n", serial_errmsg(serial)); 314 | exit(1); 315 | } 316 | 317 | printf("read %d bytes: _%s_\n", ret, buf); 318 | 319 | serial_close(serial); 320 | 321 | serial_free(serial); 322 | 323 | return 0; 324 | } 325 | ``` 326 | 327 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # c-periphery [![Build Status](https://github.com/vsergeev/c-periphery/actions/workflows/build.yml/badge.svg)](https://github.com/vsergeev/c-periphery/actions/workflows/build.yml) [![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) 2 | 3 | ## C Library for Linux Peripheral I/O (GPIO, LED, PWM, SPI, I2C, MMIO, Serial) 4 | 5 | c-periphery is a small C library for GPIO, LED, PWM, SPI, I2C, MMIO, and Serial peripheral I/O interface access in userspace Linux. c-periphery simplifies and consolidates 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. 6 | 7 | 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. 8 | 9 | Contributed libraries: [java-periphery](https://github.com/sgjava/java-periphery), [dart_periphery](https://github.com/pezi/dart_periphery) 10 | 11 | ## Examples 12 | 13 | ### GPIO 14 | 15 | ``` c 16 | #include 17 | #include 18 | #include 19 | 20 | #include "gpio.h" 21 | 22 | int main(void) { 23 | gpio_t *gpio_in, *gpio_out; 24 | bool value; 25 | 26 | gpio_in = gpio_new(); 27 | gpio_out = gpio_new(); 28 | 29 | /* Open GPIO /dev/gpiochip0 line 10 with input direction */ 30 | if (gpio_open(gpio_in, "/dev/gpiochip0", 10, GPIO_DIR_IN) < 0) { 31 | fprintf(stderr, "gpio_open(): %s\n", gpio_errmsg(gpio_in)); 32 | exit(1); 33 | } 34 | 35 | /* Open GPIO /dev/gpiochip0 line 12 with output direction */ 36 | if (gpio_open(gpio_out, "/dev/gpiochip0", 12, GPIO_DIR_OUT) < 0) { 37 | fprintf(stderr, "gpio_open(): %s\n", gpio_errmsg(gpio_out)); 38 | exit(1); 39 | } 40 | 41 | /* Read input GPIO into value */ 42 | if (gpio_read(gpio_in, &value) < 0) { 43 | fprintf(stderr, "gpio_read(): %s\n", gpio_errmsg(gpio_in)); 44 | exit(1); 45 | } 46 | 47 | /* Write output GPIO with !value */ 48 | if (gpio_write(gpio_out, !value) < 0) { 49 | fprintf(stderr, "gpio_write(): %s\n", gpio_errmsg(gpio_out)); 50 | exit(1); 51 | } 52 | 53 | gpio_close(gpio_in); 54 | gpio_close(gpio_out); 55 | 56 | gpio_free(gpio_in); 57 | gpio_free(gpio_out); 58 | 59 | return 0; 60 | } 61 | ``` 62 | 63 | [Go to GPIO documentation.](docs/gpio.md) 64 | 65 | ### LED 66 | 67 | ``` c 68 | #include 69 | #include 70 | #include 71 | 72 | #include "led.h" 73 | 74 | int main(void) { 75 | led_t *led; 76 | unsigned int max_brightness; 77 | 78 | led = led_new(); 79 | 80 | /* Open LED led0 */ 81 | if (led_open(led, "led0") < 0) { 82 | fprintf(stderr, "led_open(): %s\n", led_errmsg(led)); 83 | exit(1); 84 | } 85 | 86 | /* Turn on LED (set max brightness) */ 87 | if (led_write(led, true) < 0) { 88 | fprintf(stderr, "led_write(): %s\n", led_errmsg(led)); 89 | exit(1); 90 | } 91 | 92 | /* Get max brightness */ 93 | if (led_get_max_brightness(led, &max_brightness) < 0) { 94 | fprintf(stderr, "led_get_max_brightness(): %s\n", led_errmsg(led)); 95 | exit(1); 96 | } 97 | 98 | /* Set half brightness */ 99 | if (led_set_brightness(led, max_brightness / 2) < 0) { 100 | fprintf(stderr, "led_set_brightness(): %s\n", led_errmsg(led)); 101 | exit(1); 102 | } 103 | 104 | led_close(led); 105 | 106 | led_free(led); 107 | 108 | return 0; 109 | } 110 | ``` 111 | 112 | [Go to LED documentation.](docs/led.md) 113 | 114 | ### PWM 115 | 116 | ``` c 117 | #include 118 | #include 119 | 120 | #include "pwm.h" 121 | 122 | int main(void) { 123 | pwm_t *pwm; 124 | 125 | pwm = pwm_new(); 126 | 127 | /* Open PWM chip 0, channel 10 */ 128 | if (pwm_open(pwm, 0, 10) < 0) { 129 | fprintf(stderr, "pwm_open(): %s\n", pwm_errmsg(pwm)); 130 | exit(1); 131 | } 132 | 133 | /* Set frequency to 1 kHz */ 134 | if (pwm_set_frequency(pwm, 1e3) < 0) { 135 | fprintf(stderr, "pwm_set_frequency(): %s\n", pwm_errmsg(pwm)); 136 | exit(1); 137 | } 138 | 139 | /* Set duty cycle to 75% */ 140 | if (pwm_set_duty_cycle(pwm, 0.75) < 0) { 141 | fprintf(stderr, "pwm_set_duty_cycle(): %s\n", pwm_errmsg(pwm)); 142 | exit(1); 143 | } 144 | 145 | /* Enable PWM */ 146 | if (pwm_enable(pwm) < 0) { 147 | fprintf(stderr, "pwm_enable(): %s\n", pwm_errmsg(pwm)); 148 | exit(1); 149 | } 150 | 151 | /* Change duty cycle to 50% */ 152 | if (pwm_set_duty_cycle(pwm, 0.50) < 0) { 153 | fprintf(stderr, "pwm_set_duty_cycle(): %s\n", pwm_errmsg(pwm)); 154 | exit(1); 155 | } 156 | 157 | pwm_close(pwm); 158 | 159 | pwm_free(pwm); 160 | 161 | return 0; 162 | } 163 | ``` 164 | 165 | [Go to PWM documentation.](docs/pwm.md) 166 | 167 | ### SPI 168 | 169 | ``` c 170 | #include 171 | #include 172 | #include 173 | 174 | #include "spi.h" 175 | 176 | int main(void) { 177 | spi_t *spi; 178 | uint8_t buf[4] = { 0xaa, 0xbb, 0xcc, 0xdd }; 179 | 180 | spi = spi_new(); 181 | 182 | /* Open spidev1.0 with mode 0 and max speed 1MHz */ 183 | if (spi_open(spi, "/dev/spidev1.0", 0, 1000000) < 0) { 184 | fprintf(stderr, "spi_open(): %s\n", spi_errmsg(spi)); 185 | exit(1); 186 | } 187 | 188 | /* Shift out and in 4 bytes */ 189 | if (spi_transfer(spi, buf, buf, sizeof(buf)) < 0) { 190 | fprintf(stderr, "spi_transfer(): %s\n", spi_errmsg(spi)); 191 | exit(1); 192 | } 193 | 194 | printf("shifted in: 0x%02x 0x%02x 0x%02x 0x%02x\n", buf[0], buf[1], buf[2], buf[3]); 195 | 196 | spi_close(spi); 197 | 198 | spi_free(spi); 199 | 200 | return 0; 201 | } 202 | ``` 203 | 204 | [Go to SPI documentation.](docs/spi.md) 205 | 206 | ### I2C 207 | 208 | ``` c 209 | #include 210 | #include 211 | #include 212 | 213 | #include "i2c.h" 214 | 215 | #define EEPROM_I2C_ADDR 0x50 216 | 217 | int main(void) { 218 | i2c_t *i2c; 219 | 220 | i2c = i2c_new(); 221 | 222 | /* Open the i2c-0 bus */ 223 | if (i2c_open(i2c, "/dev/i2c-0") < 0) { 224 | fprintf(stderr, "i2c_open(): %s\n", i2c_errmsg(i2c)); 225 | exit(1); 226 | } 227 | 228 | /* Read byte at address 0x100 of EEPROM */ 229 | uint8_t msg_addr[2] = { 0x01, 0x00 }; 230 | uint8_t msg_data[1] = { 0xff, }; 231 | struct i2c_msg msgs[2] = 232 | { 233 | /* Write 16-bit address */ 234 | { .addr = EEPROM_I2C_ADDR, .flags = 0, .len = 2, .buf = msg_addr }, 235 | /* Read 8-bit data */ 236 | { .addr = EEPROM_I2C_ADDR, .flags = I2C_M_RD, .len = 1, .buf = msg_data}, 237 | }; 238 | 239 | /* Transfer a transaction with two I2C messages */ 240 | if (i2c_transfer(i2c, msgs, 2) < 0) { 241 | fprintf(stderr, "i2c_transfer(): %s\n", i2c_errmsg(i2c)); 242 | exit(1); 243 | } 244 | 245 | printf("0x%02x%02x: %02x\n", msg_addr[0], msg_addr[1], msg_data[0]); 246 | 247 | i2c_close(i2c); 248 | 249 | i2c_free(i2c); 250 | 251 | return 0; 252 | } 253 | ``` 254 | 255 | [Go to I2C documentation.](docs/i2c.md) 256 | 257 | ### MMIO 258 | 259 | ``` c 260 | #include 261 | #include 262 | #include 263 | #include 264 | 265 | #include "mmio.h" 266 | 267 | struct am335x_rtcss_registers { 268 | uint32_t seconds; /* 0x00 */ 269 | uint32_t minutes; /* 0x04 */ 270 | uint32_t hours; /* 0x08 */ 271 | /* ... */ 272 | }; 273 | 274 | int main(void) { 275 | mmio_t *mmio; 276 | uint32_t mac_id0_lo, mac_id0_hi; 277 | volatile struct am335x_rtcss_registers *regs; 278 | 279 | mmio = mmio_new(); 280 | 281 | /* Open Control Module */ 282 | if (mmio_open(mmio, 0x44E10000, 0x1000) < 0) { 283 | fprintf(stderr, "mmio_open(): %s\n", mmio_errmsg(mmio)); 284 | exit(1); 285 | } 286 | 287 | /* Read lower 2 bytes of MAC address */ 288 | if (mmio_read32(mmio, 0x630, &mac_id0_lo) < 0) { 289 | fprintf(stderr, "mmio_read32(): %s\n", mmio_errmsg(mmio)); 290 | exit(1); 291 | } 292 | 293 | /* Read upper 4 bytes of MAC address */ 294 | if (mmio_read32(mmio, 0x634, &mac_id0_hi) < 0) { 295 | fprintf(stderr, "mmio_read32(): %s\n", mmio_errmsg(mmio)); 296 | exit(1); 297 | } 298 | 299 | printf("MAC address: %08X%04X\n", __bswap_32(mac_id0_hi), __bswap_16(mac_id0_lo)); 300 | 301 | mmio_close(mmio); 302 | 303 | /* Open RTC subsystem */ 304 | if (mmio_open(mmio, 0x44E3E000, 0x1000) < 0) { 305 | fprintf(stderr, "mmio_open(): %s\n", mmio_errmsg(mmio)); 306 | exit(1); 307 | } 308 | 309 | regs = mmio_ptr(mmio); 310 | 311 | /* Read current RTC time */ 312 | printf("hours: %02x minutes: %02x seconds %02x\n", regs->hours, regs->minutes, regs->seconds); 313 | 314 | mmio_close(mmio); 315 | 316 | mmio_free(mmio); 317 | 318 | return 0; 319 | } 320 | ``` 321 | 322 | [Go to MMIO documentation.](docs/mmio.md) 323 | 324 | ### Serial 325 | 326 | ``` c 327 | #include 328 | #include 329 | #include 330 | 331 | #include "serial.h" 332 | 333 | int main(void) { 334 | serial_t *serial; 335 | uint8_t s[] = "Hello World!"; 336 | uint8_t buf[128]; 337 | int ret; 338 | 339 | serial = serial_new(); 340 | 341 | /* Open /dev/ttyUSB0 with baudrate 115200, and defaults of 8N1, no flow control */ 342 | if (serial_open(serial, "/dev/ttyUSB0", 115200) < 0) { 343 | fprintf(stderr, "serial_open(): %s\n", serial_errmsg(serial)); 344 | exit(1); 345 | } 346 | 347 | /* Write to the serial port */ 348 | if (serial_write(serial, s, sizeof(s)) < 0) { 349 | fprintf(stderr, "serial_write(): %s\n", serial_errmsg(serial)); 350 | exit(1); 351 | } 352 | 353 | /* Read up to buf size or 2000ms timeout */ 354 | if ((ret = serial_read(serial, buf, sizeof(buf), 2000)) < 0) { 355 | fprintf(stderr, "serial_read(): %s\n", serial_errmsg(serial)); 356 | exit(1); 357 | } 358 | 359 | printf("read %d bytes: _%s_\n", ret, buf); 360 | 361 | serial_close(serial); 362 | 363 | serial_free(serial); 364 | 365 | return 0; 366 | } 367 | ``` 368 | 369 | [Go to Serial documentation.](docs/serial.md) 370 | 371 | ## Building c-periphery with CMake 372 | 373 | ### Static library 374 | 375 | Build c-periphery into a static library: 376 | 377 | ``` console 378 | $ mkdir build 379 | $ cd build 380 | $ cmake .. 381 | $ make 382 | ``` 383 | 384 | ### Shared Library 385 | 386 | Build c-periphery into a shared library: 387 | 388 | ``` console 389 | $ mkdir build 390 | $ cd build 391 | $ cmake -DBUILD_SHARED_LIBS=ON .. 392 | $ make 393 | ``` 394 | 395 | Install the shared library and headers: 396 | 397 | ``` console 398 | $ sudo make install 399 | ``` 400 | 401 | ### Tests 402 | 403 | Build c-periphery tests from the build directory: 404 | 405 | ``` console 406 | $ make tests 407 | ``` 408 | 409 | ### Cross-compilation 410 | 411 | Set the `CC` environment variable with the cross-compiler prior to build: 412 | 413 | ``` console 414 | $ export CC=arm-linux-gnueabihf-gcc 415 | $ mkdir build 416 | $ cd build 417 | $ cmake .. 418 | $ make 419 | ``` 420 | 421 | If additional cross-compiler tools are needed, use a `CMAKE_TOOLCHAIN_FILE` to fully specify the toolchain parameters: 422 | 423 | ``` console 424 | $ mkdir build 425 | $ cd build 426 | $ cmake -DCMAKE_TOOLCHAIN_FILE=/path/to/arm-linux-gnueabihf.cmake .. 427 | $ make 428 | ``` 429 | 430 | ## Building c-periphery with vanilla Make 431 | 432 | ### Static library 433 | 434 | Build c-periphery into a static library: 435 | 436 | ``` console 437 | $ make 438 | ``` 439 | 440 | ### Tests 441 | 442 | Build c-periphery tests: 443 | 444 | ``` console 445 | $ make tests 446 | ``` 447 | 448 | ### Cross-compilation 449 | 450 | Set the `CROSS_COMPILE` environment variable with the cross-compiler prefix when building: 451 | 452 | ``` console 453 | $ CROSS_COMPILE=arm-linux-gnueabihf- make 454 | ``` 455 | 456 | ## Building c-periphery into another project statically 457 | 458 | Include the header files from `src/` and link in the `periphery.a` static library: 459 | 460 | ``` console 461 | $ gcc -I/path/to/periphery/src myprog.c /path/to/periphery/periphery.a -o myprog 462 | ``` 463 | 464 | ## Building c-periphery into another project dynamically 465 | 466 | If the header files and shared library are installed on the system, simply link with `-lperiphery`: 467 | 468 | ``` console 469 | $ gcc myprog.c -lperiphery -o myprog 470 | ``` 471 | 472 | Otherwise, additional include (`-I`) and library (`-L`) paths may be required. 473 | 474 | ## Building c-periphery into another project with CMake 475 | 476 | Add to project's `CMakeLists.txt`: 477 | 478 | ```cmake 479 | find_package(periphery REQUIRED) 480 | # If package is installed locally, specify search path explicitly: 481 | # find_package(periphery REQUIRED PATHS ) 482 | 483 | ... 484 | 485 | add_executable(YOUR_TARGET src/myprog.c) 486 | target_link_libraries(YOUR_TARGET PRIVATE periphery::periphery) 487 | ``` 488 | 489 | ## Documentation 490 | 491 | `man` page style documentation for each interface wrapper is available in [docs](docs/) folder. 492 | 493 | ## Testing 494 | 495 | The tests located in the [tests](tests/) folder may be run to test the correctness and functionality of c-periphery. Some tests require interactive probing (e.g. with an oscilloscope), the installation of a physical loopback, or the existence of a particular device on a bus. See the usage of each test for more details on the required test setup. 496 | 497 | ## License 498 | 499 | c-periphery is MIT licensed. See the included [LICENSE](LICENSE) file. 500 | 501 | -------------------------------------------------------------------------------- /src/pwm.c: -------------------------------------------------------------------------------- 1 | /* 2 | * c-periphery 3 | * https://github.com/vsergeev/c-periphery 4 | * License: MIT 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #include "pwm.h" 21 | 22 | #define P_PATH_MAX 256 23 | /* Delay between checks for successful PWM export (100ms) */ 24 | #define PWM_EXPORT_STAT_DELAY 100000 25 | /* Number of retries to check for successful PWM exports */ 26 | #define PWM_EXPORT_STAT_RETRIES 10 27 | 28 | struct pwm_handle { 29 | unsigned int chip; 30 | unsigned int channel; 31 | uint64_t period_ns; 32 | 33 | struct { 34 | int c_errno; 35 | char errmsg[96]; 36 | } error; 37 | }; 38 | 39 | static int _pwm_error(pwm_t *pwm, int code, int c_errno, const char *fmt, ...) { 40 | va_list ap; 41 | 42 | pwm->error.c_errno = c_errno; 43 | 44 | va_start(ap, fmt); 45 | vsnprintf(pwm->error.errmsg, sizeof(pwm->error.errmsg), fmt, ap); 46 | va_end(ap); 47 | 48 | /* Tack on strerror() and errno */ 49 | if (c_errno) { 50 | char buf[64] = {0}; 51 | strerror_r(c_errno, buf, sizeof(buf)); 52 | snprintf(pwm->error.errmsg+strlen(pwm->error.errmsg), sizeof(pwm->error.errmsg)-strlen(pwm->error.errmsg), ": %s [errno %d]", buf, c_errno); 53 | } 54 | 55 | return code; 56 | } 57 | 58 | pwm_t *pwm_new(void) { 59 | pwm_t *pwm = calloc(1, sizeof(pwm_t)); 60 | if (pwm == NULL) 61 | return NULL; 62 | 63 | pwm->chip = -1; 64 | pwm->channel = -1; 65 | 66 | return pwm; 67 | } 68 | 69 | int pwm_open(pwm_t *pwm, unsigned int chip, unsigned int channel) { 70 | char channel_path[P_PATH_MAX]; 71 | struct stat stat_buf; 72 | int ret; 73 | 74 | snprintf(channel_path, sizeof(channel_path), "/sys/class/pwm/pwmchip%u/pwm%u", chip, channel); 75 | 76 | /* Check if PWM channel exists */ 77 | if (stat(channel_path, &stat_buf) < 0) { 78 | char path[P_PATH_MAX]; 79 | char buf[16]; 80 | int fd, len; 81 | 82 | /* Export the PWM channel */ 83 | snprintf(path, sizeof(path), "/sys/class/pwm/pwmchip%u/export", chip); 84 | 85 | len = snprintf(buf, sizeof(buf), "%u\n", channel); 86 | 87 | if ((fd = open(path, O_WRONLY)) < 0) 88 | return _pwm_error(pwm, PWM_ERROR_OPEN, errno, "Opening PWM: opening 'export'"); 89 | 90 | if (write(fd, buf, len) < 0) { 91 | int errsv = errno; 92 | close(fd); 93 | return _pwm_error(pwm, PWM_ERROR_OPEN, errsv, "Opening PWM: writing 'export'"); 94 | } 95 | 96 | if (close(fd) < 0) 97 | return _pwm_error(pwm, PWM_ERROR_OPEN, errno, "Opening PWM: closing 'export'"); 98 | 99 | /* Wait until PWM channel appears */ 100 | unsigned int retry_count; 101 | for (retry_count = 0; retry_count < PWM_EXPORT_STAT_RETRIES; retry_count++) { 102 | if ((ret = stat(path, &stat_buf)) < 0 && errno != ENOENT) 103 | return _pwm_error(pwm, PWM_ERROR_OPEN, errno, "Opening PWM: stat 'pwm%u/' after export", channel); 104 | else if (ret == 0) 105 | break; 106 | 107 | usleep(PWM_EXPORT_STAT_DELAY); 108 | } 109 | 110 | if (retry_count == PWM_EXPORT_STAT_RETRIES) 111 | return _pwm_error(pwm, PWM_ERROR_OPEN, 0, "Opening PWM: waiting for 'pwm%u/' timed out", channel); 112 | 113 | snprintf(path, sizeof(path), "/sys/class/pwm/pwmchip%u/pwm%u/period", chip, channel); 114 | 115 | /* Loop until period is writable. This could take some time after 116 | * export as application of udev rules after export is asynchronous. */ 117 | for (retry_count = 0; retry_count < PWM_EXPORT_STAT_RETRIES; retry_count++) { 118 | if ((fd = open(path, O_WRONLY)) < 0) { 119 | if (errno != EACCES || (errno == EACCES && retry_count == PWM_EXPORT_STAT_RETRIES - 1)) 120 | return _pwm_error(pwm, PWM_ERROR_OPEN, errno, "Opening PWM: opening 'pwm%u/period' after export", channel); 121 | } else { 122 | close(fd); 123 | break; 124 | } 125 | 126 | usleep(PWM_EXPORT_STAT_DELAY); 127 | } 128 | } 129 | 130 | memset(pwm, 0, sizeof(pwm_t)); 131 | pwm->chip = chip; 132 | pwm->channel = channel; 133 | 134 | ret = pwm_get_period_ns(pwm, &pwm->period_ns); 135 | if (ret < 0) 136 | return ret; 137 | 138 | return 0; 139 | } 140 | 141 | int pwm_enable(pwm_t *pwm) { 142 | return pwm_set_enabled(pwm, true); 143 | } 144 | 145 | int pwm_disable(pwm_t *pwm) { 146 | return pwm_set_enabled(pwm, false); 147 | } 148 | 149 | int pwm_close(pwm_t *pwm) { 150 | char path[P_PATH_MAX]; 151 | char buf[16]; 152 | int len; 153 | int fd; 154 | 155 | if (pwm->channel == ((unsigned int) -1)) 156 | return 0; 157 | 158 | /* Unexport the PWM */ 159 | snprintf(path, sizeof(path), "/sys/class/pwm/pwmchip%u/unexport", pwm->chip); 160 | 161 | len = snprintf(buf, sizeof(buf), "%u\n", pwm->channel); 162 | 163 | if ((fd = open(path, O_WRONLY)) < 0) 164 | return _pwm_error(pwm, PWM_ERROR_CLOSE, errno, "Closing PWM: opening 'unexport'"); 165 | 166 | if (write(fd, buf, len) < 0) { 167 | int errsv = errno; 168 | close(fd); 169 | return _pwm_error(pwm, PWM_ERROR_CLOSE, errsv, "Closing PWM: writing 'unexport'"); 170 | } 171 | 172 | if (close(fd) < 0) 173 | return _pwm_error(pwm, PWM_ERROR_CLOSE, errno, "Closing PWM: closing 'unexport'"); 174 | 175 | pwm->chip = -1; 176 | pwm->channel = -1; 177 | 178 | return 0; 179 | } 180 | 181 | void pwm_free(pwm_t *pwm) { 182 | free(pwm); 183 | } 184 | 185 | static int pwm_read_attribute(pwm_t *pwm, const char *name, char *buf, size_t len) { 186 | char path[P_PATH_MAX]; 187 | int fd, ret; 188 | 189 | if (!len) 190 | return 0; 191 | 192 | snprintf(path, sizeof(path), "/sys/class/pwm/pwmchip%u/pwm%u/%s", pwm->chip, pwm->channel, name); 193 | 194 | if ((fd = open(path, O_RDONLY)) < 0) 195 | return _pwm_error(pwm, PWM_ERROR_QUERY, errno, "Opening PWM '%s'", name); 196 | 197 | if ((ret = read(fd, buf, len - 1)) < 0) { 198 | int errsv = errno; 199 | close(fd); 200 | return _pwm_error(pwm, PWM_ERROR_QUERY, errsv, "Reading PWM '%s'", name); 201 | } 202 | 203 | buf[ret] = '\0'; 204 | 205 | if (close(fd) < 0) 206 | return _pwm_error(pwm, PWM_ERROR_QUERY, errno, "Closing PWM '%s'", name); 207 | 208 | return 0; 209 | } 210 | 211 | static int pwm_write_attribute(pwm_t *pwm, const char *name, const char *buf, size_t len) { 212 | char path[P_PATH_MAX]; 213 | int fd; 214 | 215 | snprintf(path, sizeof(path), "/sys/class/pwm/pwmchip%u/pwm%u/%s", pwm->chip, pwm->channel, name); 216 | 217 | if ((fd = open(path, O_WRONLY)) < 0) 218 | return _pwm_error(pwm, PWM_ERROR_CONFIGURE, errno, "Opening PWM '%s'", name); 219 | 220 | if (write(fd, buf, len) < 0) { 221 | int errsv = errno; 222 | close(fd); 223 | return _pwm_error(pwm, PWM_ERROR_CONFIGURE, errsv, "Writing PWM '%s'", name); 224 | } 225 | 226 | if (close(fd) < 0) 227 | return _pwm_error(pwm, PWM_ERROR_CONFIGURE, errno, "Closing PWM '%s'", name); 228 | 229 | return 0; 230 | } 231 | 232 | int pwm_get_enabled(pwm_t *pwm, bool *enabled) { 233 | char buf[2]; 234 | int ret; 235 | 236 | if ((ret = pwm_read_attribute(pwm, "enable", buf, sizeof(buf))) < 0) 237 | return ret; 238 | 239 | if (buf[0] == '0') 240 | *enabled = false; 241 | else if (buf[0] == '1') 242 | *enabled = true; 243 | else 244 | return _pwm_error(pwm, PWM_ERROR_QUERY, errno, "Unknown PWM 'enabled' value"); 245 | 246 | return 0; 247 | } 248 | 249 | int pwm_get_period_ns(pwm_t *pwm, uint64_t *period_ns) { 250 | char buf[32]; 251 | int ret; 252 | uint64_t value; 253 | 254 | if ((ret = pwm_read_attribute(pwm, "period", buf, sizeof(buf))) < 0) 255 | return ret; 256 | 257 | errno = 0; 258 | value = strtoul(buf, NULL, 10); 259 | if (errno != 0) 260 | return _pwm_error(pwm, PWM_ERROR_QUERY, errno, "Unknown PWM 'period' value"); 261 | 262 | /* Cache the period for fast duty cycle updates */ 263 | pwm->period_ns = value; 264 | 265 | *period_ns = value; 266 | 267 | return 0; 268 | } 269 | 270 | int pwm_get_duty_cycle_ns(pwm_t *pwm, uint64_t *duty_cycle_ns) { 271 | char buf[32]; 272 | int ret; 273 | uint64_t value; 274 | 275 | if ((ret = pwm_read_attribute(pwm, "duty_cycle", buf, sizeof(buf))) < 0) 276 | return ret; 277 | 278 | errno = 0; 279 | value = strtoul(buf, NULL, 10); 280 | if (errno != 0) 281 | return _pwm_error(pwm, PWM_ERROR_QUERY, errno, "Unknown PWM 'duty_cycle' value"); 282 | 283 | *duty_cycle_ns = value; 284 | 285 | return 0; 286 | } 287 | 288 | int pwm_get_period(pwm_t *pwm, double *period) { 289 | int ret; 290 | uint64_t period_ns; 291 | 292 | if ((ret = pwm_get_period_ns(pwm, &period_ns)) < 0) 293 | return ret; 294 | 295 | *period = ((double) period_ns) / 1e9; 296 | 297 | return 0; 298 | } 299 | 300 | int pwm_get_duty_cycle(pwm_t *pwm, double *duty_cycle) { 301 | int ret; 302 | uint64_t duty_cycle_ns; 303 | 304 | if ((ret = pwm_get_duty_cycle_ns(pwm, &duty_cycle_ns)) < 0) 305 | return ret; 306 | 307 | *duty_cycle = ((double) duty_cycle_ns) / ((double) pwm->period_ns); 308 | 309 | return 0; 310 | } 311 | 312 | int pwm_get_frequency(pwm_t *pwm, double *frequency) { 313 | int ret; 314 | uint64_t period_ns; 315 | 316 | if ((ret = pwm_get_period_ns(pwm, &period_ns)) < 0) 317 | return ret; 318 | 319 | *frequency = 1e9 / ((double) period_ns); 320 | 321 | return 0; 322 | } 323 | 324 | int pwm_get_polarity(pwm_t *pwm, pwm_polarity_t *polarity) { 325 | int ret; 326 | char buf[16]; 327 | 328 | if ((ret = pwm_read_attribute(pwm, "polarity", buf, sizeof(buf))) < 0) 329 | return ret; 330 | 331 | if (strcmp(buf, "normal\n") == 0) 332 | *polarity = PWM_POLARITY_NORMAL; 333 | else if (strcmp(buf, "inversed\n") == 0) 334 | *polarity = PWM_POLARITY_INVERSED; 335 | else 336 | return _pwm_error(pwm, PWM_ERROR_QUERY, errno, "Unknown PWM 'polarity' value"); 337 | 338 | return 0; 339 | } 340 | 341 | int pwm_set_enabled(pwm_t *pwm, bool enabled) { 342 | return pwm_write_attribute(pwm, "enable", enabled ? "1\n" : "0\n", 2); 343 | } 344 | 345 | int pwm_set_period_ns(pwm_t *pwm, uint64_t period_ns) { 346 | char buf[32]; 347 | int len; 348 | int ret; 349 | 350 | len = snprintf(buf, sizeof(buf), "%" PRId64 "\n", period_ns); 351 | 352 | if ((ret = pwm_write_attribute(pwm, "period", buf, len)) < 0) 353 | return ret; 354 | 355 | /* Cache the period for fast duty cycle updates */ 356 | pwm->period_ns = period_ns; 357 | 358 | return 0; 359 | } 360 | 361 | int pwm_set_duty_cycle_ns(pwm_t *pwm, uint64_t duty_cycle_ns) { 362 | char buf[32]; 363 | int len; 364 | 365 | len = snprintf(buf, sizeof(buf), "%" PRId64 "\n", duty_cycle_ns); 366 | 367 | return pwm_write_attribute(pwm, "duty_cycle", buf, len); 368 | } 369 | 370 | int pwm_set_period(pwm_t *pwm, double period) { 371 | uint64_t period_ns = (uint64_t)(period * 1e9); 372 | 373 | return pwm_set_period_ns(pwm, period_ns); 374 | } 375 | 376 | int pwm_set_duty_cycle(pwm_t *pwm, double duty_cycle) { 377 | uint64_t duty_cycle_ns; 378 | 379 | if (duty_cycle < 0 || duty_cycle > 1) 380 | return _pwm_error(pwm, PWM_ERROR_ARG, 0, "PWM duty cycle out of bounds (should be between 0.0 and 1.0)"); 381 | 382 | duty_cycle_ns = (uint64_t)(((double) pwm->period_ns) * duty_cycle); 383 | 384 | return pwm_set_duty_cycle_ns(pwm, duty_cycle_ns); 385 | } 386 | 387 | int pwm_set_frequency(pwm_t *pwm, double frequency) { 388 | uint64_t period_ns = (uint64_t)(1e9 / frequency); 389 | 390 | return pwm_set_period_ns(pwm, period_ns); 391 | } 392 | 393 | int pwm_set_polarity(pwm_t *pwm, pwm_polarity_t polarity) { 394 | const char *buf; 395 | 396 | if (polarity == PWM_POLARITY_NORMAL) 397 | buf = "normal\n"; 398 | else if (polarity == PWM_POLARITY_INVERSED) 399 | buf = "inversed\n"; 400 | else 401 | return _pwm_error(pwm, PWM_ERROR_ARG, 0, "Invalid PWM polarity (can be normal, inversed)"); 402 | 403 | return pwm_write_attribute(pwm, "polarity", buf, strlen(buf)); 404 | } 405 | 406 | unsigned int pwm_chip(pwm_t *pwm) { 407 | return pwm->chip; 408 | } 409 | 410 | unsigned int pwm_channel(pwm_t *pwm) { 411 | return pwm->channel; 412 | } 413 | 414 | int pwm_tostring(pwm_t *pwm, char *str, size_t len) { 415 | double period; 416 | char period_str[16]; 417 | double duty_cycle; 418 | char duty_cycle_str[16]; 419 | pwm_polarity_t polarity; 420 | const char *polarity_str; 421 | bool enabled; 422 | const char *enabled_str; 423 | 424 | if (pwm_get_period(pwm, &period) < 0) 425 | strcpy(period_str, ""); 426 | else 427 | snprintf(period_str, sizeof(period_str), "%f", period); 428 | 429 | if (pwm_get_duty_cycle(pwm, &duty_cycle) < 0) 430 | strcpy(duty_cycle_str, ""); 431 | else 432 | snprintf(duty_cycle_str, sizeof(duty_cycle_str), "%f", duty_cycle); 433 | 434 | if (pwm_get_polarity(pwm, &polarity) < 0) 435 | polarity_str = ""; 436 | else 437 | polarity_str = (polarity == PWM_POLARITY_NORMAL) ? "normal" : 438 | (polarity == PWM_POLARITY_INVERSED) ? "inversed" : "unknown"; 439 | 440 | if (pwm_get_enabled(pwm, &enabled) < 0) 441 | enabled_str = ""; 442 | else 443 | enabled_str = enabled ? "true" : "false"; 444 | 445 | return snprintf(str, len, "PWM %u, chip %u (period=%s sec, duty_cycle=%s%%, polarity=%s, enabled=%s)", pwm->channel, pwm->chip, period_str, duty_cycle_str, polarity_str, enabled_str); 446 | } 447 | 448 | int pwm_errno(pwm_t *pwm) { 449 | return pwm->error.c_errno; 450 | } 451 | 452 | const char *pwm_errmsg(pwm_t *pwm) { 453 | return pwm->error.errmsg; 454 | } 455 | --------------------------------------------------------------------------------