You Don’t Know Where It’s Been
Touchpads and touchscreens have largely replaced mousepads on laptop computers. They are fast, easy to use, and don’t require complex mouse navigation. In this article, Jeff focuses on resistive touchscreens—which respond by applying pressure—and how they can be used as input devices for microcontrollers.
I disabled the touchpad on my laptop. Because it was placed right where I rest my hands for typing, I was always touching and dragging on it at the most inopportune times. My text would be highlighted, and poof—changed to some other typeface or size. Or worse yet, something would be deleted altogether. I know what you’re gonna say—that proper typing posture shouldn’t include resting my palms. I’m too old to change now. If I need to select something, I’ll just use my wireless mouse.
When I bought my last laptop (10 years ago), I splurged for a touchscreen. It was supposed to increase my productivity, but, while it has come in handy, it did not make my job easier. There are many categories of touchpads and touchscreens, such as acoustic wave and infrared, but by far the most popular are capacitive and resistive. A capacitive touchpad senses a change in capacitance when your finger approaches the pad, affecting the electrostatic charge on that area. One drawback to this system is that a gloved hand may not cause enough change to be detected. This article will center on the resistive touchscreen and how it can be used as an input device.
RESISTIVE TOUCH
Let’s dissect Adafruit’s 3.7” diagonal touchscreen and see how it is made. First we define this device as a touchscreen when it is transparent and used atop a display. The combination produces an Input/Output device. A touchpad is usually not transparent, and is used mainly as an Input device. The Adafruit device is transparent, but in this project, I use it as an Input device. It’s transparency allows it to be useful in many different situations.
Usually a resistive touchpad/screen consists of at least three layers: A flexible membrane is suspended over a rigid substrate made from glass or acryl, and the two are kept separated by an insulating spacer along the edges and spacer dots on the inner surface of the two layers (Figure 1). In this way, there is no physical or electrical connection unless pressure is applied to the flexible film. Both inner surfaces are coated with a transparent conductive film such as indium tin oxide (ITO). Although the ITO is conductive, there is a measurable resistance across each layer.

