第018课 ADC和触摸屏

来自百问网嵌入式Linux wiki
Wiki讨论 | 贡献2018年1月29日 (一) 16:57的版本

第001节_ADC硬件原理

模数转换器即A/D转换器,或简称ADC,通常是指一个将模拟信号转变为数字信号的电子元件。
通常的模数转换器是把经过与标准量比较处理后的模拟量转换成以二进制数值表示的离散信号的转换器。
故任何一个模数转换器都需要一个参考模拟量作为转换的标准,比较常见的参考标准为最大的可转换信号大小。而输出的数字量则表示输入信号相对于参考信号的大小。

如图,是把可变电阻上的电压值变换的模拟信号通过ADC转换,输出数字信号。
Chapter18 lesson1 001.jpg

对于数字信号我们需要得到它的几个属性

  • 用多少位来存储这个数据(假设10bit)。
  • 最大值0b111111111
  • 它对应的电压是多少伏(模拟信号输入的最大值是多少)我们就可以根据模拟信号(电压)的最大值,来计算出对应的数值。
  • 采样/转换速度。

对于程序员,我们不关心ADC的内部机制,我们只关心:

  • 怎么启动ADC
  • 启动之后怎么得到数据,

总之:我们都是通过寄存器操作的。
Chapter18 lesson1 002.png

从图1-1-1可以看出ADC有8个多路选择器,显然,以后我们写程序的时候,我们可以8个多路选择之一, 下面是编写程序要做的步骤:

  1. 确定是哪一路信号:设置8:1MUX,选择要测量哪一个引脚,(看原理图选择要测量的引脚)
  2. 设置工作时钟(从工作室中,可以算出转换一次,需要多长时间)
  3. 启动
  4. 读状态,判断ADC转换是否成功。
  5. 读数据

ADC寄存器介绍

1.ADC 控制寄存器(ADCCON)

ADCCON控制寄存器,用于标志转换是否完成,控制是否使能预分频器,输入通道选择,工作模式,ADC是否启动。它的各位含义如下图所示。
Chapter18 lesson1 003.png

2.ADC 启动延时寄存器(ADCDLY)

ADCDLY 启动延时寄存器用于启动或初始化延时寄存器。它的各位含义如下图所示

Chapter18 lesson1 004.png

3.ADC 转换数据寄存器(ADCDAT0)

ADCDAT0转换数据寄存器,本节中只用到该寄存器的前10位(用于保存转换后的结果)。

Chapter18 lesson1 005.png

第002节_ADC编程

编程步骤:

  1. 初始化ADC
  2. 读数据,
  3. 在串口上显示出来。

Chapter18 lesson2 001.png


一.初始化ADC

下面的函数实现对ADC的初始化。

03	void adc_init(void)
04	{
05	/* [15] : ECFLG,  1 = End of A/D conversion
06	 	* [14] : PRSCEN, 1 = A/D converter prescaler enable
07	 	* [13:6]: PRSCVL, adc clk = PCLK / (PRSCVL + 1)
08	 	* [5:3] : SEL_MUX, 000 = AIN 0
09		 * [2]   : STDBM
10	 	* [0]   : 1 = A/D conversion starts and this bit is cleared after the startup.
11	 	*/
12		ADCCON = (1<<14) | (49<<6) | (0<<3);
13
14		ADCDLY = 0xff;	
15	}
  • 第12行:配置ADCCON寄存器,使能A/D 转换器预分频器,设置A/D 转换器预分频值,上拉使能。
  • 第14行:设置ADC 转换启动延时值。


二.读数据

在这个读函数中启动ADC,并且等待ADC转换成功。然后返回数据,

17	int adc_read_ain0(void)
18	{
19			/* 启动ADC */
20		ADCCON |= (1<<0);
21
22		while (!(ADCCON & (1<<15)));  /* 等待ADC结束 */
23
24		return ADCDAT0 & 0x3ff;
25	}
  • 第20行:启动ADC。
  • 第22行:等待A/D转换结束(ADCCON第15位置1),
  • 第24行:返回转换的值。(ADCDAT0寄存器的前10位,是保存转换后的值)。


三.ADC测试

函数代码如下: 函数功能:在串口/LCD上打印ADC转换后的结果。

04	void adc_test(void)
05	{
06		int val;
07		double vol;
08		int m; /* 整数部分 */
09		int n; /* 小数部分 */
10	
11		adc_init();
12
13		while (1)
14		{
15			val = adc_read_ain0();
16			vol = (double)val/1023*3.3;   /* 1023----3.3v */
17			m = (int)vol;   /* 3.01, m = 3 */
18			vol = vol - m;  /* 小数部分: 0.01 */
19			n = vol * 1000;  /* 10 */
20
21			/* 在串口上打印 */
22			printf("vol: %d.%03dv", m, n);  /* 3.010v */
23
24			/* 在LCD上打印 */
25			//fb_print_string();
26			}
27	}
  • 第11行:初始化ADC.
  • 第15行:把ADC转换得到的值赋值给变量val.
  • 第16行:把变量val的值转化为电压值。
  • 第17行:取vol整数部分赋值给变量m。
  • 第18行:取vol的小数部分赋值给vol。


测试

把生成的二进制文件烧录到开发板上,接上SPI模块,旋转可变电阻就可以在串口上看到电压值发生变化。

第003节_电阻触摸屏硬件原

这节课我们来讲电阻触摸屏的硬件原理

假设有一个比较长的电阻,电阻是R 上面接3.3V电压,下面接地
Chapter18 lesson3 001.png

假设整个电阻的阻值是R某一个触电它的阻值是R1 根据欧姆定律

3.3v/R = V/R1
V=3.3 *(R1/R)

假设R1是x坐标 R的长度是l 这个电阻非常的均匀,那么这个电压就等于 3.3V * (x / l) 这个电压和这个触电的x坐标有一个线性关系 我使用ADC把这个电压算出来,就可以间接得到这个触电的x坐标 电阻触摸屏就是使用欧姆定律使用电阻原理作出来的

可以上百度图片搜索触摸屏,就知道了触摸屏的样子,它是一个透明的薄膜,注意 LCD是LCD 触摸屏是触摸屏它是两个设备, 我们只不过是把触摸屏做的和LCD大小一样,粘在LCD上面, 实际上触摸屏是由两层膜组成,他们靠的非常近
上面这层右边引出来,代表xp ,p代表正极
上面这层左边引出来,代表xm, m代表负极
Chapter18 lesson3 002.png

下面这层膜 前面这条边引出来为yp,后面这层边为ym

假设我们手指要点击触摸屏,那么上下就会粘贴在一起,我怎么算出这个 x y点的坐标呢? 测量触电x坐标: xp接3.3v,xm接GND
Chapter18 lesson3 003.png

yp,ym不接电源

2 测yp电压 上下膜连接在一起,我就可以通过yp测量这个触电的电压 这个yp就像探测一样,从前面的原理我们可以知道,当这个触电越靠近左边这个电压越小,越靠近右边电压越大 这个yp的电压就可以认为是这个触电的坐标(x坐标)


类似的我们怎么测量触电y坐标 类似的xp xm不接电源,同样yp接3.3v, ym接GND,这时候电流就从 yp这里流向ym,让后我们就可以测量xp电压 当按下屏幕时,上下两层膜链接在一起,这个xp就像探针一样,这个触电越靠近yp电压值越大,越靠近ym电压值越小
Chapter18 lesson3 004.png

yp接3.3V ym接GND,xp xm不接电源 测量xp电压,就是y坐标
注意 x y坐标都是电压值,不是屏幕上480 * 272 这些值,我们需要把电压值转换为坐标值,这需要经过一些转换

我们测量xp yp可以得到触点的两个方向的电压值,这些电压值和坐标是线性关系 我们现在总结下使用触摸屏的流程
<1>按下触摸屏 按下触摸屏时,对于一个高效的系统,产生中断,这是触摸屏中断
<2>在触摸中断程序中 启动ADC,(获得数据,xy坐标)启动ADC就开始模数转换,不可能瞬间完成,
<3>ADC完成, 产生中断
<4>ADC中断中读取x y坐标,我们来想想,在这个流程里,启动触摸屏的源头是按下触摸屏,那如果长按触摸屏,我按下之后一直不松开 滑动手指呢
那么谁来触发后续的多次ADC转换呢 不可能只启动一次吧, 为了支持 长按 滑动操作,我们需要启用定时器.

<5> 启动定时器
<6> 定时器中断发生,判断触摸屏是否仍被按下,如果按下就循环上述过程
<6.1>在触摸中断程序中 启动ADC,(获得数据,xy坐标)启动ADC就开始模数转换,不可能瞬间完成
<6.2>ADC完成, 产生中断
<6.3>ADC中断中读取x y坐标,)
<7> 松开结束一个流程
这就是整个触摸屏的使用流程

在14章里讲解了触摸屏,他抽象了几张图

平时的时候上下两层膜并不连接,我们按下触摸屏的时候就会产生中断,那么你怎么知道产生中断,肯定是由某个引脚的电平发生变化,平时 Y_ADC/xp是高电平,按下之后Y_ADC就接地了,就是被拉低了,就产生了低电平
Chapter18 lesson3 005.png

产生低电平后就知道触摸屏被按下了,这个时候就需要测量电压值读取x坐标,XP XM通电我就测量YP的电压,这不就是 x 点的坐标
Chapter18 lesson3 006.png

读取Y坐标
YP YM 通电,按下后XP通电,这不就是y点的坐标么
Chapter18 lesson3 007.png

第004节_S3C2440触摸屏接口

回顾上节触摸屏使用原理
Chapter18 lesson4 001.png

在不使用触摸屏的时候,必须要把 S1 S2 S3断开,S4 S5闭合,只有这样当我按下触摸屏,上面的电平才能从高变低,会产生一个中断信号,而当我去读取X坐标的值时
Chapter18 lesson4 002.png

必须让S1 S3闭合,这样电流才可以通过,同时让S2 S4 S5断开,这时候YP这层膜就相当于探针一样去测量电压

当我读取y坐标值
Chapter18 lesson4 003.png

