ADC&touchscream

我所用的是电阻屏,因为电阻屏本身的缺点,如不能支持多点触控,所以如今电阻屏使用得非常少,主要是用电容屏;但是从开发的角度来讲,两种区别不是很大。
简单介绍一下电阻屏的硬件原理
在这里插入图片描述
(图来自网络)
在上一篇博客中,提到了在A/D转换中的滑动电阻,在这里就是很好的应用
我们的触摸屏实际上是两层,上层的前后两端连接着x的正负极,下层的前后两端连接着y的正负极。
当触摸屏上的某一点被按下时,可以参照上图的图1,被按下的那一点就相当于导通了,然后分两次,分别测量x的正负极和y的正负极电压,我们就可以根据电压的大小而获得x/y坐标。从电阻屏原理上也可以很好理解为什么不能支持多点触控,因为当两个点同时被按下时,就相当于有可变电阻上有两个地方导通了,我们只能测量两端的电压,根据基本的电路原理,电流显然只会从靠近阴极端的导通点流过,所以只能获得一个点的坐标,当然如果在不同象限里可能还会有不同的情况
电容屏的硬件原理也简单介绍一下
在这里插入图片描述
(图来自网络)
利用人体电流感应现象,在手指和屏幕之间形成一个电容,手指触摸时吸走一个微小电流,这个电流会导致触摸板上4个电极上发生电流流动,控制器通过计算这4个电流的比例就能算。

如何获得触摸屏上的数据
1、按下触摸屏会产生一个触摸中断
2、我们在触摸中断处理程序中启动ADC
3、ADC完成后获得了坐标数据,产生中断(这个和触摸屏中断是两回事)
4、在中断处理例程中,读取ADC所获得的坐标数据
5、启动定时器中断
6、产生定时器中断后,在中断处理例程中,判断当前触摸屏是否仍然被按下
7、如果触摸屏依然被按下,进入步骤1
8、如果触摸屏没有被按下,做收尾工作,结束流程

寄存器的配置
首先看下ADC&触摸屏相关的寄存器(上一篇讲过ADC寄存器,这一篇主要将触摸屏寄存器)
ADC CONTROL REGISTER (ADCCON)
READ_ START A/D conversion start by read 这一位使能了才可以读
ENABLE_START A/D conversion starts by enable 这一位相当于是ADC的开关位

ADC TOUCH SCREEN CONTROL REGISTER (ADCTSC)
这个寄存器是设置触摸屏的关键寄存器,我们根据读不同的数据,来控制相关的位开或关
还有两个数据寄存器,分别是用来存储x和y的坐标数据

现在分析与中断相关的寄存器

