Linux驱动开发笔记(二十二)——多点电容触摸屏

视频:第25.1讲 Linux 多点电容触摸屏实验-Linux多点电容触摸TypeA协议_哔哩哔哩_bilibili

文档:《【正点原子】I.MX6U开发指南V1.81.pdf》六十四章

imx6ull mini板/alpha板皆可

使用1080×600的7寸LCD


多点触摸(Multi-touch, MT)协议分为TypeA和TypeB。

TypeA:不对触摸点进行跟踪、分类,直接上报原始的点坐标等数据

TypeB:适用于有硬件追踪并能区分触摸点的触摸设备。此类型设备通过slot更新触摸点的信息。

1、MT事件

触摸点的信息通过ABS_MT事件上报给 linux 内核。只有 ABS_MT 事件是用于多点触摸的。MT事件定义如下:

cpp 复制代码
// 来自 usr/include/linux/input-event-codes.h
// 或 include/uapi/linux/input.h
/*
 * 0x2e 是保留值,不应在输入驱动中使用
 * 它曾被 HID 用作 ABS_MISC+6,用户空间需要检测下一个ABS_*事件是合法的?还是只是ABS_MISC+n?
 * 我们在这里定义 ABS_RESERVED,这样用户空间就可以依赖它来检测上述所描述的情况
 */

// 详细内容可以查看multi-touch-protocol.txt
#define ABS_RESERVED		0x2e

#define ABS_MT_SLOT		    0x2f	/* 正在被修改的MT槽位(slot) */  √
#define ABS_MT_TOUCH_MAJOR	0x30	/* 触摸椭圆的长轴 */
#define ABS_MT_TOUCH_MINOR	0x31	/* 触摸椭圆的短轴(若为圆形可省略) */
#define ABS_MT_WIDTH_MAJOR	0x32	/* 接近椭圆的长轴宽度 */
#define ABS_MT_WIDTH_MINOR	0x33	/* 接近椭圆的短轴宽度(若为圆形可省略) */
#define ABS_MT_ORIENTATION	0x34	/* 椭圆的方向/朝向 */
#define ABS_MT_POSITION_X	0x35	/* 触摸中心的 X 坐标 */  √
#define ABS_MT_POSITION_Y	0x36	/* 触摸中心的 Y 坐标 */  √
#define ABS_MT_TOOL_TYPE	0x37	/* 触摸工具的类型 */
#define ABS_MT_BLOB_ID		0x38	/* 将一组数据包归为一个"blob" 的 ID */
#define ABS_MT_TRACKING_ID	0x39	/* 触点的唯一跟踪 ID */  √
#define ABS_MT_PRESSURE		0x3a	/* 触点区域上的压力 */
#define ABS_MT_DISTANCE		0x3b	/* 触点悬停距离 */
#define ABS_MT_TOOL_X		0x3c	/* 工具中心的 X 坐标 */
#define ABS_MT_TOOL_Y		0x3d	/* 工具中心的 Y 坐标 */

// 打√的四个最常用

不管是哪个类型的设备,最终都要调用 input_sync()函数来标识多点触摸信息传输完成,告诉接收者处理之前累计的所有消息,并且准备好下一次接收。

Type B和Type A相比最大的区别就是 Type B 可以区分出触摸点,因此可以减少发送到用户空间的数据。Type B使用slot协议区分具体的触摸点,slot需要用到 ABS_MT_TRACKING_ID,这个ID由硬件提供, 或者通过原始数据计算出来。

对于Type A设备,内核驱动一次性将触摸屏上所有的触摸点信息全部上报,每个触摸点的信息在本次上报事件流中的顺序不重要,因为事件的过滤和触摸点跟踪是在内核空间处理的。

2、MT相关函数

初始化 input_mt_init_slots

input_mt_init_slots用于初始化MT的输入slots。编写MT驱动的时必须先调用此函数初始化 slots。

cpp 复制代码
int input_mt_init_slots(struct input_dev *dev,     // MT设备对应的input设备
                        unsigned int num_slots,    // 设备要使用的SLOT数量(触摸点的数量上限)
			            unsigned int flags)        // 其他flags信息,可设置的flags如下。
                                                   // 可以采用'|'或运算同时设置多个flags
                                                   // 没有flag写0即可
// return:0成功,负值失败


#define INPUT_MT_POINTER       0x0001  /* 指针类设备,例如触控板 */
#define INPUT_MT_DIRECT        0x0002  /* 直接输入设备,例如触摸屏 */
#define INPUT_MT_DROP_UNUSED   0x0004  /* 丢弃在当前帧中未出现的触点 */
#define INPUT_MT_TRACK         0x0008  /* 使用内核中的触点跟踪机制 */
#define INPUT_MT_SEMI_MT       0x0010  /* 半多点设备,手指计数由驱动自行处理 */

input_mt_slot

此函数用于Type B类,用于产生ABS_MT_SLOT事件,告诉内核当前上报的是哪个触摸点的坐标数据。

cpp 复制代码
static inline void input_mt_slot(struct input_dev *dev, int slot){
	input_event(dev, EV_ABS, ABS_MT_SLOT, slot);
}

// dev:MT设备对应的input设备
// slot:当前发送的是哪个slot/触摸点的坐标信息

input_mt_report_slot_state

此函数用于Type B类型,用于产生ABS_MT_TRACKING_ID和ABS_MT_TOOL_TYPE事件。

其中ABS_MT_TRACKING_ID事件给slot关联一个ABS_MT_TRACKING_ID,ABS_MT_TOOL_TYPE事件指定触摸工具类型。

cpp 复制代码
void input_mt_report_slot_state(struct input_dev *dev,    // dev:MT设备对应的input设备
				                unsigned int tool_type,   // tool_type:触摸工具类型。可选类型如下。
                                bool active)              // active:如下

#define MT_TOOL_FINGER	0x00  // 手指
#define MT_TOOL_PEN		0x01  // 手写笔
#define MT_TOOL_PALM	0x02  // 手掌(常用于手掌误触检测)
#define MT_TOOL_DIAL	0x0a  // 旋钮/拨盘类
#define MT_TOOL_MAX		0x0f  // 最大工具类型编号(用于范围界定)

active:

true表示连续触摸,input子系统内核会自动分配一个ABS_MT_TRACKING_ID给slot。

false表示触摸点抬起,表示某个触摸点无效了,input子系统内核会分配-1给slot,表示触摸

点溢出。

上报坐标值

Type A 和 Type B 类型都使用此函数上报触摸点坐标。

cpp 复制代码
static inline void input_report_abs(struct input_dev *dev, unsigned int code, int value){
	input_event(dev, EV_ABS, code, value);
}
// dev:MT设备对应的input设备
// code:要上报的是x还是y坐标(ABS_MT_POSITION_X或ABS_MT_POSITION_Y)
// value:具体的坐标值