必须让S2 S4闭合,这样电流才可以流 下来,同时S1 S3 S5断开,这个时候XP这层膜就相当于探针一样,我可以来测量这里的电压,从而得到Y坐标的电压值

在测量x y坐标时,这个S5上拉电阻都要断开, 我们需要控制这几个开关,实际上2440就提供了这几个开关的控制方法, 打开2440的芯片手册看触摸屏时怎么操作的, 从440到450总共10页不到, 我们看有一个8:1 MUX的多路选择器,以及XP YP
Chapter18 lesson4 004.png

442页触摸屏接口模式
Chapter18 lesson4 005.png

正常模式,在上节视频中我们有讲解过
x y分离转换模式,
看看我们的X Y坐标原理图,可以单独转换X坐标 单独转换Y坐标
换句话说就是逐个去测量X Y坐标,
他首先会启动X坐标的ADC转换,转换成功后数据会保存在ADCDAT0里,同时会产生一个中断,在这个中断服务程序里,就可以把X坐标读取出来,让后可以启动Y坐标的转换,转换成功后数据会保存在ADCDAT,同时会产生一个中断,进入这个中断把Y坐标读取出来 测量一次会产生2个中断,一个时X坐标中断,一个是Y坐标中断

自动的或连续的X/Y坐标转换模式
也就是说不需要单独控制,不需要单独去读取X坐标Y坐标,可以设置寄存器,让它一次性的测量X坐标测量Y坐标,X坐标保存在ADCDAT0 Y坐标保存在ADCDAT1,最后产生一个中断,也就是读取X/Y坐标只需要产生一次中断

等待中断模式
所谓等待中断模式,就是等待按下或者等待松开
对于下面这幅图,我按下的时候XP从高电平变为低电平,松开时,XP从低电平变为高电平,这就是按下松开都可以检测到

我们要等待按下或者松开时 需要设置rADCTSC =0xd3这个值

Standby Mode静默模式/省电模式(我们不关心这个)

Chapter18 lesson4 006.png

443页编程要点
Chapter18 lesson4 007.png

  • AD转换数据时可以通过中断或者查询模式来得到数据,使用中断模式时,从AD转换开始,到得到数据可能会有些延迟,因为中断服务程序的进入和退出需要一定的时间,(也就是说,如果你对数据转换的速度要求的非常高,就可以使用查询方式),可以查询ADCCON[15]来判断是否转换结束

444页 剩下就是寄存器操作
Chapter18 lesson4 008.png

ECFLG状态位 AD转换是否结束
PRSCEN 使能ADC转换
PRSCVL 设置A/D转换预分频值
SEL_MUX选择输入通道,后面我们使用自动转换XY坐标,所以这里不需要设置
ENABLE_START 启动转换


445页

ADCTSC这个寄存器是重要的

Chapter18 lesson4 009.png

UD_SEN Bit8是用来判断触摸屏是被按下还是被松开

0表明被按下,1表明被松开

YM_SEN Bit7  YM开关使能控制S4

Chapter18 lesson4 001.png

0表示断开 1闭合

YP_SEN Bit6 YP开关 0表示闭合 1 表示断开

  • 寄存器位的含义不同

XM_SEN Bit5 XM开关 0 断开 1 闭合

XP_SEN Bit4 XP开关

0 闭合 1 断开

PULL_UP Bit3 控制S5开关 0 上拉(闭合)

1 断开

AUTO_PST Bit2 自动连续转换X坐标Y坐标

上节视频里我们设置是 0 正常的ADC转换
如果需要连续转换ADC坐标的话,需要设置为1 ,如果需要手动转换ADC坐标的话,需要设置为0

XY_PST Bit[1:0] 对于手动转换X Y坐标我们需要手动设置XY_PST 里面的位,是测量X坐标还是测量Y坐标, 也可以设置这两位等于11 让其等于等待模式, 也就是等待触摸屏被按下或者被松开.

如果设置自动连续转换的话,Bit2 AUTO_PST设置为1 XY_PST设置为00

如果使用手动转换的话设置AUTO_PST为0 XY_PST设置为01 手动转换X坐标模式 或者设置为10 Y坐标转换模式

447页ADCDATA0 ADC数据寄存器
Chapter18 lesson4 0010.png

UPDOWN Bit15 可以读取这一位去判断触摸屏是按下还是松开
AUTO_PST Bit14 自动测量
XY_PST Bit[13:12] 和上面ADCTSC寄存器中 AUTO_PST Bit2   XY_PST Bit[1:0]原理相同
XPDATA Bit[9:0]最低10位用来保存ADC的值

448页'ADCDAT1寄存器 和ADCDAT0功能一样的,只不过保存的数据不同
Chapter18 lesson4 0011.png

这个的低10位是用来保存 Y坐标的值

接下来是ADCUPDN触摸屏按下或者松开检查寄存器
Chapter18 lesson4 0012.png

TST_UP Bit1 触摸屏松开中断产生
TST_DN Bit0 触摸屏按下中断产生

手册看完了,涉及到中断,我们看下这个图
Chapter18 lesson4 001.png

它会涉及两个中断,按下或者松开,触摸笔的状态中断,另外一个启动ADC以后,ADC结束时也会产生一个中断,但是这个手册里没有看到中断的是能寄存器

那我们猜测一下,ADC模块或者触摸屏模块一定会发出中断
首先是ADC或者触摸屏产生中断,通过中断控制器发送中断给CPU
Chapter18 lesson4 0013.png

肯定有寄存器禁止/使能ADC或者触摸屏中断

我们看看中断控制器芯片手册中都需要设置什么
Chapter18 lesson4 0013.1.png

ADC中断源
Chapter18 lesson4 0014.png

ADC结束中断或者触摸屏中断,看来他们合起来用一个中断 既然合并必然还会有一个寄存器来分辨到底是ADC还是触摸屏发生的中断变化


SRCPND寄存器 31位为ADC中断
Chapter18 lesson4 0015.png

Chapter18 lesson4 0016.png

设置Bit[31]

INTMOD寄存器 来决定是普通中断还是快中断模式
Chapter18 lesson4 0017.png

设置Bit[31]
Chapter18 lesson4 0018.png

INTMSK寄存器 用来表示是否屏蔽这个中断
Chapter18 lesson4 0019.png

设置Bit[31]

Chapter18 lesson4 0020.png

优先级我们不需要设置

INTPND
Chapter18 lesson4 0021.png

设置Bit[31]表示中断是否正在处理
Chapter18 lesson4 0022.png

INTOFFSET 设置Bit[31]
Chapter18 lesson4 0023.png

到底是ADC中断还是触摸屏中断,肯定有其他寄存器可以设置


SUBSOURCE PENDING寄存器

INT_ADC_S Bit[10]表示ADC中断
INT_TC Bit[9]表示触摸屏中断

Chapter18 lesson4 0024.png


INTSUBMSK 应该也是同样的位

INT_ADC_S Bit[10]表示ADC中断激活/屏蔽
INT_TC Bit[9]表示触摸屏中断激活/屏蔽

Chapter18 lesson4 0025.png

我们可以通过INTSUBMSK来屏蔽ADC中断或者TouchScreen中断 当然也可以是能某个中断 可以通过SUBSRCPND来分辨到底产生那个中断 INTSUBMSK 和SUBSOURCPND这两个寄存器都会汇集到一起 变成一个叫做INT_ADC的中断来发送给CPU

框图就是这样

Chapter18 lesson4 0026.png

我们怎么写程序? 写出一个框架

1 初始化ADC/TouchScreen接口ADCCON时钟接口
2 一开始触摸屏是没有被按下的,设置TS处于等待中断模式
3 设置中断
INTSUBMSK使能ADC中断和触摸屏中断,还有INTMSK设置这个寄存器使能ANT_ADC让他能够发给CPU
4 按下触摸屏,进入TS中断
	4.1 进入自动采集模式(自动转换XY坐标)
	4.2 启动ADC
5.转换完之后产生ADC中断
	5.1 读数据
	5.2 再次进入 “'等待中断”'模式
	5.3 启动定时器,处理长按或者滑动
6 定时器中断
       6.1 判断是否松开,若松开结束
       6.2 若按下重新执行 4.2启动ADC步骤

第005节_触摸屏编程_按下松开检测

  • 开始触摸屏编程,关于触摸屏编程大概会分为3个小节
  • 第006节_触摸屏编程_ADC中断
  • 第007节_触摸屏编程_定时器程序优化
  • 参考《嵌入式Linux应用开发完全手册》第14章 ADC和触摸屏接口
  • 可以参考下面这张图

Chapter18 lesson5 001.png

看懂这张图的关键点在于 里面有个中断程序 AdcTsIntHandle 它是总的中断,这里面要分辨 if, 如果是ADC中断 那么就调用Isr_adc来处理中段 else if, 如果是触摸屏中断,那么就调用Isr_tc中断, 这些都是总中断具体的中断.

我们看看是怎么做的

  • 一开始设置中断
  • 初始化触摸屏控制器,进入等待中断模式
  • 这个时候如果按下触摸屏就会进入Pen Down中断
  • 就会进入AdcTsIntHandle这个总中断函数
  • 这里面分辨是按下触摸屏
  • 进入自动(连续) X/Y轴坐标转换模式,启动ADC,
  • ADC结束之后会产生一个ADC中断
  • 又再次进入这个AdcTsIntHandle总中断
  • 这里面分辨是ADC中断,这里面调用Isr_Adc
  • 我可以读出这里面的数据,再次设置寄存器
  • 进入等待Pen UP中断模式
  • 松开触摸笔会再次产生一个中断
  • 进入总中断AdcTsIntHandle这里面分辨,原来是松开了触摸笔,再次调用Isr_tc
  • 这里面又会设置进入等待Pen Down中断模式

我们开始写代码,再上一个视频ADC代码上进行修改

002_touchscreen_018_005/adc_touchscreen

我们在adc_touchscreen目录下添加几个文件

touchscreen_test.c
touchscreen.c

我们打开touchscreen.c文件

void touchscreen_init(void)
{
	看看上面流程图
	/*1设置触摸屏接口:也就是寄存器 */

	/*2 设置中断我们需要提供中断处理函数 */

	/*3 让触摸屏控制器进入"等待中断模式" */

}

