Is there a simple way to use the 16 key touchpad (TTP229) with Particle?

Has anyone of you tried to use this nice 16 key touchpad?

I ordered a few samples, as I want to use them in my new home, mainly for selecting lighting “scenes” in different rooms.

Originally, the header pins are sticking out on the front side, making it difficult to integrate this in a flat surface.

On this unit, I have removed the plastic spacer and bent the pins up, so that they can be connected under the surface where it’s mounted.

You can see on the panel that it has SCL and SDO pins. Sounds good!

So, I want to connect this touchpad with 3 wires (incl. GND) to the room’s Particle (Photon). 5V power supply comes from the 5V power bus which runs through the rooms.

I have googled around and found a few interesting Arduino sketches to start from:

  1. Arduino forum: LINK

This link also contains the links to a datasheet.

  1. Tutorial: LINK

This guy has a Github repository for his project: LINK

  1. A very complicated (Israeli) approach: LINK

This guy has a story about several (contradictory) datasheets…

I want to try to port some of these approaches, but if someone has already developed some sketch or ported one, I’d like to try it out first…

:older_man:

This keypad seems to be I2C, so you’ll want to use D0/D1 for SDA/SCL.
Just for testing you might want to use the Vin pin as 5V source - and when you explicitly state you’ve got a 5V rail going through the rooms, I’d assume you have got a common GND for this too.

Doesn’t look to complicated, but the Arduino guys seem to do it all by hand. Would be nice to see if there are some datasheets to be found that tell about the actual I2C protocol.

1 Like

Yes, if it were I2C, that would be nice, but as I read on in those links, it seems that it’s not I2C but some proprietary protocol…

But first of all, some optional possibilities must be unlocked with some jumpers: ([LINK][1])
One of them is the possibility to use the 16 keys: It comes standard with only 8 keys activated…

Shall I put one in the mail? :smiley:
[1]: http://itimewaste.blogspot.be/2014/12/arduino-code-for-ttp229-touch-16-button.html

I found another even more interesting one on the Arduino forum: LINK

Interesting: It uses interrupt, generated by the touchpad, so that it should not be polled continuously.

But how much of this is compatible with Particle? :confounded:

And this guy explains to put jumpers like this: :cold_sweat:

:older_man:

@FiDel, looking at the TTP229 datasheet, the device IS I2C compatible. However, it seems configuring pin/jumpers is required. I’m surprised there are not I2C drivers available.

2 Likes

OK, thanks @peekay123, that’s good news, because I’d like to be able to connect 2 to one Photon.
Do you think that’s possible?

Targeted application in our new home is to control the lighting “scenes” from 2 sides of the room.

The more I looked around in Arduino world, the more people I found, using different approaches and techniques to make the keys work in some way. But indeed, the documentation is far from clear!

The “touchpad16_interrupt.ino” code for Arduino, I mentioned in my last post, looks most useful for my purpose, because of the “interrupt” feature.

As I will give each room Particle a number of tasks, I would prefer that it responds as quickly as possible to the operation of this switch.

Here’s the sketch which he says works: (But even I can tell it’s not so Particle compatible…)

    /*
     * touchpad16_interrupt.ino
     *
     * Created: 5/27/2015 9:02:08 PM
     * Author: Steve Stover (Stumpy842)
     *  http://forum.arduino.cc/index.php?topic=301382.msg2252580#msg2252580
     *
     * 16 Key Capacitive Touchpad using TTP229B IC
     * http://www.ebay.com/itm/371304274498
     *
     * The Touchpad has a jumper on TP1/SAHL for active high serial out
     *   and a jumper on TP2/KYSEL for 16-keys operation
     *
     * Processing the input via external interrupt INT1
     *   on an Arduino Nano v3.0
     
     Comments from Stumpy842:
     I've written a short sketch to demonstrate how to use this touchpad
     without the need to constantly poll it for a (possible) input, as most of the other examples I have seen are doing.
     Here instead I'm using an external interrupt generated by the touchpad to get the key value.
     If you examine the datasheet for the TTP229B you will note that the timing diagrams for 2-wires serial interface
     show the SDO pin generating a pulse of width DV (typ. 93uS) to signal data available.
     So all I do is wire SDO to INT1 and in the ISR (interrupt service routine) wait for the pulse to end,
     then clock the SCL pin 16 times and record the value on SDO for each clock pulse, ORing the values together.
    
     The touchVal is a volatile global variable in the sketch used in the ISR to store the value of the touchpad key(s).
     Because it might change at any time, I copy it to another variable, 
     touchValc in an atomic block to avoid it changing during the time its being accessed.
     Then touchValc (the copy) can be used freely in the main loop to process the key value.
     
    */
    
    #include <util/atomic.h>
    
    #define CLR(x,y) (x&=(~(1<<y)))
    #define SET(x,y) (x|=(1<<y))
    
    #define clock_Pin 2
    #define sdo_Pin 3
    
    // Touchpad value
    volatile uint16_t touchVal;  // var for ISR access
    uint16_t touchValc;          // copy var for main loop
    
    void setup() {
      Serial.begin(9600);
      pinMode(clock_Pin, OUTPUT);
      pinMode(sdo_Pin, INPUT);
      // set up INT1 on digital pin 3
      EICRA = (1 << ISC11) | (1 << ISC10);  // external INT1 on rising edge
      EIMSK = (1 << INT1);  // External Interrupt Request 1 Enable
      sei();
    }
    
    void loop() {
      ATOMIC_BLOCK(ATOMIC_FORCEON) {
        touchValc = touchVal;
      }
      if (touchValc) {
        Serial.print(touchValc, DEC);
        for (byte b=0; b<=15; b++) {
          if ((touchValc >> b) & 1) {
            Serial.print('\t'); Serial.print("bit "); Serial.print(b);
            Serial.print('\t'); Serial.print("pad "); Serial.print(b+1);
          }
        }
        Serial.println();
      }
    }
    
    ISR(INT1_vect) {
      touchVal = 0;
      delayMicroseconds(100);
      for (byte i=0; i<=15; i++) {
        SET(PORTD, clock_Pin);
        delayMicroseconds(50);
        touchVal |= (digitalRead(sdo_Pin) << i);
        CLR(PORTD, clock_Pin);
        delayMicroseconds(50);
      }
    }

