Control Surface Library - MCU Question

Good day, I'm starting a MIDI console project and I've copied the "MIDI-Controller-Finished-Example" code as an example. I was able to assign the 4 faders and the pan pots using MIDI Learn but I have a few items not working right.

1.I assigned the 4 buttons but they work just once. I removed the reference to the [MCU::MUTE_1] for that didn't seem to work.
2. The encoder has 5 pins (VCC, GND, S1, S2, Key) put the code has only 2 pins assigned.
3. Also the button to select bank doesn't seem to work.

How does one set up the MCU? I'm working with Logic Pro X.

Here the code:

 
#include <Control_Surface.h>
 
USBMIDI_Interface usbmidi;
 
// If the jog wheels or other encoders are too slow in your software, increase
// this value.
// (It will be multiplied with the actual speed of the encoder, as the name
// implies.) Default is 1.
const int speedMultiplier = 1;
// 
Bank<2> bank(4); // A bank with four channels, and 2 bank settings
 
Bankable::CCPotentiometer faders[] {
  {{bank, BankType::CHANGE_CHANNEL}, A0, {MIDI_CC::Channel_Volume, CHANNEL_1}},
  {{bank, BankType::CHANGE_CHANNEL}, A1, {MIDI_CC::Channel_Volume, CHANNEL_2}},
  {{bank, BankType::CHANGE_CHANNEL}, A2, {MIDI_CC::Channel_Volume, CHANNEL_3}},
  {{bank, BankType::CHANGE_CHANNEL}, A3, {MIDI_CC::Channel_Volume, CHANNEL_4}},
};
 
//CCPotentiometer knobsTop[] {
//  {A4, 0x10},    
//  {A5, 0x11}, 
//  {A6, MIDI_CC::General_Purpose_Controller_3},
//  {A7, MIDI_CC::General_Purpose_Controller_4},
//};
// 
Bankable::CCPotentiometer knobsSide[] {
  {{bank, BankType::CHANGE_CHANNEL}, A4, {MIDI_CC::Pan, CHANNEL_1}},
  {{bank, BankType::CHANGE_CHANNEL}, A5, {MIDI_CC::Pan, CHANNEL_2}},
  {{bank, BankType::CHANGE_CHANNEL}, A6, {MIDI_CC::Pan, CHANNEL_3}},
  {{bank, BankType::CHANGE_CHANNEL}, A7, {MIDI_CC::Pan, CHANNEL_4}},
};
// 
Bankable::NoteButtonLatching switches[] {
  {{bank, BankType::CHANGE_ADDRESS}, 2, {0x10, CHANNEL_1}},
  {{bank, BankType::CHANGE_ADDRESS}, 3, {0x11, CHANNEL_2}},
  {{bank, BankType::CHANGE_ADDRESS}, 5, {0x12, CHANNEL_3}},
  {{bank, BankType::CHANGE_ADDRESS}, 7, {0x13, CHANNEL_4}},
};
 
CCRotaryEncoder enc {
  {1, 0},                                // pins
  MIDI_CC::General_Purpose_Controller_5, // address
  speedMultiplier,                       // multiplier
  4,                                     // pulses per click
};
 
SwitchSelector selector {bank, 11};
// 
void setup() {
  Control_Surface.begin();
  pinMode(LED_BUILTIN, OUTPUT);
}
 
void loop() { // Refresh all inputs
  Control_Surface.loop();
  digitalWrite(LED_BUILTIN, digitalRead(11));
}

After reworking the code, I got rid the bankable element. Everything works now and I assigned the corresponding command in Logic. I added a LED to the first button (Mute) and it works but somehow nothing else is working...I removed the LED from the code and everything works again. What I am doing wrong? I want to add an LED for every Mute and Solo buttons. Here the code:

#include <Control_Surface.h>

USBMIDI_Interface usbmidi;

int led1 = 20;
//int led2 = 19;
int btnstate1 = true;
//int btnstate2 = true;

const int speedMultiplier = 1;

// Instantiate a CCButton object
CCButton button [] {
  {2, {0x14, CHANNEL_1}}, // Mute 1
  {3, {0x15, CHANNEL_2}}, // Mute 2
  {4, {0x16, CHANNEL_3}}, // Mute 3
  {5, {0x17, CHANNEL_4}}, // Mute 4
  {6, {0x18, CHANNEL_1}}, // Solo 1
  {7, {0x19, CHANNEL_2}}, // Solo 2
  {8, {0x20, CHANNEL_3}}, // Solo 3
  {9, {0x21, CHANNEL_4}}, // Solo 4
  {10, {0x22, CHANNEL_1}},// Individual Track Zoom In
  {11, {0x23, CHANNEL_1}},// Individual Track Zoom Out
  {21, {0x31, CHANNEL_1}},// Set Right Locator by Playhead
  {22, {0x32, CHANNEL_1}},// Set Left Locator by Playhead
 {12, {0x33, CHANNEL_1}},// Cycle
};

