-
開發(fā)環(huán)境:
- 開發(fā)板:JZ2440V3
- CPU:samsunS3C2440
- 內(nèi)核:Linux3.4.2
- 編譯工具:arm-linux-gcc 4.3.2
- LCD:4.3存液晶屏AT043TN24
- 參考文獻:
觸摸屏使用過程:
- 觸摸屏某點被按下,產(chǎn)生INT_TC中斷;
- 在中斷處理程序中,打開定時器
- 定時器時間到,啟動ADC轉換,得到x和y坐標;
- ADC結束,產(chǎn)生ADC中斷;
- 、在ADC中斷處理函數(shù)里,上報(input_event),啟
- 抬起,松開屏幕
1、編寫基本框架(s3c_ts.c)
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/input.h>
#include <linux/init.h>
#include <linux/serio.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <asm/io.h>
#include <asm/irq.h>
static struct input_dev *s3c_ts_dev;
static int s3c_ts_init(void)
{
/* 1. 分配一個input_dev結構體 */
s3c_ts_dev = input_allocate_device();
/* 2. 設置 */
/* 2.1 能產(chǎn)生哪類事件 */
set_bit(EV_KEY, s3c_ts_dev->evbit);
set_bit(EV_ABS, s3c_ts_dev->evbit);
/* 2.2 能產(chǎn)生這類事件里的哪些事件 */
set_bit(BTN_TOUCH, s3c_ts_dev->keybit);
input_set_abs_params(s3c_ts_dev, ABS_X, 0, 0x3FF, 0, 0);
input_set_abs_params(s3c_ts_dev, ABS_Y, 0, 0x3FF, 0, 0);
input_set_abs_params(s3c_ts_dev, ABS_PRESSURE, 0, 1, 0, 0);
/* 3. 注冊 */
input_register_device(s3c_ts_dev);
/* 4. 硬件相關的操作 */
/* 4.1 使能時鐘(CLKCON[15]) */
/* 4.2 設置S3C2440的ADC/TS寄存器 */
return 0;
}
static void s3c_ts_exit(void)
{
input_unregister_device(s3c_ts_dev);
input_free_device(s3c_ts_dev);
}
module_init(s3c_ts_init);
module_exit(s3c_ts_exit);
MODULE_LICENSE("GPL");
2、分析內(nèi)核自帶觸摸屏驅(qū)動
先打開JZ2440開發(fā)板原理圖,查看觸摸屏的四根引腳(TSYM、TSYP、TSXM、TSXP)分別接在ADC的——AIN4、5、6、7上面。

通過S3C2440手冊搜索發(fā)現(xiàn),這些引腳不需要配置什么寄存器就可以使用。
看一下內(nèi)核自帶的觸摸屏驅(qū)動(drivers/input/touchscreen/s3c2410_ts.c),找到probe函數(shù),看做了什么事情,
ts.clock = clk_get(dev, "adc");
if (IS_ERR(ts.clock)) {
dev_err(dev, "cannot get adc clock source\n");
return -ENOENT;
}
clk_enable(ts.clock);
這里有個使能時鐘的函數(shù):
Tips:在內(nèi)核啟動的時候,為了省電,會把一些不相關的模塊給關掉。
怎么關?就是通過設置CLKCON寄存器或者是(Clock Gating Control Register),我們在要用任何模塊之前必須把對應的位置1(打開模塊時鐘)。
內(nèi)核中,就是通過clk_get 和clk_enable使能模塊時鐘的。
然后,再看芯片手冊上的ADC和觸摸屏接口那一章:
The 10-bit CMOS ADC (Analog to Digital Converter) is a recycling type device with 8-channel analog inputs. It converts the analog input signal into 10-bit binary digital codes at a maximum conversion rate of 500KSPS with 2.5MHz A/D converter clock. A/D converter operates with on-chip sample-and-hold function and power down mode is supported.
Touch Screen Interface can control/select pads (XP, XM, YP, YM) of the Touch Screen for X, Y position conversion. Touch Screen Interface contains Touch Screen Pads control logic and ADC interface logic with an interrupt generation logic.
這里說的就是這里面有個10位的mos ADC轉換器,有8路信道。在ADC工作頻率為2.5MHz時,最大轉換頻率是500KSPS,同時,因為Power Supply Voltage: 3.3V (最大輸入電壓是3.3v ),所以如果ADC的輸入電壓是3.3V的話,輸出就是10個1(0x3ff),如果是0V的話,輸出就是0。每個刻度就是3.3v/10位 10位的話就是1024 就是3.3V/1024 最小刻度是3mv
然后是我們的ADC轉換時間