Anyway, for me there’s no hurry…
I can only start installing all room Particles from next winter.
Now I should only know which wiring I need to control those touchpads.

:clap: :older_man:

The datasheet states that the controler supports eight different only one I2C addresses, so having just, but two of these on one bus should be no problem if you can multiplex them via digital pins for Vcc or some other way around this


Sorry for the initial mis-information. I overlooked the “fixed” when reading the datasheet about B1~B3

1 Like

OK, that’s great!

What I’m trying to do is (in a nutshell) the following:

In our new homes (my son’s and ours) we want to control everything controllable with Photons: Entry control, security, alerts, monitoring of gas and other dangers, lighting, heating, ventilation, music etc…

In each room, one Photon will collect the data from the sensors in the room and control the devices in the same room.

All Photons communicate by sending events, so that data collected by one Photon can control devices connected to another Photon.

So, 2 keypads will be connected to one Particle in Room1 and 2 more keypads will be connected to another Particle in Room2. The lights of both adjacent rooms will respond to the choices on one of the keypads.

The idea is that you can only activate one key at the time: Select “scene” 1 or 2 or 3 or 4 or…
I2C sounds also good to me. I believe it is very reliable, isn’t it?


So, the “architecture” :house_with_garden: I have in mind for the sketch controlling these keypads is the following:

In the loop() , the status of both keypads should be called every loop by calling a function like touchKeyStatus() with as parameter the module ID code or so.

That function returns 16 (public) boolean variables: touchKey1, 2, 3,…

This will allow me to use commands in the loop() like this:

If touchKey5, then do Action1

:older_man:

You seem to like your discreete variable names like touchKey1, touchKey2, …, T1, T2, …, but I don’t (and nobody should).
This makes code so much longer, bigger, less maintainable and it’s poor practice - so please try to befriend yourself with arrays :wink:
And once you got used to this you’ll find them just as friendly to read as these explicit names.

3 Likes

@FiDel, now where I got your board and tried to set the I2C address, I realised a mistake in my earlier post.
I’ve edited it accordingly - there is only one address possible.

Sorry for the mis-information, but a workaround should be possible (see above) :blush:

1 Like

Glad that you know the way around this! I have no idea how I can use 2 I2C devices on one bus without changing their bus addres...

And the next disappointing revelation.
@FiDel, you were right after all. The TTP229-L does support I2C, but the chip on the keypad is a TTP229-B which doesn’t.
So we have to use the other approach, but that’s not too bad after the insight that the main advantage of having multiple pads on one I2C bus is not an option due to the inability to select other addresses for the keypad.
I’ll keep you posted.

BTW, these should be the correct datasheets
https://dl.dropboxusercontent.com/u/87066238/SUNROM-TTP229-BSF_V1.1_EN.pdf
http://www.sunrom.com/get/611100
https://dl.dropboxusercontent.com/u/87066238/TTP229B-Schematic.pdf
https://www.openimpulse.com/blog/wp-content/uploads/wpsc/downloadables/TTP229B-Schematic-Diagram.pdf

