Linux内核编程(十四)IIC总线驱动FT5X06触摸屏


  

前述:

   本章涉及知识点:输入子系统、GPIO子系统、IIC、中断、设备树等。 对于IIC的基础知识,这里不做过多的介绍,详细情况查看下面的两篇文章。
文章一:超详细!新手必看!STM32基础-IIC串行通信协议-IO口模拟IIC操作BMP180。
文章二:Linux应用编程(四)IIC(获取BMP180温度/气压数据)。

一、IIC子系统框架

在这里插入图片描述

二、FT5X06触摸屏寄存器

1. x值的寄存器

在这里插入图片描述

在这里插入图片描述

2. y值的寄存器

在这里插入图片描述
y值也同理,也是由寄存器的12位构成。

3. 触摸寄存器

判断是否被触摸/按下。
在这里插入图片描述

二、I2C设备驱动层

1. i2c_client编写(C语言版-旧内核)

这里的i2c_client相当于平台总线的设备层,即描述硬件资源,使用如下:

i2c_client.c

#include <linux/module.h>
#include <linux/init.h>
#include <linux/i2c.h>

struct i2c_adapter *i2c_ada;

struct i2c_board_info ft5x06_info[] = {
    { I2C_BOARD_INFO("my-ft5x06", 0x38) },  //描述设备信息,用于驱动层匹配。
};

static int __init ft5x06_client_init(void) {
    struct i2c_client *client;

// 1. 获取IIC1的控制器。
    i2c_ada = i2c_get_adapter(1);  
    if (!i2c_ada) {
        printk(KERN_ERR "Failed to get i2c adapter\n");
        return -ENODEV;
    }
//2. 创建一个新设备    
    client = i2c_new_device(i2c_ada, ft5x06_info); 
    if (!client) {
        printk(KERN_ERR "Failed to create new i2c device\n");
        i2c_put_adapter(i2c_ada);  
        return -ENODEV;
    }
    return 0;
}

static void __exit ft5x06_client_exit(void) {
    // Put the adapter back after use
    i2c_put_adapter(i2c_ada);
}

module_init(ft5x06_client_init);
module_exit(ft5x06_client_exit);
MODULE_LICENSE("GPL");

i2c_driver.c
这里是一个简单示例,为了演示如何匹配。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/i2c.h>

// Probe function
static int ft5x06_probe(struct i2c_client *client, const struct i2c_device_id *id) {
    printk(KERN_INFO "This is ft5x06_probe\n");
    return 0;  // Return 0 to indicate successful probing
}

// Remove function
static int ft5x06_remove(struct i2c_client *client) {
    return 0;
}

const struct i2c_device_id table[] = {
	{"my-ft5x06", 0},
	{}
};

static struct i2c_driver ft5x06_driver = {
    .driver = {
        .owner = THIS_MODULE,
        .name = "my-ft5x06",
    },
    .probe = ft5x06_probe,
    .remove = ft5x06_remove,
    .id_table = table,	//匹配的设备列表
};

static int __init ft5x06_driver_init(void)
{
    int ret;
// 1. 注册一个IIC驱动。
    ret = i2c_add_driver(&ft5x06_driver);
    if (ret < 0) {
        printk(KERN_ERR "i2c_add_driver is error\n");
        return ret;
    }
    return 0;
}

// Module exit function
static void __exit ft5x06_driver_exit(void) 
{
// 2. 卸载一个IIC驱动。
    i2c_del_driver(&ft5x06_driver);
}

module_init(ft5x06_driver_init);
module_exit(ft5x06_driver_exit);

MODULE_LICENSE("GPL");

2. i2c_client编写(设备树版-新内核)

   这部分相当于平台总线的设备层代码,即用于描述硬件。当然可以使用设备树来替代。那么我们在使用设备树描述时该怎么写呢?
   答:首先我们确定使用哪个I2C控制器,每个控制器都在设备树通用文件中对应一个节点(内核以有)。假设我们使用i2c控制器1。使用如下所示。

i2c_client设备树节点。

&i2c{     //相当于在i2c1节点下添加节点。
	  status = "okay";     //使能控制器
      myft5x06:my-ft5x06@38{
	      compatible="my-ft5x06";
	      reg=<0x38>;
	      //复位引脚
		  reset-gpio==<&gpio0 RX_PB5 0>;  //gpio0 PB5 引脚,初始为低电平。
		  //中断引脚
		  interrupt-parent=<&gpio3>;   //使用gpio_c中断控制器。
		  interrupts-gpio = <&gpio3 RX_PA2  0>;  //gpio3 PA2引脚,初始为低电平。
		  interrupts=<36 IRQF_TRIGGER_FALLING>;   //中断号为36,触发方式为下降沿触发。

	  	  pinctrl-names = "default";  //固定属性名称,这个属性定义了引脚控制器配置的名称,default 表示默认配置。
		  pinctrl-0 = <&ft5x06_pinctrl>;
	};
};



