How to daisy 2 Touch Boards and use them as HID Keyboard instead of Midi

Hello everyone,

Im working on a schoolproject with some Bare Conductive Touch boards.
Bare Conductive has some codes available where you can daisy up to 6 Touchboards.
Now is the code written to use it as Midi output (codes more explained github link):

[Code to daisy 2 or more Touch boards and use them as Midi]
(GitHub - BareConductive/multi-board-touch-midi: Multi Board Bare Conductive Touch MIDI Demo Code)

Primary board code:

// compiler error handling
#include "Compiler_Errors.h"

// touch includes
#include <MPR121.h>
#include <Wire.h>
#define MPR121_ADDR 0x5C
#define MPR121_INT 4

#include <SoftwareSerial.h>

SoftwareSerial mySerial(12, 10); // Soft TX on 10, we don't use RX in this code

// number of boards config
// you can reduce this to improve response time, but the code will work fine with it 
// left at 6 - do not try to increase this beyond 6!
// don't forget to edit the notes variable below if you change this
const uint8_t numSecondaryBoards = 6;
const uint8_t totalNumElectrodes = (numSecondaryBoards+1)*12;

// serial comms
const uint8_t serialPacketSize = 13;
uint8_t incomingPacket[serialPacketSize];

// secondary board touch variables
bool thisExternalTouchStatus[numSecondaryBoards][12];
bool lastExternalTouchStatus[numSecondaryBoards][12];

// compound touch variables
bool touchStatusChanged = false;
bool isNewTouch[totalNumElectrodes];
bool isNewRelease[totalNumElectrodes];

// MIDI config
uint8_t note = 0; // The MIDI note value to be played
const uint8_t resetMIDI = 8; // Tied to VS1053 Reset line
uint8_t instrument = 0;

// these are the MIDI notes you want to map to each electrode
// starting with E0 thru E11 on the primary board
// then E0 thru E11 on the first secondary board and so on
// note - the number of note values below MUST equal the total number of electrodes
const uint8_t notes[totalNumElectrodes] = {24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107};

void setup(){  
  Serial.begin(57600);
  
  pinMode(LED_BUILTIN, OUTPUT);
   
  //while (!Serial) {}; //uncomment when using the serial monitor 
  Serial.println("Bare Conductive Multi Board MIDI Piano");

  if(!MPR121.begin(MPR121_ADDR)) Serial.println("error setting up MPR121");
  MPR121.setInterruptPin(MPR121_INT);

  // Reset the VS1053
  pinMode(resetMIDI, OUTPUT);
  digitalWrite(resetMIDI, LOW);
  delay(100);
  digitalWrite(resetMIDI, HIGH);
  delay(100);

  mySerial.begin(31250);
  
  // initialise MIDI
  setupMidi();

  for(int i=0; i<numSecondaryBoards; i++){
    for(int j=0; j<12; j++){
      thisExternalTouchStatus[i][j] = false;
      lastExternalTouchStatus[i][j] = false;
    }
  }

  for(int i=0; i<totalNumElectrodes; i++){
    isNewTouch[i] = false;
    isNewRelease[i] = false;
  }   

  for(int a=A0; a<=A5; a++){
    pinMode(a, OUTPUT);
    digitalWrite(a, LOW); 
  }

  Serial1.begin(57600);
  Serial1.setTimeout(3); // 3ms is more than enough time for a packet to be transferred (should take 2.26ms)
  delay(100);
}

void loop(){
  
  // reset everything that we combine from the two boards
  resetCompoundVariables();
  
  readLocalTouchInputs();
  
  readRemoteTouchInputs();
  
  processTouchInputs();
  
}


void readLocalTouchInputs(){

  // update our compound data on the local touch status

  if(MPR121.touchStatusChanged()){
    MPR121.updateTouchData();
    touchStatusChanged = true;
    
    for(int i=0; i<12; i++){
      isNewTouch[i] = MPR121.isNewTouch(i);
      isNewRelease[i] = MPR121.isNewRelease(i);
    }
  }

}

void readRemoteTouchInputs(){

  uint8_t numBytesRead;

  for(int a=A0; a<A0+numSecondaryBoards; a++){

    digitalWrite(a, HIGH);

    // try to read a full packet
    numBytesRead = Serial1.readBytesUntil(0x00, incomingPacket, serialPacketSize);

    // only process a complete packet
    if(numBytesRead==serialPacketSize){
      // save last status to detect touch / release edges
      for(int i=0; i<12; i++){
        lastExternalTouchStatus[a-A0][i] = thisExternalTouchStatus[a-A0][i];
      }
      
      if(incomingPacket[0] == 'T'){ // ensure we are synced with the packet 'header'
        for(int i=0; i<12; i++){
          if(incomingPacket[i+1]=='1'){
            thisExternalTouchStatus[a-A0][i] = true;
          } else {
            thisExternalTouchStatus[a-A0][i] = false;
          }
        }
      } 

      // now that we have read the remote touch data, merge it with the local data
      for(int i=0; i<12; i++){
        if(lastExternalTouchStatus[a-A0][i] != thisExternalTouchStatus[a-A0][i]){
          touchStatusChanged = true;
          if(thisExternalTouchStatus[a-A0][i]){
            // shift remote data up the array by 12 so as not to overwrite local data
            isNewTouch[i+(12*((a-A0)+1))] = true;
          } else {
            isNewRelease[i+(12*((a-A0)+1))] = true;
          }
        }
      }
    } else {
      Serial.print("incomplete packet from secondary board ");
      Serial.println(a-A0, DEC);
      Serial.print("number of bytes read was ");
      Serial.println(numBytesRead);
    }

    digitalWrite(a, LOW);
  }
}

