Linux下IIC子系統和觸摸屏驅動
1.IIC簡介
I2C( Inter-Integrated Circuit)總線是由 PHILIPS 公司開發的兩線式串行總線,用于連接微控制器及其外圍設備。是微電子通信控制領域廣泛采用的一種總線標準。具有接口線少,控制方式簡單,器件封裝形式小,通信速率較高等優點。
- I2C特性
(1)只要求兩條總線線路一條串行數據線SDA,一條串行時鐘線SCL;
(2)每個連接到總線的器件都可以通過唯一的地址和一直存在的簡單的主機/從機關系軟件設定地址,主機可以作為主機發送器或主機接收器;
(3)它是一個真正的多主機總線,如果兩個或更多主機同時初始化數據傳輸可以通過沖突檢測和仲裁防止數據被破壞;
(4)串行的 8 位雙向數據傳輸位速率在標準模式下可達100kbit/s,快速模式下可達400kbit/s,高速模式下可達 3.4Mbit/s;
(5)片上的濾波器可以濾去總線數據線上的毛刺波保證數據完整;
(6)連接到相同總線的IC數量只受到總線的最大電容400pF;
IIC是屬串行通訊總線,同步傳輸、半雙工。
?I2C 總線上的每一個設備都可以作為主設備或者從設備,而且每一個設備都會對應一個唯一的地址(可以從I2C 器件的數據手冊得知),主從設備之間就通過這個地址來確定與哪個器件進行通信,在通常的應用中,我們把 CPU 帶 I2C 總線接口的模塊作為主設備,把掛接在總線上的其他設備都作為從設備。
IIC時序介紹參考:STM32CubeMx之硬件IIC驅動EEPROM
2.Linux下IIC驅動
Linux下編寫IIC驅動有兩種方式:
1. 將IIC驅動當做普通字符類設備來實現驅動注冊;
2. 利用Linux下IIC驅動框架(IIC子系統)完成注冊;
第一種方式操作簡單,只需要會基本的字符設備驅動框架即可完成對IIC驅動注冊;但可移植性較差;第二種方式則操作比較復雜,需要掌握IIC子系統框架注冊流程,但可移植性強。
2.1 IIC子系統驅動模型
??從上圖可以看出,IIC框架注意分為三層:適配器層、設備層和驅動層。三層之間關系可以簡化如下關系:
?適配器層主要實現IIC硬件接口配置、產生IIC實現,該代碼一般由芯片廠商提供。
設備層主要是通過調用獲取適配器的函數獲取IIC硬件資源(得到IIC時序的相關接口函數),根據IIC模塊手冊和硬件原理圖確定模塊的設備地址,完成IIC設備層注冊。
驅動層通過設備名字完成和設備層匹配,獲取設備資源,注冊設備驅動,實現應用層相關接口函數。
2.2 IIC子系統相關接口函數
2.2.1 設備層接口函數
IIC設備層注冊步驟:
- 調用獲取適配器函數i2c_get_adapter根據IIC總線編號獲取IIC適配器資源;
- 填充struct i2c_board_info 結構體,調用i2c_new_device注冊IIC設備;
- 注銷設備時調用IIC設備注銷函數i2c_unregister_device;
- 獲取適配器資源i2c_get_adapter
struct i2c_adapter *i2c_get_adapter(int nr)
函數功能: 獲取適配器資源
形參: nr --IIC總線編號
返回值: 成功返回適配器資源結構體指針
-
struct i2c_board_info結構體
??在struct i2c_board_info結構體中,需要關系的參數是 設備名字type 和 設備地址addr,若有中斷資源,則可填入 中斷號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;//中斷號
};
- 注冊設備i2c_new_device
struct i2c_client *i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
函數功能: 注冊IIC設備層
形參: adap–IIC適配器資源
??? info --IIC設備板級資源信息
返回值: 注冊成功返回IIC信息結構體指針
- 注銷設備 i2c_unregister_device
void i2c_unregister_device(struct i2c_client *client)
函數功能: 注銷IIC設備層
形參: client–IIC信息結構體指針,IIC注冊函數返回值;
2.2.2 驅動層接口函數
IIC驅動層注冊步驟:
- 填充驅動層結構體struct i2c_driver ,調用驅動注冊函數i2c_add_driver ;
- 注銷驅動時調用注銷函數i2c_del_driver ;
- 驅動層結構體struct i2c_driver
- 在struct i2c_driver結構體中我們必須實現的有資源匹配函數probe、資源釋放函數remove、結構體struct device_driver中的設備名name、資源匹配結構體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;//資源匹配結構體
};
- 驅動注冊和注銷函數
//驅動注冊函數
#define i2c_add_driver(driver)
//驅動注銷函數
void i2c_del_driver(struct i2c_driver *);
2.2.3 IIC讀寫字符串函數
//讀取多個數據函數,成功返回讀取到的字節數,注意,改函數最大多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.電容屏ft5x06驅動注冊
FT5x06系列集成電路單片機電容式觸控面板控制器集成電路與一個內置的8位微控制器單元(MCU)他們采用互電容的方法,支持多點觸控功能。結合共同電容觸摸面板,FT5x06友好的輸入功能,可應用于許多便攜式設備,如手機,MIDs,上網本和筆記本個人電腦。
- FT5X06內核架構
?? FT5X06支持I2C、SPI、UART三種接口。本次我們采用IIC協議完成驅動注冊。
?? FT5X06的I2C接口時序:
3.1 FT5X06驅動注冊
硬件平臺:tiny4412
開發平臺:ubuntu18.04
交叉編譯器:arm-linux-gcc
內核:linux3.5
3.1.1 根據原理圖要到IIC硬件資源信息
??要實現IIC子系統的設備和驅動層注冊,必要參數為:IIC總線編號、設備名字、設備地址。
??根據Tiny4412的開發板原理圖,可知觸摸屏接口采用的是I2C1總線,中斷檢測腳為GPX1_6。
??由于本身tiny4412的內核中已自帶觸摸屏驅動,所以FT5X06的設備地址可根據mach-tiny4412.c的板級注冊中找到:
3.1.2 裁剪內核,卸載自帶的觸摸屏驅動
由于本身tiny4412的內核中已自帶觸摸屏驅動,所以若想自己編寫該驅動則需要先卸載內核中的觸摸屏驅動,才能實現自己的驅動文件注冊。即需要完成內核裁剪,卸載自帶的觸摸屏驅動。
打開linux3.5內核的配置界面,找到ft5x06的驅動位置,將其卸載。
[wbyq@wbyq linux-3.5]$ make menuconfig
Device Drivers --->
Input device support --->
Touchscreens --->
FocalTech ft5x0x TouchScreen driver
??最后重新編譯內核,燒寫內核,重新啟動開發板。
3.1.3 注冊IIC設備層
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
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 注冊IIC驅動層
??注冊IIC驅動層,完成FT5X06中斷注冊。初始化工作,初始化等待隊列頭,注冊雜項設備。實現ioctl函數接口和poll函數接口。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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,%dn",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=%dn",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 編寫應用層函數
??通過幀緩沖框架完成LCD應用編程,實現畫點函數。打開觸摸屏驅動,獲取觸摸屏坐標,調用畫點函數,繪制實時坐標位置。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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("屏幕緩存大小:%dn",fb_fix.smem_len);
printf("一行的字節數:%dn",fb_fix.line_length);
/*3.獲取屏幕可變參數*/
memset(&fb_var,0, sizeof(fb_var));
ioctl(fd,FBIOGET_VSCREENINFO,&fb_var);
printf("屏幕尺寸:%d*%dn",fb_var.xres,fb_var.yres);
printf("顏色位數:%dn",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;
}
}
}
-
觸摸屏
+關注
關注
42文章
2306瀏覽量
116183 -
Linux
+關注
關注
87文章
11304瀏覽量
209483 -
IIC
+關注
關注
11文章
300瀏覽量
38333
發布評論請先 登錄
相關推薦
評論