【轉】S3C2440上觸摸屏驅動實例開發講解

•共享資源,歡迎轉載:http://hbhuanggang.cublog.cn
一、開發環境

•主  機:VMWare--Fedora 9
•開發板:Mini2440--64MB Nand, Kernel:2.6.30.4
•編譯器:arm-linux-gcc-4.3.2
二、前提知識

1、Linux輸入子系統(Input Subsystem):

   在Linux中,輸入子系統是由輸入子系統設備驅動層、輸入子系統核心層(Input Core)和輸入子系統事件處理層(Event Handler)組成。其中設備驅動層提供對硬件各寄存器的讀寫訪問和將底層硬件對用戶輸入訪問的響應轉換爲標準的輸入事件,再通過核心層提交給事件處理層;而核心層對下提供了設備驅動層的編程接口,對上又提供了事件處理層的編程接口;而事件處理層就爲我們用戶空間的應用程序提供了統一訪問設備的接口和驅動層提交來的事件處理。所以這使得我們輸入設備的驅動部分不在用關心對設備文件的操作,而是要關心對各硬件寄存器的操作和提交的輸入事件。下面用圖形來描述一下這三者的關係吧!


另外,又找了另一幅圖來說明Linux輸入子系統的結構,可能更加形象容易理解。如下:

 


2、輸入子系統設備驅動層實現原理:

   在Linux中,Input設備用input_dev結構體描述,定義在input.h中。設備的驅動只需按照如下步驟就可實現了。
①、在驅動模塊加載函數中設置Input設備支持input子系統的哪些事件;
②、將Input設備註冊到input子系統中;
③、在Input設備發生輸入操作時(如:鍵盤被按下/擡起、觸摸屏被觸摸/擡起/移動、鼠標被移動/單擊/擡起時等),提交所發生的事件及對應的鍵值/座標等狀態。

Linux中輸入設備的事件類型有(這裏只列出了常用的一些,更多請看linux/input.h中):

EV_SYN     0x00     同步事件
EV_KEY     0x01     按鍵事件
EV_REL     0x02     相對座標(如:鼠標移動,報告的是相對最後一次位置的偏移)
EV_ABS     0x03     絕對座標(如:觸摸屏和操作杆,報告的是絕對的座標位置)
EV_MSC     0x04     其它
EV_LED     0x11     LED
EV_SND     0x12     聲音
EV_REP     0x14     Repeat
EV_FF      0x15     力反饋
 

用於提交較常用的事件類型給輸入子系統的函數有: void input_report_key(struct input_dev *dev, unsigned int code, int value); //提交按鍵事件的函數
void input_report_rel(struct input_dev *dev, unsigned int code, int value); //提交相對座標事件的函數
void input_report_abs(struct input_dev *dev, unsigned int code, int value); //提交絕對座標事件的函數
 

注意,在提交輸入設備的事件後必須用下列方法使事件同步,讓它告知input系統,設備驅動已經發出了一個完整的報告: void input_sync(struct input_dev *dev)
 


三、觸摸屏驅動的實現步驟

1、硬件原理圖分析:

   S3c2440芯片內部觸摸屏接口與ADC接口是集成在一起的,硬件結構原理圖請看:S3C2440上ADC驅動實例開發講解中的圖,其中通道7(XP或AIN7)作爲觸摸屏接口的X座標輸入,通道5(YP或AIN5)作爲觸摸屏接口的Y座標輸入。在"S3C2440上ADC驅動實例開發講解"中,AD轉換的模擬信號是由開發板上的一個電位器產生並通過通道1(AIN0)輸入的,而這裏的模擬信號則是由點觸觸摸屏所產生的X座標和Y座標兩個模擬信號,並分別通過通道7和通道5輸入。S3c2440提供的觸摸屏接口有4種處理模式,分別是:正常轉換模式、單獨的X/Y位置轉換模式、自動X/Y位置轉換模式和等待中斷模式,對於在每種模式下工作的要求,請詳細查看數據手冊的描述。本驅動實例將採用自動X/Y位置轉換模式和等待中斷模式。

注意:在每步中,爲了讓代碼邏輯更加有條理和容易理解,就沒有考慮代碼的順序,比如函數要先定義後調用。如果要編譯此代碼,請嚴格按照C語言的規範來調整代碼的順序。

2、建立觸摸屏驅動程序my2440_ts.c,首先實現加載和卸載部分,在驅動加載部分,我們主要做的事情是:啓用ADC所需要的時鐘、映射IO口、初始化寄存器、申請中斷、初始化輸入設備、將輸入設備註冊到輸入子系統。代碼如下:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/clk.h>
#include <linux/init.h>
#include <linux/input.h>
#include <linux/serio.h>
#include <plat/regs-adc.h>
#include <asm/irq.h>
#include <asm/io.h>

 

/*用於保存從平臺時鐘列表中獲取的ADC時鐘*/
static struct clk *adc_clk;

/*定義了一個用來保存經過虛擬映射後的內存地址*/
static void __iomem *adc_base;

/*定義一個輸入設備來表示我們的觸摸屏設備*/
static struct input_dev *ts_dev;

/*設備名稱*/
#define DEVICE_NAME    "my2440_TouchScreen"