我们设置中断处理函数
void AdcTsIntHandle(void)
{
}
看一下之前我们是怎么写中断的,看一下interrupt.c文件
void key_eint_irq(int irq)
有个中断号

那么我们也定义个int irq参数
void AdcTsIntHandle(int irq)
我们在这个里面分辨一下
if (SUBSRCPND & (1<<TC_INT_BIT))  /* 如果是触摸屏中断 */
/*调用*/	
	Isr_Tc();

else if //如果是ADC中断
//调用
	Isr_Adc();
我们等会实现这两个函数

我们继续写代码
void touchscreen_init(void)
{
	看看上面流程图

	/*1 设置中断我们需要提供中断处理函数 */
	adc_ts_int_init();

	/*2 设置触摸屏接口:也就是寄存器 */
	adc_ts_reg_init();

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

}
 

//我们先来实现 adc_ts_int_init

void adc_ts_int_init(void)
{
	/*注册中断处理函数*/
//怎么注册看之前的代码
	register_irq(irq, irq_handle);

中断号是多少?
打开芯片手册,找到中断控制器
Chapter18 lesson5 002.png
Chapter18 lesson5 003.png

我们是31号中断

register_irq(31, AdcTsIntHandle);	

怎么使能中断?

我们需要把 INTSUBMISK寄存器的Bit9 Bit10设置为0 宏定义

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


使能中断,清零

INTSUBMSK &= ~((1<<ADC_INT_BIT) | (1<<TC_INT_BIT));

还有INTMSK我们也需要把Bit31清零

#define INT_ADC_TC (31)

Bit31位清零操作

INTMSK    &= ~(1<<INT_ADC_TC);

这句可以不用设置,因为register_irq已经设置

假设产生中断就会进入AdcTsIntHandle函数中 分辨是触摸屏终端还是ADC中断

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

	if  /* ADC中断 */
		Isr_Adc();

}

如何进行分辨
Chapter18 lesson5 004.png

 if (SUBSRCPND & (1<<TC_INT_BIT))  /* 如果是触摸屏中断 */
 	Isr_Tc();
 if (SUBSRCPND & (1<<ADC_INT_BIT))  /* ADC中断 */
 	Isr_Adc();

//我们要引用寄存器地址头文件
#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;
}

读一下寄存器,找到触摸屏的寄存器触摸笔,按下松开状态寄存器
Chapter18 lesson5 001.png

我们可以读它 Bit1表示up Bit0表示down

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

第006节_触摸屏编程_ADC中断

这节课我们加上ADC中断把触点的xy坐标读出来

查看touchscreen.c

写出这个自动测量的函数
void enter_auto_measure_mode(void)
{
//现在是自动测量,我们没有机会分别设置这些开关

设置AUTO_PST =1
XY_PST = 00

	ADCTSC = AUTO_PST | NO_OPR_MODE;

}

//现在是自动测量,我们没有机会分别设置这些开关
Chapter18 lesson6 001.png

进入中断处理函数

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

	if (SUBSRCPND & (1<<ADC_INT_BIT))  /* ADC中断,则会进入Adc中断处理函数 */

		Isr_Adc();
	SUBSRCPND = (1<<TC_INT_BIT) | (1<<ADC_INT_BIT);
}

进入触摸屏中断处理函数

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	
	{
		/* 进入"自动测量"模式 */
		enter_auto_measure_mode();

		/* 启动ADC */

ENABLE_START = 1就可以了
		ADCCON |= (1<<0);
	}
}

/* 启动ADC */

Chapter18 lesson6 002.png

Adc中断处理函数

void Isr_Adc(void)
{
	进入adc中断后,等待触摸笔松开
	int x = ADCDAT0 & 0x3ff;
	int y = ADCDAT1 & 0x3ff;
		
	printf("x = %08d, y = %08d\n\r", x, y);

//等待触摸笔松开模式
	enter_wait_pen_up_mode();
}

烧写 实验发现打印一堆乱码
Chapter18 lesson6 003.png

应该是printf函数出了问题 打开my_printf.c文件,找到printf函数 应该是处理第二个数据的时候,没有设置初始值

/*reference :   int vprintf(const char *format, va_list ap); */
static int my_vprintf(const char *fmt, va_list ap) 
{
	char lead=' ';
	int  maxwidth=0;
	
	 for(; *fmt != '\0'; fmt++)
	 {
			if (*fmt != '%') {
				outc(*fmt);
				continue;
			}
	//碰到 % 就重新处理, 初始值应该重新设置初始值上去
		lead=' ';
		maxwidth=0;
		
		//format : %08d, %8d,%d,%u,%x,%f,%c,%s 
		    fmt++;
		if(*fmt == '0'){
			lead = '0';
			fmt++;	
		}
		
		while(*fmt >= '0' && *fmt <= '9'){
			maxwidth *=10;
			maxwidth += (*fmt - '0');
			fmt++;
		}
		
			switch (*fmt) {
		case 'd': out_num(va_arg(ap, int),          10,lead,maxwidth); break;
		case 'o': out_num(va_arg(ap, unsigned int),  8,lead,maxwidth); break;				
		case 'u': out_num(va_arg(ap, unsigned int), 10,lead,maxwidth); break;
		case 'x': out_num(va_arg(ap, unsigned int), 16,lead,maxwidth); break;
			case 'c': outc(va_arg(ap, int   )); break;		
			case 's': outs(va_arg(ap, char *)); break;		  		
				
			default:  
				outc(*fmt);
				break;
			}
	}
	return 0;
}

重新烧写执行,发现数据变化幅度很大,但至少Adc已经有输出
Chapter18 lesson6 004.png

我们需要解决输出值不线性的问题

到底是触摸屏质量问题,还是Adc转化精度问题, 觉得应该是触摸屏电压不稳定 之前不知道DELAY寄存器是用来干嘛的
Chapter18 lesson6 005.png

等待中断模式时,当触摸笔按下时我们会产生中断,但是可以通过 DELAY来延时产生中断

在前面有一张图
Chapter18 lesson6 006.png

按下触摸笔,延迟A 才可以产生中断,你才可以测量X Y坐标 A = D(晶振的周期) D就是 DELAY就是那个寄存器的值 晶振周期时12M 我们需要设置一下

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中断
	 * 10ms为120000
	 *  延时时间 = ADCDLY * 晶振周期 = ADCDLY * 1 / 12000000 = 5ms
	 */
	ADCDLY = 60000;	
}

再次烧写,发现数据并不规律 我们需要再次改进程序

我们按下触摸屏会产生触摸屏中断,启动自动测量,启动Adc,Adc成功后会进入Adc中断,在函数中打印数据

也许测量过程很长 我们就需要判断

void Isr_Adc(void)
{
	int x = ADCDAT0;
	int y = ADCDAT1;
//松开的话打印也是错误的值,所以如果仍然按下才打印
	if (!(x & (1<<15))) /* 如果仍然按下才打印 */
	{
		x &= 0x3ff;
		y &= 0x3ff;
		//打印10进制
		printf("x = %08d, y = %08d\n\r", x, y);
	}

	enter_wait_pen_up_mode();
}

烧写执行 发现X Y轴输出有问题
Chapter18 lesson6 007.png

厂家把X Y轴搞反了

电路图中

TSYP TSXP接反
TSYM TSXM接反

Chapter18 lesson6 008.png

我们后面使用触摸屏时会使用软件处理这点,不会导致任何问题 

有一个缺点 我们按下触摸屏会输出一个数据,再按下触摸屏又输出一个数据 我长按并没有输出数据,我滑动也没有输出数据 我们需要使用定时器改进这个问题

各种方向的旋转都可以由软件转换
Chapter18 lesson6 009.png

我们需要把触摸屏的坐标TS XY坐标转换成LCD的XY坐标 需要用应用程序做 我们常使用Tslib库来做,这些旋转倒置都没有问题

第007节_触摸屏编程_定时器程序优化

  • 有一个缺点
  • 我们按下触摸屏会输出一个数据,再按下触摸屏又输出一个数据
  • 我长按并没有输出数据,我滑动也没有输出数据
  • 我们需要使用定时器改进这个问题

这个处理流程是怎么样的?

  • 按下期间启动定时器
  • 定时器每过10ms / 20ms就中断一次
  • 在中断函数里测量触电的XY坐标
  • 这样就可以得到连续的数据

打开定时器Timer.c

#include "s3c2440_soc.h"

//定义一个宏 TIMER_NUM = 32
#define TIMER_NUM  32
#define NULL  ((void *)0)

typedef void(*timer_func)(void);

//定义一个结构体 ,既存放有函数指针又存放有数据
typedef struct timer_desc {
	char *name;
	timer_func fp;
}timer_desc, *p_timer_desc;

//我们需要往这个结构体数组里面添加函数,注册Timer函数
timer_desc timer_array[TIMER_NUM];


//注册Timer函数
int register_timer(char *name, timer_func fp)
{
	int i;
//搜索这个数组,如果fp等于0的话,就表示没有占用这个数组项,我就把它填充进去

	for (i = 0; i < TIMER_NUM; i++)
	{
		if (!timer_array[i].fp)
		{	
			 [i].name = name;
			timer_array[i].fp   = fp;
			return 0;
//注册成功
		}
	}
//否则,表示已经满了,注册失败
	return -1;
}


我们不需要使用Timer定时器的时候unregister_timer函数, 考虑到我们需要从数组里面把这个Timer去掉,我们怎么找到这个Timer?
传入一个函数指针,以后卸载使用名字找到对应的项.

void unregister_timer(char *name)
{
//对于unregister_timer就反过来操作,遍历每一项
	int i;
	for (i = 0; i < TIMER_NUM; i++)
	{
//如果这个数组项里面的名字等于我传进来我名字
		if (!strcmp(timer_array[i].name, name))
		{
//也就表示我找到了这两项,设置成NULL
			timer_array[i].name = NULL;
			timer_array[i].fp   = NULL;
			return 0;
		}
	}
//否则return -1;找不到选择的项
	return -1;
}

应该让其从某个数组里面把需要定时器处理的函数依次执行,这样做,我们以后添加定时器处理函数时就不需要修改Timer.c

