Programming for a Touchscreen on the Raspberry Pi

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;

TFT TFT TFT

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.


Git repository here
The code can be pulled down to your Raspberry Pi with;

pi@raspberrypi ~ $ git clone https://github.com/mwilliams03/BerryIMU.git

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.

pi@raspberrypi ~/ $ cat /proc/bus/input/devices
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.

  1. EV_SYN: Indicates a new event.
  2. EV_KEY: absolute binary results, such as keys and buttons.
  3. EV_REL: relative results, such as the axes on a mouse.
  4. EV_ABS: absolute integer results, such as the axes on a joystick or touchscreen.
  5. EV_MSC: miscellaneous uses that didn’t fit anywhere else.
  6. EV_LED: LEDs and similar indications.
  7. EV_SND: sound output, such as buzzers.
  8. EV_REP: enables autorepeat of keys in the input core.
  9. EV_FF: sends force-feedback effects to a device.
  10. EV_PWR: power management events.
  11. 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.

pi@raspberrypi ~/ $ sudo apt-get install evtest

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.

Raspberry Pi LED cube VoxCube

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 320×240.

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);
        }

Further reading;

Raspberry Pi low Level graphics” target=”_blank

notro’s fbtft Wiki

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

12 thoughts on “Programming for a Touchscreen on the Raspberry Pi”

  1. Hi 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..

  2. 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?

      1. 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.

    1. 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

  3. 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?

  4. 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

  5. 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.

  6. 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

Leave a Reply

Your email address will not be published. Required fields are marked *