GPIO编程之LED灯设备控制
sysfs方式控制gpio简介
我们igkboard开发板支持sysfs方式控制gpio的电平行为,下面简单介绍一下sysfs方式控制。
首先查看/sys/class/gpio文件夹,如存在该文件夹说明,系统支持sysfs方式控制gpio
- 关于sysfs是什么?
sysfs 是最初基于 ramfs 的基于 ram 的文件系统。它提供了一种将内核数据结构、它们的属性以及它们之间的链接导出到用户空间的方法。 可以理解为驱动程序将一些驱动设备在内核程序的属性,通过sysfs的方式,导出到用户空间,最终以文本文件的方式显示。上一节ds18b20的驱动正是此方式,后面驱动编程时候,我们可以利用该机制个性化将我们自己的去驱动程序的属性导出到用户空间。
- 下面我们简单介绍/sys/class/gpio中文件的作用:
- /sys/class/gpio/export文件用于通知系统需要导出控制的GPIO引脚编号
- /sys/class/gpio/unexport 用于通知系统注销已导出的GPIO
- /sys/class/gpio/gpiochipX目录保存系统中GPIO寄存器的信息,包括每个寄存器控制引脚的起始编号base,寄存器名称,引脚总数 导出一个引脚的操作步骤
sysfs中gpio编号计算方法
- 查看igkboard开发板扩展的gpio管脚
该40pin扩展图中,已经标好每个gpio对应的/sys/class/gpio 导出时候的编号。但是再此还是需要知道是如何计算的,计算公式如下
假设需要导出的gpio是GPIO0X_IOY
计算其编号为 NUM = (X - 1) * 32 + Y
示例 :GPIO01_IO29 GPIO05_IO09
NUM_GPIO01_IO29 = 0 + 29 = 29
NUM_GPIO05_IO09 = 4 * 32 + 9 = 137
sysfs常用接口使用
下面将GPIO05_IO09作为操作对象,进行示例测试。
- 导出GPIO05_IO09到用户空间
可以看到/sys/class/gpio文件夹下增加了gpio137文件夹,查看该文件夹。下面讲解三个常用属性接口 active_low、direction、value - direction:gpio的输入输出属性,可以为in或out。
- active_low:gpio的有效电平为低使能属性,可以为1或0(一般为0)。active_low为0时,高电平为有效电平,value为1时,gpio电平为高电平,0时为低电平;active_low为1时,低电平为有效电平,value为1时,gpio电平为低电平,0时为高电平。为了符合软件编程习惯,一般设置active_low=0。
- value:gpio的电平值,实际电平高低和有效电平属性相关。可以为1或0。
libgpiod库简介
libgpiod是用于与linux GPIO交互的C库和工具,从 linux 4.8 后,官方不推荐使用 GPIO sysfs 接口,libgpiod库封装了 ioctl 调用和简单的API接口。以下是libgpiod的仓库https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/。 与sysfs方式相比,libgpiod可以保证所有分配的资源,在关闭文件描述符后得到完全释放,并且拥有sysfs方式接口中不存在的功能(如时间轮询,一次设置/读取多个gpio值)。此外libgpiod还包含一组命令行工具,允许用户使用脚本对gpio进行个性化操作。
当前igkboard开发板的linux内核支持libgpiod的库使用,登录开发板后在/usr/lib文件夹之中查看的该动态库的存在,查看gpiodetect工具版本。
note:系统libgpiod库的版本为v2.0,而本文所使用的函数接口名为v1.63版本。
版本的差异具体可以参考gpiod.h文件。
故系统中没有v1.63版本的动态库,所以本文使用静态库进行编译。
gpiod命令行工具
目前有六个命令行工具可用
- gpiodetect - 列出系统上存在的所有 gpiochips,它们的名称、标签和 GPIO管脚数量。
- gpioinfo - 列出指定 gpio 的所属chip、它们的名称、被使用者名字、方向、激活状态和附加标志。
- gpioget<gpiochip_name + OFFSET> - 读取指定 GPIO 的值。
- gpioset<gpiochip_name + gpio_line_number> - 设置指定 GPIO 的值。
- gpiofind<gpio line number?> - 获取 gpiochip 名称和给定行名称的行偏移。
- gpiomon - 监听 GPIO 上的特定事件。
下面列出几个常用的功能示例,同样以点亮led灯和读取gpio管脚电平作为示例。 - gpiodetec和gpioinfo使用,用于查看信息。
- gpioset设置**GPIO05_IO09(gpiochip4 9)**电平。命令行中可以将gpiochip4使用最后数字编号进行输入,示例如下(note:v2.0版本中指定芯片时需要加上-c了解更多帮助加上-h):
root@igkboard:~# gpioset gpiochip4 -c 8=1 9=1
root@igkboard:~# gpioset gpiochip4 -c 8=1 9=0
root@igkboard:~# gpioset -c 4 8=0 9=1
root@igkboard:~# gpioset -c 4 8=1 9=0
note:GPIO05_IO09(gpiochip4 9)注意命名与编程时的数字对应关系
libgpiod编程相关结构体(v1.63)
代表支持的gpio芯片的相关信息
- gpiod_chip
struct gpiod_chip {
struct gpiod_line **lines; //每个 gpio芯片的gpiod_line 数组地址,每一个gpio口对应一个line
unsigned int num_lines; //该gpio芯片下的gpio线路数量
int fd; //设备描述符,即库中底层使用ioctl打开的gpio芯片设备节点的描述符
char name[32]; //芯片的名称
char label[32]; //芯片的标签
};
- gpiod_line
每个gpio芯片下的gpio口的信息
struct gpiod_line {
unsigned int offset; //gpio 的偏移量,如GPIO05_IO09 偏移 9
int direction; //gpio的方向
bool active_low; //是否是低电平有效,前面介绍过此属性
int output_value; //最后写入 GPIO 的逻辑值
__u32 info_flags;
__u32 req_flags;
int state; //和事件相关的一个状态值
struct gpiod_chip *chip;//所属芯片的地址
struct line_fd_handle *fd_handle;
char name[32]; //名字,编程时候可以给使用的gpio赋予名字
char consumer[32]; //使用者名字
};
交叉编译安装libgpiod库
下载交叉编译工具链
Linaro 是一个致力于 ARM 架构开源软件的组织。他们提供用于交叉编译基于 ARM 架构的应用程序的工具链。
访问 Linaro 网站:
https://www.linaro.org/downloads/
选择工具链:
选择适用于你需求的工具链。查找与你的目标架构相对应的版本(ARMv7 硬浮点使用 arm-linux-gnueabihf)。
下载工具链:
下载工具链包到你的开发机。
解压工具链:
将下载的存档文件解压到你的机器上的一个目录。
本文中的目录
安装libgpiod库
-
下载libgpiod解压
由上文链接下载1.6.3版本 -
添加环境变量
vim setenv.sh
- 使之生效
source sentenv.sh
- 依次运行:
./autogen.sh --enable-tools=yes --host=arm-linux-gnueabihf
./configure --enable-tools=yes --host=arm-linux-gnueabihf -- --prefix=${自己的项目路径}
make
make install
解释:在一个项目的 autogen.sh 脚本中,–host 是一个用于指定目标体系结构(target architecture)的配置选项。这个选项通常在使用 Autotools 工具链(如 Autoconf、Automake 和 Libtool)生成配置脚本时被指定。
在 Autotools 中,有三个相关的配置选项,它们分别是:
–host: 用于指定生成的程序将在哪个体系结构上运行。这是交叉编译时非常重要的一个选项。例如,如果你在 x86 架构的系统上编译一个要在 ARM 架构上运行的程序,你可以使用 --host=arm-linux。
–build: 用于指定进行编译的系统的体系结构。如果省略该选项,Autotools 将尝试自动检测。
–target: 用于指定生成的程序所生成的代码将运行在哪个体系结构上。通常情况下,–target 与 --host 相同,除非你在进行交叉编译。
这些选项的使用可以使 Autotools 在不同的体系结构上生成正确的配置和构建文件,以确保生成的程序能够在指定的目标体系结构上运行。
例如,如果你的 autogen.sh 文件包含 --host=arm-linux,则表示你希望生成的程序能够在 ARM 架构上运行。这在交叉编译的情况下是很常见的。
- note:如果出现报错:undefined reference to rpl_malloc
打开configure文件注释掉下图的一行
vim configure
- 安装成功后在项目中看到此三个文件夹:
libgpiod常用函数解析
- 获取需要的gpio芯片
struct gpiod_chip *gpiod_chip_open(const char *path);
功能描述:根据gpiochip路径打开需要的chip 参数解析:path:要打开的 gpiochip 的路径 返回值:成功返回GPIO 芯片句柄,失败则返回 NULL 。
- 获取需要的gpio口
struct gpiod_line* gpiod_chip_get_line(struct gpiod_chip* chip,uint offset);
功能描述:获取给定偏移量值 GPIO 句柄 参数解析:chip:GPIO 芯片句柄 offset:GPIO 偏移量 返回值:成功返回GPIO 句柄,失败则返回 NULL。
- 设置gpio为输出方向并且初始化逻辑值
int gpiod_line_request_output(struct gpiod_line* line,const(char)* consumer,int default_val);
功能描述:设置输出方向并且初始化逻辑值 参数解析: line:GPIO 句柄 consumer:使用者的名称 default_val:初始值 返回值:成功返回0,失败则返回-1 。
- 设置gpio的逻辑值
int gpiod_line_set_value(struct gpiod_line* line,int value);
功能描述:设置单个 GPIO 值 参数解析:line:GPIO 句柄 value:设定的值 返回值:成功返回0,失败则返回-1 。
- 设置gpio为输入方向
int gpiod_line_request_input(struct gpiod_line *line, const char *consumer);
功能描述:设置gpio为输入方向 参数解析: line:GPIO 句柄 consumer:使用者的名称 返回值:成功返回0,失败则返回-1。
- 读取gpio的逻辑值
int gpiod_line_get_value(struct gpiod_line *line);
功能描述:读取单个 GPIO 逻辑 参数解析:line:GPIO 句柄 返回值:成功返回0或1(即逻辑值),失败则返回-1。
- 关闭gpio芯片句柄
void gpiod_chip_close(struct gpiod_chip* chip);
功能描述:关闭 GPIO 芯片句柄并释放所有分配的资源 参数解析:chip: GPIO 芯片句柄。
- 释放gpio口
void gpiod_line_release(struct gpiod_line* line);
功能描述:释放gpio口 参数解析:line:GPIO 句柄。
程序设计
函数封装设计
- 查看开发板dev下的gpiochip节点,有五组GPIO,分别对应GPIO1_XX~GPIO5_XX
root@igkboard:~# ls /dev/gpiochip*
/dev/gpiochip0 /dev/gpiochip1 /dev/gpiochip2 /dev/gpiochip3 /dev/gpiochip4
示例:现需要对GPIO05_IO09 进行电平控制 易知需选择的chip是gpiochip4 所需要的偏移是9
- 对gpio的初始化进行如下函数封装
typedef struct gpiod_led_s
{
struct gpiod_chip *chip;/* gpio 芯片*/
struct gpiod_line *line;/* gpio控制口*/
}gpiod_led_t;
/* led 初始化函数*/
int led_init(gpiod_led_t *gpiod_led, unsigned char gpio_chip_num, unsigned char gpio_off_num)
{
char dev_name[16];
if(gpio_chip_num == 0 || gpio_chip_num > 5 || gpio_off_num == 0 || gpio_off_num > 32 || !gpiod_led )
{
printf("[INFO] %s argument error.\n", __FUNCTION__);
return -1;
}
memset(dev_name, 0, sizeof(dev_name));
snprintf(dev_name, sizeof(dev_name), "/dev/gpiochip%d", gpio_chip_num-1);
if(!(gpiod_led->chip = gpiod_chip_open(dev_name)))
{
printf("fail to open chip0\n");
return -2;
}
if(!(gpiod_led->line = gpiod_chip_get_line(gpiod_led->chip, gpio_off_num)))
{
printf("fail to get line_led\n");
return -3;
}
/* 设置初始值为灭 */
if(gpiod_line_request_output(gpiod_led->line, "led_out", OFF) < 0)
{
printf("fail to request line_led for output mode\n");
return -4;
}
return 0;
}
led控制与释放函数的封装
/*led控制函数*/
int led_control(gpiod_led_t *gpiod_led, int status)
{
int level;
if( !gpiod_led )
{
printf("[INFO] %s argument error.\n", __FUNCTION__);
return -1;
}
level = (status==ON) ? ON: OFF; //纠正status参数
if(gpiod_line_set_value(gpiod_led->line, level) < 0)
{
printf("fail to set line_led value\n");
return -2;
}
return 0;
}
完整代码
/*********************************************************************************
* Copyright: (C) 2024 Chenyujiang<2631336290@qq.com>
* All rights reserved.
*
* Filename: gpio_led_test.c
* Description: This file used to test tricolor light blink
*
* Version: 1.0.0(2024年01月28日)
* Author: Chenyujiang <2631336290@qq.com>
* ChangeLog: 1, Release initial version on "2024年01月28日 16时39分22秒"
*
********************************************************************************/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <gpiod.h> //链接时加上 -lgpio
#define OFF 0
#define ON 1
typedef struct gpiod_led_s
{
struct gpiod_chip *chip;
struct gpiod_line *line;
}gpiod_led_t;
enum LED_INDEX
{
LED_RED = 0,
LED_GREEN,
LED_BLUE,
LED_MAX,
};
int led_init(gpiod_led_t *gpiod_led, unsigned char gpio_chip_num, unsigned char gpio_off_num);
int led_control(gpiod_led_t *gpiod_led, int status);
static inline void msleep(unsigned long ms);
void blink_led(gpiod_led_t *gpiod_led, unsigned int interval);
int led_release(gpiod_led_t *gpiod_led);
int main(int argc,char *argv[])
{
gpiod_led_t led[LED_MAX];
if(led_init(&led[LED_RED],1,10) < 0)
{
printf("LED_RED init error.\n");
return -1;
}
if(led_init(&led[LED_GREEN],1,11) < 0)
{
printf("LED_GREEN init error.\n");
return -2;
}
if(led_init(&led[LED_BLUE],5,9) < 0)
{
printf("LED_BLUE init error.\n");
return -3;
}
while(1)
{
blink_led(&led[LED_RED],500);
blink_led(&led[LED_GREEN],500);
blink_led(&led[LED_BLUE],500);
}
led_release(&led[LED_RED]);
led_release(&led[LED_GREEN]);
led_release(&led[LED_BLUE]);
return 0;
}
int led_init(gpiod_led_t *gpiod_led, unsigned char gpio_chip_num, unsigned char gpio_off_num)
{
char dev_name[16];
if(gpio_chip_num == 0 || gpio_chip_num > 5 || gpio_off_num == 0 || gpio_off_num > 32 || !gpiod_led )
{
printf("[INFO] %s argument error.\n", __FUNCTION__);
return -1;
}
memset(dev_name, 0, sizeof(dev_name));
snprintf(dev_name, sizeof(dev_name), "/dev/gpiochip%d", gpio_chip_num-1);
if(!(gpiod_led->chip = gpiod_chip_open(dev_name)))
{
printf("fail to open chip%d.\n",gpio_chip_num);
return -2;
}
if(!(gpiod_led->line = gpiod_chip_get_line(gpiod_led->chip, gpio_off_num)))
{
printf("fail to get line_led.\n");
return -3;
}
/* 设置初始值为灭 */
if(gpiod_line_request_output(gpiod_led->line, "led_out", OFF) < 0)
{
printf("fail to request line_led for output mode.\n");
return -4;
}
return 0;
}
int led_control(gpiod_led_t *gpiod_led, int status)
{
int level;
if(!gpiod_led)
{
printf("[INFO] %s argument error.\n",__FUNCTION__);
return -1;
}
level = (status == ON) ? ON : OFF;
if(gpiod_line_set_value(gpiod_led->line,level) < 0)
{
printf("fail to set line_led value.\n");
return -2;
}
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 * 1000;
cSleep.tv_nsec = ulTmp * 1000;
}
else
{
cSleep.tv_nsec = 0;
}
nanosleep(&cSleep,0);
}
void blink_led(gpiod_led_t *gpiod_led, unsigned int interval)
{
led_control(gpiod_led,ON);
msleep(interval);
led_control(gpiod_led,OFF);
msleep(interval);
}
int led_release(gpiod_led_t *gpiod_led)
{
if(!gpiod_led)
{
printf("[INFO] %s argument error.\n", __FUNCTION__);
return -1;
}
gpiod_line_release(gpiod_led->line);
gpiod_chip_close(gpiod_led->chip);
return 0;
}
编写makefile如下
使用静态编译是因为开发板上的libgpiod库不匹配。
现在我们在开发板上通过 tftp 命令 或其它方式将编译生成的测试程序下载到开发板上。
将可执行文件传输到开发板上后,利用chmod命令增加执行权限
root@igkboard:~# chmod +x gpiod_led_test
root@igkboard:~# ./gpiod_led_test