/*********************************************************************************
|
* 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 IGKBoard
|
* VCC <-----> 5V
|
* Led <-----> #Pin28(PWM8)
|
* 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 <getopt.h>
|
#include <libgen.h>
|
|
#define PWM_SYS_PATH "/sys/class/pwm/pwmchip0"
|
|
#define ENABLE 1
|
#define DISABLE 0
|
|
|
typedef struct pwm_s
|
{
|
char pwm_path[64]; /* PWM path, such as /sys/class/pwm/pwmchip0 */
|
char chn_path[64]; /* channel path, such as /sys/class/pwm/pwmchip0/pwm0 */
|
int pwm_num; /* pwm number */
|
int chn_num; /* channel number */
|
} pwm_t;
|
|
|
int init_pwm(pwm_t *pwm, int pwm_num, int chn_num);
|
int conf_pwm(pwm_t *pwm, int freq, int duty);
|
int turn_pwm(pwm_t *pwm, int status);
|
int term_pwm(pwm_t *pwm);
|
|
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(" -p[pwm ] Specify PWM chip, such as 1 for pwmchip1\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 -p 1 -c 0 -f 10000 -d 50 -s 1\n", progname);
|
printf("Example Led : %s -p 1 -c 1 -f 100 -d 50 -s 1\n", progname);
|
printf("Example disable: %s -p 1 -c 0 -s 0\n", progname);
|
|
printf("\n");
|
banner(progname);
|
return;
|
}
|
|
int main(int argc, char **argv)
|
{
|
int rv;
|
char *progname=NULL;
|
int chn_num = -1;
|
int pwm_num = -1;
|
int freq = 2500;
|
int duty = 50;
|
int status = ENABLE;
|
pwm_t pwm;
|
|
struct option long_options[] = {
|
{"pwm", required_argument, NULL, 'p'},
|
{"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, "p:c:f:d:s:vh", long_options, NULL)) != -1)
|
{
|
switch (rv)
|
{
|
case 'p': /* Set pwm chip, such as 1 for pwmchip1 */
|
pwm_num = atoi(optarg);
|
break;
|
|
case 'c': /* Set pwm channel, such as 0,1 */
|
chn_num = 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( pwm_num<0 || chn_num<0 )
|
{
|
program_usage(progname);
|
return 0;
|
}
|
|
init_pwm(&pwm, pwm_num, chn_num);
|
|
|
if( status )
|
{
|
if( (rv=conf_pwm(&pwm, freq, duty)) < 0 )
|
{
|
printf("Configure PWM failure, rv=%d\n", rv);
|
return 1;
|
}
|
|
turn_pwm(&pwm, ENABLE);
|
}
|
else
|
{
|
term_pwm(&pwm);
|
}
|
|
return 0;
|
}
|
|
int init_pwm(pwm_t *pwm, int pwm_num, int chn_num)
|
{
|
if( !pwm || pwm_num<0 || chn_num<0 )
|
{
|
printf("Invalid input arguments\n");
|
return -1;
|
}
|
|
snprintf(pwm->pwm_path, sizeof(pwm->pwm_path), "/sys/class/pwm/pwmchip%d", pwm_num);
|
snprintf(pwm->chn_path, sizeof(pwm->chn_path), "/sys/class/pwm/pwmchip%d/pwm%d", pwm_num, chn_num);
|
pwm->pwm_num = pwm_num;
|
pwm->chn_num = chn_num;
|
|
return 0;
|
}
|
|
/* check PWM $channel export or not */
|
int check_pwm(pwm_t *pwm)
|
{
|
/* check /sys/class/pwm/pwmchipX/pwmY exist or not */
|
return access(pwm->chn_path, F_OK) ? 0 : 1;
|
}
|
|
/* export($export=1)/unexport($export=0) PWM $channel */
|
int export_pwm(pwm_t *pwm, int export)
|
{
|
int fd;
|
char buf[32];
|
char path[256];
|
|
if( export && check_pwm(pwm) )
|
return 0; /* export already when export */
|
else if( !export && !check_pwm(pwm) )
|
return 0; /* unexport already when unexport */
|
|
/* export PWM channel by echo Y > /sys/class/pwm/pwmchipX/export */
|
snprintf(path, sizeof(path), "%s/%s", pwm->pwm_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", pwm->chn_num);
|
write(fd, buf, strlen(buf));
|
|
msleep(100);
|
|
if( export && check_pwm(pwm) )
|
return 0; /* export already when export */
|
else if( !export && !check_pwm(pwm) )
|
return 0; /* unexport already when unexport */
|
|
return -4;
|
}
|
|
/* set PWM $channel */
|
int set_pwm(pwm_t *pwm, int freq, int duty)
|
{
|
int fd;
|
char buf[32];
|
char path[256];
|
int period;
|
int duty_cycle;
|
|
if( !check_pwm(pwm) )
|
return -2;
|
|
/* open PWM period file and write period in ns */
|
snprintf(path, sizeof(path), "%s/period", pwm->chn_path);
|
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/duty_cycle", pwm->chn_path);
|
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 conf_pwm(pwm_t *pwm, int freq, int duty)
|
{
|
int rv;
|
char buf[32];
|
char path[256];
|
|
if( (rv=export_pwm(pwm, 1)) )
|
{
|
printf("export PWM channel[%d] failed, rv=%d\n", pwm->chn_num, rv);
|
return rv;
|
}
|
|
if( (rv=set_pwm(pwm, freq, duty)) )
|
{
|
printf("config PWM channel[%d] failed, rv=%d\n", pwm->chn_num, rv);
|
return rv;
|
}
|
|
printf("config pwm%d channel%d with freq[%d] duty[%d] okay\n", pwm->pwm_num, pwm->chn_num, freq, duty);
|
|
return 0;
|
}
|
|
int turn_pwm(pwm_t *pwm, int status)
|
{
|
int fd;
|
char buf[32];
|
char path[256];
|
|
if( !check_pwm(pwm) )
|
return -1;
|
|
/* open PWM enable file and enable(1)/disable(0) it */
|
snprintf(path, sizeof(path), "%s/enable", pwm->chn_path);
|
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] channel[%d]%s\n", pwm->pwm_num, pwm->chn_num, status?"enable":"disable");
|
|
return 0;
|
}
|
|
int term_pwm(pwm_t *pwm)
|
{
|
if( !check_pwm(pwm) )
|
return 0;
|
|
turn_pwm(pwm, DISABLE);
|
export_pwm(pwm, 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 ;
|
}
|