this explicitly states " with 2 wire SPI type data interface (not I2C)"
http://www.sunrom.com/p/ttp229-bsf-8229bsf-ssop28-spi
http://www.sunrom.com/p/816-channel-capacitive-touch-module-ttp229

1 Like

Thanks for checking on that @ScruffR!
I’m not happy that I’m right this time… :grin:

Glad you still see a way to connect multiple touch pads to one Photon.
It’s a pity but right now I can spend very little time to this subject.
The only thing I need to be sure of right now is the number of wires from each module to the Photons…
:+1: :older_man:

This board (or rather the datasheet) is something of a mess. I understand now why most code samples you find about it use polling and “manual” clocking.
As already said I2C is not an option, but at least this is documented (once you finde the right docs), but then the datasheet states that you could use the data valid pulse (DV) which should come when a touch or release is detected. But unfortunately this does not seem to come reliably (or at least I couldn’t make it happen all the time), so I’m now also down to the “clumsy” way - but at least this works like a charm and with Software Timers it can be kept neat and tidy.

I’ve set my board up for 16 key (bridge at JP1 3) and multitouch (bridge at JP1 4 & JP2 5)

And this is my test code.

SYSTEM_MODE(SEMI_AUTOMATIC)

#define kpCLOCKPERIODE 2                    // at least x µs LOW and the same HIGH

const int kpSCL = TX;                       // KeyPad clock pin 
const int kpSDO = RX;                       // KeyPad data pin

volatile uint16_t btnState; 

char keys[17] = "................";

Timer kpTimer(100, scan);                   // Software Timer to scan every 100ms

void setup() 
{
    Serial.begin(115200);
    
    pinMode(kpSCL, OUTPUT);                 // clock output
    pinMode(kpSDO, INPUT);                  // data input
    
    pinSetFast(kpSCL);                      // set clock default HIGH (for active LOW setting)

    Particle.variable("keypad", keys);      // expose variable when cloud connected
    
    kpTimer.start();                        // start sampling every 100ms
}

void loop() 
{
    static uint16_t oldState;               // var to detect change 
    
    if(btnState != oldState)                // only report changes
    {
        oldState = btnState;                // remember new value for next time round
        for(int i=0; i<16; i++)             // build a string for easy display
          keys[i] = (btnState & (1 << i)) ? 'O' : '.';
        
        Serial.printlnf(keys);          
        
        if (btnState == 0xFFFF)             // touch all keys to connect to cloud
            Particle.connect();
    }
}

void scan()
{
    btnState = 0;                           // reset current state
    for(int i=0; i<16; i++)
    {
        pinResetFast(kpSCL);                // first set clock LOW 
        delayMicroseconds(kpCLOCKPERIODE);  // wait for output data
        pinSetFast(kpSCL);                  // latch data with rising edge
        delayMicroseconds(kpCLOCKPERIODE);  // wait again
        btnState |= pinReadFast(kpSDO) << i;// set bit 
    }
    btnState ^= 0xFFFF;                     // invert due to default setting (active LOW)
}

I’ve found that you don’t even need Vcc connected, so the minimum wires needed for one board are three and for each additional board one extra since GND and SCL can be shared.
But if you can efford extra lines you could have one shared Vcc and an SCL each too.

1 Like

OK, sounds good @ScruffR !
I’d like to try it out next week with 2 touchpads.

So, it sounds like I will need a cable with 4 lines between a touchpad and a Particle.
But which I/O pins would you use?

:older_man:

You need three lines, but to have one extra won’t harm. If you relize you’d need an extra pin for something, you could just repurpose the Vcc line, since it’s not required as parasitic powering via the SCL line worked for me reliably (with short lines tho’ - test with your actual length lines could show different - an extra cap near the keypad might help that too).

Since we have no special needs of the pins (e.g. interrupts, ADC, PWM, …) you can choose any GPIO you like.

Here is the extended code for multiple key pads (I’ve used three times RX to test for three devices with only one pad :wink: - just replace the RXs with whatever pins you choose)

SYSTEM_MODE(SEMI_AUTOMATIC)

#define kpCLOCKPERIODE 2                           // at least x µs LOW and the same HIGH

const byte kpSDO[] = { RX, RX, RX };               // KeyPad data pins (one per keypad) <-- put your pins here
const byte kpSCL   = D1;                           // KeyPad clock pin 
const int  kpCount = sizeof(kpSDO);                // retrieve count of keypads from dynamic array

volatile uint16_t btnState[kpCount]; 

char keys[kpCount][17];

Timer kpTimer(100, scan);                          // Software Timer to scan every 100ms

