3045 words
15 minutes
嵌入式liunx应用开发笔记-进阶篇(一)
I.MX6U嵌入式Linux C应用编程笔记
一、核心主题概述
本章节聚焦I.MX6U开发板的硬件外设应用编程核心,涵盖LED点亮、GPIO通用输入输出、输入设备(按键/触摸屏/鼠标)的操控逻辑,是嵌入式Linux硬件交互的基础内容。重点在于掌握应用层通过系统接口操控硬件的核心方法,以及输入子系统的数据解析逻辑,所有操作均基于正点原子I.MX6U ALPHA/Mini开发板,需配合交叉编译器与开发板硬件环境完成实战。
二、点亮LED
1. 应用层操控硬件的核心方式
- sysfs文件系统(推荐):Linux将硬件外设抽象为文件,通过
/sys/class/gpio目录下的接口操作LED,无需深入驱动细节,仅需基础文件I/O函数(open/read/write/close)即可实现控制。 - 原理:系统为每个GPIO引脚提供专属目录,通过修改目录下的文件配置引脚状态(导出、方向、电平),间接控制硬件。
- 实操指令:开发板需提前挂载sysfs文件系统(默认已挂载),编译时需指定ARM交叉编译器(如
arm-linux-gcc led.c -o led),运行时需通过sudo ./led获取/sys目录读写权限。
2. 硬件控制逻辑
- 硬件特性:LED通过GPIO引脚控制,低电平点亮、高电平熄灭(需结合开发板原理图确认引脚编号,如正点原子I.MX6U开发板常用GPIO10控制LED)。
- 软件步骤(关键流程):
- 导出GPIO引脚:执行
echo 10 > /sys/class/gpio/export,生成/sys/class/gpio/gpio10目录(指令可通过C语言write函数实现)。 - 设置引脚方向:执行
echo out > /sys/class/gpio/gpio10/direction,配置为输出模式。 - 控制LED亮灭:执行
echo 0 > /sys/class/gpio/gpio10/value(点亮)或echo 1 > /sys/class/gpio/gpio10/value(熄灭)。 - 注销引脚(可选):执行
echo 10 > /sys/class/gpio/unexport,释放系统资源。
- 导出GPIO引脚:执行
3. 开发板测试要点
- 编译指令:使用交叉编译器编译C代码,示例:
arm-linux-gcc led_app.c -o led_app。 - 烧录与运行:通过SD卡或SSH将可执行文件传输至开发板,执行
sudo chmod +x led_app赋予执行权限,再运行sudo ./led_app。 - 故障排查:若LED未点亮,需检查引脚编号是否与原理图一致、文件权限是否正确、交叉编译器是否匹配开发板架构。
三、GPIO应用编程
GPIO(通用输入输出口)是嵌入式硬件的核心外设,支持输出、输入、中断三种工作模式,是连接处理器与外部硬件的关键接口。
1. sysfs方式核心操作接口
| 操作类型 | 对应文件路径 | 操作指令(终端/代码实现) |
|---|---|---|
| 导出引脚 | /sys/class/gpio/export | 终端:echo 引脚号 > export;代码:fd = open("/sys/class/gpio/export", O_WRONLY); write(fd, "10", 2); |
| 设置方向(输出/输入) | /sys/class/gpio/gpioX/direction | 终端:echo out/in > direction;代码:fd = open("/sys/class/gpio/gpio10/direction", O_WRONLY); write(fd, "out", 3); |
| 输出电平 | /sys/class/gpio/gpioX/value | 终端:echo 0/1 > value;代码:fd = open("/sys/class/gpio/gpio10/value", O_WRONLY); write(fd, "0", 1); |
| 读取输入电平 | /sys/class/gpio/gpioX/value | 终端:cat value;代码:fd = open("/sys/class/gpio/gpio10/value", O_RDONLY); read(fd, buf, 1); |
| 注销引脚 | /sys/class/gpio/unexport | 终端:echo 引脚号 > unexport;代码:fd = open("/sys/class/gpio/unexport", O_WRONLY); write(fd, "10", 2); |
2. 三种工作模式编程实现
- 输出模式:与LED控制逻辑一致,通过
value文件写入电平值,适用于控制LED、继电器等执行器。- 实操指令:编译代码时需包含
<sys/types.h>``<sys/stat.h>``<fcntl.h>``<unistd.h>头文件,示例代码需包含文件打开、写入、关闭的完整流程。
- 实操指令:编译代码时需包含
- 输入模式:设置
direction为in后,读取value文件获取引脚状态,适用于读取传感器、按键等输入信号。- 实操指令:假设按键连接GPIO11,代码中需先导出引脚、设置方向为
in,再循环读取value文件,判断按键是否按下(如buf[0] == '0'表示按下)。
- 实操指令:假设按键连接GPIO11,代码中需先导出引脚、设置方向为
- 中断模式(高效检测信号变化):
- 用途:无需轮询,检测GPIO引脚电平变化(如按键按下/松开),降低CPU占用率。
- 配置步骤:
- 设置引脚为输入模式:
echo in > /sys/class/gpio/gpio11/direction。 - 配置触发方式:
echo falling > /sys/class/gpio/gpio11/edge(下降沿触发,可选rising上升沿/both双边沿)。 - 监听中断:通过
poll()函数监听value文件变化,代码中需初始化struct pollfd结构体,设置fd为value文件描述符、events为POLLPRI,调用poll(&fdset, 1, -1)阻塞等待中断。
- 设置引脚为输入模式:
- 实操指令:编译时需链接
-lpthread库(如arm-linux-gcc gpio_interrupt.c -o gpio_interrupt -lpthread),运行时确保引脚触发方式配置正确。
3. 典型测试用例
- 输出测试:循环切换GPIO电平,实现LED闪烁,代码中通过
sleep(1)控制闪烁频率。 - 输入测试:读取按键对应的GPIO状态,打印按键触发信息,终端运行后可看到“按键按下”“按键松开”提示。
- 中断测试:配置按键引脚为下降沿触发,触发后打印中断事件及当前电平,测试时按下按键即可触发中断响应。
四、输入设备应用编程
Linux将所有输入设备(按键、触摸屏、鼠标等)统一抽象为“输入子系统”,应用层通过读取/dev/input目录下的设备文件(如/dev/input/event0)获取输入数据,核心是解析struct input_event结构体数据。
1. 输入子系统核心概念
- 设备映射:每个输入设备对应
/dev/input/eventX(X为设备编号),通过cat /proc/bus/input/devices可查看设备与文件的对应关系(实操指令:开发板终端执行该命令,找到“Key”“Touchscreen”对应的event文件)。 - 数据格式(核心结构体):
struct input_event {struct timeval time; // 事件发生时间__u16 type; // 事件类型(EV_KEY按键/EV_ABS绝对坐标/EV_REL相对坐标)__u16 code; // 事件编码(KEY_1按键/ABS_X轴坐标/REL_X鼠标移动)__s32 value; // 事件值(1按下/0松开/坐标值)};
- 实操指令:代码中需循环读取
struct input_event数据,包含<linux/input.h>头文件,编译时无需额外链接库,直接使用交叉编译器编译即可。
2. 按键应用编程
- 核心逻辑:打开按键对应的
/dev/input/eventX文件,循环读取数据,筛选type=EV_KEY的事件。 - 关键解析规则:
code:对应按键编号(如KEY_1、KEY_ESC,定义在linux/input.h中)。value:1表示按键按下,0表示松开,2表示长按。
- 实操指令:
- 开发板终端执行
cat /proc/bus/input/devices,找到按键对应的event文件(如event1)。 - 代码中打开
/dev/input/event1,循环调用read(fd, &event, sizeof(event))读取数据。 - 编译指令:
arm-linux-gcc key_app.c -o key_app,运行后按下开发板按键,终端打印按键状态。
- 开发板终端执行
3. 触摸屏应用编程
(1)基础特性
- 支持单点触摸和多点触摸,数据通过
EV_ABS(绝对坐标事件)传输。 - 核心编码:
ABS_X(X轴坐标)、ABS_Y(Y轴坐标)、BTN_TOUCH(触摸状态,1按下/0松开)。 - 实操指令:确认触摸屏设备文件(通常为
/dev/input/event2),代码中需同时监听ABS_X、ABS_Y、BTN_TOUCH事件。
(2)上报事件流程
- 触摸按下流程:
- 触摸屏检测到物理触摸后,首先上报
type=EV_KEY、code=BTN_TOUCH、value=1(表示触摸按下)事件,告知系统触摸开始。 - 紧接着连续上报
type=EV_ABS、code=ABS_X、value=X坐标值和type=EV_ABS、code=ABS_Y、value=Y坐标值事件,传输初始触摸坐标。 - 最后上报
type=EV_SYN、code=SYN_REPORT、value=0事件,表示一组触摸数据同步完成,系统可处理该组坐标。
- 触摸屏检测到物理触摸后,首先上报
- 触摸移动流程:
- 触摸点在屏幕上移动时,不再上报
BTN_TOUCH事件,持续上报EV_ABS类型的ABS_X和ABS_Y坐标更新事件。 - 每更新一次坐标后,都会上报
EV_SYN同步事件,确保系统实时获取移动轨迹。
- 触摸点在屏幕上移动时,不再上报
- 触摸松开流程:
- 触摸点离开屏幕时,上报
type=EV_KEY、code=BTN_TOUCH、value=0(表示触摸松开)事件。 - 随后上报
EV_SYN同步事件,告知系统触摸流程结束。
- 触摸点离开屏幕时,上报
- 多点触摸流程:
- 第一个触摸点按下:上报
BTN_TOUCH=1、ABS_MT_TRACKING_ID=0(触摸点ID)、ABS_MT_POSITION_X/Y坐标,配合EV_SYN同步。 - 第二个触摸点按下:上报
ABS_MT_TRACKING_ID=1、对应坐标及EV_SYN同步,与第一个触摸点的事件独立区分。 - 触摸点移动:各自上报对应
ABS_MT_POSITION_X/Y坐标更新及同步事件。 - 触摸点松开:上报对应
ABS_MT_TRACKING_ID=-1(标识该触摸点失效)、BTN_TOUCH=0(最后一个点松开时)及EV_SYN同步事件。
- 第一个触摸点按下:上报
(3)事件含义详解
| 事件类型(type) | 事件编码(code) | 事件值(value)含义 |
|---|---|---|
| EV_KEY | BTN_TOUCH | 1:触摸按下;0:触摸松开;无2(长按无效,触摸移动时保持1,松开时变为0) |
| EV_ABS | ABS_X | 单点触摸X轴坐标值(范围0~屏幕X轴最大分辨率,如800) |
| EV_ABS | ABS_Y | 单点触摸Y轴坐标值(范围0~屏幕Y轴最大分辨率,如480) |
| EV_ABS | ABS_MT_TRACKING_ID | 多点触摸时的触摸点ID(0、1、2…,-1表示该触摸点失效) |
| EV_ABS | ABS_MT_POSITION_X | 多点触摸时对应ID触摸点的X轴坐标 |
| EV_ABS | ABS_MT_POSITION_Y | 多点触摸时对应ID触摸点的Y轴坐标 |
| EV_SYN | SYN_REPORT | 0:一组触摸事件同步完成,系统可处理该组数据;用于分隔不同触摸阶段或不同触摸点的事件 |
| EV_SYN | SYN_MT_REPORT | 0:多点触摸时单个触摸点的事件同步完成,告知系统该触摸点的坐标等信息已完整上报 |
(4)单点触摸解析
- 数据流程:触摸按下(
BTN_TOUCH=1)→ 传输ABS_X/ABS_Y坐标 → 触摸移动(持续更新坐标)→ 触摸松开(BTN_TOUCH=0),每阶段均以EV_SYN同步事件收尾。 - 编程步骤:
- 打开触摸屏设备文件:
fd = open("/dev/input/event2", O_RDONLY);。 - 循环读取
struct input_event数据,分别处理EV_KEY(触摸状态)、EV_ABS(坐标)、EV_SYN(同步)事件。 - 存储坐标值:当
code=ABS_X时记录X坐标,code=ABS_Y时记录Y坐标,BTN_TOUCH=1且收到SYN_REPORT时打印坐标。
- 打开触摸屏设备文件:
- 实操指令:运行程序后触摸屏幕,终端输出“触摸坐标:X=xxx, Y=xxx”。
(5)多点触摸解析
- 核心编码:
ABS_MT_TRACKING_ID(触摸点ID,区分多个触摸点)、ABS_MT_POSITION_X/Y(多点坐标)。 - 编程要点:通过
ABS_MT_TRACKING_ID识别不同触摸点(非-1表示有效触摸点),分别记录各点坐标及状态变化,收到SYN_MT_REPORT表示单个触摸点事件完整,收到SYN_REPORT表示所有触摸点事件同步完成。 - 实操指令:编译代码时需确保支持多点触摸(开发板硬件需支持),运行后同时触摸屏幕多个点,终端打印各触摸点ID及坐标。
4. 鼠标应用编程
- 事件类型:
EV_REL(相对坐标事件,鼠标移动)、EV_KEY(鼠标按键事件)。 - 数据解析规则:
code=REL_X/REL_Y:鼠标X/Y轴相对移动量(正值向右/向下,负值向左/向上)。code=BTN_LEFT:左键状态(1按下,0松开),BTN_RIGHT为右键。
- 实操指令:
- 确认鼠标设备文件(通常为
/dev/input/event3)。 - 代码中循环读取事件,当
type=EV_REL时打印移动量,type=EV_KEY时打印按键状态。 - 编译运行后移动鼠标、点击按键,终端输出对应操作信息。
- 确认鼠标设备文件(通常为
五、关键总结与实践要点
- 核心思想:嵌入式Linux硬件操控的核心是“文件抽象”,无论是GPIO还是输入设备,均通过文件I/O接口实现交互,无需关注底层驱动细节。
- 重点技能:
- 掌握sysfs文件系统操作GPIO的流程(导出-配置-控制-注销),熟练使用文件I/O函数。
- 熟练解析
struct input_event结构体,区分不同输入设备的事件类型和编码,尤其掌握触摸屏的事件上报流程及各事件含义。
- 实践注意:
- 操作前必须确认硬件引脚编号和设备文件路径,避免因硬件差异导致程序失效(参考开发板原理图)。
- 交叉编译时需确保编译器与开发板架构匹配(如I.MX6U为ARM架构,使用
arm-linux-gcc)。 - 输入设备编程需处理事件同步问题,避免数据丢失或解析错误(重点关注
EV_SYN事件的同步作用)。
- 常见问题排查:
- 程序无法运行:检查文件权限(添加
sudo)、设备文件路径是否正确。 - 数据读取异常:确认事件类型和编码是否匹配设备(通过
cat /proc/bus/input/devices核对)。 - 硬件无响应:检查引脚连接是否正确、开发板电源是否正常。
- 程序无法运行:检查文件权限(添加
嵌入式liunx应用开发笔记-进阶篇(一)
https://blog.huangzy.xyz/posts/嵌入式liunx应用开发笔记-进阶篇一/ Some information may be outdated