IIC subsystem and touch screen driver under Linux

IIC subsystem and touch screen driver under Linux

1. Introduction to IIC

  The I2C (Inter-Integrated Circuit) bus is a two-wire serial bus developed by PHILIPS for connecting microcontrollers and their peripherals. It is a bus standard widely used in the field of microelectronics communication control. It has the advantages of less interface lines, simple control mode, small package form of the device, and high communication rate.

  • I2C characteristics

      (1) only require two bus lines, one serial data line SDA, and one serial clock line SCL;
      (2) each device connected to the bus can pass a unique address and always-existing simple master/slave The address is set by the machine-related software, and the host can be used as a host transmitter or a host receiver;
      (3) It is a real multi-master bus, if two or more hosts initiate data transmission at the same time, the data can be prevented from being corrupted by collision detection and arbitration Destruction;
      (4) Serial 8-bit bidirectional data transmission bit rate can reach 100kbit/s in standard mode, 400kbit/s in fast mode, and 3.4Mbit/s in high-speed mode; (5) On-chip
      filtering The filter can filter the burr wave on the bus data line to ensure data integrity;
      (6) The number of ICs connected to the same bus is only limited by the maximum capacitance of the bus 400pF;
      IIC is a serial communication bus, synchronous transmission, half-duplex.

  Each device on the I2C bus can be used as a master device or a slave device, and each device will correspond to a unique address (can be known from the data sheet of the I2C device), and the master and slave devices are determined by this address. Which device communicates? In common applications, we use the CPU module with I2C bus interface as the master device, and use other devices connected to the bus as slave devices.

  IIC timing introduction reference: STM32CubeMx hardware IIC drive EEPROM

2. IIC driver under Linux

  There are two ways to write the IIC driver under Linux:
   1. Regard the IIC driver as a common character device to realize driver registration;
   2. Use the IIC driver framework (IIC subsystem) under Linux to complete the registration;
   the first method is easy to operate, only need The basic character device driver framework can complete the IIC driver registration; but the portability is poor; the second method is more complicated to operate and needs to master the registration process of the IIC subsystem framework, but the portability is strong.

2.1 IIC subsystem driving model

insert image description here
  As can be seen from the above figure, the IIC framework is divided into three layers: adapter layer, device layer and driver layer. The relationship between the three layers can be simplified as follows:
insert image description here
  the adapter layer mainly implements IIC hardware interface configuration and generates IIC implementation, and the code is generally provided by the chip manufacturer.
  The device layer mainly obtains the IIC hardware resource by calling the function of the adapter (obtains the relevant interface function of the IIC timing), determines the device address of the module according to the IIC module manual and the hardware schematic diagram, and completes the registration of the IIC device layer.
  The driver layer completes the matching with the device layer through the device name, obtains device resources, registers the device driver, and implements the relevant interface functions of the application layer.

2.2 IIC subsystem related interface functions

2.2.1 Device layer interface function

  • IIC device layer registration steps:
  1. Call the get adapter function i2c_get_adapter to get the IIC adapter resource according to the IIC bus number;
  2. Fill the struct i2c_board_info structure and call i2c_new_device to register the IIC device;
  3. Call the IIC device unregister function i2c_unregister_device when unregistering the device ;
  • Get the adapter resource i2c_get_adapter

struct i2c_adapter *i2c_get_adapter(int nr)
function: get adapter resource
parameter: nr --IIC bus number
return value: successfully return adapter resource structure pointer

  • struct i2c_board_info structure
      In the struct i2c_board_info structure, the parameters that need to be related are device name type and device address addr . If there is an interrupt resource, you can fill in the interrupt number irqr .
struct i2c_board_info {
    
    
	char		type[I2C_NAME_SIZE];//名字,驱动层和设备层匹配参数
	unsigned short	flags;//设备地址位数,一般不填或填0表示7位地址
	unsigned short	addr;//IIC设备地址
	void		*platform_data;//私有数据
	struct dev_archdata	*archdata;
	struct device_node *of_node;
	int		irq;//中断号
};
  • Register device i2c_new_device

