Multiplexed capsense - coding advice

TheOtherAdamski

New member
Hello Teensy fans!

I've been attempting to make a midi instrument to control my sampler, as well as learning how to use microcontrollers and how to write code. After plenty of trial and error, I have ending up making a device that uses a Teensy LC and a 74HC4051 multiplexer to read 16 capacitive inputs (thanks to Paul Badger and Paul Stoffregen's capsense library), two piezometric disks to determine an average velocity and output midi messages via a standard 5 pin din midi output. The device also has 16 leds controlled through two shift registers, one under each capacitive pad, so that when a pad is touched, it'll light up from underneath. Finally, there is an accelerometer that I hope to use to control pitch and modulation. I don't have a schematic but here's a picture of it with the top off - IMG_7180.JPG. I know it's hard to make out what's what here, but hopefully you can get a good idea of what I'm trying to achieve.

Guess what? I've bitten off more than I can chew and although I will eventually learn how to do this, I would be enormously grateful to anyone that could give me a few pointers and help me to get over the wall I've hit.

The code is here:

#include <CapacitiveSensor.h>

//eventually this code should:
//read 16 capacitive inputs
//if touch is detected, send corresponding midi note
//take average velocity from two piezometric disks
//light up corresponding pad
//use data from accelerometer to control midi pitch and cc

////////////////////////////////////////////////////////////////////////////

#define SER 6 // data in
#define SRCLK 7 // shift register clock
#define RCLK 8 // storage register (sometimes called the latch pin)
#define SRCLR 4 // clear shift register
#define OE 5 // enable output

#define TOTAL_SHIFT_PINS 16

///////////////////////////Velocity Sensing////////////////////////////

const int piezoA = 16;
const int piezoB = 17;
int readingA = 0;
int readingB = 0;
int velocityReading = 0;

///////////////////////////MULTIPLEXED CAPSENSE///////////////////////////

#define muxS0Pin 12
#define muxS1Pin 11
#define muxS2Pin 10
#define muxS3Pin 9

#define muxSignalPin A0
#define capSenseSendPin A1

CapacitiveSensor capSense = CapacitiveSensor(capSenseSendPin, muxSignalPin);

#define n_inputs 16

#define sampleRate 300

/////////////////////////////MIDI SETUP/////////////////////////////////////

const uint8_t NOTE_OFF = 0x80;
const uint8_t NOTE_ON = 0x90;
const uint8_t KEY_PRESSURE = 0xA0;
const uint8_t CC = 0xB0;
const uint8_t PROGRAM_CHANGE = 0xC0;
const uint8_t CHANNEL_PRESSURE = 0xD0;
const uint8_t PITCH_BEND = 0xE0;
const unsigned long headerResendTime = 1000; // send a new header every second

/////////////////////////////////////////////////////////////////////////////

void setup() {

Serial1.begin(31250);

pinMode(muxS0Pin, OUTPUT);
pinMode(muxS1Pin, OUTPUT);
pinMode(muxS2Pin, OUTPUT);
pinMode(muxS3Pin, OUTPUT);

digitalWrite(muxS0Pin, LOW);
digitalWrite(muxS1Pin, LOW);
digitalWrite(muxS2Pin, LOW);
digitalWrite(muxS3Pin, LOW);

pinMode(SER, OUTPUT);
pinMode(SRCLK, OUTPUT);
pinMode(SRCLR, OUTPUT);
pinMode(RCLK, OUTPUT);
pinMode(OE, OUTPUT);

clearShiftRegisters(); turnOutputsOn(); oneAtATime(); clearShiftRegisters();
}

//////////////////////////////MAIN LOOP//////////////////////////////////

void loop() {
getVelocity();
printData();
}

///////////////////////GET VELOCITY FUNCTION//////////////////////////////

void getVelocity(){
readingA = analogRead(piezoA);
readingB = analogRead(piezoB);
velocityReading = (readingA + readingB / 2);
velocityReading = map (velocityReading, 0, 1023, 0, 127);
}

//////////////////////PRINT VELOCITY/NOTE DATA FUNCTION///////////////////////////////