CCPotentiometer knobsTop [] {
  {A0, {0x24, CHANNEL_1}}, // Pan 1
  {A1, {0x25, CHANNEL_2}}, // Pan 2
  {A2, {0x26, CHANNEL_3}}, // Pan 3
  {A3, {0x27, CHANNEL_4}}, // Pan 4
  {A4, {0x28, CHANNEL_1}}, // Vertical Zoom 
  {A5, {0x29, CHANNEL_1}}, // Horizontal Zoom
  {A6, {0x30, CHANNEL_1}}, // Mater Volume  
};

//Rotary Encoder For Scrolling
CCRotaryEncoder enc {
  {0, 1},                                // pins - Playhead (Beats, Scrubbing)
  MIDI_CC::General_Purpose_Controller_5, // address
  speedMultiplier,                       // multiplier
  4,                                     // pulses per click
};

void setup() {
  Control_Surface.begin();
  //pinMode(2,INPUT);
  //pinMode(3,INPUT);
  pinMode(led1, OUTPUT);
// pinMode(led2, OUTPUT);

 }

void loop() { // Refresh all inputs
  if (digitalRead(2) == true) {
btnstate1 = !btnstate1;
digitalWrite(led1, btnstate1);
} while(digitalRead(2) == true);
delay(50);

//  if (digitalRead(3) == true) {
//btnstate2 = !btnstate2;
//digitalWrite(led2, btnstate2);
//} while(digitalRead(3) == true);
//delay(50);

Control_Surface.loop();
}


This code is blocking. It won't update the Control Surface code as long as you're in the while loop.
Get rid of the while loop and the delay.

If you're using the MCU protocol, use the CCLED class: Control Surface: MIDI Input Elements LEDs
Otherwise, use the CCButton::getButtonState function, don't call digitalRead() yourself.

ok I see. How you implement CCButton::getButtonState ? Logic is MCU compatible, how would you write code to track Mute, Solo, Record, Select and pan?

#include <Control_Surface.h>

USBMIDI_Interface midi;

template <class T, size_t N> constexpr size_t len(const T (&)[N]) { return N; }

CCButton buttons [] {
  {2, {0x14, CHANNEL_1}}, // Mute 1
  {3, {0x15, CHANNEL_2}}, // Mute 2
  {4, {0x16, CHANNEL_3}}, // Mute 3
  {5, {0x17, CHANNEL_4}}, // Mute 4
};

pin_t leds [] {
  20, 21, 22, 23,
};

BitArray<len(leds)> led_states;

static_assert(len(leds) == len(buttons), "");

void setup() {
  Control_Surface.begin();
  for (auto led : leds)
    pinMode(led, OUTPUT);
}

void loop() {
  Control_Surface.loop();
  for (size_t i = 0; i < len(buttons); ++i) {
    if (buttons[i].getButtonState() == Button::Falling) {
      bool new_state = !led_states.get(i);
      digitalWrite(leds[i], new_state ? HIGH : LOW);
      led_states.set(i, new_state);
    }
  }
}

Use NoteButton and NoteLED, with the correct constants from Control Surface: MCU MIDI Note controls

ok i'll try that later on. i have 100mm fader but in Logic o-127 i'm just half way up fader. Is there a way to adjust that?

127 is the maximum value you can send using ordinary MIDI Control Change messages. You can use a USBDebugMIDI_Interface and verify that you get values up to 127/0x7F in the serial monitor if you move the fader all the way up.

Alternatives include 14-bit Control Change messages and Pitch Bend messages.

Logic respond to the maximum, i.e. when I move the fader the sound start from infinity all the way up to 6db but my 100mm fader is only half way by the time I reach 6db. Is this something that can be adjusted?

Also, is there a way to change the MIDI interface name? Right now it's just "Control Surface" but i'm building multiple MIDI controllers and it would be nice if I can assign different name.

Did you try the USBDebugMIDI_Interface approach I suggested? What were the results?
What board are you using? Did you connect the fader to 5V or 3.3V?

Presently I'm using a Teensy LC but I will get a Pro Micro. I'm running at 5v. I tried the USBDebugMIDI_Interface but it says: "Board at usb:fd510000 is not available"...any idea?

There's your problem. The Teensy LC is a 3.3V device, don't connect your potentiometer to 5V.

Did you select the right serial port in Tools > Port?

Yep, 3v is working and I check the port and the monitor is working too, MIDI data 0-127.

Thanks!

I have one more question: I'd like the console I'm building to be modular meaning one module would have the transport buttons, jog wheel and about 30 buttons to control Logic functions which would the control unit and let's say 6 module with 4 channels each with faders, Pan, Mute, Solo, Rec and Sel.

I'm thinking of using a Teensy 4.1 in the control unit and I would it to recognize every additional unit as I plug them in. Would it be as simple as planning for a connector to carry the signals to a bunch of multiplexers? Any ideas if that makes sense?

Also, as I press Solo on track 1, how do make sure the Solo button is blinking and all the Mute buttons on the other units lit up?

Hello Pieter,