struct i2c_client *i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
function function: register IIC device layer
formal parameters: adap–IIC adapter resource
    info --IIC device board level resource information
return value: return IIC information structure after successful registration pointer

  • unregister device i2c_unregister_device

void i2c_unregister_device(struct i2c_client *client)
function function: unregister IIC device layer
formal parameters: client–IIC information structure pointer, return value of IIC registration function;

2.2.2 Driver layer interface function

  • IIC driver layer registration steps:
  1. Fill the driver layer structure struct i2c_driver and call the driver registration function i2c_add_driver ;
  2. Call the logout function i2c_del_driver when the driver is logged out ;
  • Driver layer structure struct i2c_driver

  In the struct i2c_driver structure, we must implement the resource matching function probe, the resource release function remove, the device name in the structure struct device_driver, and the resource matching structure id_table .

truct i2c_driver {
    
    
int (*probe)(struct i2c_client *, const struct i2c_device_id *);//资源匹配函数
	int (*remove)(struct i2c_client *);//资源释放函数
	struct device_driver driver;//驱动相关结构体信息
	const struct i2c_device_id *id_table;//资源匹配结构体
};
  • Driver registration and unregistration functions

//Driver registration function
#define i2c_add_driver(driver)
//Driver cancellation function
void i2c_del_driver(struct i2c_driver *);

2.2.3 IIC read and write string functions

//读取多个数据函数,成功返回读取到的字节数,注意,改函数最大多32字节
s32 i2c_smbus_read_i2c_block_data(const struct i2c_client *client,u8 command, u8 length, u8 *values);
//写入多个数据函数,成功返回0,注意,改函数最大多32字节
s32 i2c_smbus_write_i2c_block_data(const struct i2c_client *client,u8 command, u8 length,const u8 *values);

3. Capacitive screen ft5x06 driver registration

  FT5x06 series integrated circuit MCU capacitive touch panel controller integrated circuit with a built-in 8-bit microcontroller unit (MCU) They adopt mutual capacitance method to support multi-touch function. Combined with the common capacitive touch panel, the FT5x06 has friendly input functions and can be applied to many portable devices such as mobile phones, MIDs, netbooks and notebook PCs.

  • FT5X06 Core Architecture
    insert image description here
      FT5X06 supports three interfaces: I2C, SPI, and UART. This time we use the IIC protocol to complete the driver registration.
       I2C interface timing of FT5X06:
    insert image description here

3.1 FT5X06 driver registration

Hardware platform: tiny4412
Development platform: ubuntu18.04
Cross compiler: arm-linux-gcc
Kernel: linux3.5

3.1.1 According to the schematic diagram, IIC hardware resource information is required

  To realize the device and driver layer registration of the IIC subsystem, the necessary parameters are: IIC bus number, device name, and device address .
  According to the schematic diagram of the Tiny4412 development board, it can be seen that the touch screen interface uses the I2C1 bus, and the interrupt detection pin is GPX1_6.
insert image description here
insert image description here
  Since the kernel of tiny4412 has its own touch screen driver, the device address of FT5X06 can be found in the board-level registration of mach-tiny4412.c :
insert image description here
insert image description here

3.1.2 Cut the kernel and uninstall the built-in touch screen driver

  Since the kernel of tiny4412 has its own touch screen driver, if you want to write the driver yourself, you need to uninstall the touch screen driver in the kernel before you can register your own driver file. That is, it is necessary to complete the kernel cutting and uninstall the built-in touch screen driver.
  Open the configuration interface of the linux3.5 kernel, find the driver location of ft5x06, and uninstall it.

[wbyq@wbyq linux-3.5]$ make menuconfig
Device Drivers  --->
Input device support  ---> 
    Touchscreens  --->
       FocalTech ft5x0x TouchScreen driver

