Linux内核GPIO工具概述
本文档对Linux内核GPIO工具目录中的所有C文件进行简要分析,包括主要函数、执行流程、生成工具名称和使用示例。
工具概述
- lsgpio: 列出GPIO信息
- gpio-hammer: 闪烁GPIO管脚
- gpio-event-mon: 监控GPIO事件
目录结构
gpio/
├── Makefile # 构建配置
├── lsgpio.c # 列出GPIO工具
├── gpio-hammer.c # 闪烁GPIO工具
├── gpio-event-mon.c # GPIO事件监控工具
├── gpio-utils.c # 共享实用函数库
└── gpio-utils.h # 共享头文件
1. lsgpio.c - 列出GPIO信息
1.1 主要函数
1.1.1 print_flags()
c
void print_flags(unsigned long flags)
{
int i;
int printed = 0;
for (i = 0; i < ARRAY_SIZE(flagnames); i++) {
if (flags & flagnames[i].mask) {
if (printed)
fprintf(stdout, " ");
fprintf(stdout, "%s", flagnames[i].name);
printed++;
}
}
}
功能: 将GPIO标志位转换为可读字符串并打印。
参数:
flags: GPIO标志位掩码
支持的标志:kernel: 内核使用output: 输出模式active-low: 低电平有效open-drain: 开漏输出open-source: 开源输出
1.1.2 list_device()
c
int list_device(const char *device_name)
{
struct gpiochip_info cinfo;
char *chrdev_name;
int fd;
int ret;
int i;
ret = asprintf(&chrdev_name, "/dev/%s", device_name);
if (ret < 0)
return -ENOMEM;
fd = open(chrdev_name, 0);
if (fd == -1) {
ret = -errno;
fprintf(stderr, "Failed to open %s\n", chrdev_name);
goto exit_close_error;
}
/* Inspect this GPIO chip */
ret = ioctl(fd, GPIO_GET_CHIPINFO_IOCTL, &cinfo);
if (ret == -1) {
ret = -errno;
perror("Failed to issue CHIPINFO IOCTL\n");
goto exit_close_error;
}
fprintf(stdout, "GPIO chip: %s, \"%s\", %u GPIO lines\n",
cinfo.name, cinfo.label, cinfo.lines);
/* Loop over the lines and print info */
for (i = 0; i < cinfo.lines; i++) {
struct gpioline_info linfo;
memset(&linfo, 0, sizeof(linfo));
linfo.line_offset = i;
ret = ioctl(fd, GPIO_GET_LINEINFO_IOCTL, &linfo);
if (ret == -1) {
ret = -errno;
perror("Failed to issue LINEINFO IOCTL\n");
goto exit_close_error;
}
fprintf(stdout, "\tline %2d:", linfo.line_offset);
if (linfo.name[0])
fprintf(stdout, " \"%s\"", linfo.name);
else
fprintf(stdout, " unnamed");
if (linfo.consumer[0])
fprintf(stdout, " \"%s\"", linfo.consumer);
else
fprintf(stdout, " unused");
if (linfo.flags) {
fprintf(stdout, " [");
print_flags(linfo.flags);
fprintf(stdout, "]");
}
fprintf(stdout, "\n");
}
exit_close_error:
if (close(fd) == -1)
perror("Failed to close GPIO character device file");
free(chrdev_name);
return ret;
}
功能: 列出指定GPIO设备的所有管脚信息。
参数:
device_name: GPIO设备名称(如"gpiochip0")
执行流程:
- 打开GPIO设备文件
- 使用
GPIO_GET_CHIPINFO_IOCTL获取芯片信息 - 打印芯片名称、标签和管脚数量
- 遍历所有管脚,使用
GPIO_GET_LINEINFO_IOCTL获取每条管脚信息 - 打印管脚偏移、名称、消费者标签和标志
1.1.3 print_usage()
c
void print_usage(void)
{
fprintf(stderr, "Usage: lsgpio [options]...\n"
"List GPIO chips, lines and states\n"
" -n <name> List GPIOs on a named device\n"
" -? This helptext\n"
);
}
功能: 打印工具使用说明。
1.1.4 main()
c
int main(int argc, char **argv)
{
const char *device_name = NULL;
int ret;
int c;
while ((c = getopt(argc, argv, "n:")) != -1) {
switch (c) {
case 'n':
device_name = optarg;
break;
case '?':
print_usage();
return -1;
}
}
if (device_name)
ret = list_device(device_name);
else {
const struct dirent *ent;
DIR *dp;
/* List all GPIO devices one at a time */
dp = opendir("/dev");
if (!dp) {
ret = -errno;
goto error_out;
}
ret = -ENOENT;
while (ent = readdir(dp), ent) {
if (check_prefix(ent->d_name, "gpiochip")) {
ret = list_device(ent->d_name);
if (ret)
break;
}
}
ret = 0;
if (closedir(dp) == -1) {
perror("scanning devices: Failed to close directory");
ret = -errno;
}
}
error_out:
return ret;
}
功能: 程序入口点,处理命令行参数并执行相应操作。
执行流程:
- 解析命令行参数(-n指定设备,-?显示帮助)
- 如果指定了设备,则列出该设备的GPIO管脚
- 否则,遍历
/dev目录下所有以"gpiochip"开头的设备并列出
1.2 命令参数详解
bash
lsgpio [-n device-name]
参数说明:
-n <name>: 指定要列出的GPIO设备名称(如"gpiochip0"),如果不指定则列出所有GPIO设备-?: 显示帮助信息
1.3 使用示例
bash
# 列出所有GPIO设备
lsgpio
# 列出指定GPIO设备
lsgpio -n gpiochip0
2. gpio-hammer.c - 闪烁GPIO
2.1 主要函数
2.1.1 hammer_device()
c
int hammer_device(const char *device_name, unsigned int *lines, int nlines,
unsigned int loops)
{
struct gpiohandle_data data;
char swirr[] = "-\\|/";
int fd;
int ret;
int i, j;
unsigned int iteration = 0;
memset(&data.values, 0, sizeof(data.values));
ret = gpiotools_request_linehandle(device_name, lines, nlines,
GPIOHANDLE_REQUEST_OUTPUT, &data,
"gpio-hammer");
if (ret < 0)
goto exit_error;
else
fd = ret;
ret = gpiotools_get_values(fd, &data);
if (ret < 0)
goto exit_close_error;
fprintf(stdout, "Hammer lines [");
for (i = 0; i < nlines; i++) {
fprintf(stdout, "%d", lines[i]);
if (i != (nlines - 1))
fprintf(stdout, ", ");
}
fprintf(stdout, "] on %s, initial states: [", device_name);
for (i = 0; i < nlines; i++) {
fprintf(stdout, "%d", data.values[i]);
if (i != (nlines - 1))
fprintf(stdout, ", ");
}
fprintf(stdout, "]\n");
/* Hammertime! */
j = 0;
while (1) {
/* Invert all lines so we blink */
for (i = 0; i < nlines; i++)
data.values[i] = !data.values[i];
ret = gpiotools_set_values(fd, &data);
if (ret < 0)
goto exit_close_error;
/* Re-read values to get status */
ret = gpiotools_get_values(fd, &data);
if (ret < 0)
goto exit_close_error;
fprintf(stdout, "[%c] ", swirr[j]);
j++;
if (j == sizeof(swirr)-1)
j = 0;
fprintf(stdout, "[");
for (i = 0; i < nlines; i++) {
fprintf(stdout, "%d: %d", lines[i], data.values[i]);
if (i != (nlines - 1))
fprintf(stdout, ", ");
}
fprintf(stdout, "]\r");
fflush(stdout);
sleep(1);
iteration++;
if (loops && iteration == loops)
break;
}
fprintf(stdout, "\n");
ret = 0;
exit_close_error:
gpiotools_release_linehandle(fd);
exit_error:
return ret;
}
功能: 反复闪烁指定的GPIO管脚。
参数:
device_name: GPIO设备名称lines: GPIO管脚偏移数组nlines: 管脚数量loops: 循环次数(0表示无限循环)
执行流程:
- 请求GPIO管脚句柄(输出模式)
- 获取初始状态
- 循环翻转GPIO状态
- 打印当前状态
- 延迟1秒
- 获取初始管脚状态并打印
- 进入循环:
- 反转所有管脚状态
- 设置新的管脚状态
- 读取并打印当前状态
- 显示旋转动画
- 等待1秒
- 检查是否达到循环次数
- 释放GPIO管脚句柄
2.1.2 print_usage()
c
void print_usage(void)
{
fprintf(stderr, "Usage: gpio-hammer [options]...\n"
"Hammer GPIO lines, 0->1->0->1...\n"
" -n <name> Hammer GPIOs on a named device (must be stated)\n"
" -o <n> Offset[s] to hammer, at least one, several can be stated\n"
" [-c <n>] Do <n> loops (optional, infinite loop if not stated)\n"
" -? This helptext\n"
"\n"
"Example:\n"
"gpio-hammer -n gpiochip0 -o 4\n"
);
}
功能: 打印工具使用说明。
2.1.3 main()
c
int main(int argc, char **argv)
{
const char *device_name = NULL;
unsigned int lines[GPIOHANDLES_MAX];
unsigned int loops = 0;
int nlines;
int c;
int i;
i = 0;
while ((c = getopt(argc, argv, "c:n:o:?")) != -1) {
switch (c) {
case 'c':
loops = strtoul(optarg, NULL, 10);
break;
case 'n':
device_name = optarg;
break;
case 'o':
lines[i] = strtoul(optarg, NULL, 10);
i++;
break;
case '?':
print_usage();
return -1;
}
}
nlines = i;
if (!device_name || !nlines) {
print_usage();
return -1;
}
return hammer_device(device_name, lines, nlines, loops);
}
功能: 程序入口点,处理命令行参数并执行闪烁操作。
执行流程:
- 解析命令行参数(-n指定设备,-o指定管脚,-c指定循环次数)
- 验证参数有效性
- 调用
hammer_device()执行闪烁操作
2.2 命令参数详解
bash
gpio-hammer -n <device-name> -o <offset1> [-o <offset2> ...] [-c <loops>]
参数说明:
-n <name>: 指定要操作的GPIO设备名称(必须参数)-o <n>: 指定要闪烁的GPIO管脚偏移,可以指定多个(至少一个必须参数)-c <n>: 指定闪烁循环次数,0表示无限循环(可选参数)-?: 显示帮助信息
2.3 使用示例
bash
# 无限闪烁gpiochip0的第4号线
gpio-hammer -n gpiochip0 -o 4
# 闪烁gpiochip0的第4和5号线10次
gpio-hammer -n gpiochip0 -o 4 -o 5 -c 10
3. gpio-event-mon.c - GPIO事件监控
3.1 主要函数
3.1.1 monitor_device()
c
int monitor_device(const char *device_name,
unsigned int line,
uint32_t handleflags,
uint32_t eventflags,
unsigned int loops)
{
struct gpioevent_request req;
struct gpiohandle_data data;
char *chrdev_name;
int fd;
int ret;
int i = 0;
ret = asprintf(&chrdev_name, "/dev/%s", device_name);
if (ret < 0)
return -ENOMEM;
fd = open(chrdev_name, 0);
if (fd == -1) {
ret = -errno;
fprintf(stderr, "Failed to open %s\n", chrdev_name);
goto exit_close_error;
}
req.lineoffset = line;
req.handleflags = handleflags;
req.eventflags = eventflags;
strcpy(req.consumer_label, "gpio-event-mon");
ret = ioctl(fd, GPIO_GET_LINEEVENT_IOCTL, &req);
if (ret == -1) {
ret = -errno;
fprintf(stderr, "Failed to issue GET EVENT "
"IOCTL (%d)\n",
ret);
goto exit_close_error;
}
/* Read initial states */
ret = ioctl(req.fd, GPIOHANDLE_GET_LINE_VALUES_IOCTL, &data);
if (ret == -1) {
ret = -errno;
fprintf(stderr, "Failed to issue GPIOHANDLE GET LINE "
"VALUES IOCTL (%d)\n",
ret);
goto exit_close_error;
}
fprintf(stdout, "Monitoring line %d on %s\n", line, device_name);
fprintf(stdout, "Initial line value: %d\n", data.values[0]);
while (1) {
struct gpioevent_data event;
ret = read(req.fd, &event, sizeof(event));
if (ret == -1) {
if (errno == -EAGAIN) {
fprintf(stderr, "nothing available\n");
continue;
} else {
ret = -errno;
fprintf(stderr, "Failed to read event (%d)\n",
ret);
break;
}
}
if (ret != sizeof(event)) {
fprintf(stderr, "Reading event failed\n");
ret = -EIO;
break;
}
fprintf(stdout, "GPIO EVENT %llu: ", event.timestamp);
switch (event.id) {
case GPIOEVENT_EVENT_RISING_EDGE:
fprintf(stdout, "rising edge");
break;
case GPIOEVENT_EVENT_FALLING_EDGE:
fprintf(stdout, "falling edge");
break;
default:
fprintf(stdout, "unknown event");
}
fprintf(stdout, "\n");
i++;
if (i == loops)
break;
}
exit_close_error:
if (close(fd) == -1)
perror("Failed to close GPIO character device file");
free(chrdev_name);
return ret;
}
功能: 监控指定GPIO管脚的事件(上升沿/下降沿)。
参数:
device_name: GPIO设备名称line: GPIO管脚偏移handleflags: 管脚处理标志eventflags: 事件标志(上升沿/下降沿)loops: 监控次数(0表示无限监控)
执行流程:
- 打开GPIO设备文件
- 使用
GPIO_GET_LINEEVENT_IOCTL请求事件监控 - 获取初始管脚状态并打印
- 进入循环:
- 读取事件数据
- 打印事件时间戳和类型(上升沿/下降沿)
- 检查是否达到监控次数
- 关闭设备文件
3.1.2 print_usage()
c
void print_usage(void)
{
fprintf(stderr, "Usage: gpio-event-mon [options]...\n"
"Listen to events on GPIO lines, 0->1 1->0\n"
" -n <name> Listen on GPIOs on a named device (must be stated)\n"
" -o <n> Offset to monitor\n"
" -d Set line as open drain\n"
" -s Set line as open source\n"
" -r Listen for rising edges\n"
" -f Listen for falling edges\n"
" [-c <n>] Do <n> loops (optional, infinite loop if not stated)\n"
" -? This helptext\n"
"\n"
"Example:\n"
"gpio-event-mon -n gpiochip0 -o 4 -r -f\n"
);
}
功能: 打印工具使用说明。
3.1.3 main()
c
int main(int argc, char **argv)
{
const char *device_name = NULL;
unsigned int line = -1;
unsigned int loops = 0;
uint32_t handleflags = GPIOHANDLE_REQUEST_INPUT;
uint32_t eventflags = 0;
int c;
while ((c = getopt(argc, argv, "c:n:o:dsrf?")) != -1) {
switch (c) {
case 'c':
loops = strtoul(optarg, NULL, 10);
break;
case 'n':
device_name = optarg;
break;
case 'o':
line = strtoul(optarg, NULL, 10);
break;
case 'd':
handleflags |= GPIOHANDLE_REQUEST_OPEN_DRAIN;
break;
case 's':
handleflags |= GPIOHANDLE_REQUEST_OPEN_SOURCE;
break;
case 'r':
eventflags |= GPIOEVENT_REQUEST_RISING_EDGE;
break;
case 'f':
eventflags |= GPIOEVENT_REQUEST_FALLING_EDGE;
break;
case '?':
print_usage();
return -1;
}
}
if (!device_name || line == -1) {
print_usage();
return -1;
}
if (!eventflags) {
printf("No flags specified, listening on both rising and "
"falling edges\n");
eventflags = GPIOEVENT_REQUEST_BOTH_EDGES;
}
return monitor_device(device_name, line, handleflags,
eventflags, loops);
}
功能: 程序入口点,处理命令行参数并执行事件监控。
执行流程:
- 解析命令行参数(-n指定设备,-o指定管脚,-r监控上升沿,-f监控下降沿,-c指定次数)
- 验证参数有效性
- 如果未指定事件标志,则默认监控上升沿和下降沿
- 调用
monitor_device()执行事件监控
3.2 命令参数详解
bash
gpio-event-mon -n <device-name> -o <offset> [-d] [-s] [-r] [-f] [-c <loops>]
参数说明:
-n <name>: 指定要监控的GPIO设备名称(必须参数)-o <n>: 指定要监控的GPIO管脚偏移(必须参数)-d: 设置管脚为开漏模式-s: 设置管脚为开源模式-r: 监控上升沿事件-f: 监控下降沿事件-c <n>: 指定监控事件次数,0表示无限监控(可选参数)-?: 显示帮助信息
3.3 使用示例
bash
# 监控gpiochip0的第4号线的所有事件
gpio-event-mon -n gpiochip0 -o 4
# 监控gpiochip0的第4号线的上升沿
gpio-event-mon -n gpiochip0 -o 4 -r
# 监控gpiochip0的第4号线的上升沿和下降沿5次
gpio-event-mon -n gpiochip0 -o 4 -r -f -c 5
4. gpio-utils.c - 共享实用函数库
4.1 主要函数
4.1.1 gpiotools_request_linehandle()
c
int gpiotools_request_linehandle(const char *device_name, unsigned int *lines,
unsigned int nlines, unsigned int flag,
struct gpiohandle_data *data,
const char *consumer_label)
{
struct gpiohandle_request req;
char *chrdev_name;
int fd;
int i;
int ret;
ret = asprintf(&chrdev_name, "/dev/%s", device_name);
if (ret < 0)
return -ENOMEM;
fd = open(chrdev_name, 0);
if (fd == -1) {
ret = -errno;
fprintf(stderr, "Failed to open %s, %s\n",
chrdev_name, strerror(errno));
goto exit_close_error;
}
for (i = 0; i < nlines; i++)
req.lineoffsets[i] = lines[i];
req.flags = flag;
strcpy(req.consumer_label, consumer_label);
req.lines = nlines;
if (flag & GPIOHANDLE_REQUEST_OUTPUT)
memcpy(req.default_values, data, sizeof(req.default_values));
ret = ioctl(fd, GPIO_GET_LINEHANDLE_IOCTL, &req);
if (ret == -1) {
ret = -errno;
fprintf(stderr, "Failed to issue %s (%d), %s\n",
"GPIO_GET_LINEHANDLE_IOCTL", ret, strerror(errno));
}
exit_close_error:
if (close(fd) == -1)
perror("Failed to close GPIO character device file");
free(chrdev_name);
return ret < 0 ? ret : req.fd;
}
功能: 请求GPIO管脚句柄,用于后续操作。
参数:
device_name: GPIO设备名称lines: GPIO管脚偏移数组nlines: 管脚数量flag: 管脚标志(输入/输出等)data: 初始数据(输出模式时使用)consumer_label: 消费者标签
返回值: 成功返回文件描述符,失败返回错误码
4.1.2 gpiotools_set_values()
c
int gpiotools_set_values(const int fd, struct gpiohandle_data *data)
{
int ret;
ret = ioctl(fd, GPIOHANDLE_SET_LINE_VALUES_IOCTL, data);
if (ret == -1) {
ret = -errno;
fprintf(stderr, "Failed to issue %s (%d), %s\n",
"GPIOHANDLE_SET_LINE_VALUES_IOCTL", ret,
strerror(errno));
}
return ret;
}
功能: 设置GPIO管脚的值。
参数:
fd: GPIO管脚句柄文件描述符data: 要设置的值
4.1.3 gpiotools_get_values()
c
int gpiotools_get_values(const int fd, struct gpiohandle_data *data)
{
int ret;
ret = ioctl(fd, GPIOHANDLE_GET_LINE_VALUES_IOCTL, data);
if (ret == -1) {
ret = -errno;
fprintf(stderr, "Failed to issue %s (%d), %s\n",
"GPIOHANDLE_GET_LINE_VALUES_IOCTL", ret,
strerror(errno));
}
return ret;
}
功能: 获取GPIO管脚的值。
参数:
fd: GPIO管脚句柄文件描述符data: 存储获取的值
4.1.4 gpiotools_release_linehandle()
c
int gpiotools_release_linehandle(const int fd)
{
int ret;
ret = close(fd);
if (ret == -1) {
perror("Failed to close GPIO LINEHANDLE device file");
ret = -errno;
}
return ret;
}
功能: 释放GPIO管脚句柄。
参数:
fd: GPIO管脚句柄文件描述符
4.1.5 gpiotools_get()
c
int gpiotools_get(const char *device_name, unsigned int line)
{
struct gpiohandle_data data;
unsigned int lines[] = {line};
gpiotools_gets(device_name, lines, 1, &data);
return data.values[0];
}
功能: 获取单个GPIO管脚的值。
参数:
device_name: GPIO设备名称line: GPIO管脚偏移
4.1.6 gpiotools_set()
c
int gpiotools_set(const char *device_name, unsigned int line,
unsigned int value)
{
struct gpiohandle_data data;
unsigned int lines[] = {line};
data.values[0] = value;
return gpiotools_sets(device_name, lines, 1, &data);
}
功能: 设置单个GPIO管脚的值。
参数:
device_name: GPIO设备名称line: GPIO管脚偏移value: 要设置的值(0或1)
4.1.7 gpiotools_gets()
c
int gpiotools_gets(const char *device_name, unsigned int *lines,
unsigned int nlines, struct gpiohandle_data *data)
{
int fd;
int ret;
int ret_close;
ret = gpiotools_request_linehandle(device_name, lines, nlines,
GPIOHANDLE_REQUEST_INPUT, data,
COMSUMER);
if (ret < 0)
return ret;
fd = ret;
ret = gpiotools_get_values(fd, data);
ret_close = gpiotools_release_linehandle(fd);
return ret < 0 ? ret : ret_close;
}
功能: 获取多个GPIO管脚的值。
参数:
device_name: GPIO设备名称lines: GPIO管脚偏移数组nlines: 管脚数量data: 存储获取的值
4.1.8 gpiotools_sets()
c
int gpiotools_sets(const char *device_name, unsigned int *lines,
unsigned int nlines, struct gpiohandle_data *data)
{
int ret;
ret = gpiotools_request_linehandle(device_name, lines, nlines,
GPIOHANDLE_REQUEST_OUTPUT, data,
COMSUMER);
if (ret < 0)
return ret;
return gpiotools_release_linehandle(ret);
}
功能: 设置多个GPIO管脚的值。
参数:
device_name: GPIO设备名称lines: GPIO管脚偏移数组nlines: 管脚数量data: 要设置的值
5. gpio-utils.h - 共享头文件
定义了共享实用函数库的接口,包括:
- 常量定义
- 结构体声明
- 函数原型
6. 构建系统
6.1 Makefile文件分析
以下是完整的Makefile内容及详细分析:
makefile
# SPDX-License-Identifier: GPL-2.0 # 许可证声明
include ../scripts/Makefile.include # 包含通用脚本
bindir ?= /usr/bin # 默认安装目录
# 自动检测内核源代码树根目录
ifndef building_out_of_srctree
# 递归向上查找内核源代码树
srctree := $(patsubst %/,%,$(dir $(CURDIR)))
srctree := $(patsubst %/,%,$(dir $(srctree)))
endif
# 禁用make内置规则,提高构建性能并避免意外行为
MAKEFLAGS += -r
# 编译选项配置
override CFLAGS += -O2 -Wall -g -D_GNU_SOURCE -I$(OUTPUT)include
# -O2: 优化级别
# -Wall: 开启所有警告
# -g: 包含调试信息
# -D_GNU_SOURCE: 启用GNU扩展
# -I$(OUTPUT)include: 包含构建目录头文件
# 定义构建目标
ALL_TARGETS := lsgpio gpio-hammer gpio-event-mon
ALL_PROGRAMS := $(patsubst %,$(OUTPUT)%,$(ALL_TARGETS))
# 默认目标:构建所有工具
all: $(ALL_PROGRAMS)
# 导出构建环境变量
export srctree OUTPUT CC LD CFLAGS
# 包含内核工具构建框架
include $(srctree)/tools/build/Makefile.include
# 头文件准备:创建符号链接到内核头文件
$(OUTPUT)include/linux/gpio.h: ../../include/uapi/linux/gpio.h
mkdir -p $(OUTPUT)include/linux 2>&1 || true # 创建目录,忽略错误
ln -sf $(CURDIR)/../../include/uapi/linux/gpio.h $@ # 创建符号链接
# 准备目标:确保头文件存在
prepare: $(OUTPUT)include/linux/gpio.h
# 构建gpio-utils共享库
GPIO_UTILS_IN := $(OUTPUT)gpio-utils-in.o
$(GPIO_UTILS_IN): prepare FORCE
$(Q)$(MAKE) $(build)=gpio-utils # 子目录构建
# 构建lsgpio工具
LSGPIO_IN := $(OUTPUT)lsgpio-in.o
$(LSGPIO_IN): prepare FORCE $(OUTPUT)gpio-utils-in.o
$(Q)$(MAKE) $(build)=lsgpio
$(OUTPUT)lsgpio: $(LSGPIO_IN)
$(QUIET_LINK)$(CC) $(CFLAGS) $(LDFLAGS) $< -o $@ # 链接可执行文件
# 构建gpio-hammer工具
GPIO_HAMMER_IN := $(OUTPUT)gpio-hammer-in.o
$(GPIO_HAMMER_IN): prepare FORCE $(OUTPUT)gpio-utils-in.o
$(Q)$(MAKE) $(build)=gpio-hammer
$(OUTPUT)gpio-hammer: $(GPIO_HAMMER_IN)
$(QUIET_LINK)$(CC) $(CFLAGS) $(LDFLAGS) $< -o $@
# 构建gpio-event-mon工具
GPIO_EVENT_MON_IN := $(OUTPUT)gpio-event-mon-in.o
$(GPIO_EVENT_MON_IN): prepare FORCE $(OUTPUT)gpio-utils-in.o
$(Q)$(MAKE) $(build)=gpio-event-mon
$(OUTPUT)gpio-event-mon: $(GPIO_EVENT_MON_IN)
$(QUIET_LINK)$(CC) $(CFLAGS) $(LDFLAGS) $< -o $@
# 清理目标:删除构建产物
clean:
rm -f $(ALL_PROGRAMS) # 删除可执行文件
rm -f $(OUTPUT)include/linux/gpio.h # 删除头文件链接
# 删除所有.o文件和.d依赖文件
find $(if $(OUTPUT),$(OUTPUT),.) -name '*.o' -delete -o -name '\.*.d' -delete
# 安装目标:将工具安装到系统目录
install: $(ALL_PROGRAMS)
install -d -m 755 $(DESTDIR)$(bindir); # 创建安装目录
for program in $(ALL_PROGRAMS); do # 安装每个工具
install $$program $(DESTDIR)$(bindir);
done
# 强制目标:确保每次都执行
FORCE:
# 伪目标声明
.PHONY: all install clean FORCE prepare
6.2 主要目标
bash
make all # 构建所有三个工具
make install # 将工具安装到/usr/bin
make clean # 清理构建产物
6.3 生成的工具
- lsgpio: 列出GPIO信息
- gpio-hammer: 闪烁GPIO管脚
- gpio-event-mon: 监控GPIO事件
7. 总结
这些GPIO工具提供了从用户空间操作GPIO管脚的便捷方式,展示了Linux GPIO字符设备接口的使用方法。工具之间通过共享实用函数库实现代码复用,提高了可维护性。
每个工具都有明确的职责:
- lsgpio: 用于查看GPIO系统状态
- gpio-hammer: 用于测试GPIO输出功能
- gpio-event-mon: 用于监控GPIO输入事件
这些工具对于调试和测试GPIO功能非常有用,特别是在嵌入式系统开发中。