Project tutorial
Capacitive Touch Sensing Grid

Capacitive Touch Sensing Grid © GPL3+

Build a 6x6 grid with an MPR121, expanding possible inputs from a mere 12 to 36!

  • 558 views
  • 0 comments
  • 10 respects

Components and supplies

Necessary tools and machines

09507 01
Soldering iron (generic)

Apps and online services

About this project

Capacitive Sensing

A capacitor is simply two conductors with an insulator in between. This allows for a charge to be stored and then discharged. This also lets changes in its capacitance be sensed, such as a finger coming into contact with a copper pad. To read more about how capacitive touch sensing works, you can view this article.

Necessary Components

The most important part of any capacitive-sensing circuit is the capacitive sensor. I went with the MPR121, mainly due to how easy it is to interface with it. It’s also nice because the board itself is cheap and has plenty of input pins to connect (12 total).

The brain of the whole circuit is an Arduino Nano, just because it’s what I had on hand and this project doesn’t need powerful hardware to run. The last piece is a long strip of copper tape, which is simply a thin piece of copper with an adhesive layer underneath. Anything conductive can be used in its place, such as bananas, screws, or aluminum strips.

Assembling the Grid

To begin, I got a piece of card stock and cut it into a square-ish shape. Next, I cut the copper strip into 12 smaller strips and attached them to the card stock, ensuring that there was even spacing between cells. Finally, I soldered a jumper wire to the edge of each copper strip and plugged it into the breadboard, along with the MPR121 and Arduino Nano. The MPR121 communicates with the Nano via I2C, so the SDA, SCL, VCC, GND, and IRQ lines were all attached.

The Capacitive Sensing Program

The program is fairly simple in operation. Every time the MPR121 senses a change, it generates an interrupt via its IRQ pin. Because this pin is active LOW, it means that the interrupt is active whenever it goes from HIGH to LOW, otherwise known as FALLING. The IRQ pin is attached to pin 2 on the Arduino Nano, which is the ATmega328p’s INT0 pin. It can also be attached to pin 3, which acts as INT1. The attachInterrupt() function is called that passes in a pointer to the callback function, which will cause the function to run whenever the interrupt is triggered.

The callback function simply updates a two-dimensional array of values, in which each cell is a product of the row and column at that location. For example, if row 4 has a value of 3000, and column 2 has a value of 1200, then the cell at (4, 2) = 3000 * 1200.

In the loop function, the two-dimensional is printed out every second by calling printTable().

Display Program

Pygame is a great framework for creating interactive GUIs with Python, so I used it to display the grid of values. The table of values is printed out via Serial, which has TABLE to signify the start of each table. Then, there are 6 lines of data, with each element being separated by the `|` symbol. The values are parsed by first stripping away the whitespace, then using the built-in split() method to convert the string into a list. The value is then mapped to a number between 0 and 255, which is used to color each square in the window, ranging from blue (no touch) to red (touched).

Future Possibilities

In the future, I would like to connect this circuit up as a way to have many virtual buttons, thus saving space and hardware. It could be used to control an LED matrix, interact with an exhibit, and much more.

Code

Arduino Nano CodeC/C++
#include <Wire.h>
#include "Adafruit_MPR121.h"

Adafruit_MPR121 cap_sense = Adafruit_MPR121();

#define MPR121_ADDR 0x5A
#define PRINT_DELAY 200

uint32_t sensedPoints[6][6];
volatile bool readFlag = false;

// Rows are pin 0-5, cols are pins 6-11
/*

Pin 6 | 7 | 8 | 9 | 10 | 11
0 |   |   |   |   |    |
------|---|---|---|----|---
1 |   |   |   |   |    |
------|---|---|---|----|---
2 |   |   |   |   |    |
------|---|---|---|----|---
3 |   |   |   |   |    |
------|---|---|---|----|---
4 |   |   |   |   |    |
------|---|---|---|----|---
5 |   |   |   |   |    |
------|---|---|---|----|---

*/