/*定義一個WAIT4INT宏,該宏將對ADC觸摸屏控制寄存器進行操作
S3C2410_ADCTSC_YM_SEN這些宏都定義在regs-adc.h中*/
#define WAIT4INT(x)    (((x)<<8) | S3C2410_ADCTSC_YM_SEN | S3C2410_ADCTSC_YP_SEN | /
                    S3C2410_ADCTSC_XP_SEN | S3C2410_ADCTSC_XY_PST(3))

static int __init ts_init(void)
{
    int ret;

    /*從平臺時鐘隊列中獲取ADC的時鐘,這裏爲什麼要取得這個時鐘,因爲ADC的轉換頻率跟時鐘有關。
    系統的一些時鐘定義在arch/arm/plat-s3c24xx/s3c2410-clock.c中*/
    adc_clk = clk_get(NULL, "adc");
    if(!adc_clk)
    {
        /*錯誤處理*/
        printk(KERN_ERR "falied to find adc clock source/n");
        return -ENOENT;
    }

    /*時鐘獲取後要使能後纔可以使用,clk_enable定義在arch/arm/plat-s3c/clock.c中*/
    clk_enable(adc_clk);

    /*將ADC的IO端口占用的這段IO空間映射到內存的虛擬地址,ioremap定義在io.h中。
     注意:IO空間要映射後才能使用,以後對虛擬地址的操作就是對IO空間的操作,
     S3C2410_PA_ADC是ADC控制器的基地址,定義在mach-s3c2410/include/mach/map.h中,0x20是虛擬地址長度大小*/
    adc_base = ioremap(S3C2410_PA_ADC, 0x20);
    if(adc_base == NULL)
    {
        /*錯誤處理*/
        printk(KERN_ERR "failed to remap register block/n");
        ret = -EINVAL;
        goto err_noclk;
    }

    /*初始化ADC控制寄存器和ADC觸摸屏控制寄存器*/
    adc_initialize();

    /*申請ADC中斷,AD轉換完成後觸發。這裏使用共享中斷IRQF_SHARED是因爲該中斷號在ADC驅動中也使用了,
    最後一個參數1是隨便給的一個值,因爲如果不給值設爲NULL的話,中斷就申請不成功*/
    ret = request_irq(IRQ_ADC, adc_irq, IRQF_SHARED | IRQF_SAMPLE_RANDOM, DEVICE_NAME, 1);
    if(ret)
    {
        printk(KERN_ERR "IRQ%d error %d/n", IRQ_ADC, ret);
        ret = -EINVAL;
        goto err_nomap;
    }

    /*申請觸摸屏中斷,對觸摸屏按下或提筆時觸發*/
    ret = request_irq(IRQ_TC, tc_irq, IRQF_SAMPLE_RANDOM, DEVICE_NAME, 1);
    if(ret)
    {
        printk(KERN_ERR "IRQ%d error %d/n", IRQ_TC, ret);
        ret = -EINVAL;
        goto err_noirq;
    }

    /*給輸入設備申請空間,input_allocate_device定義在input.h中*/
    ts_dev = input_allocate_device();

    /*下面初始化輸入設備,即給輸入設備結構體input_dev的成員設置值。
    evbit字段用於描述支持的事件,這裏支持同步事件、按鍵事件、絕對座標事件,
    BIT宏實際就是對1進行位操作,定義在linux/bitops.h中*/
    ts_dev->evbit[0] = BIT(EV_SYN) | BIT(EV_KEY) | BIT(EV_ABS);
   
    /*keybit字段用於描述按鍵的類型,在input.h中定義了很多,這裏用BTN_TOUCH類型來表示觸摸屏的點擊*/
    ts_dev->keybit[BITS_TO_LONGS(BTN_TOUCH)] = BIT(BTN_TOUCH);

    /*對於觸摸屏來說,使用的是絕對座標系統。這裏設置該座標系統中X和Y座標的最小值和最大值(0-1023範圍)
    ABS_X和ABS_Y就表示X座標和Y座標,ABS_PRESSURE就表示觸摸屏是按下還是擡起狀態*/
    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);

    /*以下是設置觸摸屏輸入設備的身份信息,直接在這裏寫死。
    這些信息可以在驅動掛載後在/proc/bus/input/devices中查看到*/
    ts_dev->name          = DEVICE_NAME;   /*設備名稱*/
    ts_dev->id.bustype    = BUS_RS232;     /*總線類型*/   
    ts_dev->id.vendor     = 0xDEAD;        /*經銷商ID號*/
    ts_dev->id.product    = 0xBEEF;        /*產品ID號*/
    ts_dev->id.version    = 0x0101;        /*版本ID號*/

    /*好了,一些都準備就緒,現在就把ts_dev觸摸屏設備註冊到輸入子系統中*/
    input_register_device(ts_dev);

    return 0;

/*下面是錯誤跳轉處理*/
err_noclk:
    clk_disable(adc_clk);
    clk_put(adc_clk);

err_nomap:
    iounmap(adc_base);

err_noirq:
    free_irq(IRQ_ADC, 1);

    return ret;
}