void timer_irq(void)
{
	int i;
	for (i = 0; i < TIMER_NUM; i++)
	{
//判断指针是否为空,如果不是空的话就继续执行timer_array[i].fp();这个函数
		if (timer_array[i].fp)
		{
			timer_array[i].fp();
		}
	}	
}

如果想继续点灯的话,需要单独注册led_timer_irq 在led.c文件里注册led_timer_irq 把这个函数放在led_timer_irq函数下面,防止编译错误,每10ms改函数被调用一次.

int led_init(void)
{
	/* 设置GPFCON让GPF4/5/6配置为输出引脚 */
	GPFCON &= ~((3<<8) | (3<<10) | (3<<12));
	GPFCON |=  ((1<<8) | (1<<10) | (1<<12));
//led是名字,led_timer_irq是函数指针
	register_timer("led", led_timer_irq);
}

/* 每10ms该函数被调用一次 
 * 每500ms操作一下LED实现计数
 */
void led_timer_irq(void)
{
	/* 点灯计数 */
	static int timer_num = 0;
	static int cnt = 0;
	int tmp;

	timer_num++;
	if (timer_num < 50)
		return;
	timer_num = 0;
//操作led
	cnt++;

	tmp = ~cnt;
	tmp &= 7;
	GPFDAT &= ~(7<<4);
	GPFDAT |= (tmp<<4);
}

修改main.c

int main(void)
{
	led_init();//初始化led
	//interrupt_init();  /* 初始化中断控制器 */
	key_eint_init();   /* 初始化按键, 设为中断源 */
	timer_init();//打开定时器
	
	puts("\n\rg_A = ");
	printHex(g_A);
	puts("\n\r");

	//nor_flash_test();
	lcd_test();

	//adc_test();

	touchscreen_test();

	while (1);
	
	return 0;
}

修改timer.c文件

void timer_init(void)
{
	/* 设置TIMER0的时钟 修改时钟频录让其10ms中断一次 */
	/* Timer clk = PCLK / {prescaler value+1} / {divider value} 
	             = 50000000/(49+1)/16
	             = 62500
	 */
	TCFG0 = 49;  /* Prescaler 0 = 49, 用于timer0,1 */
	TCFG1 &= ~0xf;
	TCFG1 |= 3;  /* MUX0 : 1/16 */

	/* 设置TIMER0的初值 */
	TCNTB0 = 625;  /* 10Ms中断一次 */

	/* 加载初值, 启动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);
}

烧写到nandflash发现无输出,可能是前重定位前的代码超出了4k,所以我们使用Norflash启动,发现可以正常运行

我们修改Makefile把负责重定位代码往前移,其他无关代码往后放,接着看star.S

.text
.global _start

_start:
	b reset          /* vector 0 : reset */
	ldr pc, und_addr /* vector 4 : und */
	ldr pc, swi_addr /* vector 8 : swi */
	b halt			 /* vector 0x0c : prefetch aboot */
	b halt			 /* vector 0x10 : data abort */
	b halt			 /* vector 0x14 : reserved */
	ldr pc, irq_addr /* vector 0x18 : irq */
	b halt			 /* vector 0x1c : fiq */

//我们需要把这些异常往后放
und_addr:
	.word do_und

swi_addr:
	.word do_swi

irq_addr:
	.word do_irq

reset:
	/* 关闭看门狗 */
	ldr r0, =0x53000000
	ldr r1, =0
	str r1, [r0]

	/* 设置MPLL, FCLK : HCLK : PCLK = 400m : 100m : 50m */
	/* LOCKTIME(0x4C000000) = 0xFFFFFFFF */
	ldr r0, =0x4C000000
	ldr r1, =0xFFFFFFFF
	str r1, [r0]

	/* CLKDIVN(0x4C000014) = 0X5, tFCLK:tHCLK:tPCLK = 1:4:8  */
	ldr r0, =0x4C000014
	ldr r1, =0x5
	str r1, [r0]

	/* 设置CPU工作于异步模式 */
	mrc p15,0,r0,c1,c0,0
	orr r0,r0,#0xc0000000   //R1_nF:OR:R1_iA
	mcr p15,0,r0,c1,c0,0

	/* 设置MPLLCON(0x4C000004) = (92<<12)|(1<<4)|(1<<0) 
	 *  m = MDIV+8 = 92+8=100
	 *  p = PDIV+2 = 1+2 = 3
	 *  s = SDIV = 1
	 *  FCLK = 2*m*Fin/(p*2^s) = 2*100*12/(3*2^1)=400M
	 */
	ldr r0, =0x4C000004
	ldr r1, =(92<<12)|(1<<4)|(1<<0)
	str r1, [r0]

	/* 一旦设置PLL, 就会锁定lock time直到PLL输出稳定
	 * 然后CPU工作于新的频率FCLK
	 */

	/* 设置内存: sp 栈 */
	/* 分辨是nor/nand启动
	 * 写0到0地址, 再读出来
	 * 如果得到0, 表示0地址上的内容被修改了, 它对应ram, 这就是nand启动
	 * 否则就是nor启动
	 */
	mov r1, #0
	ldr r0, [r1] /* 读出原来的值备份 */
	str r1, [r1] /* 0->[0] */ 
	ldr r2, [r1] /* r2=[0] */
	cmp r1, r2   /* r1==r2? 如果相等表示是NAND启动 */
	ldr sp, =0x40000000+4096 /* 先假设是nor启动 */
	moveq sp, #4096  /* nand启动 */
	 streq r0, [r1]   /* 恢复原来的值 */

	bl sdram_init
	//bl sdram_init2	 /* 用到有初始值的数组, 不是位置无关码 */

	/* 重定位text, rodata, data段整个程序 */
	bl copy2sdram

	/* 清除BSS段 */
	bl clean_bss

	/* 复位之后, cpu处于svc模式
	 * 现在, 切换到usr模式
	 */
	mrs r0, cpsr         /* 读出cpsr */
	bic r0, r0, #0xf     /* 修改M4-M0为0b10000, 进入usr模式 */
	bic r0, r0, #(1<<7)  /* 清除I位, 使能中断 */
	msr cpsr, r0

	/* 设置 sp_usr */
	ldr sp, =0x33f00000

	ldr pc, =sdram
sdram:
	bl uart0_init

	bl print1
	/* 故意加入一条未定义指令 */
init.c 需要放在前面
nand_init
sdram_init初始化也放前面
sdram:

bl uart0_init

这就跳到sdram了,重定位后就随便操作

修改Makefile 我们把 start.o init.o nand_flash.o放在最前面

objs = start.o init.o nand_flash.o led.o uart.o main.o exception.o interrupt.o timer.o nor_flash.o my_printf.o string_utils.o lib1funcs.o

objs += lcd/font.o
objs += lcd/framebuffer.o
objs += lcd/geometry.o
objs += lcd/lcd.o
objs += lcd/lcd_4.3.o
objs += lcd/lcd_controller.o
objs += lcd/lcd_test.o
objs += lcd/s3c2440_lcd_controller.o
objs += lcd/font_8x16.o

objs += adc_touchscreen/adc.o
objs += adc_touchscreen/adc_test.o

objs += adc_touchscreen/touchscreen.o
objs += adc_touchscreen/touchscreen_test.o


all: $(objs)
	#arm-linux-ld -Ttext 0 -Tdata 0x30000000  start.o led.o uart.o init.o main.o -o sdram.elf
	arm-linux-ld -T sdram.lds $^ libgcc.a -o sdram.elf
	arm-linux-objcopy -O binary -S sdram.elf sdram.bin
	arm-linux-objdump -D sdram.elf > sdram.dis
clean:
	rm -f *.bin $(objs) *.elf *.dis
	
%.o : %.c
	arm-linux-gcc -march=armv4 -c -o $@ $<

%.o : %.S
	arm-linux-gcc -march=armv4 -c -o $@ $<

这节课讲定时器的优化, 下节课讲怎么使用定时器来改进触摸屏

第008节_触摸屏编程_使用定时器支持长按

可以使用定时器把长按或者滑动触摸屏的值读出来,我们按下触摸屏就会产生触摸屏中断,这个时候可以启动ADC,ADC成功后再次产生中段,在这个中断中启动定时器,我们也可以在触摸屏中断里启动定时器.

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


//定义一个全局变量设置timer状态
static volatile int g_ts_timer_enable = 0;


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


进入touchscreen.c adc中断处理函数

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_disable();
		enter_wait_pen_down_mode();
	}

	enter_wait_pen_up_mode();
}

//触摸屏定时器处理函数
/* 每10ms该函数被调用一次 
 */
void touchscreen_timer_irq(void)
{
	/* 如果触摸屏仍被按下, 进入"自动测量模式", 启动ADC */
//如果定时器并没有被使能,则return
	if (get_status_of_ts_timer() == 0)
		return;
//如果松开,则什么事情都不做
	if (ADCDAT0 & (1<<15)) /* 如果松开 */
	{
//设置定时器状态
		ts_timer_disable();
//触摸笔进入等待模式
		enter_wait_pen_down_mode();
		return;
	}

//否则启动测量
	else  /* 按下状态,启动下一次测量 */
	{
		/* 进入"自动测量"模式 */
		enter_auto_measure_mode();

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


static void ts_timer_enable(void)
{

//我们使用定时器时把它的状态设置为1
	g_ts_timer_enable = 1;
}


//有启用定时器就有关闭定时器
static void ts_timer_disable(void)
{

//我们不使用定时器把定时器状态设置为0
	g_ts_timer_enable = 0;
}


//我们如何获取定时器状态?
static int get_status_of_ts_timer(void)
{
//返回定时器状态
	return g_ts_timer_enable;
}

我们回顾一下处理过程,根据流程图分析

  • 首先从main.c函数开始
int main(void)
{
	led_init();
	//interrupt_init();  /* 初始化中断控制器 */
	key_eint_init();   /* 初始化按键, 设为中断源 */
	timer_init();
	
	puts("\n\rg_A = ");
	printHex(g_A);
	puts("\n\r");

	//nor_flash_test();
	lcd_test();

	//adc_test();
       //执行touchscreen_test
	touchscreen_test();

	while (1);
	
	return 0;
}

进入touchscreen_test.c文件执行init初始化程序

void touchscreen_test(void)
{
	touchscreen_init();
}

进入touchscreen.c文件

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


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;	
}
 
//进入adc触摸屏中断处理
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 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);
}


