Raspberry Pi, GfxLCD and touch panel AD7843

At least, I mentioned touch so many times. Today we will create a new driver and add it to GfxLCD library.

GfxLCD @ GitHub

Planning

My LCD has an AD7843 chipset for touch support. It has an SPI interface.
What we gonna do is to create a function to track touches.
I’m not sure if will be able to use an interrupt but this would be perfect. If not we still have good old loop 🙂
Touch input has a big signal noise, this is a problem so we need a good filtering algorithm.
We will start with displaying (x, y) of the touch point.

Driver

We need to prepare few variables:

class AD7843(object):
    def __init__(self, width, height, spi=0):
        self.width = width
        self.height = height
        self.spi = spidev.SpiDev()
        self.spi.open(spi, 0)
        self.spi.max_speed_hz = 2000000
        self.spi.mode = 0
        self.correction = {
            'x': 364,
            'y': 430,
            'ratio_x': 14.35,
            'ratio_y': 10.59
        }

One thing requires special attention, variable self.correction.
Our touch sensor is far from perfection and additionally, each unit is slightly different. That’s why we need to compensate for errors.
In my case screen was slight moved in x any y so I had to compensate for it. And additionally display has (0, 0) at top-left corner but touch (0, 0) is at bottom-right.
This is very bad 🙂 That’s why I used 364 and 430 values. In your case, they may differ, for now, we have to find correct values by hand.
Ratios are also found by hand.

To get x and y we need to send a command to the panel and read a response. There is one catch 🙂 It is 12b bit interface. So it is another complication but with google and http://www.avrfreaks.net/forum/avr-ad7843
we made it:

    def get_x(self, value):
        """correct value to x"""
        return self.width - int((value - self.correction['x']) / self.correction['ratio_x'])

    def get_y(self, value):
        """correct value to y"""
        return self.height - int((value - self.correction['y']) /  self.correction['ratio_y'])

    def get_position(self):
        """get touch coords"""
        self.spi.xfer2([0xd0])
        rx = self.spi.readbytes(2)
        self.spi.xfer2([0x90])
        ry = self.spi.readbytes(2)

        tc_rx = rx[0] << 5 tc_rx |= rx[1] >> 3

        tc_ry = ry[0] << 5 tc_ry |= ry[1] >> 3

        x = self.get_x(tc_rx)
        y = self.get_y(tc_ry)
        if x < 0 or x > self.width or y < 0 or y > self.height:
            return None

        return x, y

Now, we just need the test code:

import RPi.GPIO
import sys
sys.path.append("../../")
from gfxlcd.driver.ad7843.ad7853 import AD7843
RPi.GPIO.setmode(RPi.GPIO.BCM)

touch = AD7843(240, 320)

while True:
    try:
        ret = touch.get_position()
        if ret:           
            print(ret[0], ret[1])

    except KeyboardInterrupt:
        touch.close()

This shows some readings:

67 145
70 147
74 150
79 150
79 150
75 150
83 149
81 148
79 148
79 147
94 147
121 124
159 86

and the big problem. After touching the panel I didn’t move a finger and readings are quite spread.

Touch

What can we do to with readings? We could read like 10-20 samples and calculate the average?
Let’s see if this has some sense. I pressed screen around (80, 150). Using average on above reading we get
1140/13 = 88 and 1841/13 = 142. Not bad.
Let’s try this method:

    def get_position(self):
        """get touch coords"""
        buffer = []
        while len(buffer) < 20:
            self.spi.xfer2([0xd0])
            rx = self.spi.readbytes(2)
            self.spi.xfer2([0x90])
            ry = self.spi.readbytes(2)

            tc_rx = rx[0] << 5 tc_rx |= rx[1] >> 3

            tc_ry = ry[0] << 5 tc_ry |= ry[1] >> 3

            x = self.get_x(tc_rx)
            y = self.get_y(tc_ry)
            if x < 0 or x > self.width or y < 0 or y > self.height:
                return None
            buffer.append((x, y))

        return self._calculate_avr(buffer)

    def _calculate_avr(self, points):
        """calculate x,y by average"""
        sum_x = 0
        sum_y = 0
        for point in points:
            sum_x += point[0]
            sum_y += point[1]

        return int(sum_x / len(points)), int(sum_y / len(points))

Readings are much better:

97 146
97 146
97 146
98 147
100 147
101 147
101 148
107 147
128 139

Those 5 last reading are taken during finger removal. Interesting.
Let’s say that they are good enough.

Now we will check if we can get something from an interrupt.

Interrupt

This is a nice functionality. When panel detects touch it inform us and we can get readings. It is much better that a loop.
Let’s see if we can get it working.
We need to wire T_IRQ with RPi, In my case, it is pin 14. If we have a pin, we need to send it to our driver class. It is also a good idea to send a function that would be a callback.
So in main, I have:


def callback(position):
    print('(x,y)', position)

touch = AD7843(240, 320, 14, callback)

Here we send a pin and callback.

touch.init()

while True:
    try:
        time.sleep(0.05)

    except KeyboardInterrupt:
        touch.close()

Ok. Now let’s upgrade a driver class:

    def __init__(self, width, height, int_pin=None, callback=None, spi=0):
        (..)
        self.int_pin = int_pin
        self.callback = callback

    def init(self):
        """some init functions"""
        if self.int_pin:
            RPi.GPIO.setup(self.int_pin, RPi.GPIO.IN)
            RPi.GPIO.add_event_detect(self.int_pin, RPi.GPIO.FALLING, callback=self._interrupt, bouncetime=300)

    def _interrupt(self, channel):
        """call users callback"""
        self.callback(self.get_position())

Ok. What we have here. We attach the pin to a  _interrupt function. So when we get an interrupt, we call user function with (x, y).

To my surprise, it worked. This is unexpected but very welcome. But I have some bad readings:

(x,y) (95, 82)
(x,y) (119, 92)
(x,y) None
(x,y) (134, 152)

I think we need to change function get_position a little. We were returning None if the faulty read was taken. Maybe we should change it, so faulty reads are ignored:

            if x >= 0 and x <= self.width and y >= 0 and y <= self.height:
                buffer.append((x, y))

And this time it is perfect:)
Finally, a little refactor, I extracted bouncetime to the variable and changed it to 500.

And it stopped working… Woot? Lot’s of undo and.. still dead.. what has happened?
Checked reading in while loop and they are ok. So it didn’t burn.
Raspberry Pi rebooted and…still dead. Wires are ok. No idea was has happened…
But I changed GPIO to #26 and it is back! Uff…

Summary

Yey! Another success:) We added touch support for AD7843 to our GfxLCD library.

Advertisements

One comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s