Make a USB midi controller with 16 (to 27) analog inputs for cheap!

controllers

#1

Hi everyone!
Here's how to make a USB midi controller for dirt cheap!
This one has has a red breakoutboard that you can attach 16 analog potentiometers to.
The user @SmashedTransistors reminded me that If you choose to utilize the remaining analog inputs on the Teensy then this project will end up adding up to 27 inputs!!!

what you'll need:

  • 1 Teensy LC ---> buy from pjrc $11.65
  • 1 CD74HC4067 breakout board ---> buy from sparkfun ($4.95)
  • 1 to 16 10k linear B potentiometers. ---> buy from ebay (5pc for $1.25)
  • 1 Breadboard -----> buy from ebay ($1.50)
  • Wire....pennies per ft! buy lots because its always handy to have lots of.

Step 1:
Once you have all these items wire them up together like this...for simplicity sake only 3 potentiometers are shown but you can connect 16, just remember to connect any additional pots to +/- and the center pins of the potentiometers go to the "C" pins on the red breakout board.

Step 2:
Install Arduino 1.8.4 (newer versions don't support teensy LC yet :frowning: )
Get it here ---> https://www.arduino.cc/en/Main/OldSoftwareReleases

Step 3:
Install the Teensy loader application
Get it here ---> https://www.pjrc.com/teensy/loader.html

Step 4:
Plug your Teensy into your computer via usb...

Step 5:
Open Arduino 1.8.4 and click "tools" and then set the board type to Teensy LC

Step 6:
Click tools and also set the USB type to MIDI

Step 7:
Delete the "void setup" and "loop setup" code so you have a nice blank page in arduino.

Step 8:
Paste this code into the blank page

//************LIBRARIES USED**************
// include the ResponsiveAnalogRead library for analog smoothing
#include <ResponsiveAnalogRead.h>
//usbMIDI.h library is added automatically when code is compiled as a MIDI device

// ******CONSTANT VALUES******** 
// customize code behaviour here!
const int muxTimeMin = 500; // minimum micro-seconds between MUX reads 
const int channel = 1; // MIDI channel
const int MUX_PINS = 16; // number of MUX Channnels

// define the CC ID numbers on which to send them..

const int CCID[MUX_PINS] = {70,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36};


//******VARIABLES***********
// a data array and a lagged copy to tell when MIDI changes are required
byte data[MUX_PINS];
byte dataLag[MUX_PINS]; // when lag and new are not the same then update MIDI CC value
byte i=0; // global index for MUX channel reads

//mapping of mux to teensy digital pins
int pin_Out_S0 = 4;
int pin_Out_S1 = 3;
int pin_Out_S2 = 2;
int pin_Out_S3 = 1;
int pin_In_Mux1 = A0;


//****** TIMER VARIABLE *** change scale here!
elapsedMicros mux1Updated; // switch to micros to run at speed and tune with muxTimeMin setting above
//elapsedMillis mux1Updated; // switch to millis to troubleshoot 

//************INITIALIZE LIBRARY OBJECTS**************
// initialize the ReponsiveAnalogRead objects
ResponsiveAnalogRead analog[]{
  {pin_In_Mux1 ,true},
  {pin_In_Mux1 ,true},
  {pin_In_Mux1 ,true},
  {pin_In_Mux1 ,true},
  {pin_In_Mux1 ,true},
  {pin_In_Mux1 ,true},
  {pin_In_Mux1 ,true},
  {pin_In_Mux1 ,true},
  {pin_In_Mux1 ,true},
  {pin_In_Mux1 ,true},
  {pin_In_Mux1 ,true},
  {pin_In_Mux1 ,true},
  {pin_In_Mux1 ,true},
  {pin_In_Mux1 ,true},
  {pin_In_Mux1 ,true},
  {pin_In_Mux1 ,true}
}; 



//************SETUP**************
void setup() {
  //! don't forget to set for output!
  pinMode(pin_Out_S0, OUTPUT);
  pinMode(pin_Out_S1, OUTPUT);
  pinMode(pin_Out_S2, OUTPUT);
  pinMode(pin_Out_S3, OUTPUT);
}

//************LOOP**************
void loop() {
  nextMUXpin();
  while (usbMIDI.read()) {
     // controllers must call .read() to keep the queue clear even if they are not responding to MIDI
  }
}


//************MUX SECTION**************
void nextMUXpin(){  
  if (mux1Updated>muxTimeMin) {  
    // update the ResponsiveAnalogRead object every loop
    analog[i].update(); 
    // if the repsonsive value has change, print out 'changed'
    if(analog[i].hasChanged()) {
      data[i] = analog[i].getValue()>>3;
      if (data[i] != dataLag[i]){
        dataLag[i] = data[i];
        usbMIDI.sendControlChange(CCID[i], data[i], channel);
        serialPringMIDIdata(); // use to troublshoot
      }
    }  

    //reset timer
    mux1Updated = 0; 
    //increment index
    i++;
    if (i>15)   {i=0;}      
    // set mux control pins for next pass
    digitalWrite(pin_Out_S0, HIGH && (i & B00000001));
    digitalWrite(pin_Out_S1, HIGH && (i & B00000010));
    digitalWrite(pin_Out_S2, HIGH && (i & B00000100));
    digitalWrite(pin_Out_S3, HIGH && (i & B00001000));
  }
}

// **useful for debugging, comment out function call to run full speed
void serialPringMIDIdata(){
  Serial.print(i,DEC);
  Serial.print(" :");
  Serial.print(HIGH && (i & B00000001),BIN);
  Serial.print(HIGH && (i & B00000010),BIN);
  Serial.print(HIGH && (i & B00000100),BIN);
  Serial.print(HIGH && (i & B00001000),BIN);
  Serial.print(" MUX_PIN: ");
  Serial.print(i,DEC); 
  Serial.print(" CC: ");
  Serial.print(CCID[i],DEC); 
  Serial.print(" DATA HEX: ");
  Serial.println(data[i],HEX); 
}

It was made by the user oddson over at the Teensy forum (https://forum.pjrc.com) ... HERE is a link to the full thread if you're bored and wanna see how he developed it.

To set the CC values of the C0 pins on the red breakoutboard, change the values in the "const int CCID[MUX_PINS] = {70,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36};" to whatever you want! left to right (70 to 36) are c0 to c15

Step 9:
Click "verify"

Wait a little while and allow the arduino software to verify and compile the code.

Step 10:
Once its done compiling, click "upload"

Step 11:
Plug your fancy new DIY midi controller into the axoloti and enjoy!

Note: this USB midi controller is gunna take up your USB port..not ideal if you want to use a USB keyboard....I'll make another tutorial on how to hardwire one of these directly to the MIDI in pins once I figure it out...many people have attempted to help me so far but I'm pretty dense and haven't been able to get it to work yet....:sweat_smile:


Teensy and Mux Through I2C
New retro lookin' synth .... v.02
#2

Thanks @Androoclops !

How many usable analog inputs does the teensy LC have without the 4067 ?


#3

12 I believe.
Adding this board to it increases the number to 27 .


#4

12, wow, just what i need.
I'm investigating how to make a very compact pressure sensitive (poly aftertouch) 60 key keyboard arranged in a 12 x 5 matrix...
At the moment, i plan to implement it on the Axoloti, but the teensy is smaller and best fitted for a midi project without audio.


#5

cool! Post that project when you're done!


#6

oh yeah, heres a video of how to make one without a breakout board


I've done this tutorial before, its quite simple and fun.

#7

amazing the money we save DIY... I've watched that vid before... well, ok, a few times. cheers!


#8

@Androoclops,

My goal is to make the simplest and fastest way to scan a 12 x 5 FSR matrix :roll_eyes:

I have a few questions regarding multiplexing and the teensy LC.

  • What is the maximum multiplexing speed it can achieve with the 4067 ?

  • Does the teensy LC digital outputs can be set to open drain ?

  • If so, how much current can they handle ?

    • self answer about drive current : Teensy LC thread : 5mA on most outputs excepted 4 outputs that are 20mA :face_with_raised_eyebrow:
  • how good are the analog inputs on the teensy. (On the Axoloti, i find them quite jittery and i may say that 8 bits out of 12 are usable, even with shielding).

Sorry for this bunch of questions, but i think i am hooked.


#9

About one problem you didn't mention, I can't find teensy lc board in the tools list, so I google it, turns out I need to download a file called teensyduino on PJRC.com/ , and now it's solved.

Also I wanna know if I wanna use two 4067 on one LC board, how should I change the code?


#10

Just make another analog in pin like int pin_In_Mux2 = A1; And add all code with Mux1, but then Mux2


#11

Thank you

I've tried but the CC numbers are messy with each other and the signal is not stable... Some knobs can control more than 1 signal and I can't fix it. Here's my code, can you help me?

//************LIBRARIES USED**************
// include the ResponsiveAnalogRead library for analog smoothing

include

include

int RUN = 7;
//usbMIDI.h library is added automatically when code is compiled as a MIDI device

// ******CONSTANT VALUES********
// customize code behaviour here!
const int muxTimeMin = 500; // minimum micro-seconds between MUX reads
const int channel = 1; // MIDI channel
const int MUX1_PINS = 16; // number of MUX Channnels
const int MUX2_PINS = 16;

// define the CC ID numbers on which to send them..

const int CCID1[MUX1_PINS] = {18,17,16,15,14,13,12,11,28,27,26,25,24,23,22,21};
const int CCID2[MUX2_PINS] = {0,1,2,10,31,32,33,34,35,36,20,3,4,5,6,30};

//******VARIABLES***********
// a data array and a lagged copy to tell when MIDI changes are required
byte data1[MUX1_PINS];
byte dataLag1[MUX1_PINS]; // when lag and new are not the same then update MIDI CC value
byte i=0; // global index for MUX channel reads
byte data2[MUX2_PINS];
byte dataLag2[MUX2_PINS];

//mapping of mux to teensy digital pins
int pin_Out1_S0 = 1;
int pin_Out1_S1 = 2;
int pin_Out1_S2 = 3;
int pin_Out1_S3 = 4;
int pin_In_Mux1 = A9;

int pin_Out2_S0 = 5;
int pin_Out2_S1 = 6;
int pin_Out2_S2 = 7;
int pin_Out2_S3 = 8;
int pin_In_Mux2 = A8;

//****** TIMER VARIABLE *** change scale here!
elapsedMicros mux1Updated; // switch to micros to run at speed and tune with muxTimeMin setting above
//elapsedMillis mux1Updated; // switch to millis to troubleshoot

//************INITIALIZE LIBRARY OBJECTS**************
// initialize the ReponsiveAnalogRead objects
ResponsiveAnalogRead analog1[]{
{pin_In_Mux1 ,true},
{pin_In_Mux1 ,true},
{pin_In_Mux1 ,true},
{pin_In_Mux1 ,true},
{pin_In_Mux1 ,true},
{pin_In_Mux1 ,true},
{pin_In_Mux1 ,true},
{pin_In_Mux1 ,true},
{pin_In_Mux1 ,true},
{pin_In_Mux1 ,true},
{pin_In_Mux1 ,true},
{pin_In_Mux1 ,true},
{pin_In_Mux1 ,true},
{pin_In_Mux1 ,true},
{pin_In_Mux1 ,true},
{pin_In_Mux1 ,true},
};
ResponsiveAnalogRead analog2[]{
{pin_In_Mux2 ,true},
{pin_In_Mux2 ,true},
{pin_In_Mux2 ,true},
{pin_In_Mux2 ,true},
{pin_In_Mux2 ,true},
{pin_In_Mux2 ,true},
{pin_In_Mux2 ,true},
{pin_In_Mux2 ,true},
{pin_In_Mux2 ,true},
{pin_In_Mux2 ,true},
{pin_In_Mux2 ,true},
{pin_In_Mux2 ,true},
{pin_In_Mux2 ,true},
{pin_In_Mux2 ,true},
{pin_In_Mux2 ,true},
{pin_In_Mux2 ,true}
};

//************SETUP**************
void setup() {
//! don't forget to set for output!
pinMode(pin_Out1_S0, OUTPUT);
pinMode(pin_Out1_S1, OUTPUT);
pinMode(pin_Out1_S2, OUTPUT);
pinMode(pin_Out1_S3, OUTPUT);
pinMode(pin_Out2_S0, OUTPUT);
pinMode(pin_Out2_S1, OUTPUT);
pinMode(pin_Out2_S2, OUTPUT);
pinMode(pin_Out2_S3, OUTPUT);
Serial.begin(31250);
pinMode(RUN, INPUT);
}

//************LOOP**************
void loop() {

int STATE = digitalRead(RUN);
Serial.println(RUN);
delay(1);

nextMUXpin();
while (usbMIDI.read()) {
// controllers must call .read() to keep the queue clear even if they are not responding to MIDI
}
}

//************MUX SECTION**************
void nextMUXpin(){
if (mux1Updated>muxTimeMin) {
// update the ResponsiveAnalogRead object every loop
analog1[i].update();
analog2[i].update();
// if the repsonsive value has change, print out 'changed'
if(analog1[i].hasChanged()) {
data1[i] = analog1[i].getValue()>>3;
if (data1[i] != dataLag1[i]){
dataLag1[i] = data1[i];
usbMIDI.sendControlChange(CCID1[i], data1[i], channel);
serialPringMIDIdata(); // use to troublshoot
}
}
if(analog2[i].hasChanged()) {
data2[i] = analog2[i].getValue()>>3;
if (data2[i] != dataLag2[i]){
dataLag2[i] = data2[i];
usbMIDI.sendControlChange(CCID2[i], data2[i], channel);
serialPringMIDIdata(); // use to troublshoot
}
}

//reset timer
mux1Updated = 0; 
//increment index
i++;
if (i>15)   {i=0;}      
// set mux control pins for next pass
digitalWrite(pin_Out1_S0, HIGH && (i & B00000001));
digitalWrite(pin_Out1_S1, HIGH && (i & B00000010));
digitalWrite(pin_Out1_S2, HIGH && (i & B00000100));
digitalWrite(pin_Out1_S3, HIGH && (i & B00001000));

digitalWrite(pin_Out2_S0, HIGH && (i & B00000001));
digitalWrite(pin_Out2_S1, HIGH && (i & B00000010));
digitalWrite(pin_Out2_S2, HIGH && (i & B00000100));
digitalWrite(pin_Out2_S3, HIGH && (i & B00001000));

}
}

// **useful for debugging, comment out function call to run full speed
void serialPringMIDIdata(){
Serial.print(i,DEC);
Serial.print(" :");
Serial.print(HIGH && (i & B00000001),BIN);
Serial.print(HIGH && (i & B00000010),BIN);
Serial.print(HIGH && (i & B00000100),BIN);
Serial.print(HIGH && (i & B00001000),BIN);
Serial.print(" MUX_PIN: ");
Serial.print(i,DEC);
Serial.print(" CC: ");
Serial.print(CCID1[i],DEC);
Serial.print(" DATA HEX: ");
Serial.println(data1[i],HEX);

Serial.print(i,DEC);
Serial.print(" :");
Serial.print(HIGH && (i & B00000001),BIN);
Serial.print(HIGH && (i & B00000010),BIN);
Serial.print(HIGH && (i & B00000100),BIN);
Serial.print(HIGH && (i & B00001000),BIN);
Serial.print(" MUX_PIN: ");
Serial.print(i,DEC);
Serial.print(" CC: ");
Serial.print(CCID2[i],DEC);
Serial.print(" DATA HEX: ");
Serial.println(data2[i],HEX);
}

yes I also wrote a button for the start function but it still not work either...


#12

What value pots are you using?
How many pots are you using?


#13

I'm using 1,2,3,4 and A9 for the first mux, and 5,6,7,8 plus A8 for the second mux.


#14

Thank you guys again
I just figured it out!!
I duplicated the code and set all data into double, and it worked!!
I‘ve been tracking this webpage for half a year(A chinese girl who wasn't good at english and was using translation software to understand what I see LOL) and my little instrument has been through 3 versions... I'll send my project here after the exhibition this weekend!!!!!
Thank you so much~~~~~


#15

Can you post the basics of what you've done (parts used), I want to do something very similar.


#16

Why don't you reuse the same outputs to control both 4067 ?


#17

I need a list of CC number to control every knobs in the patch. Use the same outputs is unstable for the signal


#18

We understand that you need CC for every knob.
But, you can use the same selectors (1,2,3,4) on both mux, and use A8 for knob 0-15, and A9 for knob 16-31.
So, you only use 6 Axoloti pins, instead of 10.

If this did not work, then there was an error in your wiring or your code.


#19

Hello. It not directly relate but notesandvolts member on youtube have good tutorial for how to do many control with midi using mux method. It can be handy for lots of controls into few connections.


#20

I really didn't think of it... I may try this method on the other projects. Thank you.