在电阻式的触摸屏上开发嵌入式应用时,Qt+tslib的组合很常见,可谓经典组合。tslib库中提供了一些测试程序,比如ts_calibrate,ts_test,可以用来对触摸屏进行校准,测试。在Qt中也提供了一个例程,也是用来触屏校准的(http://doc.qt.io/qt-4.8/qt-qws-mousecalibration-calibration-cpp.html) ,Qt库中把触摸屏模拟成鼠标设备来实现,我试了这种方法,发现校准后在/etc/pointercal产生的数据不大对,和ts_calibrate程序产生的数据相差很大,校准后触屏无法正常使用,而且用鼠标点点点也能触发校准完成。由于我的项目应用中是鼠标和触屏混用的,这种校准方式不可取,于是就想到了利用tslib库中的方法,来实现Qt上的触摸屏校准应用。
首先分下下tslib库中的test/ts_calibrate.c程序,去除显示部分(显示校准点十字框),代码如下。
typedef struct {
int x[5], xfb[5];
int y[5], yfb[5];
int a[7];
} calibration;
int perform_calibration(calibration *cal) {
int j;
float n, x, y, x2, y2, xy, z, zx, zy;
float det, a, b, c, e, f, i;
float scaling = 65536.0;
...
}
static void get_sample (struct tsdev *ts, calibration *cal,
int index, int x, int y, char *name)
{
...
put_cross(x, y, 2 | XORMODE);
...
getxy (ts, &cal->x [index], &cal->y [index]);
cal->xfb [index] = x;
cal->yfb [index] = y;
}
int main()
{
struct tsdev *ts; calibration cal;
int cal_fd; char cal_buffer[256];
char *tsdevice = NULL; char *calfile = NULL;
if( (tsdevice = getenv("TSLIB_TSDEVICE")) != NULL ) {
ts = ts_open(tsdevice,0);
} else {
...
}
if (ts_config(ts)) {
perror("ts_config");
exit(1);
}
get_sample (ts, &cal, 0, 50, 50, "Top left");
get_sample (ts, &cal, 1, xres - 50, 50, "Top right");
get_sample (ts, &cal, 2, xres - 50, yres - 50, "Bot right");
get_sample (ts, &cal, 3, 50, yres - 50, "Bot left");
get_sample (ts, &cal, 4, xres / 2, yres / 2, "Center");
if (perform_calibration (&cal)) {
if ((calfile = getenv("TSLIB_CALIBFILE")) != NULL) {
cal_fd = open (calfile, O_CREAT | O_RDWR);
} else {
cal_fd = open ("/etc/pointercal", O_CREAT | O_RDWR);
}
sprintf (cal_buffer,"%d %d %d %d %d %d %d",
cal.a[1], cal.a[2], cal.a[0],
cal.a[4], cal.a[5], cal.a[3], cal.a[6]);
write (cal_fd, cal_buffer, strlen (cal_buffer) + 1);
close (cal_fd);
} else {
printf("Calibration failed.\n");
}
}
分析下ts_calibrate.c中程序的校准流程如下。
程序中,触摸屏设备TSLIB_TSDEVICE校准文件TSLIB_CALIBFILE都是从环境变量中获取的,在配置tslib库中应该指定。TSLIB_TSDEVICE一般是/dev/input/event0文件,这个跟触屏设备有关,可以用cat /proc/bus/input/devices命令查看确认,或者直接在终端用cat /dev/input/event0命令,然后点击触摸屏,会有数据打印出。TSLIB_TSCALIBFILE一般是/etc/pointercal文件,文件中记录了计算后的校准值。通过ts_open()、ts_config()函数打开和初始化触摸屏设备后,在get_sample()函数中通过put_cross()函数显示光标,然后通过getxy()函数等待用户点击触屏上的光标,获取触点数据,然后存在cal结构体中。获取完5个校准点(左上、右上、右下、左下、中间)数据后,通过perform_calibration()进行计算,把校准值写入到/etc/pointercal文件中。
我们把ts_calibrate.c的程序移植到Qt应用中,显示部分肯定不能用在tslib库中的了。tslib库中是c程序,显示都是直接操作framebuffer,而Qt作为跨平台的GUI库,显示是基本功能,直接把ts_calibrate.c中校准部分移植过来即可,这样代码改动量最小。
校准程序很简单,要注意的一点是,getxy()函数是异步的,会阻塞等待用户点击触摸屏上的校准点,因此,getsample()函数有必要放到线程中运行,与Qt GUI线程分离开来。在Qt中进行校准的流程与tslib库中完全一致,就是显示部分采用Qt的库,代码参考了Qt库中的qws/mousecalibration/calibration.cpp文件。这里进行简要分析下。
首先是调用方式,把校准流程封装到Calibration类中,调用时,两行代码即可。
Calibration cal;
cal.exec();
在实例化Calibration对象后,会弹出一个校准界面,覆盖原有程序界面,校准完成后,返回原有程序界面。这个流程跟弹出一个对话框,点击对话框后返回类似。事实上,Calibration就是继承自QDialog类的。首先分析下Calibration类的构造函数。
Calibration::Calibration()
{
QRect desktop = QApplication::desktop()->geometry();
desktop.moveTo(QPoint(0, 0));
setGeometry(desktop);
setFocusPolicy(Qt::StrongFocus);
setFocus();
setModal(true);
int width = qt_screen->deviceWidth();
int height = qt_screen->deviceHeight();
int dx, dy;
QPoint *points = data.screenPoints;
dx = 50;
dy = 50;
points[0] = QPoint(dx, dy);
points[1] = QPoint(width - dx, dy);
points[2] = QPoint(width - dx, height - dy);
points[3] = QPoint(dx, height - dy);
points[4] = QPoint(width / 2, height / 2);
pressCount = 0;
}
构造函数中,定义了一个Qt窗口(全屏),把它设置为模态的,然后定义了要校准显示的5个十字光标的位置。再来看下exec()函数。
int Calibration::exec()
{
activateWindow();
calibThread = new CalibThread;
if(calibThread->Config()){
connect(calibThread, SIGNAL(nextPoint()),
this, SLOT(OnNextPoint()), Qt::QueuedConnection);
calibThread->start();
int ret = QDialog::exec();
return ret;
}else{
delete calibThread;
return 0;
}
}
在exec()函数中,首先将构造函数中定义的窗口置顶显示(这里会进行一次窗口刷新),然后定义了校准线程,注意这里校准线程与Calibration对象有一个信号和槽的绑定,作用后面会介绍。最后定义了一个QDialog(无显示内容)对象,让其以模态的方式运行。
接下来看校准线程,校准线程与tslib库中的程序就很一致了。其构造函数和run()函数如下。
CalibThread::CalibThread()
{
}
bool CalibThread::Config()
{
char *tsdevice = NULL;
if( (tsdevice = getenv("TSLIB_TSDEVICE")) != NULL ) {
ts = ts_open(tsdevice,0);
Log_d(QString("getenv TSLIB_TSDEVICE: %1").arg(tsdevice));
} else {
Log_e(QString("getenv TSLIB_TSDEVICE NULL").arg(tsdevice));
return false;
}
if (!ts) {
Log_e("ts NULL");
return false;
}
if (ts_config(ts)) {
Log_e("ts_config error");
return false;
}
xres = qt_screen->deviceWidth();
yres = qt_screen->deviceHeight();
Log_d(QString("ts_config ok, xres: %1, yres: %2").arg(xres).arg(yres));
return true;
}
void CalibThread::run()
{
get_sample (ts, &cal, 0, 50, 50, (char*)"Top left");
emit nextPoint();
get_sample (ts, &cal, 1, xres - 50, 50, (char*)"Top right");
emit nextPoint();
get_sample (ts, &cal, 2, xres - 50, yres - 50, (char*)"Bot right");
emit nextPoint();
get_sample (ts, &cal, 3, 50, yres - 50, (char*)"Bot left");
emit nextPoint();
get_sample (ts, &cal, 4, xres / 2, yres / 2, (char*)"Center");
emit nextPoint();
}
线程中阻塞运行get_sample()函数5次,每次获取完校准点数据后,发射nextPoint()信号,通知Calibration类进行界面刷新,在下一个校准点显示十字光标。Calibration类中的槽函数和刷新函数如下。
void Calibration::paintEvent(QPaintEvent*)
{
QPainter p(this);
p.fillRect(rect(), Qt::white);
QPoint point = data.screenPoints[pressCount];
// Map to logical coordinates in case the screen is transformed
QSize screenSize(qt_screen->deviceWidth(), qt_screen->deviceHeight());
point = qt_screen->mapFromDevice(point, screenSize);
p.fillRect(point.x() - 6, point.y() - 1, 13, 3, Qt::black);
p.fillRect(point.x() - 1, point.y() - 6, 3, 13, Qt::black);
}
void Calibration::accept()
{
if(pressCount == 5){
Log_d("Calibration accept success, pressCount: 5");
}else{
Log_d("Error, pressCount is not 5");
return;
}
if (calibThread->Perform_calibration()) {
calibThread->Calibration_write();
} else {
Log_e("perform_calibration failed.");
}
delete calibThread;
QWSServer::instance()->closeMouse();
QWSServer::instance()->openMouse();
QDialog::accept();
}
void Calibration::OnNextPoint()
{
if (++pressCount < 5)
repaint();
else
accept();
}
在Calibration类槽函数OnNextPoint()中,首先判断校准完5个点没,没有,则刷新绘制下一个点(在paintEvent()函数中完成),校准完,则跳转到accept()函数中进行校准值计算,并写入校准文件。程序中get_sample()函数,perform_calibration()函数都与tslib库中的完全一致,相当于调用了tslib中原生的校准方法。写校准值函数的程序如下。
int CalibThread::Calibration_write()
{
char *calfile = NULL;
char cal_buffer[256];
unsigned int len;
QFile file;
if ((calfile = getenv("TSLIB_CALIBFILE")) != NULL) {
file.setFileName(calfile);
if(!file.open(QIODevice::ReadWrite | QIODevice::Text)){
Log_e("file.open error");
return -1;
}
} else {
Log_e("TSLIB_CALIBFILE NULL");
return -2;
}
len = sprintf(cal_buffer,"%d %d %d %d %d %d %d %d %d ",
cal.a[1], cal.a[2], cal.a[0],
cal.a[4], cal.a[5], cal.a[3], cal.a[6],
xres, yres);
Log_d("Calibration constants: " + cal_buffer);
file.write(cal_buffer, len);
file.close();
return 0;
}
完整的程序下载链接Qt tslib校准程序。