void printData(){
Serial.print (velocityReading); Serial.print("\t") ;
for (byte input = 0; input < 16 ; ++input) {
Serial.print(readMux(input)); Serial.print("\t") ;
}
Serial.println ();
}

/////////////////////////READMUX FUNCTION/////////////////////////////////

int readMux(int input) {

int controlPin[] = {muxS0Pin, muxS1Pin, muxS2Pin, muxS3Pin};

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
};
//loop through the 4 contol pins
for (int k = 0; k < 4; k ++) {
digitalWrite(controlPin[k], muxChannel[input][k]);
}
return capSense.capacitiveSensor (sampleRate);
}

//////////////////////////////////MIDI FUNCTIONS////////////////////////////////

void sendMIDIHeader(uint8_t header) {
static unsigned long lastHeaderTime = millis();
static uint8_t runningHeader = 0;
if (header != runningHeader // If the new header is different from the previous
|| (millis() - lastHeaderTime)
> headerResendTime) { // Or if the last header was sent more than 1 s ago
Serial1.write(header); // Send the status byte over Serial
runningHeader = header; // Remember the new header
lastHeaderTime = millis();
}
}
void sendMIDI(uint8_t messageType, uint8_t channel, uint8_t data1, uint8_t data2) {
if (messageType == NOTE_OFF) { // Replace note off messages
messageType = NOTE_ON; // with a note on message
data2 = 0; // with a velocity of zero.
}
channel--; // Decrement the channel, because MIDI channel 1
// corresponds to binary channel 0
uint8_t statusByte = messageType | channel; // Combine the messageType (high nibble)
// with the channel (low nibble)
// Both the message type and the channel
// should be 4 bits wide
statusByte |= 0b10000000; // Set the most significant bit of the status byte
data1 &= 0b01111111; // Clear the most significant bit of the data bytes
data2 &= 0b01111111;
sendMIDIHeader(statusByte); // Send the header over Serial, using running
//status
Serial1.write(data1); // Send the data bytes over Serial
Serial1.write(data2);
}
void sendMIDI(uint8_t messageType, uint8_t channel, uint8_t data) {
channel--; // Decrement the channel, because MIDI channel 1
// corresponds to binary channel 0
uint8_t statusByte = messageType | channel; // Combine the messageType (high nibble)
// with the channel (low nibble)
// Both the message type and the channel
// should be 4 bits wide
statusByte |= 0b10000000; // Set the most significant bit of the status byte
data &= 0b01111111; // Clear the most significant bit of the data byte
sendMIDIHeader(statusByte); // Send the header over Serial, using running
//status
Serial1.write(data); // Send the data byte over Serial
}

/////////////////////////SHIFT REGISTER FUNCTIONS/////////////////////

void oneAtATime() {
turnOutputsOn();
int data = HIGH;
int i;
for (i = 0; i < TOTAL_SHIFT_PINS; ++i) {
shiftDataIn(data); // first time here data will be HIGH.
copyShiftToStorage();
delay(77);
data = LOW; // after first time it will always be LOW.
}
}
// This doesn't change the value stored in the storage registers.
void turnOutputsOn() {
digitalWrite(OE, LOW);
}

// This doesn't change the value stored in the storage registers.
void turnOutputsOff() {
digitalWrite(OE, HIGH);
}

// clear the shift registers without affecting the storage registers.
void clearShiftRegisters() {
digitalWrite(SRCLR, LOW);
digitalWrite(SRCLR, HIGH);
}

// All the data in the shift registers moves over 1 unit and the new data goes in at shift register 0.
// The data that was in the shift register 7 goes to the next register (if any).
void shiftDataIn(int data) {
digitalWrite(SER, data);
digitalWrite(SRCLK, HIGH);
digitalWrite(SRCLK, LOW);
}

// copy the 8 shift registers into the shift registers,
// which changes the output voltage of the pins.
void copyShiftToStorage() {
digitalWrite(RCLK, HIGH);
digitalWrite(RCLK, LOW);
}