3、信息上报流程 及 示例函数

TypeA:

cpp 复制代码
// 来自《指南pdf》的 示例代码 64.1.2.1
ABS_MT_POSITION_X x[0]  // 上报第0个点的坐标(使用input_report_abs函数)
ABS_MT_POSITION_Y y[0]
SYN_MT_REPORT           // 上报SYN_MT_REPORT事件(使用input_mt_sync函数)
                        // 用于隔离不同的触摸点数据信息

ABS_MT_POSITION_X x[1]
ABS_MT_POSITION_Y y[1]
SYN_MT_REPORT


SYN_REPORT              // 所有触摸点上报完成,上报SYN_REPORT事件(使用input_sync函数)
cpp 复制代码
static inline void input_mt_sync(struct input_dev *dev){
	input_event(dev, EV_SYN, SYN_MT_REPORT, 0);
}

// input_dev:input设备
// 本质是上报SYN_MT_REPORT事件,通知接收者获取触摸数据 并且准备接收下一个触摸点数据
// TypeA通过input_mt_sync()函数来隔离不同的触摸点数据信息

示例:

cpp 复制代码
// 来自drivers/input/touchscreen/st1232.c
static irqreturn_t st1232_ts_irq_handler(int irq, void *dev_id){

    ............

	/* multi touch protocol */
	for (i = 0; i < MAX_FINGERS; i++) {
		if (!finger[i].is_valid)
			continue;

		input_report_abs(input_dev, ABS_MT_TOUCH_MAJOR, finger[i].t);  // 上报触摸点
		input_report_abs(input_dev, ABS_MT_POSITION_X, finger[i].x);
		input_report_abs(input_dev, ABS_MT_POSITION_Y, finger[i].y);
		input_mt_sync(input_dev);    // input_mt_sync隔离不同触摸点
		count++;
	}

	/* SYN_MT_REPORT only if no contact */
	if (!count) {
		input_mt_sync(input_dev);
		if (ts->low_latency_req.dev) {
			dev_pm_qos_remove_request(&ts->low_latency_req);
			ts->low_latency_req.dev = NULL;
		}
	} else if (!ts->low_latency_req.dev) {
		/* First contact, request 100 us latency. */
		dev_pm_qos_add_ancestor_request(&ts->client->dev,
						&ts->low_latency_req,
						DEV_PM_QOS_RESUME_LATENCY, 100);
	}

	/* SYN_REPORT */
	input_sync(input_dev);  // 所有触摸点发送完成

end:
	return IRQ_HANDLED;
}

TypeB:

Type B设备驱动需要给每个识别出来的触摸点分配一个slot,后面使用这个slot来上报触摸点信息。

通过的ABS_MT_TRACKING_ID来新增、替换或删除触摸点。一个非负的ID表示一个有效的触摸点,-1这个ID表示未使用slot。

cpp 复制代码
// 来自《指南pdf》的示例代码64.1.3.1
ABS_MT_SLOT 0            // 第0个触摸点slot 0    使用input_mt_slot函数
ABS_MT_TRACKING_ID 45    // 新手指,id=45        使用input_mt_report_slot_state函数
ABS_MT_POSITION_X x[0]   // 触摸点坐标           使用input_report_abs函数
ABS_MT_POSITION_Y y[0]

ABS_MT_SLOT 1            // 第1个触摸点slot 1
ABS_MT_TRACKING_ID 46    // 新手指,id=46
ABS_MT_POSITION_X x[1]
ABS_MT_POSITION_Y y[1]

SYN_REPORT               // 所有触摸点上报完成,上报SYN_REPORT事件(使用input_sync函数)

示例:

cpp 复制代码
// drivers/input/touchscreen/ili210x.c
static void ili210x_report_events(struct input_dev *input,
				  const struct touchdata *touchdata)
{
	int i;
	bool touch;
	unsigned int x, y;
	const struct finger *finger;

	for (i = 0; i < MAX_TOUCHES; i++) {
		input_mt_slot(input, i);    // 上报第i个触摸点信息

		finger = &touchdata->finger[i];

		touch = touchdata->status & (1 << i);
		input_mt_report_slot_state(input, MT_TOOL_FINGER, touch);
		if (touch) {
			x = finger->x_low | (finger->x_high << 8);
			y = finger->y_low | (finger->y_high << 8);

			input_report_abs(input, ABS_MT_POSITION_X, x);  // 上报坐标数据
			input_report_abs(input, ABS_MT_POSITION_Y, y);
		}
	}

	input_mt_report_pointer_emulation(input, false);
	input_sync(input);        // 结束信号
}

4、LCD触摸屏驱动代码

4.1 设备树

《开发板原理图/IMX6ULL_ALPHA(底板原理图).pdf》(或mini版原理图皆可)可以看到LCD用到的脚:

因此设备树需要修改4个脚:

|----------|--------------|--------------|
| CN_RST | SNVS_TAMPER9 | RESET |
| CT_INT | GPIO1_IO09 | 有触摸事件时产生中断信号 |
| I2C2_SDA | UART5_RXD | I2C数据线 |
| I2C2_SCL | UART5_TXD | I2C时钟线 |

将下面这段代码放到imx6ull-alientek-emmc.dts中&iomuxc的imx6ul-evk里面去:

cpp 复制代码
		pinctrl_tsc: tscgrp {
			fsl,pins = <
				MX6UL_PAD_GPIO1_IO09__GPIO1_IO09 0xF080
			>;
		};

// 同时检查有没有其他地方对GPIO1_IO09进行复用的,注释掉
// 还要检查包含"<&gpio1 9"的内容,有的话注释掉

将下面这段代码放到imx6ull-alientek-emmc.dts中&iomuxc_snvs的imx6ul-evk里面去:

cpp 复制代码
// 对于SNVS_TAMPER的复用,宏定义在imx6ull-pinfunc-snvs.h文件中。具体问题详见附录A
		pinctrl_tsc_reset: tsc_reset {
			fsl,pins = <
				MX6ULL_PAD_SNVS_TAMPER9__GPIO5_IO09 0x10B0
			>;
		};

// 同时检查有没有其他地方对SNVS_TAMPER9进行复用的,注释掉
// 并检查包含"gpio5 9"的内容,有的话注释掉

至于UART5复用为i2c2,&iomuxc中已经配置了(如果没改过的话),我们直接使用即可:

然后配置LCDtf5426节点。将下面的代码写到&i2c2里面:

cpp 复制代码
	ft5426:ft5426@38 {
		compatible = "edt,edt-ft5426";
		reg = <0x38>;
		pinctrl-names = "default";
		pinctrl-0 = <&pinctrl_tsc 
					 &pinctrl_tsc_reset>;
		interrupt-parent = <&gpio1>;
		interrupts = <9 0>;  // gpio1 io09
		reset-gpios = <&gpio5 9 GPIO_ACTIVE_LOW>;
		interrupt-gpios = <&gpio1 9 GPIO_ACTIVE_LOW>;
		status = "okay";
	};

同时,imx6ull-alientek-emmc.dts中能找到下面两段代码。这两段是电阻触摸屏的代码,我们不用,将其注释掉:

复制、扔给tftp:

cpp 复制代码
make dtbs
sudo cp arch/arm/boot/dts/imx6ull-alientek-emmc.dtb    /..../tftpboot/

然后reboot重启开发板

检查一下设备树是否正常:

bash 复制代码
ls /proc/device-tree/soc/aips-bus@02100000/i2c@021a4000/
# 在imx6ull.dtsi中搜索i2c2,可以在aips2: aips-bus@02100000节点下找到i2c2: i2c@021a4000

能够找到tf5426:

4.2 文件结构

bash 复制代码
24_MULTITOUCH (工作区)
├── .vscode
│   ├── c_cpp_properties.json
│   └── settings.json
├── 24_multitouch.code-workspace
├── Makefile
├── ft5426reg.h
└── ft5426.c

4.3 Makefile

bash 复制代码
CFLAGS_MODULE += -w

KERNELDIR := /home/for/linux/imx6ull/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek  # 内核路径
# KERNELDIR改成自己的 linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek文件路径(这个文件从正点原子"01、例程源码"中直接搜,cp到虚拟机里面)

CURRENT_PATH := $(shell pwd)	# 当前路径

obj-m := multitouch.o			    # 编译文件

build: kernel_modules			# 编译模块

kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean	

4.4 屏幕显示

如果你也把前面LCD那一节的教程跳过去了的话,此时插上屏幕是没法用的。具体配置方法可见《指南pdf》的59.4.1部分内容或视频第20.4讲 Linux LCD驱动实验

4.4.1 图形化配置界面
bash 复制代码
cd /..../linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/  # 首先进入linux内核根目录
make menuconfig  # 打开图形化配置界面

依次进入如下选项:
-> Device Drivers
  -> Graphics support 
  -> Bootup logo (LOGO [=y]) 
    -> Standard black and white Linux logo
    -> Standard 16-color Linux logo 
    -> Standard 224-color Linux logo

确保下面这三个前面都是[*]选中(按下空格或Y选中/不选)

先不编译,后面修改完设备树后一块编译。

4.4.2 设备树

先看一下自己的屏幕的分辨率。可以通过屏幕背面的这一块来判断(我猜的),我这里就是7寸1024*600:

在imx6ull-alientek-emmc.dts文件中,查看&lcdif的display-timings部分内容。

我这里的设备树里面,是一个480*272的设置。需要对其进行修改,这些参数的含义可以参考修改LCD驱动点亮LCD显示小企鹅logo的第二部分内容。如果你也是1024*600的屏,将display-timings的内容修改为:

bash 复制代码
		display-timings {
			native-mode = <&timing0>;
			timing0: timing0 {
			clock-frequency = <51200000>;
			hactive = <1024>;
			vactive = <600>;
			hfront-porch = <160>;
			hback-porch = <140>;
			hsync-len = <20>;
			vback-porch = <20>;
			vfront-porch = <12>;
			vsync-len = <3>;

			hsync-active = <0>;
			vsync-active = <0>;
			de-active = <1>;
			pixelclk-active = <0>;
			};
		};
bash 复制代码
# 在linux内核根目录下
make
sudo cp arch/arm/boot/zImage /..../tftpboot/
sudo cp arch/arm/boot/dts/imx6ull-alientek-emmc.dtb /..../tftpboot/

重启开发板,现在屏幕左上角应该能显示企鹅logo了。

4.4.3 设置LCD作为终端控制台

首先查看一下开发板启动信息:

现在我们要修改这一段bootargs的console,在其基础上增加一个"console=tty1",将LCD和串口都作为控制台:

bash 复制代码
setenv bootargs 'console=tty1 console=ttymxc0,115200 root=/dev/nfs rw nfsroot=192.168.5.11:/home/..../nfs/rootfs ip=192.168.5.9:192.168.5.11:192.168.5.1:255.255.255.0::eth0:off'
# 在原来的命令基础上增加"console=tty1"即可                                           ↑虚拟机/服务器ip                          ↑开发板ip    ↑虚拟机ip    ↑网关ip     ↑子网掩码

saveenv    # 保存

boot       # 启动

然后修改开发板的/etc/inittab内容。直接在虚拟机的VSCODE终端修改即可:

bash 复制代码
sudo vi  /..../nfs/rootfs/etc/inittab

按 i 插入内容,将下面这段内容写到文件中,并:wq退出:

bash 复制代码
tty1::askfirst:-/bin/sh

现在启动开发板。如果你遇到启动时卡住或者提示nfs:server is not responding,still trying,详见附录B。LCD的控制台和串口控制台并不是镜像的,如果要在LCD这边的控制台输入内容,需要找个键盘查到开发板的USB接口上。

终于结束了,接着写驱动。

4.5 ft5426reg.h

ft5426的寄存器可见:

《指南pdf》二十八章表28.1.1.1 或

【正点原子】阿尔法Linux开发板(A盘)-基础资料/06、硬件资料/01、芯片资料/FT5426(电容触摸屏IC)/FTS_AN_CTPM_Standard_eng.pdf(但是正点原子的LCD屏触摸点的XY寄存器和手册中的反的,正点原子的是先Y后X,手册中是先X后Y)

将下面这段代码贴到ft5426reg.h文件中:

cpp 复制代码
#define MAX_SUPPORT_POINTS 5 /* 5 点触摸 */
#define TOUCH_EVENT_DOWN 0x00 /* 按下 */
#define TOUCH_EVENT_UP 0x01 /* 抬起 */
#define TOUCH_EVENT_ON 0x02 /* 接触 */
#define TOUCH_EVENT_RESERVED 0x03 /* 保留 */

/* FT5X06 寄存器相关宏定义 */
#define FT5X06_TD_STATUS_REG 0X02 /* 状态寄存器地址 */
#define FT5x06_DEVICE_MODE_REG 0X00 /* 模式寄存器 */
#define FT5426_IDG_MODE_REG 0XA4 /* 中断模式 */
#define FT5X06_READLEN 29 /* 要读取的寄存器个数 */

4.6 驱动代码ft5426.c

4.6.1 驱动框架

先搭一个框架出来。框架内容与IIC的4.5.1部分代码一致,但是将包含mpu6050的函数名等修改为了ft5x06,并修改了匹配表,且去除了设备操作集及其函数(因为只是个INPUT设备)