insert image description here
  Finally, recompile the kernel, program the kernel, and restart the development board.

3.1.3 Register IIC device layer

#include <linux/kernel.h>
#include <linux/module.h>
#include <asm/io.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/platform_device.h>
#include <linux/i2c.h>

#include <linux/gpio.h>
#include <mach/gpio.h>
#include <plat/gpio-cfg.h>

static struct i2c_board_info ft5x06_info=
{
    
    
	I2C_BOARD_INFO("ft5x06",0x38),
	//.irq=	
};
static struct i2c_client *client;

static int __init wbyq_ft5x06_dev_init(void)
{
    
    
	ft5x06_info.irq=gpio_to_irq(EXYNOS4_GPX1(6));//中断号
	struct i2c_adapter *adap=i2c_get_adapter(1);//获取适配器编号
	client=i2c_new_device(adap,&ft5x06_info);
	i2c_put_adapter(adap);
	if(client==NULL)return -ENODEV;
	printk("ft5x06设备层注册成功\n");
    return 0;
}
static void __exit wbyq_ft5x06_dev_cleanup(void)
{
    
    
	/*注销设备层*/
	i2c_unregister_device(client);
	printk("ft5x06设备层注销成功\n");
	
}
module_init(wbyq_ft5x06_dev_init);//驱动入口函数
module_exit(wbyq_ft5x06_dev_cleanup);//驱动出口函数

MODULE_LICENSE("GPL");//驱动注册协议
MODULE_AUTHOR("it_ashui");
MODULE_DESCRIPTION("Exynos4 eeprom_dev Driver");

3.1.4 Register IIC driver layer

  Register the IIC driver layer and complete the FT5X06 interrupt registration. Initialize work, initialize wait queue heads, register miscellaneous devices. Implement the ioctl function interface and the poll function interface.

#include <linux/kernel.h>
#include <linux/module.h>
#include <asm/io.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/platform_device.h>
#include <linux/i2c.h>
#include <linux/delay.h>
#include <linux/workqueue.h>
#include <linux/interrupt.h>
#include <linux/wait.h>
#include <linux/poll.h>

#include <linux/gpio.h>
#include <mach/gpio.h>
#include <plat/gpio-cfg.h>
#define TOUCH_GETXY 0X80
static struct work_struct ft5x06_work;
static struct i2c_client *i2c_client;
struct Touch_xy
{
    
    
	int x;
	int y;
	int stat;
};
static struct Touch_xy touchxy;
DECLARE_WAIT_QUEUE_HEAD(ft5x06_q);//等待队列头
static void ft5x06_work_func(struct work_struct *work)
{
    
    	
	u8 val_data[8];
	u32 x,y;
	u32 stat;
	i2c_smbus_read_i2c_block_data(i2c_client,0,8,val_data);
	x=(val_data[3]&0Xf)<<8|val_data[4];
	y=(val_data[5]&0Xf)<<8|val_data[6];
	stat=val_data[2]&0xf;
	if(stat)
	{
    
    
		touchxy.x=x;
		touchxy.y=y;
		touchxy.stat=1;
	}
	else
	{
    
    
		touchxy.stat=0;
	}
	wake_up(&ft5x06_q);//唤醒进程
	//printk("(x,y):%d,%d\n",x,y);	
}
static irqreturn_t  ft5x06_irq_work(int irq, void *dev)
{
    
    
	schedule_work(&ft5x06_work);
	return IRQ_HANDLED;
}