/*初始化ADC控制寄存器和ADC觸摸屏控制寄存器*/
static void adc_initialize(void)
{
    /*計算結果爲(二進制):111111111000000,再根據數據手冊得知
    此處是將AD轉換預定標器值設爲255、AD轉換預定標器使能有效*/
    writel(S3C2410_ADCCON_PRSCEN | S3C2410_ADCCON_PRSCVL(0xFF), adc_base + S3C2410_ADCCON);

    /*對ADC開始延時寄存器進行設置,延時值爲0xffff*/
    writel(0xffff, adc_base + S3C2410_ADCDLY);

    /*WAIT4INT宏計算結果爲(二進制):11010011,再根據數據手冊得知
    此處是將ADC觸摸屏控制寄存器設置成等待中斷模式*/
    writel(WAIT4INT(0), adc_base + S3C2410_ADCTSC);
}

static void __exit ts_exit(void)
{
    /*屏蔽和釋放中斷*/
    disable_irq(IRQ_ADC);
    disable_irq(IRQ_TC);
    free_irq(IRQ_ADC, 1);
    free_irq(IRQ_TC, 1);

    /*釋放虛擬地址映射空間*/
    iounmap(adc_base);

    /*屏蔽和銷燬時鐘*/
    if(adc_clk)
    {
        clk_disable(adc_clk);
        clk_put(adc_clk);
        adc_clk = NULL;
    }

    /*將觸摸屏設備從輸入子系統中註銷*/
    input_unregister_device(ts_dev);
}

module_init(ts_init);
module_exit(ts_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Huang Gang");
MODULE_DESCRIPTION("My2440 Touch Screen Driver");
 

3、接下來要做的是,在兩個中斷服務程序中實現觸摸屏狀態和座標的轉換。先看代碼,如下:

/*定義一個外部的信號量ADC_LOCK,因爲ADC_LOCK在ADC驅動程序中已申明
這樣就能保證ADC資源在ADC驅動和觸摸屏驅動中進行互斥訪問*/
extern struct semaphore ADC_LOCK;

/*做爲一個標籤,只有對觸摸屏操作後纔對X和Y座標進行轉換*/
static int OwnADC = 0;

/*用於記錄轉換後的X座標值和Y座標值*/
static long xp;
static long yp;

/*用於計數對觸摸屏壓下或擡起時模擬輸入轉換的次數*/
static int count;

/*定義一個AUTOPST宏,將ADC觸摸屏控制寄存器設置成自動轉換模式*/
#define AUTOPST    (S3C2410_ADCTSC_YM_SEN | S3C2410_ADCTSC_YP_SEN | S3C2410_ADCTSC_XP_SEN | /
                S3C2410_ADCTSC_AUTO_PST | S3C2410_ADCTSC_XY_PST(0))


/*觸摸屏中斷服務程序,對觸摸屏按下或提筆時觸發執行*/
static irqreturn_t tc_irq(int irq, void *dev_id)
{
    /*用於記錄這一次AD轉換後的值*/
    unsigned long data0;
    unsigned long data1;

    /*用於記錄觸摸屏操作狀態是按下還是擡起*/
    int updown;

    /*ADC資源可以獲取,即上鎖*/
    if (down_trylock(&ADC_LOCK) == 0)
    {
        /*標識對觸摸屏進行了操作*/
        OwnADC = 1;

        /*讀取這一次AD轉換後的值,注意這次主要讀的是狀態*/
        data0 = readl(adc_base + S3C2410_ADCDAT0);
        data1 = readl(adc_base + S3C2410_ADCDAT1);

        /*記錄這一次對觸摸屏是壓下還是擡起,該狀態保存在數據寄存器的第15位,所以與上S3C2410_ADCDAT0_UPDOWN*/
        updown = (!(data0 & S3C2410_ADCDAT0_UPDOWN)) && (!(data1 & S3C2410_ADCDAT0_UPDOWN));

        /*判斷觸摸屏的操作狀態*/
        if (updown)
        {
            /*如果是按下狀態,則調用touch_timer_fire函數來啓動ADC轉換,該函數定義後面再講*/
            touch_timer_fire(0);
        }
        else
        {
            /*如果是擡起狀態,就結束了這一次的操作,所以就釋放ADC資源的佔有*/
            OwnADC = 0;
            up(&ADC_LOCK);
        }
    }

    return IRQ_HANDLED;
}