框架中的I2C读写寄存器函数与IIC中的4.5.2部分代码完全一致。

cpp 复制代码
#include<linux/module.h>
#include<linux/input.h>
#include<linux/i2c.h>
#include<linux/cdev.h>
#include<linux/device.h>
#include<linux/fs.h>
#include<linux/uaccess.h>
#include<linux/string.h>
#include<linux/delay.h>
#include "ft5426reg.h"
 
#define FT5X06_CNT 1
#define FT5X06_NAME "ft5x06"
 
// 设备结构体 ===================================================
 
struct ft5x06_dev_struct {
    struct i2c_client *client;
    void *private_data;
};
static struct ft5x06_dev_struct ft5x06dev;
 
// 寄存器读写函数 ===================================================

// reg:目标寄存器地址  buf:要写的数据  len:要写的信息的长度
static int ft5x06_write_reg(struct ft5x06_dev_struct *dev, u8 reg, u8 *buf, int len){

    int err = 0;
    struct i2c_client *client = (struct i2c_client*)dev->client;
    struct i2c_msg msg;
    u8 b[256];
 
    b[0] = reg;
    memcpy(&b[1], buf, len);
 
    msg.addr = client->addr;// 从机地址(AP3216C)
    msg.flags = 0;          // 发送。flag详见1.4
    msg.buf = b;            // 发送内容:要读取的寄存器地址
    msg.len = len + 1;      // 要发送的数据长度(len) + 寄存器地址长度(1)
 
    err = i2c_transfer(client->adapter, &msg, 1); // 这里要写成&msg!不是msg
    if(err != 1){
        printk("ft5x06_write_reg fail!\r\n");
        return -1;
    }
    return 0;
}
// reg:目标寄存器地址  data:保存读取到的信息  len:要读取信息的长度
static int ft5x06_read_reg(struct ft5x06_dev_struct *dev, u8 reg, void *data, int len){
    int err = 0;
    struct i2c_client *client = (struct i2c_client*)dev->client;
    struct i2c_msg msg[2];
 
    // ①发送:要读取的寄存器的地址
    msg[0].addr = client->addr; // 从机地址
    msg[0].flags = 0;           // 发送
    msg[0].buf = &reg;          // 要发送的数据:目标寄存器地址
    msg[0].len = 1;             // 发送数据的长度
 
    // ②接收:读到的目标寄存器的数据
    msg[1].addr = client->addr; // 从机地址
    msg[1].flags = I2C_M_RD;    // 接收
    msg[1].buf = data;          // 接收的数据保存到data
    msg[1].len = len;           // 接收数据的长度   
 
    // 发送msg
    err = i2c_transfer(client->adapter, msg, 2);
 
    if (err!= 2){
        printk("ft5x06_read_reg fail!\r\n");
        return -1;
    }
    return 0;
}


 
// 匹配表 ===================================================
 
static struct i2c_device_id ft5x06_id[] = {
    {"edt-ft5426",0},
    {/*sentinel*/},
};
static struct of_device_id ft5x06_of_match[] = {
    {.compatible = "edt,edt-ft5426"},
    {/*sentinel*/},
};
 
// 总线设备结构体 ===================================================
static int ft5x06_probe(struct i2c_client *client, const struct i2c_device_id *id){
    pr_info("ft5x06 probe!\r\n");
    return 0;
}
 
static int ft5x06_remove(struct i2c_client *client){
    pr_info("ft5x06 remove!\r\n");
    return 0;
}
 
static struct i2c_driver ft5x06_driver = {
    .probe = ft5x06_probe,
    .remove = ft5x06_remove,
    .driver = {
        .owner = THIS_MODULE,
        .name = "edt_ft5x06",
        .of_match_table = of_match_ptr(ft5x06_of_match),
    },
    .id_table = ft5x06_id,
};
 
// 驱动入口/出口 ===================================================
 
static int __init ft5x06_init(void){
    i2c_add_driver(&ft5x06_driver);
    return 0;
}
 
static void __exit ft5x06_exit(void){
    i2c_del_driver(&ft5x06_driver);
}
 
module_init(ft5x06_init);
module_exit(ft5x06_exit);
MODULE_LICENSE("GPL");

先试一下,看看能不能probe成功:

bash 复制代码
make
sudo cp ft5426.ko /home/for/linux/nfs/rootfs/lib/modules/4.1.15/

cd /lib/modules/4.1.15/
depmod
modprobe ft5426.ko  # 应有"ft5x06 probe!"输出
4.6.2 增加中断功能

主要在4.5.1框架基础上增加了:

完善了 设备结构体ft5x06_dev_struct

增加了 头文件

增加了 中断处理函数ft5x06_handler

增加了 ft5x06 reset脚初始化ft5x06_ts_reset

增加了 ft5x06 中断脚初始化ft5x06_ts_irq

并在probe函数中增加对上面两个引脚初始化函数的调用

此时modprobe之后,如果触摸屏幕,控制台会触发中断,执行中断处理函数ft5x06_handler,输出"handler!"

cpp 复制代码
#include<linux/module.h>
#include<linux/input.h>
#include<linux/i2c.h>
#include<linux/cdev.h>
#include<linux/device.h>
#include<linux/fs.h>
#include<linux/uaccess.h>
#include<linux/string.h>
#include<linux/delay.h>
#include<linux/gpio.h>
#include<linux/interrupt.h>
#include<linux/of_gpio.h>
#include "ft5426reg.h"
 
#define FT5X06_CNT 1
#define FT5X06_NAME "ft5x06"
 
// 设备结构体 ===================================================
 
struct ft5x06_dev_struct {
    struct device_node* nd;
    int irq_pin,reset_pin; // 引脚号
    int irq_num;           // 中断号
    void *private_data;
    struct i2c_client *client;

};
static struct ft5x06_dev_struct ft5x06dev;
 
// 寄存器读写函数 ===================================================

