1. 硬件接线

img

硬件接线如上图所示

大屏为edp屏幕,分辨率为19201080
小屏为hdmi屏幕,分辨率为1024
768

屏幕触摸均通过串口上报数据,大屏触摸数据节点为/dev/ttyS4,小屏触摸数据节点为/dev/ttyS5

2. 文档解析

根据屏幕规格书解析串口接收设备

img

1
2
3
4
5
6
7
8
9
$ cat /dev/ttyS4 | hexdump -C
00000000 55 54 81 1a 07 39 04 ff 00 31 55 54 82 1a 07 39 |UT...9...1UT...9|
00000010 04 ff 00 32 55 54 82 1a 07 39 04 ff 00 32 55 54 |...2UT...9...2UT|
00000020 82 1a 07 39 04 ff 00 32 55 54 82 1a 07 39 04 ff |...9...2UT...9..|
00000030 00 32 55 54 82 1a 07 39 04 ff 00 32 55 54 84 1a |.2UT...9...2UT..|
00000040 07 39 04 ff 00 34 55 54 81 c6 05 48 07 ff 00 ed |.9...4UT...H....|
00000050 55 54 82 c6 05 48 07 ff 00 ee 55 54 82 c6 05 48 |UT...H....UT...H|
00000060 07 ff 00 ee 55 54 82 c6 05 48 07 ff 00 ee 55 54 |....UT...H....UT|
00000070 82 c6 05 48 07 ff 00 ee 55 54 84 c6 05 48 07 ff |...H....UT...H..|

对比规格书确定上报格式无误。

3. 软件调试

软件调试可细分为驱动层和应用层,由于触摸数据是通过串口进行上报的,如果在驱动中直接处理串口数据会很麻烦,而且后续的校准也不方便。所以现在软件的处理方法是驱动层注册input设备节点,应用层读取并解析串口设备并写入input节点。

3.1. 驱动层

先以单个屏幕进行调试,两个屏幕的调试思路都是一样的

编写regist_uarttp1.c驱动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
#include <linux/module.h>
#include <linux/init.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/input.h>
#include <linux/slab.h>
#include <linux/input/mt.h>

#define DEVICE_NAME "uart_tp_4"
#define MAX_ABS_X 800 // 自定义X轴最大值
#define MAX_ABS_Y 1280 // 自定义Y轴最大值

static struct input_dev *uarttp_input_dev; // input设备结构体
static struct miscdevice my_misc_device; // 提前声明misc设备(解决this_device访问问题)

// 设备读操作
static ssize_t my_misc_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) {
char msg[] = "Hello from misc device!\n";
size_t len = strlen(msg);

if (*ppos >= len) // 已经读完
return 0;

if (count > len - *ppos)
count = len - *ppos;

if (copy_to_user(buf, msg + *ppos, count))
return -EFAULT;

*ppos += count;
return count;
}

// 模拟上报触摸事件
static void uarttp_report_touch(int x, int y, int pressure) {
if (!uarttp_input_dev)
return;

if (pressure > 0) {
input_mt_slot(uarttp_input_dev, 0);
input_report_abs(uarttp_input_dev, ABS_MT_TRACKING_ID, 0);
input_report_abs(uarttp_input_dev, ABS_MT_POSITION_X, x);
input_report_abs(uarttp_input_dev, ABS_MT_POSITION_Y, y);
input_report_abs(uarttp_input_dev, ABS_MT_TOUCH_MAJOR, pressure);
input_report_abs(uarttp_input_dev, ABS_MT_WIDTH_MAJOR, pressure);
input_report_key(uarttp_input_dev, BTN_TOUCH, 1); // 按下触摸键
} else {
// 释放触摸
input_mt_slot(uarttp_input_dev, 0);
input_report_abs(uarttp_input_dev, ABS_MT_TRACKING_ID, -1);
input_report_key(uarttp_input_dev, BTN_TOUCH, 0);
}

// 同步事件
input_sync(uarttp_input_dev);
}

