Hi there helpful Arduino forum folk. I'm hoping someone can point out the hopefully easy to solve error I've made with my project.
I have followed the instructions of using more than one ttp229 cap sensor by using a 4051 multiplexer, found here Instruction: One or more 16-key capacitive keypads with Arduino Uno & SPI - #3
I have the first 229 chip set to operate in 16 keys input mode, the second in 8 key mode. I have made a custom PCB after testing with the keypad modules you can get on ebay.
In making the PCB I had to route traces to suit the layout so key order is remapped thanks to some code a forum member suggested a couple of months back (uint16_t reorder(uint16_t inputWord))
The project is a simple 16 step beat sequencer for a music installation, and the electrode attached to TP15 of the cap IC will not read for me. I no longer have the keypad modules set up in my test rig and if the problem happened then I didn't detect it, but I don't think I did.
So this could be a hardware problem or a software problem, which is why I've attached the schematic I used and the code. Everything else works from a hardware perspective, though my code is still being worked on.
The relevant piece of code is the part below the "READ STEP INPUT BUTTONS AND COUNT PRESSES" comment. I considered editing my code to remove all the other stuff but didn't in case there's something interacting with the button code I'm missing.
My hope is I've made some silly mistake with a a for loop, or an array is too small or some other thing like that, but I've tried everything I can think of. Hence, coming here and baring my soul to the critical eyes of the forum experts!
/*
code for one "Unit" of eight 16 step sequencers. One arduino Nano (addressed as Pro Mini in Arduino IDE due to CH340 chip issues)
handles touch interface (2x TTP229), neopixels, network communication and power.
Another Arduino (actual Pro Mini, programmed via 6 pin header and red USB-FTDI) handles Mozzi based audio, triggered via hardware Serial port TX on sequencer Nano to RX on Pro Mini.
working:
Multiply: when pressed for the first time, multiply tempo by 2. Second press, multiple based tempo by 0.5 for half speed. Thrird press, base tempo
Direction: toggle between forward and reverse behaviour of the sequencer count
IO: toggle between sequencer running (lights and sound) and not. Sequence input should still work but not playback
Clear: toggle on and off to erase steps as it plays through the loop
Elect: Concept: the 8 players can vote for a leader who can control the other sequencers from one unit. They should be able to mute and reverse.
this needs a reliable network to function but the idea is to hold this button (which will require timing the button press?)which will turn off
the led display of the sequencer working, but not the sound, and display every second of the 16 ring leds to indicate the 7 other units. You can't vote for
yourself so the 12 o'clock position should be indicated with a different colour led. If one of the 8 units receives 5 or more votes then they become leader
for a set duration. The more I think about this the more I think it's for September as it's going to take ages to code this.
Trigger: acts as a realtime sample trigger. TRY step input whne playing
Leds are Green Red Blue GRB
bug: step button 5 doesn't work. LED is fine but not this? Something to do with re-ordering of buttons from cap touch sensor. Tried fiddling with the for()loop to no avail.
autoClear: prevents the installation being left on interminably and creating a nuisance. Resets to default settings after 100 bars
to do: elect...figure out how this will work in practice
From : kinda stuck in a rut with this. Initial state should mean that the sequencer is in defualt run mode but instead it goes straight into fromMode. Once in fromMode you can pick the start point
but the leds are messed up. Desired behaviour:
press From button. Enter fromMode, where the current from point is lit in blue, the other steps (by defualt, all the other 15 positions) are lit green.
Automatically enter editing mode which polls buttons states. Once a step is chosen, light the selected step blue and all other buttons green.
To exit, it would be nice if it just automatically went back to normal sequencer operation after one revolution, or press the From button again
bugs: step input still works in fromMode.
- once from point is selected, no steps before it are accessible to change it again, only steps within the new loop bounds
*/
#include <SPI.h>
const uint8_t MUX_A = 5; // Used to select mux data channel
const uint8_t MUX_B = 6;
uint16_t key_map_8229_0; // The scanned keys
uint16_t key_map_8229_1;
//note that silkscreen on PCB does not match the functions: Mute =ONOFF , Direction = ELECT, To= FROM, From = TO, Speed = MULTIPLY, Trigger = DIRECTION, Elect = CLEAR, Clear = TRIGGER
#define FROM 0 //from
#define ONOFF 1 //mute
#define DIRECTION 2 //direction
#define TO 3 //to
#define TRIGGER 4 //trigger
#define CLEAR 5 //clear
#define ELECT 6 //elect
#define SPEED 7 //speed
#define halfSpeed 1
#define normalSpeed 0
#define doubleSpeed 2
// inputWord is your incoming 16-bit value
uint16_t inputWord;
// Remapped output value
uint16_t outputWord = 0;
int stepNum[16];
int stopCount; //save variable count for when playhead is stopped
#include <Adafruit_NeoPixel.h>
#define LED_PIN 4
#define LED_COUNT 16
// Declare our NeoPixel strip object:
Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);
unsigned long pixelPrevious = 0; // Previous Pixel Millis
unsigned long patternPrevious = 0; // Previous Pattern Millis
int patternCurrent = 0; // Current Pattern Number
int patternInterval = 5000; // Pattern Interval (ms)
int pixelInterval = 50; // Pixel Interval (ms)
int pixelQueue = 0; // Pattern Pixel Queue
int pixelCycle = 0; // Pattern Pixel Cycle
uint16_t pixelCurrent = 0; // Pattern Current Pixel Number
uint16_t pixelNumber = LED_COUNT; // Total Number of Pixels
const long tempo = 100; // interval at which to blink (milliseconds)
long interval = 0; // interval at which to blink (milliseconds)
int loopStart = 0;
int loopEnd = 16;
int count = 0;
int reverseCount = 16;
int autoClear = 0; //if same loop plays a certain number of times it gets wiped to prevent leaving the installation running when no-one is there.
int resetCount = 100; //the number of bar repetitions before global parameter reset occurs
int onOffCounter = 1; //on-off function button
int multiplyCounter = 0;
int clear = 0;
int trigger = 0;
int direction = 0;
int loopFrom = 0;
int fromMode = 0; // flag for when loop start position is chosen by pressing step position.
int toMode = 0;
int ledOrder[16] = { 4, 3, 2, 1, 0, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5 }; // fixing layout of leds. First led in the chain is
//in position 13 in relation the touchPCB, and are also running anti-clockwise so this fixes that issue
// Variables will change:
int buttonPushCounter[16]; // counter for the number of button presses
int buttonOrder[16]; // = { 12, 13, 14, 15, 0, 2, 3, 1, 7, 6, 5, 4, 8, 11, 10, 9 };
int buttonState[16]; // current state of the button
int lastButtonState[16]; // previous state of the button
int functionNames[8] = { FROM, ONOFF, DIRECTION, TO, TRIGGER, CLEAR, ELECT, SPEED };
int functionButtonState[8];
int functionLastButtonState[8];
int frombuttonState; // current state of the button
int lastFromButtonState; // previous state of the button
unsigned long previousMillis = 0; // will store last time LED was updated
int frontRemainder = 0;
int backRemainder = 0;
// setup() function -- runs once at startup --------------------------------
void setup() {
pinMode(MUX_A, OUTPUT);
pinMode(MUX_B, OUTPUT);
fromMode = 0;
Serial.begin(9600);
SPI.begin();
SPI.beginTransaction(SPISettings(14000000, MSBFIRST, SPI_MODE3));
delay(500); // Allow 500ms for the 8229BSF to get ready after turn-on
// These lines are specifically to support the Adafruit Trinket 5V 16 MHz.
// Any other board, you can remove this part (but no harm leaving it):
#if defined(__AVR_ATtiny85__) && (F_CPU == 16000000)
clock_prescale_set(clock_div_1);
#endif
// END of Trinket-specific code.
strip.begin(); // INITIALIZE NeoPixel strip object (REQUIRED)
strip.show(); // Turn OFF all pixels ASAP
strip.setBrightness(200); // Set BRIGHTNESS to about 1/5 (max = 255)
}
// loop() function -- runs repeatedly as long as board is on ---------------
void loop() {
//Serial.println(fromMode);
// Get data frpm the Keypads
spi_scan_8229_0();
spi_scan_8229_1();
// MULTIPLY TEMPO CODE (half / normal / double)
if (multiplyCounter == halfSpeed) {
interval = tempo * 2;
//Serial.println("half");
}
if (multiplyCounter == normalSpeed) {
interval = tempo * 1;
//Serial.println("normal");
}
if (multiplyCounter == doubleSpeed) {
interval = tempo * 0.5;
//Serial.println("doulbe");
}
//SEQUENCER BUTTONS LOOP
if (fromMode == 0 && toMode == 0) { //if From and To modes aren't engaged then run the main show
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
// save the last time you blinked the LED
previousMillis = currentMillis;
//Serial.println(count);
//FORWARD DIRECTION PLAYBACK
if (onOffCounter == 1 && direction == 1) { //main play control.
count++;
//REAL TIME TRIGGER MESSAGE
//from Trig function key
if (trigger == 1) { //
Serial.print(1); // trigger the drum in realtime, but quantised by the division of the sequencer
buttonPushCounter[count] = 1; //TRY THIS FOR LIVE STEP INPUT
}
if (count >= loopEnd) { //if count goes beyond its bounds reset to whatever value loopstart is
count = loopStart;
autoClear++; //keep track of how many loops have played
//Serial.println(autoClear);
}
//Playhead indicated with WHITE led chaser in currently selected direction
strip.setPixelColor(ledOrder[count], strip.Color(200, 200, 200));
strip.show();
}
//RESET FUNCTION
if (autoClear >= resetCount) {
for (int i = 0; i < 16; i++) { //resets the sequencer clearing every step
buttonPushCounter[i] = 0;
}
direction = 1; //reset direction to forward
interval = tempo * 1; //reset speed to normal
autoClear = 0; // reset counter
loopStart = 0;
loopEnd = 16;
}
///////////////////////////MAIN TRIGGER TO PLAY SAMPLE///////////////////////////////////////////////////////
Serial.print(buttonPushCounter[count]); //this is the main line to trigger drum samples on Arduino Pro Mini via serial
///////////////////////////MAIN TRIGGER TO PLAY SAMPLE///////////////////////////////////////////////////////
//REVERSE DIRECTION PLAYBACK
if (onOffCounter == 1 && direction == 0) {
count--; //decrement counter
// Serial.println(count);
if (trigger == 1) { //
Serial.print(1); // trigger the drum in realtime, but quantised by the division of the sequencer
buttonPushCounter[count] = 1; //TRY THIS FOR LIVE STEP INPUT
}
if (count < loopStart) {
count = loopEnd - 1; //minus one stops decrementer going into non-existent minus numbers and confusing the sequencer.
autoClear++; //while this works, it auto resets every n number of bars regardless of whether anything is in the sequence.
//would be better to have it only start counting once some user input has been made.
}
//Playhead indicated with WHITE led chaser in currently selected direction
strip.setPixelColor(ledOrder[count], strip.Color(200, 200, 200));
strip.show();
}
}
//STOPPED BEHAVIOUR
if (onOffCounter == 0 && direction == 1 || direction == 0) { //if transport stopped in either forward or reverse playback direction
stopCount = count; //log the current position in the step count so this can be indicated with led when transport stopped.
strip.setPixelColor(ledOrder[stopCount], strip.Color(200, 200, 200));
strip.show();
}
if (onOffCounter == 0) {
//REAL TIME TRIGGER MESSAGE
//from Trig function key
if (trigger == 1) { //
Serial.println(1); // trigger the drum in realtime, but quantised by the division of the sequencer
}
}
// CLEAR
if (clear == 0) { //erase step input data. Indicate erase mode by RED rotating led ring
strip.setPixelColor(ledOrder[count], strip.Color(0, 200, 0));
strip.show();
buttonPushCounter[count] = 0;
}
// LED POSITION ANIMATION
//turns off the last led so they don't all stay on after one iteration through loop
if (buttonPushCounter[ledOrder[count]] == 0) {
strip.setPixelColor(count - 1, strip.Color(0, 0, 0));
strip.show();
}
// READ STEP INPUT BUTTONS AND COUNT PRESSES
for (int i = loopStart; i < loopEnd; i++) { //for (int i = ; i < 16; i++) {
//read the values of the 16 (re-ordered) sequencer input buttons.
buttonState[i] = bitRead(reorder(key_map_8229_0), i);
if (buttonState[i] != lastButtonState[i]) {
// if the state has changed, increment the counter
if (buttonState[i] == 0) {
// if the current state is HIGH then the button went from off to on:
buttonPushCounter[i]++;
}
}
// save the current state as the last state, for next time through the loop
lastButtonState[i] = buttonState[i];
//STEP VELOCITY LED COLOURS
//These if statements determine colour of button presses in Sequencer step velocity layers. Yellow = LOW, red = HIGH
if (buttonPushCounter[i] == 0) {
strip.setPixelColor(ledOrder[i], strip.Color(0, 0, 0));
strip.show();
}
if (buttonPushCounter[i] == 1) { // yellow or low velocity
strip.setPixelColor(ledOrder[i], strip.Color(200, 250, 0));
strip.show();
}
if (buttonPushCounter[i] == 2) { //orange or medium velocity
strip.setPixelColor(ledOrder[i], strip.Color(40, 250, 0));
strip.show();
}
if (buttonPushCounter[i] == 3) {
buttonPushCounter[i] = 0;
}
}
}
if (fromMode == 2) { //inital fromMode state
Serial.println("from mode 1");
strip.setPixelColor(ledOrder[loopStart], strip.Color(0, 0, 250));
strip.show();
for (int i = loopStart + 1; i < loopEnd; i++) { // Loop keeps playing but all LEDs go GREEN to show loop length, and From position flashes BLUE
strip.setPixelColor(ledOrder[i], strip.Color(250, 0, 0));
strip.show();
}
fromMode = 3;
}
if (fromMode == 3) {
Serial.println("from mode 2");
for (int i = 0; i < 16; i++) { //when button is selected, new loopStart is set, and leds should indicate this
//read the values of the 16 (re-ordered) sequencer input buttons.
buttonState[i] = bitRead(reorder(key_map_8229_0), i);
if (buttonState[i] != lastButtonState[i]) {
// if the state has changed, increment the counter
if (buttonState[i] == 0) {
// if the current state is HIGH then the button went from off to on:
buttonPushCounter[i]++;
}
}
// save the current state as the last state, for next time through the loop
lastButtonState[i] = buttonState[i];
if (buttonPushCounter[i] == 1) { // yellow or low velocity
loopStart = i;
strip.setPixelColor(ledOrder[loopStart], strip.Color(0, 0, 250)); //doesn't shut off previously selected loopStart positions
strip.show();
for (int i = loopStart + 1; i < loopEnd; i++) { // Loop keeps playing but all LEDs go GREEN to show loop length, and From position flashes BLUE
strip.setPixelColor(ledOrder[i], strip.Color(250, 0, 0));
strip.show();
}
/////////////////////////// turn off leds before and after loop start and end if new endpoints selected
if (loopEnd != 15) {
for (int i = loopEnd + 1; i <= 15; i++) {
strip.setPixelColor(ledOrder[i], strip.Color(0, 0, 0));
strip.show();
}
}
if (loopStart != 0) {
for (int i = 0; i <= loopStart - 1; i++) {
strip.setPixelColor(ledOrder[i], strip.Color(0, 0, 0));
strip.show();
}
}
fromMode = 0;
//need to turn off leds outside of the loop length
}
}
}
// for (int i = loopEnd; i > loopStart; i--) {
// strip.setPixelColor(ledOrder[i], strip.Color(0, 0, 0));
// strip.show();
// Serial.println("turn off others");
// }
// FUNCTION BUTTONS LOOP
/*
0 = multiply DONE
1 = clear DONE
2 = trig DONE
3 = direction
4 = from
5 = elect LATERZ
6 = onoff DONE
7 = to
*/
// This for loop reads values from the function buttons to control how the sequencer operates
for (int i = 0; i <= 7; i++) {
functionButtonState[i] = bitRead((key_map_8229_1), i);
//Serial.println(functionButtonState[i]);
if (functionButtonState[i] != functionLastButtonState[i]) {
// if the state has changed, increment the counter
if (functionButtonState[2] == 0) { // trigger drum pad
trigger = 1;
// Serial.println("trig");
} else {
trigger = 0;
}
if (functionButtonState[0] == 0) { // mutliply pad
multiplyCounter++;
Serial.println(multiplyCounter);
if (multiplyCounter > 2) { //default behaviour where sequencer runs at "x1" speed
multiplyCounter = -1;
}
}
if (functionButtonState[1] == 0) { // clear pad
//ideally this would work as a function button, so that when it's pressed and the step keys that are to be cleared as selected, they are turned off / buttonPushCounter set to 0
clear++;
//Serial.println("clear");
if (clear > 1) {
clear = 0;
}
}
if (functionButtonState[3] == 0) { // direction pad
//
direction++;
//Serial.println("direction");
//Serial.println(direction);
if (direction > 1) {
direction = 0;
}
}
if (functionButtonState[7] == 0) { // loopFrom pad
fromMode++;
Serial.print("from");
Serial.println(fromMode);
if (fromMode > 2) {
fromMode = 0;
}
}
if (functionButtonState[4] == 0) { // loopT0 pad
// loopTo++;
// Serial.println("to");
// //Serial.println(direction);
// if (loopTo > 1) {
// loopTo = 0;
// }
}
if (functionButtonState[6] == 0) { //on off pad
// if the current state is HIGH then the button went from off to on:
onOffCounter++;
//Serial.print("onOff counter = ");
//Serial.println(onOffCounter);
if (onOffCounter >= 2) {
onOffCounter = 0;
}
//Serial.print(i);
// Serial.print("number of button pushes: ");
//Serial.println(buttonPushCounter[i]);
}
}
functionLastButtonState[i] = functionButtonState[i];
}
} //END LOOP
// READING TTP229 capacitive ICs
//for Sequencer
void spi_scan_8229_0() {
// Select MUX channel for 8229_0
digitalWrite(MUX_A, 0);
digitalWrite(MUX_B, 0);
// Get state of all the keys as a 16 bit integer
key_map_8229_0 = SPI.transfer16(0);
}
//for Function keys
void spi_scan_8229_1() {
// Select MUX channel for 8229_1
digitalWrite(MUX_A, 1);
digitalWrite(MUX_B, 0);
// Get state of all the keys as a 16 bit integer
key_map_8229_1 = SPI.transfer(0);
}
//re-ordering physical layout of keys vs desired UI location
uint16_t reorder(uint16_t inputWord) {
// Remapped output value
uint16_t outputWord = 0;
// Relates input bit positions to outputs
// e.g., output bit 0 is input bit 3
const byte mapbit[16] = { 11, 12, 13, 14, 16, 1, 2, 0, 6, 5, 4, 3, 7, 10, 9, 8 }; //{12, 13, 14, 15, 0, 2, 3, 1, 7, 6, 5, 4, 8, 11, 10, 9};
for (int i = 0; i < 16; i++) { //something iffy happening with TP15(interface=5). Either nt reporting or conflicting with 6
if (inputWord & (1 << mapbit[i])) {
outputWord |= (1 << i);
}
}
return outputWord;
}
[TouchPCB v47.pdf|attachment](upload://iMJgl3LlZWLEjCjZxnqWf7RHmXf.pdf) (284.8 KB)

