Linux学习笔记(应用篇三)

基于I.MX6ULL-MINI开发板

开发板中所有的设备(对象)都会在/sys/devices 体现出来,是 sysfs 文件系统中最重要的目录结构

/sys下的子目录 说明
/sys/devices 这是系统中所有设备存放的目录,也就是系统中的所有设备在 sysfs 中的呈现、表达,也是 sysfs 管理设备的最重要的目录结构。
/sys/block 块设备的存放目录,这是一个过时的接口,按照 sysfs 的设计理念,系统所有的设备都存放在/sys/devices 目录下,所以/sys/block 目录下的文件通常是链接到/sys/devices 目录下的文件。
/sys/bus 这是系统中的所有设备按照总线类型分类放置的目录结构,/sys/devices 目录下每一种设备都是挂在某种总线下的,譬如 i2c 设备挂在 I2C 总线下。同样,/sys/bus 目录下的文件通常也是链接到了/sys/devices 目录。
/sys/class 这是系统中的所有设备按照其功能分类放置的目录结构,同样该目录下的文件也是链接到了/sys/devices 目录。按照设备的功能划分组织在/sys/class 目录下,譬如/sys/class/leds目录中存放了所有的 LED 设备,/sys/class/input 目录中存放了所有的输入类设备。
/sys/dev 这是按照设备号的方式放置的目录结构,同样该目录下的文件也是链接到了/sys/devices 目录。该目录下有很多以主设备号:次设备号(major:minor)命名的文件,这些文件都是链接文件,链接到/sys/devices 目录下对应的设备。
/sys/firmware 描述了内核中的固件。
/sys/fs 用于描述系统中所有文件系统,包括文件系统本身和按文件系统分类存放的已挂载点。
/sys/kernel 这里是内核中所有可调参数的位置。
/sys/module 这里有系统中所有模块的信息。
/sys/power 这里是系统中电源选项,有一些属性可以用于控制整个系统的电源状态。

应用层想要对底层硬件进行操控,通常可以通过两种方式

  1. /dev/目录下的设备文件(设备节点);
  2. /sys/目录下设备的属性文件。

LED学习

led设备路径

bash 复制代码
/sys/class/leds/sys-led

brightness:LED亮度,可读可写,0亮,非0灭

max_brightness:LED最大亮度,只读

trigger:触发模式,可读可写,模式如下:

none(无触发)、mmc0(当对 mmc0 设备发起读写操作的时候 LED 会闪烁)、timer(LED 会有规律的一亮一灭,被定时器控制住)、heartbeat(心跳呼吸模式,LED 模仿人的心跳呼吸那样亮灭变化)。

代码

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#define LED_TRIGGER "/sys/class/leds/sys-led/trigger"
#define LED_BRIGHTNESS "/sys/class/leds/sys-led/brightness"
/*
在 C 语言中,反斜杠 \ 的作用是 行连接符(line continuation character)。
它允许你在多个行中编写一条连续的语句,而不需要写成一行。
具体来说,反斜杠告诉编译器:下一行是当前行的延续,继续合并在一起,而不加上额外的换行符。
*/
#define USAGE()     fprintf(stderr,"usage:\n" \
               "    %s<on|off>\n"   \
               "    %s<trigger><type>\n",argv[0],argv[0])  //argv[0]存放程序的名称或路径


int main(int argc, char *argv[])//argc参数个数,argv[]参数
{
    int fd1, fd2;

    //校验传参
    if(2>argc){
        USAGE();
        return 1;
    }

    // 打开sysfs中的trigger和brightness文件
    fd1 = open(LED_TRIGGER, O_RDWR);
    if (0 > fd1) {
        perror("open LED_TRIGGER error");
        return 1;
    }

    fd2 = open(LED_BRIGHTNESS, O_RDWR);
    if (0 > fd2) {
        perror("open LED_BRIGHTNESS error");
        close(fd1);
        return 1;
    }

    //根据传参控制LED
    if(!strcmp(argv[1],"on")){//判断传入的第一个参数是否是on
        write(fd1,"none",4);//往trigger写入none,共4个字节,trigger设置为无触发
        write(fd2,"1",1);//点亮LED
    }
    else if(!strcmp(argv[1],"off")){//判断传入的第一个参数是否是off
        write(fd1,"none",4);
        write(fd2,"0",1);//熄灭LED
    }
    else if(!strcmp(argv[1],"trigger")){//如果传入的第一个参数是trigger但是参数个数不是三个,提示用法
        if(3!=argc){
            USAGE();
            return 1;
        }
        if(0 > write(fd1,argv[2],strlen(argv[2]))){//判断写入是否正确,将第三个参数即触发模式传给fd1 (none、mmc0、timer、heartbeat)
            perror("write error");
            return 1;
        }
    }
    else
        USAGE();

    return 0;
}

