1. 程序员需要关注的硬件特性
1.1 ADC的特性
- 输出数据用多少位描述 10bit
- 模拟输入最大值 0-3.3V
- 采样/转换速度 每秒转换 500 * 1000 次
1.2 看本SoC ADC细节
选择输入信号,8选1.
1.2.1 如何初始化ADC
说明ADC的工作时钟需要降频,且降频方式是设置预分频器。
ADC 支持 standby mode
什么是 standby mode
ADC 的 standby mode 是一种低功耗模式,在该模式下,ADC 将停止转换并将其电源降至最低水平以节省能源。
在 standby mode 中,ADC 的控制电路和电源电压将被关闭,因此它不会消耗任何额外的功率。在进入 standby mode 前,需要确保 ADC 已经完成了转换并将结果存储在适当的寄存器中,以便在需要时可以读取结果。
当需要重新激活 ADC 以进行转换时,可以使用唤醒事件或外部触发器来触发 ADC,这将使 ADC 进入正常模式并开始转换。在正常模式下,ADC 将消耗更多的功率,但可以进行连续转换以提供更高的采样率。
1.2.2 如何读数据
读ADC数据
- 手动开启转换:通常在中断服务程序中手动开启,开启后,要等待一段时间,检查 ADCCON[15] 确定转换完成,再读取 ADCDAT 寄存器
- 读后启动:当ADCCON[1] 设置了 start-by-read 模式,每当读数据后,ADC会立即启动
由于 ADC 测量范围为 0-3.3V ,位为10bit,所以从 ADCDAT读出的数据和 0-3.3V线性对应。
2. 触摸屏
2.1 触摸屏硬件原理
所以触摸屏的工作流程
- 按下触摸屏,触发中断
do { - 中断处理函数中,启动ADC读取X,Y坐标,中断返回
- ADC处理完成,触发中断
- 在ADC处理函数中,获得X,Y坐标,中断返回
- 启动定时器,定时器中断发生
- 定时器中断中判断触摸屏是否仍然被按下
} while (触摸屏状态为被按下); - 松开
2.2 详细分析4线触摸屏工作原理
所以控制ADC工作的关键就是控制 上拉,XP,YP,XM,YM 开关。
不过实际编码时,SoC提供了 自动测量X,Y模式,当使用次模式时,不需要手动设置这5个开关。
3. 示例代码
#include <linux/module.h>
#include <linux/clk.h>
#include <linux/bitops.h>
#include <linux/input.h>
#include <linux/spinlock.h>
#include <asm/atomic.h>
#include <linux/poll.h>
#include <linux/interrupt.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/irq.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm-arm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
struct ts_regs {
unsigned int adccon;
unsigned int adctsc;
unsigned int adcdly;
unsigned int adcdat0;
unsigned int adcdat1;
unsigned int adcupdn;
};
static struct ts_regs *ts_regs;
static struct input_dev *ts_dev;
static void ts_timeout(unsigned long data);
static struct timer_list ts_timer = TIMER_INITIALIZER(ts_timeout, 0, 0);
inline static void
enter_wait_press_down_mode(void)
{
ts_regs->adctsc = 0xd3;
}
inline static void
enter_wait_press_up_mode(void)
{
ts_regs->adctsc = 0x1d3;
}
inline static void
enable_adc_auto_measure(void)
{
/*
* [2] : 1 自动测量X,Y
* [3] : 1 上拉电阻关闭, 实际测试发现,不需要设置也能正常工作,可能是SoC自动处理了
*/
//ts_regs->adctsc |= (1 << 2) | ( 1 << 3);
ts_regs->adctsc |= (1 << 2);
}
inline static void
start_adc(void)
{
ts_regs->adccon |= (1 << 0); // 手动开启ADC
}
static int
is_exception_data(unsigned int *xs, unsigned int *ys, unsigned int cnt)
{
unsigned int i, offset;
if (cnt <= 1)
return 0;
for (i = 0; i < cnt - 1; i++) {
offset = (xs[i] > xs[i+1] ? xs[i] - xs[i+1] : xs[i+1] - xs[i]);
if (offset > 20)
return 1;
offset = (ys[i] > ys[i+1] ? ys[i] - ys[i+1] : ys[i+1] - ys[i]);
if (offset > 20)
return 1;
}
return 0;
}
static void
ts_timeout(unsigned long data)
{
del_timer(&ts_timer);
if (ts_regs->adcdat0 & (1 << 15)) {
// 张开状态
enter_wait_press_down_mode(); // 触摸笔已经移开,ADC获得的值无效
input_report_abs(ts_dev, ABS_PRESSURE, 0);
input_sync(ts_dev);
}
else {
// 闭合状态, 启动ADC进行测量
enable_adc_auto_measure();
start_adc();
}
}
static irqreturn_t
adc_handler(int irq, void *dev_id)
{
unsigned int x, y, av_x, av_y;
static unsigned int xs[4], ys[4], cnt = 0;
// 读取ADC数据
x = ts_regs->adcdat0 & 0x3ff;
y = ts_regs->adcdat1 & 0x3ff;
if (ts_regs->adcdat0 & (1 << 15)) {
// 触摸屏是张开状态
enter_wait_press_down_mode(); // 触摸笔已经移开,ADC获得的值无效
cnt = 0; // 原有数据清零
input_report_abs(ts_dev, ABS_PRESSURE, 0);
input_sync(ts_dev);
}
else {
// 触摸屏是闭合状态
// 只记录非异常数据
xs[cnt] = x;
ys[cnt] = y;
cnt++;
// 如果缓存的数据中有异常数据,则全部丢弃,重新采集
if (is_exception_data(xs, ys, cnt)) {
cnt = 0;
}
// 当缓存数据不足,则缓存,并重启ADC,进行多次测量
if (cnt < 4) {
enable_adc_auto_measure();
start_adc();
}
else { // 当多次测量够了,则输出,并等待抬起
cnt = 0;
av_x = (xs[0] + xs[1] + xs[2] + xs[3]) / 4;
av_y = (ys[0] + ys[1] + ys[2] + ys[3]) / 4;
enter_wait_press_up_mode();
// 上报事件
input_report_abs(ts_dev, ABS_X, av_x);
input_report_abs(ts_dev, ABS_Y, av_y);
input_report_abs(ts_dev, ABS_PRESSURE, 1);
input_sync(ts_dev);
// printk("x : %d, y : %d\n", av_x, av_y);
// 完成一次测量后,启动定时器, 每隔一段时间启动ADC测量,以处理滑动事件
mod_timer(&ts_timer, jiffies + msecs_to_jiffies(100));
}
}
return IRQ_HANDLED;
}
static irqreturn_t
ts_down_up_handler(int irq, void *dev_id)
{
if (ts_regs->adcdat0 & (1 << 15)) {
// up
enter_wait_press_down_mode();
}
else {
// down
enable_adc_auto_measure(); // 进入自动测量模式, 此时不会产生触摸屏按键中断
start_adc(); // 启动ADC, 一段时间后 ADC 中断触发, ADC启动位会自动清零
}
return IRQ_HANDLED;
}
static int __init
ts_dev_init(void)
{
int ret;
struct clk *clk;
// int input_register_device(struct input_dev *dev)
// 1. 注册 input_device
ts_dev = input_allocate_device();
ts_dev->name = "touch screen";
ts_dev->evbit[0] = BIT(EV_ABS); // 电阻触摸屏产生 绝对位置事件
input_set_abs_params(ts_dev, ABS_X,
0, 0x3FF, 0, 0); // 根据数据手册可知 ADC获得触摸屏的值范围 0-0x3FF
input_set_abs_params(ts_dev, ABS_Y,
0, 0x3FF, 0, 0);
input_set_abs_params(ts_dev, ABS_PRESSURE,
0, 1, 0, 0);
input_register_device(ts_dev);
// 2. 映射TS相关寄存器
ts_regs = ioremap(0x58000000, sizeof(*ts_regs));
// 3. 设置GPIO
// 无需设置
// 4. 硬件操作
// 4.1 使能时钟
clk = clk_get(NULL, "adc");
clk_enable(clk);
// 4.2 设置ADC
/*
* [15] : 只读
* [14] : 1 ADC预分频使能
* [13:6] : 49 预分频值
* [5:3] : 模拟信号输入选择
* [2] : 0 standby mode
* [1] : 0 读后自启动
* [0] : 0 手动启动adc
*/
ts_regs->adccon = (1 << 14) | (50 << 6) ;
ts_regs->adcdly = 0xffff; // 将ADC延迟调到最大,保证测量时电压稳定
enter_wait_press_down_mode();
// 5. 设置中断
ret = request_irq(IRQ_TC, ts_down_up_handler, IRQF_SAMPLE_RANDOM , "touch screen", NULL);
ret = request_irq(IRQ_ADC, adc_handler, IRQF_SAMPLE_RANDOM, "adc", NULL);
// 使用定时器处理滑动事件
init_timer(&ts_timer);
return 0;
}
static void __exit
ts_dev_exit(void)
{
free_irq(IRQ_TC, NULL);
free_irq(IRQ_ADC, NULL);
input_unregister_device(ts_dev);
input_free_device(ts_dev);
iounmap(ts_regs);
del_timer(&ts_timer);
}
module_init(ts_dev_init);
module_exit(ts_dev_exit);
MODULE_LICENSE("GPL");
4. 测试
4.1 使用 evdev测试
确保内核使用 evdev模块
-> Device Drivers
│ -> Input device support
│ -> Generic input layer (needed for keyboard, mouse, ...) (INPUT [=y])
安装触摸屏模块后会生成一个/dev/event,这就是evdev 作为 handler时创建的节点。
# hexdump /dev/event0
时 时间 code value
0000000 02a7 0000 e323 0004 0003 0000(X) 0205 0000 x的值
0000010 02a7 0000 e332 0004 0003 0001(Y) 0366 0000 y的值
0000020 02a7 0000 e336 0004 0003 0018(SYNC) 0001 0000
4.2 使用tslib测试
安装 lcd 驱动 ,触摸屏驱动
安装tslib
sudo apt-get install autoconf
sudo apt-get install automake
sudo apt-get install libtool
编译:
tar xzf tslib-1.4.tar.gz
cd tslib
./autogen.sh
mkdir tmp
echo "ac_cv_func_malloc_0_nonnull=yes" >arm-linux.cache
./configure --host=arm-linux --cache-file=arm-linux.cache --prefix=$(pwd)/tmp
make
make install
安装:
cd tmp
cp * -rdf /nfsroot
配置tslib
1.
修改 /etc/ts.conf第1行(去掉#号和第一个空格):
# module_raw input
改为:
module_raw input
2.
export TSLIB_TSDEVICE=/dev/event0
export TSLIB_CALIBFILE=/etc/pointercal
export TSLIB_CONFFILE=/etc/ts.conf
export TSLIB_PLUGINDIR=/lib/ts
export TSLIB_CONSOLEDEVICE=none
export TSLIB_FBDEVICE=/dev/fb0
运行
ts_calibrate
ts_test