如果PCLK是50MHZ,而ADC最大工作頻率是2.5MZ,所以要設置分頻系數(shù),把這個頻率給降低下來
然后下面就是它提供的例子 the prescaler value is 49, ADC的工作頻率就是A/D converter freq. = 50MHz/(49+1) = 1MHz .
轉換時間就需要Conversion time = 1/(1MHz / 5cycles) = 1/200kHz = 5us (1MHZ的5個周期 就是5us )
- 再看下接口模式:
正常的轉換模式(Normal Conversion Mode):正常轉換模式就是一般的ADC操作,比如說你想測量某個電壓。
分離的xy坐標轉換模式(Separate X/Y Position Conversion Mode) :這種模式分為兩個部分。一種是測量X坐標,一種是測量Y坐標。
進入X或Y坐標模式需要采取的措施是:
1.設置0x69到TSCONn寄存器
2.通過設置TSADCCONn開始轉換
3.X坐標轉換結束后能被中斷給通知
4.從TSDATXn讀出坐標轉換數(shù)據(jù)
自動(連續(xù))的XY轉換模式(Auto (Sequential) X/Y Position Conversion Mode):當你進入這個模式之后,它會自動的幫你即轉換x坐標也轉換Y坐標
-
等待中斷模式,就是等待按下產(chǎn)生中斷模式:若想在我們按下觸摸屏后讓它產(chǎn)生中斷,就要進入該模式。當觸摸筆按下的時候,觸摸屏會產(chǎn)生INT_PENn這個中斷。
- 怎么進入這個模式呢?設置rADCTSC=0xd3 就可以了。
3、硬件相關代碼編寫
3.1 使能時鐘
//函數(shù)體外定義全局變量clk
struct clk* clk;
/* 4.1 使能時鐘(CLKCON[15]) */
clk = clk_get(NULL, "adc");
clk_enable(clk);
3.2 設置S3C2440的ADC控制寄存器
//函數(shù)體外定義寄存器結構體
struct s3c_ts_regs {
unsigned long adccon;
unsigned long adctsc;
unsigned long adcdly;
unsigned long adcdat0;
unsigned long adcdat1;
unsigned long adcupdn;
};
//僅在本文件內(nèi)使用的靜態(tài)寄存器指針變量
static volatile struct s3c_ts_regs *s3c_ts_regs;
//在初始化函數(shù)內(nèi)對寄存器結構體進行地址映射
s3c_ts_regs = ioremap(0x58000000, sizeof(struct s3c_ts_regs));
3.3 設置ADC控制寄存器
- ADCCON

PRESCEN(bit[14]) : =1 A/D converter prescaler enable
PRSCVL(bit[13:6]): =49 A/D converter prescaler value,最大值為2.5MHz,在此我們?nèi)?MHz,所以ADCCLK=PCLK/(49+1)=50MHz/(49+1)=1MHz
SEL_MUX(bit[5:3]):模擬信號輸入信道選擇,若為普通的AD轉換,則可在此選擇AIN0-3,我們先不設
STDBM(bit[2]):省電模式選擇位,默認為0,我們用的是非省電模式,所以在此可不用設置
READ_START(bit[1]):通過讀操作自動啟動AD轉換,我們在此不選該功能
ENABLE_START(bit[0]): 通過置1手動開啟AD轉換(轉換完成后自動清零),先設為0
s3c_ts_regs->adccon = (1<<14)|(49<<6);
3.4 編寫觸摸中斷處理函數(shù)(pen_down_up_irq)并注冊中斷(INT_TC)
當觸摸屏被按下時,AD轉換進入等待中斷模式(通過設置ADCTSC=0xd3)


當觸摸屏沒有被按下時,由于上拉電阻的原因,Y_ADC處于高電平狀態(tài);當被按下時,Y_ADC的電壓由于聯(lián)通到y(tǒng)軸接地而變?yōu)榈碗娖?,此低電平就是中斷的觸發(fā)信號,使之產(chǎn)生pen down事件。