GPIO应用编程

GPIO目录

bash 复制代码
/sys/class/gpio

一共包含了 5 个 GPIO控制器,分别为 GPIO1、GPIO2、GPIO3、GPIO4、GPIO5,在这里分别对应 gpiochip0、gpiochip32、gpiochip64、gpiochip96、gpiochip128 这 5 个文件夹,每一个 gpiochipX 文件夹用来管理一组 GPIO

每一个gpiochipX文件夹里面有以下内容

base:与 gpiochipX 中的 X 相同,表示该控制器所管理的这组 GPIO 引脚中最小的编号,如gpiochip32的base就是32

label:该组 GPIO 对应的标签,也就是名字

ngpio:该控制器所管理的 GPIO 引脚的数量,引脚编号范围是:base ~ base+ngpio-1

GPIO5_IO10在sysfs中对应的编号:128+10=138

export

export:用于将指定编号的 GPIO 引脚导出。在使用 GPIO 引脚之前,需要将其导出,导出成功之后才能使用它。注意 export 文件是只写文件,不能读取,将一个指定的编号写入到 export 文件中即可将对应的 GPIO 引脚导出

如下图,这个文件夹就是导出来的 GPIO 引脚对应的文件夹,用于管理、控制该 GPIO 引脚

unexport

unexport:将导出的 GPIO 引脚删除。当使用完 GPIO 引脚之后,我们需要将导出的引脚删除,同样该文件也是只写文件、不可读

gpioX文件夹里的文件

direction :配置 GPIO 引脚为输入或输出模式。该文件可读、可写,读表示查看 GPIO 当前是输入还是输出模式,写表示将 GPIO 配置为输入或输出模式;读取或写入操作可取的值为"out"(输出模式)和"in"(输入模式)

value :在 GPIO 配置为输出模式下,向 value 文件写入"0"控制 GPIO 引脚输出低电平,写入"1"则控制 GPIO 引脚输出高电平。在输入模式下,读取 value 文件获取 GPIO 引脚当前的输入电平状态。
active_low :这个属性文件用于控制极性,可读可写,默认情况下为 0,如果设置为1,则逻辑1为低电平,逻辑0为高电平
edge :控制中断的触发模式,该文件可读可写。在配置 GPIO 引脚的中断触发模式之前,需将其设置为输入模式
非中断引脚echo "none" > edge
上升沿触发echo "rising" > edge
下降沿触发echo "falling" > edge
边沿触发 echo "both" > edge

输出代码

c 复制代码
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>


static char gpio_path[100];//存放gpio设备文件路径

//修改GPIO的状态
static int gpio_config(const char *attr, const char *value)//attr:修改的选项(direction、active_value、value)value:传入的值
{
    char file_path[100];
    int fd;
    int len;

    sprintf(file_path, "%s/%s", gpio_path, attr);//将/sys/class/gpio/gpiox/(direction或active_value或value)传给file_path
    if(0 > (fd = open(file_path,O_WRONLY)))
    {
        perror("open error");
        return fd;
    }
    len = strlen(value);
    if(len!= write(fd,value,len))//向file_path写入value,即修改gpio的状态
    {
        perror("write error");
        close(fd);
        return 1;
    }
    close(fd);
    return 0;
}

int main(int argc, char *argv[])
{
    //判断传参是否正确
    if(3!=argc)
    {
        fprintf(stderr,"usage: %s<gpio><value>\n", argv[0]);
        return 1;
    }


    //判断gpiox文件是否导出
    sprintf(gpio_path,"/sys/class/gpio/gpio%s",argv[1]);

    if(access(gpio_path,F_OK))//判断路径是否存在,不存在则创建,F_OK参数用于判断文件是否存在
    {
        int fd;
        int len;

        if(0 > (fd = open("/sys/class/gpio/export",O_WRONLY)))//打开export文件并将路径存放在fd,准备写入
        {
            perror("open error");
            return 1;
        }

        len = strlen(argv[1]);

        if(len != write(fd,argv[1],len))//向export写入gpio号,即导出gpio文件
        {
            perror("write error");
            close(fd);
            return 1;
        }

        close(fd);//关闭文件
    }
    //修改gpio的状态
    if(gpio_config("direction","out"))//配置为输出模式
    {
        return 1;
    }

    if(gpio_config("active_low","0"))//默认极性
    {
        return 1;
    }

    if(gpio_config("value",argv[2]))//控制GPIO高低电平
    {
        return 1;
    }

    //退出程序
    return 0;

}

