第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和触摸屏接口
- 可以参考下面这张图
看懂这张图的关键点在于 里面有个中断程序 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);
我们是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();
}
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;
}
//读一下寄存器
找到触摸屏的寄存器
触摸笔按下松开状态寄存器
我们可以读它
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;
}
进入中断处理函数
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 */
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();
}
应该是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转化精度问题,
觉得应该是触摸屏电压不稳定
之前不知道DELAY寄存器是用来干嘛的
等待中断模式时,当触摸笔按下时我们会产生中断,但是可以通过 DELAY来延时产生中断
按下触摸笔,延迟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轴搞反了
电路图中
TSYP TSXP接反 TSYM TSXM接反
我们后面使用触摸屏时会使用软件处理这点,不会导致任何问题
有一个缺点 我们按下触摸屏会输出一个数据,再按下触摸屏又输出一个数据 我长按并没有输出数据,我滑动也没有输出数据 我们需要使用定时器改进这个问题
我们需要把触摸屏的坐标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);
}
}