The touchscreen is made up of multiple layers. The layers are held isolated until pressure is applied.
On four-wire touchscreens, there is a pair of electrodes on each ITO layer (Figure 2) The resistance is linear between the electrodes on each substrate, and the two substrates are perpendicular to one another, creating X and Y layers. The electrodes are connected to a touch controller through a four-wire flex cable. The four wires are referred to as X+ (left), X- (right), Y+ (top) and Y- (bottom). An advantage of the four-wire touchscreens is that it is possible to determine the touch pressure by measuring the contact resistance (RTouch) between the two ITO layers (Figure 3). RTouch decreases as the touch pressure (or the size of the depressed area) increases. This characteristic can be useful in applications that require position detection and the amount of pressure.
The connection state of these four wires determines what parameter can be measured. Let’s turn our attention to a device that contains all the parts necessary to do this. Many microcontrollers have the ability to interface directly with these four wires, but there is an advantage to using a dedicated device, which will shortly become apparent. Figure 4 shows the inner workings of the analog front end of the Texas Instruments TSC2007. You’ll note that the X+, X-, Y+, and Y- inputs can be totally disconnected from any internal device through FET switches. The “+” connections can be connected individually to VCC, whereas the “-” connections can be connected individually to GND. Additionally, the ADC references can be VCC/GND or “+”/”-”. The ADC inputs are either single-ended, for measuring onboard temperatures or the aux input, or differential, for measuring the X+, Y+ or Y- inputs.
— ADVERTISMENT—
—Advertise Here—
The TSC2007 is an I2C device with 16 registers that you can access. Table 1 shows these registers and how they affect the internal connections, depending on the register accessed. The values, C4:0, make up the upper nibble of the register of interest. The lower nibble determines some other parameters. The setup command 0xBx has its own definition for the lower nibble (Table 2). The MAV filter takes seven consecutive readings, sorts them, and averages the middle three samples as the reported value. For all other commands, the lower nibble takes on different parameters as shown in Table 3.
The I2C communications are simple (Listing 1). Write the command to the I2C device (0x48), and then read a word from the I2C device. Remember this is a 7-bit address, and it will be shifted left one place to make room for the write=0 read=1 bit. I use an array to hold information used in the transfer of data to and from the I2C device. Command and configuration constants have their values shifted to the right four bit positions within a byte:
const byte measureTemp0 = 0<<4;
const byte measureAux = 2<<4;
const byte measureTemp1 = 4<<4;
const byte activateX = 8<<4;
const byte activateY = 9<<4;
const byte activateXY = 10<<4;
const byte setupCommand = 11<<4;
const byte measureX = 12<<4;
const byte measureY = 13<<4;
const byte measureZ1 = 14<<4;
const byte measureZ2 = 15<<4;
const byte powerdownIRQON = 0<<2;
const byte adONIRQOFF = 1<<2;
const byte adOFFIRQON = 2<<2;
const byte adc12Bit = 0<<1;
const byte adc8Bit = 1<<1;
and then those variables used by the I2C routines:
unsigned int x, y, z1, z2;
byte i2cAddress = 0x48;
byte byteCount;
byte byteArray[255];
The sendCommand() routine handles the writing and reading necessary for each command. Note that data is passed via a byteArray[], one byte written (command) and two bytes read (MSB and LSB).
ADVANTAGE TSC2007
To take a complete set of readings from the attached touchscreen, we need to send five commands, Measure Position X, Measure Position Y, Measure Position Z1, Measure Position Z2, and one final command to put the device into low-power mode. (See Listing 2 for the code.) Sending each command requires the command of interest to be placed in byteArray[0]. After sending the command, the result is placed into the appropriate variables. Note that the 12-bit data returned is in the upper 12 bits of the word placed into byteArray[0] (MSB) and byteArray[1] (LSB). These bytes are rotated right by 4 bits to get the actual value saved in the variable.
Listing 1
These three routines handle all the data communication needed between the micro and the TSC2007.
void sendCommandTSC2007()
{
byteCount = 1;
writeI2C();
//
byteCount = 2;
readI2C();
}
void readI2C()
{
byte i = 0;
if (Wire.requestFrom(i2cAddress, byteCount)) // request byteCount from slave device
{
while (Wire.available()) // slave may send less than requested
{
byteArray[i] = Wire.read(); // receive a byte as character
i++;
}
}
}
void writeI2C()
{
Wire.beginTransmission(i2cAddress); // transmit to I2CADDRESS
for(byte i=0; i<byteCount; i++)
{
Wire.write(byteArray[i]); // sends one byte
Wire.endTransmission(); // stop transmitting
}
}
Listing 2
Retrieving all pertinent data and leaving the device in low-power mode requires sending five commands. Note the Boolean return of the (x < 4096) && (y < 4096). This essentially says return a 1 (OK) if x and y are both less than 0x1000. 0-4095 are legal values for x and y.
boolean readTouch()
{
byteArray[0] = measureX + adONIRQOFF + adc12Bit;
sendCommandTSC2007();
x = (byteArray[0] << 4) + (byteArray[1] >> 4);
//
byteArray[0] = measureY + adONIRQOFF + adc12Bit;
sendCommandTSC2007();
y = (byteArray[0] << 4) + (byteArray[1] >> 4);
//
byteArray[0] = measureZ1 + adONIRQOFF + adc12Bit;
sendCommandTSC2007();
z1 = (byteArray[0] << 4) + (byteArray[1] >> 4);
//
byteArray[0] = measureZ2 + adONIRQOFF + adc12Bit;
sendCommandTSC2007();
z2 = (byteArray[0] << 4) + (byteArray[1] >> 4);
//
byteArray[0] = measureTemp0 + powerdownIRQON + adc12Bit;
sendCommandTSC2007();
return (x < 4095) && (y < 4095);
}
You may have already noticed the pen touch back in Figure 4. Figure 5 shows how the touchscreen is connected in the Power-down mode. The selected pull-up resistor (50kΩ/90kΩ) holds the X+ line HIGH because X- is left floating. The only way X+ can go LOW is for there to be a touch, which connects X+ to GND through Y- ( Y+ is left floating). The summed resistance of Y, Z, and X is still low enough to bring X+ down to a logic LOW. This is reflected via the PENIRQ output, which can be used to signal a touch while in Power-down mode.