// reg:目标寄存器地址  buf:要写的数据  len:要写的信息的长度
static int ft5x06_write_reg(struct ft5x06_dev_struct *dev, u8 reg, u8 *buf, int len){

    int err = 0;
    struct i2c_client *client = (struct i2c_client*)dev->client;
    struct i2c_msg msg;
    u8 b[256];
 
    b[0] = reg;
    memcpy(&b[1], buf, len);
 
    msg.addr = client->addr;// 从机地址(AP3216C)
    msg.flags = 0;          // 发送。flag详见1.4
    msg.buf = b;            // 发送内容:要读取的寄存器地址
    msg.len = len + 1;      // 要发送的数据长度(len) + 寄存器地址长度(1)
 
    err = i2c_transfer(client->adapter, &msg, 1); // 这里要写成&msg!不是msg
    if(err != 1){
        printk("ft5x06_write_reg fail!\r\n");
        return -1;
    }
    return 0;
}
// reg:目标寄存器地址  data:保存读取到的信息  len:要读取信息的长度
static int ft5x06_read_reg(struct ft5x06_dev_struct *dev, u8 reg, void *data, int len){
    int err = 0;
    struct i2c_client *client = (struct i2c_client*)dev->client;
    struct i2c_msg msg[2];
 
    // ①发送:要读取的寄存器的地址
    msg[0].addr = client->addr; // 从机地址
    msg[0].flags = 0;           // 发送
    msg[0].buf = &reg;          // 要发送的数据:目标寄存器地址
    msg[0].len = 1;             // 发送数据的长度
 
    // ②接收:读到的目标寄存器的数据
    msg[1].addr = client->addr; // 从机地址
    msg[1].flags = I2C_M_RD;    // 接收
    msg[1].buf = data;          // 接收的数据保存到data
    msg[1].len = len;           // 接收数据的长度   
 
    // 发送msg
    err = i2c_transfer(client->adapter, msg, 2);
 
    if (err!= 2){
        printk("ft5x06_read_reg fail!\r\n");
        return -1;
    }
    return 0;
}




// 中断处理函数
static irqreturn_t ft5x06_handler(int irq, void *dev_id){
    printk("handler!\r\n");
    return IRQ_HANDLED;
}

// ft5x06复位脚初始化
static int ft5x06_ts_reset(struct i2c_client *client, struct ft5x06_dev_struct *dev){
    int ret = 0;
    if(gpio_is_valid(dev->reset_pin)){ // 如果reset脚有效,则申请

        // 申请reset脚,默认低电平
        ret = devm_gpio_request_one(&client->dev, dev->reset_pin, GPIOF_OUT_INIT_LOW, "edt-ft5x06 reset");// devm,不需要手动释放
        if(ret){
            dev_err(&client->dev,"Failed to request GPIO %d, error %d\r\n",dev->reset_pin,ret);
            return ret;
        }
        msleep(5);
        gpio_set_value(dev->reset_pin, 1); // 拉高,停止复位
        msleep(300);
    }
    return 0;
}

// 中断脚初始化
static int ft5x06_ts_irq(struct i2c_client *client, struct ft5x06_dev_struct *dev){
    int ret = 0;
    if(gpio_is_valid(dev->irq_pin)){
        ret = devm_gpio_request_one(&client->dev, dev->irq_pin, GPIOF_IN, "edt-ft5x06 irq");

        if(ret){
            dev_err(&client->dev,"Failed to request GPIO %d, error %d\r\n",dev->irq_pin,ret);
            return ret;
        }
    }
    ret = devm_request_threaded_irq(&client->dev, client->irq, NULL, ft5x06_handler, IRQF_TRIGGER_FALLING | IRQF_ONESHOT, client->name, &ft5x06dev);
    return ret;
}



 
// 匹配表 ===================================================
 
static struct i2c_device_id ft5x06_id[] = {
    {"edt-ft5426",0},
    {/*sentinel*/},
};
static struct of_device_id ft5x06_of_match[] = {
    {.compatible = "edt,edt-ft5426"},
    {/*sentinel*/},
};
 




// 总线设备结构体 ===================================================
static int ft5x06_probe(struct i2c_client *client, const struct i2c_device_id *id){
    pr_info("ft5x06 probe!\r\n");

    ft5x06dev.client = client;

    // 获取irq和reset引脚

    ft5x06dev.irq_pin = of_get_named_gpio(client->dev.of_node, "interrupt-gpios", 0);
    ft5x06dev.reset_pin = of_get_named_gpio(client->dev.of_node, "reset-gpios", 0);

    ft5x06_ts_reset(client, &ft5x06dev);

    ft5x06_ts_irq(client, &ft5x06dev);



    return 0;
}
 
static int ft5x06_remove(struct i2c_client *client){
    pr_info("ft5x06 remove!\r\n");
    return 0;
}
 
static struct i2c_driver ft5x06_driver = {
    .probe = ft5x06_probe,
    .remove = ft5x06_remove,
    .driver = {
        .owner = THIS_MODULE,
        .name = "edt_ft5x06",
        .of_match_table = of_match_ptr(ft5x06_of_match),
    },
    .id_table = ft5x06_id,
};
 
// 驱动入口/出口 ===================================================
 
static int __init ft5x06_init(void){
    i2c_add_driver(&ft5x06_driver);
    return 0;
}
 
static void __exit ft5x06_exit(void){
    i2c_del_driver(&ft5x06_driver);
}
 
module_init(ft5x06_init);
module_exit(ft5x06_exit);
MODULE_LICENSE("GPL");

现在modprobe这个代码,触摸屏幕时会打印"handler!"。

4.6.3 完善中断处理函数

下图来自《指南pdf》二十八章表28.1.1.1:

虽然从表上看每个触摸点只有4个寄存器,但是可以发现,每个触摸点的第一个寄存器地址(X高位)之间的差值为6(有两个地址没用到),《FTS_AN_CTPM_Standard_eng.pdf》2.1的这张表能看的很清楚:

现在也可以看出来ft5426reg.h中为甚FT5X06_READLEN是29了:1(触摸状态寄存器) + 5(5个触摸点) * 6(每个触摸点6个寄存器) - 2(最后一个触摸点的后两个寄存器不需要读进来) = 29

以上就可以得到完整的驱动代码了。主要在4.6.2基础上进行了以下修改:

增加了 引用的头文件

完善了 中断处理函数ft5x06_handler,进行触摸点的读取和上报。并删掉里面的printk。

完善了 probe函数,增加了INPUT框架

cpp 复制代码
#include<linux/module.h>
#include<linux/input.h>
#include<linux/i2c.h>
#include<linux/cdev.h>
#include<linux/device.h>
#include<linux/fs.h>
#include<linux/uaccess.h>
#include<linux/string.h>
#include<linux/delay.h>
#include<linux/gpio.h>
#include<linux/interrupt.h>
#include<linux/of_gpio.h>
#include"ft5426reg.h"
#include<linux/input/mt.h>
#include<linux/input/touchscreen.h>


 
#define FT5X06_CNT 1
#define FT5X06_NAME "ft5x06"
 
// 设备结构体 ===================================================
 
struct ft5x06_dev_struct {
    struct device_node* nd;
    int irq_pin,reset_pin; // 引脚号
    int irq_num;           // 中断号
    void *private_data;
    struct i2c_client *client;
    struct input_dev *input;
};
static struct ft5x06_dev_struct ft5x06dev;
 
// 寄存器读写函数 ===================================================

