5.6 Touch 设备
5.6 Touch 设备
📚 本节导读
学习时长: 约 60 分钟
难度级别: ⭐⭐⭐☆☆
前置知识: C 语言基础、触摸屏基础知识、5.1 设备驱动框架、GPIO 中断
Touch(触摸屏)设备是嵌入式人机交互的重要输入设备。OneOS 将触摸屏抽象为 OS_DEVICE_TYPE_TOUCH 类型设备,提供统一的事件处理机制。本节讲解 OneOS 中 Touch 设备的完整用法。Touch 设备驱动位于 drivers/touch/ 目录,核心文件为 touch.h 与 touch.c。
本节会带你:
- 看懂 Touch 设备如何基于 5.1 的设备框架实现统一触摸事件接口;
- 掌握
os_touch_device、os_touch_info、os_touch_config、os_touch_data、os_touch_ops等核心结构体的每个字段; - 掌握触摸事件类型(NONE/UP/DOWN/MOVE)与厂商、触摸 IC 类型宏;
- 掌握框架层
os_touch_read的坐标映射机制(X/Y 反向、交换、物理→用户范围映射); - 逐个拆解
os_hw_touch_register、os_touch_irq_notify、os_touch_control、os_touch_read的完整源码; - 通过多个可运行示例掌握触摸事件处理、设备信息获取、坐标校准、中断驱动的实际开发。
说明:本节所有源码均取自项目
drivers/touch/touch.h与drivers/touch/touch.c,与项目源码逐字一致(含原始英文注释)。
🎯 学习目标
- 理解 OneOS 中 Touch 设备框架的设计理念与中断回调机制
- 掌握
os_touch_device、os_touch_info、os_touch_config、os_touch_data、os_touch_ops各字段含义与来源 - 掌握触摸事件的四种类型(NONE、UP、DOWN、MOVE)
- 掌握 Touch 设备的控制命令与框架层
os_touch_control的分发逻辑 - 理解框架层
os_touch_read的坐标映射机制(反向、交换、范围映射)与编译期宏 - 掌握
os_hw_touch_register、os_touch_irq_notify的注册与中断通知流程 - 理解更换触摸芯片时如何编写 BSP 层 Touch 驱动
- 能够编写完整的触摸屏事件处理程序
一、概述
Touch(触摸屏)设备是嵌入式人机交互的重要输入设备。OneOS 将触摸屏抽象为 OS_DEVICE_TYPE_TOUCH 类型设备,提供统一的事件处理机制。Touch 设备驱动位于 drivers/touch/ 目录下,核心头文件为 touch.h。
Touch 设备框架的特点:
- 事件驱动:支持按下(DOWN)、抬起(UP)、移动(MOVE)三种触摸事件
- 中断回调:通过
irq_handle中断回调函数处理触摸事件,无需轮询 - 多点触控:通过
track_id追踪多点,支持多点同时触摸 - 坐标变换:框架层统一完成物理坐标与用户坐标的范围映射、X/Y 反向与交换
- 时间戳:每条触摸数据附带时间戳,支持 RTC 或系统滴答两种时钟源
1.1 Touch 设备的分层架构
Touch 设备基于 5.1 设备框架,通过 os_touch_ops 与具体触摸芯片驱动解耦:
┌────────────────────────────────────────────────────────────┐
│ 应用层(Application) │
│ os_device_open_s / os_device_read_block │
│ os_device_control(控制命令)/ os_device_close │
└──────────────────────────────┬─────────────────────────────┘
│ os_device_read/control
▼
┌────────────────────────────────────────────────────────────┐
│ Touch 框架层(drivers/touch/touch.c) │
│ os_touch_init/deinit/read/control(框架层设备操作) │
│ os_touch_read:坐标映射(反向/交换/范围映射) │
│ os_touch_control:控制命令分发 │
│ os_touch_irq_notify / os_hw_touch_register │
└──────────────────────────────┬─────────────────────────────┘
│ 调用 touch->ops->touch_readpoint / touch_control
▼
┌────────────────────────────────────────────────────────────┐
│ BSP 触摸芯片驱动(如 ft6x06.c / gt9x.c) │
│ 实现 struct os_touch_ops 的两个函数指针 │
│ 通过 I2C/SPI 读触摸寄存器,配置中断引脚 │
└──────────────────────────────┬─────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────┐
│ 硬件(触摸屏芯片 + 中断引脚) │
└────────────────────────────────────────────────────────────┘关键理解:Touch 框架层不直接操作触摸芯片。它负责"设备框架适配 + 坐标映射 + 事件分发",真正读寄存器、配置中断的是 BSP 触摸芯片驱动实现的 struct os_touch_ops。这让上层应用与具体触摸芯片(FT/GT/STMPE 等)解耦。
1.2 Touch 设备使用场景
一句话核心
Touch 设备适用于需要采集用户手指按下/抬起/移动事件并映射到屏幕坐标的人机交互场景,如 GUI 界面、游戏操作、手势识别。
与按钮/键盘等输入设备的机制对比
| 维度 | Touch | GPIO 按钮 | 键盘矩阵 |
|---|---|---|---|
| 输入信息 | 连续坐标 + 多事件 | 开关状态 | 按键码 |
| 是否支持手势 | 是(滑动/缩放) | 否 | 否 |
| 多点支持 | 是(track_id) | 否 | 多键但非坐标 |
| 中断方式 | 触摸芯片 INT 引脚 | 每按钮 GPIO | 矩阵扫描 |
| 应用场景 | GUI、游戏、绘图 | 开关、功能键 | 文本输入 |
为什么用 Touch 而不是按钮:GUI 界面需要连续的坐标输入和移动/抬起等事件,按钮只能表达开关量。Touch 一次触摸即可表达"位置 + 动作 + 时间",信息量远大于按钮。
典型应用场景
场景 1:GUI 触摸交互(点击/滑动控制界面)
┌──────────────────────────────┐
│ LCD 屏幕 │
│ ┌────────────────────────┐ │
│ │ 按钮A 按钮B │ │
│ │ ▲(手指触摸) │ │
│ └────────────────────────┘ │
│ 触摸屏(覆盖在 LCD 上) │
└──────────────────────────────┘为什么用 Touch:用户点击屏幕上的按钮,Touch 驱动上报 DOWN 事件 + 坐标,GUI 据此定位按钮。滑动上报 MOVE 事件,用于页面滚动。
实际工作:任务 os_device_read_block 阻塞等待 → 手指按下触发中断 → ft6x06_irq_handler 调 os_touch_irq_notify → 唤醒读任务 → os_touch_read 读坐标并做范围映射 → 应用根据 DOWN 事件坐标定位按钮。
场景 2:手势识别(滑动切换页面)
为什么用 Touch:Touch 持续上报 DOWN → MOVE → UP 事件,应用可记录起止坐标判断滑动方向(见示例 3 手势识别)。
实际工作:手指在屏幕上滑动,MOVE 事件连续上报;手指抬起触发 UP 事件。应用对比"按下坐标"与"抬起坐标"的差值判断左滑/右滑/上滑/下滑。
选型边界表(何时不该用 Touch)
| 场景 | 该用什么 | 为什么 |
|---|---|---|
| 只传开关状态/功能键 | GPIO 按钮 | Touch 成本高、复杂度高 |
| 需要精确的文本/字符输入 | 键盘矩阵/物理键盘 | Touch 软键盘体验和精度有限 |
| 极低成本简单交互 | 按钮/旋转编码器 | Touch 需要触摸芯片 + 引脚 + 驱动 |
结合源码的使用注意点
- 读返回值是"点数"而非字节数:
os_touch_read返回touch->ops->touch_readpoint(touch, buf, len)的点数(如 1、5),不是字节数。os_device_read_block的返回值据此判断(见 4.3)。 - 控制命令用原始宏:
os_touch_control的switch(cmd)判断的是原始OS_TOUCH_CTRL_*值(0~9),不是IOC_TOUCH(cmd)的编码值。因此os_device_control应传OS_TOUCH_CTRL_GET_INFO而不是IOC_TOUCH(cmd)(见 4.4 演算)。 - 坐标映射在框架层做:X/Y 反向、交换、范围映射都在
os_touch_read里完成(编译期宏OS_TOUCH_X_REVERSE等 + 运行时比例),BSP 只上报原始坐标(见 4.3)。 SET_X_TO_Y命令框架层不处理:OS_TOUCH_CTRL_SET_X_TO_Y(7)虽然定义,但框架层os_touch_control的 switch 没有该 case,会落到default返回OS_FAILURE。横竖屏的 X/Y 交换应通过编译期宏OS_TOUCH_XY_SWAP配置(见 4.3)。
二、核心数据结构(字段详解)
本节对照 drivers/touch/touch.h 讲解 Touch 框架涉及的五个核心结构体。来源:drivers/touch/touch.h。
2.1 Touch 设备结构体 os_touch_device
来源:drivers/touch/touch.h
typedef struct os_touch_device os_touch_t;
struct os_touch_device
{
struct os_device parent; /* The standard device */
struct os_touch_info info; /* The touch info data */
struct os_touch_config config; /* The touch config data */
const struct os_touch_ops *ops; /* The touch ops */
os_err_t (*irq_handle)(os_touch_t *touch); /* Called when an interrupt is generated, registered by the driver */
};| 字段 | 类型 | 含义 |
|---|---|---|
parent | struct os_device | 内嵌的通用设备对象,用于把触摸设备注册进 5.1 设备框架 |
info | struct os_touch_info | 触摸设备信息(类型/厂商/点数/坐标范围) |
config | struct os_touch_config | 触摸设备配置(中断引脚/通信设备名) |
ops | const struct os_touch_ops * | 触摸操作函数表(touch_readpoint/touch_control) |
irq_handle | 函数指针 | 中断回调函数,触摸产生中断时被调用(由驱动注册) |
关键理解:parent 内嵌 os_device,使触摸设备能注册进设备框架(类型 OS_DEVICE_TYPE_TOUCH)。irq_handle 是可选的——应用可通过轮询(os_device_read_block)或中断(GPIO 中断回调)两种方式获取触摸事件;框架层本身不调用 irq_handle,它是给上层 GUI/应用注册的入口。
2.2 触摸设备信息 os_touch_info
来源:drivers/touch/touch.h
struct os_touch_info
{
uint8_t type; /* The touch type */
uint8_t vendor; /* Vendor of touchs */
uint8_t point_num; /* Support point num */
int32_t range_x; /* X coordinate range */
int32_t range_y; /* Y coordinate range */
int32_t user_range_x; /* X coordinate range defined by user*/
int32_t user_range_y; /* Y coordinate range defined by user*/
};| 字段 | 类型 | 含义 |
|---|---|---|
type | uint8_t | 触摸类型(OS_TOUCH_TYPE_CAPACITANCE 电容 / RESISTANCE 电阻) |
vendor | uint8_t | 触摸芯片厂商(OS_TOUCH_VENDOR_GT/FT 等) |
point_num | uint8_t | 支持的最大触摸点数(多点触控) |
range_x | int32_t | X 轴物理坐标范围 |
range_y | int32_t | Y 轴物理坐标范围 |
user_range_x | int32_t | X 轴用户坐标范围(如映射到屏幕分辨率) |
user_range_y | int32_t | Y 轴用户坐标范围 |
关键理解——物理坐标 vs 用户坐标:
range_x/range_y是触摸芯片上报的原始物理坐标范围(如电容屏常为 4096);user_range_x/user_range_y是用户想要的范围(如屏幕分辨率 480×320);os_hw_touch_register会把user_range_x/y初始化为range_x/y(见 4.1);- 当两者不同时,
os_touch_read用比例换算把物理坐标映射到用户坐标(见 4.3)。
2.3 触摸设备配置 os_touch_config
来源:drivers/touch/touch.h
struct os_touch_config
{
struct os_device_pin_mode *irq_pin; /* Interrupt pin, The purpose of this pin is to notification read data */
char *dev_name; /* The name of the communication device */
void *user_data;
};| 字段 | 类型 | 含义 |
|---|---|---|
irq_pin | struct os_device_pin_mode * | 中断引脚配置,用于通知应用有数据可读 |
dev_name | char * | 通信设备名称(如 I2C 设备名 "i2c2") |
user_data | void * | 用户自定义数据指针 |
关键理解:irq_pin 描述触摸芯片的 INT 中断引脚——当有触摸事件时,触摸芯片拉高/拉低该引脚,驱动据此通知应用读取数据。dev_name 通常是触摸芯片所连接的 I2C/SPI 总线设备名(如 FT6X06 通过 OS_FT6X060_I2C_BUS_NAME 指定的 I2C 总线通信)。
2.4 触摸数据 os_touch_data
来源:drivers/touch/touch.h
struct os_touch_data
{
uint8_t event; /* The touch event of the data */
uint8_t track_id; /* Track id of point */
uint8_t width; /* Point of width */
uint16_t x_coordinate; /* Point of x coordinate */
uint16_t y_coordinate; /* Point of y coordinate */
os_tick_t timestamp; /* The timestamp when the data was received */
};| 字段 | 类型 | 含义 |
|---|---|---|
event | uint8_t | 触摸事件类型(OS_TOUCH_EVENT_DOWN/UP/MOVE/NONE) |
track_id | uint8_t | 触摸点 ID(多点触控标识,区分不同手指) |
width | uint8_t | 触摸点宽度 |
x_coordinate | uint16_t | X 坐标(映射后) |
y_coordinate | uint16_t | Y 坐标(映射后) |
timestamp | os_tick_t | 数据时间戳(os_touch_get_ts() 获取) |
每条触摸数据包含事件类型、轨迹 ID、坐标、宽度和时间戳,支持多点触控追踪。
2.5 Touch 操作函数表 os_touch_ops
来源:drivers/touch/touch.h
struct os_touch_ops
{
os_size_t (*touch_readpoint)(struct os_touch_device *touch, struct os_touch_data *data, os_size_t touch_num);
os_err_t (*touch_control)(struct os_touch_device *touch, int cmd, void *arg);
};| 函数指针 | 说明 |
|---|---|
touch_readpoint | 从触摸芯片读取触摸点数据,返回实际读取的点数 |
touch_control | 控制命令处理(获取 ID、设置范围等) |
关键理解:touch_readpoint 返回点数(不是字节数),框架层 os_touch_read 直接把这个返回值作为 os_device_read 的返回值。touch_control 处理 OS_TOUCH_CTRL_* 命令(框架层 os_touch_control 会先分发再转发给它,见 4.4)。
三、触摸事件、厂商与芯片类型
本节对照 drivers/touch/touch.h 讲解触摸事件、厂商类型、触摸 IC 类型、时间戳获取宏。来源:drivers/touch/touch.h。
3.1 触摸事件类型
#define OS_TOUCH_EVENT_NONE (0) /* Touch none */
#define OS_TOUCH_EVENT_UP (1) /* Touch up event */
#define OS_TOUCH_EVENT_DOWN (2) /* Touch down event */
#define OS_TOUCH_EVENT_MOVE (3) /* Touch move event */| 事件 | 值 | 说明 |
|---|---|---|
OS_TOUCH_EVENT_NONE | 0 | 无触摸事件 |
OS_TOUCH_EVENT_UP | 1 | 手指从触摸屏抬起 |
OS_TOUCH_EVENT_DOWN | 2 | 手指按下触摸屏 |
OS_TOUCH_EVENT_MOVE | 3 | 手指在触摸屏上滑动 |
这四种事件是触摸交互的基础:一次完整触摸的典型序列是 DOWN → MOVE(可多次)→ UP。应用根据事件类型做出不同响应。
注意:
OS_TOUCH_EVENT_MOVE不是所有触摸芯片都会上报(部分芯片只在 DOWN/UP 时触发中断)。BSP 驱动把芯片自己的事件(如 FT6X06 的EVENT_DOWN/UP/CONTACT)映射为这些标准事件(见 ft6x06.c 的ft6x06_read_point)。
3.2 厂商类型
#define OS_TOUCH_VENDOR_UNKNOWN (0) /* unknown */
#define OS_TOUCH_VENDOR_GT (1) /* GTxx series */
#define OS_TOUCH_VENDOR_FT (2) /* FTxx series */| 厂商 | 值 | 说明 |
|---|---|---|
OS_TOUCH_VENDOR_UNKNOWN | 0 | 未知厂商 |
OS_TOUCH_VENDOR_GT | 1 | GTxx 系列(汇顶 Goodix) |
OS_TOUCH_VENDOR_FT | 2 | FTxx 系列(敦泰 FocalTech) |
3.3 触摸 IC 类型
#define OS_TOUCH_TYPE_NONE (0) /* touch ic none */
#define OS_TOUCH_TYPE_CAPACITANCE (1) /* capacitance ic */
#define OS_TOUCH_TYPE_RESISTANCE (2) /* resistance ic */| 类型 | 值 | 说明 |
|---|---|---|
OS_TOUCH_TYPE_NONE | 0 | 无触摸 IC |
OS_TOUCH_TYPE_CAPACITANCE | 1 | 电容式触摸 IC |
OS_TOUCH_TYPE_RESISTANCE | 2 | 电阻式触摸 IC |
3.4 时间戳获取宏
#ifdef OS_USING_RTC
#define os_touch_get_ts() time(OS_NULL) /* API for the touch to get the timestamp */
#else
#define os_touch_get_ts() os_tick_get_value() /* API for the touch to get the timestamp */
#endif| 系统配置 | os_touch_get_ts() 展开 | 说明 |
|---|---|---|
OS_USING_RTC 已定义 | time(OS_NULL) | 使用 RTC 实时时钟时间戳(Unix 时间戳) |
OS_USING_RTC 未定义 | os_tick_get_value() | 使用系统滴答时间戳(tick 计数值) |
关键理解:时间戳来源由编译期宏 OS_USING_RTC 决定。若系统启用 RTC,每条触摸数据带 Unix 时间戳(便于记录绝对时间);否则用系统 tick(相对时间,便于计算间隔)。
3.5 引脚无值宏
#define OS_PIN_NONE 0xFFFF /* RT PIN NONE */OS_PIN_NONE 表示"无有效引脚",值为 0xFFFF。当触摸芯片不使用中断引脚时,os_touch_config.irq_pin 可用该值占位。
四、核心 API 详解
本节逐个拆解 drivers/touch/touch.c 中的核心函数。来源:drivers/touch/touch.c。
4.1 os_hw_touch_register —— 注册 Touch 设备
函数签名
int os_hw_touch_register(os_touch_t *touch, const char *name, void *data)参数 / 返回值说明
| 参数 | 类型 | 说明 |
|---|---|---|
touch | os_touch_t * | Touch 设备结构体指针,必须已填充 ops 和 info 字段 |
name | const char * | 设备名称,如 "touch"、"touch0" |
data | void * | 用户自定义数据指针 |
| 返回值 | 说明 |
|---|---|
OS_SUCCESS | 注册成功 |
| 其他 | 注册失败(os_device_register 的返回值) |
完整源码
int os_hw_touch_register(os_touch_t *touch, const char *name, void *data)
{
int8_t result;
os_device_t *device;
OS_ASSERT(touch != OS_NULL);
touch->info.user_range_x = touch->info.range_x;
touch->info.user_range_y = touch->info.range_y;
device = &touch->parent;
device->ops = &os_touch_ops;
device->type = OS_DEVICE_TYPE_TOUCH;
device->user_data = data;
result = os_device_register(device, name);
if (result != OS_SUCCESS)
{
LOG_E(DRV_EXT_TAG, "os_touch register err code: %d", result);
return result;
}
LOG_I(DRV_EXT_TAG, "os_touch init success");
return OS_SUCCESS;
}分步详解
第一步:初始化用户坐标范围(无条件执行)
int8_t result;
os_device_t *device;
OS_ASSERT(touch != OS_NULL);
touch->info.user_range_x = touch->info.range_x;
touch->info.user_range_y = touch->info.range_y;关键点:把 user_range_x/y 初始化为 range_x/y——即默认"用户范围 = 物理范围"(1:1 映射)。这样若 BSP 不特别设置用户范围,坐标就是原始物理坐标,不会做比例缩放。这也是"防呆"设计。
第二步:配置父设备类型、ops、user_data(无条件执行)
device = &touch->parent;
device->ops = &os_touch_ops;
device->type = OS_DEVICE_TYPE_TOUCH;
device->user_data = data;取内嵌 parent,设为 OS_DEVICE_TYPE_TOUCH 类型,挂上框架层操作表 os_touch_ops(见 4.5),存用户数据。
第三步:注册到设备框架(条件 os_device_register 成功)
result = os_device_register(device, name);
if (result != OS_SUCCESS)
{
LOG_E(DRV_EXT_TAG, "os_touch register err code: %d", result);
return result;
}
LOG_I(DRV_EXT_TAG, "os_touch init success");
return OS_SUCCESS;调用 os_device_register 注册,失败则打印错误日志并返回;成功打印"init success"并返回 OS_SUCCESS。
条件走向总结
os_hw_touch_register() 条件走向总结:
├── OS_ASSERT(touch != OS_NULL)
├── touch->info.user_range_x/y = range_x/y(默认 1:1)
├── 配置 device->ops/type/user_data
├── result = os_device_register(device, name)
│ ├── result != OS_SUCCESS → LOG_E + 返回 result
│ └── result == OS_SUCCESS → 继续
├── LOG_I("os_touch init success")
└── 返回 OS_SUCCESS4.2 os_touch_irq_notify —— 中断通知
函数签名
void os_touch_irq_notify(os_touch_t *touch)参数 / 返回值说明
| 参数 | 类型 | 说明 |
|---|---|---|
touch | os_touch_t * | Touch 设备指针 |
| 返回值 | 说明 |
|---|---|
| 无 | 该函数无返回值,仅做通知 |
完整源码
void os_touch_irq_notify(os_touch_t *touch)
{
OS_ASSERT(touch != OS_NULL);
touch->parent.rx_count = 1;
os_device_recv_notify(&touch->parent);
}分步详解
第一步:断言设备非空(无条件执行)
OS_ASSERT(touch != OS_NULL);第二步:标记有数据并通知读任务(无条件执行)
touch->parent.rx_count = 1;
os_device_recv_notify(&touch->parent);把 rx_count 置为 1(表示"有触摸数据可读"),调用 os_device_recv_notify 唤醒阻塞在 os_device_read_block(等待 rx_sem)的任务。
关键理解——谁调用它:
os_touch_irq_notify由 BSP 触摸芯片驱动的 GPIO 中断回调调用(如 ft6x06.c 的ft6x06_irq_handler里os_touch_irq_notify((os_touch_t *)args))。当触摸芯片 INT 引脚触发中断时,回调调用它通知应用层读取数据。应用层读任务被唤醒后,调用os_device_read_block→os_touch_read读取真实触摸数据。
4.3 框架层读取与坐标映射(os_touch_read)
函数签名
static os_ssize_t os_touch_read(os_device_t *dev, os_off_t pos, void *buf, os_size_t len)参数 / 返回值说明
| 参数 | 类型 | 说明 |
|---|---|---|
dev | os_device_t * | 设备指针 |
pos | os_off_t | 偏移(未使用) |
buf | void * | 传出触摸数据缓冲区(struct os_touch_data 数组) |
len | os_size_t | 请求读取的点数 |
| 返回值 | 说明 |
|---|---|
> 0 | 实际读取的点数(touch_readpoint 的返回值) |
0 | 无数据(buf 为空、len 为 0 或读不到点) |
完整源码
static os_ssize_t os_touch_read(os_device_t *dev, os_off_t pos, void *buf, os_size_t len)
{
os_size_t result = 0;
os_touch_t *touch;
uint32_t range_x, range_y;
struct os_touch_data *read_data;
OS_ASSERT(dev != OS_NULL);
touch = (os_touch_t *)dev;
if (buf == NULL || len == 0)
{
return 0;
}
dev->rx_count = 0;
result = touch->ops->touch_readpoint(touch, buf, len);
if (result == 0)
return 0;
read_data = (struct os_touch_data *)buf;
range_x = touch->info.range_x;
range_y = touch->info.range_y;
#ifdef OS_TOUCH_X_REVERSE
if (read_data->x_coordinate >= touch->info.range_x)
read_data->x_coordinate = 0;
else
read_data->x_coordinate = touch->info.range_x - read_data->x_coordinate;
#endif
#ifdef OS_TOUCH_Y_REVERSE
if (read_data->y_coordinate >= touch->info.range_y)
read_data->y_coordinate = 0;
else
read_data->y_coordinate = touch->info.range_y - read_data->y_coordinate;
#endif
#ifdef OS_TOUCH_XY_SWAP
uint16_t tmp;
tmp = read_data->x_coordinate;
read_data->x_coordinate = read_data->y_coordinate;
read_data->y_coordinate = tmp;
range_x = touch->info.range_y;
range_y = touch->info.range_x;
#endif
if (touch->info.range_x != touch->info.user_range_x)
read_data->x_coordinate = read_data->x_coordinate * touch->info.user_range_x / range_x;
if (touch->info.range_y != touch->info.user_range_y)
read_data->y_coordinate = read_data->y_coordinate * touch->info.user_range_y / range_y;
return result;
}分步详解
第一步:参数校验(条件 buf != NULL 且 len != 0)
OS_ASSERT(dev != OS_NULL);
touch = (os_touch_t *)dev;
if (buf == NULL || len == 0)
{
return 0;
}buf 为空或请求长度 0 时直接返回 0(无法写入数据)。
第二步:清零 rx_count 并读取触摸点(无条件执行)
dev->rx_count = 0;
result = touch->ops->touch_readpoint(touch, buf, len);
if (result == 0)
return 0;
read_data = (struct os_touch_data *)buf;
range_x = touch->info.range_x;
range_y = touch->info.range_y;先清零 rx_count(本次读取消费掉中断标记的数据),调用 touch_readpoint 读取最多 len 个触摸点。若返回 0(无触摸),直接返回 0。取首个触摸数据作为 read_data(框架只对第一个点做坐标映射)。
第三步:X 轴反向(条件编译宏 OS_TOUCH_X_REVERSE)
#ifdef OS_TOUCH_X_REVERSE
if (read_data->x_coordinate >= touch->info.range_x)
read_data->x_coordinate = 0;
else
read_data->x_coordinate = touch->info.range_x - read_data->x_coordinate;
#endif当编译期定义了 OS_TOUCH_X_REVERSE 时,把 X 坐标反向:x' = range_x - x(若 x >= range_x 越界则置 0)。
数值演算(X 反向):设
range_x = 4096,x = 1200:
计算项 代入计算 结果 判断 1200 >= 4096否 反向 4096 - 12002896
第四步:Y 轴反向(条件编译宏 OS_TOUCH_Y_REVERSE)
#ifdef OS_TOUCH_Y_REVERSE
if (read_data->y_coordinate >= touch->info.range_y)
read_data->y_coordinate = 0;
else
read_data->y_coordinate = touch->info.range_y - read_data->y_coordinate;
#endif与 X 反向对称,把 Y 坐标反向:y' = range_y - y。
第五步:X/Y 交换(条件编译宏 OS_TOUCH_XY_SWAP)
#ifdef OS_TOUCH_XY_SWAP
uint16_t tmp;
tmp = read_data->x_coordinate;
read_data->x_coordinate = read_data->y_coordinate;
read_data->y_coordinate = tmp;
range_x = touch->info.range_y;
range_y = touch->info.range_x;
#endif当编译期定义了 OS_TOUCH_XY_SWAP 时,交换 X/Y 坐标(用于横竖屏切换),并同步交换 range_x/range_y(保证后续范围映射正确)。
数值演算(XY 交换):设
x=2896, y=512, range_x=4096, range_y=4096:
操作 代入计算 结果 交换 x' = 512,y' = 2896range_x4096(原 range_y)range_y4096(原 range_x)
第六步:物理→用户范围映射(条件 range_x != user_range_x 等)
if (touch->info.range_x != touch->info.user_range_x)
read_data->x_coordinate = read_data->x_coordinate * touch->info.user_range_x / range_x;
if (touch->info.range_y != touch->info.user_range_y)
read_data->y_coordinate = read_data->y_coordinate * touch->info.user_range_y / range_y;
return result;当物理范围 ≠ 用户范围时,用比例换算映射:x' = x * user_range_x / range_x(range_x 可能已被 XY 交换更新)。
数值演算(范围映射):设
range_x = 4096,user_range_x = 480,x = 2048:
计算项 代入计算 结果 判断 4096 != 480是 映射 2048 * 480 / 4096240
完整映射示例(一次读取的全流程):触摸屏物理范围
4096×4096,屏幕分辨率480×320,设手指按下点为物理(1000, 500),编译期定义了OS_TOUCH_X_REVERSE:
步骤 计算 结果 X 反向 4096 - 1000x = 3096范围映射 X 3096 * 480 / 4096x = 362范围映射 Y 500 * 320 / 4096y = 39最终坐标 (362, 39)
条件走向总结
os_touch_read() 条件走向总结:
├── buf == NULL 或 len == 0 → 返回 0
├── dev->rx_count = 0
├── result = touch_readpoint(touch, buf, len)
│ ├── result == 0(无触摸)→ 返回 0
│ └── result > 0 → 继续
├── OS_TOUCH_X_REVERSE 已定义
│ └── x >= range_x → x = 0;否则 x = range_x - x
├── OS_TOUCH_Y_REVERSE 已定义
│ └── y >= range_y → y = 0;否则 y = range_y - y
├── OS_TOUCH_XY_SWAP 已定义
│ └── 交换 x/y 与 range_x/range_y
├── range_x != user_range_x → x = x * user_range_x / range_x
├── range_y != user_range_y → y = y * user_range_y / range_y
└── 返回 result(点数)4.4 框架层控制入口(os_touch_control)
函数签名
static os_err_t os_touch_control(os_device_t *dev, int cmd, void *args)参数 / 返回值说明
| 参数 | 类型 | 说明 |
|---|---|---|
dev | os_device_t * | 设备指针 |
cmd | int | 控制命令(原始 OS_TOUCH_CTRL_* 值 0~9) |
args | void * | 命令参数指针 |
| 返回值 | 说明 |
|---|---|
OS_SUCCESS | 命令处理成功 |
OS_FAILURE | 参数为空、命令不支持(default)或底层 touch_control 失败 |
完整源码
static os_err_t os_touch_control(os_device_t *dev, int cmd, void *args)
{
os_touch_t *touch;
os_err_t result = OS_SUCCESS;
OS_ASSERT(dev != OS_NULL);
touch = (os_touch_t *)dev;
switch (cmd)
{
case OS_TOUCH_CTRL_GET_ID:
if (args)
result = touch->ops->touch_control(touch, OS_TOUCH_CTRL_GET_ID, args);
else
result = OS_FAILURE;
break;
case OS_TOUCH_CTRL_GET_INFO:
if (args)
result = touch->ops->touch_control(touch, OS_TOUCH_CTRL_GET_INFO, args);
else
result = OS_FAILURE;
break;
case OS_TOUCH_CTRL_SET_MODE:
result = touch->ops->touch_control(touch, OS_TOUCH_CTRL_SET_MODE, args);
break;
case OS_TOUCH_CTRL_SET_X_RANGE:
result = touch->ops->touch_control(touch, OS_TOUCH_CTRL_SET_X_RANGE, args);
if (result == OS_SUCCESS)
{
touch->info.range_x = *(int32_t *)args;
LOG_D(DRV_EXT_TAG, "set x coordinate range :%d\r\n", touch->info.range_x);
}
break;
case OS_TOUCH_CTRL_SET_Y_RANGE:
result = touch->ops->touch_control(touch, OS_TOUCH_CTRL_SET_Y_RANGE, args);
if (result == OS_SUCCESS)
{
touch->info.range_y = *(uint32_t *)args;
LOG_D(DRV_EXT_TAG, "set y coordinate range :%d \r\n", touch->info.range_y);
}
break;
case OS_TOUCH_CTRL_SET_USER_X_RANGE:
touch->info.user_range_x = *(int32_t *)args;
LOG_D(DRV_EXT_TAG, "set user x coordinate range :%d\r\n", touch->info.user_range_x);
break;
case OS_TOUCH_CTRL_SET_USER_Y_RANGE:
touch->info.user_range_y = *(uint32_t *)args;
LOG_D(DRV_EXT_TAG, "set user y coordinate range :%d \r\n", touch->info.user_range_y);
break;
case OS_TOUCH_CTRL_DISABLE_INT:
dev->rx_size = 0;
result = touch->ops->touch_control(touch, OS_TOUCH_CTRL_DISABLE_INT, args);
break;
case OS_TOUCH_CTRL_ENABLE_INT:
dev->rx_size = 1;
result = touch->ops->touch_control(touch, OS_TOUCH_CTRL_ENABLE_INT, args);
break;
default:
return OS_FAILURE;
}
return result;
}分步详解
第一步:按命令分发(条件 cmd 的值)
switch (cmd)
{
case OS_TOUCH_CTRL_GET_ID:
if (args)
result = touch->ops->touch_control(touch, OS_TOUCH_CTRL_GET_ID, args);
else
result = OS_FAILURE;
break;
case OS_TOUCH_CTRL_GET_INFO:
if (args)
result = touch->ops->touch_control(touch, OS_TOUCH_CTRL_GET_INFO, args);
else
result = OS_FAILURE;
break;
case OS_TOUCH_CTRL_SET_MODE:
result = touch->ops->touch_control(touch, OS_TOUCH_CTRL_SET_MODE, args);
break;
case OS_TOUCH_CTRL_SET_X_RANGE:
case OS_TOUCH_CTRL_SET_Y_RANGE:
case OS_TOUCH_CTRL_SET_USER_X_RANGE:
case OS_TOUCH_CTRL_SET_USER_Y_RANGE:
case OS_TOUCH_CTRL_DISABLE_INT:
case OS_TOUCH_CTRL_ENABLE_INT:
/* 这些 case 的完整处理见上方"完整源码" */
break;
default:
return OS_FAILURE;
}switch(cmd) 判断的是原始 OS_TOUCH_CTRL_* 值。关键:OS_TOUCH_CTRL_SET_X_TO_Y(7)没有对应 case,会落到 default 返回 OS_FAILURE——即框架层不处理 X/Y 交换命令(横竖屏交换应通过编译期宏 OS_TOUCH_XY_SWAP,见 4.3)。
第二步:GET_ID/GET_INFO(条件 args 非空)
case OS_TOUCH_CTRL_GET_ID:
if (args)
result = touch->ops->touch_control(touch, OS_TOUCH_CTRL_GET_ID, args);
else
result = OS_FAILURE;
break;args 是传出缓冲区(ID/INFO 写入其中)。args 为空返回 OS_FAILURE(无法写入)。GET_INFO 同理。
第三步:SET_X_RANGE/SET_Y_RANGE(条件底层成功才更新 info)
case OS_TOUCH_CTRL_SET_X_RANGE:
result = touch->ops->touch_control(touch, OS_TOUCH_CTRL_SET_X_RANGE, args);
if (result == OS_SUCCESS)
{
touch->info.range_x = *(int32_t *)args;
LOG_D(DRV_EXT_TAG, "set x coordinate range :%d\r\n", touch->info.range_x);
}
break;先让底层 touch_control 处理,底层成功后才更新 touch->info.range_x。
第四步:SET_USER_X/Y_RANGE(无条件更新 info)
case OS_TOUCH_CTRL_SET_USER_X_RANGE:
touch->info.user_range_x = *(int32_t *)args;
LOG_D(DRV_EXT_TAG, "set user x coordinate range :%d\r\n", touch->info.user_range_x);
break;直接更新 touch->info.user_range_x(不依赖底层,因为这是框架层的数据)。
第五步:DISABLE_INT/ENABLE_INT(先更新 rx_size 再转底层)
case OS_TOUCH_CTRL_DISABLE_INT:
dev->rx_size = 0;
result = touch->ops->touch_control(touch, OS_TOUCH_CTRL_DISABLE_INT, args);
break;
case OS_TOUCH_CTRL_ENABLE_INT:
dev->rx_size = 1;
result = touch->ops->touch_control(touch, OS_TOUCH_CTRL_ENABLE_INT, args);
break;关键点:禁用中断时 dev->rx_size = 0,使能中断时 dev->rx_size = 1。这影响设备框架的阻塞读行为——os_device_read_block 只在 rx_size != 0 时才阻塞等待(见 4.6 关联)。即"使能中断 → 可阻塞等触摸事件;禁用中断 → 读立即返回无数据"。
条件走向总结
os_touch_control() 条件走向总结:
├── switch(cmd)
│ ├── GET_ID / GET_INFO
│ │ ├── args 非空 → 转 touch_control,结果存 result
│ │ └── args 为空 → result = OS_FAILURE
│ ├── SET_MODE → 转 touch_control
│ ├── SET_X_RANGE / SET_Y_RANGE
│ │ ├── touch_control 成功 → 更新 info.range_x/y
│ │ └── touch_control 失败 → 不更新
│ ├── SET_USER_X/Y_RANGE → 直接更新 info.user_range_x/y
│ ├── DISABLE_INT → dev->rx_size = 0,转 touch_control
│ ├── ENABLE_INT → dev->rx_size = 1,转 touch_control
│ └── default(含 SET_X_TO_Y)→ 返回 OS_FAILURE
└── 返回 result4.5 框架层操作表与初始化/反初始化(os_touch_ops / os_touch_init / os_touch_deinit)
操作表 os_touch_ops:
/* clang-format off */
const static struct os_device_ops os_touch_ops =
{
os_touch_init,
os_touch_deinit,
os_touch_read,
OS_NULL,
os_touch_control
};
/* clang-format on */| 槽位 | 值 | 说明 |
|---|---|---|
| init | os_touch_init | 设备打开时初始化 |
| deinit | os_touch_deinit | 设备关闭时反初始化 |
| read | os_touch_read | 读取触摸数据 |
| write | OS_NULL | 无写操作(触摸是输入设备) |
| control | os_touch_control | 控制命令 |
初始化 os_touch_init:
static os_err_t os_touch_init(os_device_t *dev)
{
OS_ASSERT(dev != OS_NULL);
dev->rx_size = 0;
dev->rx_count = 0;
return OS_SUCCESS;
}设备打开时把 rx_size/rx_count 清零(无触摸数据),返回 OS_SUCCESS。
反初始化 os_touch_deinit:
static os_err_t os_touch_deinit(os_device_t *dev)
{
OS_ASSERT(dev != OS_NULL);
/* touch disable interrupt */
os_touch_control(dev, OS_TOUCH_CTRL_DISABLE_INT, 0);
return OS_SUCCESS;
}设备关闭时调用 os_touch_control(dev, OS_TOUCH_CTRL_DISABLE_INT, 0) 禁用中断(并置 rx_size = 0),返回 OS_SUCCESS。
条件走向总结(os_touch_init/os_touch_deinit)
os_touch_init():纯顺序执行(无分支)
├── OS_ASSERT(dev != OS_NULL)
├── dev->rx_size = 0;dev->rx_count = 0
└── 返回 OS_SUCCESS
os_touch_deinit():纯顺序执行(无分支)
├── OS_ASSERT(dev != OS_NULL)
├── os_touch_control(dev, OS_TOUCH_CTRL_DISABLE_INT, 0)(禁用中断)
└── 返回 OS_SUCCESS4.6 关联:设备框架的阻塞读
Touch 设备通过 os_device_read_block 读取,其阻塞逻辑与 5.1 设备框架一致(依赖 dev->rx_size 与 dev->rx_count)。关键关联:
os_touch_irq_notify把rx_count置 1 并os_device_recv_notify唤醒阻塞读者(见 4.2);os_touch_read读数据时把rx_count清零(见 4.3);OS_TOUCH_CTRL_DISABLE_INT把rx_size置 0 → 阻塞读不再等待(见 4.4)。
读流程时序(中断模式):
触摸芯片 INT 触发
│
▼
BSP 中断回调 ft6x06_irq_handler
│ os_touch_irq_notify(touch)
▼
touch->parent.rx_count = 1
os_device_recv_notify(&parent) ──▶ 唤醒阻塞在 os_device_read_block 的任务
│
▼
应用任务 os_device_read_block(dev, 0, &data, len)
│ 调用 dev->ops->read → os_touch_read
▼
dev->rx_count = 0
result = touch_readpoint(touch, buf, len) (读真实触摸点)
坐标映射(反向/交换/范围)→ 返回点数五、使用示例
以下示例基于项目头文件
drivers/touch/touch.h的接口,均含返回值检查。
示例 1:触摸事件处理
#include <touch.h>
#include <device.h>
#include <os_task.h>
#define TOUCH_DEV_NAME "touch"
void touch_demo_task(void *arg)
{
os_device_t *dev;
struct os_touch_data data;
/* 1. 打开触摸设备 */
dev = os_device_open_s(TOUCH_DEV_NAME);
if (dev == OS_NULL)
{
os_kprintf("Failed to open touch device\r\n");
return;
}
while (1)
{
/* 2. 阻塞等待触摸事件(返回值为读取到的点数) */
if (os_device_read_block(dev, 0, &data, 1) > 0)
{
switch (data.event)
{
case OS_TOUCH_EVENT_DOWN:
os_kprintf("Touch DOWN: (%d, %d), track_id=%d, width=%d, ts=%d\r\n",
data.x_coordinate, data.y_coordinate,
data.track_id, data.width, data.timestamp);
break;
case OS_TOUCH_EVENT_MOVE:
os_kprintf("Touch MOVE: (%d, %d), track_id=%d\r\n",
data.x_coordinate, data.y_coordinate,
data.track_id);
break;
case OS_TOUCH_EVENT_UP:
os_kprintf("Touch UP: (%d, %d), track_id=%d\r\n",
data.x_coordinate, data.y_coordinate,
data.track_id);
break;
default:
break;
}
}
}
}实际效果:打开 "touch" 后,os_device_read_block(dev, 0, &data, 1) 阻塞等待触摸事件,len 传 1(请求 1 个点)。返回值是点数(os_touch_read 返回 touch_readpoint 的点数),用 > 0 判断有效。收到 DOWN/MOVE/UP 事件后打印坐标、track_id、宽度和时间戳。
示例 2:获取触摸设备信息
#include <touch.h>
#include <device.h>
void get_touch_info_demo(void)
{
os_device_t *dev;
struct os_touch_info info;
dev = os_device_open_s("touch");
if (dev == OS_NULL) return;
/* 获取触摸设备信息(命令传原始宏,非 IOC_TOUCH) */
os_err_t ret = os_device_control(dev, OS_TOUCH_CTRL_GET_INFO, &info);
if (ret != OS_SUCCESS)
{
os_kprintf("Get touch info failed: %d\r\n", ret);
os_device_close(dev);
return;
}
os_kprintf("Touch Device Info:\r\n");
os_kprintf(" Type: %s\r\n", info.type == OS_TOUCH_TYPE_CAPACITANCE ? "Capacitance" : "Resistance");
os_kprintf(" Vendor: %d\r\n", info.vendor);
os_kprintf(" Point Num: %d\r\n", info.point_num);
os_kprintf(" Range X: %d\r\n", info.range_x);
os_kprintf(" Range Y: %d\r\n", info.range_y);
os_device_close(dev);
}实际效果:os_device_control(dev, OS_TOUCH_CTRL_GET_INFO, &info) 直接传原始 OS_TOUCH_CTRL_GET_INFO,不套 IOC_TOUCH(cmd)——因为 os_touch_control 的 switch 判断的是原始值(见 4.4 关键理解)。成功则打印类型/厂商/点数/范围。
示例 3:坐标校准与映射
#include <touch.h>
#include <device.h>
void calibrate_touch_demo(void)
{
os_device_t *dev;
int32_t x_range = 4096; /* 物理 X 范围 */
int32_t y_range = 4096; /* 物理 Y 范围 */
int32_t user_x = 480; /* 屏幕 X 分辨率 */
int32_t user_y = 320; /* 屏幕 Y 分辨率 */
dev = os_device_open_s("touch");
if (dev == OS_NULL) return;
/* 设置物理坐标范围 */
os_err_t ret = os_device_control(dev, OS_TOUCH_CTRL_SET_X_RANGE, &x_range);
if (ret != OS_SUCCESS)
{
os_kprintf("Set X range failed: %d\r\n", ret);
os_device_close(dev);
return;
}
ret = os_device_control(dev, OS_TOUCH_CTRL_SET_Y_RANGE, &y_range);
if (ret != OS_SUCCESS)
{
os_kprintf("Set Y range failed: %d\r\n", ret);
os_device_close(dev);
return;
}
/* 设置用户坐标范围(映射到屏幕分辨率) */
ret = os_device_control(dev, OS_TOUCH_CTRL_SET_USER_X_RANGE, &user_x);
if (ret != OS_SUCCESS)
{
os_kprintf("Set user X range failed: %d\r\n", ret);
os_device_close(dev);
return;
}
ret = os_device_control(dev, OS_TOUCH_CTRL_SET_USER_Y_RANGE, &user_y);
if (ret != OS_SUCCESS)
{
os_kprintf("Set user Y range failed: %d\r\n", ret);
os_device_close(dev);
return;
}
os_kprintf("Touch calibrated: phys(%d,%d) -> user(%d,%d)\r\n",
x_range, y_range, user_x, user_y);
os_device_close(dev);
}实际效果:设置物理范围 4096×4096、用户范围 480×320。之后 os_touch_read 会把物理坐标按比例映射到屏幕分辨率(x * 480 / 4096)。每条 os_device_control 都检查返回值。
示例 4:注册自定义 Touch 设备(BSP 驱动视角)
#include <touch.h>
#include <device.h>
#include <os_memory.h>
/* 底层 Touch 操作实现 */
static os_size_t my_touch_readpoint(struct os_touch_device *touch, struct os_touch_data *data, os_size_t touch_num)
{
/* 从硬件读取触摸点数据,返回读取到的点数 */
if (touch_num < 1)
return 0;
data[0].event = OS_TOUCH_EVENT_DOWN;
data[0].track_id = 0;
data[0].width = 10;
data[0].x_coordinate = 100;
data[0].y_coordinate = 200;
data[0].timestamp = os_touch_get_ts();
return 1; /* 返回读取到的点数 */
}
static os_err_t my_touch_control(struct os_touch_device *touch, int cmd, void *arg)
{
switch (cmd)
{
case OS_TOUCH_CTRL_GET_INFO:
*(struct os_touch_info *)arg = touch->info;
break;
case OS_TOUCH_CTRL_GET_ID:
*(uint16_t *)arg = 0x6A06; /* 芯片 ID */
break;
case OS_TOUCH_CTRL_SET_X_RANGE:
touch->info.range_x = *(int32_t *)arg;
break;
case OS_TOUCH_CTRL_SET_Y_RANGE:
touch->info.range_y = *(int32_t *)arg;
break;
default:
break;
}
return OS_SUCCESS;
}
static const struct os_touch_ops my_touch_ops =
{
.touch_readpoint = my_touch_readpoint,
.touch_control = my_touch_control,
};
static struct os_touch_device my_touch;
void my_touch_init(void)
{
/* 填充设备信息 */
my_touch.info.type = OS_TOUCH_TYPE_CAPACITANCE;
my_touch.info.vendor = OS_TOUCH_VENDOR_GT;
my_touch.info.point_num = 1;
my_touch.info.range_x = 4096;
my_touch.info.range_y = 4096;
/* 绑定操作函数表 */
my_touch.ops = &my_touch_ops;
/* 注册到设备框架(user_range_x/y 会被初始化为 range_x/y) */
os_err_t ret = os_hw_touch_register(&my_touch, "touch", OS_NULL);
if (ret != OS_SUCCESS)
{
os_kprintf("Touch register failed: %d\r\n", ret);
return;
}
os_kprintf("Touch device registered\r\n");
}实际效果:实现 os_touch_ops 两个函数后,填充 info(类型/厂商/点数/物理范围)与 ops,os_hw_touch_register 注册为 "touch"。注意不需要手动设置 user_range_x/y——os_hw_touch_register 会自动初始化为 range_x/y(见 4.1)。
示例 5:中断驱动的触摸处理
#include <touch.h>
#include <device.h>
#include <pin/pin.h>
/* 触摸芯片 INT 引脚中断回调(由 BSP 注册到 os_pin_attach_irq) */
void touch_irq_handler(void *args)
{
/* 通知上层有触摸数据可读 */
os_touch_irq_notify((os_touch_t *)args);
}
/* 触摸读取任务 */
void touch_read_task(void *arg)
{
os_device_t *dev;
struct os_touch_data data;
dev = os_device_open_s("touch");
if (dev == OS_NULL) return;
while (1)
{
/* 阻塞等待,被中断通知唤醒 */
if (os_device_read_block(dev, 0, &data, 1) > 0)
{
if (data.event != OS_TOUCH_EVENT_NONE)
{
os_kprintf("event=%d, (%d,%d)\r\n",
data.event, data.x_coordinate, data.y_coordinate);
}
}
}
}实际效果:触摸芯片 INT 引脚触发中断 → touch_irq_handler 调 os_touch_irq_notify 置 rx_count=1 并唤醒 touch_read_task → 任务 os_device_read_block 读数据。这种"中断通知 + 阻塞读"模式无需轮询,CPU 高效。
六、更换触摸芯片(Touch 驱动移植)—— 全局视角
本节从全局视角讲解:当你要换一款触摸芯片(或换一颗 MCU 导致触摸驱动失效)时,OneOS 的 Touch 驱动要做什么才能在新芯片上跑起来。核心结论与 5.2~5.5 一致:应用层与框架层完全不用动,只需要新写/移植 BSP 层触摸芯片驱动。
在动手移植之前,先认识 BSP 层触摸驱动文件到底承担了什么职责——这是理解移植步骤的前提。
6.1 认识 BSP 层驱动:ft6x06.c / gt9x.c 等
BSP 层的触摸驱动位于 drivers/touch/,每个触摸芯片一个 .c 文件(如 ft6x06.c、gt9x.c、stmpe811.c、ft5x06.c、ft3x67.c)。以 FT6X06(drivers/touch/ft6x06.c)为例,它做了这几件事:
(1)定义"触摸芯片实例"结构体 struct ft6x06_touch
struct ft6x06_touch
{
os_touch_t touch_device;
struct os_i2c_bus_device *i2c_bus;
uint16_t i2c_addr;
uint16_t id;
uint8_t currActiveTouchNb;
uint8_t currActiveTouchIdx;
};touch_device:内嵌的os_touch_t,是注册进设备框架的"公共部分";i2c_bus/i2c_addr:触摸芯片所连接的 I2C 总线与从机地址(FT6X06 通过 I2C 通信);id:芯片 ID(用于校验驱动匹配)。
(2)实现 I2C 读写助手函数
static uint8_t TS_IO_Read(struct ft6x06_touch *ft6x06, uint8_t reg)
{
unsigned char value;
os_i2c_master_send(ft6x06->i2c_bus, ft6x06->i2c_addr, 0, ®, 1);
os_i2c_master_recv(ft6x06->i2c_bus, ft6x06->i2c_addr, 0, &value, 1);
return value;
}用 I2C 总线读写触摸芯片的寄存器(os_i2c_master_send/recv,见 5.3 I2C 设备)。
(3)实现 struct os_touch_ops 的两个函数
ft6x06_read_point 读取触摸寄存器并把芯片事件映射为标准事件:
static os_size_t ft6x06_read_point(struct os_touch_device *touch, struct os_touch_data *data, os_size_t read_num)
{
uint16_t input_x = 0;
uint16_t input_y = 0;
uint16_t event = FT6X06_EVENT_INVALID;
struct ft6x06_touch *ft6x06 = (struct ft6x06_touch *)touch;
ft6x06_GetXY(ft6x06, &input_x, &input_y, &event);
switch (event)
{
case FT6X06_EVENT_DOWN:
data->event = OS_TOUCH_EVENT_DOWN;
break;
case FT6X06_EVENT_UP:
data->event = OS_TOUCH_EVENT_UP;
break;
case FT6X06_EVENT_CONTACT:
data->event = OS_TOUCH_EVENT_MOVE;
break;
default:
data->event = OS_TOUCH_EVENT_NONE;
return 0;
}
data->timestamp = os_touch_get_ts();
data->track_id = 0;
data->x_coordinate = input_x;
data->y_coordinate = input_y;
return 1;
}ft6x06_control 处理 OS_TOUCH_CTRL_GET_ID(读芯片 ID)、GET_INFO(拷贝 info)、ENABLE/DISABLE_INT(os_pin_irq_enable 开关中断引脚)。
(4)写初始化函数并注册
static int os_hw_ft6x06_init(void)
{
struct ft6x06_touch *ft6x06 = os_calloc(1, sizeof(struct ft6x06_touch));
OS_ASSERT(ft6x06);
ft6x06->i2c_bus = os_i2c_bus_device_find(OS_FT6X060_I2C_BUS_NAME);
OS_ASSERT(ft6x06->i2c_bus);
ft6x06->i2c_addr = FT6X06_I2C_ADDRESS;
ft6x06->id = ft6x06_ReadID(ft6x06);
if (ft6x06->id != FT6206_ID_VALUE)
{
ft6x06->i2c_addr = FT6X06_I2C_ADDRESS_A02;
ft6x06->id = ft6x06_ReadID(ft6x06);
if (ft6x06->id != FT6206_ID_VALUE)
{
os_kprintf("invalid ft6x06\r\n");
}
}
ft6x06_Reset(ft6x06);
ft6x06_TS_Start(ft6x06);
os_touch_t *touch_device = &ft6x06->touch_device;
touch_device->info.type = OS_TOUCH_TYPE_CAPACITANCE;
touch_device->info.vendor = OS_TOUCH_VENDOR_UNKNOWN;
touch_device->info.point_num = 1;
touch_device->info.range_x = 480;
touch_device->info.range_y = 800;
touch_device->ops = &ft6x06_touch_ops;
os_hw_touch_register(touch_device, "touch", ft6x06);
/* set irq handle */
os_pin_mode(OS_FT6X06_IRQ_PIN, PIN_MODE_INPUT_PULLUP);
os_pin_attach_irq(OS_FT6X06_IRQ_PIN, PIN_IRQ_MODE_RISING, ft6x06_irq_handler, (void *)touch_device);
os_pin_irq_enable(OS_FT6X06_IRQ_PIN, PIN_IRQ_DISABLE);
return 0;
}
OS_INIT_CALL(os_hw_ft6x06_init, OS_INIT_LEVEL_COMPONENT, OS_INIT_SUBLEVEL_LOW);说明:上面 init 片段完整展示了
os_hw_ft6x06_init的主线(找 I2C 总线 → ID 校验 → 填 info 与 ops →os_hw_touch_register→ 配置中断引脚),完整实现(含ft6x06_TS_Start内部的硬件配置)见项目drivers/touch/ft6x06.c。移植时关键是把握这条主线。
(5)"根据所选触摸芯片决定"到底是怎么决定的?—— 三层选择机制
项目不是把所有触摸驱动都编译进去,而是通过 Kconfig + weave.yaml 只编译所选芯片的驱动:
第一层:Kconfig 选择芯片型号(drivers/touch/Kconfig)
menu "Touch"
config OS_USING_TOUCH
bool "Using Touch device drivers"
default n
if OS_USING_TOUCH
config OS_TOUCH_X_REVERSE
bool "x coordinate reverse"
default n
config OS_TOUCH_Y_REVERSE
bool "y coordinate reverse"
default n
config OS_TOUCH_XY_SWAP
bool "xy coordinate swap"
default n
menuconfig OS_USING_GT9X
bool "Enable gt9x"
select OS_USING_I2C
select BSP_USING_I2C
default n
if OS_USING_GT9X
config OS_GT9X_I2C_BUS_NAME
string "gt9x i2c bus name"
default "soft_i2c1"
config OS_GT9X_I2C_ADDR
hex "gt9x i2c addr"
default 0x28
config OS_GT9X_RST_PIN
int "gt9x rst pin"
default 136
config OS_GT9X_IRQ_PIN
int "gt9x irq pin"
default 119
endif
menuconfig OS_USING_FT6X06
bool "Enable ft6x06"
select OS_USING_I2C
select BSP_USING_I2C
default n
if OS_USING_FT6X06
config OS_FT6X060_I2C_BUS_NAME
string "ft6x06 i2c bus name"
default "i2c2"
config OS_FT6X06_I2C_ADDR
hex "ft6x06 i2c addr(7bit)"
default 0x2a
config OS_FT6X06_IRQ_PIN
int "ft6x06 irq pin"
default 149
endif
# 其余芯片(stmpe811 / ft5x06 / ft3x67)配置结构与上面类似
endif
endmenu注意:
OS_TOUCH_X_REVERSE/OS_TOUCH_Y_REVERSE/OS_TOUCH_XY_SWAP这三个编译期宏在 Kconfig 里配置,对应os_touch_read里的#ifdef坐标变换(见 4.3)。选中 GT9X 会自动select OS_USING_I2C和select BSP_USING_I2C(因为触摸芯片走 I2C)。
第二层:weave.yaml 决定哪些源文件被编译(drivers/touch/weave.yaml)
# 组名
group_name: touch
# 依赖宏控
depend_macro:
- OS_USING_TOUCH
# 源码
source_file:
- touch.c ? {is_define("OS_USING_TOUCH")}
- gt9x.c ? {is_define("OS_USING_GT9X")}
- ft6x06.c ? {is_define("OS_USING_FT6X06")}
- stmpe811.c ? {is_define("OS_USING_STMPE811")}
- ft5x06.c ? {is_define("OS_USING_FT5X06")}
- ft3x67.c ? {is_define("OS_USING_FT3X67")}touch.c 由 OS_USING_TOUCH 控制(框架层必编译);gt9x.c 由 OS_USING_GT9X 控制;ft6x06.c 由 OS_USING_FT6X06 控制。每个芯片驱动平级、各按自己的宏条件编译——这正是"每个 drv_xxx.c 平级实现、各自条件编译"的体现。
6.2 移植前的全局视角:分层全景图
回顾 Touch 设备的完整分层(本书 1.1 节已详解):应用层 → Touch 框架层(touch.c)→ BSP 触摸芯片驱动(ft6x06.c/gt9x.c)→ 硬件层。
移植的关键洞察:
- 只有 BSP 触摸芯片驱动和具体芯片强相关(依赖芯片的 I2C/SPI 寄存器、坐标格式、中断引脚)。
- 框架层和应用层与具体芯片无关(只依赖
touch.h定义的抽象接口)。 - 所以"换触摸芯片"本质上 = 新写/移植一个 BSP 触摸驱动文件(实现
struct os_touch_ops两个函数 + init 注册)。
6.3 移植需要提供的"接口契约"
要移植,必须先满足 BSP 层与框架层之间的契约——也就是 struct os_touch_ops 的两个函数(见 2.5 节)必须全部实现,并映射到新芯片的硬件能力:
| ops 函数 | 需实现的能力 | 新芯片上对应什么 |
|---|---|---|
touch_readpoint | 读取触摸点数据,返回点数;把芯片事件映射为标准事件(DOWN/UP/MOVE/NONE) | 通过 I2C/SPI 读触摸坐标寄存器,解析事件/坐标/track_id |
touch_control | 处理 OS_TOUCH_CTRL_GET_ID/GET_INFO/ENABLE_INT/DISABLE_INT 等 | 读芯片 ID 寄存器、配置中断引脚 |
此外还需要:
- 触摸芯片实例结构体
struct xxx_touch:内嵌os_touch_t,扩展 I2C/SPI 总线指针、地址、ID、内部状态(如 FT 的struct ft6x06_touch)。 - 通信助手函数:通过 I2C/SPI 读写芯片寄存器的函数(如
TS_IO_Read/TS_IO_Write)。 - init/初始化函数:找总线、读 ID 校验、填 info 与 ops、
os_hw_touch_register、配置中断引脚,并通过OS_INIT_CALL让系统自动调用。 - 中断回调:触摸芯片 INT 引脚的中断回调里调用
os_touch_irq_notify(见 4.2)。
6.4 移植的标准流程(6 步)
下面以"为 FT6X06 同类芯片编写 Touch 驱动"为例,给出通用流程:
第 1 步:定义触摸芯片实例结构体
struct my_touch
{
os_touch_t touch_device; /* 框架层公共部分 */
struct os_i2c_bus_device *i2c_bus; /* 所连 I2C 总线 */
uint16_t i2c_addr; /* I2C 从机地址 */
uint16_t id; /* 芯片 ID */
};第 2 步:实现 I2C 读写助手
static uint8_t my_io_read(struct my_touch *touch, uint8_t reg)
{
unsigned char value;
os_i2c_master_send(touch->i2c_bus, touch->i2c_addr, 0, ®, 1);
os_i2c_master_recv(touch->i2c_bus, touch->i2c_addr, 0, &value, 1);
return value;
}第 3 步:实现 touch_readpoint(映射事件 + 填坐标)
static os_size_t my_read_point(struct os_touch_device *touch, struct os_touch_data *data, os_size_t read_num)
{
if (read_num < 1)
return 0;
uint8_t status = my_io_read((struct my_touch *)touch, MY_TOUCH_STATUS_REG);
/* 无触摸 */
if ((status & MY_TOUCH_STATUS_MASK) == 0)
{
data->event = OS_TOUCH_EVENT_NONE;
return 0;
}
/* 把芯片事件映射为标准事件 */
switch (status & MY_TOUCH_EVENT_MASK)
{
case MY_TOUCH_EVENT_DOWN:
data->event = OS_TOUCH_EVENT_DOWN;
break;
case MY_TOUCH_EVENT_UP:
data->event = OS_TOUCH_EVENT_UP;
break;
default:
data->event = OS_TOUCH_EVENT_MOVE;
break;
}
data->timestamp = os_touch_get_ts();
data->track_id = 0;
data->x_coordinate = my_io_read((struct my_touch *)touch, MY_TOUCH_X_REG);
data->y_coordinate = my_io_read((struct my_touch *)touch, MY_TOUCH_Y_REG);
return 1; /* 返回点数 */
}关键点:必须把芯片自己的事件码映射为标准事件(OS_TOUCH_EVENT_DOWN/UP/MOVE/NONE),且 data->timestamp 用 os_touch_get_ts(),并返回点数。
第 4 步:实现 touch_control
static os_err_t my_control(struct os_touch_device *touch, int cmd, void *data)
{
switch (cmd)
{
case OS_TOUCH_CTRL_GET_ID:
*(uint16_t *)data = ((struct my_touch *)touch)->id;
break;
case OS_TOUCH_CTRL_GET_INFO:
*(struct os_touch_info *)data = touch->info;
break;
case OS_TOUCH_CTRL_ENABLE_INT:
os_pin_irq_enable(MY_TOUCH_IRQ_PIN, PIN_IRQ_ENABLE);
break;
case OS_TOUCH_CTRL_DISABLE_INT:
os_pin_irq_enable(MY_TOUCH_IRQ_PIN, PIN_IRQ_DISABLE);
break;
default:
break;
}
return OS_SUCCESS;
}第 5 步:实现 init 并注册
static void my_touch_irq_handler(void *args)
{
os_touch_irq_notify((os_touch_t *)args);
}
static int os_hw_my_touch_init(void)
{
struct my_touch *touch = os_calloc(1, sizeof(struct my_touch));
OS_ASSERT(touch);
touch->i2c_bus = os_i2c_bus_device_find("i2c2");
OS_ASSERT(touch->i2c_bus);
touch->i2c_addr = MY_TOUCH_I2C_ADDR;
touch->id = my_io_read(touch, MY_TOUCH_ID_REG);
if (touch->id != MY_TOUCH_ID_VALUE)
{
os_kprintf("invalid touch chip\r\n");
return -1;
}
os_touch_t *touch_device = &touch->touch_device;
touch_device->info.type = OS_TOUCH_TYPE_CAPACITANCE;
touch_device->info.vendor = OS_TOUCH_VENDOR_UNKNOWN;
touch_device->info.point_num = 1;
touch_device->info.range_x = 480;
touch_device->info.range_y = 800;
touch_device->ops = &my_touch_ops;
os_err_t ret = os_hw_touch_register(touch_device, "touch", touch);
if (ret != OS_SUCCESS)
return ret;
/* 配置中断引脚 */
os_pin_mode(MY_TOUCH_IRQ_PIN, PIN_MODE_INPUT_PULLUP);
os_pin_attach_irq(MY_TOUCH_IRQ_PIN, PIN_IRQ_MODE_RISING, my_touch_irq_handler, (void *)touch_device);
os_pin_irq_enable(MY_TOUCH_IRQ_PIN, PIN_IRQ_DISABLE);
return 0;
}
OS_INIT_CALL(os_hw_my_touch_init, OS_INIT_LEVEL_COMPONENT, OS_INIT_SUBLEVEL_LOW);关键点:init 里找 I2C 总线 → 读 ID 校验 → 填 info 与 ops → os_hw_touch_register → 配置中断引脚,并通过 OS_INIT_CALL 挂载让系统自动调用。中断回调里调 os_touch_irq_notify(见 4.2)。
第 6 步:验证
移植完成后,用本节"示例 1(触摸事件处理)"或"示例 5(中断驱动的触摸处理)"验证:
dev = os_device_open_s("touch");
if (dev == OS_NULL) return;
struct os_touch_data data;
if (os_device_read_block(dev, 0, &data, 1) > 0)
os_kprintf("event=%d, (%d,%d)\r\n", data.event, data.x_coordinate, data.y_coordinate);- 手指按下能读到 DOWN 事件 →
touch_readpoint事件映射正确; - 坐标与屏幕吻合 → 物理坐标正确,且
range_x/y设置正确(os_touch_read做范围映射); - 中断模式能自动唤醒 → 中断回调 +
os_touch_irq_notify正确。
6.5 触摸芯片未适配时该怎么办?(补型号还是新建驱动)
| 情况 | 判断标准 | 工作量大不大 |
|---|---|---|
| 情况 A:已有同类芯片驱动可参考 | drivers/touch/ 里有类似芯片(如都有 FT 系列),寄存器结构相似 | 小,改寄存器地址/映射即可 |
| 情况 B:全新触摸芯片 | 项目 drivers/touch/ 完全没有该芯片 | 大,按 6.4 的 6 步从零写 |
情况 A(参考同类驱动):如从 FT6X06 换 FT5X06,两者寄存器结构相近,可复制 ft6x06.c 改寄存器地址、ID、坐标解析、触点数。
情况 B(全新芯片):按 6.4 的 6 步从零写,并注意:
- 在
drivers/touch/weave.yaml加一行my_touch.c ? {is_define("OS_USING_MY_TOUCH")}(见 6.1 第二层); - 在
drivers/touch/Kconfig加menuconfig OS_USING_MY_TOUCH,并select OS_USING_I2C/select BSP_USING_I2C(若走 I2C); - 若芯片走 SPI 而非 I2C,把通信助手函数改为 SPI 读写。
6.6 移植检查清单(快速自查)
| 检查项 | 是否满足 |
|---|---|
struct os_touch_ops 的 touch_readpoint/touch_control 已实现并绑定 | ☐ |
touch_readpoint 把芯片事件映射为标准事件(DOWN/UP/MOVE/NONE) | ☐ |
touch_readpoint 用 os_touch_get_ts() 填时间戳并返回点数 | ☐ |
touch_control 处理 GET_ID/GET_INFO/ENABLE_INT/DISABLE_INT | ☐ |
init 里 os_hw_touch_register 前已填 info(type/vendor/point_num/range)与 ops | ☐ |
中断回调调用 os_touch_irq_notify | ☐ |
OS_INIT_CALL 挂载 init,确保应用使用前已注册 | ☐ |
| 新芯片已在 weave.yaml 加条件编译、Kconfig 加选择项 | ☐ |
6.7 移植的价值与注意事项
价值:一旦 BSP 触摸驱动移植完成,应用层代码一行不改即可在不同触摸芯片间复用(FT/GT/STMPE 只需换 weave.yaml 选择的驱动文件)。
注意事项:
- 事件必须映射为标准事件:芯片自己的事件码(如 FT 的
EVENT_DOWN/UP/CONTACT)必须映射为OS_TOUCH_EVENT_DOWN/UP/MOVE/NONE,否则应用层 switch 不识别。 - 返回语义是"点数":
touch_readpoint返回点数(0 表示无触摸),os_touch_read直接透传,别误返回字节数。 range_x/y必须是物理范围:os_touch_read基于它做范围映射,填错会导致坐标错乱。user_range_x/y不用手动设:os_hw_touch_register自动初始化为range_x/y。- 命令传原始宏:应用层
os_device_control传OS_TOUCH_CTRL_*原始值,不是IOC_TOUCH(cmd)(见 4.4)。 SET_X_TO_Y命令框架层不处理:横竖屏 X/Y 交换用编译期宏OS_TOUCH_XY_SWAP(见 4.3)。
📝 本节小结
核心要点回顾
- 分层抽象设计:Touch 设备基于 5.1 设备框架,
os_touch_device内嵌os_device,通过os_touch_ops与具体触摸芯片解耦。 - 事件类型:
OS_TOUCH_EVENT_NONE/UP/DOWN/MOVE四种标准事件,一次触摸的典型序列是 DOWN → MOVE → UP。 - 物理/用户坐标:
range_x/y是物理范围,user_range_x/y是用户范围,默认 1:1,os_touch_read按比例映射。 - 坐标变换在框架层:
os_touch_read里用编译期宏OS_TOUCH_X_REVERSE/Y_REVERSE/XY_SWAP做反向/交换 + 运行时范围映射。 - 中断通知:
os_touch_irq_notify置rx_count=1并os_device_recv_notify唤醒阻塞读者(见 4.2)。 - 注册流程:
os_hw_touch_register初始化 user_range、配置设备类型/ops、注册设备框架(见 4.1)。 - 控制命令:
os_touch_control判断原始OS_TOUCH_CTRL_*值;SET_X_TO_Y(7)无对应 case 返回OS_FAILURE;DISABLE/ENABLE_INT会同步改dev->rx_size。 - 更换触摸芯片移植(见第六章):移植核心 = 实现
os_touch_ops两个函数 + init 注册 + 中断回调;应用层与框架层不用改;换芯片本质是换 weave.yaml 选择的那个drv_xxx.c。
关键易错点速查
| 易错点 | 正确做法 |
|---|---|
os_device_control 传 IOC_TOUCH(cmd) | os_touch_control switch 判断的是原始值,IOC_TOUCH 编码不匹配 → 应传 OS_TOUCH_CTRL_* 原始宏 |
os_device_read_block 返回值误判为字节数 | 返回值是点数(touch_readpoint 返回值)→ 用 > 0 判断 |
期望 SET_X_TO_Y 命令交换坐标 | 框架层不处理该命令(返回 OS_FAILURE)→ 用编译期宏 OS_TOUCH_XY_SWAP |
| BSP 没映射芯片事件 | 芯片事件码直接进 data.event,应用层 switch 不识别 → 必须映射为标准事件 |
range_x/y 填错 | os_touch_read 基于它做范围映射 → 必须填物理范围 |
手动设置 user_range_x/y | 注册时会自动初始化为 range_x/y → 无需手动设(除非要自定义映射) |
中断回调没调 os_touch_irq_notify | 阻塞读者永远等不到数据 → 中断回调必须调它 |
改了 range_x 却没同步 user_range_x | 范围映射基于两者的比例 → 校准后按需设置两者 |
| 移植新芯片忘加 weave.yaml/Kconfig | 驱动不编译/无法选择 → 记得加 is_define 与 menuconfig |
下一步
- 5.7 Timer设备:了解 OneOS 硬件定时器设备的定时器模式、超时回调、PWM 输出与编码器模式。