The PENIRQ circuit is enabled in the Power-down mode. It is responsible for creating an interrupt that you can use to halt program execution and gather touch data. The alternative is to poll the device periodically for touch action; however, this might tax processor resources.
I used one of the interrupt input pins to capture this state. An interrupt can be set up on the Arduino UNO as follows:
pinMode(interruptPin, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(interruptPin), touch, FALLING);
Doing this will allow a falling edge on the interrupt input pin to force execution flow to a special routine to sample the TSC2007 registers. In the attachInterrupt() command above, the special routine is designated as touch(). This special routine simply sets the volatile Boolean variable state = HIGH.
The main loop of the application can now go about any process it needs to, without having to poll the TSC2007 to find out if a touch has occurred. A simple test of state will determine if the TSC2007 needs attention.
— ADVERTISMENT—
—Advertise Here—
if(!state)
{
waitForTouch();
displayTouchPoint();
displayButton();
state = HIGH;
}
The waitForTouch() routine handles the readTouch() routine and makes sure the results are legal, within the limits of the screen. What this means is that the ADC reports a value between 0 and 4,095 as the touch position, but in reality, you may not be able to get to these values because of the width of your finger tip or the physical attributes of the ITO layer. I added a calibration routine to pick up the minimum and maximum positions when you touch the UL, UR, LL, and LR points of the screen. I use the minimum and maximum from this calibration to determine the legal maximum and minimum points for the X and Y of the touchscreen. I found the conversion values to be a minimum of ~400 and a maximum of ~3,600. These values are stored in EEPROM (within the microcontroller) and used each time the board is powered up. Holding down a push button connected to input D8 will force a re-calibration to occur upon power up.
NOW WHAT?
Now that we can get X, Y, and Z values from the touchscreen, what can we do with this? Many of you have seen a touchscreen similar to this, pre-mounted on some kind of LCD display. The costs for this are more than three or four times the cost of just the touch panel. When you want a dynamic display with reconfigurable touch areas, this combo is a great choice. However, there are times when you won’t need a changing display, and using this touchscreen with a fixed background is a good, inexpensive alternative. This project is appropriate for both, since you will need to know how to segregate the touch areas based on need.
For this application I will set up the touchscreen as a standard 3 x 4 key matrix. The touch-tone keypad comes to mind. This was used on home phones after the age of the rotary dial phone. (You still might have one in your home as a land line.) For you cell phone users, the dial pad is the same, but your cell does not have to produce Dual Tone Multi Frequency (DTMF) codes in order to dial. DTMF dialing was used to send button pushes as audio over a land line’s twisted-pair connections to the telephone company.
FYI, this required a physical pair of copper wires from every telephone in every house to the phone company’s local switching station. The phone company used the detection of these tones to decode them back into the numbers you dialed for use in their switching equipment. I would encourage those not familiar with the history of the early phone company to investigate this fascinating story further.
To divide the screen into multiple areas of detection, we need to know a couple of things. First and most obvious is the layout of the matrix. In this case, I am rotating the touch panel so that the smaller dimension is horizontal, and dividing it into three columns. The longer dimension is vertical and divided into four rows.
The next item is knowing the minimum and maximum extents of the ADC conversion values (earlier I found 400 and 3,600 to be reasonable). So, if we divide the span (3,600 – 400 = 3,200) by 3 we get three areas of 1,066. We’ll call the first area C0 (400 – 1,466), the second area C1 (1,466 – 2,532), and the third area C2 (2,532 – 3,598). Likewise, if we divide the span, 3,200, by 4 we get four areas of 800. We’ll call the first area R0 (400 – 1,200), the second area R1 (1,200 – 2,000), the third area R2 (2,000 – 2,800), and the fourth area R3 (2,800 – 3,600).
The numbers of importance here are the borders between areas, and these are saved into two arrays, arrayBorderX and arrayBorderY. There are two X borders: 1,466 and 2,532. There are three Y borders: 1,200, 2,000, and 2,800. The displayTouchPoint() routine determines which area the X and Y touchscreen values fall into.
With the screen rotated 90 degrees, resistance goes up from left to right, so we’ll begin with column 2 on the right arrayBorderX[column-1]. If the X value > arrayBorderX[2-1] then we know it must be in column 2. If not, then we go to column 1 and check that. If the X value > arrayBorderX[1-1] then we know it must be in column 1. If not, then we know it must be column 0. Vertically, the resistance goes up from bottom to top, so we’ll begin with row 0 at the top arrayBorderX[row]. This time we are looking to see if Y < arrayBorderY[row]. Otherwise we move on to the next row. At this point, we have identified in which area of the touchscreen the touch has occurred. Where you go from here depends on your application.
IDENTIFIED AREA ASSOCIATION
For this demonstration, I will use the 3 x 4 matrix of areas and wish to identify only which area is touched. The photo in Figure 6a shows the underlay I used to give the user a visual sense of each key. Although this uses the digits 0-9, plus the “*” and “#” keys, these could be colors, symbols, or any other markings. As it stands, this could be used as a security keypad to allow entry into a secure area. The other underlays shown in Figure 6 are a few other ideas of what can be done with this setup.
Once the area has been identified, the hard part is done. In this case, I have identified a row and column position. I could have selected one of an array of 12 just as easily. Because I’m using two separate variables, I filter these through multiple switch/case structures, to break these down to a specific row/column intersection. For this application, I simply print a message identifying the button; however, this could be any command, such as “play a DTMF tone,” “increase the volume,” “turn left,” or “vend a Coke.”
I have to admit, for many projects, the combination display/touchscreen is hard to beat. However, you can find instances where what’s behind the touchscreen doesn’t need to be dynamic. For these cases, you can cut the parts cost and use a static graphic. Touchscreens can be quite handy when you need multiple buttons and you want a clean-looking enclosure. So, don’t listen to your mother—go ahead and touch that thing! Too much to do, so little time.
RESOURCES
Texas Instruments TSC2007 Touchscreen Controller, www.ti.com/product/TSC2007
Adafruit Resistive Touch screen – 3.7” Diagonal, www.adafruit.com/product/333
TSC2007 I2C Resistive Touch Screen Controller, www.adafruit.com/product/5423
PUBLISHED IN CIRCUIT CELLAR MAGAZINE • OCTOBER 2022 #387 – Get a PDF of the issue
Sponsor this ArticleJeff Bachiochi (pronounced BAH-key-AH-key) has been writing for Circuit Cellar since 1988. His background includes product design and manufacturing. You can reach him at: jeff.bachiochi@imaginethatnow.com or at: www.imaginethatnow.com.