Control Surface  1.1.1
MIDI Control Surface library for Arduino
MCU-OLED-SSD1306-x2.ino

MCU-OLED-SSD1306-x2

An example demonstrating the use of DisplayElements to display information from the DAW on two small OLED displays.

Boards:
Teensy 3.x

Connections

Add a capacitor between the reset pins of the displays and ground, and a resistor from reset to 3.3V. The values are not critical, 0.1µF and 10kΩ work fine.
You do need some way to reset the displays, without it, it won't work.
Alternatively, you could use an IO pin from the Teensy to reset the displays, but this just "wastes" a pin.

Behavior

Mapping

Map "Control Surface" as a Mackie Control Universal unit in your DAW.

Note
There seem to be some differences in the way some applications handle VU meters: some expect the hardware to decay automatically, some don't.
If you notice that the meters behave strangely, try both MCU::VUDecay::Hold and MCU::VUDecay::Default, or try a different decay time.

Demo

Written by PieterP, 2019-10-12
https://github.com/tttapa/Control-Surface

#include <Control_Surface.h> // Include the Control Surface library
// Include the display interface you'd like to use
// ----------------------------- MIDI Interface ----------------------------- //
// ========================================================================== //
/*
* Instantiate a MIDI interface to use for the Control Surface.
*/
// ----------------------------- Display setup ------------------------------ //
// ========================================================================== //
/*
* Instantiate and initialize the SSD1306 OLED display
*/
constexpr uint8_t SCREEN_WIDTH = 128;
constexpr uint8_t SCREEN_HEIGHT = 64;
constexpr int8_t OLED_DC = 17; // Data/Command pin of the display
constexpr int8_t OLED_reset = -1; // Use the external RC circuit for reset
constexpr int8_t OLED_CS_L = 10; // Chip Select pin of the left display
constexpr int8_t OLED_CS_R = 18; // Chip Select pin of the right display
constexpr uint32_t SPI_Frequency = SPI_MAX_SPEED;
// Instantiate the displays
Adafruit_SSD1306 ssd1306Display_L = {
SCREEN_WIDTH, SCREEN_HEIGHT, &SPI, OLED_DC,
OLED_reset, OLED_CS_L, SPI_Frequency,
};
Adafruit_SSD1306 ssd1306Display_R = {
SCREEN_WIDTH, SCREEN_HEIGHT, &SPI, OLED_DC,
OLED_reset, OLED_CS_R, SPI_Frequency,
};
// --------------------------- Display interface ---------------------------- //
// ========================================================================== //
// Implement the display interface, specifically, the begin and drawBackground
// methods.
class MySSD1306_DisplayInterface : public SSD1306_DisplayInterface {
public:
MySSD1306_DisplayInterface(Adafruit_SSD1306 &display)
void begin() override {
#if defined(ADAFRUIT_SSD1306_HAS_SETBUFFER) && ADAFRUIT_SSD1306_HAS_SETBUFFER
disp.setBuffer(buffer);
#endif
// Initialize the Adafruit_SSD1306 display
if (!disp.begin())
FATAL_ERROR(F("SSD1306 initialization failed."), 0x1306);
// If you override the begin method, remember to call the super class method
}
void drawBackground() override { disp.drawLine(1, 8, 126, 8, WHITE); }
#if defined(ADAFRUIT_SSD1306_HAS_SETBUFFER) && ADAFRUIT_SSD1306_HAS_SETBUFFER
// We'll use a static buffer to avoid dynamic memory usage, and to allow
// multiple displays to reuse one single buffer.
static uint8_t buffer[(SCREEN_WIDTH * SCREEN_HEIGHT + 7) / 8];
#endif
} display_L = ssd1306Display_L, display_R = ssd1306Display_R;
#if defined(ADAFRUIT_SSD1306_HAS_SETBUFFER) && ADAFRUIT_SSD1306_HAS_SETBUFFER
uint8_t MySSD1306_DisplayInterface::buffer[];
#endif
// ------------------------------- Bank setup ------------------------------- //
// ========================================================================== //
/*
* Create a bank and a bank selector to change its setting.
*/
Bank<2> bank(4); // Create a new bank with four tracks per bank
// Create a new bank selector to control the bank using a push button
IncrementSelector<2> bankselector = {bank, 5};
// -------------------------- MIDI Input Elements --------------------------- //
// ========================================================================== //
/*
* Define all elements that listen for MIDI messages.
*/
// Main MCU LCD screen, used to get track names
MCU::LCD<> lcd = {};
// Time display_L keeps track of the bar counter
MCU::TimeDisplay timedisplay = {};
// Play / Record
// Mute
{bank, MCU::MUTE_1},
{bank, MCU::MUTE_2},
{bank, MCU::MUTE_3},
{bank, MCU::MUTE_4},
};
// Solo
{bank, MCU::SOLO_1},
{bank, MCU::SOLO_2},
{bank, MCU::SOLO_3},
{bank, MCU::SOLO_4},
};
// Record arm / ready
{bank, MCU::REC_RDY_1},
{bank, MCU::REC_RDY_2},
{bank, MCU::REC_RDY_3},
{bank, MCU::REC_RDY_4},
};
// VU meters
{bank, 1, MCU::VUDecay::Hold},
{bank, 2, MCU::VUDecay::Hold},
{bank, 3, MCU::VUDecay::Hold},
{bank, 4, MCU::VUDecay::Hold},
};
// VPot rings
{bank, 1},
{bank, 2},
{bank, 3},
{bank, 4},
};
// ---------------------------- Display Elements ---------------------------- //
// ========================================================================== //
/*
* Define all display_L elements that display_L the state of the input elements.
*/
// Track names
MCU::LCDDisplay lcddisps[] = {
// track (1), position (0, 40), font size (1)
{display_L, lcd, bank, 1, {0, 40}, 1, WHITE},
{display_L, lcd, bank, 2, {64, 40}, 1, WHITE},
{display_R, lcd, bank, 3, {0, 40}, 1, WHITE},
{display_R, lcd, bank, 4, {64, 40}, 1, WHITE},
};
// Time display
MCU::TimeDisplayDisplay timedisplaydisplay = {
// position (0, 0), font size (1)
display_L, timedisplay, {0, 0}, 1, WHITE,
};
// Play / Record
NoteBitmapDisplay playDisp = {
display_L, play, XBM::play_7, {16 + 64, 0}, WHITE,
};
NoteBitmapDisplay recordDisp = {
display_L, record, XBM::record_7, {26 + 64, 0}, WHITE,
};
// Mute
NoteBitmapDisplay muteDisp[] = {
{display_L, mute[0], XBM::mute_10B, {14, 50}, WHITE},
{display_L, mute[1], XBM::mute_10B, {14 + 64, 50}, WHITE},
{display_R, mute[2], XBM::mute_10B, {14, 50}, WHITE},
{display_R, mute[3], XBM::mute_10B, {14 + 64, 50}, WHITE},
};
// Solo
NoteBitmapDisplay soloDisp[] = {
{display_L, solo[0], XBM::solo_10B, {14, 50}, WHITE},
{display_L, solo[1], XBM::solo_10B, {14 + 64, 50}, WHITE},
{display_R, solo[2], XBM::solo_10B, {14, 50}, WHITE},
{display_R, solo[3], XBM::solo_10B, {14 + 64, 50}, WHITE},
};
NoteBitmapDisplay rudeSoloDisp = {
display_L, rudeSolo, XBM::solo_7, {36 + 64, 0}, WHITE};
// Record arm / ready
NoteBitmapDisplay recrdyDisp[] = {
{display_L, recrdy[0], XBM::rec_rdy_10B, {14 + 14, 50}, WHITE},
{display_L, recrdy[1], XBM::rec_rdy_10B, {14 + 14 + 64, 50}, WHITE},
{display_R, recrdy[2], XBM::rec_rdy_10B, {14 + 14, 50}, WHITE},
{display_R, recrdy[3], XBM::rec_rdy_10B, {14 + 14 + 64, 50}, WHITE},
};
// VU meters
MCU::VUDisplay vuDisp[] = {
// position (32+11, 60), width (16), bar height (3) px, bar spacing (1) px
{display_L, vu[0], {32 + 11, 60}, 16, 3, 1, WHITE},
{display_L, vu[1], {32 + 11 + 64, 60}, 16, 3, 1, WHITE},
{display_R, vu[2], {32 + 11, 60}, 16, 3, 1, WHITE},
{display_R, vu[3], {32 + 11 + 64, 60}, 16, 3, 1, WHITE},
};
// VPot rings
MCU::VPotDisplay vpotDisp[] = {
// position (0, 10), outer radius (14) px, inner radius (12) px
{display_L, vpot[0], {0, 10}, 14, 12, WHITE},
{display_L, vpot[1], {64, 10}, 14, 12, WHITE},
{display_R, vpot[2], {0, 10}, 14, 12, WHITE},
{display_R, vpot[3], {64, 10}, 14, 12, WHITE},
};
// Bank seting
BankDisplay bankDisp[] = {
// first track of the bank (1), position (0, 50), font size (2)
{display_L, bank, 1, {0, 50}, 2, WHITE},
{display_L, bank, 2, {64, 50}, 2, WHITE},
{display_R, bank, 3, {0, 50}, 2, WHITE},
{display_R, bank, 4, {64, 50}, 2, WHITE},
};
// --------------------------------- Setup ---------------------------------- //
// ========================================================================== //
void setup() {
// The default SPI MOSI pin (11) is used for I²S, so we need to use the
// alternative MOSI pin (7)
SPI.setMOSI(7);
// Correct relative mode for MCU rotary encoders
Control_Surface.begin(); // Initialize Control Surface
}
// ---------------------------------- Loop ---------------------------------- //
// ========================================================================== //
void loop() {
Control_Surface.loop(); // Refresh all elements
}
USBMIDI_Interface
A class for MIDI interfaces sending MIDI messages over a USB MIDI connection.
Definition: USBMIDI_Interface.hpp:35
NoteBitmapDisplay
A class that displays a bitmap depending on the state of a MIDINote.
Definition: NoteBitmapDisplay.hpp:10
MACKIE_CONTROL_RELATIVE
Relative mode used by the Mackie Control Universal protocol.
Definition: RelativeCCSender.hpp:62
Bank
A class that groups Bankable BankableMIDIOutputs and BankableMIDIInputs, and allows the user to chang...
Definition: Bank.hpp:77
SSD1306_DisplayInterface::drawBackground
void drawBackground() override=0
Draw a custom background.
MCU::Bankable::VPotRing
A class for MIDI input elements that represent Mackie Control Universal V-Pots.
Definition: VPotRing.hpp:213
NoteValue
MIDI Input Element that listens to a single note and saves its velocity value.
Definition: NoteCCRange.hpp:205
AH::SPI_MAX_SPEED
constexpr static Frequency SPI_MAX_SPEED
Definition: AH/Settings/Settings.hpp:85
MCU::VPotDisplay
Definition: VPotDisplay.hpp:12
IncrementSelector
Selector with one button that increments the selection.
Definition: IncrementSelector.hpp:58
RelativeCCSender::setMode
static void setMode(relativeCCmode mode)
Definition: RelativeCCSender.hpp:105
MCU::PLAY
constexpr uint8_t PLAY
Definition: MCU.hpp:126
MCU::MUTE_4
constexpr uint8_t MUTE_4
Definition: MCU.hpp:39
MCU::TimeDisplayDisplay
Definition: TimeDisplayDisplay.hpp:11
MCU::SOLO_4
constexpr uint8_t SOLO_4
Definition: MCU.hpp:30
MCU::Bankable::VU
A class for MIDI input elements that represent Mackie Control Universal VU meters.
Definition: VU.hpp:353
FATAL_ERROR
#define FATAL_ERROR(msg, errc)
Print the error message and error code, and stop the execution.
Definition: Error.hpp:60
MCU::MUTE_1
constexpr uint8_t MUTE_1
Definition: MCU.hpp:36
BankDisplay
A class for displaying the setting of a Bank object.
Definition: SelectorDisplay.hpp:37
Control_Surface.h
The main header file that includes all Control-Surface header files.
XBM::solo_10B
const XBitmap solo_10B
XBitmap solo_10B (10px × 10px)
Definition: XBitmaps.hpp:116
Control_Surface_::loop
void loop()
Update all MIDI elements, send MIDI events and read MIDI input.
Definition: Control_Surface_Class.cpp:48
MCU::REC_RDY_4
constexpr uint8_t REC_RDY_4
Definition: MCU.hpp:21
MCU::MUTE_3
constexpr uint8_t MUTE_3
Definition: MCU.hpp:38
SSD1306_DisplayInterface
This class creates a mapping between the Adafruit_SSD1306 display driver and the general display inte...
Definition: DisplayInterfaceSSD1306.hpp:13
Control_Surface
Control_Surface_ & Control_Surface
A predefined instance of the Control Surface to use in the Arduino sketches.
Definition: Control_Surface_Class.cpp:176
MCU::RECORD
constexpr uint8_t RECORD
Definition: MCU.hpp:127
MCU::SOLO_2
constexpr uint8_t SOLO_2
Definition: MCU.hpp:28
MCU::LCDDisplay
Displays the text of the Mackie Control Universal LCD screen for a single track.
Definition: LCDDisplay.hpp:18
MCU::SOLO_3
constexpr uint8_t SOLO_3
Definition: MCU.hpp:29
MCU::TimeDisplay
Definition: TimeDisplay.hpp:12
MCU::RUDE_SOLO
constexpr uint8_t RUDE_SOLO
Definition: MCU.hpp:146
XBM::rec_rdy_10B
const XBitmap rec_rdy_10B
XBitmap rec_rdy_10B (10px × 10px)
Definition: XBitmaps.hpp:81
MCU::REC_RDY_1
constexpr uint8_t REC_RDY_1
Definition: MCU.hpp:18
XBM::play_7
const XBitmap play_7
XBitmap play_7 (7px × 7px)
Definition: XBitmaps.hpp:67
MIDI_Notes::F
constexpr int8_t F
Definition: Notes.hpp:23
MCU::LCD
Definition: LCD.hpp:31
DisplayInterfaceSSD1306.hpp
MCU::VUDisplay
Definition: VUDisplay.hpp:10
DisplayInterface::begin
virtual void begin()
Initialize the display.
Definition: DisplayInterface.cpp:7
MCU::REC_RDY_3
constexpr uint8_t REC_RDY_3
Definition: MCU.hpp:20
XBM::solo_7
const XBitmap solo_7
XBitmap solo_7 (7px × 7px)
Definition: XBitmaps.hpp:130
MCU::VUDecay::Hold
constexpr unsigned int Hold
Don't decay automatically, hold the latest value until a new one is received.
Definition: VU.hpp:41
XBM::record_7
const XBitmap record_7
XBitmap record_7 (7px × 7px)
Definition: XBitmaps.hpp:102
Bankable::NoteValue
MIDI Input Element that listens to a single note and saves its value.
Definition: NoteCCRange.hpp:327
XBM::mute_10B
const XBitmap mute_10B
XBitmap mute_10B (10px × 10px)
Definition: XBitmaps.hpp:39
Control_Surface_::begin
void begin()
Initialize the Control_Surface.
Definition: Control_Surface_Class.cpp:25
MCU::MUTE_2
constexpr uint8_t MUTE_2
Definition: MCU.hpp:37
MCU::REC_RDY_2
constexpr uint8_t REC_RDY_2
Definition: MCU.hpp:19
MCU::SOLO_1
constexpr uint8_t SOLO_1
Definition: MCU.hpp:27