文章目录

  • 1- 连接示意图
  • 2- GPIO编程之LED灯设备控制
  • (1)关于sysfs是什么?
  • (2)sysfs中gpio编号计算方法
  • (3)sysfs常用接口使用
  • (4)测试让灯亮起来
  • 3- libgpiod库简介
  • (1) gpiod命令行工具
  • 【1】gpiodetect
  • 【2】gpioinfo
  • 【3】gpioset
  • 【4】gpioget
  • (2)libgpiod编程相关结构体
  • 【1】struct gpiod_chip
  • 【2】struct gpiod_line
  • (3)libgpiod常用函数解析
  • 【1】获取需要的芯片函数
  • 【2】获取需要的gpio口函数
  • 【3】设置gpio为输出方向并且初始化逻辑值函数
  • 【4】设置gpio的逻辑值函数
  • 【5】设置gpio为输入方向函数
  • 【6】读取gpio的逻辑值函数
  • 【7】关闭gpio芯片句柄函数
  • 【8】释放gpio口函数
  • 4- 编写编译程序点亮LED灯
  • (1)安装libgpiod库
  • (2)编写程序
  • (3)编译程序
  • (4)下载到开发板运行程序



1- 连接示意图

LED模块连接示意图

libgpio_libgpio


我们使用三色灯与开发板的137(GPIO5_IO09)、10(GPIO1_IO10、11(GPIO1_IO11)、GND连接.

libgpio_libgpio_02


2- GPIO编程之LED灯设备控制

(1)关于sysfs是什么?

sysfs是一个基于ram的内存文件系统(ramfs)。它提供了一种方法用于导出内核数据结构,属性,以及它们两者之间的联系到用户空间。
可以理解为驱动程序将一些驱动设备在内核程序的属性,通过sysfs的方式,导出到用户空间,最终以文本文件的方式显示。

下面我们简单介绍/sys/class/gpio中文件的作用:

  • /sys/class/gpio/export:文件用于通知系统需要导出控制的GPIO引脚编号
  • /sys/class/gpio/unexport: 用于通知系统注销已导出的GPIO
  • /sys/class/gpio/gpiochipX:目录保存系统中GPIO寄存器的信息,包括每个寄存器控制引脚的起始编号base,寄存器名称,引脚总数导出一个引脚的操作步骤

(2)sysfs中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
这里可以看到,如果是GPIO01开头的,那么gpio的编号直接就是IO后面的编号。

(3)sysfs常用接口使用

下面将GPIO05_IO09作为操作对象,进行示例测试。
导出GPIO05_IO09到用户空间:

root@igkboard:~# echo 137 > /sys/class/gpio/export 
root@igkboard:~# cd /sys/class/gpio                   
root@igkboard:/sys/class/gpio# ls
export  gpio137  gpiochip0  gpiochip128  gpiochip32  gpiochip64  gpiochip96  unexport

可以看到/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。

(4)测试让灯亮起来

输如状态读取gpio的电平。(使用杜邦线短接GPIO05_IO09和gnd或3.3V)

root@igkboard:/sys/class/gpio# cd gpio137    
root@igkboard:/sys/class/gpio/gpio137# echo in > direction  
root@igkboard:/sys/class/gpio/gpio137# cat value 
0

我们来测试一下,让gpio137这个管脚的灯亮起来(蓝色的):

root@igkboard:/sys/class/gpio/gpio137# echo out > direction   
root@igkboard:/sys/class/gpio/gpio137# echo 1 > value

可以看见蓝色LED灯亮起来了:

libgpio_c语言_03

注销已导出的gpio:

root@igkboard:/sys/class/gpio# echo 137 > unexport     
root@igkboard:/sys/class/gpio# ls
export  gpiochip0  gpiochip128  gpiochip32  gpiochip64  gpiochip96  unexport

3- libgpiod库简介

libgpiod是用于与linux GPIO交互的C库和工具。字符设备(gpiod代表GPIO设备)
由于linux 4.8,GPIO sysfs接口已被弃用。用户空间应该使用取而代之的是字符设备。这个库封装了ioctl调用和简单API背后的数据结构。
GPIO(General Purpose Input/Output Port)通用输入输出接口

(1) gpiod命令行工具

目前有六个命令行工具可用,实验室树莓派上也可以使用(需要sudo权限)

  • 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 上的特定事件

下面我们分别测试一下命令(管脚GPIO5_IO09蓝色LED灯):

【1】gpiodetect
root@igkboard:~# gpiodetect
gpiochip0 [209c000.gpio] (32 lines)
gpiochip1 [20a0000.gpio] (32 lines)
gpiochip2 [20a4000.gpio] (32 lines)
gpiochip3 [20a8000.gpio] (32 lines)
gpiochip4 [20ac000.gpio] (32 lines)
【2】gpioinfo
root@igkboard:~# gpioinfo
gpiochip0 - 32 lines:
        line   0:      unnamed       unused   input  active-high 
        line   1:      unnamed       unused   input  active-high 
        line   2:      unnamed       unused   input  active-high 
        line   3:      unnamed       unused   input  active-high 
        line   4:      unnamed       unused   input  active-high 
        line   5:      unnamed       unused   input  active-high 
        line   6:      unnamed       unused   input  active-high 
        line   7:      unnamed       unused   input  active-high 
        line   8:      unnamed       unused  output  active-high 
        line   9:      unnamed "regulator-sd1-vmmc" output active-high [used]
        line  10:      unnamed       unused   input  active-high 
        line  11:      unnamed      "sysfs"  output  active-high [used]
        line  12:      unnamed       unused   input  active-high 
        line  13:      unnamed       unused   input  active-high 
        line  14:      unnamed       unused   input  active-high 
        line  15:      unnamed       unused   input  active-high 
        line  16:      unnamed       unused   input  active-high 
        line  17:      unnamed       unused   input  active-high 
        line  18:      unnamed         "w1"  output  active-high [used open-drain]
        line  19:      unnamed         "cd"   input   active-low [used]
        line  20:      unnamed       unused   input  active-high 
        line  21:      unnamed       unused   input  active-high 
        line  22:      unnamed       unused   input  active-high 
        line  23:      unnamed       unused   input  active-high 
        line  24:      unnamed       unused   input  active-high 
        line  25:      unnamed       unused   input  active-high 
        line  26:      unnamed       unused   input  active-high 
        line  27:      unnamed       unused   input  active-high 
        line  28:      unnamed       unused   input  active-high 
        line  29:      unnamed       unused   input  active-high 
        line  30:      unnamed       unused   input  active-high 
        line  31:      unnamed       unused   input  active-high 
        ......
        太多就省略了
【3】gpioset

gpioset设置GPIO05_IO09(gpiochip4 9)电平。命令行中可以将gpiochip4使用最后数字编号进行输入。
可以看见蓝灯亮,第二个命令蓝灯灭(管脚GPIO5_IO09蓝色LED灯)。

root@igkboard:~# gpioset gpiochip4 9=1     
root@igkboard:~# gpioset gpiochip4 9=0
【4】gpioget

gpioget获取GPIO05_IO09的电平值。使用杜邦线短接VCC3.3或GND进行实际电平控制。

root@igkboard:~# gpioget gpiochip4 9
1
root@igkboard:~# gpioset gpiochip4 9=0 
root@igkboard:~# gpioget gpiochip4 9   
0

(2)libgpiod编程相关结构体

具体可以参考一下这篇文章

【1】struct 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];    //芯片的标签
};
【2】struct gpiod_line
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];	//使用者名字
};

