觸摸屏設備驅動程序
由於觸摸屏設備簡單、價格低廉,到處應用
在消費電子商品、工業控制系統、甚至航空領域都有應用
觸摸屏作爲一種最新的電腦輸入設備,是目前最簡單、方便、自然的的一種人機交互方式,具有堅固耐用、反應速度快、節省空間、易於交流等許多優點。
事實上,觸摸屏是一個使多媒體信息系統改頭換面的設備,它賦予多媒體系統以嶄新的面貌,是極富有吸引力的全新多媒體交互設備
從技術原理來區別觸摸屏,可分爲5類:
1,矢量壓力傳感技術觸摸屏
2,電阻式觸摸屏
3,電容式觸摸屏
4,紅外線技術觸摸屏
5,表面聲波技術觸摸屏
矢量壓力傳感技術觸摸屏已經退出歷史舞臺
紅外線技術觸摸屏價格低廉,但其外框易碎,容易產生光干擾,曲面情況下失真
電容技術觸摸屏設計構思合理,但其圖像失真問題很難得到解決
電阻技術觸摸屏的定位準確,但其價格頗高,且怕刮易損
表面聲波觸摸屏解決了以往觸摸屏的各種缺陷,清晰且不容易被破壞,適用於各種場合,缺點是:屏幕表面如果有水滴和塵土會使觸摸屏變得遲鈍
電阻式觸摸屏的屏體部分是一塊與顯示器表面相匹配的多層複合薄膜,由一層玻璃或有機玻璃作爲底層,表面塗有一層透明的導電層,上面再蓋有一層外表面硬化處理、光滑防刮的塑料層,它的內表面也塗有一層透明導電層,在兩層導電層之間有很多細小(小於千分之一英寸)的透明隔離點把它們隔開絕緣。
四線觸摸屏包含兩個阻性層。其中一層在屏幕的左右邊緣各有一條垂直總線,另一層在屏幕的底部和頂部各有一條水平總線,見圖4。爲了在X軸方向進行測量,將左側總線偏置爲0V,右側總線偏置爲VREF。將頂部或底部總線連接到ADC,當頂層和底層相接觸時即可作一次測量。 爲了在Y軸方向進行測量,將頂部總線偏置爲VREF,底部總線偏置爲0V。將ADC輸入端接左 側總線或右側總線,當頂層與底層相接觸時即可對電壓進行測量。圖5顯示了四線觸摸屏在兩層相接觸時的簡化模型。對於四線觸摸屏,最理想的連接方法是將偏置 爲VREF的總線接ADC的正參考輸入端,並將設置爲0V的總線接ADC的負參考輸入端。
s3c2440芯片支持觸摸屏接口。這個觸摸屏接口包括一個外部晶體管控制邏輯和一個模數轉換器ADC,s3c2440芯片具有一個8通道的10位CMOS模數轉換器(ADC)。2.5MHz下A/D轉換器可達500KSPS,A/D支持片上採樣和保持功能,並支持掉電模式
s3c2440觸摸屏特點:
1,分辨率,10位
2,微分線性誤差:1.0LSB
3,積分線性誤差:2.0LSB
4,最大轉換速率:500KSPS
5,低功耗
6,供電電壓:3.3V
7,輸入模擬電壓範圍:0~3.3V
8,片上採樣保持功能
9,普通轉換模式
10,分離X/Y軸座標轉換模式
11,自動(連續)X/Y軸座標轉換模式
12,等待中斷模式
S3C2440觸摸屏4種工作模式:
1,正常轉換模式
在不使用觸摸屏設備時,可以單獨使用觸摸屏接口中共用的模數轉換器ADC。這這種模式下,可以通過設置ADCCON寄存器啓動普通的A/D轉韓,當轉換結束時,結果被寫到ADCCDAT0寄存器中
2,等待中斷模式
當設置觸摸屏接口控制器的ADCTSC寄存器爲0XD3時,觸摸屏就處於等待中斷模式。這時觸摸屏等待觸摸信號的到來。當觸摸信號到來時,觸摸屏接口控制器將通過INT_TC線產生中斷信號,表示有觸摸動作發生。當中斷髮生,觸摸屏可以轉換爲其他兩種狀態來讀取觸摸點的位置(x,y)。這兩種模式是獨立的X/Y位置轉換模式和自動X/Y位置轉換模式
3,獨立X/Y位置轉換模式
獨立的X/Y位置轉換模式由兩個子模式組成,分別是X位置模式和Y位置模式。X位置模式將轉換後的X座標寫到ADCDAT0寄存器的XPDATA位。轉換後,觸摸屏接口控制器會通過INT_ADC中斷線產生中斷信號,由中斷處理函數來處理。Y位置模式將轉換後的Y座標寫到ADCDAT1寄存器的YPDATA位。同樣轉換後,觸摸屏接口控制器會通過INT_ADC中斷線產生中斷信號,由中斷處理函數來處理
4,自動X/Y位置轉化模式
這種模式觸摸屏控制器自動轉換X位置和Y位置。當位置轉換後,模式觸摸屏接口控制器自動寫轉換後的X座標寫到ADCDAT0寄存器的XPDATA位;寫轉換後的Y座標到ADCDAT1寄存器的YPDATA位。當轉換完後,觸摸屏接口控制器會通過INT_AC中斷行產生中斷信號
寄存器:
ADCCON:模數轉換控制寄存器,用於控制AD轉換、是否使用分頻、設置分頻係數、讀取AD轉換器的狀態
ADCDLY:ADC延時寄存器,用於正常模式下和等待中斷模式下的延時操作
ADCDAT0:ADC轉換數據寄存器0,用於存儲觸摸屏的點擊狀態、工作模式、X座標等
ADCDAT1:ADC轉換數據寄存器1,用於存儲觸摸屏的點擊狀態、工作模式、Y座標等
注意:ADCDAT0和ADCDAT1的第15位,表示X和Y方向上檢測到的觸摸屏是否被按下。只有當ADCDAT0和ADCDAT1寄存器的第15位,即兩個寄存器的15位都等於0時,才表示觸摸屏被按下,或者有觸筆點擊觸摸屏。
ADCTSC:ADC觸摸屏控制寄存器,用於控制觸摸屏的YMON,nYPON,nXPON和XMON等狀態
觸摸屏設備驅動程序
struct s3c2410ts {
struct input_dev dev;
long xp;
long yp;
int count;
int shift;
char phys[32];
};
觸摸屏設備驅動程序的初始化函數、退出函數、和中斷處理函數關係如圖:
1,當模塊加載時,會調用初始化函數s3c2410ts_init()。在該函數中會調用probe()函數,該函數中會進一步調用request_irq()函數註冊兩個中斷。這兩個中斷的處理函數分別是stylus_updown()和stylus_action()。request_irq()函數會操作內核中的一箇中斷描述符數組結構irq_dest。該數組結構比較複雜,主要功能就是記錄中斷號對應的中斷處理函數
2,當中斷到來時,會到中斷描述符數組中詢問中斷號對應的處理函數,然後執行該函數。在本例中,這兩個中斷的處理函數分別是stylus_updown()和stylus_action()。
3,卸載模塊時,會調用退出函數s3c2410ts_exit()。在該函數中,會調用free_irq()來釋放設備所使用的中斷號。free_irq()函數也會操作中斷描述符數組結構irq_desc,將該設備對應的中斷處理函數刪除
4,中斷處理函數stylus_updown()中會調用touch_timer_fire()。這個函數也被定時器觸發,觸發的條件是,緩衝區中的數據不爲0,也就是有觸摸時間產生
加載和卸載函數:
int __init s3c2410ts_init(void) //加載函數
{
return driver_register(&s3c2410ts_driver); //註冊一個觸摸屏設備驅動程序,之後內核會以s3c2410ts_driver中的name成員爲依據,在系統查找已經註冊的相同name的設備。如果 找到相應的設備,就調用s3c2410ts_driver中定義的探測函數probe()。driver_register()函數是Linux設備驅動 模型中最常用的函數
}
void __exit s3c2410ts_exit(void) //卸載函數
{
driver_unregister(&s3c2410ts_driver); //卸載觸摸屏設備驅動程序
}
下面來看看導火線:s3c2410ts_driver:
static struct device_driver s3c2410ts_driver = {
.name = "s3c2410-ts", //名字
.bus = &platform_bus_type, //總線
.probe = s3c2410ts_probe, //探測函數,該函數在模塊加載函數完成且設備匹配成功後執行
.remove = s3c2410ts_remove, //移除函數
};
probe()對應的s3c2410ts_probe()。這個函數在觸摸屏設備的初始化過程中,檢查設備是否準備就緒、映射物理地址到虛擬地址、配置GPIO引腳、註冊相應的中斷和註冊輸入設備等,大多數Linux設備驅動程序中,當執行完模塊加載後,就執行probe():
static int __init s3c2410ts_probe(struct device *dev)
{
struct s3c2410_ts_mach_info *info; //定義了觸摸屏接口相關硬件的配置信息結構體指針
info = ( struct s3c2410_ts_mach_info *)dev->platform_data; //從device的平臺數據dev->platform_data中獲得指向struct s3c2410_ts_match_info結構體的指針
if (!info) //說明沒有平臺數據
{
printk(KERN_ERR "Hm... too bad : no platform data for ts\n");
return -EINVAL;
}
#ifdef CONFIG_TOUCHSCREEN_S3C2410_DEBUG //如果內核配置了調試信息選項,則打印調試信息
printk(DEBUG_LVL "Entering s3c2410ts_init\n");
#endif
adc_clock = clk_get(NULL, "adc"); //獲得ADC的時鐘源,並賦值給adc_clock指針
if (!adc_clock) { //如果沒有正確的獲得ADC時鐘源,錯誤退出
printk(KERN_ERR "failed to get adc clock source\n");
return -ENOENT;
}
clk_use(adc_clock); //增加時鐘源的使用計數
clk_enable(adc_clock); //啓動ADC時鐘源
#ifdef CONFIG_TOUCHSCREEN_S3C2410_DEBUG //配置調試信息選項的話打印調試信息
printk(DEBUG_LVL "got and enabled clock\n");
#endif
base_addr=ioremap(S3C2410_PA_ADC,0x20); //把一個ADC控制寄存器的物理地址內存映射到一個虛擬地址。這段被映射的長度是0x20。
#define S3C2410_PA_ADC (0x58000000) //這是觸摸屏一組寄存器的首地址
if (base_addr == NULL) { //映射失敗退出
printk(KERN_ERR "Failed to remap register block\n");
return -ENOMEM;
}
/* Configure GPIOs */
s3c2410_ts_connect(); //配置處理器的GPIO。這個函數配置端口G的12,13,14,15引腳爲XMON,nXPON,YMON,nPON。
/* Set the prscvl*/
if ((info->presc&0xff) > 0) //設置AD轉換的分頻係數。如果info->presc的低8位的值大於0,那麼配置ADCCON寄存器
位14:是否使用AD轉換器的預分頻功能,位6-13位共8位表示AD轉換的預分頻器的數值
writel(S3C2410_ADCCON_PRSCEN | S3C2410_ADCCON_PRSCVL(info->presc&0xFF),\
base_addr+S3C2410_ADCCON);
else
writel(0,base_addr+S3C2410_ADCCON); //不使用預分頻功能,將ADCCON寄存器各位都設置爲0
/* Initialise the adcdly registers */
if ((info->delay&0xffff) > 0) //如果需要延時。寫延時時間到ADCDLY寄存器
writel(info->delay & 0xffff, base_addr+S3C2410_ADCDLY);
writel(WAIT4INT(0), base_addr+S3C2410_ADCTSC); //寫ADC觸摸屏設備的ADCTSC寄存器,使觸摸屏設備 處於等待中斷模式
/* Initialise input stuff */
memset(&ts, 0, sizeof(struct s3c2410ts)); //清零全局變量
init_input_dev(&ts.dev); //申請並初始化一個輸入設備。通過這個輸入設備,驅動程序才能和用戶交互
ts.dev.evbit[0] = ts.dev.evbit[0] = BIT(EV_SYN) | BIT(EV_KEY) | BIT(EV_ABS); //下面是初始化ts變量
ts.dev.keybit[LONG(BTN_TOUCH)] = BIT(BTN_TOUCH);
input_set_abs_params(&ts.dev, ABS_X, 0, 0x3FF, 0, 0);
input_set_abs_params(&ts.dev, ABS_Y, 0, 0x3FF, 0, 0);
input_set_abs_params(&ts.dev, ABS_PRESSURE, 0, 1, 0, 0);
sprintf(ts.phys, "ts0");
ts.dev.private = &ts;
ts.dev.name = s3c2410ts_name;
ts.dev.phys = ts.phys;
ts.dev.id.bustype = BUS_RS232;
ts.dev.id.vendor = 0xDEAD;
ts.dev.id.product = 0xBEEF;
ts.dev.id.version = S3C2410TSVERSION;
ts.shift = info->oversampling_shift;
/* Get irqs */
if (request_irq(IRQ_ADC, stylus_action, SA_SAMPLE_RANDOM,
"s3c2410_action", &ts.dev)) { //申請ADC中斷,並設置中斷處理函數爲stylus_action
printk(KERN_ERR "s3c2410_ts.c: Could not allocate ts IRQ_ADC !\n");
iounmap(base_addr);
return -EIO;
}
if (request_irq(IRQ_TC, stylus_updown, SA_SAMPLE_RANDOM,
"s3c2410_action", &ts.dev)) { //申請觸摸屏中斷,並設置中斷處理函數爲stylus_updown
printk(KERN_ERR "s3c2410_ts.c: Could not allocate ts IRQ_TC !\n");
iounmap(base_addr);
return -EIO;
}
printk(KERN_INFO "%s successfully loaded\n", s3c2410ts_name);
/* All went ok, so register to the input system */
input_register_device(&ts.dev); //將觸摸屏設備註冊到輸入子系統中
return 0;
}
s3c2410_ts_mach_info是s3c2410觸摸屏接口相關硬件的配置信息
struct s3c2410_ts_mach_info
{
int delay; //延時時間
int presc; //預分頻值
int oversampling_shift; //輸入數據的緩衝區大小
};
下面介紹觸摸屏設備配置,具體的硬件知識,請參考相關資料:
s3c2410_ts_connect()用來處理GPG端口12,13,14,15引腳,設置爲與觸摸屏相關的狀態。這裏分別設爲XMON,nXPON,YMON,和nYOPN狀態:
static inline void s3c2410_ts_connect(void)
{
s3c2410_gpio_cfgpin(S3C2410_GPG12, S3C2410_GPG12_XMON); //GPG的端口12引腳設置爲讀寫觸摸屏設備XM引腳的值,EINT【20】連接到觸摸屏XM引腳上
s3c2410_gpio_cfgpin(S3C2410_GPG13, S3C2410_GPG13_nXPON); //GPG的端口13引腳設置爲讀寫觸摸屏設備nXPON引腳的值,EINT[21]連接到觸摸屏的nXPON上
s3c2410_gpio_cfgpin(S3C2410_GPG14, S3C2410_GPG14_YMON); //GPG的端口13引腳設置爲讀寫觸摸屏設備YMON引腳的值,EINT[22]連接到觸摸屏的YMON上
s3c2410_gpio_cfgpin(S3C2410_GPG15, S3C2410_GPG15_nYPON); //GPG的端口14引腳設置爲讀寫觸摸屏設備nYPON引腳的值,EINT[23]連接到觸摸屏的nYPON上
}
觸摸屏設備中斷處理函數
當觸摸屏設備驅動的探測函數執行完之後,驅動程序處於等待狀態。在等待狀態中,驅動程序可以接受兩個中斷信號,並觸發中斷處理函數。這兩個中斷是觸摸屏中斷(IRQ_TC)和ADC中斷(IRQ_ADC)。
stylus_updown():
當觸摸屏被按下時,會產生觸摸中斷信號IRQ_TC,該函數會激發stylus_updown()函數的調用:
static irqreturn_t stylus_updown(int irq, void *dev_id, struct pt_regs *regs)
{
unsigned long data0;
unsigned long data1;
int updown; //用來表示觸摸屏是否被按下。按下是1,否則是0
/********************************debug************************************/
printk(KERN_INFO "You touch the screen\n");
/*************************************************************************/
data0 = readl(base_addr+S3C2410_ADCDAT0); //讀取ADCDATA0寄存器的值
data1 = readl(base_addr+S3C2410_ADCDAT1); //讀取ADCDATA1寄存器的值
updown = (!(data0 & S3C2410_ADCDAT0_UPDOWN)) && (!(data1 & S3C2410_ADCDAT1_UPDOWN)); //判斷觸摸筆是否被按下
/* TODO we should never get an interrupt with updown set while
* the timer is running, but maybe we ought to verify that the
* timer isn't running anyways. */
if (updown) //如果被按下,執行touch_timer_fire()
touch_timer_fire(0);
return IRQ_HANDLED;
}
touch_timer_fire()函數:繼續處理觸摸中斷:
static void touch_timer_fire(unsigned long data)
{
unsigned long data0;
unsigned long data1;
int updown;
data0 = readl(base_addr+S3C2410_ADCDAT0);
data1 = readl(base_addr+S3C2410_ADCDAT1);
updown = (!(data0 & S3C2410_ADCDAT0_UPDOWN)) && (!(data1 & S3C2410_ADCDAT1_UPDOWN)); //上面的和stylus_updown()函數中前面的一樣
if (updown) {
if (ts.count != 0) { //用來向輸入子系統報告當前觸摸筆的位置
ts.xp >>= ts.shift;
ts.yp >>= ts.shift;
#ifdef CONFIG_TOUCHSCREEN_S3C2410_DEBUG
{
struct timeval tv;
do_gettimeofday(&tv);
printk(DEBUG_LVL "T: %06d, X: %03ld, Y: %03ld\n", (int)tv.tv_usec, ts.xp, ts.yp);
printk(KERN_INFO "T: %06d, X: %03ld, Y: %03ld\n", (int)tv.tv_usec, ts.xp, ts.yp);
}
#endif
input_report_abs(&ts.dev, ABS_X, ts.xp);
input_report_abs(&ts.dev, ABS_Y, ts.yp);
input_report_key(&ts.dev, BTN_TOUCH, 1);
input_report_abs(&ts.dev, ABS_PRESSURE, 1);
input_sync(&ts.dev);
}
ts.xp = 0; //將x,y座標和count設置爲0,表示緩衝區中沒有數據,也就是沒有觸屏按下的事件發生
ts.yp = 0;
ts.count = 0;
writel(S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST, base_addr+S3C2410_ADCTSC); //將AD轉換模式設置爲自動轉模式
// #define S3C2410_ADCTSC_PULL_UP_DISABLE (1 << 3) 表示將XP上拉,另外
// #define AUTOPST (S3C2410_ADCTSC_YM_SEN | S3C2410_ADCTSC_YP_SEN | S3C2410_ADCTSC_XP_SEN
// | S3C2410_ADCTSC_AUTO_PST | S3C2410_ADCTSC_XY_PST(0)
ADCOPST宏主要任務是將AD轉換器設置爲自動XY座標轉換模式。S3C2410_ADCTSC_YM_SEN表示將YMON輸出爲1;S3C2410_ADCTSC_YP_SEN表示將nYPON輸出爲1;S3C2410_ADCTSC_XP_SEN表示將nXPOM輸出爲1;S3C2410_ADCTSC_ATUO_PST表示將ADCTSC寄存器的第二位設置爲1,即將AD轉換器設置爲自動XY座標轉換模式。S3C2410_ADCTSC_XY_PST(0)爲0,表示AD轉換器沒有任何操作
writel(readl(base_addr+S3C2410_ADCCON) | S3C2410_ADCCON_ENABLE_START, base_addr+S3C2410_ADCCON); //將ADCCON寄存器的第0位設置爲1.其中S3C2410_ADCCON_ENABLE_START宏表示第0位爲1.這句話的代碼功能是啓動AD轉換功能,當啓動後,ADCCON第0位自動設置爲0
} else { //表示觸摸屏沒有被按下的操作。此時調用input_report_key()函數向輸入子系統報告觸摸屏被彈起事件。input_report_key()函數的第三個參數傳遞0,表示按鍵被釋放。input_report_abs()函數發送觸摸屏的一個絕對座標。它們最終表現在文件系統中,應用程序可以訪問Linux的文件系統,從而得到觸摸屏的信息。事實上,不使用這兩個函數驅動程序仍然能夠控制觸摸屏設備,但是需要通過其他方法訪問觸摸屏設備的狀態
ts.count = 0;
input_report_key(&ts.dev, BTN_TOUCH, 0);
input_report_abs(&ts.dev, ABS_PRESSURE, 0);
input_sync(&ts.dev); //通知事件發送者發送一個完整的報告。這裏BTN_TOUCH和ABS_PRESSURE事件不能單獨發送,必須同時發送
writel(WAIT4INT(0), base_addr+S3C2410_ADCTSC); //將觸摸屏重新設置爲等待中斷模式,等待觸摸屏被按下。這些工作是通過設置觸摸屏控制寄存器ADCTSC來完成的。WAIT4INT是需要寫入的ADCTSC寄存器的值,定義:
// #define WAIT4INT(x) (((x) << 8) | S3C2410_ADCTSC_YM_SEN | S3C2410_ADCTSC_YP_SEN | S3C2410_ADCTSC_XP_SEN | S3C2410_ADCTSC_XY_PST(3))
}
}
stylus_action函數:
觸摸屏設備產生的另一箇中斷是ADC中斷(IRQ_ADC)。觸摸屏在自動X/Y位置轉換模式和獨立的X/Y位置轉換模式時,當座標數據轉換之後會產生IRQ_ADC中斷。中斷產生之後會調用stylus_action()函數:
static irqreturn_t stylus_action(int irq, void *dev_id, struct pt_regs *regs)
{
unsigned long data0;
unsigned long data1;
data0 = readl(base_addr+S3C2410_ADCDAT0);
data1 = readl(base_addr+S3C2410_ADCDAT1);
ts.xp += data0 & S3C2410_ADCDAT0_XPDATA_MASK; //獲得觸摸點的X座標,把它加到ts.xp上。ADCDAT0寄存器的[0:9]表示X座標。該座標在ADC中斷產生前存入ADCDAT0
ts.yp += data1 & S3C2410_ADCDAT1_YPDATA_MASK; //獲得觸摸點的Y座標,把它加到ts.yp上。ADCDAT0寄存器的[0:9]表示Y座標。該座標在ADC中斷產生前存入ADCDAT1
ts.count++; //計數器加1
if (ts.count < (1<<ts.shift)) { //如果緩衝區爲滿,再次激活ADC轉換器
writel(S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST, base_addr+S3C2410_ADCTSC);
writel(readl(base_addr+S3C2410_ADCCON) | S3C2410_ADCCON_ENABLE_START, base_addr+S3C2410_ADCCON);
} else {
mod_timer(&touch_timer, jiffies+1); //修改定時器,將其時間延後一個單位。在下一個定時器時刻將調用touch_timer定時器指定 的函數
writel(WAIT4INT(1), base_addr+S3C2410_ADCTSC); //將觸摸屏設置爲等待中斷模式
}
return IRQ_HANDLED;
}
touch_timer定時器:
touch_timer定時器用來當緩衝區不爲空時,不斷觸發touch_timer_fire()函數。touch_timer_fire()函數讀取觸摸屏的座標信息,並傳遞給內核子系統。
全局變量:
static struct timer_list touch_timer =
TIMER_INITIALIZER(touch_timer_fire, 0, 0);
s3c2440觸摸屏驅動模塊的remove()函數:
remove()函數是Linux設備驅動程序中一個非常重要的函數,這個函數實現了與probe()函數相反的功能,體現了Linux內核中,資源分配和釋放的思想 。資源應該在使用時分配,在不使用時釋放。這個函數釋放了申請的中斷、時鐘、內存。
static int s3c2410ts_remove(struct device *dev)
{
disable_irq(IRQ_ADC); //關閉中斷,並釋放中斷 #define IRQ_ADC S3C2410_IRQ(70)
disable_irq(IRQ_TC); //#define IRQ_TC (0x0)
free_irq(IRQ_TC,&ts.dev);
free_irq(IRQ_ADC,&ts.dev);
if (adc_clock) { //關閉並釋放ADC時鐘源
clk_disable(adc_clock);
clk_unuse(adc_clock);
clk_put(adc_clock);
adc_clock = NULL;
}
input_unregister_device(&ts.dev); //註銷觸摸屏輸入設備
iounmap(base_addr); //釋放映射的虛擬內存地址
return 0;
}