//进入触摸屏处理函数
//触摸屏定时器处理函数
/* 每10ms该函数被调用一次 
 */
void touchscreen_timer_irq(void)
{
	/* 如果触摸屏仍被按下, 进入"自动测量模式", 启动ADC */
//如果定时器并没有被使能,则return
	if (get_status_of_ts_timer() == 0)
		return;
//如果松开,则什么事情都不做
	if (ADCDAT0 & (1<<15)) /* 如果松开 */
	{
//设置定时器状态
		ts_timer_disable();
//触摸笔进入等待模式
		enter_wait_pen_down_mode();
		return;
	}

//否则启动测量
	else  /* 按下状态,启动下一次测量 */
	{
		/* 进入"自动测量"模式 */
		enter_auto_measure_mode();

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

第009节_触摸屏编程_较准原理

我们需要校准触摸屏,所谓校准就是找到一个公式把电压值转换为坐标值

触摸屏和LCD是两个东西,触摸屏覆盖在LCD上. Chapter18 lesson9 001.png

问:得到触电的(x1,y1)怎么换算出LCD的坐标值 (X,Y)?
Chapter18 lesson9 002.png

x=479-0/x2-x1 * (x3-x1) + 0
x=长度的比例
x1 0 原点的触摸屏/LCD坐标

我们只需要确定两个点就可以把lcd坐标确定下来,但是我们可以做的更好
假设由于制作工艺问题,导致触摸屏和LCD坐标并不相同,需要其他公式计算

  • X轴方向

Chapter18 lesson9 003.png

s1'是TS上X轴两个点的距离
s1 是LCD上X轴两个点的距离
s2' s2
Kx= LCD距离/触摸屏距离
= (s1 + s2) / (s1' + s2')
= 2s/(s1' + s2')

Y轴方向
Chapter18 lesson9 004.png

TS距离是d1'
LCD距离是d1
Ky=(d1 + d2) / (d1' + d2')
= 2d / (d1' + d2')

我们现在有了斜率,给定一个坐标,我们需要需要原点的触屏LCD坐标

原点我们选在最中间 可以忽略掉上下左右的偏差
Chapter18 lesson9 005.png

原点坐标在触摸屏上是xc' yc',在LCD上是 xc yc ,那我们的校准公式,对于给定的x3,我们如何求出x

X= (x3 - xc' ) * Kx + xc

对于给定的y'我们如何算出Y轴坐标?

y = (y' - yc') * Ky + yc

我们需要点击触摸屏上这5个点,同时需要把这五个点坐标打印显示出来.

这节视频我们讲的时校准原理

第010节_触摸屏编程_较准与画线编

这个程序我们怎么写
Chapter18 lesson10 001.png

我们需要得到这5个点的坐标 给这5个点分别设置为ABCDE

第一步

  • 在A点显示 +
  • 客户点击 +
  • 记录触摸屏的坐标
  • BCDE上循环操作,显示点击读取的操作

第二步

  • 根据这些数据,确定公式

第三步

  • 以后得到TS触点时,可转换出LCD坐标

我们需要实现这几个函数

显示 + 在x y 中显示 fb_disp_cross(int x , int y)

如何记录 ts_read_raw ,读到原始数据,根据这些数据,确定公式 ts_calibrate

如何转换出LCD坐标?

ts_read

我们实现这几个函数

  • 我们先实现 +
  • 我们既然画线就在geometry.c中实现

Chapter18 lesson10 002.png

画十字架, 原点x,y

x-10 y
x+10 y
x y+10
x y+10
void fb_disp_cross(int x, int y, unsigned int color)
{
	draw_line(x-10, y, x+10, y, color);
	draw_line(x, y-10, x, y+10, color);
}


编辑touchscreen.c

//+ 坐标的x值
static int g_ts_x;
//+ 坐标Y值
static int g_ts_y;

//表示数据并未有效
//设置成volatile类型,有两个地方会用到一个是中断report_ts_xy
//另一个是程序ts_read_raw,我们一定确保这个值是从内存中读取出来
//让双方得到真实的值
static volatile char g_ts_data_valid = 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);
//实现report_ts_xy函数来打印
		report_ts_xy(x, y);

		/* 启动定时器以再次读取数据 */
		ts_timer_enable();
	}
	else
	{
		ts_timer_disable();
		enter_wait_pen_down_mode();
	}

	enter_wait_pen_up_mode();
}

//report_ts_xy函数的实现

void report_ts_xy(int x, int y)
{
	//printf("x = %08d, y = %08d\n\r", x, y);
//一开始标记位=0表示没有数据
	if (g_ts_data_valid == 0)
	{
		g_ts_x = x;
		g_ts_y = y;
		g_ts_data_valid = 1;
	}
}

//读到原始数据
void ts_read_raw(int *px, int *py)
{
//当按下触摸屏时会产生ADC中断
	while (g_ts_data_valid == 0);
	*px = g_ts_x;
	*py = g_ts_y;
//读完数据清零
	g_ts_data_valid = 0;
}

下面我们实现ts_calibrate校准函数,在tslib.c文件中

//设置斜率为全局变量
static double g_kx;
static double g_ky;

static int g_ts_xc, g_ts_yc;
static int g_lcd_xc, g_lcd_yc;


//加一个调换的全局变量
static int g_ts_xy_swap = 0;



/*
----------------------------
|                          |
|  +(A)              (B)+  |
|                          |
|                          |
|                          |
|            +(E)          |
|                          |
|                          |
|                          |
|  +(D)              (C)+  |
|                          |
----------------------------

*/
ABCDE这5个点都实现

void ts_calibrate(void)
{
	unsigned int fb_base;
	int xres, yres, bpp;
//定义ABCDE触摸屏坐标
	int a_ts_x, a_ts_y;
	int b_ts_x, b_ts_y;
	int c_ts_x, c_ts_y;
	int d_ts_x, d_ts_y;
	int e_ts_x, e_ts_y;

	/* X轴方向 */
	int ts_s1, ts_s2;
	int lcd_s;

	/* Y轴方向 */
	int ts_d1, ts_d2;
	int lcd_d;
//通过调用framebuffer.c里面的函数,来获取LCD坐标
	/* 获得LCD的参数: fb_base, xres, yres, bpp */
	get_lcd_params(&fb_base, &xres, &yres, &bpp);

	/* 对于ABCDE, 循环: 显示"+"、点击、读ts原始值 */
//A坐标,X分辨率50, Y分辨率50
	/* A(50, 50) */
	get_calibrate_point_data(50, 50, &a_ts_x, &a_ts_y);
//B坐标,X分辨率-50
	/* B(xres-50, 50) */
	get_calibrate_point_data(xres-50, 50, &b_ts_x, &b_ts_y);
//C坐标,X分辨率-50,Y分辨率-50
	/* C(xres-50, yres-50) */
	get_calibrate_point_data(xres-50, yres-50, &c_ts_x, &c_ts_y);
//D坐标,X分辨率位置50,Y分辨率-50
	/* D(50, yres-50) */
	get_calibrate_point_data(50, yres-50, &d_ts_x, &d_ts_y);
//E坐标,X分辨率除2,Y分辨率除2
	/* E(xres/2, yres/2) */
	get_calibrate_point_data(xres/2, yres/2, &e_ts_x, &e_ts_y);

//读取XY坐标值后确定XY是否反转
	/* 确定触摸屏数据XY是否反转 */
	g_ts_xy_swap = is_ts_xy_swap(a_ts_x, a_ts_y, b_ts_x, b_ts_y);
//如果反转,对调所有点的XY坐标
	if (g_ts_xy_swap)
	{
		/* 对调所有点的XY坐标 */
		swap_xy(&a_ts_x, &a_ts_y);
		swap_xy(&b_ts_x, &b_ts_y);
		swap_xy(&c_ts_x, &c_ts_y);
		swap_xy(&d_ts_x, &d_ts_y);
		swap_xy(&e_ts_x, &e_ts_y);
	}

Chapter18 lesson10 003.png

	/* 确定公式的参数并保存 */
//Ts_S1值=B点ts和A点tsX轴方向的距离
	ts_s1 = b_ts_x - a_ts_x;
//Ts_S2值
	ts_s2 = c_ts_x - d_ts_x;
//lcd_s值
	lcd_s = xres-50 - 50;
//ts_d1值
	ts_d1 = d_ts_y - a_ts_y;
//ts_d2值
	ts_d2 = c_ts_y - b_ts_y;
//lcd_d值
	lcd_d = yres-50-50;
//X轴的斜率
	g_kx = ((double)(2*lcd_s)) / (ts_s1 + ts_s2);
//Y轴的斜率
	g_ky = ((double)(2*lcd_d)) / (ts_d1 + ts_d2);

//中心点E点的坐标
	g_ts_xc = e_ts_x;

	g_ts_yc = e_ts_y;

	g_lcd_xc = xres/2;
	g_lcd_yc = yres/2;
}

我们需要把 + 在LCD上显示出来并且读出数据

void get_calibrate_point_data(int lcd_x, int lcd_y, int *px, int *py)
{
	fb_disp_cross(lcd_x, lcd_y, 0xffffff);

	/* 等待点击 */

	ts_read_raw(px, py);
}
//比如我们之前发现上报的X轴Y轴值反了
//比如正常情况下从A点移动到B点是x值变化比较大y值不变,但是目前的情况是y值变化比较大,x值不变
//我分根据这个特性分辨

int is_ts_xy_swap(int a_ts_x, int a_ts_y, int b_ts_x, int b_ts_y)
{
	int dx = b_ts_x - a_ts_x;
	int dy = b_ts_y - a_ts_y;
//减出来的值有可能是负数,我们需要取绝对值
	if (dx < 0)
		dx = 0 - dx;
	if (dy < 0)
		dy = 0 - dy;

	if(dx > dy)
		return 0; /* xy没有反转 */
	else
		return 1; /* xy反了 */
}