// write接口
static ssize_t my_misc_write(struct file *file, const char __user *buf,
size_t count, loff_t *ppos) {
char data[20];
char *p = NULL;
char *ptr;
int cnt = 0;
int x, y, pressure;

memset(data, 0, sizeof(data));

if (count <= 20 && copy_from_user(data, buf, count) == 0) {
ptr = data;
do {
p = strsep(&ptr, ","); // 替换strsplit为内核标准strsep函数
if (p != NULL) {
cnt++;
switch (cnt) {
case 1:
x = simple_strtoull(p, NULL, 10);
break;
case 2:
y = simple_strtoull(p, NULL, 10);
break;
case 3:
pressure = simple_strtoull(p, NULL, 10);
// 校验范围
x = clamp(x, 0, MAX_ABS_X);
y = clamp(y, 0, MAX_ABS_Y);
pressure = clamp(pressure, 0, 255);
uarttp_report_touch(x, y, pressure);
return count;
default:
return -EINVAL;
}
}
} while (p != NULL);
}
return -EINVAL;
}

// 文件操作集
static const struct file_operations my_misc_fops = {
.owner = THIS_MODULE,
.read = my_misc_read,
.write = my_misc_write,
};

// 定义并初始化 miscdevice
static struct miscdevice my_misc_device = {
.minor = MISC_DYNAMIC_MINOR, // 动态分配次设备号
.name = DEVICE_NAME, // /dev/uart_tp_4
.fops = &my_misc_fops,
.mode = 0666, // 设备权限
};

// 初始化input设备(修复核心编译错误)
static int __init uarttp_input_init(void) {
int ret;

// 1. 修正:传递正确的device指针
uarttp_input_dev = devm_input_allocate_device(my_misc_device.this_device);
if (!uarttp_input_dev) {
pr_err("Failed to allocate input device\n");
return -ENOMEM;
}

// 2. 设置input设备基本属性
uarttp_input_dev->name = "UART TP Input Device";
uarttp_input_dev->phys = "uart_tp/input0";
uarttp_input_dev->id.bustype = BUS_VIRTUAL;
uarttp_input_dev->dev.parent = my_misc_device.this_device;

// 3. 设置input事件支持
__set_bit(EV_KEY, uarttp_input_dev->evbit);
__set_bit(EV_ABS, uarttp_input_dev->evbit);
__set_bit(BTN_TOUCH, uarttp_input_dev->keybit);
uarttp_input_dev->evbit[0] = BIT_MASK(EV_SYN) | BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);

// 4. 多点触摸配置
input_mt_init_slots(uarttp_input_dev, 16, 0);
__set_bit(INPUT_PROP_DIRECT, uarttp_input_dev->propbit);

// 5. 设置绝对坐标参数
input_set_abs_params(uarttp_input_dev, ABS_MT_POSITION_X, 0, MAX_ABS_X, 0, 0);
input_set_abs_params(uarttp_input_dev, ABS_MT_POSITION_Y, 0, MAX_ABS_Y, 0, 0);
input_set_abs_params(uarttp_input_dev, ABS_MT_PRESSURE, 0, 255, 0, 0);
input_set_abs_params(uarttp_input_dev, ABS_MT_WIDTH_MAJOR, 0, 255, 0, 0);
input_set_abs_params(uarttp_input_dev, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0);
input_set_abs_params(uarttp_input_dev, ABS_MT_TRACKING_ID, 0, 255, 0, 0);

// 6. 注册input设备
ret = input_register_device(uarttp_input_dev);
if (ret) {
pr_err("Failed to register input device: %d\n", ret);
input_free_device(uarttp_input_dev);
uarttp_input_dev = NULL;
return ret;
}

pr_info("UART TP input device registered\n");
return 0;
}

// 模块加载
static int __init my_misc_init(void) {
int ret;

// 1. 注册misc设备
ret = misc_register(&my_misc_device);
if (ret) {
pr_err("Failed to register misc device\n");
return ret;
}

// 2. 注册input设备
ret = uarttp_input_init();
if (ret) {
misc_deregister(&my_misc_device);
return ret;
}

pr_info("my_misc device registered at /dev/%s\n", DEVICE_NAME);
return 0;
}

// 模块卸载
static void __exit my_misc_exit(void) {
// 注销input设备
if (uarttp_input_dev) {
input_unregister_device(uarttp_input_dev);
uarttp_input_dev = NULL;
}

// 注销misc设备
misc_deregister(&my_misc_device);
pr_info("my_misc device unregistered\n");
}