static void touch_timer_fire(unsigned long data)
{
    /*用於記錄這一次AD轉換後的值*/
      unsigned long data0;
      unsigned long data1;

    /*用於記錄觸摸屏操作狀態是按下還是擡起*/
    int updown;

    /*讀取這一次AD轉換後的值,注意這次主要讀的是狀態*/
      data0 = readl(adc_base + S3C2410_ADCDAT0);
    data1 = readl(adc_base + S3C2410_ADCDAT1);

    /*記錄這一次對觸摸屏是壓下還是擡起,該狀態保存在數據寄存器的第15位,所以與上S3C2410_ADCDAT0_UPDOWN*/
     updown = (!(data0 & S3C2410_ADCDAT0_UPDOWN)) && (!(data1 & S3C2410_ADCDAT0_UPDOWN));

    /*判斷觸摸屏的操作狀態*/
     if (updown)
    {
        /*如果狀態是按下,並且ADC已經轉換了就報告事件和數據*/
         if (count != 0)
        {
            long tmp;
                                                                                                
            tmp = xp;
            xp = yp;
            yp = tmp;
                                                                                                
            xp >>= 2;
            yp >>= 2;

#ifdef CONFIG_TOUCHSCREEN_MY2440_DEBUG
            /*觸摸屏調試信息,編譯內核時選上此項後,點擊觸摸屏會在終端上打印出座標信息*/
            struct timeval tv;
            do_gettimeofday(&tv);
            printk(KERN_DEBUG "T: %06d, X: %03ld, Y: %03ld/n", (int)tv.tv_usec, xp, yp);
#endif

            /*報告X、Y的絕對座標值*/
             input_report_abs(ts_dev, ABS_X, xp);
             input_report_abs(ts_dev, ABS_Y, yp);

            /*報告觸摸屏的狀態,1表明觸摸屏被按下*/
            input_report_abs(ts_dev, ABS_PRESSURE, 1);

            /*報告按鍵事件,鍵值爲1(代表觸摸屏對應的按鍵被按下)*/
             input_report_key(ts_dev, BTN_TOUCH, 1);

            /*等待接收方受到數據後回覆確認,用於同步*/
             input_sync(ts_dev);
         }

        /*如果狀態是按下,並且ADC還沒有開始轉換就啓動ADC進行轉換*/
         xp = 0;
         yp = 0;
         count = 0;

        /*設置觸摸屏的模式爲自動轉換模式*/
         writel(S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST, adc_base + S3C2410_ADCTSC);

        /*啓動ADC轉換*/
         writel(readl(adc_base + S3C2410_ADCCON) | S3C2410_ADCCON_ENABLE_START, adc_base + S3C2410_ADCCON);
     }
    else
    {
        /*否則是擡起狀態*/
         count = 0;

        /*報告按鍵事件,鍵值爲0(代表觸摸屏對應的按鍵被釋放)*/
         input_report_key(ts_dev, BTN_TOUCH, 0);

        /*報告觸摸屏的狀態,0表明觸摸屏沒被按下*/
         input_report_abs(ts_dev, ABS_PRESSURE, 0);

        /*等待接收方受到數據後回覆確認,用於同步*/
         input_sync(ts_dev);

        /*將觸摸屏重新設置爲等待中斷狀態*/
         writel(WAIT4INT(0), adc_base + S3C2410_ADCTSC);

        /*如果觸摸屏擡起,就意味着這一次的操作結束,所以就釋放ADC資源的佔有*/
        if (OwnADC)
        {
            OwnADC = 0;
            up(&ADC_LOCK);
        }
     }
}

/*定義並初始化了一個定時器touch_timer,定時器服務程序爲touch_timer_fire*/
static struct timer_list touch_timer = TIMER_INITIALIZER(touch_timer_fire, 0, 0);

/*ADC中斷服務程序,AD轉換完成後觸發執行*/
static irqreturn_t adc_irq(int irq, void *dev_id)
{
    /*用於記錄這一次AD轉換後的值*/
    unsigned long data0;
    unsigned long data1;

    if(OwnADC)
    {
        /*讀取這一次AD轉換後的值,注意這次主要讀的是座標*/
        data0 = readl(adc_base + S3C2410_ADCDAT0);
        data1 = readl(adc_base + S3C2410_ADCDAT1);

        /*記錄這一次通過AD轉換後的X座標值和Y座標值,根據數據手冊可知,X和Y座標轉換數值
        分別保存在數據寄存器0和1的第0-9位,所以這裏與上S3C2410_ADCDAT0_XPDATA_MASK就是取0-9位的值*/
        xp += data0 & S3C2410_ADCDAT0_XPDATA_MASK;
        yp += data1 & S3C2410_ADCDAT1_YPDATA_MASK;

        /*計數這一次AD轉換的次數*/
        count++;

        if (count < (1<<2))
        {
            /*如果轉換的次數小於4,則重新啓動ADC轉換*/
            writel(S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST, adc_base + S3C2410_ADCTSC);
            writel(readl(adc_base + S3C2410_ADCCON) | S3C2410_ADCCON_ENABLE_START, adc_base + S3C2410_ADCCON);
        }
        else
        {
            /*否則,啓動1個時間滴答的定時器,這是就會去執行定時器服務程序上報事件和數據*/
            mod_timer(&touch_timer, jiffies + 1);
            writel(WAIT4INT(1), adc_base + S3C2410_ADCTSC);
        }
    }

    return IRQ_HANDLED;
}
 

我們從整體上描述轉換這個的過程:
(1)如果觸摸屏感覺到觸摸,則觸發觸摸屏中斷即進入tc_irq,獲取ADC_LOCK後判斷觸摸屏狀態爲按下,則調用touch_timer_fire啓動ADC轉換;
(2)當ADC轉換啓動後,觸發ADC中斷即進入adc_irq,如果這一次轉換的次數小於4,則重新啓動ADC進行轉換,如果4次完畢後,啓動1個時間滴答的定時器,停止ADC轉換,也就是說在這個時間滴答內,ADC轉換是停止的;
(3)這裏爲什麼要在1個時間滴答到來之前停止ADC的轉換呢?這是爲了防止屏幕抖動。
(4)如果1個時間滴答到來則進入定時器服務程序touch_timer_fire,判斷觸摸屏仍然處於按下狀態則上報事件和轉換的數據,並重啓ADC轉換,重複第(2)步;
(5)如果觸摸擡起了,則上報釋放事件,並將觸摸屏重新設置爲等待中斷狀態。