在芯片手册中断控制器章节中,可以在INTERRUPT SOURCES看到 INT_ADC的介绍是ADC EOC and Touch interrupt (INT_ADC_S/INT_TC) ,说明ADC和触摸屏是共用中断的
在“ARM异常和中断”的博客中,我已经详细介绍了中断控制器的各类寄存器,只需要设置 SRCPND, INTMOD(不需要设置), INTMSK, PRIORITY(在这里也不需要设置), INTPND 对应的31位。
然而上面所讲的INT_ADC_S/INT_TC共用中断号,所以我们必须要将两种区分开来
可以借助SUBSRCPND / INTSUBMSK进行区分,其实这是和中断源相关
INT_ADC_S 在第10位,INT_TC 在第9位
ADC和TC可以通过SUBSRCPND寄存器来区分,然后可以通过INTSUBMSK对其进行屏蔽或使能,最后将两个中断进行合并,发送给SRCPND寄存器,可以参照如下的OVERVIEW,这两种中断就是对应着Request sources ( with sub-register )的处理方法
在这里插入图片描述
根据刚才对寄存器的分析,重新优化一下上面的编程流程
如何获得触摸屏上的数据
1、初始化ADC/TS接口寄存器
2、将TS设置为等待中断模式
3、设置中断(设置INTSUBMSK使能INT_ADC_S /INT_TS中断,设置INTMSK使能INT_ADC
4、按下触摸屏后,首先会触发INT_TS中断,中断处理程序:进入自动采集模式,会自动转换X,Y坐标(需要我们设置TS相关的寄存器),启动ADC(需要我们使能ADC)
5、当坐标转换完成后(可以读相关寄存器的状态位),就会产生ADC中断,中断服务例程中读取ADC数据寄存器,获得数据,然后再次进入等待模式
6、考虑到可能会存在长按触摸屏(比如滑动触摸屏),需要设置一个定时器中断,当定时器中断发生时,中断处理例程:检查当前触摸屏是否有被按下(检查SUBSRCPND寄存器的INT_TS位是否被设置为1),如果有被按下,那么进入步骤4,如果没有,则退出。

测试程序

#include "../s3c2440_soc.h"

#define ADC_INT_BIT (10)
#define TC_INT_BIT  (9)

#define INT_ADC_TC   (31)


/* ADCTSC's bits */
#define WAIT_PEN_DOWN    (0<<8)
#define WAIT_PEN_UP      (1<<8)

#define YM_ENABLE        (1<<7)
#define YM_DISABLE       (0<<7)

#define YP_ENABLE        (0<<6)
#define YP_DISABLE       (1<<6)

#define XM_ENABLE        (1<<5)
#define XM_DISABLE       (0<<5)

#define XP_ENABLE        (0<<4)
#define XP_DISABLE       (1<<4)

#define PULLUP_ENABLE    (0<<3)
#define PULLUP_DISABLE   (1<<3)

#define AUTO_PST         (1<<2)

#define WAIT_INT_MODE    (3)
#define NO_OPR_MODE      (0)

void enter_wait_pen_down_mode(void)
{
	ADCTSC = WAIT_PEN_DOWN | PULLUP_ENABLE | YM_ENABLE | YP_DISABLE | XP_DISABLE | XM_DISABLE | WAIT_INT_MODE;
}

void enter_wait_pen_up_mode(void)
{
	ADCTSC = WAIT_PEN_UP | PULLUP_ENABLE | YM_ENABLE | YP_DISABLE | XP_DISABLE | XM_DISABLE | WAIT_INT_MODE;
}

void Isr_Tc(void)
{
	printf("ADCUPDN = 0x%x, ADCDAT0 = 0x%x, ADCDAT1 = 0x%x, ADCTSC = 0x%x\n\r", ADCUPDN, ADCDAT0, ADCDAT1, ADCTSC);
	
	if (ADCDAT0 & (1<<15))
	{
		printf("pen up\n\r");
		enter_wait_pen_down_mode();
	}
	else	
	{
		printf("pen down\n\r");

		/* 进入"等待触摸笔松开的模式" */
		enter_wait_pen_up_mode();
	}
}

void AdcTsIntHandle(int irq)
{
	if (SUBSRCPND & (1<<TC_INT_BIT))  /* 如果是触摸屏中断 */
		Isr_Tc();

//	if (SUBSRCPND & (1<<ADC_INT_BIT))  /* ADC中断 */
//		Isr_Adc();
	SUBSRCPND = (1<<TC_INT_BIT) | (1<<ADC_INT_BIT);
}

void adc_ts_int_init(void)
{
	SUBSRCPND = (1<<TC_INT_BIT) | (1<<ADC_INT_BIT);

	/* 注册中断处理函数 */
	register_irq(31, AdcTsIntHandle);	

	/* 使能中断 */
	INTSUBMSK &= ~((1<<ADC_INT_BIT) | (1<<TC_INT_BIT));
	//INTMSK    &= ~(1<<INT_ADC_TC);
}


void adc_ts_reg_init(void)
{
	/* [15] : ECFLG,  1 = End of A/D conversion
	 * [14] : PRSCEN, 1 = A/D converter prescaler enable
	 * [13:6]: PRSCVL, adc clk = PCLK / (PRSCVL + 1)
	 * [5:3] : SEL_MUX, 000 = AIN 0
	 * [2]   : STDBM
	 * [0]   : 1 = A/D conversion starts and this bit is cleared after the startup.
	 */
	ADCCON = (1<<14) | (49<<6) | (0<<3);

	ADCDLY = 0xff;	
}


void touchscreen_init(void)
{
	/* 设置触摸屏接口:寄存器 */
	adc_ts_reg_init();

	printf("ADCUPDN = 0x%x, SUBSRCPND = 0x%x, SRCPND = 0x%x\n\r", ADCUPDN, SUBSRCPND, SRCPND);

	/* 设置中断 */
	adc_ts_int_init();

	/* 让触摸屏控制器进入"等待中断模式" */
	enter_wait_pen_down_mode();
}

这个测试程序主要是测试在按下触摸屏后,产生的触摸屏中断是否可以检测到
当我们检测到触摸屏中断后,在中断处理例程中,启动ADC(ADC在这个程序中已经使能了),从数据寄存器中获得数据,当读取成功后,触发ADC中断,然后进入ADC中断处理例程中,读取x, y的坐标,因为是测试程序,我们把坐标打印出来
程序做如下改动

void enter_auto_measure_mode(void)
{
	ADCTSC = AUTO_PST | NO_OPR_MODE;
}

void Isr_Tc(void)
{
	//printf("ADCUPDN = 0x%x, ADCDAT0 = 0x%x, ADCDAT1 = 0x%x, ADCTSC = 0x%x\n\r", ADCUPDN, ADCDAT0, ADCDAT1, ADCTSC);
	
	if (ADCDAT0 & (1<<15))
	{
		//printf("pen up\n\r");
		enter_wait_pen_down_mode();
	}
	else	
	{
		//printf("pen down\n\r");

		/* 进入"自动测量"模式 */
		enter_auto_measure_mode();

		/* 启动ADC */
		ADCCON |= (1<<0);
	}
}


void Isr_Adc(void)
{
	int x = ADCDAT0;
	int y = ADCDAT1;

	if (!(x & (1<<15))) /* 如果仍然按下才打印 */
	{
		x &= 0x3ff;
		y &= 0x3ff;
		
		printf("x = %08d, y = %08d\n\r", x, y);
	}

	enter_wait_pen_up_mode();
}

void AdcTsIntHandle(int irq)
{
	if (SUBSRCPND & (1<<TC_INT_BIT))  /* 如果是触摸屏中断 */
		Isr_Tc();

	if (SUBSRCPND & (1<<ADC_INT_BIT))  /* ADC中断 */
		Isr_Adc();
	SUBSRCPND = (1<<TC_INT_BIT) | (1<<ADC_INT_BIT);
}
void adc_ts_reg_init(void)
{
	/* [15] : ECFLG,  1 = End of A/D conversion
	 * [14] : PRSCEN, 1 = A/D converter prescaler enable
	 * [13:6]: PRSCVL, adc clk = PCLK / (PRSCVL + 1)
	 * [5:3] : SEL_MUX, 000 = AIN 0
	 * [2]   : STDBM
	 * [0]   : 1 = A/D conversion starts and this bit is cleared after the startup.
	 */
	ADCCON = (1<<14) | (49<<6) | (0<<3);

	/*  按下触摸屏, 延时一会再发出TC中断
	 *  延时时间 = ADCDLY * 晶振周期 = ADCDLY * 1 / 12000000 = 5ms
	 */
	ADCDLY = 60000;	
}

简单一下这个程序
可以从init函数出发
首先我们要启动TS和ADC,需要设定MUX从哪一路接受到信号(正是这个设置,配合硬件将TS和ADC结合起来),设置时钟频率(这是配置每个外设硬件都需要做的事),所以需要使能分频
然后开始中断处理:先注册中断处理函数,然后使能中断,由于在注册中断的底层程序中,我们已经对常规的中断控制寄存器使能了,所以在这里只需要使能INTSUBMASK。在中断处理程序中,我们要先判断传进来的中断是触摸屏中断还是ADC终端,如何判断,根据SUBSRCPND进行判断,判断其是TS对应的位还是ADC对应的位被置为1。现在问题来了,为什么要将这个TS中断处理程序和ADC中断处理程序都放在一个函数中呢?因为它们是共用一个中断号。
由于我们是先按下触摸屏,触发了TS中断,在TS中断程序中,我们需要进一步判断到底因为触摸屏被按下触发的TS中断还是因为up触发TS中断,如果是up,需要设置管脚,让其进行等待按下模式,如果是按下,那么就设置ADCTSC进入自动测量模式,然后设置ADCCON,启动ADC,当数据测量完成后,会产生ADC中断,然后我们在ADC中断服务例程中读取并打印坐标数据。
然而在我们测试的时候出现了两个问题
首先是串口打印坐标有问题
我发现串口打印坐标一直不停地输出乱码,我的第一反应是,没有清中断,反复检查后发现,已经清中断,然而检查一下串口输出,发现X坐标输出正常8位,Y坐标输出很多位。所以可能是串口输出函数有问题
问题二:输出坐标和实际坐标不一致,发现是实际坐标轴设置有问题

调试完程序后,坐标可以显示,但是我们又发现了一个问题,当我们长按屏幕时或者滑动屏幕时,发现触摸屏只有一次显示,所以在此之前,我们只能对触摸屏进行点操作,不能进行线操作。
这是为什么呢?当我们按下屏幕时,触发TS中断后,在中断处理例程中,首先会进入等待测量模式,在这种模式下,可能无法接收到TS中断。
为什么要这样做呢?如果不这样做的话,可以想象,当我们按下屏幕时,可能只是按一下,结果变成了双击甚至N击。
需要引入定时器,通过定时器产生中断来检查SUBSRCPND寄存器,当前的屏幕是否为按下的状态;
之前的博客中有写过定时器中断,但是当时的中断处理例程是跑马灯,需要改进,考虑到以后可能会其他程序需要用到定时器中断,所以我们再次用上注册的思想
改进timer.c的代码如下

#include "s3c2440_soc.h"

#define TIMER_NUM 32

typedef void(*timer_func)(void);

typedef struct timer_desc {
	char *name;
	timer_func 	fp;
}timer_desc, *p_timer_desc;

timer_desc timer_array[TIMER_NUM];

int register_timer(char *name, timer_func fp)
{
	int i;

	for(i = 0; i < TIMER_NUM; i++)
	{
		if(!timer_array[i].name) //timer_array[i]是一个结构体,一个结构体怎么会存在NULL呢
		{
			timer_array[i].name = name;
			timer_array[i].fp = fp;
		}
		return 0;
	}
	
//	INTMSK &= ~(1<< );

	return -1;
}

int unregister_timer(char *name)
{
	int i;

	for(i = 0; i < TIMER_NUM; i++)
	{
		if(!strcmp(timer_array[i].name,name)) //字符比较不可以直接用 ==,必须要用strcmp
		{
			timer_array[i].name = NULL;
			timer_array[i].fp = NULL;
		}

		return 0;
	}

	return -1;
}


void timer_ts_adc_irq(void)
{
	/*检测寄存器,是否仍然被按下*/
}

/*
*	本来我想用name作为如下函数的传参,用name来遍历数组,让这个func只执行name匹配到的	*	函数,显然是行不通的,因为当定时器产生中断后,就会执行timer_irq函数,但是我们无法
*	在这之前把name找到
*/
void timer_irq(void)
{
	int i;
	for (i = 0; i < TIMER_NUM; i++)
	{
		if (timer_array[i].fp)
		{
			timer_array[i].fp();
		}
	}	
}
void timer_init(void)
{
	/* 设置TIMER0的时钟 */
	/* Timer clk = PCLK / {prescaler value+1} / {divider value} 
	             = 50000000/(99+1)/16
	             = 31250
	 */
	TCFG0 = 99;  /* Prescaler 0 = 99, 用于timer0,1 */
	TCFG1 &= ~0xf;
	TCFG1 |= 3;  /* MUX0 : 1/16 */

	/* 设置TIMER0的初值 */
	TCNTB0 = 15625;  /* 0.5s中断一次 */

	/* 加载初值, 启动timer0 */
	TCON |= (1<<1);   /* Update from TCNTB0 & TCMPB0 */

	/* 设置为自动加载并启动 */
	TCON &= ~(1<<1);
	TCON |= (1<<0) | (1<<3);  /* bit0: start, bit3: auto reload */
	
	/* 设置中断 */
	register_irq(10, timer_irq);

}

增加了定时器中断文件后,我们用定时器中断来对TS/ADS进行优化
我们在ts_adc初始化函数中增加定时器的注册,在定时器中断中所做的内容和TS中断处理例程基本相同,因为当定时器产生中断后,可以在某种程度上视作是TS中断发生了。
代码如下

void touchscreen_timer_irq(void)
{
	/*检测当前屏幕是否被按下的状态,如果是,则进入自动测量模式,同时启动ADC*/

	if (ADCDAT0 & (1<<15)) /*pen up*/
	{
		enter_wait_pen_down_mode();
		return;
	}	
	else /*pen down*/
	{
		enter_auto_measure_mode();
		
		/* 启动ADC */
		ADCCON |= (1<<0);
	}
}


void touchscreen_init(void)
{
	/* 设置触摸屏接口:寄存器 */
	adc_ts_reg_init();

	printf("ADCUPDN = 0x%x, SUBSRCPND = 0x%x, SRCPND = 0x%x\n\r", ADCUPDN, SUBSRCPND, SRCPND);

	/* 设置中断 */
	adc_ts_int_init();

	/*注册定时器中断*/
	register_timer("touchscreen", touchscreen_timer_irq);

	/* 让触摸屏控制器进入"等待中断模式" */
	enter_wait_pen_down_mode();
}

我从屏幕的一端水平划到另外一端时,如下是串口输出结果

x = 00000526, y = 00000523
x = 00000525, y = 00000575
x = 00000015, y = 00001011
x = 00000524, y = 00000630
x = 00000525, y = 00000670
x = 00000524, y = 00000669
x = 00000528, y = 00000680
x = 00000526, y = 00000698
x = 00000522, y = 00000929
x = 00000522, y = 00000920
x = 00000521, y = 00000943
x = 00000522, y = 00000927
x = 00000517, y = 00001012

我水平滑动,即使忽略掉硬件本身的X/Y轴反转的问题,结构本应该是x坐标变化很小,而Y轴的坐标线性增加。然而结果并非如此,同时我设置的定时器中断是每10ms触发一次,显然打印的结果偏少。
考虑到触摸屏中断和定时器中断都可以启动ADC并打印坐标数据,所以我在这两个程序中增加打印调试信息。按理来说,只有第一组数据是由于触摸屏中断 产生的,此后的数据都应该是定时器中断产生的,然而从如下的串口打印信息来看,并不是这样

ISR_TC
x = 00000562, y = 00000307
timer_irq
x = 00000590, y = 00000309
timer_irq
x = 00000643, y = 00000313
ISR_TC
x = 00000781, y = 00000347
timer_irq
x = 00000781, y = 00000342
timer_irq
x = 00000786, y = 00000344
timer_irq
x = 00000795, y = 00000376
timer_irq
x = 00000811, y = 00000342
timer_irq
x = 00000814, y = 00000338
timer_irq
x = 00000810, y = 00000337
timer_irq
x = 00000793, y = 00000355

所以我们需要考虑建立一个类似于互斥锁的机制,由于这是裸板,可以考虑用全局变量来代替互斥锁
代码优化如下

static void ts_timer_enable(void)
{
	g_ts_timer_enable = 1;
}

static void ts_timer_disble(void)
{
	g_ts_timer_enable = 0;
}


static int get_status_of_ts_timer(void)
{
	return g_ts_timer_enable;
}

/* 
 * 每10ms该函数被调用一次
 */
void touchscreen_timer_irq(void)
{
	/*检测当前屏幕是否被按下的状态,如果是,则进入自动测量模式,同时启动ADC*/

	if(get_status_of_ts_timer() == 0)
		return;

	if (ADCDAT0 & (1<<15)) /*pen up*/
	{
		ts_timer_disble();
		enter_wait_pen_down_mode();
		return;
	}	
	else /*pen down*/
	{
		enter_auto_measure_mode();
		
		printf("timer_irq\n\r");
		/* 启动ADC */
		ADCCON |= (1<<0);
	}
}



void Isr_Tc(void)
{
	//printf("ADCUPDN = 0x%x, ADCDAT0 = 0x%x, ADCDAT1 = 0x%x, ADCTSC = 0x%x\n\r", ADCUPDN, ADCDAT0, ADCDAT1, ADCTSC);
	
	if (ADCDAT0 & (1<<15))
	{
		//printf("pen up\n\r");
		enter_wait_pen_down_mode();
	}
	else	
	{
		//printf("pen down\n\r");

		/* 进入"自动测量"模式 */
		enter_auto_measure_mode();

		printf("ISR_TC\n\r");

		/* 启动ADC */
		ADCCON |= (1<<0);
	}
}


void Isr_Adc(void)
{
	int x = ADCDAT0;
	int y = ADCDAT1;

	if (!(x & (1<<15))) /* 如果仍然按下才打印 */
	{
		x &= 0x3ff;
		y &= 0x3ff;
		
		printf("x = %08d, y = %08d\n\r", x, y);

		ts_timer_enable();
	}
	else
	{
		ts_timer_disble();
		enter_wait_pen_down_mode();
	}
	enter_wait_pen_up_mode();
}

上面获得的坐标只是从ADC的数据寄存器中读出的电压值,把电压值转换为坐标值的原理非常简单,就是安装等比例的方式就可以了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值