To accept input from a touchscreen we have to use the event interface of the Linux input system. We use the ioctl capabilities of the event interface, in addition to the normal read and write calls to get information from the touchscreen. This blog post explains how to use the touchscreen within your own programs using C as well as writing directly to the framebuffer.
Images of my TFT from a previous post;
![]() | ![]() | ![]() |
The touch screen I have is a 3.2" TFT from SainSmart. The controller for the touchscreen on this TFT is an ADS7846.
The principles are the same for other controllers and the code attached will work with some modifications.
[wp_ad_camp_3]
Git repository here
The code can be pulled down to your Raspberry Pi with;
Linux Input System
To accept input from a touchscreen we have to use the event interface of the Linux input system.
We use the ioctl capabilities of the event interface, in addition to the normal read and write calls to get information from the touchscreen.
To view the input devices on your system, use cat /proc/bus/input/devices
Below is the output from my Raspberry Pi.
I: Bus=0000 Vendor=0000 Product=0000 Version=0000
N: Name="ADS7846 Touchscreen"
P: Phys=spi0.1/input0
S: Sysfs=/devices/platform/bcm2708_spi.0/spi_master/spi0/spi0.1/input/input0
U: Uniq=
H: Handlers=mouse0 event0
B: PROP=0
B: EV=b
B: KEY=400 0 0 0 0 0 0 0 0 0 0
B: ABS=1000003
The above output shows that the handler for my touchscreen is event0, or "/dev/input/event0".
An example ioctl is shown below, this gets the name of the device using EVIOCGNAME.
EVIOCGNAME is defined in linux/input.h. The code below will return "ADS7846 Touchscreen" for my touchscreen.
char name[256] = "Unknown"; ioctl(fd, EVIOCGNAME(sizeof(name)), name); printf("Input device name: \"%s\"\n", name);
The event interface has a number of features and capabilities to help control the input device.
- EV_SYN: Indicates a new event.
- EV_KEY: absolute binary results, such as keys and buttons.
- EV_REL: relative results, such as the axes on a mouse.
- EV_ABS: absolute integer results, such as the axes on a joystick or touchscreen.
- EV_MSC: miscellaneous uses that didn't fit anywhere else.
- EV_LED: LEDs and similar indications.
- EV_SND: sound output, such as buzzers.
- EV_REP: enables autorepeat of keys in the input core.
- EV_FF: sends force-feedback effects to a device.
- EV_PWR: power management events.
- EV_FF_STATUS: device reporting of force-feedback effects back to the host.
The above features are listed in linux/input.h as;
#define EV_SYN 0x00 #define EV_KEY 0x01 #define EV_REL 0x02 #define EV_ABS 0x03 #define EV_MSC 0x04 #define EV_LED 0x11 #define EV_SND 0x12 #define EV_REP 0x14 #define EV_FF 0x15 #define EV_PWR 0x16 #define EV_FF_STATUS 0x17
Under the above event types there are more features and event codes. These are used to give more specific information. E.g. X axis, key pressed, button press on a joystick, etc..
More information can be found here;
https://www.kernel.org/doc/Documentation/input/event-codes.txt
https://www.kernel.org/doc/Documentation/input/input.txt
The drivers I use for my touchscreen uses these options and codes;
Event type 0 (EV_SYN) Event type 1 (EV_KEY) Event code 330 (BTN_TOUCH) Event type 3 (EV_ABS) Event code 0 (ABS_X) Value 1209 Min 280 Max 3830 Event code 1 (ABS_Y) Value 2771 Min 190 Max 3830 Event code 24 (ABS_PRESSURE) Value 0 Min 0 Max 15000
From the above output;
- EV_SYN(Sync) will be used to indicate a new event.
- EV_KEY (Key) is used to indicate the screen touch state has changed. And uses the code 330.
- EV_ABS (Absolute) Absolute value used for X & Y coordinates and pressure.
I have included code to get these values. You can also use evtest to crawl your input device for these features and codes.
Below is an example output from the my code for some of the touchscreen events;
Event type is Key & Event code is TOUCH(330) & Event value is 1 = Touch Starting Event type is Absolute & Event code is X(0) & Event value is 2163 = Raw X Event type is Absolute & Event code is X(1) & Event value is 2193 = Raw Y Event type is Absolute & Event code is X(24) & Event value is 13806 = Pressure Event type is Sync = Start of New Event Event type is Absolute & Event code is X(0) & Event value is 2178 = Raw X Event type is Absolute & Event code is X(1) & Event value is 2204 = Raw Y Event type is Absolute & Event code is X(24) & Event value is 13637 = Pressure Event type is Sync = Start of New Event Event type is Absolute & Event code is X(0) & Event value is 2143 = Raw X Event type is Absolute & Event code is X(1) & Event value is 2249 = Raw Y Event type is Absolute & Event code is X(24) & Event value is 13405 = Pressure Event type is Sync = Start of New Event Event type is Key & Event code is TOUCH(330) & Event value is 0 = Touch Finished
- Line 1 indicates touch is starting.
- Lines 3,4,5 are the raw values for X, Y and Pressure values
- Line 5 indicates the start of a new event.
- Line 6 to 12 are three more events. This is me sliding my finger across the touchscreen.
- Lines 13,14 & 15 is a new event indicating that touch has finished.
The Code
In the attached code "touch.c" contains the 3 main functions related to the touchscreen;
int openTouchScreen() void getTouchScreenDetails(int *screenXmin,int *screenXmax,int *screenYmin,int *screenYmax) void getTouchSample(int *rawX, int *rawY, int *rawPressure)
Open the display
First, we need to open the display (event0)
int openTouchScreen() { if ((fd = open("/dev/input/event0", O_RDONLY)) < 0) { return 1; } }
Retrieving initial details from the touchscreen
The code below is the section of getTouchScreenDetails() that retrieves some initial details from the touchscreen. These include the name of the driver, supported codes used and very importantly the min and max values for X & Y coordinates, which are passed to *screenXmin, *screenXmax, *screenYmin, *screenYmax.
void getTouchScreenDetails(int *screenXmin,int *screenXmax,int *screenYmin,int *screenYmax) { for (i = 0; i < EV_MAX; i++) if (test_bit(i, bit[0])) { printf(" Event type %d (%s)\n", i, events ? events : "?"); if (!i) continue; ioctl(fd, EVIOCGBIT(i, KEY_MAX), bit); for (j = 0; j < KEY_MAX; j++){ if (test_bit(j, bit)) { printf(" Event code %d (%s)\n", j, names ? (names[j] ? names[j] : "?") : "?"); if (i == EV_ABS) { ioctl(fd, EVIOCGABS(j), abs); for (k = 0; k < 5; k++) if ((k < 3) || abs[k]){ printf(" %s %6d\n", absval[k], abs[k]); if (j == 0){ if (absval[k] == "Min ") *screenXmin = abs[k]; if (absval[k] == "Max ") *screenXmax = abs[k]; } if (j == 1){ if (absval[k] == "Min ") *screenYmin = abs[k]; if (absval[k] == "Max ") *screenYmax = abs[k]; } } } } } } }
Sampling the touchscreen
To poll the touchscreen for input events, getTouchSample() is used in the main loop. This function also outputs all events to the console. The important lines of code are where we match event type EV_ABS and codes 0,1 & 330 which are X, Y and Pressure respectively. The Event values are then passed back to main using the pointers *rawX, *rawY and *rawPressure.
void getTouchSample(int *rawX, int *rawY, int *rawPressure) rb=read(fd,ev,sizeof(struct input_event)*64); for (i = 0; i < (rb / sizeof(struct input_event)); i++){ if (ev.type == EV_SYN) printf("Event type is %s%s%s = Start of New Event\n",KYEL,events[ev.type],KWHT); else if (ev.type == EV_KEY && ev.code == 330 && ev.value == 1) printf("Event type is %s%s%s & Event code is %sTOUCH(330)%s & Event value is %s1%s = Touch Starting\n", KYEL,events[ev.type],KWHT,KYEL,KWHT,KYEL,KWHT$ else if (ev.type == EV_KEY && ev.code == 330 && ev.value == 0) printf("Event type is %s%s%s & Event code is %sTOUCH(330)%s & Event value is %s0%s = Touch Finished\n", KYEL,events[ev.type],KWHT,KYEL,KWHT,KYEL,KWHT$ else if (ev.type == EV_ABS && ev.code == 0 && ev.value > 0){ printf("Event type is %s%s%s & Event code is %sX(0)%s & Event value is %s%d%s = Raw X\n", KYEL,events[ev.type],KWHT,KYEL,KWHT,KYEL,ev.value,KWHT); *rawX = ev.value; } else if (ev.type == EV_ABS && ev.code == 1 && ev.value > 0){ printf("Event type is %s%s%s & Event code is %sX(1)%s & Event value is %s%d%s = Raw Y\n", KYEL,events[ev.type],KWHT,KYEL,KWHT,KYEL,ev.value,KWHT); *rawY = ev.value; } else if (ev.type == EV_ABS && ev.code == 24 && ev.value > 0){ printf("Event type is %s%s%s & Event code is %sX(24)%s & Event value is %s%d%s = Pressure\n", KYEL,events[ev.type],KWHT,KYEL,KWHT,KYEL,ev.value,KWHT); *rawPressure = ev.value; } }
Framebuffer
The framebuffer can be accessed through special device nodes, usually located in the/dev directory, i.e. /dev/fb*.
For my TFT the framebuffer is /dev/fb1.
The framebuffer can be accessed using ioctl calls. Similar to the input subsystem.
Before we can draw on the frame buffer, we need to open it and then store the space usde by the framebuffer in memory. We then write to this space to draw to the TFT.
In the attached code “framebuffer.c” contains the 3 main functions related to the framebffer;
int framebufferInitialize(int *xres, int *yres) void drawSquare(int x, int y) void put_pixel_16bpp(int x, int y, int r, int g, int b)
Initialize the framebuffer
The main will call framebufferInitialize() to initialize the frame buffer.
int framebufferInitialize(int *xres, int *yres) { char *fbdevice = "/dev/fb1" ; fb = open(fbdevice, O_RDWR); if (fb == -1) { perror("open fbdevice"); return -1; } if (ioctl(fb, FBIOGET_FSCREENINFO, &fix) < 0) { perror("ioctl FBIOGET_FSCREENINFO"); close(fb); return -1; } if (ioctl(fb, FBIOGET_VSCREENINFO, &var) < 0) { perror("ioctl FBIOGET_VSCREENINFO"); close(fb); return -1; } printf("Original %dx%d, %dbpp\n", var.xres, var.yres, var.bits_per_pixel ); // Store for reset (copy vinfo to vinfo_orig) memcpy(&orig_var, &var, sizeof(struct fb_var_screeninfo)); printf("Framebuffer %s%s%s resolution;\n",KYEL, fbdevice, KWHT); printf("%dx%d, %d bpp\n\n\n", var.xres, var.yres, var.bits_per_pixel ); // map framebuffer to user memory screensize = fix.smem_len; fbp = (char*)mmap(0, screensize, PROT_READ | PROT_WRITE, MAP_SHARED, fb, 0); if ((int)fbp == -1) { printf("Failed to mmap.\n"); } *xres = var.xres; *yres = var.yres; }
- Line 5 opens the framebuffer device for reading and writing
- Line 11 uses the ioctl call FBIOGET_FSCREENINFO to get the fixed screen information. Which we will use later to map some space in memory.
- Line 17 uses the ioctl call FBIOGET_VSCREENINFO to get the variable screen information. Which later we will use to work out the X and Y resolution of the screen.
- Line 35 maps the framebuffer to memory using the pointer fbp
- Lines 46 & 47 will return X and Y resolutions to main
More information here;
https://www.kernel.org/doc/Documentation/fb/framebuffer.txt
Draw Object
The drawSquare() function is a very simple function used to draw a square on the TFT where it has been touched. It uses put_pixel_16bpp to paint a pixal to the TFT.
I subtracted 2 from both X and Y to place the square in the center of the touch point.
void drawSquare(int x, int y) for ( h = 0; h< height;h++) for ( w = 0; w< width;w++) put_pixel_16bpp( h+(x-2), w+(y-2) , def_r[YELLOW],def_g[YELLOW],def_b[YELLOW]);
Paint a pixel on the TFT
put_pixel_16bpp() is used to paint a pixel on a 16Bit Per Pixel screen. The X and Y values correlate to the screen resolution. It also accepts R,G & B values for color.
void put_pixel_16bpp(int x, int y, int r, int g, int b) { unsigned int pix_offset; unsigned short c; //pixel's byte offset inside the buffer pix_offset = x*2 + y * fix.line_length; //some magic to work out the color c = ((r / 8) << 11) + ((g / 4) << 5) + (b / 8); // write 'two bytes at once' *((unsigned short*)(fbp + pix_offset)) = c; }
The main program
There are two things I haven't covered yet and that is calibrating of the touchscreen and jitter.
This blog entry wont cover calibration.
Jitter is when some of your sampling results for the X or Y values are out. Nearly all touchscreen do this. A quick fix is to collect a number of samples and then average the value. The are also other complex libraries available that do a lot better job at reducing jitter. Eg. tslib
We do need to scale the values for the X and Y coordinates read from the touchscreen as they are at a higher sensitivity than what the resolution of the TFT is.
My touchscreen returns a value between 190/280 and 3830 for the X or Y readings. So we need to scale this to match the resolution of the TFT, which is 320x240.
I use the values returned from getTouchScreenDetails() and framebufferInitialize() to work out what the scale value is.
scaleXvalue = ((float)screenXmax-screenXmin) / xres; printf ("X Scale Factor = %f\n", scaleXvalue); scaleYvalue = ((float)screenYmax-screenYmin) / yres; printf ("Y Scale Factor = %f\n", scaleYvalue);
The main loop is below. To try and reduce jitter, i take a number of samples and then average them.
while(1){ for (sample = 0; sample < SAMPLE_AMOUNT; sample++){ getTouchSample(&rawX, &rawY, &rawPressure); Xsamples[sample] = rawX; Ysamples[sample] = rawY; } Xaverage = 0; Yaverage = 0; for ( x = 0; x < SAMPLE_AMOUNT; x++ ){ Xaverage += Xsamples[x]; Yaverage += Ysamples[x]; } Xaverage = Xaverage/SAMPLE_AMOUNT; Yaverage = Yaverage/SAMPLE_AMOUNT; scaledX = Xaverage / scaleXvalue; scaledY = Yaverage / scaleYvalue; drawSquare(scaledX, scaledY); }
[wp_ad_camp_3]
Further reading;
Raspberry Pi low Level graphics" target="_blank
Valdodov's TFT and touchscreen driver
Getting started with the input subsystem
How to calibrate a touchscreen
http://www.raspberrypi.org/phpBB3/viewtopic.php?f=41&t=44977
http://www.raspberrypi.org/phpBB3/viewtopic.php?f=41&t=33679
Raspberry Pi with a 3.2″ TFT with Touch control
Thanks for the post, its very usefull.
We are trying to port your code to a c++ toolkit called openframeworks but we are stock on the event part that we can not compile
https://github.com/mwilliams03/Pi-Touchscreen-basic/blob/master/touch.h
Here you can read about
http://forum.openframeworks.cc/t/pitft-adafruit-tft-touchscreen/15107/8
maybe you can give us some clue
thanks again!!!
In that repo, there are two programs. One that creates buttons on the TFT which is used to control the GPIO of the Raspberry Pi.
https://github.com/mwilliams03/Pi-Touchscreen-basic/blob/master/buttonExample.c
The other is a simple paint program for the TFT. Which reads touch events and then writes pixels to the framebuffer.
https://github.com/mwilliams03/Pi-Touchscreen-basic/blob/master/main.c
just do a
make main
to compileHi mwilliams,
this post was very useful to me.. currently im need to simulate/inject a touch screen event without user input at driver level.. is it possible in ads784? my academic project is related to this.. so need your help in this..your attached codes read the inputs from the driver.. but is it possible to inject an user touch event?
Pls do contact me through my mail id mentioned below.. or pls share your id..
This is quite useful for a home project I am working on. Thanks for this! One question… my touch input is 90 degrees rotated from the display. So the red and blue button touches are actually above and below the green button. Is there an easy way to rotate the touch events so they correspond to the display buttons?
I have the same problem of the input being 90 degrees off from the screen. Any update where to change that?
Try swapping X and W input.
In the code, change this;
scaledX = rawX/scaleXvalue;
scaledY = rawY/scaleYvalue;
to this;
scaledY = rawX/scaleXvalue;
scaledX = rawY/scaleYvalue;
let me know if it works and I will added a note in the post.
Did you ever get the touch input to rotate 90deg to match the screen? It’s been frustrating and can’t find any post on it anywhere..
Im using the 3.5 ” Adafruit screen
Thanks
Mark,
I have the same 90 degree off touch screen problem , but I am a noob I guess , because these instructions seem somewhat vague.
What file are you referring to that needs the change posted above?
there two programs in the git repo.
main.c is used to draw on the screen
buttonExample.c is to create virtual buttons.
If you are having the 90 degree issue with either file, just change this
getTouchSample(&rawX, &rawY, &rawPressure);
to
getTouchSample(&rawY, &rawX, &rawPressure);
This snippet of code is found in the main loop of both files
Thanks for the post, its very good!
I draw the images to framebuffer, and see blinking cursor on the screen.
How to programmatically disable blinking text cursor on the framebuffer screen?
Thanks.
Thanks for posting this, I just tried your evtest program on my Raspberry Pi 2 equipped with the 7″ multi-touch TFT and it produces numbers!! I don’t know what they all mean yet buts its a step down the road to getting my touch user interface off the ground.
For any ones information who is interested, the evtest program asked me to choose an interface number and for the touch screen it was ‘3’
Philip J
Hello Philip, can you tell me how you did, because I got segmentation error with my official display ?
Where is the “link” between “framebuffer” and SPI device ?
I am just tying to “output” to SPI attached on RPi as SPI 0.
What did I missed?
This was a great tutorial. I have a very tangent question – being a hardware noob. How do you get your jumpers to stay secure on your pins ? my jumpers feel so small I am at a loss.
Thank you!
just found your code when searching for XPT2026 touch screen.
Excellent work ! It helps me a lot with my new Raspberry control board, thank you !
Mark;
I am the original creator, and still, the guy behind Linux point of sale, ViewTouch (GPLv3 and on github, yeah). Our touchscreen code was written in 1995 and I’m sure is not handling touch events using any of the many things learned about handling touch events in the past 25 years. I’m not a programmer but I do know that we treat a touch event as a mouse click. There is an obvious advantage to that but still, I would like to ask if you or anyone you know would be able to take a look at the code and see if there is something that can be done to refactor/update the code that handles touch events.
Absolutely brilliant. Thank you Mark for a really useful set of examples. Got adafruit my PiTFT 3.5″ working in minutes – saved me hours of slog. I had to use your
getTouchSample(&rawY, &rawX, &rawPressure);
trick to swap X Y and it worked fine.
Just need to find out how to calibrate the touch now…