(3)libgpiod常用函数解析

【1】获取需要的芯片函数
struct gpiod_chip *gpiod_chip_open(const char *path);
  • 功能描述:根据gpiochip路径打开需要的chip
  • 参数解析:path:要打开的 gpiochip 的路径
  • 返回值:成功返回GPIO 芯片句柄,失败则返回 NULL
【2】获取需要的gpio口函数
struct gpiod_line* gpiod_chip_get_line(struct gpiod_chip* chip,uint offset);
  • 功能描述:获取给定偏移量值GPIO句柄
  • 参数解析:chip:GPIO芯片句柄
  • offset:GPIO 偏移量
  • 返回值:成功返回GPIO 句柄,失败则返回 NULL
【3】设置gpio为输出方向并且初始化逻辑值函数
int gpiod_line_request_output(struct gpiod_line* line,const(char)* consumer,int default_val);
  • 功能描述:设置输出方向并且初始化逻辑值
  • 参数解析:line:GPIO句柄
  • consumer:使用者的名称
  • default_val:初始值
  • 返回值:成功返回0,失败则返回-1
【4】设置gpio的逻辑值函数
int gpiod_line_set_value(struct gpiod_line* line,int value);
  • 功能描述:设置单个 GPIO 值
  • 参数解析:line:GPIO句柄
  • value:设定的值
  • 返回值:成功返回0,失败则返回-1