void processTouchInputs(){
  // only make an action if we have one or fewer pins touched
  // ignore multiple touches
  
  for (int i=0; i < totalNumElectrodes; i++){  // Check which electrodes were pressed
    if(isNewTouch[i]){   
      //pin i was just touched
      Serial.print("pin ");
      Serial.print(i);
      Serial.println(" was just touched");
      noteOn(0, notes[i], 0x60);
      digitalWrite(LED_BUILTIN, HIGH);  
    } else if(isNewRelease[i]){
      //pin i was just released
      // Serial.print("pin ");
      // Serial.print(i);
      // Serial.println(" is no longer being touched");
      noteOff(0, notes[i], 0x60);
      digitalWrite(LED_BUILTIN, LOW);
    }
  }
}

void resetCompoundVariables(){

  // simple reset for all coumpound variables

  touchStatusChanged = false;

  for(int i=0; i<totalNumElectrodes; i++){
    isNewTouch[i] = false;
    isNewRelease[i] = false;
  }  
}

// functions below are little helpers based on using the SoftwareSerial
// as a MIDI stream input to the VS1053 - all based on stuff from Nathan Seidle

// Send a MIDI note-on message.  Like pressing a piano key.
// channel ranges from 0-15
void noteOn(byte channel, byte note, byte attack_velocity) {
  talkMIDI( (0x90 | channel), note, attack_velocity);
}

// Send a MIDI note-off message.  Like releasing a piano key.
void noteOff(byte channel, byte note, byte release_velocity) {
  talkMIDI( (0x80 | channel), note, release_velocity);
}

// Sends a generic MIDI message. Doesn't check to see that cmd is greater than 127, 
// or that data values are less than 127.
void talkMIDI(byte cmd, byte data1, byte data2) {
  mySerial.write(cmd);
  mySerial.write(data1);

  // Some commands only have one data byte. All cmds less than 0xBn have 2 data bytes 
  // (sort of: http://253.ccarh.org/handout/midiprotocol/)
  if( (cmd & 0xF0) <= 0xB0)
    mySerial.write(data2);
}

// SETTING UP THE INSTRUMENT:
// The below function "setupMidi()" is where the instrument bank is defined. Use the VS1053 instrument library
// below to aid you in selecting your desire instrument from within the respective instrument bank


void setupMidi(){
  
  // Volume - don't comment out this code!
  talkMIDI(0xB0, 0x07, 127); //0xB0 is channel message, set channel volume to max (127)
  
  // ---------------------------------------------------------------------------------------------------------
  // Melodic Instruments GM1 
  // ---------------------------------------------------------------------------------------------------------
  // To Play "Electric Piano" (5):
  talkMIDI(0xB0, 0, 0x00); // Default bank GM1  
  // We change the instrument by changing the middle number in the brackets 
  // talkMIDI(0xC0, number, 0); "number" can be any number from the melodic table below
  talkMIDI(0xC0, 5, 0); // Set instrument number. 0xC0 is a 1 data byte command(55,0) 
  // ---------------------------------------------------------------------------------------------------------
  // Percussion Instruments (Drums, GM1 + GM2) 
  // ---------------------------------------------------------------------------------------------------------  
  // uncomment the two lines of code below to use - you will also need to comment out the two "talkMIDI" lines 
  // of code in the Melodic Instruments section above 
  // talkMIDI(0xB0, 0, 0x78); // Bank select: drums
  // talkMIDI(0xC0, 0, 0); // Set a dummy instrument number
  // ---------------------------------------------------------------------------------------------------------
  
}

Secondary board code:

// compiler error handling
#include "Compiler_Errors.h"

// touch includes
#include <MPR121.h>
#include <Wire.h>
#define MPR121_ADDR 0x5C
#define MPR121_INT 4

const int triggerPin = A0;
boolean thisTriggerValue = false;
boolean lastTriggerValue = false;

char touchStatus[12] = {'0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0'};

void setup(){  
  MPR121.begin(MPR121_ADDR);
  MPR121.setInterruptPin(MPR121_INT);

  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, LOW);

  pinMode(triggerPin, INPUT);
  digitalWrite(triggerPin, LOW); // ensure internal pullup is disabled

  pinMode(1, INPUT); // ensure that TX goes out of circuit when the UART is disabled
 
  for(int i=0; i<12; i++){
    MPR121.setTouchThreshold(i, 40);
    MPR121.setTouchThreshold(i, 20);
  }
}

void loop(){
  processInputs();
  thisTriggerValue = digitalRead(triggerPin);
  if(thisTriggerValue && !lastTriggerValue){ // rising edge triggered
    sendSerialStatus();
  }
  lastTriggerValue = thisTriggerValue;
}