Tips:
左邊的圖為讀取x坐標時的等效電路圖:測X_ADC時,S1(XP_SEN Enable)、S2(YP_SEN Disable)、S3(XM_SEN Enable),S4(YM_SEN Disable)、S5(PULL_UP Disable);
測Y_ADC時,S1(XP_SEN Disable)、S2(YP_SEN Enable)、S3(XM_SEN Disable斷開,S4(YM_SEN Enable)、S5(PULL_UP Disable)接通;
//調(diào)用該函數(shù)進入等待觸摸屏按下模式
static void enter_wait_pen_down_mode(void)
{
s3c_ts_regs->adctsc = 0xd3;
}
//調(diào)用該函數(shù)進入等待觸摸屏松開模式
static void enter_wait_pen_up_mode(void)
{
s3c_ts_regs->adctsc = 0x1d3;
}
//觸摸屏中斷處理函數(shù)V1.0
static irqreturn_t pen_down_up_irq(int irq, void *dev_id)
{
if (s3c_ts_regs->adcdat0 & (1<<15)) //Stylus up 松開
{
printk("pen up\n");
enter_wait_pen_down_mode(); //送開的話,就進入等待下次按下的模式
}
else //Stylus down 按下
{
printk("pen down\n");
enter_wait_pen_up_mode(); //按下的話,就進入等待松開的模式
}
return IRQ_HANDLED;
}
在初始化函數(shù)中,注冊中斷處理函數(shù):
request_irq(IRQ_TC, pen_down_up_irq, IRQF_SAMPLE_RANDOM, "ts_pen", NULL);
3.5 編寫退出函數(shù)
static void s3c_ts_exit(void)
{
free_irq(IRQ_TC, NULL);
iounmap(s3c_ts_regs);
input_unregister_device(s3c_ts_dev);
input_free_device(s3c_ts_dev);
}
4、測試
4.1 編譯無觸摸屏驅(qū)動的內(nèi)核映像文件
//虛擬機(上位機)命令行下
cd linux3.4.2
make menuconfig //去掉原來的LCD驅(qū)動程序
make uImage
依次進入:
- Device Drivers——>
- Input device support——>
- Touchscreens——>
- < > S3C2410/S3C2440 touchscreens
- Touchscreens——>
- Input device support——>
4.2 使用新內(nèi)核啟動并加載自己的觸摸屏驅(qū)動
//Uboot命令行
tftp 30000000 uImage 或者 nfs 30000000 虛擬機IP:網(wǎng)絡文件系統(tǒng)目錄/uImage
bootm 30000000
//新內(nèi)核啟動后的命令行下
insmod s3c_ts.ko
按下/松開觸摸屏進行測試
//新內(nèi)核啟動后的命令行下
pen down
pen up
#
#
pen down
pen up
5、拓展——按下時啟動ADC,顯示坐標
修改觸摸屏中斷處理函數(shù),當觸摸屏被按下時,進入TC中斷處理函數(shù),自動測量模式并啟動ADC轉換;
AD轉換完成后產(chǎn)生ADC中斷,并在中斷處理函數(shù)中完成相應任務(例如顯示x、y點坐標)后,進入等待觸摸屏松開模式。
5.1 修改觸摸屏(TC)中斷處理函數(shù)
//觸摸屏中斷處理函數(shù)V2.0
static irqreturn_t pen_down_up_irq(int irq, void *dev_id)
{
if (s3c_ts_regs->adcdat0 & (1<<15)) //Stylus up 松開
{
printk("pen up\n"); //觸摸屏被松開
enter_wait_pen_down_mode(); //松開的話,就進入等待下次按下的模式
}
else //Stylus down 按下
{
//printk("pen down\n");
//enter_wait_pen_up_mode();
enter_measure_xy_mode(); //按下的話,就進入測量的模式
start_adc(); //啟動ADC轉換
enter_wait_pen_up_mode(); //進入等待觸摸屏被松開
}
return IRQ_HANDLED;
}
5.2 編輯進入自動測量模式函數(shù)
static void enter_measure_xy_mode(void)
{
s3c_ts_regs->adctsc = (1<<3)|(1<<2); //斷開上拉電阻,打開AUTO_PST
}
5.3 編輯啟動ADC函數(shù)
static void start_adc(void)
{
s3c_ts_regs->adccon |= (1<<0);
}
5.4 編輯ADC中斷處理函數(shù)

//在初始化文件中編寫ADC中斷處理函數(shù)
static irqreturn_t adc_irq(int irq, void *dev_id)
{
static int cnt = 0;
printk("adc_irq cnt = %d, x = %d, y = %d\n", ++cnt, adcdat0 & 0x3ff, adcdat1 & 0x3ff);
return IRQ_HANDLED;
}
5.5 注冊ADC中斷處理函數(shù)
//在初始化函數(shù)中對ADC中斷進行注冊
request_irq(IRQ_ADC, adc_irq, IRQF_SAMPLE_RANDOM, "adc", NULL);
5.6 修改退出函數(shù)
static void s3c_ts_exit(void)
{
free_irq(IRQ_TC, NULL);
free_irq(IRQ_ADC, NULL);
iounmap(s3c_ts_regs);
input_unregister_device(s3c_ts_dev);
input_free_device(s3c_ts_dev);
}
5.7 測試
修改完成后,重新編譯生成s3c_tc.ko驅(qū)動文件,下載安裝后測試:
rmmod s3c_tc //卸載舊驅(qū)動
insmod s3c_tc.ko //安裝新驅(qū)動
//碰觸觸摸屏
#
#
adc_irq cnt = 1, x = 330, y = 667
pen_up
adc_irq cnt = 2, x = 239, y = 739
pen_up
adc_irq cnt = 3, x = 215, y = 779
#
#
測試發(fā)現(xiàn):當我們滑動觸摸屏時,其輸出不能連續(xù)顯示。且輸出的坐標也不是太精確!
6、優(yōu)化——支持滑動操作、坐標更精確
6.1 使電壓穩(wěn)定后再進行轉換——設置ADC延時
//修改初始化函數(shù),添加ADCDLY延時
static int s3c_ts_init(void)
{
struct clk* clk;
/* 1. 分配一個input_dev結構體 */
s3c_ts_dev = input_allocate_device();
/* 2. 設置 */
/* 2.1 能產(chǎn)生哪類事件 */
set_bit(EV_KEY, s3c_ts_dev->evbit);
set_bit(EV_ABS, s3c_ts_dev->evbit);
/* 2.2 能產(chǎn)生這類事件里的哪些事件 */
set_bit(BTN_TOUCH, s3c_ts_dev->keybit);
input_set_abs_params(s3c_ts_dev, ABS_X, 0, 0x3FF, 0, 0);
input_set_abs_params(s3c_ts_dev, ABS_Y, 0, 0x3FF, 0, 0);
input_set_abs_params(s3c_ts_dev, ABS_PRESSURE, 0, 1, 0, 0);
/* 3. 注冊 */
input_register_device(s3c_ts_dev);
/* 4. 硬件相關的操作 */
/* 4.1 使能時鐘(CLKCON[15]) */
clk = clk_get(NULL, "adc");
clk_enable(clk);
/* 4.2 設置S3C2440的ADC/TS寄存器 */
s3c_ts_regs = ioremap(0x58000000, sizeof(struct s3c_ts_regs));
/* bit[14] : 1-A/D converter prescaler enable
* bit[13:6]: A/D converter prescaler value,
* 49, ADCCLK=PCLK/(49+1)=50MHz/(49+1)=1MHz
* bit[0]: A/D conversion starts by enable. 先設為0
*/
s3c_ts_regs->adccon = (1<<14)|(49<<6);
request_irq(IRQ_TC, pen_down_up_irq, IRQF_SAMPLE_RANDOM, "ts_pen", NULL);
request_irq(IRQ_ADC, adc_irq, IRQF_SAMPLE_RANDOM, "adc", NULL);
/* 優(yōu)化措施1:
* 設置ADCDLY為最大值, 這使得電壓穩(wěn)定后再發(fā)出IRQ_TC中斷
*/
s3c_ts_regs->adcdly = 0xffff;
enter_wait_pen_down_mode();
return 0;
}
6.2 多次ADC轉換,求平均值
從按下至松開的這段期間,進行多次AD轉換,并求其平均數(shù)。
static irqreturn_t adc_irq(int irq, void *dev_id)
{
static int cnt = 0;
static int x[4], y[4];
int adcdat0, adcdat1;
/* 優(yōu)化措施2: 如果ADC完成時, 發(fā)現(xiàn)觸摸筆已經(jīng)松開, 則丟棄此次結果 */
adcdat0 = s3c_ts_regs->adcdat0;
adcdat1 = s3c_ts_regs->adcdat1;
if (s3c_ts_regs->adcdat0 & (1<<15)) /* 已經(jīng)松開 */
{
cnt = 0;
enter_wait_pen_down_mode();
}
else
{
/* 優(yōu)化措施3: 多次測量求平均值 */
x[cnt] = adcdat0 & 0x3ff;
y[cnt] = adcdat1 & 0x3ff;
++cnt;
if (cnt == 4) //如果測量次數(shù)已經(jīng)到了4次,則打印出平均值,并等待觸摸屏被松開
{
printk("x = %d, y = %d\n", (x[0]+x[1]+x[2]+x[3])/4, (y[0]+y[1]+y[2]+y[3])/4);
cnt = 0;
enter_wait_pen_up_mode();
}
else //如果測量次數(shù)不夠4次,則再度啟動adc轉換
{
enter_measure_xy_mode();
start_adc();
}
}
return IRQ_HANDLED;
}
6.3 添加軟件過濾功能
6.3.1 編寫軟件過濾函數(shù)
把四次測量的值放入數(shù)組中,從頭至尾方向,依次使前兩項的平均值和第3項的值進行比較,若絕對值在允許誤差范圍內(nèi),則表示過濾成功,返回1,否則失敗,返回0
static int s3c_filter_ts(int x[], int y[])
{
#define ERR_LIMIT 10
int avr_x, avr_y;
int det_x, det_y;
avr_x = (x[0] + x[1])/2;
avr_y = (y[0] + y[1])/2;
det_x = (x[2] > avr_x) ? (x[2] - avr_x) : (avr_x - x[2]);
det_y = (y[2] > avr_y) ? (y[2] - avr_y) : (avr_y - y[2]);
if ((det_x > ERR_LIMIT) || (det_y > ERR_LIMIT))
return 0;
avr_x = (x[1] + x[2])/2;
avr_y = (y[1] + y[2])/2;
det_x = (x[3] > avr_x) ? (x[3] - avr_x) : (avr_x - x[3]);
det_y = (y[3] > avr_y) ? (y[3] - avr_y) : (avr_y - y[3]);
if ((det_x > ERR_LIMIT) || (det_y > ERR_LIMIT))
return 0;
return 1;
}
6.3.2 修改adc中斷處理函數(shù)
static irqreturn_t adc_irq(int irq, void *dev_id)
{
static int cnt = 0;
static int x[4], y[4];
int adcdat0, adcdat1;
/* 優(yōu)化措施2: 如果ADC完成時, 發(fā)現(xiàn)觸摸筆已經(jīng)松開, 則丟棄此次結果 */
adcdat0 = s3c_ts_regs->adcdat0;
adcdat1 = s3c_ts_regs->adcdat1;
if (s3c_ts_regs->adcdat0 & (1<<15))
{
/* 已經(jīng)松開 */
cnt = 0;
enter_wait_pen_down_mode();
}
else
{
/* 優(yōu)化措施3: 多次測量求平均值 */
x[cnt] = adcdat0 & 0x3ff;
y[cnt] = adcdat1 & 0x3ff;
++cnt;
if (cnt == 4)
{
/* 優(yōu)化措施4: 軟件過濾 */
if (s3c_filter_ts(x, y))
{
printk("x = %d, y = %d\n", (x[0]+x[1]+x[2]+x[3])/4, (y[0]+y[1]+y[2]+y[3])/4);
}
cnt = 0;
enter_wait_pen_up_mode();
}
else
{
enter_measure_xy_mode();
start_adc();
}
}
return IRQ_HANDLED;
}
6.4 支持滑動操作
6.4.1 定義一個定時器
static struct timer_list ts_timer;
6.4.2 修改初始化函數(shù),添加定時器
static int s3c_ts_init(void)
{
struct clk* clk;
/* 1. 分配一個input_dev結構體 */
s3c_ts_dev = input_allocate_device();
/* 2. 設置 */
/* 2.1 能產(chǎn)生哪類事件 */
set_bit(EV_KEY, s3c_ts_dev->evbit);
set_bit(EV_ABS, s3c_ts_dev->evbit);
/* 2.2 能產(chǎn)生這類事件里的哪些事件 */
set_bit(BTN_TOUCH, s3c_ts_dev->keybit);
input_set_abs_params(s3c_ts_dev, ABS_X, 0, 0x3FF, 0, 0);
input_set_abs_params(s3c_ts_dev, ABS_Y, 0, 0x3FF, 0, 0);
input_set_abs_params(s3c_ts_dev, ABS_PRESSURE, 0, 1, 0, 0);
/* 3. 注冊 */
input_register_device(s3c_ts_dev);
/* 4. 硬件相關的操作 */
/* 4.1 使能時鐘(CLKCON[15]) */
clk = clk_get(NULL, "adc");
clk_enable(clk);
/* 4.2 設置S3C2440的ADC/TS寄存器 */
s3c_ts_regs = ioremap(0x58000000, sizeof(struct s3c_ts_regs));
/* bit[14] : 1-A/D converter prescaler enable
* bit[13:6]: A/D converter prescaler value,
* 49, ADCCLK=PCLK/(49+1)=50MHz/(49+1)=1MHz
* bit[0]: A/D conversion starts by enable. 先設為0
*/
s3c_ts_regs->adccon = (1<<14)|(49<<6);
request_irq(IRQ_TC, pen_down_up_irq, IRQF_SAMPLE_RANDOM, "ts_pen", NULL);
request_irq(IRQ_ADC, adc_irq, IRQF_SAMPLE_RANDOM, "adc", NULL);
/* 優(yōu)化措施1:
* 設置ADCDLY為最大值, 這使得電壓穩(wěn)定后再發(fā)出IRQ_TC中斷
*/
s3c_ts_regs->adcdly = 0xffff;
/* 優(yōu)化措施5: 使用定時器處理長按,滑動的情況
*
*/
init_timer(&ts_timer);
ts_timer.function = s3c_ts_timer_function;
add_timer(&ts_timer); //在出口函數(shù)中要刪除它
enter_wait_pen_down_mode();
return 0;
}
6.4.3 修該ADC中斷處理函數(shù),添加啟動定時器處理長按、滑動的情況
添加的代碼,見優(yōu)化措施5:
static irqreturn_t adc_irq(int irq, void *dev_id)
{
static int cnt = 0;
static int x[4], y[4];
int adcdat0, adcdat1;
/* 優(yōu)化措施2: 如果ADC完成時, 發(fā)現(xiàn)觸摸筆已經(jīng)松開, 則丟棄此次結果 */
adcdat0 = s3c_ts_regs->adcdat0;
adcdat1 = s3c_ts_regs->adcdat1;
if (s3c_ts_regs->adcdat0 & (1<<15))
{
/* 已經(jīng)松開 */
cnt = 0;
input_report_abs(s3c_ts_dev, ABS_PRESSURE, 0);
input_report_key(s3c_ts_dev, BTN_TOUCH, 0);
input_sync(s3c_ts_dev);
enter_wait_pen_down_mode();
}
else
{
// printk("adc_irq cnt = %d, x = %d, y = %d\n", ++cnt, adcdat0 & 0x3ff, adcdat1 & 0x3ff);
/* 優(yōu)化措施3: 多次測量求平均值 */
x[cnt] = adcdat0 & 0x3ff;
y[cnt] = adcdat1 & 0x3ff;
++cnt;
if (cnt == 4)
{
/* 優(yōu)化措施4: 軟件過濾 */
if (s3c_filter_ts(x, y))
{
printk("x = %d, y = %d\n", (x[0]+x[1]+x[2]+x[3])/4, (y[0]+y[1]+y[2]+y[3])/4);
}
cnt = 0;
enter_wait_pen_up_mode();
/* 優(yōu)化措施5:啟動定時器處理長按/滑動的情況 */
mod_timer(&ts_timer, jiffies + HZ/100); //添加一個10ms的定時器,時間到了就進入定時器處理函數(shù)
}
else
{
enter_measure_xy_mode();
start_adc();
}
}
return IRQ_HANDLED;
}
6.4.4 編輯定時器處理函數(shù)
當10ms的定時器已經(jīng)到了的話,
static void s3c_ts_timer_function(unsigned long data)
{
if (s3c_ts_regs->adcdat0 & (1<<15)) /* 如果已經(jīng)松開,則等待下次按下 */
{
enter_wait_pen_down_mode();
}
else
{
/* 測量X/Y坐標 */
enter_measure_xy_mode();
start_adc();
}
}
6.4.5 修改出口函數(shù)
static void s3c_ts_exit(void)
{
free_irq(IRQ_TC, NULL);
free_irq(IRQ_ADC, NULL);
iounmap(s3c_ts_regs);
input_unregister_device(s3c_ts_dev);
input_free_device(s3c_ts_dev);
del_timer(&ts_timer); //刪除定時器
}
6.4.6 編譯測試
重新編譯安裝驅(qū)動后,使用觸摸屏滑動或長按,觀察輸出數(shù)據(jù):坐標可以連續(xù)輸出!
7、將printk函數(shù)替換成上報事件,得到完整觸摸屏驅(qū)動
7.1 修改adc中斷處理函數(shù)
注釋掉printk函數(shù),添加上報事件語句:
static irqreturn_t adc_irq(int irq, void *dev_id)
{
static int cnt = 0;
static int x[4], y[4];
int adcdat0, adcdat1;
/* 優(yōu)化措施2: 如果ADC完成時, 發(fā)現(xiàn)觸摸筆已經(jīng)松開, 則丟棄此次結果 */
adcdat0 = s3c_ts_regs->adcdat0;
adcdat1 = s3c_ts_regs->adcdat1;
if (s3c_ts_regs->adcdat0 & (1<<15))
{
/* 已經(jīng)松開 */
cnt = 0;
input_report_abs(s3c_ts_dev, ABS_PRESSURE, 0); //壓力為0
input_report_key(s3c_ts_dev, BTN_TOUCH, 0); //無觸摸屏按鍵類事件
input_sync(s3c_ts_dev);
enter_wait_pen_down_mode();
}
else
{
// printk("adc_irq cnt = %d, x = %d, y = %d\n", ++cnt, adcdat0 & 0x3ff, adcdat1 & 0x3ff);
/* 優(yōu)化措施3: 多次測量求平均值 */
x[cnt] = adcdat0 & 0x3ff;
y[cnt] = adcdat1 & 0x3ff;
++cnt;
if (cnt == 4)
{
/* 優(yōu)化措施4: 軟件過濾 */
if (s3c_filter_ts(x, y))
{
//printk("x = %d, y = %d\n", (x[0]+x[1]+x[2]+x[3])/4, (y[0]+y[1]+y[2]+y[3])/4);
input_report_abs(s3c_ts_dev, ABS_X, (x[0]+x[1]+x[2]+x[3])/4);
input_report_abs(s3c_ts_dev, ABS_Y, (y[0]+y[1]+y[2]+y[3])/4);
//此處僅需表示松開或按下兩種壓力狀態(tài),若是繪圖板,則會有多個壓力值
input_report_abs(s3c_ts_dev, ABS_PRESSURE, 1);
input_report_key(s3c_ts_dev, BTN_TOUCH, 1); //觸摸屏按鍵類事件
input_sync(s3c_ts_dev);
}
cnt = 0;
enter_wait_pen_up_mode();
/* 啟動定時器處理長按/滑動的情況 */
mod_timer(&ts_timer, jiffies + HZ/100);
}
else
{
enter_measure_xy_mode();
start_adc();
}
}
return IRQ_HANDLED;
}
7.2 修改TC中斷處理函數(shù)
static irqreturn_t pen_down_up_irq(int irq, void *dev_id)
{
if (s3c_ts_regs->adcdat0 & (1<<15))
{
//printk("pen up\n");
input_report_abs(s3c_ts_dev, ABS_PRESSURE, 0);
input_report_key(s3c_ts_dev, BTN_TOUCH, 0);
input_sync(s3c_ts_dev);
enter_wait_pen_down_mode();
}
else
{
//測量xy坐標
enter_measure_xy_mode();
start_adc();
}
return IRQ_HANDLED;
}
7.3 編譯測試
7.3.1 使用hexdump命令測試
rmmod s3c_tc
ls /dev/event* //安裝新驅(qū)動前查看原來有哪些
ls:/dev/event*:No such file or directory
insmod s3c_tc.ko
ls /dev/event* //安裝完新驅(qū)動后,查看新增加的是哪個event?
/dev/event0
hexdump /dev/event0
輸出如下:

其輸出結果的前4行分別對應adc_irq處理函數(shù)中的4個上報事件:
input_report_abs(s3c_ts_dev, ABS_X, (x[0]+x[1]+x[2]+x[3])/4);
input_report_abs(s3c_ts_dev, ABS_Y, (y[0]+y[1]+y[2]+y[3])/4);
input_report_abs(s3c_ts_dev, ABS_PRESSURE, 1); input_report_key(s3c_ts_dev, BTN_TOUCH, 1);
前面的秒和微妙代表后面事件發(fā)生的時間。
第一行代表上報事件input_report_abs(s3c_ts_dev, ABS_X, (x[0]+x[1]+x[2]+x[3])/4);
type:代表EV_ABS宏的取值為3,即絕對位移;
code:代表ABS_X宏的取值為0,即x方向;
value:4個字節(jié)表示x坐標值。
第4行代表上報事件input_report_key(s3c_ts_dev, BTN_TOUCH, 1);
type:代表EV_ABS宏的取值為1,即按鍵;
code:代表ABS_X宏的取值為014a,即BTN_TOUCH;
value:4個字節(jié)表示按鍵值1。
7.3.2 使用tslib測試
- 安裝tslib
tar xzf tslib-1.4.tar.gz
cd tslib
./autogen.sh
提示錯誤:./autogen.sh: 4: autoreconf: not found
解決方法:sudo apt-get install autoconf automake libtool
mkdir tmp
echo "ac_cv_func_malloc_0_nonnull=yes" >arm-linux.cache
./configure --host=arm-linux --cache-file=arm-linux.cache --prefix=$(pwd)/tmp
make
make install
- 復制tmp目錄下的所有文件到開發(fā)板的根目錄中
cp tmp /nfs_root/first_fs/ts_dir -rfd
cd /mnt/ts_dir
cp * / -rfd
- 安裝驅(qū)動程序
# insmod cfbcopyarea.ko
# insmod cfbfillrect.ko
# insmod cfbimgblt.ko
# insmod s3c_lcd.ko //也可以使用內(nèi)核自帶的LCD驅(qū)動
//如果發(fā)生段錯誤,則重新編譯lcd驅(qū)動后,再加載
# ls /dev/ev*
ls:/dev/event*:No such file or directory
# insmod s3c_ts.ko
# ls /dev/ev*
/dev/event0 //這是觸摸屏設備
#
# ls /dev/fb0 //這是LCD設備
-
測試
- 修改/etc/ts.conf第一行(去掉#號和第一個空格)
# module_raw input 改為: module_raw input- 添加環(huán)境變量
# export TSLIB_TSDEVICE=/dev/event0 //指定tc觸摸屏設備 # 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 //指定lcd顯示屏 # ts_calibrate //開始校驗 錯誤提示:selected device is not a touchscreen I understand

-
解決辦法:看到這個錯誤的提示,我們應該想到tslib源碼里面肯定有這個錯誤提示的條件,這樣就讓我們有了思路,我們可以去找到這個文件看看他錯誤判斷的條件是啥?
- 查找“selected device is not a touchscreen I understand”出處:
cd tslib/plugins grep -nr "selected device is not a touchscreen I understand" 匹配到二進制文件 .libs/input.so 匹配到二進制文件 .libs/input-raw.o input-raw.c:61: fprintf(stderr, "selected device is not a touchscreen I understand\n");可以看出,該錯誤提示語句出自input-raw.c之手!
- 打開input-raw.c文件,找出相關段落
47 static int check_fd(struct tslib_input *i) 48 { 49 struct tsdev *ts = i->module.dev; 50 int version; 51 u_int32_t bit; 52 u_int64_t absbit; 53 54 if (! ((ioctl(ts->fd, EVIOCGVERSION, &version) >= 0) && 55 (version == EV_VERSION) && 56 (ioctl(ts->fd, EVIOCGBIT(0, sizeof(bit) * 8), &bit) >= 0) && 57 (bit & (1 << EV_ABS)) && 58 (ioctl(ts->fd, EVIOCGBIT(EV_ABS, sizeof(absbit) * 8), &absbit) >= 0) && 59 (absbit & (1 << ABS_X)) && 60 (absbit & (1 << ABS_Y)) && (absbit & (1 << ABS_PRESSURE)))) { 61 fprintf(stderr, "selected device is not a touchscreen I understand\n"); 62 return -1; 63 } 64 65 if (bit & (1 << EV_SYN)) 66 i->using_syn = 1; 67 68 return 0; 69 }tslib通過EVIOCGVERSION來獲取驅(qū)動的版本號,然后再通過EVIOCGBIT來判斷設備是否為觸摸屏,最后獲取觸摸屏的X軸(ABS_X),Y軸(ABS_Y),以及壓力(ABS_PRESSURE)。
其中只要有一項內(nèi)容不正確,tslib都會認為該設備不是觸摸屏,而打印出“selecteddevice is not a touchscreen I understand”錯誤。
EVIOCGBIT ioctl處理 4個參數(shù) ( ioctl(fd, EVIOCGBIT(ev_type, max_bytes), bitfield))。 ev_type是返回的 type feature( 0是個特殊 case,表示返回設備支持的所有的type features)。 max_bytes表示返回的最大字節(jié)數(shù)。 bitfield域是指向保存結果的內(nèi)存指針。 return value表示保存結果的實際字節(jié)數(shù),如果調(diào)用失敗,則返回負值。
這里用bit和EV_ABS相與來判斷是不是EV_ABS事件,判斷是這個事件后再判斷這個事件是否又X,Y軸和壓力值。
image-20210726233040736- 核查內(nèi)核(linux-3.4.2/include/linux/input.h)與編譯tslib的編譯器(/usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/usr/include/linux/input.h)中的EV_VERSION版本號是否一致。
內(nèi)核中的定義為: #define EV_VERSION 0x010001
編譯器中的定義: #define EV_VERSION 0x010000
兩者不等,所以只要將內(nèi)核中的版本號改為0x010000再重新編譯內(nèi)核,或者將編譯器中的改為0x010001,然后再重新編譯tslib庫也行。
另外:若在觸摸屏驅(qū)動程序中沒有同時上報坐標值和壓力值也會引起類似的錯誤提醒。因為tslib同時判斷的幾個條件必須同時滿足,方才不會報錯。所以不管你的應用程序需不需要測試壓力值,在你的觸摸屏驅(qū)動程序中都要上報壓力值。即:
input_report_abs(s3c_ts_dev, ABS_X, x);
input_report_abs(s3c_ts_dev, ABS_Y, y);
input_report_abs(s3c_ts_dev, ABS_PRESSURE, 1);
input_report_key(s3c_ts_dev, BTN_TOUCH, 1);
input_sync(s3c_ts_dev);
還有一點需要提醒的是,在設置tslib環(huán)境變量的時候,一定要正確設置觸摸屏(例如:event0)和顯示屏(例如:fb0)的文件名,特別是當內(nèi)核中安裝了多個event事件的時候。
- 測試繼續(xù):
# ts_calibrate
xres = 480, yres = 272 //屏幕出現(xiàn)十字線,用于點擊校驗
Took 3 samples...
Top left : X = 713 Y = 806
。。。
Center : X = 477 Y = 524
...
Calibration constant:16972928 -18997 -162 ...
# ls /etc/pointercal
/etc/pointercal
# ts_test //十字架會隨著你的手指在觸摸屏上移動。
215.787878: 124 234 1
215.782448: 102 219 0
。。。
# ts_ //按下tab命令補全鍵,顯示所有可執(zhí)行測試命令
ts_harvest ts_print_raw//打印觸摸點原始坐標
ts_calibrate ts_print//打印觸摸點像素坐標
ts_test
