• 新唐NUC980使用记录:在用户应用中使用GPIO


    目的

    GPIO是最基础的外设,使用频率也非常高,这篇文章将简单体验在NUC980 Liunx用户应用中使用GPIO功能。

    这篇文章中内容均在下面的开发板上进行测试:
    《新唐NUC980使用记录:自制开发板(基于NUC980DK61YC)》

    开发板中提供了两组共四个直连到GPIO口上的轻触按钮和发光二极管,可以方便地进行GPIO功能测试:
    在这里插入图片描述

    这篇文章主要是在下面文章基础上进行的:
    《新唐NUC980使用记录:访问以太网(LAN8720A) & 启用SSH》

    基础说明

    默认情况下NUC980官方的内核是配置启用了sysfs文件系统GPIO支持的:
    在这里插入图片描述
    所以可以通过读写 /sys/class/gpio/ 目录下指定GPIO口编号的文件来操作GPIO口。GPIO口编号换算如下:
    PB13 = 32 x 1(PA) + 13 = 45
    PF10 = 32 x 5(PA/PB/PC/PD/PE) + 10 = 170
    PE10 = 32 x 4(PA/PB/PC/PD) + 10 = 138
    PE12 = 32 x 4(PA/PB/PC/PD) + 12 = 140

    需要注意的是默认情况下PE10和PE12是被设置成USB相关功能的,需要修改内核进行调整:
    在这里插入图片描述

    在终端中操作

    基于上面内容我们就可以直接在终端中操作GPIO口了:

    # 导出以使用GPIO45
    echo 45 > /sys/class/gpio/export
    # 导出后将在 /sys/class/gpio/ 目录下出现 gpio45 目录,读写其中的文件即可操作该GPIO口
    
    # 将GPIO45设置为输出模式
    echo out > /sys/class/gpio/gpio45/direction
    # 将GPIO45设置为输出高电平
    echo 1 > /sys/class/gpio/gpio45/value
    # 将GPIO45设置为输出低电平
    echo 0 > /sys/class/gpio/gpio45/value
    
    # ====================
    
    # 导出以使用GPIO140
    echo 140 > /sys/class/gpio/export
    # 将GPIO140设置为输入模式
    echo in > /sys/class/gpio/gpio140/direction
    # 打印GPIO140端口电平
    cat /sys/class/gpio/gpio140/value
    
    # ====================
    
    # 取消使用GPIO45
    echo 45 > /sys/class/gpio/unexport
    # 取消使用GPIO45
    echo 140 > /sys/class/gpio/unexport
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    在这里插入图片描述
    上面演示中按钮按下和松开时可以读取到不同的电平值。

    使用程序操作

    除了在终端中使用,也可以通过程序进行操作:

    cd ~/nuc980-sdk/
    mkdir -p apps/gpio
    cd apps/gpio/
    gedit main.c
    
    • 1
    • 2
    • 3
    • 4

    在 main.c 文件中写入下面代码:

    #include 
    #include 
    
    int main(void)
    {
        int i;
    
        system("echo 45 > /sys/class/gpio/export");
        system("echo out > /sys/class/gpio/gpio45/direction");
    
        for (i = 0; i < 8; i++)
        {
            system("echo 0 > /sys/class/gpio/gpio45/value");
            sleep(1);
            system("echo 1 > /sys/class/gpio/gpio45/value");
            sleep(1);
        }
    
        system("echo 45 > /sys/class/gpio/unexport");
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    编译生成程序并拷贝到开发板中:

    export PATH=$PATH:/home/nx/nuc980-sdk/buildroot-2022.02.3/output/host/bin
    arm-linux-gcc main.c
    # 开发板启用了SSH的话可以使用SCP命令将程序通过网络拷贝到开发板中
    scp a.out root@192.168.31.142:/root/
    
    • 1
    • 2
    • 3
    • 4

    在开发板上运行程序:

    /root/a.out
    
    • 1

    上面代码也可以使用下面这种传统的文件操作方式:

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    #define OPEN_FILE(fd, file, flag) \
        (fd) = open((file), (flag)); \
        if ((fd) < 0) { printf("open %s failed!\n", (file)); return -1; }
    
    #define WRITE_FILE(fd, str) \
        if(write((fd), (str), strlen(str)) != strlen(str)) \
        { printf("write %s failed!\n", (str)); return -1; }
    
    int main(void)
    {
        int i, fd;
    
        if(access("/sys/class/gpio/gpio45/", F_OK))
        {
            OPEN_FILE(fd, "/sys/class/gpio/export", O_WRONLY);
            WRITE_FILE(fd, "45");
            close(fd);
        }
    
        OPEN_FILE(fd, "/sys/class/gpio/gpio45/direction", O_WRONLY);
        WRITE_FILE(fd, "out");
        close(fd);
    
    	OPEN_FILE(fd, "/sys/class/gpio/gpio45/value", O_RDWR);
        for (i = 0; i < 8; i++)
        {
            lseek(fd, 0, SEEK_SET); // 移动文件操作指针到文件开头
            WRITE_FILE(fd, "0");
            sleep(1);
            lseek(fd, 0, SEEK_SET); // 移动文件操作指针到文件开头
            WRITE_FILE(fd, "1");
            sleep(1);
        }
        close(fd);
    
        OPEN_FILE(fd, "/sys/class/gpio/unexport", O_WRONLY);
        WRITE_FILE(fd, "45");
        close(fd);
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    上面的C语言程序都是输出控制LED的,也可以用程序来读取按键输入电平:

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    #define OPEN_FILE(fd, file, flag) \
        (fd) = open((file), (flag)); \
        if ((fd) < 0) { printf("open %s failed!\n", (file)); return -1; }
    
    #define WRITE_FILE(fd, str) \
        if(write((fd), (str), strlen(str)) != strlen(str)) \
        { printf("write %s failed!\n", (str)); return -1; }
    
    #define READ_FILE(fd, bufptr, size) \
        if (read((fd), bufptr, size) != size) \
        { printf("read failed!\n"); return -1; }
        
    int main(void)
    {
        int i, fd;
        char value[2] = {0};
    
        if(access("/sys/class/gpio/gpio140/", F_OK))
        {
            OPEN_FILE(fd, "/sys/class/gpio/export", O_WRONLY);
            WRITE_FILE(fd, "140");
            close(fd);
        }
    
        OPEN_FILE(fd, "/sys/class/gpio/gpio140/direction", O_WRONLY);
        WRITE_FILE(fd, "in");
        close(fd);
    
        OPEN_FILE(fd, "/sys/class/gpio/gpio140/value", O_RDWR);
        for (i = 0; i < 8; i++)
        {
            lseek(fd, 0, SEEK_SET); // 移动文件操作指针到文件开头
            READ_FILE(fd, value, 1);
            printf("value is %s\n", value);
            sleep(2);
        }
        close(fd);
    
        OPEN_FILE(fd, "/sys/class/gpio/unexport", O_WRONLY);
        WRITE_FILE(fd, "140");
        close(fd);
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    在这里插入图片描述
    在程序运行过程中按下按钮可以看到输出的值改变。

    上面对于输入值获取操作属于轮询方式,也可以使用中断方式来获取(开发板上两个按键的引脚都是有外部中断功能的):

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include  
    
    #define OPEN_FILE(fd, file, flag) \
        (fd) = open((file), (flag)); \
        if ((fd) < 0) { printf("open %s failed!\n", (file)); return -1; }
    
    #define WRITE_FILE(fd, str) \
        if(write((fd), (str), strlen(str)) != strlen(str)) \
        { printf("write %s failed!\n", (str)); return -1; }
    
    #define READ_FILE(fd, bufptr, size) \
        if (read((fd), bufptr, size) != size) \
        { printf("read failed!\n"); return -1; }
        
    int main(void)
    {
        int i, fd, ret;
        char value[2] = {0};
        struct pollfd fds[1];
        nfds_t nfds = 1;
        
    
        if(access("/sys/class/gpio/gpio140/", F_OK))
        {
            OPEN_FILE(fd, "/sys/class/gpio/export", O_WRONLY);
            WRITE_FILE(fd, "140");
            close(fd);
        }
    
        OPEN_FILE(fd, "/sys/class/gpio/gpio140/direction", O_WRONLY);
        WRITE_FILE(fd, "in");
        close(fd);
    
        OPEN_FILE(fd, "/sys/class/gpio/gpio140/edge", O_WRONLY); // edge文件用于设置外部中断触发方式
        // none 无; rising 上升沿触发; falling 下降沿触发(实际测试有点问题); both 双边触发
        WRITE_FILE(fd, "rising"); // 使能下降沿中断
        close(fd);
    
        OPEN_FILE(fd, "/sys/class/gpio/gpio140/value", O_RDWR | O_NONBLOCK);
        READ_FILE(fd, value, 1); // 先读取一次,以免触发第一次不期望的中断
        fds[0].fd = fd;
        fds[0].events = POLLPRI;
        for (i = 0; i < 8; i++)
        {
            ret = poll(fds, nfds, 5000);
            // ret = poll(fds, nfds, -1); // timeout=-1 无限等待
            if (ret > 0)
            {
                // 这里返回的fds[0].revents其实是 POLLERR | POLLPRI
                if (fds[0].revents & POLLPRI)
                {
                    lseek(fd, 0, SEEK_SET); // 移动文件操作指针到文件开头
                    READ_FILE(fd, value, 1);
                    printf("value is %s\n", value);
                }
            }
            else if (ret == 0)
            {
                printf("timeout!\n");
            }
            else
            {
                printf("poll error!\n");
            }
        }
        close(fd);
    
        OPEN_FILE(fd, "/sys/class/gpio/unexport", O_WRONLY);
        WRITE_FILE(fd, "140");
        close(fd);
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79

    在这里插入图片描述

    总结

    GPIO是最基础的外设,使用频率非常高,同时在用户应用中使用GPIO也是比较简单的,基础的使用参考上面这些内容就差不多了。

  • 相关阅读:
    Redis高级应用
    List 模拟实现
    图解超经典的KNN算法
    HTML5 Canvas 数据持久化存储之属性列表
    解密PDF密码
    【Git】Git基本配置和常用命令
    2022年全国职业院校技能大赛:网络系统管理项目-模块B--Windows样题2
    OpenTiny Vue 3.11.0 发布:增加富文本、ColorPicker等4个新组件,迎来了贡献者大爆发!
    【前端】爱心代码
    头条系统-05-延迟队列精准发布文章-概述&添加任务(db和redis实现延迟任务)、取消&拉取任务&定时刷新(redis管道、分布式锁setNx)
  • 原文地址:https://blog.csdn.net/Naisu_kun/article/details/126055923