static int ft5x06_open(struct inode *inode, struct file *fp)
{
    
    
	printk("open函数调用成功");
	return 0;
}
static long ft5x06_ioctl(struct file *fp, unsigned int cmd, unsigned long data)
{
    
    
	int ret;
	switch(cmd)
	{
    
    
		case TOUCH_GETXY:
			ret=copy_to_user((void *)data,&touchxy,sizeof(touchxy));
			if(ret)return -1;
		default:
				return -1;
	}
	return 0;
}
static unsigned int ft5x06_poll(struct file *filp, struct poll_table_struct *p)
{
    
    
	int mask=0;
	poll_wait(filp,&ft5x06_q, p);
	if(touchxy.stat)mask|=POLLIN;
	return mask;
}
static int ft5x06_release(struct inode *inode, struct file *fp)
{
    
    
	printk("release函数调用成功");
	return 0;
}
static struct file_operations  ft5x06_fops=
{
    
    
	.open			=ft5x06_open,
	.unlocked_ioctl	=ft5x06_ioctl,
	.poll			=ft5x06_poll,
	.release		=ft5x06_release
};
static struct miscdevice ft5x06_misc=
{
    
    
	.minor=MISC_DYNAMIC_MINOR,
	.name="ft5x06",
	.fops=&ft5x06_fops
};

static int ft5x06_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
    
    
	int ret;
	i2c_client=client;
	printk("资源匹配成功addr=%#x,irq=%d\n",client->addr,client->irq);
	/*初始化工作*/
	INIT_WORK(&ft5x06_work, ft5x06_work_func);
	/*注册中断*/
	ret=request_irq(client->irq,ft5x06_irq_work,IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING,"ft5x06",NULL);
	if(ret)
	{
    
    
		printk("中断注册失败\n");
		return ret;
	}
	/*注册杂项设备*/
	misc_register(&ft5x06_misc);
	return 0;
}
static int eeprom_remove(struct i2c_client *client)
{
    
    
	printk("eeprom 驱动层资源释放成功\n");
	misc_deregister(&ft5x06_misc);
	free_irq(client->irq,NULL);
	return 0;
}
static struct i2c_device_id id_table[]=
{
    
    
		{
    
    "ft5x06",0},
		{
    
    }
};

static struct i2c_driver ft5x06_driver=
{
    
    
	.probe=ft5x06_probe,
	.remove=eeprom_remove,
	.driver=
	{
    
    
		.name="ft5x06_drv",
	},
	.id_table=id_table,
};
static int __init wbyq_eeprom_drv_init(void)
{
    
    
	int ret;
	ret=i2c_add_driver(&ft5x06_driver);
	if(ret)return ret;
	printk("ft5x06驱动层注册成功\n");
    return 0;
}
/*驱动释放*/
static void __exit wbyq_eeprom_drv_cleanup(void)
{
    
    
	/*注销设备层*/
	i2c_del_driver(&ft5x06_driver);
	printk("ft5x06驱动层注销成功\n");

}
module_init(wbyq_eeprom_drv_init);//驱动入口函数
module_exit(wbyq_eeprom_drv_cleanup);//驱动出口函数

MODULE_LICENSE("GPL");//驱动注册协议
MODULE_AUTHOR("it_ashui");
MODULE_DESCRIPTION("Exynos4 platform_drv Driver");

3.1.5 Writing application layer functions

  The LCD application programming is completed through the frame buffer framework, and the drawing point function is realized. Open the touch screen driver, get the coordinates of the touch screen, call the drawing point function, and draw the real-time coordinate position.

#include <stdio.h>
#include <linux/fb.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <string.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <unistd.h>
#include <poll.h>
#define TOUCH_GETXY 0X80
struct Touch_xy
{
    
    
	int x;
	int y;
	int stat;
};
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;

/*LCD画点函数*/
static unsigned char *lcd_p=NULL;//屏幕缓存地址
static struct fb_fix_screeninfo fb_fix;//固定参数结构体
static struct fb_var_screeninfo fb_var;//可变参数结构体
void LCD_DrawLine(u16 x1, u16 y1, u16 x2, u16 y2,u32 c);
static inline void LCD_DrawPoint(int x,int y,int c)
{
    
    
	//获取要绘制的点的地址
	unsigned int *p= (unsigned int *)(lcd_p+y*fb_fix.line_length+x*fb_var.bits_per_pixel/8);
	*p=c;//写入颜色值
}