void setup() 
{
  Serial.begin(115200);
    
  pinMode(kpSCL, OUTPUT);                          // clock output
  pinSetFast(kpSCL);                               // set clock default HIGH (for active LOW setting)

  for (int i=0; i < kpCount; i++)                  // set all data pins as 
    pinMode(kpSDO[i], INPUT);                      // input
    

  Particle.variable("keypad", (char*)keys);        // expose variable when cloud connected
    
  kpTimer.start();                                 // start sampling every 100ms
}

void loop() 
{
  static uint16_t oldState[kpCount];               // var to detect change 
  bool needPrint = false;
    
  for (int kp=0; kp < kpCount; kp++)
  {
    if(btnState[kp] != oldState[kp])               // only report changes
    {
      needPrint = true;
      oldState[kp] = btnState[kp];                 // remember new value for next time round
      for(int i=0; i<16; i++)                      // build a string for easy display
        keys[kp][i] = (btnState[kp] & (1 << i)) ? 'T' : '_';
      keys[kp][16] = '|';

      if (btnState[kp] == 0xFFFF)                  // touch all keys to connect to cloud
        Particle.connect();
    }

    if (kp == kpCount-1 && needPrint)
    {
      ((char*)keys)[sizeof(keys)-1] = '\0';
      Serial.println((char*)keys);          
    }
  }
}

void scan()
{
  for (int kp=0; kp < kpCount; kp++)
    btnState[kp] = 0;                              // reset current state

  for(int i=0; i<16; i++)
  {
    pinResetFast(kpSCL);                           // first set clock LOW 
    delayMicroseconds(kpCLOCKPERIODE);             // wait for output data
    pinSetFast(kpSCL);                             // latch data with rising edge
    delayMicroseconds(kpCLOCKPERIODE);             // wait again
    for (int kp=0; kp < kpCount; kp++)
      btnState[kp] |= pinReadFast(kpSDO[kp]) << i; // set bit 
  }
  for (int kp=0; kp < kpCount; kp++)
    btnState[kp] ^= 0xFFFF;                        // invert due to default setting (active LOW)
}
2 Likes

Very interesting @ScruffR, I could never have written this sketch!
It will take me some time to digest these nice arrays… :yum:

Tomorrow, when I can find a few hours, I’ll try it out with 2 touchpads.
I assume I can also change kpSCL to another pin instead of D1, correct? (In case I need D1 for I2C devices…)
If so, I will use D2 for SCL and D3, D4 for SDO:

const byte kpSDO[] = { D3, D4 };
const byte kpSCL   = D2;

Thanks also for the cabling tips!
No problem, I have 4 lines as I will use cable with 2 twisted pairs for each pad.
And I will surely test it with 10 meter length first.
:older_man:

You’re welcome to ask any questions about my alien :alien: coding style :wink:
I know that some of these things might not be clear at first sight.

And yes, you can choose these pin combos too.

1 Like

Fantastic @ScruffR, all works fantastic! :+1:
What a symphony!!!

This is really a nice piece of original coding…
I’m still trying to figure out how exactly it works!

Now I have to figure out what to do with the array:
If key 1 is pressed, then set lights to scene1 etc…

:hand::older_man:

Many thanks for this great sketch @ScruffR!

I am now trying to digest how your sketch works and find a way to use the 16 keys of each keypad in the loop(), to control devices.

Therefore, as a first “orientation” I added Serial.print commands with both “btnState” arrays between the existing monitor output:

  Serial.print(btnState[1]);
  Serial.print(" - ");
  Serial.println(btnState[2]);

Pressing keys 1-16 of key-pad1, outputs the following lines to the serial monitor:

________________|T_______________
1 - 24415
________________|_T______________
2 - 24415
________________|__T_____________
4 - 24415
________________|___T____________
8 - 24415
________________|____T___________
16 - 24415
________________|_____T__________
32 - 24415
________________|______T_________
64 - 24415
________________|_______T________
128 - 24415
________________|________T_______
256 - 24415
________________|_________T______
512 - 24415
________________|__________T_____
1024 - 24415
________________|___________T____
2048 - 24415
________________|____________T___
4096 - 24415
________________|_____________T__
8192 - 24415
________________|______________T_
16384 - 24415
________________|_______________T
32768 - 24415

This logic is quite clear: With each higher key number, the btnState value doubles: 1,2,4,8,16,32…

But it’s not so clear with keypad 2:

T_______________|________________
0 - 24404
_T______________|________________
0 - 21599
___T____________|________________
0 - 24415
______T_________|________________
0 - 24415
_______T________|________________
0 - 24415
_________T______|________________
0 - 24415
___________T____|________________
0 - 24415
_____________T__|________________
0 - 24415
______________T_|________________
0 - 24415
_______________T|________________
0 - 24415

I suppose this way, we see the decimal values but we need to see the 16 bits of the keys.
My experience with arrays is not up to speed yet…