module_init(my_misc_init);
module_exit(my_misc_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("EThan");
MODULE_DESCRIPTION("UART TP misc + input device driver");

MAX_ABS_XMAX_ABS_Y的最大值可以不用太精确,因为屏幕分辨率不统一,且后续有问题还是通过上报应用进行数据微调。

将驱动编译成ko后,在开发板安装驱动进行调试

1
2
3
4
5
$ ls
register_uarttp1.ko
$ insmod register_uarttp1.ko
$ ls /dev/uart_tp_4
/dev/uart_tp_4

驱动安装成功后,可以看见内核成功注册/dev/uart_tp_4节点

/dev/uart_tp_4节点写入坐标数据,观察屏幕指针

1
2
3
$ echo "200,200,1" > /dev/uart_tp_4
$ echo "400,200,1" > /dev/uart_tp_4
$ echo "400,400,1" > /dev/uart_tp_4

可以看到屏幕指针变化如下,写入数据为"X坐标,Y坐标,压力值",由于屏幕不是电阻屏,所以这里的压力值可以用0和1表示是否触摸。

img

3.2. 应用层

驱动层解决后,就可以开始根据通过文档中的串口协议解析接收到的触摸数据了。

且将解析到的数据上报至驱动注册的/dev/uart_tp_4节点,就可以实现触摸功能。

编写ts_report_uart4.c上报应用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <termios.h>
#include <getopt.h>
#include <time.h>

// 核心配置参数
#define UART_DEV_PATH "/dev/ttyS4" // 串口输入设备(读取触摸数据)
#define REPORT_DEV_PATH "/dev/uart_tp_4"// 上报设备(输出x,y,1/0格式)
#define KTV_FRAME_LEN 10 // KTV协议一帧长度(10字节)
#define SERIAL_BAUDRATE B9600 // 串口波特率(9600bps)

// 坐标分辨率配置(原始→目标)
#define RAW_MAX_X 4095 // KTV协议X轴最大值(12位:0~4095)
#define RAW_MAX_Y 4095 // KTV协议Y轴最大值(12位:0~4095)
#define SCREEN_MAX_X 522 // 屏幕X轴最大值(根据实际屏幕调整)
#define SCREEN_MAX_Y 1280 // 屏幕Y轴最大值(根据实际屏幕调整)

// KTV协议定义
#define KTV_HEADER1 0x55
#define KTV_HEADER2 0x54
#define STATUS_FIRST_TOUCH 0x81 // 首次触摸(按下)
#define STATUS_CONTINUE_TOUCH 0x82 // 持续触摸(按下)
#define STATUS_RELEASE_TOUCH 0x84 // 释放触摸(抬起)

// 旋转角度定义(支持命令行配置)
typedef enum {
ROTATE_0 = 0, // 0度(默认)
ROTATE_90 = 1, // 90度顺时针
ROTATE_180 = 2, // 180度
ROTATE_270 = 3 // 270度顺时针(=90度逆时针)
} RotateType;

// KTV协议帧结构体
typedef struct {
unsigned char header1; // 帧头1:0x55
unsigned char header2; // 帧头2:0x54
unsigned char status; // 触摸状态
unsigned char xCoorL; // X坐标低8位
unsigned char xCoorH; // X坐标高4位
unsigned char yCoorL; // Y坐标低8位
unsigned char yCoorH; // Y坐标高4位
unsigned char tail1; // 尾码1:0xFF
unsigned char tail2; // 尾码2:0x00
unsigned char checksum; // 校验字节
} KTV_Touch_Frame;

// 触摸状态缓存(解决无释放问题)
typedef struct {
int last_x;
int last_y;
int last_pressed;
struct timespec last_touch_time; // 最后触摸时间
} TouchCache;

static TouchCache touch_cache = {0, 0, 0, {0, 0}};

/**
* @brief 计算KTV协议校验和(遵循协议公式)
*/
static unsigned char calc_ktv_checksum(KTV_Touch_Frame *frame) {
return (0x52 + frame->status + frame->xCoorL + frame->xCoorH +
frame->yCoorL + frame->yCoorH) & 0xFF;
}

/**
* @brief 串口配置(优化超时和读取模式)
*/
static int uart_config(int fd) {
struct termios opt;
memset(&opt, 0, sizeof(opt));

if (tcgetattr(fd, &opt) < 0) {
perror("tcgetattr failed");
return -1;
}

// 波特率配置
cfsetispeed(&opt, SERIAL_BAUDRATE);
cfsetospeed(&opt, SERIAL_BAUDRATE);

// 8位数据位、无校验、1位停止位
opt.c_cflag &= ~PARENB;
opt.c_cflag &= ~CSTOPB;
opt.c_cflag &= ~CSIZE;
opt.c_cflag |= CS8;
opt.c_cflag |= CLOCAL | CREAD; // 本地模式 + 启用接收

// 原始模式(禁用输入输出处理)
opt.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
opt.c_oflag &= ~OPOST;

// 优化读取超时(100ms超时,至少读取1字节)
opt.c_cc[VTIME] = 1; // 100ms(单位:0.1秒)
opt.c_cc[VMIN] = 1; // 至少读取1字节才返回

// 应用配置
tcflush(fd, TCIOFLUSH);
if (tcsetattr(fd, TCSANOW, &opt) < 0) {
perror("tcsetattr failed");
return -1;
}

return 0;
}

/**
* @brief 坐标旋转(支持0/90/180/270度)
*/
static void rotate_coordinate(int raw_x, int raw_y, RotateType rotate,
int *rotated_x, int *rotated_y) {
switch (rotate) {
case ROTATE_90:
*rotated_x = raw_y;
*rotated_y = RAW_MAX_X - raw_x;
break;
case ROTATE_180:
*rotated_x = RAW_MAX_X - raw_x;
*rotated_y = RAW_MAX_Y - raw_y;
break;
case ROTATE_270:
*rotated_x = RAW_MAX_Y - raw_y;
*rotated_y = raw_x;
break;
case ROTATE_0:
default:
*rotated_x = raw_x;
*rotated_y = raw_y;
break;
}
}

/**
* @brief 坐标缩放(线性映射:原始坐标→屏幕坐标)
*/
static void scale_coordinate(int raw_x, int raw_y, int *screen_x, int *screen_y) {
*screen_x = (raw_x * SCREEN_MAX_X) / RAW_MAX_X;
*screen_y = (raw_y * SCREEN_MAX_Y) / RAW_MAX_Y;

// 边界保护
*screen_x = (*screen_x < 0) ? 0 : (*screen_x > SCREEN_MAX_X ? SCREEN_MAX_X : *screen_x);
*screen_y = (*screen_y < 0) ? 0 : (*screen_y > SCREEN_MAX_Y ? SCREEN_MAX_Y : *screen_y);
}

/**
* @brief 坐标防抖(仅当坐标变化超过阈值时更新)
*/
static int coordinate_debounce(int new_x, int new_y, int last_x, int last_y) {
const int DEBOUNCE_THRESHOLD = 5; // 防抖阈值(根据实际调整)
int dx = abs(new_x - last_x);
int dy = abs(new_y - last_y);
return (dx > DEBOUNCE_THRESHOLD || dy > DEBOUNCE_THRESHOLD);
}

/**
* @brief 上报数据到/dev/uart_tp4(格式:x,y,1/0)
*/
static int report_to_uarttp4(int report_fd, int x, int y, int is_pressed) {
char report_buf[32];
int len;

// 防抖:仅当状态/坐标变化时上报
if (is_pressed == touch_cache.last_pressed && !coordinate_debounce(x, y, touch_cache.last_x, touch_cache.last_y)) {
return 0; // 无变化,无需上报
}

// 格式化上报数据(修复is_pressed=10的笔误)
len = snprintf(report_buf, sizeof(report_buf), "%d,%d,%d\n", x, y, is_pressed);
if (len < 0 || len >= sizeof(report_buf)) {
fprintf(stderr, "report buffer overflow\n");
return -1;
}

// 写入上报设备
if (write(report_fd, report_buf, len) != len) {
perror("write to /dev/uart_tp_4 failed");
return -1;
}

// 更新缓存
touch_cache.last_x = x;
touch_cache.last_y = y;
touch_cache.last_pressed = is_pressed;
clock_gettime(CLOCK_MONOTONIC, &touch_cache.last_touch_time);

// 打印日志(调试用)
printf("Report: %s", report_buf);
return 0;
}

/**
* @brief 检查触摸超时,自动释放(解决无释放问题)
*/
static int check_touch_timeout(int report_fd) {
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);

// 计算超时时间(300ms无触摸则自动释放)
long long elapsed_ms = (now.tv_sec - touch_cache.last_touch_time.tv_sec) * 1000 +
(now.tv_nsec - touch_cache.last_touch_time.tv_nsec) / 1000000;

if (touch_cache.last_pressed == 1 && elapsed_ms > 300) {
// 超时自动上报释放状态
return report_to_uarttp4(report_fd, touch_cache.last_x, touch_cache.last_y, 0);
}
return 0;
}