四、移植和測試觸摸屏驅動程序

    移植和測試請看Linux-2.6.30.4在2440上的移植之觸摸屏驅動

[轉載]S3C2440A的ADC和觸摸屏接口

原文地址:S3C2440A的ADC和觸摸屏接口作者:晨星
    S3C2440A內置一個帶8個模擬輸入通道的10位逐次逼近型(recycling type)CMOS模數轉換器。在2.5MHz的模數轉換時鐘頻率下,轉換速率可達到500KSPS(Kilo Samples Per Second),並且支持片內採樣保持功能和省電模式。S3C2440A還帶有觸摸屏接口,可以控制/選擇觸摸屏的XP,XM,YP,YM輸入以進行X,Y位置轉換。

    AD轉換頻率 = GCLK / (p + 1)

    AD轉換時間 = 1 / (AD轉換頻率 / 5) = 5 * (p + 1) / GCLK

    其中,GCLK是系統主時鐘頻率,一般等於50MHz;p是預分頻值,在0到255之間;除以5表示每次轉換需要5個時鐘週期。AD轉換器的設計最大時鐘頻率爲2.5MHz,所以p最大爲19;最大轉換頻率爲0.5MHz,所以最大轉換速率爲0.5M個採樣每秒,即500KSPS。

    簡單地使用AD轉換器是很容易實現的,參考《基於ARM的嵌入式系統開發與實例》第12章的程序,稍作修改就可以了。主要的修改是選擇模擬輸入通道爲AIN1,因爲開發板上的可調電阻是通過這個通道連接到處理器的。程序運行後,用螺絲刀調節可調電阻,可以看到採樣值的變化。

    困難的是如何實現觸摸屏接口。開發板配套的示例程序只是在觸摸筆離開屏幕的時候輸出觸摸筆的位置,這顯然是不滿足通常的應用的。一般要求在觸摸筆按下時可以判斷按下位置,隨後筆在屏幕上滑動時可以不斷判斷筆的位置,最後還要判斷筆離開屏幕。

    1 觸摸屏接口模式

    觸摸屏有四種接口模式:

   (1)普通轉換模式

    與通用AD轉換的使用方式很相似。通過設置ADCCON來初始化,並以一個讀和寫ADCDAT0的操作完成。

   (2)X/Y位置分別轉換模式

    又可以分成兩種模式:X位置轉換模式和Y位置轉換模式。這兩種模式下,觸摸屏分別把X、Y位置轉換數據寫入到ADCDAT0和ADCDAT1中之後,向中斷控制器發起中斷請求。

   (3)自動(順序)X/Y位置轉換模式

    觸摸屏控制器依次轉換X和Y位置,把轉換結果分別寫入到ADCDAT0和ADCDAT1中,然後向中斷控制器發起中斷請求。

   (4)等待中斷模式

    設置ADCTSC爲0xD3,當觸摸筆按下時,控制器發起中斷請求。

    注意這裏的中斷類型:最終向處理器發起的中斷請求類型是INT_ADC,但它分爲兩種子中斷類型,即INT_SUB_ADC表示AD採樣完成;INT_SUB_TC表示觸摸屏中斷,即筆在屏幕上按下或者離開屏幕。觸摸屏X、Y位置採樣完成時發起的中斷是INT_SUB_ADC,而不是INT_SUB_TC,因爲X、Y位置採樣也是一種AD採樣動作,採樣完成也就是AD轉換完成。

     2 定時採樣

     若採用普通轉換模式,則需要選擇某個觸摸屏輸入(XP/XM/YP/YM),然後在中斷髮生時從ADCDAT0中讀取採樣值,X和Y位置需要分別採樣。此外,也不懂XP/XM/YP/YM是什麼意思,要看觸摸屏接口芯片文檔才知道。

     X/Y位置分別採樣模式:採樣自動發生,採樣完成後從ADCDAT0和ADCDAT1中分別讀取X或者Y位置。與普通採樣模式的不同只是在於不需要選擇觸摸屏輸入,還有就是Y位置是從ADCDAT1讀取的(而不是從ADCDAT0)。

     X/Y位置自動採樣:採樣自動發生,只是在X和Y都完成採樣後才發起中斷請求。

     等待中斷模式:當觸摸筆在屏幕上按下或者離開屏幕時發起中斷請求。注意這個與X、Y位置採樣無關。

     要實現X、Y位置判斷,當然是用自動採樣模式最好;而要判斷觸摸筆的按下與離開,則應使用等待中斷模式。問題是怎麼把二者結合起來。最終採用的方法是,採用等待中斷模式來判斷筆的按下與離開;另外設置一個採樣定時器,每隔一定時間(10ms)對X、Y位置進行採樣。有幾點要注意:

     (1)只有在筆按下狀態時才進行位置採樣,筆不在屏幕上時,採樣是沒有意義的。

     (2)位置採樣採用輪詢模式實現,即等待採樣完成;採樣完成後要恢復ADCTSC,重置爲等待中斷模式。

     (3)在普通轉換模式,分別採樣模式和自動採樣模式下,ADCDAT0的最高位無效,不能用它來判斷筆的狀態。只有在觸摸屏中斷(INT_SUB_TC)中纔可以用ADCDAT0來判斷觸摸筆狀態。

     (4)位置採樣完成後要屏蔽BIT_SUB_ADC。否則,ADC持續進行採樣,會以很高的頻率(因爲採樣頻率很高)發起INT_SUB_ADC中斷請求,最終向處理器發起INT_ADC請求,這會嚴重影響處理器的工作。這也是不採用自動採樣,而要用定時器定時採樣的原因。

      最終程序如下,其中GetCursorPos()被採樣定時器(10ms)中斷服務程序調用,進行一次位置採樣。這裏獲取的X、Y位置值只是AD轉換器的輸出結果,一般與圖形系統中的座標值是不同的,需要通過一定的計算才能把採樣值轉換爲座標值,這就是觸摸屏校準了。

 typedef struct
{
    volatile int btn_state;
    volatile int x_pos;
    volatile int y_pos;
    volatile int int_count;
    volatile int start_flag;
}MOUSE_STATE;