【5】设置gpio为输入方向函数
int gpiod_line_request_input(struct gpiod_line *line, const char *consumer);
  • 功能描述:设置gpio为输入方向
  • 参数解析: line:GPIO 句柄
  • consumer:使用者的名称
  • 返回值:成功返回0,失败则返回-1
【6】读取gpio的逻辑值函数
int gpiod_line_get_value(struct gpiod_line *line);
  • 功能描述:读取单个 GPIO 逻辑
  • 参数解析:line:GPIO 句柄
  • 返回值:成功返回0或1(即逻辑值),失败则返回-1
【7】关闭gpio芯片句柄函数
void gpiod_chip_close(struct gpiod_chip*  chip);
  • 功能描述:关闭 GPIO 芯片句柄并释放所有分配的资源
  • 参数解析:chip: GPIO 芯片句柄
【8】释放gpio口函数
void gpiod_line_release(struct gpiod_line*  line);
  • 功能描述:释放gpio口
  • 参数解析:line:GPIO 句柄

4- 编写编译程序点亮LED灯

(1)安装libgpiod库

首先我们需要安装libgpiod库及头文件。

#安装libgpiod库及头文件
sudo apt install  libgpiod-dev vim

查看我们安装的库:

dpkg -L libgpiod-dev
wangdengtao@wangdengtao-virtual-machine:~$  dpkg -L libgpiod-dev
/.
/usr
/usr/include
/usr/include/gpiod.h
/usr/include/gpiod.hpp
/usr/lib
/usr/lib/x86_64-linux-gnu
/usr/lib/x86_64-linux-gnu/libgpiod.a
/usr/lib/x86_64-linux-gnu/libgpiodcxx.a
/usr/lib/x86_64-linux-gnu/pkgconfig
/usr/lib/x86_64-linux-gnu/pkgconfig/libgpiod.pc
/usr/lib/x86_64-linux-gnu/pkgconfig/libgpiodcxx.pc
/usr/share
/usr/share/doc
/usr/share/doc/libgpiod-dev
/usr/share/doc/libgpiod-dev/README.gz
/usr/share/doc/libgpiod-dev/copyright
/usr/lib/x86_64-linux-gnu/libgpiod.so
/usr/lib/x86_64-linux-gnu/libgpiodcxx.so
/usr/share/doc/libgpiod-dev/changelog.Debian.gz

(2)编写程序

查看开发板dev下的gpiochip节点,有五组GPIO,分别对应GPIO1_XX~GPIO5_XX。我们的操作其实就是控制每个管脚的电平,就能够控制led的亮灭。

root@igkboard:~# ls /dev/gpiochip*
/dev/gpiochip0  /dev/gpiochip1  /dev/gpiochip2  /dev/gpiochip3  /dev/gpiochip4

完整代码中ms级函数的实现涉及到了纳秒级函数的使用,可以参考这篇文章:C语言的sleep、usleep、nanosleep等休眠函数的了解与用法 完整代码:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <gpiod.h> //gpiod 头文件支持,链接时候加上 -lgpiod