//如果是反的我们需要调换回来,我们需要确定XY是否反转

//我们写出一个对调函数
void swap_xy(int *px, int *py)
{
	int tmp = *px;
	*px = *py;
	*py = tmp;
}

/*
 * 读TS原始数据, 转换为LCD坐标
 */
void ts_read(int *lcd_x, int *lcd_y)
{
	int ts_x, ts_y;
	
	ts_read_raw(&ts_x, ts_y);

	if (g_ts_xy_swap)
	{
		swap_xy(&ts_x, &ts_y);
	}

	/* 使用公式计算 */
//斜率 * (触摸屏坐标 - 中心点除,触摸屏坐标) + 中心点LCD坐标 
	*lcd_x = g_kx * (ts_x - g_ts_xc) + g_lcd_xc;
	*lcd_y = g_ky * (ts_y - g_ts_yc) + g_lcd_yc;
}

接下来我们画线, 我们在framebuffer.c中把清屏函数单独实现

void clear_screen(unsigned int color)
{
	int x, y;
	unsigned char *p0;
	unsigned short *p;
	unsigned int *p2;

	/* 往framebuffer中写数据 */
	if (bpp == 8)
	{
		/* bpp: palette[color] */

		p0 = (unsigned char *)fb_base;
		for (x = 0; x < xres; x++)
			for (y = 0; y < yres; y++)
				*p0++ = color;
	}
	else if (bpp == 16)
	{
		/* 让LCD输出整屏的红色 */

		/* 565: 0xf700 */

		p = (unsigned short *)fb_base;
		for (x = 0; x < xres; x++)
			for (y = 0; y < yres; y++)
				*p++ = convert32bppto16bpp(color);
			
	}
	else if (bpp == 32)
	{
		p2 = (unsigned int *)fb_base;
		for (x = 0; x < xres; x++)
			for (y = 0; y < yres; y++)
				*p2++ = color;

	}
}

先显示一个一个点,让后显示开始校准提示, 校准完提示 ok draw

打开我们的 touchscreen_test.c文件

void touchscreen_test(void)
{
	unsigned int fb_base;
	int xres, yres, bpp;

	int x, y;

	/* 获得LCD的参数: fb_base, xres, yres, bpp */
	get_lcd_params(&fb_base, &xres, &yres, &bpp);

	touchscreen_init();

	/* 清屏 */
	clear_screen(0);
//我们在70像素的地方显示文字,背景白色显示文字
	/* 显示文字提示较准 */
	fb_print_string(70, 70, "Touc cross to calibrate touchscreen", 0xffffff);
	ts_calibrate();

	/* 显示文字提示绘画 */
	fb_print_string(70, yres - 70, "OK! To draw!", 0xffffff);

	while (1)
	{
		ts_read(&x, &y);
//我们先打印值
		printf(" x = %d, y = %d\n\r", x, y);
//描绿色的线
		fb_put_pixel(x, y, 0xff00);
	}
}

我们添加了 tslib.c需要修改Makefile

添加objs += adc_touchscreen/tslib.o

objs = start.o init.o nand_flash.o led.o uart.o main.o exception.o interrupt.o timer.o nor_flash.o my_printf.o string_utils.o lib1funcs.o

objs += lcd/font.o
objs += lcd/framebuffer.o
objs += lcd/geometry.o
objs += lcd/lcd.o
objs += lcd/lcd_4.3.o
objs += lcd/lcd_controller.o
objs += lcd/lcd_test.o
objs += lcd/s3c2440_lcd_controller.o
objs += lcd/font_8x16.o

objs += adc_touchscreen/adc.o
objs += adc_touchscreen/adc_test.o

objs += adc_touchscreen/touchscreen.o
objs += adc_touchscreen/touchscreen_test.o


objs += adc_touchscreen/tslib.o


all: $(objs)
	#arm-linux-ld -Ttext 0 -Tdata 0x30000000  start.o led.o uart.o init.o main.o -o sdram.elf
	arm-linux-ld -T sdram.lds $^ libgcc.a -o sdram.elf
	arm-linux-objcopy -O binary -S sdram.elf sdram.bin
	arm-linux-objdump -D sdram.elf > sdram.dis
clean:
	rm -f *.bin $(objs) *.elf *.dis
	
%.o : %.c
	arm-linux-gcc -march=armv4 -c -o $@ $<

%.o : %.S
	arm-linux-gcc -march=armv4 -c -o $@ $<

第011节_触摸屏编程_测试

  • 发现程序有bug,点击坐标一次,程序就完成执行,我们需要修改触摸屏文件tsib.c
static double g_kx;
static double g_ky;

static int g_ts_xc, g_ts_yc;
static int g_lcd_xc, g_lcd_yc;
static int g_ts_xy_swap = 0;


static unsigned int fb_base;
static int xres, yres, bpp;


void get_calibrate_point_data(int lcd_x, int lcd_y, int *px, int *py)
{
	int pressure;
	int x, y;
	
	fb_disp_cross(lcd_x, lcd_y, 0xffffff);

	/* 等待点击 */

	do {
//如果pressure一直是0的话我们丢掉这些数据
		ts_read_raw(&x, &y, &pressure); 
	} while (pressure == 0);

//让后再次读
	do {
		*px = x;
		*py = y;
		ts_read_raw(&x, &y, &pressure); 
		printf("get raw data: x = %08d, y = %08d\n\r", x, y);
	} while (pressure);

	printf("return raw data: x = %08d, y = %08d\n\r", *px, *py);

	/* 直到松开才返回 */
	fb_disp_cross(lcd_x, lcd_y, 0);
}

修改touchcreen.c文件添加压力值相关信息

//定义压力值全局变量
static int g_ts_pressure;
void report_ts_xy(int x, int y, int pressure);

//发现不能画线,修改 ts_read
//我们需要判断触摸笔是按下还是松开,需要添加压力值参数
/*
 * 读TS原始数据, 转换为LCD坐标
 * 我们需要加上压力值lcd_pressure
 */
int ts_read(int *lcd_x, int *lcd_y, int *lcd_pressure)
{
	int ts_x, ts_y, ts_pressure;
	int tmp_x, tmp_y;
//添加压力值参数	
	ts_read_raw(&ts_x, &ts_y, &ts_pressure);

	if (g_ts_xy_swap)
	{
		swap_xy(&ts_x, &ts_y);
	}

	/* 使用公式计算 */
	tmp_x = g_kx * (ts_x - g_ts_xc) + g_lcd_xc;
	tmp_y = g_ky * (ts_y - g_ts_yc) + g_lcd_yc;

//如果值超出了LCD范围返回-1
	if (tmp_x < 0 || tmp_x >= xres || tmp_y < 0 || tmp_y >= yres)
		return -1;
	
	*lcd_x = tmp_x;
	*lcd_y = tmp_y;
//压力值等于全局变量ts_pressure
	*lcd_pressure = ts_pressure;
	return 0;
}


void ts_read_raw(int *px, int *py, int *ppressure)
{
	while (g_ts_data_valid == 0);
	*px = g_ts_x;
	*py = g_ts_y;
	*ppressure = g_ts_pressure;
	g_ts_data_valid = 0;
}


//我们需要report上报压力值数据
void report_ts_xy(int x, int y, int pressure)
{
	//printf("x = %08d, y = %08d\n\r", x, y);

	if (g_ts_data_valid == 0)
	{
		g_ts_x = x;
		g_ts_y = y;
		g_ts_pressure = pressure;
		g_ts_data_valid = 1;
	}
}


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();
//如果松开就上报数据xy坐标00 压力值0
		report_ts_xy(0, 0, 0);
		
	}
	else	
	{
		//printf("pen down\n\r");

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

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

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

	static int adc_cnt = 0;
	static int adc_x = 0;
	static int adc_y = 0;

	if (!(x & (1<<15))) /* 如果仍然按下才打印 */
	{
#if 0		
		x &= 0x3ff;
		y &= 0x3ff;
		
		//printf("x = %08d, y = %08d\n\r", x, y);
//当我得到触电数据以后,如果当前仍是按下状态,会上报XY坐标值并且上报压力值,压力值等于1
		report_ts_xy(x, y, 1);

		/* 启动定时器以再次读取数据 */
		ts_timer_enable();
#endif
//防止数据在最后出现很大的误差
		/* 第1次启动ADC后:
		 *   a. 要连续启动N次, 获得N个数据, 求平均值并上报
		 *   b. 得到N次数据后, 再启动TIMER 
		 */
//我们直接累加
		adc_x += (x & 0x3ff);
		adc_y += (y & 0x3ff);
		adc_cnt++;
//我们取16的话右移4位比较容易操作
		if (adc_cnt == 16)
		{
//右移4位
			adc_x >>= 4;
			adc_y >>= 4;
//上报
			report_ts_xy(adc_x, adc_y, 1);

//恢复到初始值0
			adc_cnt = 0;
			adc_x = 0;
			adc_y = 0;
			
			/* 启动定时器以再次读取数据 */
			ts_timer_enable();
		}
		else
		{
			/* 否则再次启动ADC */
			/* 进入"自动测量"模式 */
			enter_auto_measure_mode();
			
			/* 启动ADC */
			ADCCON |= (1<<0);
		}
		
	}
	else
	{
		adc_cnt = 0;
		adc_x = 0;
		adc_y = 0;

		ts_timer_disable();
		enter_wait_pen_down_mode();
//如果数据转换完之前再次松开,这里我也会上报数据,0,0,0
		report_ts_xy(0, 0, 0);
	}

	enter_wait_pen_up_mode();
}

那么我们的tslib.c

void get_calibrate_point_data(int lcd_x, int lcd_y, int *px, int *py)
{
	int pressure;
	int x, y;
	
	fb_disp_cross(lcd_x, lcd_y, 0xffffff);

	/* 等待点击 */

	do {
//读取ts坐标值也加上压力值
		ts_read_raw(&x, &y, &pressure); 
//直到压力等于0的时候才返回
	} while (pressure == 0);


	do {
		*px = x;
		*py = y;
//压力值存在的情况下表示按下状态,就一直读取数据
		ts_read_raw(&x, &y, &pressure); 
		printf("get raw data: x = %08d, y = %08d\n\r", x, y);
	} while (pressure);
//打印坐标值
	printf("return raw data: x = %08d, y = %08d\n\r", *px, *py);

	/* 直到松开才返回 */
//我们操作完成后消除 +  
	fb_disp_cross(lcd_x, lcd_y, 0);
}

