在電阻式的觸摸屏上開發嵌入式應用時,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校準程式。