14.点亮 LED 灯
- 1. 应用层操控硬件的两种方式
- 1.1 sysfs 文件系统
- 1.2 sysfs 与 /sys
- 1.3 总结
- 2. LED 硬件控制方式
- 3. 编写 LED 应用程序
- 4. 在开发板上测试
1. 应用层操控硬件的两种方式
应用层如何操控底层硬件,同样也是通过文件 I/O 的方式来实现,设备文件,包括字符设备文件和块设备文件,其实设备文件便是各种硬件设备向应用层提供的一个接口,应用层通过对设备文件的 I/O 操作来操控硬件设备,譬如 LCD 显示屏、串口、按键、摄像头等等,所以设备文件其实是与硬件设备相互对应的。
设备文件通常在 /dev/ 目录下,我们也把 /dev 目录下的文件称为设备节点。设备节点并不是操控硬件设备的唯一途径,除此之外,我们还可以通过 sysfs 文件系统对硬件设备进行操控
1.1 sysfs 文件系统
sysfs 是一个基于内存的文件系统,称为虚拟文件系统。将内核信息以文件的方式提供给应用层使用。 可以产生一个包含所有系统硬件层次的视图。
1.2 sysfs 与 /sys
sysfs 文件系统挂载在 /sys 目录下,cd /sys
,然后列出所有文件,可以发现以下文件:
- devices: 系统中所有设备存放的目录
- block: 块设备的存放目录,是一个过时的接口,该目录下的文件通常链接到 devices 目录下
- bus: 这是系统中的所有设备按照总线类型分类放置的目录结构。devices 目录下每一种设备都是挂载在某种总线下的。同样,该目录下的文件通常也是链接到 devices 目录下
- class: 这是系统中所有设备按其功能分类放置的目录结构,也是链接到 devices 目录下
- dev: 按照设备号的方式放置的目录结构,也是链接到 devices 目录下。该目录下有很多主设备号:次设备号(major:minor)命名的文件,都是链接文件,链接到 devices 对应的设备
- firmware: 描述了内核中的固件
- fs: 描述系统中所有文件系统,包括文件系统本身和按文件系统分类存放的已挂载点
- kernel: 内核中所有可调参数的位置
- module: 系统中所有模块的信息
- power: 系统中电源选项,有一些属性可以用于控制整个系统的电源状态
1.3 总结
应用层对硬件进行操控,可以通过以下两种方式:
- /dev/目录下的设备文件(设备节点)
- /sys/目录下的属性文件
具体使用哪种方式需要根据不同功能类型设备进行选择,有些设备只能通过设备节点进行操控,而有些设备只能通过 sysfs 方式进行操控;当然跟设备驱动具体的实现方式有关,通常情况下,一般简单地设备会使用 sysfs 方式操控,其设备驱动在实现时会将设备的一些属性导出到用户空间 sysfs 文件系统,以属性文件的形式为用户空间提供对这些数据、属性的访问支持,譬如 LED、 GPIO 等。
但对于一些较复杂的设备通常会使用设备节点的方式, 譬如 LCD 等、触摸屏、摄像头等。
2. LED 硬件控制方式
主要关注以下三个文件:
- brigntness: 亮度等级,该属性文件可读可写
- max_brightness: 最大亮度等级,该文件只能读
- trigger: 触发模式,可读可写
[] 括起来的内容表示触发模式,none 表示无触发,常用的触发模式有 none、mmc0(对 mmc0 发起读写操作时)、timer(定时器控制)、heartbeat(心跳呼吸模式)
所以我们可以通过命令行控制 LED:
echo heartbeat > trigger // 将触发模式设置为 heartbeat
echo 1 > brightnell // 点亮 LED
命令 cat 读取以及 echo 写入到属性文件中的格式都是字符串形式,所以使用 write() 和 read() 格式也是字符串形式
3. 编写 LED 应用程序
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#define LED_TRIGGER "/sys/class/leds/sys-led/trigger"
#define LED_BRIGHTNESS "/sys/class/leds/sys-led/brightness"
#define USAGE() fprintf(stderr, "usage:\n" \
" %s <on|off>\n" \
" %s <trigger> <type>\n", argv[0], argv[0])
int main(int argc, char *argv[])
{
int fd1, fd2;
/* 校验传参 */
if (2 > argc)
{
USAGE();
exit(-1);
}
/* 打开文件 */
fd1 = open(LED_TRIGGER, O_RDWR);
if (0 > fd1)
{
perror("open error");
exit(-1);
}
fd2 = open(LED_BRIGHTNESS, O_RDWR);
if (0 > fd2)
{
perror("open error");
exit(-1);
}
/* 根据传参控制 LED */
if (!strcmp(argv[1], "on"))
{
write(fd1, "none", 4); //先将触发模式设置为 none
write(fd2, "1", 1); //点亮 LED
}
else if (!strcmp(argv[1], "off"))
{
write(fd1, "none", 4); //先将触发模式设置为 none
write(fd2, "0", 1); //LED 灭
}
else if (!strcmp(argv[1], "trigger"))
{
if (3 != argc)
{
USAGE();
exit(-1);
}
if (0 > write(fd1, argv[2], strlen(argv[2])))
perror("write error");
}
else
USAGE();
exit(0);
}
然后使用交叉编译工具进行编译,这里不详细说明交叉编译工具的安装。
4. 在开发板上测试
将生成的文件拷贝到开发板根目录下,使用不同的命令行参数就可以测试
./test on # 点亮LED
./test off # 熄灭
./test trigger heartbeat # 修改触发方式