天天看点

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)组成。其中设备驱动层提供对硬件各寄存器的读写访问和将底层硬件对用户输入访问的响应转换为标准的输入事件,再通过核心层提交给事件处理层;而核心层对下提供了设备驱动层的编程接口,对上又提供了事件处理层的编程接口;而事件处理层就为我们用户空间的应用程序提供了统一访问设备的接口和驱动层提交来的事件处理。所以这使得我们输入设备的驱动部分不在用关心对设备文件的操作,而是要关心对各硬件寄存器的操作和提交的输入事件。下面用图形来描述一下这三者的关系吧!

S3C2440上触摸屏驱动实例开发讲解(转)

另外,又找了另一幅图来说明Linux输入子系统的结构,可能更加形象容易理解。如下:

S3C2440上触摸屏驱动实例开发讲解(转)

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>

static struct clk *adc_clk; static void __iomem *adc_base; static struct input_dev *ts_dev; #define DEVICE_NAME    "my2440_TouchScreen" #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_clk = clk_get(NULL, "adc");     if(!adc_clk)     {                  printk(KERN_ERR "falied to find adc clock source/n");         return -ENOENT;     }          clk_enable(adc_clk);          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_initialize();          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;     }          ts_dev = input_allocate_device();          ts_dev->evbit[0] = BIT(EV_SYN) | BIT(EV_KEY) | BIT(EV_ABS);               ts_dev->keybit[BITS_TO_LONGS(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);          ts_dev->name          = DEVICE_NAME;        ts_dev->id.bustype    = BUS_RS232;              ts_dev->id.vendor     = 0xDEAD;             ts_dev->id.product    = 0xBEEF;             ts_dev->id.version    = 0x0101;                  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; } static void adc_initialize(void) {          writel(S3C2410_ADCCON_PRSCEN | S3C2410_ADCCON_PRSCVL(0xFF), adc_base + S3C2410_ADCCON);          writel(0xffff, adc_base + S3C2410_ADCDLY);          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、接下来要做的是,在两个中断服务程序中实现触摸屏状态和坐标的转换。先看代码,如下:

extern struct semaphore ADC_LOCK; static int OwnADC = 0; static long xp; static long yp; static int count; #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) {          unsigned long data0;     unsigned long data1;          int updown;          if (down_trylock(&ADC_LOCK) == 0)     {                  OwnADC = 1;                  data0 = readl(adc_base + S3C2410_ADCDAT0);         data1 = readl(adc_base + S3C2410_ADCDAT1);                  updown = (!(data0 & S3C2410_ADCDAT0_UPDOWN)) && (!(data1 & S3C2410_ADCDAT0_UPDOWN));                  if (updown)         {                          touch_timer_fire(0);         }         else         {                          OwnADC = 0;             up(&ADC_LOCK);         }     }     return IRQ_HANDLED; } static void touch_timer_fire(unsigned long data) {            unsigned long data0;       unsigned long data1;          int updown;            data0 = readl(adc_base + S3C2410_ADCDAT0);     data1 = readl(adc_base + S3C2410_ADCDAT1);           updown = (!(data0 & S3C2410_ADCDAT0_UPDOWN)) && (!(data1 & S3C2410_ADCDAT0_UPDOWN));           if (updown)     {                   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                           input_report_abs(ts_dev, ABS_X, xp);              input_report_abs(ts_dev, ABS_Y, yp);                          input_report_abs(ts_dev, ABS_PRESSURE, 1);                           input_report_key(ts_dev, BTN_TOUCH, 1);                           input_sync(ts_dev);          }                   xp = 0;          yp = 0;          count = 0;                   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     {                   count = 0;                   input_report_key(ts_dev, BTN_TOUCH, 0);                   input_report_abs(ts_dev, ABS_PRESSURE, 0);                   input_sync(ts_dev);                   writel(WAIT4INT(0), adc_base + S3C2410_ADCTSC);                  if (OwnADC)         {             OwnADC = 0;             up(&ADC_LOCK);         }      } } static struct timer_list touch_timer = TIMER_INITIALIZER(touch_timer_fire, 0, 0); static irqreturn_t adc_irq(int irq, void *dev_id) {          unsigned long data0;     unsigned long data1;     if(OwnADC)     {                  data0 = readl(adc_base + S3C2410_ADCDAT0);         data1 = readl(adc_base + S3C2410_ADCDAT1);                  xp += data0 & S3C2410_ADCDAT0_XPDATA_MASK;         yp += data1 & S3C2410_ADCDAT1_YPDATA_MASK;                  count++;         if (count < (1<<2))         {                          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         {                          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和触摸屏功能框图如下:

S3C2440上触摸屏驱动实例开发讲解(转)

★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和触摸屏的测试函数,主要代码:

...

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);

}

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);

    ADCDLY = 50000;

    wait_down_int();

    printf("Touch the screem to test, press any key to exit!/n/r");

    getc();

    // 屏蔽ADC中断

    ...

}