/**
* @brief 帧同步:找到有效的KTV帧头(解决粘包问题)
*/
static int sync_frame(int uart_fd) {
unsigned char byte;
int ret;

// 循环读取直到找到帧头0x55
while (1) {
ret = read(uart_fd, &byte, 1);
if (ret != 1) {
return -1;
}
if (byte == KTV_HEADER1) {
// 找到0x55后,检查下一字节是否为0x54
ret = read(uart_fd, &byte, 1);
if (ret == 1 && byte == KTV_HEADER2) {
// 帧头同步成功,回退两个字节(让后续read读取完整帧)
lseek(uart_fd, -2, SEEK_CUR);
return 0;
}
}
}
}

/**
* @brief 帮助信息
*/
static void print_help(const char *prog_name) {
printf("Usage: %s [options]\n", prog_name);
printf("Options:\n");
printf(" -r, --rotate <angle> Set coordinate rotation (0/90/180/270, default:0)\n");
printf(" -h, --help Show this help message\n");
printf("\nExample:\n");
printf(" %s -r 90 # Rotate 90 degrees clockwise\n", prog_name);
}

int main(int argc, char **argv) {
int uart_fd, report_fd;
KTV_Touch_Frame frame;
int ret;
RotateType rotate = ROTATE_0;
int raw_x, raw_y;
int rotated_x, rotated_y;
int screen_x, screen_y;
int is_pressed;


// 1. 打开串口输入设备
uart_fd = open(UART_DEV_PATH, O_RDWR | O_NOCTTY | O_NONBLOCK);
if (uart_fd < 0) {
perror("open /dev/ttyS4 failed");
return -1;
}

// 2. 配置串口
if (uart_config(uart_fd) < 0) {
close(uart_fd);
return -1;
}

// 3. 打开上报设备
report_fd = open(REPORT_DEV_PATH, O_WRONLY | O_NONBLOCK);
if (report_fd < 0) {
perror("open /dev/uart_tp_4 failed");
close(uart_fd);
return -1;
}

printf("UART touch report start:\n");
printf(" - UART input: %s\n", UART_DEV_PATH);
printf(" - Report output: %s\n", REPORT_DEV_PATH);
printf(" - Coordinate scale: %dx%d -> %dx%d\n",
RAW_MAX_X+1, RAW_MAX_Y+1, SCREEN_MAX_X+1, SCREEN_MAX_Y+1);
printf(" - Rotate angle: %d degrees\n", rotate * 90);

// 初始化触摸缓存时间
clock_gettime(CLOCK_MONOTONIC, &touch_cache.last_touch_time);

// 替换原main函数中“循环读取→解析”的部分
unsigned char uart_buf[64] = {0}; // 串口缓存
int buf_len = 0; // 缓存中未处理的字节数

while (1) {
// 1. 读取串口数据到缓存(每次读最多64字节)
ret = read(uart_fd, uart_buf + buf_len, sizeof(uart_buf) - buf_len);
if (ret < 0) {
if (errno != EAGAIN && errno != EWOULDBLOCK) {
perror("read uart data failed");
break;
}
usleep(20000); // 无数据休眠20ms,降低CPU占用
continue;
} else if (ret == 0) {
usleep(10000);
continue;
}
buf_len += ret;

// 2. 解析缓存中的完整KTV帧(帧头0x55 0x54,长度10字节)
while (buf_len >= KTV_FRAME_LEN) {
// 查找帧头(0x55 0x54)
int frame_start = -1;
for (int i = 0; i <= buf_len - KTV_FRAME_LEN; i++) {
if (uart_buf[i] == KTV_HEADER1 && uart_buf[i+1] == KTV_HEADER2) {
frame_start = i;
break;
}
}

if (frame_start == -1) {
// 未找到帧头,清空缓存(避免脏数据累积)
buf_len = 0;
break;
}

// 拷贝完整帧到frame结构体
memcpy(&frame, uart_buf + frame_start, KTV_FRAME_LEN);
// 移除已解析的帧(将剩余数据移到缓存头部)
memmove(uart_buf, uart_buf + frame_start + KTV_FRAME_LEN, buf_len - frame_start - KTV_FRAME_LEN);
buf_len -= frame_start + KTV_FRAME_LEN;

// 3. 校验帧尾和校验和(原逻辑保留)
if (frame.tail1 != 0xFF || frame.tail2 != 0x00) {
fprintf(stderr, "warning: invalid frame tail (0x%x,0x%x)\n", frame.tail1, frame.tail2);
continue;
}
if (frame.checksum != calc_ktv_checksum(&frame)) {
fprintf(stderr, "warning: checksum error (calc:0x%x, recv:0x%x)\n",
calc_ktv_checksum(&frame), frame.checksum);
continue;
}

// 4. 解析坐标+旋转+缩放+上报(原逻辑保留)
raw_x = ((frame.xCoorH & 0x0F) << 8) | frame.xCoorL;
raw_y = ((frame.yCoorH & 0x0F) << 8) | frame.yCoorL;
rotate_coordinate(raw_x, raw_y, rotate, &rotated_x, &rotated_y);
scale_coordinate(rotated_x, rotated_y, &screen_x, &screen_y);

switch (frame.status) {
case STATUS_FIRST_TOUCH:
case STATUS_CONTINUE_TOUCH:
is_pressed = 1;
break;
case STATUS_RELEASE_TOUCH:
is_pressed = 0;
break;
default:
fprintf(stderr, "warning: unknown touch status (0x%x)\n", frame.status);
continue;
}

// 打印有效数据日志(调试用)
printf("Valid touch: raw(x:%d,y:%d) → screen(x:%d,y:%d), pressed:%d\n",
raw_x, raw_y, screen_x, screen_y, is_pressed);

// 上报数据
if (report_to_uarttp4(report_fd, screen_x, screen_y, is_pressed) < 0) {
break;
}
}
}

// 退出前上报释放状态(关键:解决异常退出无释放)
report_to_uarttp4(report_fd, touch_cache.last_x, touch_cache.last_y, 0);

// 资源释放
close(uart_fd);
close(report_fd);
printf("UART touch report stopped\n");
return 0;
}