void setup()
{
    Serial.begin(115200);
    while(!Serial);
    if(!cap_sense.begin(MPR121_ADDR))
    {
        //Serial.println("Error setting up MPR121");
        while(1);
    }
    attachInterrupt(digitalPinToInterrupt(2), updateSensedFlagSet, FALLING);
    //Serial.println("Ready to sense");
}

void loop()
{
    /*if(readFlag)
    {
        readFlag = false;
        updateSensed();
    }*/
    updateSensed();
    printTable();
    delay(PRINT_DELAY);
}

void printTable()
{
    Serial.println("TABLE");
    for(int i=0; i<6; i++)
    {
        for(int j=0; j<6; j++)
        {
            if(j != 5) Serial.print(String(sensedPoints[i][j]) + " | ");
            else Serial.println(String(sensedPoints[i][j]));
        }
        //if(i == 5) Serial.println("\n-------------------\n");
    }
    
}

void updateSensedFlagSet()
{
    readFlag = true;
}

void updateSensed()
{
    //Serial.println("Reading");
    for(uint8_t i=0; i<6; i++)
    {
        for(uint8_t j=0; j<6; j++) sensedPoints[i][j] = cap_sense.filteredData(i) * cap_sense.filteredData(j + 6);
    }
    //Serial.println("Table updated");
}
Grid DisplayPython
import pygame
from pygame.locals import *
from pygame import *

from serial import Serial
import sys

# It's best to have them be multiples of 6
width = 600
height = 600

pygame.init()

serialPort = "COM8"
ser = Serial(serialPort, baudrate=115200)

screen = pygame.display.set_mode((width, height), HWSURFACE | DOUBLEBUF)

def mainLoop():
    if ser.in_waiting > 0:
        byte_str = ser.read_until()
        read_str = byte_str.decode("utf-8")
        print(read_str)
        if "TABLE" in read_str:
            print("table found")
            for row in range(6):
                byte_str = ser.read_until()
                read_str = byte_str.decode("utf-8")
                read_str.replace(" ", "")
                val_list = read_str.split('|')
                print(val_list)
                for col, val in enumerate(val_list):
                    colorVal = map(int(val), 0, 1600, 0, 255)
                    if colorVal > 1600:
                        colorVal = 1600
                    rect = pygame.Rect((col * width / 6, row * height / 6), (width / 6 - 1, height / 6 - 1))
                    pygame.draw.rect(screen, (colorVal, 0, 255 - colorVal), rect)
            pygame.display.update()
    for event in pygame.event.get():
        if event.type == KEYDOWN:
            if event.key == pygame.K_ESCAPE:
                print("exiting")
                pygame.quit()
                ser.close()
                sys.exit()

def map(val, in_min, in_max, out_min, out_max):
    return (val - in_min) * (out_max - out_min) / (in_max - in_min) + out_min

while(True):
    mainLoop()

Schematics

Wiring
Dsc 0320 gmyuvxijcg

Comments

Similar projects you might like

3D Printed Prosthetic Hand with Capacitive Touch Sensing

Project tutorial by Amal Mathew

  • 4,943 views
  • 6 comments
  • 29 respects

Playing "Flappy Bird" on an LED Matrix

Project tutorial by Arduino “having11” Guy

  • 4,428 views
  • 1 comment
  • 14 respects

Easy Motion and Gesture Detection by PIR Sensor & Arduino

Project tutorial by ElectroPeak

  • 64,875 views
  • 13 comments
  • 303 respects

Capacitive Touch Keyboard Extension with Leonardo

Project tutorial by Alex Wulff

  • 6,107 views
  • 2 comments
  • 18 respects

Portable Capacitive Touch Piano

Project tutorial by Sridhar Rajagopal

  • 4,154 views
  • 3 comments
  • 12 respects
Add projectSign up / Login