I used the code you gave me and it's working, but when I'm using 16 buttons and 16 leds (illuminating tact button) on 2 mux, the button are working but not the leds. What am I doing wrong?

#include <Control_Surface.h>

USBMIDI_Interface usbmidi;
//USBDebugMIDI_Interface usbmidi;

template <class T, size_t N> constexpr size_t len(const T (&)[N]) { return N; }

const int speedMultiplier = 1;

//************************************* MUX 1 ************************************************
//******************** Instantiate 74HC4067 Digital multiplexer ******************************
//Digital input pin 1, Address pins S0, S1, S2, S3
CD74HC4067 mux1 ={1, {0, 2, 3, 5} };
CCButton buttons1 [] = {
{ mux1.pin(15), {21, CHANNEL_1 } },  // SEL 2
{ mux1.pin(13), {20, CHANNEL_1 } },  // REC 2  
};

pin_t leds1 [] {
mux1.pin(14),
mux1.pin(12), 
};

//*********************************** FADERS & POTS ******************************************
CCPotentiometer knobsTop [] {
  {A0, {0x24, CHANNEL_1}}, // FADER 1
  {A1, {0x25, CHANNEL_2}}, // FADER 2
};

BitArray<len(leds1)> led_states1;

static_assert(len(leds1) == len(buttons1), "");

void setup() {
  Control_Surface.begin();
 for (auto led1 : leds1)
    pinMode(led1, OUTPUT);
 }

void loop() { // Refresh all inputs
  Control_Surface.loop();
  for (size_t i = 0; i < len(buttons1); ++i) {
    if (buttons1[i].getButtonState() == Button::Falling) {
      bool new_state1 = !led_states1.get(i);
      digitalWrite(leds1[i], new_state1 ? HIGH : LOW);
      led_states1.set(i, new_state1);
    }
  }
}

1 Like

You can't use a multiplexer to drive LEDs. Only one channel of the multiplexer can be active at any given time.

Friend, love that code. One question, how I do to for example when I have 2 buttons and 2 leds, when button 1 is pressed led 1 is on and led 2 is off, when button 2 is pressed, led 2 is on and led 1 is off...so only one led is on and it's the "button" led.

Sorry to use your post to make a question :slight_smile:

Unfortunately I don't the answer to your question...I not a programmer!

Regarding my question, it looks like I have to plug the LEDs into the Pro Micro each on an individual pin and used a Mux for the buttons. I have tested and it works...although I will have to use a micro processor with more input. I have a Teensy LC which has just enough pins.

I found the solution. Thnks

Hello Pieter,

I modified the code to use a shift register for the LEDs and a multiplexer for the buttons.
The buttons work but the LEDs don't light up. Here's the code:

#include <Control_Surface.h>

USBMIDI_Interface usbmidi;

template <class T, size_t N> constexpr size_t len(const T (&)[N]) { return N; }

const int speedMultiplier = 1;

//Instantiate 2 Shift Registers
SPIShiftRegisterOut<8> sreg {
  SPI,      // SPI interface to use
  10,      // Latch pin (ST_CP)
  MSBFIRST, // Bit order
};

//*** MUX 1 ***
//Instantiate 74HC4067 Digital multiplexer
//Digital input pin 1, Address pins S0, S1, S2, S3
CD74HC4067 mux1 ={4, {0, 1, 2, 3} };
CCButton buttons1 [] = {
{ mux1.pin(15), {92, CHANNEL_1 } },  // SEL 2
{ mux1.pin(14), {93, CHANNEL_1 } },  // REC 2 
{ mux1.pin(13), {94, CHANNEL_1 } },  // REC 2
{ mux1.pin(12), {95, CHANNEL_1 } },  // REC 2
{ mux1.pin(11), {96, CHANNEL_1 } },  // REC 2
{ mux1.pin(10), {97, CHANNEL_1 } },  // REC 2
{ mux1.pin(9), {98, CHANNEL_1 } },  // REC 2
{ mux1.pin(8), {99, CHANNEL_1 } },  // REC 2
};

pin_t leds1 [] {
  sreg.pin(0),
  sreg.pin(1),
  sreg.pin(2),
  sreg.pin(3),
  sreg.pin(4),
  sreg.pin(5),
  sreg.pin(6),
  sreg.pin(7),
};

BitArray<len(leds1)> led_states1;

static_assert(len(leds1) == len(buttons1), "");

void setup() {
  Control_Surface.begin();
for (auto led1 : leds1)
    pinMode(led1, OUTPUT);
}

void loop() { // Refresh all inputs
  Control_Surface.loop();
  for (size_t i = 0; i < len(buttons1); ++i) {
    if (buttons1[i].getButtonState() == Button::Falling) {
      bool new_state1 = !led_states1.get(i);
      digitalWrite(leds1[i], new_state1 ? HIGH : LOW);
      led_states1.set(i, new_state1);
    }
  }
}

I'm using a Teensy LC. On the LC, the MOSI0 is pin 11 and the SCK0 is pin 13. Do I have to define them somehow?

Thanks for you helps.