int main()
{
    
    
	/*1.打开设备*/
	int fd=open("/dev/fb0", 2);
	if(fd<0)
	{
    
    
		printf("打开设备失败\n");
	}
	/*2.获取固定参数*/
	memset(&fb_fix,0, sizeof(fb_fix));
 	ioctl(fd,FBIOGET_FSCREENINFO,&fb_fix);
	printf("屏幕缓存大小:%d\n",fb_fix.smem_len);
	printf("一行的字节数:%d\n",fb_fix.line_length);
	/*3.获取屏幕可变参数*/
	memset(&fb_var,0, sizeof(fb_var));
	ioctl(fd,FBIOGET_VSCREENINFO,&fb_var);
	printf("屏幕尺寸:%d*%d\n",fb_var.xres,fb_var.yres);
	printf("颜色位数:%d\n",fb_var.bits_per_pixel);
	/*4.将屏幕缓冲区映射到进程空间*/
	lcd_p=mmap(NULL,fb_fix.smem_len,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
	close(fd);
	if(lcd_p==(void *)-1)
	{
    
    
		printf("内存映射失败\n");
		return 0;
	}
	memset(lcd_p,0xff,fb_fix.smem_len);//将屏幕清空为白色
	
	fd=open("/dev/ft5x06",2);
	if(fd<0)
	{
    
    
		printf("设备打开失败\n");
		return 0;
	}
	struct pollfd fds=
	{
    
    
		.fd=fd,
		.events=POLLIN,
		
	};
	int ret;
	struct Touch_xy touchxy;
	int x1,y1;
	while(1)
	{
    
    
		ret=poll(&fds,1,-1);
		ioctl(fd,TOUCH_GETXY,&touchxy);
		if((x1-touchxy.x >=25|| touchxy.x-x1>=25) || (y1-touchxy.y >=25|| touchxy.y-y1>=25))
		{
    
    
			x1=touchxy.x;
			y1=touchxy.y;
		}
		if(x1!=touchxy.x || touchxy.y!=y1)
		{
    
    
			LCD_DrawLine(touchxy.x, touchxy.y, x1, y1,0);
			x1=touchxy.x;
			y1=touchxy.y;
		}		
	}	
AA:	
	//取消映射
	munmap(lcd_p,fb_fix.smem_len);
	return 0;
}
/*
函数功能:画直线
参    数:
x1,y1:起点坐标
x2,y2:终点坐标 
*/
void LCD_DrawLine(u16 x1, u16 y1, u16 x2, u16 y2,u32 c)
{
    
    
	u16 t; 
	int xerr=0,yerr=0,delta_x,delta_y,distance; 
	int incx,incy,uRow,uCol; 
	delta_x=x2-x1; //计算坐标增量 
	delta_y=y2-y1; 
	uRow=x1; 
	uCol=y1; 
	if(delta_x>0)incx=1; //设置单步方向 
	else if(delta_x==0)incx=0;//垂直线 
	else {
    
    incx=-1;delta_x=-delta_x;} 
	if(delta_y>0)incy=1; 
	else if(delta_y==0)incy=0;//水平线 
	else{
    
    incy=-1;delta_y=-delta_y;} 
	if( delta_x>delta_y)distance=delta_x; //选取基本增量坐标轴 
	else distance=delta_y; 
	for(t=0;t<=distance+1;t++ )//画线输出 
	{
    
      
		LCD_DrawPoint	(uRow,uCol,c);//画点
		xerr+=delta_x ; 
		yerr+=delta_y ; 
		if(xerr>distance) 
		{
    
     
			xerr-=distance; 
			uRow+=incx; 
		} 
		if(yerr>distance) 
		{
    
     
			yerr-=distance; 
			uCol+=incy; 
		} 
	}  
}  

insert image description here

Guess you like

Origin blog.csdn.net/weixin_44453694/article/details/126883226