中断输入代码

c 复制代码
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <poll.h>


static char gpio_path[100];//存放gpio设备文件路径

//修改GPIO的状态
static int gpio_config(const char *attr, const char *value)//attr:修改的选项(direction、active_low、value)value:传入的值
{
    char file_path[100];
    int fd;
    int len;

    sprintf(file_path, "%s/%s", gpio_path, attr);//将/sys/class/gpio/gpiox/(direction或active_low或value)传给file_path
    if(0 > (fd = open(file_path,O_WRONLY)))
    {
        perror("open error");
        return fd;
    }

    len = strlen(value);
    if(len!= write(fd,value,len))//向file_path写入value,即修改gpio的状态
    {
        perror("write error");
        close(fd);
        return 1;
    }
    close(fd);
    return 0;
}

int main(int argc, char *argv[])
{
    struct pollfd pfd;//用于poll调用来监视文件描述符的事件,记得包含头文件poll.h
    char file_path[100];//存储/sys/class/gpio/gpiox/value的路径
    int ret;            //poll函数的返回值
    char value;         //从GPIO引脚读取的值

    //判断传参是否正确
    if(2!=argc)
    {
        fprintf(stderr,"usage: %s<gpio> <value>\n", argv[0]);
        return 1;
    }


    //判断gpiox文件是否导出
    sprintf(gpio_path,"/sys/class/gpio/gpio%s",argv[1]);

    if(access(gpio_path,F_OK))//判断路径是否存在,不存在则创建,F_OK参数用于判断文件是否存在
    {
        int fd;
        int len;

        if(0 > (fd = open("/sys/class/gpio/export",O_WRONLY)))//打开export文件并将路径存放在fd,准备写入
        {
            perror("open error");
            return 1;
        }

        len = strlen(argv[1]);

        if(len != write(fd,argv[1],len))//向export写入gpio号,即导出gpio文件
        {
            perror("write error");
            return 1;
        }

        close(fd);//关闭文件
    }
    //修改gpio的状态
    if(gpio_config("direction","in"))//配置为输入模式
    {
        return 1;
    }

    if(gpio_config("active_low","0"))//默认极性
    {
        return 1;
    }

    if(gpio_config("edge","both"))//设置中断触发方式,both是边沿触发
    {
        return 1;
    }

    //打开value属性文件
    sprintf(file_path,"%s/%s",gpio_path,"value");

    if(0 > (pfd.fd = open(file_path,O_RDONLY)))//用pfd.fd存储vlaue的文件路径
    {
        perror("open error");
        return 1;
    }

    /*
    调用poll
    struct pollfd{
        int fd;         //文件描述符
        short events;   //等待的事件
        short revents;  //返回的事件
    }
    */
    pfd.events = POLLPRI;//只关心高优先级数据可读(中断)

    read(pfd.fd,&value, 1);//在进入循环之前,先读取一次GPIO状态,以清除先前的状态
    
    for( ; ; )
    {
        ret = poll(&pfd,1,-1);//调用poll,阻塞直到事件发生
        if(0 > ret)
        {
            perror("poll error");
            return 1;
        }
        else if(0 == ret)
        {
            fprintf(stderr,"poll timeout.\n");
            continue;
        }

        //校验高优先级数据是否可读
        if(pfd.revents & POLLPRI)
        {
            if(0 > lseek(pfd.fd,0,SEEK_SET))//使用lseek重置文件指针,确保每次读取最新的GPIO状态
            {
                perror("lseek error");
                return 1;
            }

            if(0 > read(pfd.fd,&value,1))
            {
                perror("read error");
                return 1;
            }
            printf("gpio中断触发<value=%c\n",value);
        }
    }
    //退出程序
    return 0;
}

poll函数复习

c 复制代码
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
/*
fds:指向一个 struct pollfd 类型的数组,储存关心的文件描述符
nfds:fds数组中元素的个数
timeout:决定poll函数的阻塞行为
	-1:一直阻塞,直到有信号来
	 0:不阻塞
	>0:阻塞时间上限
*/

