Teensy LC + CD74HC4051 multiplex Midi problem...

Here is a version retaining the array of ResponsiveAnalogRead (RAR) objects (thanks to Christoph for sorting me out on this!).
It's now very similar to the analog section of my example code and was very easy to implement the other changes once I had my head straight on how to get an RAR array when the pin is always the same.

Code:
//************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] = {21,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 = 0;
int pin_Out_S1 = 1;
int pin_Out_S2 = 2;
int pin_Out_S3 = 3;
int pin_In_Mux1 = A1;


//****** 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); 
}


As an added benefit you can leave your MUX pins float if you point their initialization at a grounded Teensy pin... RAR will read that (stable) value and you won't see 'noise' messages flying everywhere.

It also allows me to test a bit more... By having only 1 RAR object point at the pin where I have a control and the rest at a grounded pin I can get a better idea of how it's working because I only see messages generated for that MUX CC.

So I think I've tested everything except the MUX logic and timing which require the hardware.

And the timing is everything... With RAR and three-bits of reduction stability should be very easy to achieve and hardware filtering never seems to really be needed at seven bits.

That is if you can live with whatever trade-off between responsiveness and stability that the additional processing and timing issues with controlling a MUX entail. Of hardware changes lower resistance on your pots (if you can afford the current and not really below 10KOhm) and better quality pots likely have the best payoff. That and some care with signal paths.
 
Last edited:
I was at my local electronics shop and picked up a CD74HC4067 MUX breakout.

So I was able to try my code (RAR version) and, ...well I had it pretty close....

Biggest problem was I forget to setup for output for digital write (which I often do) so it took me a while to figure why the MUX would only read Ch1.

Other than that the only change is I was writing to the pins before the the index is incremented which seems clearly wrong to me today.
(Changes are made in the code posted above but not yet in the roll-your-own-hysteresis version.)

I haven't tested extensively but it appears solid even when I allow MUX reads as close a 5 microsecond apart. (Since found one setup where 5 uS left one channel bouncing between adjacent MIDI outputs but 25 uS seems to be very stable with my electrical setup.)

So far I'm using a single pot with the rest of the pins held LOW except one other which is held HIGH...

I used a header with a wire soldering 14 terminals together to make holding the MUX pins stable at some voltage easier.
headerBar.jpg
This way I can tie 14 HIGH, LOW or to a divider with one wire instead of having ground them all separately.
 
Last edited:
You could always slow down the ADC Clock(ADCK) also. I think by default in Analog.c the minimum is 12Mhz(@48Mhz), you can drop it all the way down to 3Mhz I believe.
Would probably help with charging the ADC if you gave it a little more time each read.
 
You could always slow down the ADC Clock(ADCK) also. I think by default in Analog.c the minimum is 12Mhz(@48Mhz), you can drop it all the way down to 3Mhz I believe...
Good to know but both the RAR version and my deadband version are very stable with very quick polling intervals.

Here's my corrected dead-band hysteresis version which appears to be stable even if muxTimeMin is set to zero.
Code:
// ******CONSTANT VALUES******** 
// customize code behaviour here!
const int muxTimeMin = 5; // minimum micro-seconds between MUX reads
const int channel = 1; // MIDI channel
const int A_PINS = 16; // number of Analog PINS (these are now MUX pins)
const int D_PINS = 3; // number of Digital PINS
const int ON_VELOCITY = 99; // note-one velocity sent from buttons (should be 65 to  127)

// define the CC ID numbers on which to send them..
const int CCID[A_PINS] = {21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36}; // set your own CC values for the 16 MUX pins

const int dbt = 8; // a threshold value for how big the dead-band is before recalc