&pinctrl{
	ft5x06_pinctrl:ft5x06-pinctrl{  //将两个引脚都复用为GPIO功能。
   		rockchip,pins = <0 RK_PB5 RK_FUNC_GPIO  &pcfg_pull_none>,<3 RK_PA2 RK_FUNC_GPIO  &pcfg_pull_none>; 
  }}


i2c_driver.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/input.h>
 
struct gpio_desc *reset_gpio; //复位按键
struct gpio_desc *irq_gpio; //中断按键
struct input_dev *myinput_dev;  //输入设备结构体
struct work_struct  my_work; //定义工作任务

extern struct i2c_client *ft5x06_client;
extern int ft5x06_read_reg(u8 reg_addr);
extern void ft5x06_write_reg(u8 reg_addr, u8 data, u16 len);

//中断下半部分
void work_task(struct work_struct *work)
{
     int TOUCH_XL,TOUCH_XH,x;
	 int TOUCH_YL,TOUCH_YH,y;
	 int Touch_State;
	 TOUCH_XH=ft5x06_read_reg(0x03);
     TOUCH_XL=ft5x06_read_reg(0x04);
	 x= ((TOUCH_XH<<8)|TOUCH_XL) & 0xfff; //取低11位。
	 
	 TOUCH_YH=ft5x06_read_reg(0x05);
	 TOUCH_YL=ft5x06_read_reg(0x06);
	 y= ((TOUCH_YH<<8)|TOUCH_YL) & 0xfff; //取低11位。

	 Touch_State=ft5x06_read_reg(0x02);
	 Touch_State=Touch_State &0xf; //获取寄存器值
	 
	 if(Touch_State == 0){    //没有按下
	 	input_event(myinput_dev, EV_KEY, BTN_TOUCH, 0);
	    input_event(myinput_dev, EV_SYN, SYN_REPORT, 0); //发送完毕
	    
	 }else{  //被按下
	    input_event(myinput_dev, EV_KEY, BTN_TOUCH, 1);
 		input_event(myinput_dev, EV_ABS, ABS_X, x);
		input_event(myinput_dev, EV_ABS, ABS_Y, y);
	    input_event(myinput_dev, EV_SYN, SYN_REPORT, 0); //发送完毕
 	 }
}

 //中断上半部分
irqreturn_t interrupt_handler1(int irq, void *arg)
{
	 printk("interrupt_handler1!\n");
	 schedule_work(&my_work);  //调度工作任务 
	 return IRQ_HANDLED;
}
 
static int ft5x06_probe(struct i2c_client *client, const struct i2c_device_id *id) 
{
	 int ret;
	 printk(KERN_INFO "This is ft5x06_probe\n");
	 ft5x06_client = client;
	 
 // 2. 在匹配上后获取硬件gpio资源
	 reset_gpio = gpiod_get_optional(&client->dev, "reset", 0);
	 if (IS_ERR(reset_gpio)) {
		 dev_err(&client->dev, "Failed to get GPIO for reset\n");
		 return PTR_ERR(reset_gpio);
	 }
 
	 irq_gpio = gpiod_get_optional(&client->dev, "interrupts", 0);
	 if (IS_ERR(irq_gpio)) {
		 dev_err(&client->dev, "Failed to get GPIO for interrupts\n");
		 return PTR_ERR(irq_gpio);
	 }
 
 // 3. 进行一次复位操作
	 gpiod_direction_output(reset_gpio, 0);
	 mdelay(5);
	 gpiod_direction_output(reset_gpio, 1);
	 
 // 4. 申请中断 ,使用中断上下文处理。
	 ret = request_irq(client->irq, interrupt_handler1, IRQF_TRIGGER_FALLING, "ft5x06_interrupt", NULL);  //这里触发方式要和设备树一致!
	 if (ret < 0) {
		 printk("request_irq error!\n");
		 return ret;
	 }
	 
	 
 // 5. 注册输入设备
	myinput_dev = input_allocate_device();  //为即将注册的输入设备分配内存和资源。
	 if (myinput_dev == NULL) {
		 printk(KERN_ERR "input_allocate_device failed\n");
		 return -ENOMEM;
	 }

	 myinput_dev->name ="myinput_dev";  //设置输入设备的名称。
	 
	 // 设置支持的事件类型
	 set_bit(EV_KEY, myinput_dev->evbit); // 设置支持按键事件
	 set_bit(EV_ABS , myinput_dev->evbit); // 设置支持绝对事件
	 set_bit(EV_SYN , myinput_dev->evbit); // 设置支持同步事件
	 set_bit(BTN_TOUCH, myinput_dev->keybit); // 触摸事件
	 set_bit(ABS_X, myinput_dev->absbit); // x绝对值
	 set_bit(ABS_Y, myinput_dev->absbit); // y绝对值
	 
	 input_set_abs_params(myinput_dev, ABS_X, 0,1024, 0,0);  //设置x值的范围
	 input_set_abs_params(myinput_dev, ABS_Y, 0,1024, 0,0);  //设置y值的范围
	 
      //注册输入设备
	 ret = input_register_device(myinput_dev);
	 if (ret < 0) {
		 printk(KERN_ERR "input_register_device failed: %d\n", ret);
		 goto error0;
	 }
	
// 6. 初始化共享工作任务,用于中断下文。
	 INIT_WORK(&my_work, work_task);  
     return 0;	
     
error0:
	 input_free_device(myinput_dev); //使用该函数释放输入设备分配的内存。
	 free_irq(client->irq, NULL);  //释放中断申请的内存。
	 gpiod_put(irq_gpio);
	 gpiod_put(reset_gpio);
	 return ret;
}
 