When I upload and run this code, the serial monitor gives a lovely stream of numbers. Screen Shot 2022-09-01 at 17.14.49.jpg The first column is the for velocity and the other 16 columns are for the capacitive inputs. Everything works...hooray!! With a few adjustments, I can send midi, get data from the accelerometer and make the leds do tricks.

My specific problem at this stage is how to take the data from muxRead() function (kindly posted online by an anonymous user(thanks!)) and link it to a note array for triggering note on and note off messages. I'm struggling to understand how muxRead() interacts with capSense.

Any help or pointers would be greatly appreciated as I've run out of steam and as you can probably tell, I'm still learning.View attachment Pinky_5.0.ino
 
Last edited:
Code:
#include <CapacitiveSensor.h>

//eventually this code should:
//read 16 capacitive inputs
//if touch is detected, send corresponding midi note
//take average velocity from two piezometric disks
//light up corresponding pad
//use data from accelerometer to control midi pitch and cc

////////////////////////////////////////////////////////////////////////////

#define SER 6 // data in
#define SRCLK 7 // shift register clock
#define RCLK 8 // storage register (sometimes called the latch pin)
#define SRCLR 4 // clear shift register
#define OE 5 // enable output

#define TOTAL_SHIFT_PINS 16

///////////////////////////Velocity Sensing////////////////////////////

const int piezoA = 16;
const int piezoB = 17;
int readingA = 0;
int readingB = 0;
int velocityReading = 0;

///////////////////////////MULTIPLEXED CAPSENSE///////////////////////////

#define muxS0Pin 12
#define muxS1Pin 11
#define muxS2Pin 10
#define muxS3Pin 9

#define muxSignalPin A0
#define capSenseSendPin A1

CapacitiveSensor capSense = CapacitiveSensor(capSenseSendPin, muxSignalPin);

#define n_inputs 16

#define sampleRate 300

/////////////////////////////MIDI SETUP/////////////////////////////////////

const uint8_t NOTE_OFF = 0x80;
const uint8_t NOTE_ON = 0x90;
const uint8_t KEY_PRESSURE = 0xA0;
const uint8_t CC = 0xB0;
const uint8_t PROGRAM_CHANGE = 0xC0;
const uint8_t CHANNEL_PRESSURE = 0xD0;
const uint8_t PITCH_BEND = 0xE0;
const unsigned long headerResendTime = 1000; // send a new header every second

/////////////////////////////////////////////////////////////////////////////

void setup() {

	Serial1.begin(31250);

	pinMode(muxS0Pin, OUTPUT);
	pinMode(muxS1Pin, OUTPUT);
	pinMode(muxS2Pin, OUTPUT);
	pinMode(muxS3Pin, OUTPUT);

	digitalWrite(muxS0Pin, LOW);
	digitalWrite(muxS1Pin, LOW);
	digitalWrite(muxS2Pin, LOW);
	digitalWrite(muxS3Pin, LOW);

	pinMode(SER, OUTPUT);
	pinMode(SRCLK, OUTPUT);
	pinMode(SRCLR, OUTPUT);
	pinMode(RCLK, OUTPUT);
	pinMode(OE, OUTPUT);

	clearShiftRegisters(); turnOutputsOn(); oneAtATime(); clearShiftRegisters();
}

//////////////////////////////MAIN LOOP//////////////////////////////////

void loop() {
	getVelocity();
	printData();
}

///////////////////////GET VELOCITY FUNCTION//////////////////////////////

void getVelocity() {
	readingA = analogRead(piezoA);
	readingB = analogRead(piezoB);
	velocityReading = (readingA + readingB / 2);
	velocityReading = map(velocityReading, 0, 1023, 0, 127);
}

//////////////////////PRINT VELOCITY/NOTE DATA FUNCTION///////////////////////////////

void printData() {
	Serial.print(velocityReading); Serial.print("\t");
	for (byte input = 0; input < 16; ++input) {
		Serial.print(readMux(input)); Serial.print("\t");
	}
	Serial.println();
}

/////////////////////////READMUX FUNCTION/////////////////////////////////