static MOUSE_STATE g_mouse_state;


void GetCursorPos(void)
{
    if (0 == g_mouse_state.start_flag) return;
    if (0 == g_mouse_state.btn_state) return;
  
    rINTSUBMSK &= (~BIT_SUB_ADC);
    rADCTSC  = 0x0C;
    rADCCON  = (1 << 14) + (9 << 6) + (5 << 3);
    rADCCON |= 0x01;
    while(rADCCON & 0x01);
    while(!(rADCCON & 0x8000));
    while(!(rSUBSRCPND & (BIT_SUB_ADC)));
    rINTSUBMSK |= BIT_SUB_ADC;
  
    g_mouse_state.x_pos = (rADCDAT1 & 0x3FF);
    g_mouse_state.y_pos = (rADCDAT0 & 0x3FF);
    g_mouse_state.int_count++;

    rSUBSRCPND = BIT_SUB_ADC;

    UART0_printf("[%3d,%3d]n",g_mouse_state.x_pos,g_mouse_state.y_pos);

    if (0 == (rSUBSRCPND & BIT_SUB_TC))
    {
        rSRCPND = BIT_ADC;
        rINTPND = BIT_ADC;
        if (0 == g_mouse_state.btn_state)
        {
            rADCTSC = 0xD3;
        }
        else
        {
            rADCTSC = 0x1D3;
        }
    }
}


static void __irq TouchPanelIsr(void)
{
    rSUBSRCPND = BIT_SUB_TC;
    rSRCPND = BIT_ADC;
    rINTPND = BIT_ADC;

    if (0 == g_mouse_state.btn_state)
    {
        g_mouse_state.btn_state  = 1;
        g_mouse_state.start_flag = 1;
        UART0_printf("[%3d,%3d] ** DOWN **n",g_mouse_state.x_pos,g_mouse_state.y_pos);
        rADCTSC = 0x1D3;
    }
    else
    {
        g_mouse_state.btn_state = 0;
        Uart_Printf("[%3d,%3d] ## UP ##n",g_mouse_state.x_pos,g_mouse_state.y_pos);
        rADCTSC = 0xD3;
    }
}

void TouchPanelTest(void)
{
    rADCDLY = 50000;
    rADCCON = (1 << 14) + (9 << 6) + (4 << 3);
    rADCTSC = 0xD3;

    pISR_ADC = (unsigned int)TouchPanelIsr;
    rINTMSK &= (~BIT_ADC);
    rINTSUBMSK &= (~BIT_SUB_TC);
    rINTSUBMSK |= BIT_SUB_ADC;
}
 

實驗目的:通過串口顯示輸入的電壓值及採集按下觸摸屏的(x,y)座標值藉此掌握S3C2410的ADC和觸摸屏的使用。

實驗環境及說明:恆頤S3C2410開發板H2410。H24X0E擴展板上AIN0~AIN1輸出懸空,通過外接可變電阻電路採樣電壓值;外接的觸摸屏接口實現擴展觸摸屏完成相應操作本實驗基於夏普3.5英寸LQ035Q7DB02。

實驗思路:開發板上電啓動後,自動將NandFlash開始的4K數據複製到SRAM中,然後跳轉到0地址開始執行。關閉看門狗、初始化SDRAM及NandFlash控制器、設置MPLL來改變FCLK、HCLK、PCLK的值,設置堆棧,複製4KB後的16KB數據到SDRAM,之後進入main函數中進行ADC及觸摸屏的測試。