// reg:目标寄存器地址  buf:要写的数据  len:要写的信息的长度
static int ft5x06_write_reg(struct ft5x06_dev_struct *dev, u8 reg, u8 *buf, int len){

    int err = 0;
    struct i2c_client *client = (struct i2c_client*)dev->client;
    struct i2c_msg msg;
    u8 b[256];
 
    b[0] = reg;
    memcpy(&b[1], buf, len);
 
    msg.addr = client->addr;// 从机地址(AP3216C)
    msg.flags = 0;          // 发送。flag详见1.4
    msg.buf = b;            // 发送内容:要读取的寄存器地址
    msg.len = len + 1;      // 要发送的数据长度(len) + 寄存器地址长度(1)
 
    err = i2c_transfer(client->adapter, &msg, 1); // 这里要写成&msg!不是msg
    if(err != 1){
        printk("mpu6050_write_reg fail!\r\n");
        return -1;
    }
    return 0;
    
}
// reg:目标寄存器地址  data:保存读取到的信息  len:要读取信息的长度
static int ft5x06_read_reg(struct ft5x06_dev_struct *dev, u8 reg, void *data, int len){
    int err = 0;
    struct i2c_client *client = (struct i2c_client*)dev->client;
    struct i2c_msg msg[2];
 
    // ①发送:要读取的寄存器的地址
    msg[0].addr = client->addr; // 从机地址
    msg[0].flags = 0;           // 发送
    msg[0].buf = &reg;          // 要发送的数据:目标寄存器地址
    msg[0].len = 1;             // 发送数据的长度
 
    // ②接收:读到的目标寄存器的数据
    msg[1].addr = client->addr; // 从机地址
    msg[1].flags = I2C_M_RD;    // 接收
    msg[1].buf = data;          // 接收的数据保存到data
    msg[1].len = len;           // 接收数据的长度   
 
    // 发送msg
    err = i2c_transfer(client->adapter, msg, 2);
 
    if (err!= 2){
        printk("ft5x06_read_reg fail!\r\n");
        return -1;
    }
    return 0;
}




// 中断处理函数
static irqreturn_t ft5x06_handler(int irq, void *dev_i){
    // printk("handler!\r\n");

    struct ft5x06_dev_struct *dev = dev_i;
    u8 rdbuf[FT5X06_READLEN];
    int i,type,x,y,id;
    int offset = 1; // 偏移1。我们从触摸状态寄存器0x02开始读取,触摸点寄存器的起始地址为0x02+offset=0x03
    int tplen = 6;  // 每个触摸点6个寄存器(虽然有两个没用到)
    int ret;
    bool down;

    memset(rdbuf, 0, sizeof(rdbuf));

    // 读取触摸点信息
    ft5x06_read_reg(dev, FT5X06_TD_STATUS_REG, rdbuf, FT5X06_READLEN);// 一次性读取全部29个寄存器信息
    for(i=0;i<MAX_SUPPORT_POINTS;i++){ // 提取每个触摸点的信息
        // 每个触摸点的起始地址(X高位起始地址)
        u8 *buf = &rdbuf[i * tplen + offset];

        type = buf[0] >> 6; // 触摸事件标志(寄存器[7:6]位)
        if(type == TOUCH_EVENT_RESERVED) continue; // 保留,直接下一个

        // 我们的触摸屏的寄存器是先Y后X,和pdf上的
        y = ((buf[0] << 8) | buf[1]) & 0x0fff; // 只留低12位
        x = ((buf[2] << 8) | buf[3]) & 0x0fff; // 只留低12位

        id = (buf[2] >> 4) & 0x0f;
        down = (type == TOUCH_EVENT_UP); // 如果是释放则为True(也就是按下/触摸为False)

        
        // 将触摸点信息上报  依照TypeB流程:
        input_mt_slot(dev->input, id);
        input_mt_report_slot_state(dev->input, MT_TOOL_FINGER, down);


        if(down){ // 释放,不上报,直接下一个
            continue;
        }
        else{ // 上报坐标信息
            input_report_abs(dev->input, ABS_MT_POSITION_X, x);
            input_report_abs(dev->input, ABS_MT_POSITION_Y, y);
        }
    }

    input_mt_report_pointer_emulation(dev->input, true);
    input_sync(dev->input);  // 结束


    return IRQ_HANDLED;
}

// ft5x06复位脚初始化
static int ft5x06_ts_reset(struct i2c_client *client, struct ft5x06_dev_struct *dev){
    int ret = 0;
    if(gpio_is_valid(dev->reset_pin)){ // 如果reset脚有效,则申请

        // 申请reset脚,默认低电平
        ret = devm_gpio_request_one(&client->dev, dev->reset_pin, GPIOF_OUT_INIT_LOW, "edt-ft5x06 reset");// devm,不需要手动释放
        if(ret){
            dev_err(&client->dev,"Failed to request GPIO %d, error %d\r\n",dev->reset_pin,ret);
            return ret;
        }
        msleep(5);
        gpio_set_value(dev->reset_pin, 1); // 拉高,停止复位
        msleep(300);
    }
    return 0;
}

// 中断脚初始化
static int ft5x06_ts_irq(struct i2c_client *client, struct ft5x06_dev_struct *dev){
    int ret = 0;
    if(gpio_is_valid(dev->irq_pin)){
        ret = devm_gpio_request_one(&client->dev, dev->irq_pin, GPIOF_IN, "edt-ft5x06 irq");

        if(ret){
            dev_err(&client->dev,"Failed to request GPIO %d, error %d\r\n",dev->irq_pin,ret);
            return ret;
        }
    }
    ret = devm_request_threaded_irq(&client->dev, client->irq, NULL, ft5x06_handler, IRQF_TRIGGER_FALLING | IRQF_ONESHOT, client->name, &ft5x06dev);
    return ret;
}


 
// 匹配表 ===================================================
 
static struct i2c_device_id ft5x06_id[] = {
    {"edt-ft5426",0},
    {/*sentinel*/},
};
static struct of_device_id ft5x06_of_match[] = {
    {.compatible = "edt,edt-ft5426"},
    {/*sentinel*/},
};
 