void processInputs() {
  if(MPR121.touchStatusChanged()){    
    MPR121.updateTouchData();
    for(int i=0; i<12; i++){
      if(MPR121.isNewTouch(i)){
        touchStatus[i] = '1';
      } else if(MPR121.isNewRelease(i)){
        touchStatus[i] = '0';
      }
    }
  }
}

void sendSerialStatus(){
  Serial1.begin(57600);
  Serial1.write('T');
  Serial1.write(touchStatus, 12);
  Serial1.end();
}

Now they also have a code to use a Touchboard as HID keyboard.
https://www.instructables.com/The-Touch-Board-As-an-HID-Keyboard/

// compiler error handling
#include "Compiler_Errors.h"

// touch includes
#include <MPR121.h>
#include <MPR121_Datastream.h>
#include <Wire.h>

// keyboard includes
#include <Keyboard.h>

// keyboard variables
char key;

// keyboard behaviour constants
const bool HOLD_KEY = true;  // set this to false if you want to have a single quick keystroke, true means the key is pressed and released when you press and release the electrode respectively
const char KEY_MAP[12] = {'J', 'U', 'H', 'Y', 'G', 'T', 'F', 'D', 'E', 'S', 'W', 'A'};
// const char KEY_MAP[12] = {KEY_LEFT_ARROW, KEY_RIGHT_ARROW, KEY_UP_ARROW, KEY_DOWN_ARROW, ' ', KEY_ESC, KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_PAGE_UP, KEY_PAGE_DOWN};  // more keys at http://arduino.cc/en/Reference/KeyboardModifiers
// const char KEY_MAP[12] = {'8', '3', '0', '1', '2', '4', '7', '6', '5', '9', '9', '9'};  // for memory game

// touch constants
const uint32_t BAUD_RATE = 115200;
const uint8_t MPR121_ADDR = 0x5C;
const uint8_t MPR121_INT = 4;

// MPR121 datastream behaviour constants
const bool MPR121_DATASTREAM_ENABLE = false;

void setup() {
  Serial.begin(BAUD_RATE);
  pinMode(LED_BUILTIN, OUTPUT);

  if (!MPR121.begin(MPR121_ADDR)) {
    Serial.println("error setting up MPR121");
    switch (MPR121.getError()) {
      case NO_ERROR:
        Serial.println("no error");
        break;
      case ADDRESS_UNKNOWN:
        Serial.println("incorrect address");
        break;
      case READBACK_FAIL:
        Serial.println("readback failure");
        break;
      case OVERCURRENT_FLAG:
        Serial.println("overcurrent on REXT pin");
        break;
      case OUT_OF_RANGE:
        Serial.println("electrode out of range");
        break;
      case NOT_INITED:
        Serial.println("not initialised");
        break;
      default:
        Serial.println("unknown error");
        break;
    }
    while (1);
  }

  MPR121.setInterruptPin(MPR121_INT);

  if (MPR121_DATASTREAM_ENABLE) {
    MPR121.restoreSavedThresholds();
    MPR121_Datastream.begin(&Serial);
  } else {
    MPR121.setTouchThreshold(40);
    MPR121.setReleaseThreshold(20);
  }

  MPR121.setFFI(FFI_10);
  MPR121.setSFI(SFI_10);
  MPR121.setGlobalCDT(CDT_4US);  // reasonable for larger capacitances
  
  digitalWrite(LED_BUILTIN, HIGH);  // switch on user LED while auto calibrating electrodes
  delay(1000);
  MPR121.autoSetElectrodes();  // autoset all electrode settings
  digitalWrite(LED_BUILTIN, LOW);
  
  Keyboard.begin();
}

void loop() {
  MPR121.updateAll();

  for (int i=0; i < 12; i++) {  // check which electrodes were pressed
    key = KEY_MAP[i];
    
    if (MPR121.isNewTouch(i)) {
      digitalWrite(LED_BUILTIN, HIGH);
      Keyboard.press(key);  // press the appropriate key on the "keyboard" output

      if (!HOLD_KEY) {
        Keyboard.release(key);  // if we don't want to hold the key, immediately release it
      }
    } else {
      if (MPR121.isNewRelease(i)) {
        digitalWrite(LED_BUILTIN, LOW);

        if (HOLD_KEY) {
          Keyboard.release(key);  // if we have a new release and we were holding a key, release it
        }
      }
    }
  }

  if (MPR121_DATASTREAM_ENABLE) {
    MPR121_Datastream.update();
  }
}

The thing I want:
Daisy 2 Touch boards and instead of Midi output I want HID output, so not midi mp3 sounds but keyboard input.
Can somebody please help me? I have tried to combine the codes but they are so different I cant Figure it out...
If Somebody could help me, you would be my hero.
I think it should be hard if you have knowledge of coding so anyone?

Kind Regards,
Jelle

@superfregat
You have flagged this as working and asked for the topic to be deleted. It would, however, be more helpful if you explained the fix and that the topic was left in place for anyone with a similar problem

Do you really want the topic deleted ?