//我们确定校准为什么不对,我们校准涉及 原始数据 校准公式

//我们把公式使用函数来表示
int get_lcd_x_frm_ts_x(int ts_x)
{
	return g_kx * (ts_x - g_ts_xc) + g_lcd_xc;
}

int get_lcd_y_frm_ts_y(int ts_y)
{
	return g_ky * (ts_y - g_ts_yc) + g_lcd_yc;
}


void ts_calibrate(void)
{

	int a_ts_x, a_ts_y;
	int b_ts_x, b_ts_y;
	int c_ts_x, c_ts_y;
	int d_ts_x, d_ts_y;
	int e_ts_x, e_ts_y;

	/* X轴方向 */
	int ts_s1, ts_s2;
	int lcd_s;

	/* Y轴方向 */
	int ts_d1, ts_d2;
	int lcd_d;

	/* 获得LCD的参数: fb_base, xres, yres, bpp */
	get_lcd_params(&fb_base, &xres, &yres, &bpp);

	/* 对于ABCDE, 循环: 显示"+"、点击、读ts原始值 */
	/* A(50, 50) */
	get_calibrate_point_data(50, 50, &a_ts_x, &a_ts_y);	

	/* B(xres-50, 50) */
	get_calibrate_point_data(xres-50, 50, &b_ts_x, &b_ts_y);

	/* C(xres-50, yres-50) */
	get_calibrate_point_data(xres-50, yres-50, &c_ts_x, &c_ts_y);

	/* D(50, yres-50) */
	get_calibrate_point_data(50, yres-50, &d_ts_x, &d_ts_y);
	
	/* E(xres/2, yres/2) */
	get_calibrate_point_data(xres/2, yres/2, &e_ts_x, &e_ts_y);

	/* 确定触摸屏数据XY是否反转 */
	g_ts_xy_swap = is_ts_xy_swap(a_ts_x, a_ts_y, b_ts_x, b_ts_y);

	if (g_ts_xy_swap)
	{
		/* 对调所有点的XY坐标 */
		swap_xy(&a_ts_x, &a_ts_y);
		swap_xy(&b_ts_x, &b_ts_y);
		swap_xy(&c_ts_x, &c_ts_y);
		swap_xy(&d_ts_x, &d_ts_y);
		swap_xy(&e_ts_x, &e_ts_y);
	}

	/* 确定公式的参数并保存 */
	ts_s1 = b_ts_x - a_ts_x;
	ts_s2 = c_ts_x - d_ts_x;
	lcd_s = xres-50 - 50;

	ts_d1 = d_ts_y - a_ts_y;
	ts_d2 = c_ts_y - b_ts_y;
	lcd_d = yres-50-50;

	g_kx = ((double)(2*lcd_s)) / (ts_s1 + ts_s2);
	g_ky = ((double)(2*lcd_d)) / (ts_d1 + ts_d2);

	g_ts_xc = e_ts_x;
	g_ts_yc = e_ts_y;

	g_lcd_xc = xres/2;
	g_lcd_yc = yres/2;
//打印ABCDE的坐标值
	printf("A lcd_x = %08d, lcd_y = %08d\n\r", get_lcd_x_frm_ts_x(a_ts_x), get_lcd_y_frm_ts_y(a_ts_y));
	printf("B lcd_x = %08d, lcd_y = %08d\n\r", get_lcd_x_frm_ts_x(b_ts_x), get_lcd_y_frm_ts_y(b_ts_y));
	printf("C lcd_x = %08d, lcd_y = %08d\n\r", get_lcd_x_frm_ts_x(c_ts_x), get_lcd_y_frm_ts_y(c_ts_y));
	printf("D lcd_x = %08d, lcd_y = %08d\n\r", get_lcd_x_frm_ts_x(d_ts_x), get_lcd_y_frm_ts_y(d_ts_y));
	printf("E lcd_x = %08d, lcd_y = %08d\n\r", get_lcd_x_frm_ts_x(e_ts_x), get_lcd_y_frm_ts_y(e_ts_y));
}
  • 我们转换出的XY坐标值不是特别稳定
/* 每10ms该函数被调用一次 
 */
void touchscreen_timer_irq(void)
{
	/* 如果触摸屏仍被按下, 进入"自动测量模式", 启动ADC */
	if (get_status_of_ts_timer() == 0)
		return;

	if (ADCDAT0 & (1<<15)) /* 如果松开 */
	{
		ts_timer_disable();
		enter_wait_pen_down_mode();
		//report_ts_xy(0, 0, 0);
		return;
	}
	else  /* 按下状态 */
	{
		/* 进入"自动测量"模式 */
		enter_auto_measure_mode();

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


  • 修改touchscreen-test.c文件
void touchscreen_test(void)
{
	unsigned int fb_base;
	int xres, yres, bpp;

	int x, y, pressure;

	/* 获得LCD的参数: fb_base, xres, yres, bpp */
	get_lcd_params(&fb_base, &xres, &yres, &bpp);

	touchscreen_init();

	/* 清屏 */
	clear_screen(0);

	/* 显示文字提示较准 */
	fb_print_string(70, 70, "Touc cross to calibrate touchscreen", 0xffffff);
	ts_calibrate();

	/* 显示文字提示绘画 */
	fb_print_string(70, yres - 70, "OK! To draw!", 0xffffff);

	while (1)
	{
//如果结果=0则继续执行下面操作
		if (ts_read(&x, &y, &pressure) == 0)
		{
			printf(" x = %d, y = %d\n\r", x, y);
//如果是按下状态,才会描点
			if (pressure)
			{

				fb_put_pixel(x, y, 0xff00);
			}
		}
	}
}

把LCD的电压值,成功转化成屏幕的坐标 要点

  1. 对于触摸屏要多次测量,求平均值
  2. 要丢弃非法值(以LCD分辨率作为判断标准)
  3. 校准时一定要点准

参考tslib库,

第012节_触摸屏编程_完善

我们触摸屏校准虽然可以正常运行,但是有些问题,比如在触摸屏上点一个点,同时屏幕上面会显示另一个点 我们按住屏幕不动的同时将其转换成LCD坐标并且描点,就表明数值不大稳定 问题

  1. 我们第一次点击触摸屏会出现两个点
  2. 长按,LCD上的点会越来越大

根源在于我们得到的LCD坐标值不稳定,根源ADC转换出来的xy坐标值不稳定 Chapter18 lesson12 001.JPG

我们打开touchscreen.c问题出现在这里面

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

static volatile int g_ts_timer_enable = 0;

static int g_ts_x;
static int g_ts_y;
static int g_ts_pressure;
static int g_ts_data_valid = 0;

//定义测试数据16
static int test_x_array[16];
static int test_y_array[16];

void report_ts_xy(int x, int y, int pressure);

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 enter_auto_measure_mode(void)
{
	ADCTSC = AUTO_PST | NO_OPR_MODE;
}

int is_in_auto_mode(void)
{
	return ADCTSC & AUTO_PST;
}

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))
	{
//按下状态启用触摸屏
//启动测量模式,转换结束产生adc中断
		//printf("pen up\n\r");
		enter_wait_pen_down_mode();
		report_ts_xy(0, 0, 0);
		
	}
	else	
	{
		//printf("pen down\n\r");

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

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



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

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

static int get_status_of_ts_timer(void)
{
	return g_ts_timer_enable;
}


void report_ts_xy(int x, int y, int pressure)
{
	//printf("x = %08d, y = %08d\n\r", x, y);

	if (g_ts_data_valid == 0)
	{
		g_ts_x = x;
		g_ts_y = y;
		g_ts_pressure = pressure;
		g_ts_data_valid = 1;
	}
}

void ts_read_raw(int *px, int *py, int *ppressure)
{
	while (g_ts_data_valid == 0);
	*px = g_ts_x;
	*py = g_ts_y;
	*ppressure = g_ts_pressure;
	g_ts_data_valid = 0;
}


/* 每10ms该函数被调用一次 
 */
void touchscreen_timer_irq(void)
{
	/* 如果触摸屏仍被按下, 进入"自动测量模式", 启动ADC */
	if (get_status_of_ts_timer() == 0)
		return;

	if (is_in_auto_mode())
		return;

	/* 只有在"等待中断模式"下才可以使用ADCDAT0'BIT 15来判断触摸笔状态 */

	if (ADCDAT0 & (1<<15)) /* 如果松开 */
	{
		printf("timer set pen down\n\r");
		ts_timer_disable();
		enter_wait_pen_down_mode();
		report_ts_xy(0, 0, 0);
		return;
	}
	else  /* 按下状态 */
	{
		/* 进入"自动测量"模式 */
		enter_auto_measure_mode();

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

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

	static int adc_cnt = 0;
	static int adc_x = 0;
	static int adc_y = 0;

	/* 进入ADC中断时, TS处于"自动测量模式" */

	/* 只有在"等待中断模式"下才可以使用ADCDAT0'BIT 15来判断触摸笔状态 */

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

		/* 启动定时器以再次读取数据 */
		ts_timer_enable();
#endif
		/* 第1次启动ADC后:
		 *   a. 要连续启动N次, 获得N个数据, 求平均值并上报
		 *   b. 得到N次数据后, 再启动TIMER 
		 */
		adc_x += (x & 0x3ff);
		adc_y += (y & 0x3ff);
//定义一个函数把这些值打印出来
		test_x_array[adc_cnt] = (x & 0x3ff);
		test_y_array[adc_cnt] = (y & 0x3ff);
		
		adc_cnt++;

		if (adc_cnt == 16)
		{
			adc_x >>= 4;
			adc_y >>= 4;
			report_ts_xy(adc_x, adc_y, 1);

			adc_cnt = 0;
			adc_x = 0;
			adc_y = 0;
			
			/* 启动定时器以再次读取数据 */
			/* 先设置TS进入"等待中断模式" */
//有按下就会有松开
			enter_wait_pen_up_mode();
			ts_timer_enable();
		}
		else
		{
			/* 再次启动ADC */
			/* 进入"自动测量"模式 */
			enter_auto_measure_mode();
			
			/* 启动ADC */
			ADCCON |= (1<<0);
		}
		
	}
	else
	{
		adc_cnt = 0;
		adc_x = 0;
		adc_y = 0;
		printf("adc report pen down\n\r");
		ts_timer_disable();
		enter_wait_pen_down_mode();
		report_ts_xy(0, 0, 0);
	}

	//enter_wait_pen_up_mode();  /* 启动ADC时不应该进入这个模式, 它会影响数据 */
}

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

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


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


//打印数组定义的那些值
void print_test_array(void)
{
	int i;

	printf("test array x : ");
	for (i = 0; i < 16; i++)
		printf("%08d ", test_x_array[i]);
	printf("\n\r");

	printf("test array y : ");
	for (i = 0; i < 16; i++)
		printf("%08d ", test_y_array[i]);
	printf("\n\r");
}



void ts_read_raw_test(int *px, int *py, int *ppressure)
{
	while (g_ts_data_valid == 0);
	*px = g_ts_x;
	*py = g_ts_y;
	*ppressure = g_ts_pressure;
	print_test_array();
	g_ts_data_valid = 0;
}

tslib.c使用了ts_read_raw函数

void get_calibrate_point_data(int lcd_x, int lcd_y, int *px, int *py)
{
	int pressure;
	int x, y;
	int sum_x = 0, sum_y = 0;
	int cnt = 0;
	
	fb_disp_cross(lcd_x, lcd_y, 0xffffff);

	/* 等待点击 */

	do {
		//ts_read_raw(&x, &y, &pressure); 
//我们使用测试程序去读这些值
		ts_read_raw_test(&x, &y, &pressure);
	} while (pressure == 0);


	do {
		if (cnt < 128)
		{
			sum_x += x;
			sum_y += y;
			cnt++;
		}
		//ts_read_raw(&x, &y, &pressure);
//
		ts_read_raw_test(&x, &y, &pressure);
		printf("get raw data: x = %08d, y = %08d, cnt = %d\n\r", x, y, cnt);
	} while (pressure);

	*px = sum_x / cnt;
	*py = sum_y / cnt;

	printf("return raw data: x = %08d, y = %08d\n\r", *px, *py);

	/* 直到松开才返回 */
	fb_disp_cross(lcd_x, lcd_y, 0);
}

我们来看点击一下是不是得到了距离非常远的两个值 Chapter18 lesson12 002.png 对于同一个点得到的是255 945 945 944

发现 945 944经常出现

我们查一下原因 进入touchscreen.c中

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

	static int adc_cnt = 0;
	static int adc_x = 0;
	static int adc_y = 0;

	/* 进入ADC中断时, TS处于"自动测量模式" */

	/* 只有在"等待中断模式"下才可以使用ADCDAT0'BIT 15来判断触摸笔状态 */

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

		/* 启动定时器以再次读取数据 */
		ts_timer_enable();
#endif
		/* 第1次启动ADC后:
		 *   a. 要连续启动N次, 获得N个数据, 求平均值并上报
		 *   b. 得到N次数据后, 再启动TIMER 
		 */
		adc_x += (x & 0x3ff);
		adc_y += (y & 0x3ff);

		test_x_array[adc_cnt] = (x & 0x3ff);
		test_y_array[adc_cnt] = (y & 0x3ff);
		
		adc_cnt++;

		if (adc_cnt == 16)
		{
			adc_x >>= 4;
			adc_y >>= 4;
			report_ts_xy(adc_x, adc_y, 1);

			adc_cnt = 0;
			adc_x = 0;
			adc_y = 0;
			
			/* 启动定时器以再次读取数据 */
			/* 先设置TS进入"等待中断模式" */
			enter_wait_pen_up_mode();
			ts_timer_enable();
		}
		else
		{
			/* 再次启动ADC */
			/* 进入"自动测量"模式 */
			enter_auto_measure_mode();
			
			/* 启动ADC */
			ADCCON |= (1<<0);
		}
		
	}
	else
	{
		adc_cnt = 0;
		adc_x = 0;
		adc_y = 0;
		printf("adc report pen down\n\r");
		ts_timer_disable();
		enter_wait_pen_down_mode();
		report_ts_xy(0, 0, 0);
	}