#define OFF         0
#define ON          1

typedef struct gpiod_led_s
{
    struct          gpiod_chip *chip;/* gpio 芯片*/
    struct          gpiod_line *line;/* gpio控制口*/
}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_BLUE], 5, 9) < 0)
    {
        printf("LED_BLUE init error.\n");
        return -1;
    }
    if(led_init(&led[LED_RED], 1, 11) < 0)
    {
        printf("LED_RED init error.\n");
        return -1;
    }
    if(led_init(&led[LED_GREEN], 1, 10) < 0)
    {
        printf("LED_GREEN init error.\n");
        return -1;
    }

    while(1)
    {
        blink_led(&led[LED_BLUE], 500);
        blink_led(&led[LED_RED], 500);
        blink_led(&led[LED_GREEN], 500);
    }
    led_release(&led[LED_BLUE]);
    led_release(&led[LED_RED]);
    led_release(&led[LED_GREEN]);

    return 0;
}

/* 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;
}

/* ms级休眠函数 */
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);
}

/*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;
}

/*led闪烁函数*/
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);    
}

/* led gpiod释放和关闭函数*/
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;
}

(3)编译程序

如果我们直接编译,会出现如下报错的情况:

arm-linux-gnueabihf-gcc gpiod_led.c -o gpiod_led -lgpiod
file not recognized: file format not recognized

我们前面可以用dpkg -L libgpiod-dev查看我们安装的库,其实从命名我们就可以看出这是x86的编译器。我们去动态库的文件夹file一下动态库,看得出来是x86-64的,而我要编译的是arm版本的,所以这就是原因所在。
我认为我们在自己的linux下安装的,那就默认是x86的了,最好还是在自己的开发板上装吧。

wangdengtao@wangdengtao-virtual-machine:/usr/lib/x86_64-linux-gnu$ file libgpiod.so.2.2.2
libgpiod.so.2.2.2: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=94134e18e9ca50d0e6873daafe7739822443f1af, stripped

下面来解决一下错误。
这是已经弄好整理出来的动态库静态库和头文件,其实我们只需要动态库就可以啦。
Gitee下载动态库libgpiod.so 执行命令下载:git clone https://gitee.com/Embedfire-imx6/ebf_6ull_quick_start_code.git 在当前文件就可以看见了:

wangdengtao@wangdengtao-virtual-machine:~/ebf_6ull_quick_start_code/Source/libgpio_beep/libgpiod$ ls
gpiod.h  libgpiod.a  libgpiod.so

进入文件夹将libgpiod.so文件拷贝到/usr/lib/下就可以了。

cp libgpiod.so /usr/lib/

然后再执行命令:arm-linux-gnueabihf-gcc gpiod_led.c -o gpiod_led -lgpiod 查看文件格式:

wangdengtao@wangdengtao-virtual-machine:~/wangdengtao/tftpboot$ file gpiod_led
gpiod_led: ELF 32-bit LSB pie executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, BuildID[sha1]=9b071bc7680878cfe77a74050cf4ffaa6cfb004a, for GNU/Linux 3.2.0, not stripped

没问题哦。

其实说实话直接将下载下来的头文件拷贝到/usr/include/文件夹下,以及将动态库拷贝到/usr/lib/文件夹下就可以了。

(4)下载到开发板运行程序

tftp服务器不会搭建的或者不懂的可以参考这篇文章:wpa_supplicant无线网络配置imx6ull以及搭建tftp服务器

root@igkboard:~# tftp -gr gpiod_led 192.168.1.8
root@igkboard:~# ls    
ds18b20  gpiod_led
root@igkboard:~#  sudo chmod 777 gpiod_led
root@igkboard:~# ls
ds18b20  gpiod_led
root@igkboard:~# ./gpiod_led

你就可以看见自己三色灯模块灯珠按照蓝绿红的顺序依次闪烁,闪烁时间500ms。