在嵌入式 Linux 系统中,我们编写的应用程序通常需要与硬件设备进行交互
Tips:本篇将以正点原子 ALPHA/Mini I.MX6U 开发板开发板出厂系统进行测试
互连我看的是正点原子的视频。讲的挺好的网络环境搭建
互传我看的是正点的 “【正点原子】I.MX6U网络环境TFTP&NFS搭建手册V1.3.1”
我一般是用nfs共享文件夹的方法,好用,不需要记指令
设备文件便是各种硬件设备向应用层提供的一个接口,包括字符设备文件和块设备文件
设备文件通常在/dev/目录下,我们也把/dev 目录下的文件称为设备节点。
设备节点并不是操控硬件设备的唯一途径,除此之外,我们还可以通过== sysfs 文件系统==对硬件设备进行操控
sysfs 是一个基于内存的文件系统,同 devfs、proc 文件系统一样,称为虚拟文件系统
将内核信息以文件的方式提供给应用层使用
它可以产生一个包含所有系统硬件层次的视图。对系统设备进行管理
sysfs 文件系统把连接在系统上的设备和总线组织成为一个分级的文件、展示设备驱动模型中各组件的层次关系。
sysfs 提供了一种机制,可以显式的描述内核对象、对象属性及对象间关系,用来导出内核对象 (kernel object,譬如一个硬件设备)的数据、属性到用户空间,以文件目录结构的形式为用户空间提供对这些数据、属性的访问支持。
sysfs 文件系统挂载在/sys 目录下
是 sysfs 文件系统中的目录,包括 block、bus、class、dev、devices、firmware、fs、kernel、modules、power 等,每个目录下又有许多文件或子目录
系统中所有的设备(对象)都会在/sys/devices 体现出来,是 sysfs 文件系统中最重要的目录结构
/sys/bus、/sys/class、/sys/dev 分别将设备按照挂载的总线类型、功能分类以及设备号的形式将设备组织存放在这些目录中,这些目录下的文件都是链接到了/sys/devices 中。
设备的数据、属性会导出到用户空间,以文件形式为用户空间提供对这些数据、属性的访问支持,可以把这些文件称为属性文件
应用层想要对底层硬件进行操控,通常可以通过两种方式:
⚫ /dev/目录下的设备文件(设备节点);
⚫ /sys/目录下设备的属性文件。
有些设备只能通过设备节点进行操控,而有些设备只能通过 sysfs 方式进行操控;当然跟设备驱动具体的实现方式有关
简单的设备会用sysfs,例如LED、GPIO
但对于一些较复杂的设备通常会使用设备节点的方式,譬如 LCD 等、触摸屏、摄像头等。
Linux 内核中为了尽量降低驱动开发者难度以及接口标准化,就出现了设备驱动框架的概念
对各种常见的设备进行分类,譬如 LED 类设备、输入类设备、FrameBuffer 类设备、video 类设备、PWM 设备等等,并为每一种类型的设备设计了一套成熟的、标准的、典型的驱动实现的框架,这个就叫做设备驱动框架
设备驱动框架为驱动开发和应用层提供了一套统一的接口规范
驱动工程师编写 LED 驱动时,使用 LED 驱动框架来开发自己的 LED 驱动程序,这样做的好处就在于,能够对上层应用层提供统一、标准化的接口、同时又降低了驱动开发工程师的难度。
因为一个计算机系统所能够连接、使用的外设实在太多了,不可能每一种外设都能够精准地分类到某一个设备类型中,通常把这些无法进行分类的外设就称为杂项设备。它是一种非标准接口
LED应用编程其实现使用 sysfs 方式控制
进入到/sys/class/leds 目录下。/sys/class/leds 目录下便存放了所有的 LED 类设备
sys-led 文件夹,这个便是底板上的用户 LED 设备文件夹
这里我们主要关注便是 brightness、max_brightness 以及 trigger 三个文件,这三个文件都是 LED 设备的属性文件:
⚫ brightness:亮度。对于 PWM 控制的 LED 来说,存在亮度等级的问题,不同的亮度等级对应不同的占空比,自然 LED 的亮度也是不同
但对于 GPIO控制(控制 GPIO 输出高低电平)的 LED 来说,通常不存在亮度等级这样的说法。譬如 brightness 等于 0 表示 LED 灭
⚫ max_brightness:该属性文件只能被读取,不能写,用于获取 LED 设备的最大亮度等级。
⚫ trigger:触发模式。常用的触发模式包括none(无触发)、mmc0(当对 mmc0 设备发起读写操作的时候 LED 会闪烁)、timer(LED 会有规律的一 亮一灭,被定时器控制住)、heartbeat(心跳呼吸模式,LED 模仿人的心跳呼吸那样亮灭变化)。
大家可以自己动手使用 echo 或 cat 命令进行测试、控制 LED 状态
echo timer > trigger //将 LED 触发模式设置为 timer echo none > trigger //将 LED 触发模式设置为 none echo 1 > brightness //点亮 LED echo 0 > brightness//熄灭 LED
写完代码后
在使用之前先对交叉编译工具的环境进行设置,使用 source 执行安装目录下的environment-setup-cortexa7hf-neon-poky-linux-gnueabi 脚本文件即可
source /opt/fsl-imx-x11/4.1.15-2.1.0/environment-setup-cortexa7hf-neon-poky-linux-gnueabi
然后编译就行了
${CC} -o LED led.c
这个LED就是交叉编译后arm平台能够运行的程序。
GPIO应用编程与 LED 设备一样,GPIO 同样也是通过 sysfs 方式进行操控,进入到/sys/class/gpio 目录下
⚫ gpiochipX:当前 SoC 所包含的 GPIO 控制器。板子上分别为 GPIO1、GPIO2、GPIO3、GPIO4、GPIO5,在这里分别对应 gpiochip0、gpiochip32、gpiochip64、gpiochip96、gpiochip128 这 5 个文件夹
进去之后可以看到
在这个目录我们主要关注的是 base、label、ngpio 这三个属性文件,这三个属性文件均是只读、不可写。
base:与 gpiochipX 中的 X 相同,表示该控制器所管理的这组 GPIO 引脚中最小的编号
label:该组 GPIO 对应的标签,也就是名字。
ngpio:该控制器所管理的 GPIO 引脚的数量(所以引脚编号范围是:base ~ base+ngpio-1)。
对于给定的一个 GPIO 引脚,如何计算它在 sysfs 中对应的编号呢?其实非常简单,譬如给定一个 GPIO引脚为 GPIO4_IO16,那它对应的编号是多少呢?首先我们要确定 GPIO4 对应于 gpiochip96,该组 GPIO 引脚的最小编号是 96(对应于 GPIO4_IO0),所以 GPIO4_IO16 对应的编号自然是 96 + 16 = 112
回到刚才的在/sys/class/gpio文件夹
⚫ export:用于将指定编号的 GPIO 引脚导出。在使用 GPIO 引脚之前,需要将其导出,导出成功之后才能使用它。
echo 0 > export # 导出编号为 0 的 GPIO 引脚(对于 I.MX6UL/I.MX6ULL 来说,也就是GPIO1_IO0)
导出成功之后会发现在/sys/class/gpio 目录下生成了一个名为 gpio0 的文件夹(gpioX,X 表示对应的编号)
⚫ unexport:将导出的 GPIO 引脚删除。当使用完 GPIO 引脚之后,我们需要将导出的引脚删除
echo 0 > unexport # 删除导出的编号为 0 的 GPIO 引脚
删除成功之后,之前生成的 gpio0 文件夹就会消失!
以上就给大家介绍了/sys/class/gpio 目录下的所有文件和文件夹
控制 GPIO 引脚主要是通过 export 导出之后所生成的 gpioX(X 表示对应的编号)文件夹,在该文件夹目录下存在一些属性文件可用于控制 GPIO引脚的输入、输出以及输出的电平状态等。
Tips:需要注意的是,并不是所有 GPIO 引脚都可以成功导出,如果对应的 GPIO 已经在内核中被使用了,那便无法成功导出
进入到 gpio0 目录,
我们主要关心的文件是 active_low、direction、edge 以及 value 这四个属性文件
⚫ direction:配置 GPIO 引脚为输入或输出模式。该文件可读、可写
读取或写入操作可取的值为"out"(输出模式)和"in"(输入模式)
⚫ value:在 GPIO 配置为输出模式下,向 value 文件写入"0"控制 GPIO 引脚输出低电平
⚫ active_low:这个属性文件用于控制极性,可读可写,默认情况下为 0,为高电平1。否则反向
⚫ edge:控制中断的触发模式,需将其设置为输入模式:
非中断引脚:echo “none” > edge
上升沿触发:echo “rising” > edge
下降沿触发:echo “falling” > edge
边沿触发:echo “both” > edge
当引脚被配置为中断后可以使用 poll()函数监听引脚的电平状态变化
常见的输入设备有鼠标、键盘、触摸屏、遥控器、电脑画图板等,用户通过输入设备与系统进行交互。
input 子系统驱动开发人员基于 input 子系统开发输入设备的驱动程序,input 子系统可以屏蔽硬件的差异,向应用层提供一套统一的接口。
基于 input 子系统注册成功的输入设备,都会在==/dev/input 目录下生成对应的设备节点(设备文件)==
节点名称通常为 eventX(X 表示一个数字编号 0、1、2、3 等)
读取数据的流程
①、应用程序打开/dev/input/event0 设备文件;
②、应用程序发起读操作(譬如调用 read),如果没有数据可读则会进入休眠(阻塞 I/O 情况下);
③、当有数据可读时,应用程序会被唤醒,读操作获取到数据返回;
④、应用程序对读取到的数据进行解析。
其实每一次 read 操作获取的都是一个 struct input_event 结构体类型数据,该结构体定义在
struct input_event {
struct timeval time;
__u16 type;
__u16 code;
__s32 value;
};
⚫ type:type 用于描述发生了哪一种类型的事件(对事件的分类)
#define EV_SYN 0x00 //同步类事件,用于同步事件 #define EV_KEY 0x01 //按键类事件 #define EV_REL 0x02 //相对位移类事件(譬如鼠标) #define EV_ABS 0x03 //绝对位移类事件(譬如触摸屏) #define EV_MSC 0x04 //其它杂类事件 #define EV_SW 0x05 #define EV_LED 0x11 #define EV_SND 0x12 #define EV_REP 0x14 #define EV_FF 0x15 #define EV_PWR 0x16 #define EV_FF_STATUS 0x17 #define EV_MAX 0x1f #define EV_CNT (EV_MAX+1)
数据同步
提到了同步事件类型 EV_SYN,同步事件用于实现同步操作、告知接收者本轮上报的数据已经完整。
内核将本轮需要上报、发送给接收者的数据全部上报完毕后,接着会上报一个同步事件,以告知应用程序本轮数据已经完整、可以进行同步了。
#define SYN_REPORT 0 #define SYN_CONFIG 1 #define SYN_MT_REPORT 2 #define SYN_DROPPED 3 #define SYN_MAX 0xf #define SYN_CNT (SYN_MAX+1)
所以的输入设备都需要上报同步事件,上报的同步事件通常是 SYN_REPORT,而 value 值通常为 0。
⚫ code:code 表示该类事件中的哪一个具体事件,譬如一个键盘上通常有很多按键,譬如字母 A、B、C、D 或者数字 1、2、3、4 等,而 code变量则告知应用程序是哪一个按键发生了输入事件
⚫ value:内核每次上报事件都会向应用层发送一个数据 value,对 value 值的解释随着 code 的变化而变化。如果 value 等于 1,则表示 KEY_1 键按下;value 等于 0 表示 KEY_1 键松开。