知識掌握:ADC(模數轉換器)和Touch Screen(觸摸屏)。
★S3C2410 ADC和TouchScreen概述:S3C2410內置1個8信道的10bit模數轉換器,該ADC能以500KSPS的採樣速率將外部的模擬信號轉換爲10位的二進制數字量。同時ADC部分能與CPU的觸摸屏控制器協同工作,完成對觸摸屏絕對地址的測量。ADC和TouchScreen共用一個A/D轉換器,ADC可同時採樣8路模擬輸入信號,使用觸摸屏時,AIN[7]、AIN[5]被用來測量XP/YP的電平,剩餘的六個引腳可用來做一般ADC輸入。2410ADC和觸摸屏功能框圖如下:

 
★ADC和TouchScreen控制器的工作模式:
●ADC普通轉換模式(Normal Converson Mode)---普通轉換模式(AUTO_PST=0,XY_PST=0)是用來進行一般的ADC轉換之用的,例如通過ADC測量電池電壓等等。
●獨立X/Y軸座標轉換模式(Separate X/Y Position Conversion Mode)---獨立X/Y軸座標轉換模式其實包含了X軸模式和Y軸模式2種模式。 首先進行X軸的座標轉換(AUTO_PST=0,XY_PST=1),X軸的轉換資料會寫到ADCDAT0寄存器的XPDAT中,等待轉換完成後,觸摸屏控制器會產生相應的中斷。 然後進行Y軸的座標轉換(AUTO_PST=0,XY_PST=2),Y軸的轉換資料會寫到ADCDAT1寄存器的YPDAT中,等待轉換完成後,觸摸屏控制器會產生相應的中斷。
●自動X/Y軸座標轉換模式(Auto X/Y Position Conversion Mode)---自動X/Y軸座標轉換模式(AUTO_PST=1,XY_PST=0)將會自動地進行X軸和Y軸的轉換操作,隨後產生相應的中斷。
●中斷等待模式(Wait for InterruptMode)---在系統等待"Pen Down",即觸摸屏按下的時候,其實是處於中斷等待模式。一旦被按下,實時產生"INT_TC"中斷信號。每次發生此中斷都,X軸和Y軸座標轉換資料都可以從相應的資料寄存器中讀出。
●閒置模式(Standby Mode)---在該模式下轉換資料寄存器中的值都被保留爲上次轉換時的資料。
★ADC兩種啓動方式:以手工啓動和讀結果時就自動地啓動下一次轉換;以查詢狀態位和發中斷方式獲知轉換是否結束。ADC的操作只涉及3個寄存器:
●ADCCON---設置ADCCON寄存器,選擇輸入信號通道,設置A/D轉換器的時鐘(A/D時鐘=PCLK/(PRSCVL+1))。A/D時鐘最大爲2.5MHz,且應小於PCLK的1/5;設置ADCCON寄存器,啓動轉換(設置READ_START位則讀轉換數據(讀ADCDAT0寄存器)時即啓動下一次轉換;否則可通過設置ENABLE_START位來啓動A/D轉換);ADCCON各位含義(ENABLE_START---置1啓動ADC轉換,置0無操作; RESR_START---置1允許讀操作啓動ADC轉換,置0禁止讀操作啓動ADC轉換; STDBM---置1將ADC置爲閒置狀態(模式),置0將ADC置爲正常操作狀態;SEL_MUX---選擇需要進行轉換的ADC信道; PRSCV---ADC轉換時鐘預分頻參數;PRSCEN---ADC轉換時鐘使能; ECFLG---ADC轉換完成標誌位(只讀)。爲1ADC轉換結束,爲0ADC轉換進行中)。
●ADCTSC---設置ADCTSC寄存器,使用設爲普通轉換模式,不使用觸摸屏功能;ADCTSC各位含義(XY_PST---對X/Y軸手動測量模式進行選擇;AUTO_PST---X/Y軸的自動轉換模式使能位;PULL_UP---XP端的上拉電阻使能位;XP_SEN---設置nXPON輸出狀態;XM_SEN---設置XMON輸出狀態;YP_SEN---設置nYPON輸出狀態;YM_SEN---設置YMON輸出狀態)。
●ADCDAT0---完成ADC轉換後,讀取ADCDAT0寄存器獲得數值(如果使用查詢方式,則可不斷讀取ADCCON寄存器的ECFLG位來確定是否轉換結束;否則可以使用INT_ADC中斷,發生INT_ADC中斷時表示轉換結束);ADCDAT0各位含義(XPDATA---X軸轉換資料寄存器;XY_PST---選擇X/Y軸自動轉換模式;AUTO_PST---X/Y軸自動轉換使能位;UPDOWN---選擇中斷等待模式的類型。爲0按下產生中斷,爲1釋放產生中斷)。
★觸摸屏操作還涉及到以下兩個寄存器:
●ADCDLY---ADC轉換週期等待定時器。
●ADCDAT1---同ADCDAT0。

關鍵代碼解析:
★head.S頭文件來初始化,設置SDRAM,將程序複製到SDRAM,然後跳到SDRAM繼續執行
.equ        MEM_CTL_BASE,       0x48000000
.text
.global _start
_start:
@中斷向量表處理函數,只給出復位和普通中斷模式的處理函數,其它異常未使用
    b   Reset
...
    b   HandleIRQ
@0x1c: 快中斷模式的向量地址
HandleFIQ:
    b   HandleFIQ

Reset:                                         @復位處理
    bl  disable_watch_dog        @關門喂狗
    bl  mem_control_setup       @設置存儲控制器
    ldr sp, =4096                         @設置棧指針,以下C函數調用前需要設好棧
    bl init_clock                            @設置MPLL,改變FCLK、HCLK、PCLK
    bl init_nand                            @初始化NandFlash
@將NandFlash中地址4096開始的代碼複製到SDRAM中
    ldr  r0, =0x30000000          @目標地址=0x30000000,SDRAM起始地址
    mov  r1, #4096                    @源地址=4096,連接時代碼在4096開始處
    mov  r2, #16*1024              @複製長度=16K,對於本實驗足夠
    bl  read_nand                     @調用C函數read_nand
    bl  clean_bss                      @清除bss段
    msr cpsr_c, #0xd2             @進入中斷模式
    ldr sp, =0x31000000         @設置中斷模式棧指針
    msr cpsr_c, #0xdf              @進入系統模式
    ldr sp, =0x34000000         @設置系統模式棧指針
    ldr lr, =ret_initirq                 @設置返回地址
    ldr pc, =init_irq                    @初始化中斷
