Monet in Zaandam: An interactive table

Hi Guys,

I wanted to share a short demo video of a permanent installation we made last year for the Zaans Museum in Zaandam, The Netherlands, to provide interactive context to their most important acquisition (a painting by Monet, made in Zaandam).

The installation consists of a CNC’ed (milled) wooden table top, with hidden capacitive sensors installed 5mm under the wooden surface, and a (mapped) projection from the top. The software written with OpenFrameworks 0.8.4, and there is an Arduino with a bit of custom electronics to translate the touches into OF events.

Personally I really love the feeling of interactive projection and materials like wood and/or concrete, it’s really kind of magical to touch surfaces that you are familiar with and get new feedback.



Congratulations !
I love the wooden table and hidden capacitive sensors !
Do you also have a “behind the scenes” video ?

1 Like

Thanks! Sadly we don’t. I took this photo during testing though:

As I recall the table top itself was about 4 cm thick European Ash. Previously we had made a smaller material testing setup to figure out the required thickness to combine both valid readings and a sturdy table. Turned out around 0.5cm was the sweet spot. So we needed the top to be milled from two sides.

  • The top, to both get the map (around 3-6 mm deep) and the ‘buttons’ (3 mm deeper).
  • On the bottom to reduce the material thickness to the required 5 mm around the buttons.

What was great about these sensors by the way is that they would self-calibrate upon receiving power. So the presence of the wood in its field would first be sampled as the ‘normal’ state, meaning any disturbance in that field would mean user-interaction.

1 Like

Hi @eelke ,

how did you read the different capacitive sensors? Did you use Arduino with Firmata and Capsense? Or via Processing and Spacebrew? Or directly to OpenFrameworks? Would be glad about your help, because I’m trying to connect multiple capacitive sensors to open frameworks at the moment, and it seems to be very difficult.

Best, David

Hi @da_re,

We were using an Arduino Nano with multiplexers to increase the amount of inputs, a choice made because of what we had in house. An Arduino mega would also have worked.

We’re not using Firmata but a rather simple loop, checking the input pins for changes and then passing these as an event through to openframeworks.

So the whole hardware bit is built up with:

  • Arduino Nano V3
  • 74HC4067 Multiplexers (on Sparkfun breakout board BOB-09056)
  • Sensors: Apem CG111AP0GC (green) & CG111AP0RC (red) sensors

For the multiplexers we are using:

Could you share some code, about how do you check the input pins for changes?

Sure. But please mind that because of the amount of sensors that we are using (22), and the multiplexers this bit of code is more complicated than it would be if you would just use an a couple of them. You could connect up to 6 directly to an analog input pin without using 70% of this code.

Basically I just check the input pins, compare the states with the previous states and if something has changed, send a serial message with all the new states. That is all.

// Arduino code for the Monet Machine
// Zaans Museum, Zaandam NL
// (c) Eelke Feenstra / Zesbaans 2015

// The system is built up with:
// - Arduino Nano V3
// - 74HC4067 Multiplexers (on Sparkfun breakout board BOB-09056)
// - Apem CG111AP0GC (green) & CG111AP0RC (red) sensors

// For the multiplexers we are using: 

#define MY_VERSION 004

// New in this version: 
// Move away from average analog readings, use pull-up option for the analog inputs
// This comes with a minor board tweak: pins A6 & A7 don't have a pull-up, so the left mux
// is now connected to A0.

#define NR_BUTTONS 22            // should be the exact number of attached sensors 
#define PIN_SPLIT 11             // define the cut off for left/right multiplexers

// connection stuff
bool isConnected = false;
long connectionId = 0;
unsigned long packetId = 0;
String packet = "";
bool sendPacket = false;

bool debug = false;

// measurement stuff
bool lastEdgeVal[NR_BUTTONS] = {};
bool edgeVal[NR_BUTTONS] = {};

// multiplex stuff
int muxSignalPinRight = A1; 
int muxSignalPinLeft = A0;
int muxControlPinRight[] = {A5, A4, A3, A2};
int muxControlPinLeft[] = {5, 4, 3, 2};
int muxChannel[16][4]={
  {0,0,0,0}, //channel 0
  {1,0,0,0}, //channel 1
  {0,1,0,0}, //channel 2
  {1,1,0,0}, //channel 3
  {0,0,1,0}, //channel 4
  {1,0,1,0}, //channel 5
  {0,1,1,0}, //channel 6
  {1,1,1,0}, //channel 7
  {0,0,0,1}, //channel 8
  {1,0,0,1}, //channel 9
  {0,1,0,1}, //channel 10
  {1,1,0,1}, //channel 11
  {0,0,1,1}, //channel 12
  {1,0,1,1}, //channel 13
  {0,1,1,1}, //channel 14
  {1,1,1,1}  //channel 15

void setup() {
  // clear content in arrays
  for(int i = 0; i < NR_BUTTONS; i ++){
    lastEdgeVal[i] = edgeVal[i] = 0;
  // prepare multiplex pins
  for (int i = 0; i < 4; i++)
    pinMode(muxControlPinRight[i], OUTPUT);
    pinMode(muxControlPinLeft[i], OUTPUT);
    digitalWrite(muxControlPinRight[i], LOW);
    digitalWrite(muxControlPinLeft[i], LOW);
 pinMode(muxSignalPinRight, INPUT);
 digitalWrite(muxSignalPinRight, HIGH);
 pinMode(muxSignalPinLeft, INPUT);
 digitalWrite(muxSignalPinLeft, HIGH);

void loop(){

  if (!Serial) isConnected = false;

  if (isConnected)
    sendPacket = false;
    packet = "";
    for(int i = 0; i < NR_BUTTONS; i ++)
      lastEdgeVal[i] = edgeVal[i];
      int val =  readMux((i > PIN_SPLIT-1), i%PIN_SPLIT);
      edgeVal[i] = (val < 512);

      if (debug) packet += ", " + (String) val;
      else if (edgeVal[i] && (edgeVal[i] != lastEdgeVal[i]))
          packet+= "," + (String) i;
          sendPacket = true; 
      if (debug) delay(10);
    if (sendPacket || debug) {
      packet = (String) ++packetId + packet;
  else connectSerial();

int readMux(bool left, int channel){

  if (debug) 
    Serial.print("Reading: ");
    if (left) Serial.print("Left side");
    else Serial.print("Right side");
    Serial.print(" channel " + (String) channel);
  int val = -1; 
  if (left) 
      //loop through the 4 sig
    for(int i = 0; i < 4; i ++){
      digitalWrite(muxControlPinLeft[i], muxChannel[channel][i]);
    //read the value at the SIG pin
    val = analogRead(muxSignalPinLeft);

  else {
       //loop through the 4 sig
    for(int i = 0; i < 4; i ++){
      digitalWrite(muxControlPinRight[i], muxChannel[channel][i]);
    //read the value at the SIG pin
    val = analogRead(muxSignalPinRight);

  if (debug) Serial.println(" = " + (String) val);
  //return the value
  return val;

void connectSerial()
  while (!Serial);


  String bootMsg = "\nMONET MACHINE";
  bootMsg += "\nVERSION " + (String) MY_VERSION;
  bootMsg += "\nCONNECTION No. " + (String) connectionId;
  bootMsg += "\n(C) ZESBAANS.NL 2015\n\n";

  isConnected = true;
  connectionId ++;

On the OF side, I am using ofxSerial to receive and process these messages.

thank you @eelke!