//struct pollfd 结构体
struct pollfd {
 int fd;        /* 文件描述符 */
 short events;  /* 等待的事件 */
 short revents; /* 返回的事件 */
};

输入设备

读取输入设备时,应用程序打开输入设备对应的设备文件,向其发起读操作,每一次 read 操作获取的都是一个 structinput_event 结构体类型数据,该结构体定义在<linux/input.h>头文件中

c 复制代码
struct input_event {
 struct timeval time;
 __u16 type;		//描述发生了哪一种类型的时间
 __u16 code;		//该类事件具体是哪个事件,如键盘上不同的按键1、2、3...
 __s32 value;		
/*
内核每次上报事件都会向应用层发送一个数据 value,对 value 值的解释随着 code 的变化而变化。譬如对于按键事件(type=1)来说,如果 code=2(键盘上的数字键 1,也就是 KEY_1),那么如果 value 等于 1,则表示 KEY_1 键按下;value 等于 0 表示 KEY_1 键松开,如果 value 等于 2则表示 KEY_1 键长按。再比如,在绝对位移事件中(type=3),如果 code=0(触摸点 X 坐标 ABS_X),那么 value 值就等于触摸点的 X 轴坐标值;同理,如果 code=1(触摸点 Y 坐标 ABS_Y),此时value 值便等于触摸点的 Y 轴坐标值;所以对 value 值的解释需要根据不同的 code 值而定
*/
};

type

code
Key

相对位移

绝对位移

通过数据同步使得应用程序得知本轮已经读取到完整的数据同步类事件有如下,所有的输入设备都需要上报同步事件,上报的同步事件通常是SYN_REPORT,而 value 值通常为 0

查看输入设备是哪个设备节点

c 复制代码
cat /proc/bus/input/devices

输入设备代码

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/input.h>

int main(int argc, char *argv[])
{
    struct input_event in_ev = {0};
    int fd = -1;
    int value = -1;

    //校验传参
    if(2 != argc)
    {
        fprintf(stderr, "Usage: %s <input_dev>\n", argv[0]);
        return 1;
    }

    // 打开输入事件文件
    if(0 > (fd = open(argv[1], O_RDONLY)))
    {
        perror("open error");
        return 1;
    }

    for( ; ; )
    {
        // 读取输入事件
        if(sizeof(struct input_event) != (read(fd, &in_ev, sizeof(struct input_event))))
        {
            perror("read error");
            break;
        }

        // 按键输入的类型
        if(EV_KEY == in_ev.type)
        {
           switch (in_ev.value)
           {
           case 0:
                printf("code<%d>:松开\n", in_ev.code);
                break;
            case 1:
                printf("code<%d>:按下\n", in_ev.code);
                break;  
            case 2:
                printf("code<%d>:长按\n", in_ev.code);
                break;                       
           }
        }
    }
}

开发板KEY0按键测试


键盘测试

接入键盘,我使用的是2.4G连接方式

查看设备时发现有两个

event3是键盘上的按键

event4是键盘上的旋钮

由此可知这两个东西不是同一个设备

鼠标测试


只能读取鼠标的左、右和滚轮的按下和松开

相关推荐
超级大只老咪4 小时前
快速进制转换
笔记·算法
嵩山小老虎5 小时前
Windows 10/11 安装 WSL2 并配置 VSCode 开发环境(C 语言 / Linux API 适用)
linux·windows·vscode
Fleshy数模5 小时前
CentOS7 安装配置 MySQL5.7 完整教程(本地虚拟机学习版)
linux·mysql·centos
a41324475 小时前
ubuntu 25 安装vllm
linux·服务器·ubuntu·vllm
Fᴏʀ ʏ꯭ᴏ꯭ᴜ꯭.7 小时前
Keepalived VIP迁移邮件告警配置指南
运维·服务器·笔记
一只自律的鸡7 小时前
【Linux驱动】bug处理 ens33找不到IP
linux·运维·bug
17(无规则自律)7 小时前
【CSAPP 读书笔记】第二章:信息的表示和处理
linux·嵌入式硬件·考研·高考
!chen7 小时前
linux服务器静默安装Oracle26ai
linux·运维·服务器
ling___xi8 小时前
《计算机网络》计网3小时期末速成课各版本教程都可用谢稀仁湖科大版都可用_哔哩哔哩_bilibili(笔记)
网络·笔记·计算机网络