// 总线设备结构体 ===================================================
static int ft5x06_probe(struct i2c_client *client, const struct i2c_device_id *id){
    pr_info("ft5x06 probe!\r\n");

    int ret = 0;
    ft5x06dev.client = client;

    // 获取irq和reset引脚
    ft5x06dev.irq_pin = of_get_named_gpio(client->dev.of_node, "interrupt-gpios", 0);
    ft5x06dev.reset_pin = of_get_named_gpio(client->dev.of_node, "reset-gpios", 0);

    // 初始化引脚
    ret = ft5x06_ts_reset(client, &ft5x06dev);
    if(ret<0)goto fail;
    ret = ft5x06_ts_irq(client, &ft5x06dev);
    if(ret<0)goto fail;

    // 初始化ft5426
    int data;
    data = 0;
    ft5x06_write_reg(&ft5x06dev, FT5x06_DEVICE_MODE_REG, &data, 1); // 正常模式
    data = 1;
    ft5x06_write_reg(&ft5x06dev, FT5426_IDG_MODE_REG, &data, 1);     // 中断模式

    // INPUT框架
    
    ft5x06dev.input = devm_input_allocate_device(&ft5x06dev.client->dev);
    if(!ft5x06dev.input){
        ret = -ENOMEM;
        goto fail;
    }

    ft5x06dev.input->name = FT5X06_NAME;
    ft5x06dev.input->id.bustype = BUS_I2C;
    ft5x06dev.input->dev.parent = &client->dev;


    __set_bit(EV_SYN, ft5x06dev.input->evbit); // 同步事件
    __set_bit(EV_KEY, ft5x06dev.input->evbit); // 按键事件
    __set_bit(EV_ABS, ft5x06dev.input->evbit); // 绝对坐标
    __set_bit(BTN_TOUCH, ft5x06dev.input->keybit); // 触摸

    // 单点触摸
    input_set_abs_params(ft5x06dev.input, ABS_X, 0, 1024, 0, 0); // X绝对坐标:0~1024
    input_set_abs_params(ft5x06dev.input, ABS_Y, 0,  600, 0, 0); // Y绝对坐标:0~600

    // 多点触摸
    input_mt_init_slots(ft5x06dev.input, MAX_SUPPORT_POINTS, 0); // 最大5点触摸
    input_set_abs_params(ft5x06dev.input, ABS_MT_POSITION_X, 0, 1024, 0, 0);
    input_set_abs_params(ft5x06dev.input, ABS_MT_POSITION_Y, 0,  600, 0, 0);

    ret = input_register_device(ft5x06dev.input);
    if(ret)goto fail;

    return 0;

fail:
    return ret;
}
 
static int ft5x06_remove(struct i2c_client *client){
    pr_info("ft5x06 remove!\r\n");
    input_unregister_device(ft5x06dev.input);
    return 0;
}
 
static struct i2c_driver ft5x06_driver = {
    .probe = ft5x06_probe,
    .remove = ft5x06_remove,
    .driver = {
        .owner = THIS_MODULE,
        .name = "edt_ft5x06",
        .of_match_table = of_match_ptr(ft5x06_of_match),
    },
    .id_table = ft5x06_id,
};
 
// 驱动入口/出口 ===================================================
 
static int __init ft5x06_init(void){
    i2c_add_driver(&ft5x06_driver);
    return 0;
}
 
static void __exit ft5x06_exit(void){
    i2c_del_driver(&ft5x06_driver);
}
 
module_init(ft5x06_init);
module_exit(ft5x06_exit);
MODULE_LICENSE("GPL");
4.6.4 测试
bash 复制代码
# VSCODE终端
make
sudo cp ft5426.ko /home/for/linux/nfs/rootfs/lib/modules/4.1.15/

# 串口控制台

ls /dev/input/ -l

cd /lib/modules/4.1.15/
depmod
modprobe ft5426.ko

ls /dev/input/ -l   # 会发现多了一个event

probe之后,input设备中会多出来屏幕:

bash 复制代码
 hexdump /dev/input/event1   # 查看发送的信息    有的人可能屏幕不是event1,修改为自己的

此时触摸屏幕,就会打印INPUT信息。

INPUT发送的数据格式在INPUT的3.5中有讲(也可查看《指南pdf》64.4.2部分)

bash 复制代码
编号     秒        微秒         事件类型  事件码    值
0000120 0122 0000 dbb5 0000    0003    0039    ffff ffff
0000130 0122 0000 dbb5 0000    0003    0035    018e 0000
0000140 0122 0000 dbb5 0000    0003    0036    00d4 0000
0000150 0122 0000 dbb5 0000    0001    014a    0000 0000
0000160 0122 0000 dbb5 0000    0000    0000    0000 0000
0000170 0122 0000 ac17 0001    0003    0039    0002 0000
0000180 0122 0000 ac17 0001    0001    014a    0001 0000
0000190 0122 0000 ac17 0001    0003    0000    018e 0000
00001a0 0122 0000 ac17 0001    0003    0001    00d4 0000
00001b0 0122 0000 ac17 0001    0000    0000    0000 0000

事件类型和事件码的值表示的含义都可以在input.h中找到。

事件码为0x35表示X坐标,0x36表示Y坐标。要测试XY坐标是否正确,可以分别触摸屏幕左上角(XY坐标应接近0)和右下角(XY坐标应接近最大值,如1024*600)

(由于输出顺序问题,值的左边4个是低4位,右边4个是高4位)

4.7 移植tslib

4.7.1 安装

翻译input的信息还是太抽象了。不如直接将触摸点画在屏幕上,这就需要tslib了。

从"【正点原子】阿尔法Linux开发板(A盘)-基础资料\01、例程源码\07、第三方库源码"中找到tslib-1.21.tar.bz2文件,发到ubuntu上。

bash 复制代码
cd ............  # cd到tslib包的路径下
tar ivxjf tslib-1.21.tar.bz2
sudo chown <usr>:<usr> tslib-1.21 -R  # 修改tslib源码所属用户。把<usr>替换为自己的ubuntu登录用户
                                      # 如用户名为for,就是...... for:for ......
                                      # 命令行最前面的<usr>@ubuntu......就是用户名
# 安装下面这几个,防止编译tslib出错:
sudo apt-get install autoconf
sudo apt-get install automake
sudo apt-get install libtool

mkdir tslib   # 保存编译结果
cd tslib-1.21
./autogen.sh
./configure --host=arm-linux-gnueabihf --prefix=/..<路径>../tslib   # 后面的这个路径就是上面mkdir tslib的绝对路径
make         # 编译
make install # 安装

cd ../tslib  # 进入stlib文件目录下
sudo cp * -rf /..../nfs/rootfs  # 将stlib中的所有文件都发给开发板的根文件系统

打开开发板。

bash 复制代码
vi etc/ts.conf   # 检查ts.conf中的"module_raw input"这一行,这句话前面如果有"#"则删掉#
bash 复制代码
vi etc/profile

将下面6行代码贴到profile文件中(其中第一行和最后一行需要根据自己的情况修改,具体怎么改看下一段的注释)

bash 复制代码
export TSLIB_TSDEVICE=/dev/input/event1
export TSLIB_CALIBFILE=/etc/pointercal
export TSLIB_CONFFILE=/etc/ts.conf
export TSLIB_PLUGINDIR=/lib/ts
export TSLIB_CONSOLEDEVICE=none
export TSLIB_FBDEVICE=/dev/fb0

其含义如下:

bash 复制代码
export TSLIB_TSDEVICE=/dev/input/event1  # 触摸设备文件。依照4.6.4中测试,如果你的屏不是event1就要修改
export TSLIB_CALIBFILE=/etc/pointercal   # 校准文件,如果进行屏幕校准的话校准结果就保存在这个文件中
export TSLIB_CONFFILE=/etc/ts.conf       # 触摸配置文件
export TSLIB_PLUGINDIR=/lib/ts           # tslib插件目录位置
export TSLIB_CONSOLEDEVICE=none          # 控制台设置,这里不设置
export TSLIB_FBDEVICE=/dev/fb0           # FB设备,也就是屏幕。根据实际情况配置,我的屏幕文件为/dev/fb0,因此这里设置为/dev/fb0。

至于怎么看自己的屏是不是/dev/fb0:

bash 复制代码
cat /sys/class/graphics/fb0/modes   # 查看分辨率,看看和自己的屏幕是否符合

至此配置完成tslib。

4.7.2 校准

电容屏可以不用校准,电阻屏则必须校准。

首先modprobe,然后执行命令:

bash 复制代码
ts_calibrate

校准完成以后如果不满意,或者不小心对电容屏做了校准,那么直接删除掉/etc/pointercal 文件即可。

4.7.3 测试

测试触摸屏工作是否正常,以及多点触摸是否有效。

首先modprobe,然后执行命令:

bash 复制代码
ts_test_mt

页面上面有三个按钮:拖拽、绘画、退出。直接测试即可。

4.8 将驱动添加到内核中

前面我们一直将触摸屏驱动编译为模块,每次手动加载驱动模块,这样很不方便。可以将其编译到内核中,内核启动以后就会自动加载,不需要再手动modprobe。

首先在内核源码中找个合适的位置将ft5x06.c放进去。ft5x06.c是触摸屏驱动,我们需要查找一下linux内核里面触摸屏驱动放到了哪个目录下。linux内核里面将触摸屏驱动放到了 drivers/input/touchscreen目录下,因此将ft5x06.c拷贝到这:

(但是!我自作聪明把ft5x06的寄存器宏定义写到ft5426reg.h里面去了,所以要么把ft5426reg.h也复制到这个目录下,要么把寄存器定义直接写到驱动代码里面去)

bash 复制代码
cp ft5x06.c   /..../linux/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/drivers/input/touchscreen/ -f

再修改drivers/input/touchscreen下的Makefile。在最后一行添加:

bash 复制代码
obj-y += ft5x06.o  # 依据你的驱动c文件名称而定,比如我就是ft5426.0了

重新编译linux内核,并将新的zImage贴到tftp路径下:

bash 复制代码
make
sudo cp arch/arm/boot/zImage   /..../tftpboot/

重启开发板,如果成功的话,启动信息里面应该能看到:

测试屏幕是否正常:

bash 复制代码
hexdump /dev/input/event1  # 按照自己的eventX修改

# 正点原子视频中有event0/1/2,在modprobe时屏幕是even2,而自动加载时屏幕就变成even1了,因为加载顺序发生变化
# 如果event编号变了的话,想使用tslib,记得还要修改vi etc/profile中的eventX

此时触摸屏幕,应当有数据输出。没有的话换个event试试。

4.9 Linux内核自带的触摸屏驱动

直接照着《指南pdf》64.6部分修改即可。

附录

A 两套SNVS_TAMPER宏

imx6ull-pinfunc-snvs.h文件中,关于SNVS的宏都是:

cpp 复制代码
#define MX6ULL_PAD_SNVS_TAMPER............

imx6ul-pinfunc.h文件中,关于SNVS的宏则都是:

cpp 复制代码
#define MX6UL_PAD_SNVS_TAMPER............

而在imx6ull-pinfunc.h文件中则没有关于SNVS的宏。

区别就在于6ull和6ul。

① 6ull

cpp 复制代码
#define MX6ULL_PAD_SNVS_TAMPER............

这一坨是给IOMUXC_SNVS模块,也就是SNVS域的IOMUX控制器用的(对应设备树&iomuxc_snvs部分)

② 6ul

cpp 复制代码
#define MX6UL_PAD_SNVS_TAMPER............

这一坨是给普通IOMUXC用的(对应设备树&iomuxc部分)。IMX6UL为了省电和安全,把SNVS_TAMPER挪到了SNVS IOMUXC模块,所以专门弄了一个imx6ull-pinfunc-snvs.h来描述SNVS域的寄存器。

③ 具体使用

IMX6ULL 上,SNVS_TAMPER0~9都由SNVS IOMUXC控制器管理,如果使用MX6UL _PAD_SNVS_TAMPER,是完全不起作用的。必须在&iomuxc_snvs节点中使用MX6ULL_PAD_SNVS_TAMPER。

B 增加LCD作为控制台后开发板无法正常启动

常见的原因:

1、使用udp传输,数据量变大以后就会出错,可以改为tcp传输,详见nfs:server is not responding

2、电源问题,供电不足,把开发板的电源线插上。

3、依然是数据量的问题,竟然将设备树中屏幕的频率调!低!就!可!以!了!将4.4.2中display-timings的clock-frequency调低,比如我将其从51.2M调到30M就正常了。也不要太低,我调成3M直接花屏,调到20M有明显的闪烁。也是浪费一下午找到的方法啊,怎么感觉网上大家都是改成tcp就直接好了。

C 关闭LCD定时熄屏

默认10分钟熄屏,此时触摸屏幕是唤醒不了的,只能按键或者插上键盘唤醒

相关内容详见《指南pdf》59.4.4部分:

修改linux内核中drivers/tty/vt/vt.c文件的static int blankinterval = 10*60这一行,令其等于0即可。然后重新编译linux内核,并将zImage传给tftproot。

相关推荐
TL滕1 小时前
从0开始学算法——第七天(快速排序算法练习)
笔记·学习·算法·排序算法
摇滚侠1 小时前
2025最新 SpringCloud 教程,Gateway 路由-规则配置,笔记53
笔记·spring cloud·gateway
fy zs1 小时前
Linux线程互斥与同步
linux·c++
---学无止境---1 小时前
sys_ioperm 函数详解
linux
Dovis(誓平步青云)1 小时前
《从内核视角看 Linux:环形缓冲区 + 线程池的生产消费模型实现》
linux·运维·服务器
老王熬夜敲代码1 小时前
万能引用、完美转发
c++·笔记
Cincoze-Johnny1 小时前
Linux系统-应用问题全面剖析Ⅳ:德承工控机MD-3000在Ubuntu操作系统下[TPM功能]设置教程
linux·运维·ubuntu
默|笙1 小时前
【Linux】进程(1)
linux·运维·服务器
某林2121 小时前
基于ROS2与EKF的四轮差速机器人里程计精度优化:解决建图漂移与重影问题
linux·stm32·嵌入式硬件·slam·智能小车