//启动ADC后又再次进入enter_wait_pen_up_mode电阻上拉,多次一举
	//enter_wait_pen_up_mode();  /* 启动ADC时不应该进入这个模式, 它会影响数据 */
}

Chapter18 lesson12 003.png

发现这些值中还有944,我需要继续查找原因 在touchscreen.c时钟处理函数中添加打印信息

/* 每10ms该函数被调用一次 
 */
void touchscreen_timer_irq(void)
{
	/* 如果触摸屏仍被按下, 进入"自动测量模式", 启动ADC */
	if (get_status_of_ts_timer() == 0)
		return;

	if (is_in_auto_mode())
		return;

	/* 只有在"等待中断模式"下才可以使用ADCDAT0'BIT 15来判断触摸笔状态 */

	if (ADCDAT0 & (1<<15)) /* 如果松开 */
	{
//添加打印信息
		printf("timer set pen down\n\r");
		ts_timer_disable();
//进入enter_wait_pen_down_mode,来判断触摸笔是按下还是松开
		enter_wait_pen_down_mode();
		report_ts_xy(0, 0, 0);
		return;
	}

	else  /* 按下状态 */
	{
		/* 进入"自动测量"模式 */
		enter_auto_measure_mode();

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

Chapter18 lesson12 004.png

非常频繁打印timer set pen down

我们的判断有问题

打开芯片手册,搜索这个寄存器 Bit15确实是判断按下或者松开

Chapter18 lesson12 005.png

只有在中断模式下,这一位才可以正确反应是按下还是松开的状态 修改touchscreen_timer_irq函数


/* 每10ms该函数被调用一次 
 */
void touchscreen_timer_irq(void)
{
	/* 如果触摸屏仍被按下, 进入"自动测量模式", 启动ADC */
	if (get_status_of_ts_timer() == 0)
		return;


//如果定时器中断在ADC中间产生,应该立刻返回啥都不做,不需要timer去做任何操作
//我们需要写出这个函数
//如果不是自动模式,那么就是等待中断模式
	if (is_in_auto_mode())
		return;

//中断处理函数,
	/* 只有在"等待中断模式"下才可以使用ADCDAT0'BIT 15来判断触摸笔状态 */

	if (ADCDAT0 & (1<<15)) /* 如果松开 */
	{
		printf("timer set pen down\n\r");
		ts_timer_disable();
		enter_wait_pen_down_mode();
		report_ts_xy(0, 0, 0);
		return;
	}
	else  /* 按下状态 */
	{
		/* 进入"自动测量"模式 */
		enter_auto_measure_mode();

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


//判断是否是模式模式的函数

int is_in_auto_mode(void)
{

	return ADCTSC & AUTO_PST;
}

我们接着实验

Chapter18 lesson12 006.png

发现并没有捕捉到笔的松开模式

修改tslib,取消ts_read_raw_test 重新进行测试

void get_calibrate_point_data(int lcd_x, int lcd_y, int *px, int *py)
{
	int pressure;
	int x, y;
	int sum_x = 0, sum_y = 0;
	int cnt = 0;
	
	fb_disp_cross(lcd_x, lcd_y, 0xffffff);

	/* 等待点击 */

	do {
		ts_read_raw(&x, &y, &pressure); 
		//ts_read_raw_test(&x, &y, &pressure);
	} while (pressure == 0);


	do {
		if (cnt < 128)
		{
			sum_x += x;
			sum_y += y;
			cnt++;
		}
		ts_read_raw(&x, &y, &pressure); 
		//ts_read_raw_test(&x, &y, &pressure);
		printf("get raw data: x = %08d, y = %08d, cnt = %d\n\r", x, y, cnt);
	} while (pressure);

	*px = sum_x / cnt;
	*py = sum_y / cnt;

	printf("return raw data: x = %08d, y = %08d\n\r", *px, *py);

	/* 直到松开才返回 */
	fb_disp_cross(lcd_x, lcd_y, 0);
}

//现在发现点点不准确

Chapter18 lesson12 007.png

发现校准的值和我们之前的不一样

修改我们的tslib校准程序

void get_calibrate_point_data(int lcd_x, int lcd_y, int *px, int *py)
{
	int pressure;
	int x, y;
	int sum_x = 0, sum_y = 0;
	int cnt = 0;
	
	fb_disp_cross(lcd_x, lcd_y, 0xffffff);

	/* 等待点击 */

	do {
		ts_read_raw(&x, &y, &pressure); 
		//ts_read_raw_test(&x, &y, &pressure);
	} while (pressure == 0);


	do {
//我们把求和的次数限制为128次
		if (cnt < 128)
		{
			sum_x += x;
			sum_y += y;
			cnt++;
		}
		ts_read_raw(&x, &y, &pressure); 
		//ts_read_raw_test(&x, &y, &pressure);
		printf("get raw data: x = %08d, y = %08d, cnt = %d\n\r", x, y, cnt);
	} while (pressure);

	*px = sum_x / cnt;
	*py = sum_y / cnt;

	printf("return raw data: x = %08d, y = %08d\n\r", *px, *py);

	/* 直到松开才返回 */
	fb_disp_cross(lcd_x, lcd_y, 0);
}

我们可以参考tslib

  1. 使用矩阵进行校准,适用性更强
  2. 使用多种方法消除误差,多次测量求平均值

判断相领点的距离,如果突然变化很大,就有可能是错误值

  1. ...

第一期的视频在于裸机基本操作 视频的要点在于

修改要点

  1. 启动ADC时不应该进入等待中断模式,它会影响数据
  2. 只有在"等待中断模式"下才可以使用ADCDAT0'BIT 15来判断触摸笔状态
  3. 校准非常重要,所以在程序种多次测量求平均值(不仅仅是在adc中断种求平均值)

《《所有章节目录》》