//******VARIABLES***********
//mapping of mux to teensy digital pins
int pin_Out_S0 = 0;
int pin_Out_S1 = 1;
int pin_Out_S2 = 2;
int pin_Out_S3 = 3;
int pin_In_Mux1 = A0;
//the state of the mux channels(initialized to zero)
int Mux1_State[16] = {0};
int Mux1_State_Lagged[16] = {0};
elapsedMicros mux1Updated;
//elapsedMillis mux1Updated; // switch to millis to troubleshoot 
byte i = 0; // index for mux loop


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() {
  getMUX1Data();
  // other work here... mux is called once in each loop rather than updating all in every loop
  while (usbMIDI.read()) {
     // controllers must call .read() to keep the queue clear even if they are not responding to MIDI
  }
}


//************MUX1 SECTION**************
void getMUX1Data(){  
  if (mux1Updated>muxTimeMin) {

     Mux1_State[i] = analogRead(pin_In_Mux1);

    if(abs(Mux1_State[i] - Mux1_State_Lagged[i]) > dbt ) {
      Mux1_State_Lagged[i] = Mux1_State[i];
      

        usbMIDI.sendControlChange(CCID[i], Mux1_State[i]>>3, 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(Mux1_State[i]>>3,HEX); 
}
 
heyo! Sorry I've been busy lately!
Dealing with a super painful hiatial hernia I got when my lung collapsed a few months ago...hernia started feeling way worse recently and the painkillers are making it difficult to juggle projects (i have waaay too many on the go right now and its really difficult to focus :( )

Ill check back in later but its looking like I'll need surgery again so I'm not really sure when Ill have the time...gad dangit.
 
sorry to hear that... when you are feeling better I hope you're able to make use of this.
 
Heyo! I got a chance to try it out and your code worked! Multiple analog inputs works perfectly through the mux! annnd the cc values didnt seem to jitter at all. You're a gad dang wizard!
related: any idea how to output this midi info from the teensy to 5 pin midi? Usb is awesome but the thing I'm using this for requires 5 pin apparently.

Im gunna start drafting up a vid on how to wire this thing and plop it on youtube this week. It turns out that I needed to ground my EE pin to get everything to work so Ill be damn sure to include that in the schematic......Im obviously inexperienced with multiplexers so It kinda makes sense that Id miss something like that lol

You kick ass! Thanks so much, this solves like 10000 problems at once for me !!!
 
I hope you're feeling better!

Heyo! I got a chance to try it out and your code worked! Multiple analog inputs works perfectly through the mux! annnd the cc values didnt seem to jitter at all.
Have you tried both versions? The RAR version might me more usable in noisier electrical situations but I believe the noise you were getting was mostly from the mux channel change being sent too close to the analog read. (The dead-zone can be expanded in the other version to stabilize very noisy signals but this can limit responsiveness and make finding specific CC values with a control difficult or even impossible if the threshold it too broad.)

...related: any idea how to output this midi info from the teensy to 5 pin midi? Usb is awesome but the thing I'm using this for requires 5 pin apparently.
Here's where to start: https://www.pjrc.com/teensy/td_libs_MIDI.html

If you are only doing MIDI-out then the hardware is pretty simple except you need to lower the values on the current-limiting resistors to run at 3.3 volts but still conduct sufficient current to operate the optocoupler in the receiving unit.

Some schematics for 3.3v omit any resistor on the Tx line, assuming the 220 ohm on the receiving end will do that job but this is dangerous if the line gets shorted.

Paul recommends 47 ohm for both pull-up and Tx lines. While there is no word of these values not working with any receiver; the MIDI 1.0 Electrical Specification Update gives 10 Ohms for the Tx line (enough to limit current if shorted) and 33 Ohm as a pull-up.

The MIDI library calls are nearly identical to usbMIDI and you should be able to use them instead of, or in addition to sending usbMIDI.
For control change messages the code is identical, just change usbMIDI.sendControlChange() to MIDI.sendControlChange().


The other missing update in the documentation above is you now need to initialize each MIDI object before talking to it in your script in the MIDI library that is loaded with the current Teensyduino; but if you're using Tx to send normally you just need to add this before setup():
MIDI_CREATE_DEFAULT_INSTANCE()

Use the sample test code with this line added to test your hardware before trying anything more complex.
Code:
#include <MIDI.h>
const int channel = 1;
[COLOR="#FF0000"]MIDI_CREATE_DEFAULT_INSTANCE() [/COLOR]

void setup() {
  MIDI.begin();
}

void loop() {
  int note;
  for (note=10; note <= 127; note++) {
    MIDI.sendNoteOn(note, 100, channel);
    delay(200);
    MIDI.sendNoteOff(note, 100, channel);
  }
  delay(2000);
}

It turns out that I needed to ground my EE pin to get everything to work so Ill be damn sure to include that in the schematic......Im obviously inexperienced with multiplexers so It kinda makes sense that Id miss something like that lol ...
Hey... I wasted an hour trying to figure why the MUX wasn't responding before I remembered you have to set pins to OUTPUT mode. And that was in my very first Teensy lesson.
 
HUGE REPLY!:D
I think I have a couple 47 ohm resistors laying around here. Ill give er' a go later on (hopefully tonight)

Once again, thanks for your time. I can't wait to make a video on how to do this for others. There's a lot of people who will be tripping balls once they see how quickly this project can be completed!

edit: gunna try out the rar version too, if it works half as well as the other did it'll be gold.
 
I've been testing a version of this code, and for some reason the ResponsiveAnalogRead messes up the top range of the Pots, limiting it to 1018/1019.

In conjunction with the left shift - this means MIDI 127 cannot be reached.

From my testing it seems as soon as one initialises a RAR (either the whole array, or just a single array position), AnalogRead will only ever return no higher than 1019.

This can of course be "mitigated" by calling setAnalogResolution and using 1019 - but it's a bit messy.

Anyone else hit this, or anyone using the same code and getting 1022/1023 as the top end and perhaps i'm missing something obvious??

If i use RAR without a multiplexer and just a couple pots connected to analog inputs this does not occur.

Note: I modified the code a tad to output the analog value


Code:
//************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] = {21,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 = 2;
int pin_Out_S1 = 3;
int pin_Out_S2 = 4;
int pin_Out_S3 = 5;
int pin_In_Mux1 = A1;


//****** 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(" ANALOG: ");
  Serial.print(analogRead(pin_In_Mux1));
  Serial.print(" DATA: ");
  Serial.print(data[i],DEC);
  Serial.print(" CC: ");
  Serial.print(CCID[i],DEC); 
  Serial.print(" DATA HEX: ");
  Serial.println(data[i],HEX); 
}


sample output:


Code:
10 :0101 MUX_PIN: 10 ANALOG: 935 DATA: 114 CC: 31 DATA HEX: 72
10 :0101 MUX_PIN: 10 ANALOG: 963 DATA: 118 CC: 31 DATA HEX: 76
10 :0101 MUX_PIN: 10 ANALOG: 993 DATA: 122 CC: 31 DATA HEX: 7A
10 :0101 MUX_PIN: 10 ANALOG: 1017 DATA: 124 CC: 31 DATA HEX: 7C
10 :0101 MUX_PIN: 10 ANALOG: 1018 DATA: 125 CC: 31 DATA HEX: 7D
10 :0101 MUX_PIN: 10 ANALOG: 1018 DATA: 126 CC: 31 DATA HEX: 7E
 
OK, well it turns out using the sample provided by RAR with a single pot does exactly the same thing after all. So perhaps it's my cheapo pots - strikes me rather weird thou becasue if i just use AnalogRead then i get the full range. not very responsive! :)
 
Have been using ResponsiveAnalogRead on almost every project where analog pots or faders were used.
And it never happened that RAR couldn't read 1023.
What is weird in your case is that AnalogRead does read 1023 while RAR reads 1018.

That latest test you ran ("using the sample provided by RAR"), was that including a mux? And on which Teensy?
Are you using RAR library V1.2.1?

Paul
 
I just realized that I only used RAR on numerous Teensy LC's and 3.2's with analog pots, but never on a Teensy 4.x.
If you are actually using a Teensy 4.x, you may run into the issue described here. Problem is that the RAR code actually sets the pin to input:
Code:
void ResponsiveAnalogRead::begin(int pin, bool sleepEnable, float snapMultiplier){
    pinMode(pin, INPUT ); // ensure button pin is an input
    digitalWrite(pin, LOW ); // ensure pullup is off on button pin
    
    this->pin = pin;
    this->sleepEnable = sleepEnable;
    setSnapMultiplier(snapMultiplier);
}

while you want pinMode(pin, INPUT_DISABLE); here...

I will do some tests with a Teensy 4.0 and analog pot/fader coming weekend.

Paul
 
thank you thank you thank you!!! :)

this place ROCKS!

with

Code:
 pinMode(pin_In_Mux1, INPUT_DISABLE);

in setup(), i now get

Code:
0 :0000 MUX_PIN: 0 ANALOG: 1004 DATA: 123 CC: 21 DATA HEX: 7B
7 :1110 MUX_PIN: 7 ANALOG: 500 DATA: 62 CC: 28 DATA HEX: 3E
0 :0000 MUX_PIN: 0 ANALOG: 1009 DATA: 124 CC: 21 DATA HEX: 7C
0 :0000 MUX_PIN: 0 ANALOG: 1022 DATA: 126 CC: 21 DATA HEX: 7E
0 :0000 MUX_PIN: 0 ANALOG: 1022 DATA: 127 CC: 21 DATA HEX: 7F


Yes, I am on Teensy 4.1 with RAR 1.2.1.

for anyone else who stumbles on this later, reference the single pot test (now irrelevant) was this code from the RAR Github.

Code:
// include the ResponsiveAnalogRead library
#include <ResponsiveAnalogRead.h>

// define the pin you want to use
const int ANALOG_PIN = A2;

// make a ResponsiveAnalogRead object, pass in the pin, and either true or false depending on if you want sleep enabled
// enabling sleep will cause values to take less time to stop changing and potentially stop changing more abruptly,
//   where as disabling sleep will cause values to ease into their correct position smoothly and more accurately
ResponsiveAnalogRead analog(ANALOG_PIN, true);


// the next optional argument is snapMultiplier, which is set to 0.01 by default
// you can pass it a value from 0 to 1 that controls the amount of easing
// increase this to lessen the amount of easing (such as 0.1) and make the responsive values more responsive
// but doing so may cause more noise to seep through if sleep is not enabled

void setup() {
  // begin serial so we can see analog read values through the serial monitor
  Serial.begin(9600);
  analog.enableEdgeSnap();
  analog.setAnalogResolution(1019);
}

void loop() {
  // update the ResponsiveAnalogRead object every loop
  analog.update();

  Serial.print(analog.getRawValue());
  Serial.print("\t");
  Serial.print(analog.getValue());
  
  // if the repsonsive value has change, print out 'changed'
  if(analog.hasChanged()) {
    Serial.print("\tchanged");
  }
  
  Serial.println("");
  delay(20);
}


QUOTE=PaulS;333355]I just realized that I only used RAR on numerous Teensy LC's and 3.2's with analog pots, but never on a Teensy 4.x.
If you are actually using a Teensy 4.x, you may run into the issue described here. Problem is that the RAR code actually sets the pin to input:
Code:
void ResponsiveAnalogRead::begin(int pin, bool sleepEnable, float snapMultiplier){
    pinMode(pin, INPUT ); // ensure button pin is an input
    digitalWrite(pin, LOW ); // ensure pullup is off on button pin
    
    this->pin = pin;
    this->sleepEnable = sleepEnable;
    setSnapMultiplier(snapMultiplier);
}

while you want pinMode(pin, INPUT_DISABLE); here...

I will do some tests with a Teensy 4.0 and analog pot/fader coming weekend.

Paul[/QUOTE]
 
Back
Top