From 1e6300ae4614b2d8d74687240b2985f3128bc41b Mon Sep 17 00:00:00 2001 From: guowenxue <guowenxue@gmail.com> Date: Mon, 19 Aug 2024 16:52:02 +0800 Subject: [PATCH] Add led/pwm API source code --- hal/api/leds.c | 332 +++++++++++++++++++++++ hal/api/libgpiod/build.sh | 118 ++++++++ hal/api/libgpiod/makefile | 11 hal/api/pwm.c | 309 ++++++++++++++++++++++ hal/api/makefile | 54 +++ 5 files changed, 824 insertions(+), 0 deletions(-) diff --git a/hal/api/leds.c b/hal/api/leds.c new file mode 100644 index 0000000..1918a0b --- /dev/null +++ b/hal/api/leds.c @@ -0,0 +1,332 @@ +/********************************************************************************* + * Copyright: (C) 2024 LingYun IoT System Studio + * All rights reserved. + * + * Filename: led.c + * Description: This file is used to control RGB 3-colors LED + * + * + * Pin connection: + * RGB Led Module IGKBoard + * R <-----> #Pin33 + * G <-----> #Pin35 + * B <-----> #Pin37 + * GND <-----> GND + * + ********************************************************************************/ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <dirent.h> +#include <string.h> +#include <time.h> +#include <errno.h> +#include <signal.h> + +#include <gpiod.h> + +#define DELAY 300 + +#define ON 1 +#define OFF 0 + +/* Three LEDs number */ +enum +{ + LED_R = 0, + LED_G, + LED_B, + LEDCNT, +}; + +enum +{ + ACTIVE_HIGH, /* High level will turn led on */ + ACTIVE_LOW, /* Low level will turn led on */ +}; + +/* Three LEDs hardware information */ +typedef struct led_s +{ + const char *name; /* RGB 3-color LED name */ + int chip_num; /* RGB 3-color LED connect chip */ + int gpio_num; /* RGB 3-color LED connect line */ + int active; /* RGB 3-color LED active level */ + struct gpiod_line_request *request; /* libgpiod gpio request handler */ +} led_t; + +static led_t leds_info[LEDCNT] = +{ + {"red", 0, 23, ACTIVE_HIGH, NULL }, /* GPIO1_IO23 on chip0 line 23, active high */ + {"green", 4, 1, ACTIVE_HIGH, NULL }, /* GPIO5_IO01 on chip4 line 1, active high */ + {"blue", 4, 8, ACTIVE_HIGH, NULL }, /* GPIO5_IO08 on chip4 line 8, active high */ +}; + +/* Three LEDs API context */ +typedef struct leds_s +{ + led_t *leds; /* led pointer to leds_info */ + int count; /* led count */ +} leds_t; + + +/* function declaration */ +int init_led(leds_t *leds); +int term_led(leds_t *leds); +int turn_led(leds_t *leds, int which, int cmd); +static inline void msleep(unsigned long ms); + + +int g_stop = 0; + +void sig_handler(int signum) +{ + switch( signum ) + { + case SIGINT: + case SIGTERM: + g_stop = 1; + + default: + break; + } + + return ; +} + +int main(int argc, char *argv[]) +{ + int rv; + leds_t leds = + { + .leds = leds_info, + .count = LEDCNT, + }; + + if( (rv=init_led(&leds)) < 0 ) + { + printf("initial leds gpio failure, rv=%d\n", rv); + return 1; + } + printf("initial RGB Led gpios okay\n"); + + signal(SIGINT, sig_handler); + signal(SIGTERM, sig_handler); + + while( !g_stop ) + { + turn_led(&leds, LED_R, ON); + msleep(DELAY); + turn_led(&leds, LED_R, OFF); + msleep(DELAY); + + turn_led(&leds, LED_G, ON); + msleep(DELAY); + turn_led(&leds, LED_G, OFF); + msleep(DELAY); + + turn_led(&leds, LED_B, ON); + msleep(DELAY); + turn_led(&leds, LED_B, OFF); + msleep(DELAY); + } + + term_led(&leds); + return 0; +} + +int term_led(leds_t *leds) +{ + int i; + led_t *led; + + printf("terminate RGB Led gpios\n"); + + if( !leds ) + { + printf("Invalid input arguments\n"); + return -1; + } + + for(i=0; i<leds->count; i++) + { + led = &leds->leds[i]; + + if( led->request ) + { + turn_led(leds, i, OFF); + gpiod_line_request_release(led->request); + } + } + + return 0; +} + + +int init_led(leds_t *leds) +{ + led_t *led; + int i, rv = 0; + char chip_dev[32]; + struct gpiod_chip *chip; /* gpio chip */ + struct gpiod_line_settings *settings; /* gpio direction, bias, active_low, value */ + struct gpiod_line_config *line_cfg; /* gpio line */ + struct gpiod_request_config *req_cfg; /* gpio consumer, it can be NULL */ + + + if( !leds ) + { + printf("Invalid input arguments\n"); + return -1; + } + + + /* defined in libgpiod-2.0/lib/line-settings.c: + + struct gpiod_line_settings { + enum gpiod_line_direction direction; + enum gpiod_line_edge edge_detection; + enum gpiod_line_drive drive; + enum gpiod_line_bias bias; + bool active_low; + enum gpiod_line_clock event_clock; + long debounce_period_us; + enum gpiod_line_value output_value; + }; + */ + settings = gpiod_line_settings_new(); + if (!settings) + { + printf("unable to allocate line settings\n"); + rv = -2; + goto cleanup; + } + + /* defined in libgpiod-2.0/lib/line-config.c + + struct gpiod_line_config { + struct per_line_config line_configs[LINES_MAX]; + size_t num_configs; + enum gpiod_line_value output_values[LINES_MAX]; + size_t num_output_values; + struct settings_node *sref_list; + }; + */ + + line_cfg = gpiod_line_config_new(); + if (!line_cfg) + { + printf("unable to allocate the line config structure"); + rv = -2; + goto cleanup; + } + + + /* defined in libgpiod-2.0/lib/request-config.c: + + struct gpiod_request_config { + char consumer[GPIO_MAX_NAME_SIZE]; + size_t event_buffer_size; + }; + */ + req_cfg = gpiod_request_config_new(); + if (!req_cfg) + { + printf("unable to allocate the request config structure"); + rv = -2; + goto cleanup; + } + + for(i=0; i<leds->count; i++) + { + led = &leds->leds[i]; + + snprintf(chip_dev, sizeof(chip_dev), "/dev/gpiochip%d", led->chip_num); + chip = gpiod_chip_open(chip_dev); + if( !chip ) + { + printf("open gpiochip failure, maybe you need running as root\n"); + rv = -3; + goto cleanup; + } + + /* Set as output direction, active low and default level as inactive */ + gpiod_line_settings_reset(settings); + gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_OUTPUT); + gpiod_line_settings_set_active_low(settings, led->active); + gpiod_line_settings_set_output_value(settings, GPIOD_LINE_VALUE_INACTIVE); + + /* set gpio line */ + gpiod_line_config_reset(line_cfg); + gpiod_line_config_add_line_settings(line_cfg, &led->gpio_num, 1, settings); + + /* Can be NULL for default settings. */ + gpiod_request_config_set_consumer(req_cfg, led->name); + + /* Request a set of lines for exclusive usage. */ + led->request = gpiod_chip_request_lines(chip, req_cfg, line_cfg); + + gpiod_chip_close(chip); + //printf("request %5s led[%d] for gpio output okay\n", led->name, led->gpio); + } + +cleanup: + + if( rv< 0 ) + term_led(leds); + + if( line_cfg ) + gpiod_line_config_free(line_cfg); + + if( req_cfg ) + gpiod_request_config_free(req_cfg); + + if( settings ) + gpiod_line_settings_free(settings); + + return rv; +} + +int turn_led(leds_t *leds, int which, int cmd) +{ + led_t *led; + int rv = 0; + int value = 0; + + if( !leds || which<0 || which>=leds->count ) + { + printf("Invalid input arguments\n"); + return -1; + } + + led = &leds->leds[which]; + + value = OFF==cmd ? GPIOD_LINE_VALUE_INACTIVE : GPIOD_LINE_VALUE_ACTIVE; + + gpiod_line_request_set_value(led->request, led->gpio_num, value); + + return 0; +} + +static inline void msleep(unsigned long ms) +{ + struct timespec cSleep; + unsigned long ulTmp; + + cSleep.tv_sec = ms / 1000; + if (cSleep.tv_sec == 0) + { + ulTmp = ms * 10000; + cSleep.tv_nsec = ulTmp * 100; + } + else + { + cSleep.tv_nsec = 0; + } + + nanosleep(&cSleep, 0); + + return ; +} diff --git a/hal/api/libgpiod/build.sh b/hal/api/libgpiod/build.sh new file mode 100755 index 0000000..6009e21 --- /dev/null +++ b/hal/api/libgpiod/build.sh @@ -0,0 +1,118 @@ +#!/bin/bash + +# library name and version +# Official: https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git +LIB_NAME=libgpiod-2.0 +PACK_SUFIX=tar.gz + +# Cross compiler for cross compile on Linux server +CROSS_COMPILE=arm-linux-gnueabihf- + +# this project absolute path +PRJ_PATH=$(cd $(dirname "${BASH_SOURCE[0]}") && pwd) + +# binaries install path +PREFIX_PATH=$PRJ_PATH/install + +# check installed or not file +INST_FILE=$PREFIX_PATH/lib/libgpiod.so + +#+-------------------------+ +#| Shell script functions | +#+-------------------------+ + +function pr_error() { + echo -e "\033[40;31m $1 \033[0m" +} + +function pr_warn() { + echo -e "\033[40;33m $1 \033[0m" +} + +function pr_info() { + echo -e "\033[40;32m $1 \033[0m" +} + +function check_result() +{ + if [ $? != 0 ] ; then + pr_error $1 + fi +} + +function do_export() +{ + pr_warn "set cross(${CROSS_COMPILE})" + + # export cross toolchain + export CC=${CROSS_COMPILE}gcc + export CXX=${CROSS_COMPILE}g++ + export AS=${CROSS_COMPILE}as + export AR=${CROSS_COMPILE}ar + export LD=${CROSS_COMPILE}ld + export NM=${CROSS_COMPILE}nm + export RANLIB=${CROSS_COMPILE}ranlib + export OBJDUMP=${CROSS_COMPILE}objdump + export STRIP=${CROSS_COMPILE}strip + + # export cross configure + export CONFIG_CROSS=" --build=i686-pc-linux --host=arm-linux " + + # Clear LDFLAGS and CFLAGS + export LDFLAGS= + export CFLAGS= +} + +function do_fetch() +{ + if [ -e ${INST_FILE} ] ; then + pr_warn "$LIB_NAME compile and installed alredy" + exit ; + fi + + if [ -d $LIB_NAME ] ; then + pr_warn "$LIB_NAME fetch already" + return ; + fi + + if [ ! -f ${LIB_NAME}.${PACK_SUFIX} ] ; then + wget https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/snapshot/${LIB_NAME}.${PACK_SUFIX} + check_result "ERROR: download ${LIB_NAME} failure" + fi + + pr_warn "$LIB_NAME download already, decompress it now" + tar -xzf ${LIB_NAME}.${PACK_SUFIX} +} + +function do_build() +{ + cd $LIB_NAME + + ./autogen.sh + + do_export + + echo "ac_cv_func_malloc_0_nonnull=yes" > arm-linux.cache + ./configure --prefix=${PREFIX_PATH} ${CONFIG_CROSS} --enable-static \ + --cache-file=arm-linux.cache --enable-tools + check_result "ERROR: configure ${LIB_NAME} failure" + + make -j ${JOBS} && make install + check_result "ERROR: compile ${LIB_NAME} failure" +} + +function do_clean() +{ + rm -rf ${LIB_NAME}* + rm -rf install +} + +if [[ $# == 1 && $1 == -c ]] ;then + pr_warn "start clean $LIB_NAME" + do_clean + exit; +fi + +do_fetch + +do_build diff --git a/hal/api/libgpiod/makefile b/hal/api/libgpiod/makefile new file mode 100644 index 0000000..4cc8cac --- /dev/null +++ b/hal/api/libgpiod/makefile @@ -0,0 +1,11 @@ + +all: update_cross + @./build.sh + +clean: + @./build.sh -c + +update_cross: +ifdef CROSS_COMPILE + sed -i 's|^CROSS_COMPILE=.*|CROSS_COMPILE=${CROSS_COMPILE}|g' build.sh +endif diff --git a/hal/api/makefile b/hal/api/makefile new file mode 100644 index 0000000..0c3525e --- /dev/null +++ b/hal/api/makefile @@ -0,0 +1,54 @@ +#******************************************************************************** +# Copyright: (C) 2023 LingYun IoT System Studio +# All rights reserved. +# +# Filename: Makefile +# Description: This file used to compile all the C file to respective binary, +# and it will auto detect cross compile or local compile. +# +# Version: 1.0.0(11/08/23) +# Author: Guo Wenxue <guowenxue@gmail.com> +# ChangeLog: 1, Release initial version on "11/08/23 16:18:43" +# +#******************************************************************************* + +PWD=$(shell pwd) +INSTPATH=/tftp + +CROSS_COMPILE=arm-linux-gnueabihf- +CC=${CROSS_COMPILE}gcc + +LDFLAGS += -lm + +SRCS = $(wildcard ${VPATH}/*.c) +OBJS = $(patsubst %.c,%.o,$(SRCS)) + +SRCFILES = $(wildcard *.c) +BINARIES=$(SRCFILES:%.c=%) + +# libgpiod compile install path +LIBGPIOD_PATH=libgpiod/install +CFLAGS+=-I ${LIBGPIOD_PATH}/include +LDFLAGS+=-L ${LIBGPIOD_PATH}/lib -lgpiod + +all: libs binaries install + +libs: + make -C libgpiod CROSS_COMPILE=${CROSS_COMPILE} + +binaries: ${BINARIES} + +%: %.c + $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS) + +install: + cp $(BINARIES) ${INSTPATH} + +clean: + @rm -f *.o *.lo $(BINARIES) + +distclean: clean + @rm -f tags cscope* + @make clean -C libgpiod + +.PHONY: clean entry diff --git a/hal/api/pwm.c b/hal/api/pwm.c new file mode 100644 index 0000000..408d1bc --- /dev/null +++ b/hal/api/pwm.c @@ -0,0 +1,309 @@ +/********************************************************************************* + * Copyright: (C) 2021 LingYun IoT System Studio + * All rights reserved. + * + * Filename: pwm.c + * Description: This file is used to control PWM buzzer/Led + * + * Pin connection: + * PWM Module Raspberry Pi Board + * VCC <-----> 5V + * buzzer <-----> #Pin32(BCM GPIO12) + * Led <-----> #Pin33(BCM GPIO13) + * GND <-----> GND + * + * /boot/config.txt: + * + * dtoverlay=pwm,pin=12,func=4 (Buzzer) + * dtoverlay=pwm,pin=13,func=4 (Led) + * + ********************************************************************************/ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <dirent.h> +#include <string.h> +#include <time.h> +#include <errno.h> +#include <signal.h> +#include <getopt.h> +#include <libgen.h> + +#define PWMCHIP_PATH "/sys/class/pwm/pwmchip0" + +#define ENABLE 1 +#define DISABLE 0 + +int init_pwm(int channel, int freq, int duty); +int turn_pwm(int channel, int status); +int term_pwm(int channel); +static inline void msleep(unsigned long ms); + +static inline void banner(const char *progname) +{ + printf("%s program Version v1.0.0\n", progname); + printf("Copyright (C) 2023 LingYun IoT System Studio.\n"); +} + +static void program_usage(const char *progname) +{ + + printf("Usage: %s [OPTION]...\n", progname); + printf(" This is pwm control program. \n"); + + printf(" -c[channel ] Specify PWM channel, such as 0\n"); + printf(" -f[freq ] Specify PWM frequency, default 2500(Hz)\n"); + printf(" -d[duty ] Specify PWM duty, default 50(50%%)\n"); + printf(" -s[status ] Specify PWM status: 1->on(default), 0->off\n"); + printf(" -h[help ] Display this help information\n"); + printf(" -v[version ] Display the program version\n"); + printf("\n"); + + printf("Example buzzer : %s -c 0 -f 2750 -d 50 -s 1\n", progname); + printf("Example Led : %s -c 1 -f 100 -d 50 -s 1\n", progname); + printf("Example disable: %s -c 0 -s 0\n", progname); + + printf("\n"); + banner(progname); + return; +} + +int main(int argc, char **argv) +{ + int rv; + char *progname=NULL; + int channel = -1; + int freq = 2500; + int duty = 50; + int status = ENABLE; + + struct option long_options[] = { + {"channel", required_argument, NULL, 'c'}, + {"freq", required_argument, NULL, 'f'}, + {"duty", required_argument, NULL, 'd'}, + {"status", required_argument, NULL, 's'}, + {"version", no_argument, NULL, 'v'}, + {"help", no_argument, NULL, 'h'}, + {NULL, 0, NULL, 0} + }; + + progname = basename(argv[0]); + + /* Parser the command line parameters */ + while ((rv = getopt_long(argc, argv, "c:f:d:s:vh", long_options, NULL)) != -1) + { + switch (rv) + { + case 'c': /* Set pwm channel, such as 0,1 */ + channel = atoi(optarg); + break; + + case 'f': /* Set pwm frequency */ + freq = atoi(optarg); + break; + + case 'd': /* Set pwm duty cycle */ + duty = atoi(optarg); + break; + + case 's': /* Set pwm status, 0 for disable and 1 for enable */ + status = atoi(optarg); + break; + + case 'v': /* Get software version */ + banner(progname); + return EXIT_SUCCESS; + + case 'h': /* Get help information */ + program_usage(progname); + return 0; + + default: + break; + } + } + + if(channel < 0 ) + { + program_usage(progname); + return 0; + } + + if( status ) + { + if( (rv=init_pwm(channel, freq, duty)) < 0 ) + { + printf("initial PWM failure, rv=%d\n", rv); + return 1; + } + + turn_pwm(channel, ENABLE); + } + else + { + term_pwm(channel); + } + + return 0; +} + +/* check PWM $channel export or not */ +int check_pwm(int channel) +{ + char path[256]; + + /* check /sys/class/pwm/pwmchip0/pwmN exist or not */ + snprintf(path, sizeof(path), "%s/pwm%d", PWMCHIP_PATH, channel); + return access(path, F_OK) ? 0 : 1; +} + +/* export($export=1)/unexport($export=0) PWM $channel */ +int export_pwm(int channel, int export) +{ + int fd; + char buf[32]; + char path[256]; + + if( export && check_pwm(channel) ) + return 0; /* export already when export */ + else if( !export && !check_pwm(channel) ) + return 0; /* unexport already when unexport */ + + /* export PWM channel by echo N > /sys/class/pwm/pwmchip0/export */ + snprintf(path, sizeof(path), "%s/%s", PWMCHIP_PATH, export?"export":"unexport"); + if( (fd=open(path, O_WRONLY)) < 0 ) + { + printf("open '%s' failed: %s\n", path, strerror(errno)); + return -3; + } + + snprintf(buf, sizeof(buf), "%d", channel); + write(fd, buf, strlen(buf)); + + msleep(100); + + if( export && check_pwm(channel) ) + return 0; /* export already when export */ + else if( !export && !check_pwm(channel) ) + return 0; /* unexport already when unexport */ + + return -4; +} + +/* configure PWM $channel */ +int config_pwm(int channel, int freq, int duty) +{ + int fd; + char buf[32]; + char path[256]; + int period; + int duty_cycle; + + if( !check_pwm(channel) ) + return -2; + + /* open PWM period file and write period in ns */ + snprintf(path, sizeof(path), "%s/pwm%d/period", PWMCHIP_PATH, channel); + if( (fd=open(path, O_WRONLY)) < 0 ) + { + printf("open '%s' failed: %s\n", path, strerror(errno)); + return -3; + } + period = 1000000000/freq; + snprintf(buf, sizeof(buf), "%d", period); + write(fd, buf, strlen(buf)); + + /* open PWM duty_cycle file and write duty */ + snprintf(path, sizeof(path), "%s/pwm%d/duty_cycle", PWMCHIP_PATH, channel); + if( (fd=open(path, O_WRONLY)) < 0 ) + { + printf("open '%s' failed: %s\n", path, strerror(errno)); + return -3; + } + duty_cycle = (period*duty) / 100; + snprintf(buf, sizeof(buf), "%d", duty_cycle); + write(fd, buf, strlen(buf)); + + return 0; +} + +int init_pwm(int channel, int freq, int duty) +{ + int rv; + char buf[32]; + char path[256]; + + if( (rv=export_pwm(channel, 1)) ) + { + printf("export PWM channel[%d] failed, rv=%d\n", channel, rv); + return rv; + } + + if( (rv=config_pwm(channel, freq, duty)) ) + { + printf("config PWM channel[%d] failed, rv=%d\n", channel, rv); + return rv; + } + + printf("config pwm%d with freq[%d] duty[%d] okay\n", channel, freq, duty); + + return 0; +} + +int turn_pwm(int channel, int status) +{ + int fd; + char buf[32]; + char path[256]; + + if( !check_pwm(channel) ) + return -1; + + /* open PWM enable file and enable(1)/disable(0) it */ + snprintf(path, sizeof(path), "%s/pwm%d/enable", PWMCHIP_PATH, channel); + if( (fd=open(path, O_WRONLY)) < 0 ) + { + printf("open '%s' failed: %s\n", path, strerror(errno)); + return -3; + } + snprintf(buf, sizeof(buf), "%d", status?1:0); + write(fd, buf, strlen(buf)); + + printf("pwm[%d] %s\n", channel, status?"enable":"disable"); + + return 0; +} + +int term_pwm(int channel) +{ + if( !check_pwm(channel) ) + return 0; + + turn_pwm(channel, DISABLE); + export_pwm(channel, 0); + + return 0; +} + +static inline void msleep(unsigned long ms) +{ + struct timespec cSleep; + unsigned long ulTmp; + + cSleep.tv_sec = ms / 1000; + if (cSleep.tv_sec == 0) + { + ulTmp = ms * 10000; + cSleep.tv_nsec = ulTmp * 100; + } + else + { + cSleep.tv_nsec = 0; + } + + nanosleep(&cSleep, 0); + + return ; +} -- Gitblit v1.9.1