Arduino Does Multitouch

A lot of consumer gadgets use touch sensors now. It is a cheap and reliable way to replace a variety of knobs and switches on everything from headphones to automobiles. However, creating a custom touch controller for a one-off project can be daunting. A recent ACM paper shows how just about any capacitive sensor can work as a multitouch sensor with nothing more than an Arduino although a PC running processing interprets the data for higher-level functions.

The key is that the Arduino excites the grid using PWM and then examines the signal coming out of the grid. Finger poking changes the response quite a bit and the Arduino can sense it using the analog to digital converters onboard. You can find the actual software kit online. The tutorial document is probably more interesting than the ACM paper if you only want to use the kit.

The optimum drive frequency is 10 MHz. The examples rely on harmonics of a lower frequency PWM signal to get there. The analog conversion, of course, isn’t that fast but since your finger touch rate is relatively slow, they treat the signal as an amplitude-modulated input which is very easy to decode.

The sensors can be conductive ink, thread, or copper strips. There are several example applications, including a 3D printed bunny you can pet, a control panel on a sleeve, and an interactive greeting card.

The sensor forms an image and OpenCV detects the actual touch configuration. It appears you can use the raw data from the Arduino, too, but it might be a little harder.

We imagine aluminum foil would work with this technique. If you get to the point of laying out a PCB, this might come in handy.

23 thoughts on “Arduino Does Multitouch

  1. Of course, they could have used the oscillator output of the AVR to provide the signal they were multiplexing into the matrix.

    Too bad the Arduino platform abstracts away neat features like that.

      1. You have to set some fuse bits. The point is, the Arduino platform practically prevents people from reading the datasheet of the chip they’re trying to program, so you get solutions like this where the person is trying to use the upper harmonics of a PWM signal to get an 8 MHz signal out when they could get 8 MHz properly out of PB0 as a standard feature.

        1. OTOH some of these people would not read the datasheet anyway, so instead of inefficient code (that is kinda portable and can be optimized in future) we would have no code, because it would be too uncomfortable for beginners to even start doing something.

          1. Programming for the AVR family is not difficult. You just set certain bits in certain registers, and the hardware does things for you. The difficult thing is finding which register and which bit, which can be slightly obscure but other people have figured it out before you anyways, so you can just look at examples.

            The point of the Arduino is to help a little too much – it’s spoon-feeding you and cutting the steak into pieces because you can’t hold a knife and fork – so instead of just buying an ATMega328P and plonking it down on a breadboard, you have to buy an Arduino board because you’re actually helpless with the chip and can’t do anything without Arduino.

            The point isn’t to teach people to code for embedded platforms – even badly. The point is to sell Arduinos. This is a trap for beginners.

    1. Or one could use a different chip that has cap touch sensing built in hardware. But in Arduino land there is this commandment:
      Thou shall not use better hardware and programming tools!
      Besides that, the Arduino code is inefficient because it needs plenty of overhead to keep people from accidentally misuse inputs as outputs and outputs as inputs or doing something equally unsafe…

      I recently wrote a piece of firmware for touch-sensitive multifunction switch. The code for sensiing two touch fields took about 40 lines of C, including some state machine stuff for the functions and blocking it from sensing long touch as series of short taps, and constant automatic recalibration of sensors…

        1. I’ve skipped all the variable declarations and other things to focus on the main code. Example of similar code can be found in documentation of PIC16F1827 for example. Be aware that I use global variables because sometimes I need to access them in many different places of the program. They are also very volatile, so please, no smoking…

          The config function is called as first thing to execute. The fragment below contains the setup for cap touch module and timer2 module for generating timely interrupts:
          OPTION_REG = 0;
          OPTION_REGbits.PS = 0b100;
          CPSCON0bits.CPSRNG = 0b11;
          CPSCON0bits.T0XCS = 0;
          //Timer 2 period: 0,01/(0,000000125*64*10)
          T2CONbits.T2OUTPS = 0b1001;
          T2CONbits.T2CKPS = 0b11;
          PR2 = 125;
          PIE1bits.TMR2IE = 1;
          INTCON = 0;
          INTCONbits.PEIE = 1;
          INTCONbits.GIE = 1;
          T2CONbits.TMR2ON = 1;

          This function actually performs the reading of sensor and recalibrates the prescaler for the entire module if it is necessary:

          char capGet(void){
          unsigned char tp = 0;
          while (!tp){
          INTCONbits.T0IF = 0;
          TMR0 = 0;
          CPSCON0bits.CPSON = 1;
          __delay_us(10);
          CPSCON0bits.CPSON = 0;
          tp = TMR0;
          if (INTCONbits.T0IF){
          INTCONbits.T0IF = 0;
          tp = OPTION_REGbits.PS;
          tp = (tp + 1) & 0b00000111;
          OPTION_REGbits.PS = tp;
          tp = 0;
          }
          }
          return tp;
          }

          ISR for incrementing the main program counter that relates to all other parts of the program. It also sets flag for updating the state machine 100 times a second:

          void interrupt timerINT(void){
          if (PIR1bits.TMR2IF) {
          PIR1bits.TMR2IF = 0;
          counter++;
          counter & 0b00111111;
          state.update = 1;
          }
          }

          The main piece of code in the main function is related to switching the states of the machine and checking the sensors. On of them is on/off switch, the other one switches modes. Each time state.update is set to 1, capcounter variable is incremented by 1. This is what happens next:

          if (capcounter == 10){
          capcounter = 0;
          CPSCON1bits.CPSCH = 0;
          cap0raw = capGet();
          CPSCON1bits.CPSCH = 1;
          cap1raw = capGet();
          if (cap0raw < (cap0awg-cap0trip)&& cap0raw < (cap1raw-cap1trip)){
          if (state.turnon == 0) state.turnon = 1;
          else state.turnon = 0;
          capcounter = 11;
          } //above is the fragment that compares the raw measurement with average and blocks the sensors from sensing
          else cap0awg = cap0awg*15/16 + cap0raw/16; // and this updates the average
          cap0trip = cap0awg/10; //and cap trip point
          if (cap1raw < (cap1awg-cap1trip)&& cap1raw < (cap0raw-cap0trip) && state.turnon == 1){
          if (state.mode == 7) state.mode = 0;
          else state.mode++;
          state.switching = 1;
          counter = 0;
          capcounter = 11;
          }
          else cap1awg = cap1awg*15/16 + cap1raw/16;
          cap1trip = cap1awg/10;
          }
          if (capcounter == 111)capcounter = 9; // this will set next cap sensing session after this iteration of main loop, but only when one second is counted out.

          I hope this helps…

    1. That is incredibly useful, thank you! I was designing a capacitive touch sensor from the ATTiny manual but it didn’t have the feature fleshed out well enough for me to do it with my limited spare time.

      That’ll fill in the gaps in my experience with the AVR and touch sensors nicely!

  2. Just started wondering if you can retrofit resistive touch screen arrays with this technique. Might need a screen protector film over top to stop the direct contact screwing with it.

    1. Resistive touch panels are not divided into arrays of conductive pads as capacitive panels would be. Resistive panel works more like two giant potentiometers (one for X and one for Y) if i heavily simplify it. You probably can stick capacitive panel over resistive display and use microcontroller with DACs to read the capacitive panel and feed analog signal to original circuitry (=emulate output of the resistive touchpad). But resistive devices usualy had the UI elements optimized for using pen/stylus, which makes them too small to be controlled by bulky human fingers.

      1. I know that’s how they started out. In the late resistive era though they seemed to be getting griddy somehow, early noughties until capacitive won.

        Wasn’t really meaning of upgrades to a device, but using surplus in new builds.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.