ret_initirq:
    msr cpsr_c, #0x5f               @設置I-bit=0,開IRQ中斷
    ldr lr, =halt_loop                  @設置返回地址
    ldr pc, =main                        @調用main函數
halt_loop:
    b   halt_loop
★main.c文件實現實現串口選擇ADC和TouchScreen操作,主要代碼:
#include <stdio.h>
#include <serial.h>
#include <adc_ts.h>
int main()
{
    char c;   
    init_uart0();  //波特率115200,8N1(8個數據位,無校驗位,1個停止位)
    while (1)
    {
        printf("/r/n~~~~~~ Test ADC and Touch Screem ~~~~~~/r/n");
        printf("[A] Test ADC/n/r");
        printf("[T] Test Touch Screem/n/r");
        printf("Enter your selection: ");
        c = getc();
        putc(c);
        switch (c)
        {
            case 'a':
            case 'A':
            {          
                Test_Adc();//操作ADC
                break;
            }
            case 't':
            case 'T':
            {
                Test_Ts();//操作TouchScreen
                break;
    ...
}
★adc_ts.c ADC和觸摸屏的測試函數,主要代碼:
...
/*
* 使用查詢方式讀取A/D轉換值。
* 輸入參數ch: 模擬信號通道,取值爲0~7
*/      
static int ReadAdc(int ch)
{
    //選擇模擬通道,使能預分頻功能,設置A/D轉換器的時鐘 = PCLK/(49+1)
    ADCCON = PRESCALE_EN | PRSCVL(49) | ADC_INPUT(ch);
    //清除位[2],設爲普通轉換模式
    ADCTSC &= ~(1<<2);
    //設置位[0]爲1,啓動A/D轉換
    ADCCON |= ADC_START;
    //當A/D轉換真正開始時,位[0]會自動清0
    while (ADCCON & ADC_START);
    //檢測位[15],當它爲1時表示轉換結束
    while (!(ADCCON & ADC_ENDCVT));
    //讀取數據   
    return (ADCDAT0 & 0x3ff);
}
/*
* 測試ADC。通過A/D轉換,測量可變電阻器的電壓值
*/      
void Test_Adc(void)
{
    int vol0, vol1;
    printf("Measuring the voltage of AIN0 and AIN1, press any key to exit/n/r");
    while (!awaitkey(0))    // 串口無輸入,則不斷測試
    {
        vol0 = (ReadAdc(0)*3)/1024;  // 計算電壓值
        vol1 = (ReadAdc(1)*3)/1024;  // 計算電壓值
        printf("AIN0 = %d    AIN1 = %d/r", vol0,vol1);
    }
    printf("/n");
}
static void Isr_Tc(void)
{
    if (ADCDAT0 & 0x8000)  //ADCDAT0[15]爲1表示觸摸屏被鬆開
    {
        printf("/r/nStylus Up!!/n/r");
        //wait_down_int();  //進入"等待中斷模式",等待觸摸屏被按下
    }
    else
    {
        printf("/r/nStylus Down: ");
        mode_auto_xy();  //進入自動(連續)X/Y軸座標轉換模式   
        ADCCON |= ADC_START;  //設置位[0]爲1,啓動A/D轉換
        }
    // 清INT_TC中斷
    ...
   }  
static void Isr_Adc(void)
{
    printf("xdata = %4d, ydata = %4d/r/n",(int)(ADCDAT0 & 0x3ff),(int)(ADCDAT1 & 0x3ff));
    //wait_down_int();  //進入"等待中斷模式",等待觸摸屏被鬆開
    //清INT_ADC中斷
    ...
}
void AdcTsIntHandle(void)
{
    if (SUBSRCPND & BIT_SUB_TC)
        Isr_Tc();
    if (SUBSRCPND & BIT_SUB_ADC)
        Isr_Adc();
}
/*
*測試觸摸屏,打印觸點座標
*/      
void Test_Ts(void)
{
    isr_handle_array[ISR_ADC_OFT] = AdcTsIntHandle;    // 設置ADC中斷服務程序
    INTMSK &= ~BIT_ADC;  //開啓ADC總中斷
    INTSUBMSK &= ~BIT_SUB_TC;  //開啓INT_TC中斷,即觸摸屏被按下或鬆開時產生中斷
    INTSUBMSK &= ~BIT_SUB_ADC;  //開啓INT_ADC中斷,即A/D轉換結束時產生中斷
    //使能預分頻功能,設置A/D轉換器的時鐘 = PCLK/(49+1)
    ADCCON = PRESCALE_EN | PRSCVL(49);
    /*
     *採樣延時時間 = (1/3.6864M)*50000 = 13.56ms
     *即按下觸摸屏後,再過13.56ms才採樣
     */
    ADCDLY = 50000;
    wait_down_int();/*進入“等待中斷模式”,等待觸摸屏被按下*/
    printf("Touch the screem to test, press any key to exit!/n/r");
    getc();
    // 屏蔽ADC中斷
    ...
}

 

本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/ivan240/archive/2011/04/21/6338266.aspx

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章