static int ft5x06_remove(struct i2c_client *client) 
{
	 free_irq(client->irq, NULL);
	 return 0;
}
 
static const struct of_device_id ft5x06_table[] = {
	 { .compatible = "my-ft5x06" },
	 {}
};
 
static struct i2c_driver ft5x06_driver = {
	 .driver = {
		 .owner = THIS_MODULE,
		 .name = "my-ft5x06",
		 .of_match_table = ft5x06_table,   //用于与设备树匹配。
	 },
	 .probe = ft5x06_probe,
	 .remove = ft5x06_remove,
};
 
static int __init ft5x06_driver_init(void) 
{
	int ret;
// 1. 注册一个IIC驱动。
	ret = i2c_add_driver(&ft5x06_driver);
	if (ret < 0) {
		printk(KERN_ERR "i2c_add_driver is error\n");
		return ret;
	}	
	return 0;
}
 
static void __exit ft5x06_driver_exit(void) 
{
	cancel_work_sync(&my_work);   //取消调度
	i2c_del_driver(&ft5x06_driver);  //删除IIC驱动。
	input_unregister_device(myinput_dev); // 当设备成功注销时,使用该函数注销设备
	input_free_device(myinput_dev); //使用该函数释放输入设备分配的内存。
	gpiod_put(irq_gpio); //释放GPIO内存
	gpiod_put(reset_gpio);
}
 
module_init(ft5x06_driver_init);
module_exit(ft5x06_driver_exit);

MODULE_LICENSE("GPL");

msg.c

#include <linux/i2c.h>
struct i2c_client *ft5x06_client;

int ft5x06_read_reg(u8 reg_addr)
{
    u8 data;
    struct i2c_msg msgs[] = {
        [0] = {
            .addr  = ft5x06_client->addr,  //器件地址
            .flags = 0,
            .buf   = &reg_addr,  //寄存器地址
            .len   = sizeof(reg_addr),
        },
        [1] = {
            .addr  = ft5x06_client->addr,
            .flags = 1,
            .buf   = &data,
            .len   = sizeof(data),
        },
    };

    i2c_transfer(ft5x06_client->adapter, msgs, 2);
    return data;
}

void ft5x06_write_reg(u8 reg_addr, u8 data, u16 len)
{
    u8 buff[256];
    struct i2c_msg msgs[] = {
        [0] = {
            .addr  = ft5x06_client->addr,  //器件地址
            .flags = 0,
            .buf   = buff,
            .len   = len + 1, // len + 1 to include the addr byte
        },
    };
    
    buff[0] = reg_addr;
    memcpy(&buff[1], &data ,len);
    i2c_transfer(ft5x06_client->adapter, msgs, 1);
}

三、应用层编写

app.c

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/input.h>

int main(int argc, char *argv[]) 
{
    int fd;
    struct input_event ft5x06_event;

    fd = open("/dev/input/event4", O_RDWR);
    if (fd < 0) {
        printf("open error\n");
        return -1;
    }

    while (1) {
		        read(fd, &ft5x06_event, sizeof(ft5x06_event));
		        if (ft5x06_event.type == EV_ABS) {
		            if (ft5x06_event.code == ABS_X) {
		                printf("x = %d\n", ft5x06_event.value);
		            } else if (ft5x06_event.code == ABS_Y) {
		                printf("y = %d\n", ft5x06_event.value);
		            }
		        }
    }

    close(fd);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值