这里的SCREEN_MAX_XSCREEN_MAX_Y的值是通过/dev/uart_tp_4节点调试所得(这里测试的时候将"522,1280,1"写入/dev/uart_tp_4节点时,指针刚好处于屏幕的右下方)

将上报应用在开发板上编译运行,确认触摸是否正常

img

3.3. 第二路显示屏调试注意事项

第二路显示屏只需要微调一开始的驱动和应用

regist_uarttp1.c驱动拷贝为regist_uarttp2.c

修改DEVICE_NAME变量,避免与第一路input节点冲突,修改为uart_tp_5

编译成ko后在开发板测试验证,安装成功后识别出以下两个设备节点

1
2
$ ls /dev/uart_tp_
uart_tp_4 uart_tp_5

而第二路显示屏的触摸上报应用只需要确认/dev/uart_tp_5在第二路显示器的起始坐标变化范围,并修改ts_report应用即可,这里不过多赘述。

4. 设置开机启动上报触摸坐标

创建开机启动脚本/etc/init.d/uart_tp_init.sh

1
2
3
4
5
6
7
8
9
#!/bin/bash

insmod /system/lib/modules/register_uarttp1.ko
insmod /system/lib/modules/register_uarttp2.ko
sleep 0.5


/system/lib/bin/ts_report > /dev/null &
/system/lib/bin/ts_report_extend > /dev/null &

将编译的ko和上报应用拷贝到脚本对应的目录下,并赋予应用可执行权限。

创建/lib/systemd/system/uart_touch_init.service

1
2
3
4
5
6
7
8
9
10
[Unit]
Description=uart touch input service
After=network.target
[Service]
Type=forking
User=root
ExecStart=/etc/init.d/uart_tp_init.sh

[Install]
WantedBy=multi-user.target

启动串口上报service

1
$ systemctl enable uart_touch_init.service

重启系统确定service正常运行即可

1
2
3
4
5
6
7
8
9
10
systemctl status uart_touch_init.service
● uart_touch_init.service - uart touch input service
Loaded: loaded (/lib/systemd/system/uart_touch_init.service; enabled; vend>
Active: active (running) since Wed 2025-12-31 08:20:09 UTC; 12min ago
Process: 469 ExecStart=/etc/init.d/uart_tp_init.sh (code=exited, status=0/S>
Tasks: 2 (limit: 2316)
Memory: 1.0M
CGroup: /system.slice/uart_touch_init.service
├─507 /system/lib/bin/ts_report
└─508 /system/lib/bin/ts_report_extend