第018课 ADC和触摸屏
目录
第001节_ADC硬件原理
模数转换器即A/D转换器,或简称ADC,通常是指一个将模拟信号转变为数字信号的电子元件。
通常的模数转换器是把经过与标准量比较处理后的模拟量转换成以二进制数值表示的离散信号的转换器。
故任何一个模数转换器都需要一个参考模拟量作为转换的标准,比较常见的参考标准为最大的可转换信号大小。
而输出的数字量则表示输入信号相对于参考信号的大小。如图,是把可变电阻上的电压值变换的模拟信号通过ADC转换,输出数字信号。
对于数字信号我们需要得到它的几个属性
- 用多少位来存储这个数据(假设10bit)。
- 最大值0b111111111 它对应的电压是多少伏(模拟信号输入的最大值是多少)我们就可以根据模拟信号(电压)的最大值,来计算出对应的数值。
- 采样/转换速度。
对于程序员,我们不关心ADC的内部机制,我们只关心:
- 怎么启动ADC
- 启动之后怎么得到数据,
从图1-1-1可以看出ADC有8个多路选择器,显然,以后我们写程序的时候,我们可以8个多路选择之一, 下面是编写程序要做的步骤:
- 确定是哪一路信号:设置8:1MUX,选择要测量哪一个引脚,(看原理图选择要测量的引脚)
- 设置工作时钟(从工作室中,可以算出转换一次,需要多长时间)
- 启动
- 读状态,判断ADC转换是否成功。
- 读数据
第002节_ADC编程
编程步骤:
- 初始化ADC
- 读数据,
- 在串口上显示出来。
一,初始化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模块,旋转可变电阻就可以在串口上看到电压值发生变化。原理图如图1-1-2
第003节_电阻触摸屏硬件原
这节课我们来讲电阻触摸屏的硬件原理
假设有一个比较长的电阻
电阻是R 上面接3.3V电压,下面接地
假设整个电阻的阻值是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代表负极
下面这层膜 前面这条边引出来为yp,后面这层边为ym
假设我们手指要点击触摸屏,那么上下就会粘贴在一起,我怎么算出这个 x y点的坐标呢?
测量触电x坐标:
1 xp接3.3v,xm接GND
yp,ym不接电源
2 测yp电压 上下膜连接在一起,我就可以通过yp测量这个触电的电压 这个yp就像探测一样,从前面的原理我们可以知道,当这个触电越靠近左边这个电压越小,越靠近右边电压越大 这个yp的电压就可以认为是这个触电的坐标(x坐标)
类似的我们怎么测量触电y坐标
类似的xp xm不接电源,同样yp接3.3v, ym接GND,这时候电流就从 yp这里流向ym,让后我们就可以测量xp电压
当按下屏幕时,上下两层膜链接在一起,这个xp就像探针一样,这个触电越靠近yp电压值越大,越靠近ym电压值越小
1 yp接3.3V ym接GND,xp xm不接电源 2 测量xp电压,就是y坐标
注意 x y坐标都是电压值,不是屏幕上480 * 272 这些值,我们需要把电压值转换为坐标值,这需要经过一些转换
我们测量xp yp可以得到触点的两个方向的电压值,这些电压值和坐标是线性关系 我们现在总结下使用触摸屏的流程 1 按下触摸屏 按下触摸屏时,对于一个高效的系统,产生中断,这是触摸屏中断 2 在触摸中断程序中 启动ADC,(获得数据,xy坐标) 启动ADC就开始模数转换,不可能瞬间完成, 3 ADC完成, 产生中断 4 ADC中断中读取x y坐标,
我们来想想,在这个流程里,启动触摸屏的源头是按下触摸屏,那如果长按触摸屏,我按下之后一直不松开 滑动手指呢
那么谁来触发后续的多次ADC转换呢 不可能只启动一次吧
为了支持 长按 滑动操作,我们需要启用定时器
5 启动定时器 6 定时器中断发生,判断触摸屏是否仍被按下,如果按下就循环上述过程( 2 在触摸中断程序中 启动ADC,(获得数据,xy坐标) 启动ADC就开始模数转换,不可能瞬间完成, 3 ADC完成, 产生中断 4 ADC中断中读取x y坐标,) 7 松开结束一个流程 这就是整个触摸屏的使用流程
在14章里讲解了触摸屏,他抽象了几张图
平时的时候上下两层膜并不连接,我们按下触摸屏的时候就会产生中断,那么你怎么知道产生中断,肯定是由某个引脚的电平发生变化
平时 Y_ADC/xp是高电平
按下之后Y_ADC就接地了,就是被拉低了,就产生了低电平
产生低电平后就知道触摸屏被按下了,这个时候就需要测量电压值
读取x坐标
XP XM通电我就测量YP的电压,这不就是 x 点的坐标
第004节_S3C2440触摸屏接口
在不使用触摸屏的时候,必须要把 S1 S2 S3断开,S4 S5闭合,只有这样当我按下触摸屏,上面的电平才能从高变低,会产生一个中断信号
必须让S1 S3闭合,这样电流才可以通过,同时让S2 S4 S5断开 这时候YP这层膜就相当于探针一样去测量电压
必须让S2 S4闭合,这样电流才可以流 下来,同时S1 S3 S5断开,这个时候XP这层膜就相当于探针一样,我可以来测量这里的电压,从而得到Y坐标的电压值
在测量x y坐标时,这个S5上拉电阻都要断开
我们需要控制这几个开关,实际上2440就提供了这几个开关的控制方法
打开2440的芯片手册看触摸屏时怎么操作的
从440到450总共10页不到
我们看有一个8:1 MUX的多路选择器,以及XP YP
- 正常模式,在上节视频中我们有讲解过
- x y分离转换模式
看看我们的X Y坐标原理图,可以单独转换X坐标 单独转换Y坐标 换句话说就是逐个去测量X Y坐标 他首先会启动X坐标的ADC转换,转换成功后数据会保存在ADCDAT0里,同时会产生一个中断,在这个中断服务程序里,就可以把X坐标读取出来,让后可以启动Y坐标的转换,转换成功后数据会保存在ADCDAT,同时会产生一个中断,进入这个中断把Y坐标读取出来 测量一次会产生2个中断,一个时X坐标中断,一个是Y坐标中断
3 自动的或连续的X/Y坐标转换模式 也就是说不需要单独控制,不需要单独去读取X坐标Y坐标,可以设置寄存器,让它一次性的测量X坐标测量Y坐标,X坐标保存在ADCDAT0 Y坐标保存在ADCDAT1,最后产生一个中断,也就是读取X/Y坐标只需要产生一次中断
4 等待中断模式 所谓等待中断模式,就是等待按下或者等待松开 对于下面这幅图,我按下的时候XP从高电平变为低电平,松开时,XP从低电平变为高电平,这就是按下松开都可以检测到
我们要等待按下或者松开时 需要设置rADCTSC =0xd3这个值
Standby Mode静默模式/省电模式(我们不关心这个)
1 AD转换数据时可以通过中断或者查询模式来得到数据,使用中断模式时,从AD转换开始,到得到数据可能会有些延迟,因为中断服务程序的进入和退出需要一定的时间,(也就是说,如果你对数据转换的速度要求的非常高,就可以使用查询方式),可以查询ADCCON[15]来判断是否转换结束
ECFLG状态位 AD转换是否结束
PRSCEN 使能ADC转换 PRSCVL 设置A/D转换预分频值 SEL_MUX选择输入通道,后面我们使用自动转换XY坐标,所以这里不需要设置 ENABLE_START 启动转换
UD_SEN Bit8是用来判断触摸屏是被按下还是被松开
0表明被按下,1表明被松开
YM_SEN Bit7 YM开关使能控制S4
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坐标转换模式
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功能一样的,只不过保存的数据不同
这个的低10位是用来保存 Y坐标的值
TST_UP Bit1 触摸屏松开中断产生 TST_DN Bit0 触摸屏按下中断产生
它会涉及两个中断,按下或者松开,触摸笔的状态中断,另外一个启动ADC以后,ADC结束时也会产生一个中断,但是这个手册里没有看到中断的是能寄存器
那我们猜测一下,ADC模块或者触摸屏模块一定会发出中断
首先是ADC或者触摸屏产生中断,通过中断控制器发送中断给CPU
肯定有寄存器禁止/使能ADC或者触摸屏中断
ADC结束中断或者触摸屏中断,看来他们合起来用一个中断 既然合并必然还会有一个寄存器来分辨到底是ADC还是触摸屏发生的中断变化
设置Bit[31]
设置Bit[31]
优先级我们不需要设置
到底是ADC中断还是触摸屏中断,肯定有其他寄存器可以设置
SUBSOURCE PENDING寄存器
INT_ADC_S Bit[10]表示ADC中断 INT_TC Bit[9]表示触摸屏中断
INTSUBMSK
应该也是同样的位
INT_ADC_S Bit[10]表示ADC中断激活/屏蔽 INT_TC Bit[9]表示触摸屏中断激活/屏蔽
我们可以通过INTSUBMSK来屏蔽ADC中断或者TouchScreen中断 当然也可以是能某个中断 可以通过SUBSRCPND来分辨到底产生那个中断 INTSUBMSK 和SUBSOURCPND这两个寄存器都会汇集到一起 变成一个叫做INT_ADC的中断来发送给CPU
框图就是这样
我们怎么写程序? 写出一个框架
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和触摸屏接口
- 可以参考下面这张图
![](https://i.imgur.com/Hp88Y9G.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);
//中断号是多少? 打开芯片手册,找到中断控制器 ![](https://i.imgur.com/ZgO6M5N.png) ![](https://i.imgur.com/odZ62Yr.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();
}
如何进行分辨 ![](https://i.imgur.com/f6QtQLO.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;
}
//读一下寄存器 找到触摸屏的寄存器 触摸笔按下松开状态寄存器 ![](https://i.imgur.com/Gg9sm7R.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();
}