int readMux(int input) {

	int controlPin[] = { muxS0Pin, muxS1Pin, muxS2Pin, muxS3Pin };

	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
	};
	//loop through the 4 contol pins
	for (int k = 0; k < 4; k++) {
		digitalWrite(controlPin[k], muxChannel[input][k]);
	}
	return capSense.capacitiveSensor(sampleRate);
}

//////////////////////////////////MIDI FUNCTIONS////////////////////////////////

void sendMIDIHeader(uint8_t header) {
	static unsigned long lastHeaderTime = millis();
	static uint8_t runningHeader = 0;
	if (header != runningHeader // If the new header is different from the previous
		|| (millis() - lastHeaderTime)
	> headerResendTime) { // Or if the last header was sent more than 1 s ago
		Serial1.write(header); // Send the status byte over Serial
		runningHeader = header; // Remember the new header
		lastHeaderTime = millis();
	}
}
void sendMIDI(uint8_t messageType, uint8_t channel, uint8_t data1, uint8_t data2) {
	if (messageType == NOTE_OFF) { // Replace note off messages
		messageType = NOTE_ON; // with a note on message
		data2 = 0; // with a velocity of zero.
	}
	channel--; // Decrement the channel, because MIDI channel 1
	// corresponds to binary channel 0
	uint8_t statusByte = messageType | channel; // Combine the messageType (high nibble)
	// with the channel (low nibble)
	// Both the message type and the channel
	// should be 4 bits wide
	statusByte |= 0b10000000; // Set the most significant bit of the status byte
	data1 &= 0b01111111; // Clear the most significant bit of the data bytes
	data2 &= 0b01111111;
	sendMIDIHeader(statusByte); // Send the header over Serial, using running
	//status
	Serial1.write(data1); // Send the data bytes over Serial
	Serial1.write(data2);
}
void sendMIDI(uint8_t messageType, uint8_t channel, uint8_t data) {
	channel--; // Decrement the channel, because MIDI channel 1
	// corresponds to binary channel 0
	uint8_t statusByte = messageType | channel; // Combine the messageType (high nibble)
	// with the channel (low nibble)
	// Both the message type and the channel
	// should be 4 bits wide
	statusByte |= 0b10000000; // Set the most significant bit of the status byte
	data &= 0b01111111; // Clear the most significant bit of the data byte
	sendMIDIHeader(statusByte); // Send the header over Serial, using running
	//status
	Serial1.write(data); // Send the data byte over Serial
}

/////////////////////////SHIFT REGISTER FUNCTIONS/////////////////////

void oneAtATime() {
	turnOutputsOn();
	int data = HIGH;
	int i;
	for (i = 0; i < TOTAL_SHIFT_PINS; ++i) {
		shiftDataIn(data); // first time here data will be HIGH.
		copyShiftToStorage();
		delay(77);
		data = LOW; // after first time it will always be LOW.
	}
}
// This doesn't change the value stored in the storage registers.
void turnOutputsOn() {
	digitalWrite(OE, LOW);
}

// This doesn't change the value stored in the storage registers.
void turnOutputsOff() {
	digitalWrite(OE, HIGH);
}

// clear the shift registers without affecting the storage registers.
void clearShiftRegisters() {
	digitalWrite(SRCLR, LOW);
	digitalWrite(SRCLR, HIGH);
}

// All the data in the shift registers moves over 1 unit and the new data goes in at shift register 0.
// The data that was in the shift register 7 goes to the next register (if any).
void shiftDataIn(int data) {
	digitalWrite(SER, data);
	digitalWrite(SRCLK, HIGH);
	digitalWrite(SRCLK, LOW);
}

// copy the 8 shift registers into the shift registers,
// which changes the output voltage of the pins.
void copyShiftToStorage() {
	digitalWrite(RCLK, HIGH);
	digitalWrite(RCLK, LOW);
}
Could you put your code between code tags in future, using the # button.
It makes the code so much more easy to read and therefore helps others help you.
By the way congrats on commenting the code, so many people don't seem to bother these days.
 
Back
Top