Hi there,
after a long time I’ve managed to finish a new module. This time it is an 9 channel midi trigger module, to translate the 5pin DIN MIDI connector to 9 different single outputs with 5V trigger pulses, for example to trigger different drum modules.
The module is Arduino nano based, and features a SSD1306 OLED display and a rotary encoder. I have implemented a little menu to configure the most parameters of the module, such as the input midi channel (default: 10), the midi note number for each trigger channel and the trigger length (default 10ms). All parameters can be stored in the internal eeprom.
To increase the OLED lifetime the display will go off after 10s without any input.
The front is made from 1.5mm thick aluminum, the frontplate is laminated paper covered with clear adhesive foil for school books.
Here are schematic, arduino source code (I use visual studio code) and some fotos.
Schematic (100% working):
Main menu:
Edit dialog for the midi channel:
Complete module:
PCB (funfact: I’ve forgot to connect 2(!) gnd areas to the ground plane )
complete module:
Source Code:
#include <Arduino.h>
#include <MIDI.h>
#include <EEPROM.h>
#include <Encoder.h>
#include <Wire.h>
#include <U8g2lib.h>
// ########################################################################
// declarations
MIDI_CREATE_DEFAULT_INSTANCE();
// define Arduino outputs
#define TRIG1 2
#define TRIG2 3
#define TRIG3 4
#define TRIG4 5
#define TRIG5 6
#define TRIG6 7
#define TRIG7 8
#define TRIG8 9
#define TRIG9 10
// Display
// SDA A4
// SCL A5
#define OLED_ADDRESS 0x3C
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
U8G2_SSD1306_128X64_NONAME_1_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
bool displayOn = LOW;
#define textHeight 12
#define boxHeight textHeight + 2
#define lcdWidth 128
// Rotary Encoder
#define ENCODER_OPTIMIZE_INTERRUPTS
#define CLK A2
#define DT A1
#define BUTTONENC A0
Encoder myEnc(DT, CLK);
float oldPosition = -999;
float newPosition = -999;
bool lastencoderPressed = LOW;
bool encoderPressed = LOW;
long timerDisplay = 0;
long displayOnTime = 20000;
// shut off display 10s after last action
long displayTime = 10000;
// current menu mode
int menuMode = 0;
bool doUpdateMenu = true;
// trigger length in ms
unsigned int trigLength; // = 10;
// current Midi Channel
unsigned int midiChannel; // = 10;
/*
MIDI Notes and sounds from the General Midi Table
27 Laser 60 High Bongo
28 Whip 61 Low Bongo
29 Scratch Push 62 Conga Dead Stroke
30 Scratch Pull 63 Conga
31 Stick Click 64 Tumba
32 Metronome Click 65 High Timbale
34 Metronome Bell 66 Low Timbale
35 Bass Drum 67 High Agogo
36 Kick Drum 68 Low Agogo
37 Snare Cross Stick 69 Cabasa
38 Snare Drum 70 Maracas
39 Hand Clap 71 Whistle Short
40 Electric Snare Drum 72 Whistle Long
41 Floor Tom 2 73 Guiro Short
42 Hi-Hat Closed 74 Guiro Long
43 Floor Tom 1 75 Claves
44 Hi-Hat Foot 76 High Woodblock
45 Low Tom 77 Low Woodblock
46 Hi-Hat Open 78 Cuica High
47 Low-Mid Tom 79 Cuica Low
48 High-Mid Tom 80 Triangle Mute
49 Crash Cymbal 81 Triangle Open
50 High Tom 82 Shaker
51 Ride Cymbal 83 Sleigh Bell
52 China Cymbal 84 Bell Tree
53 Ride Bell 85 Castanets
54 Tambourine 86 Surdu Dead Stroke
55 Splash cymbal 87 Surdu
56 Cowbell 91 Snare Drum Rod
57 Crash Cymbal 2 92 Ocean Drum
58 Vibraslap 93 Snare Drum Brush
59 Ride Cymbal 2
*/
// assignments for the trigger notes
unsigned int trig1Note; // = 35;
unsigned int trig2Note; // = 36;
unsigned int trig3Note; // = 38;
unsigned int trig4Note; // = 39;
unsigned int trig5Note; // = 45;
unsigned int trig6Note; // = 47;
unsigned int trig7Note; // = 48;
unsigned int trig8Note; // = 50;
unsigned int trig9Note; // = 42;
// trigger timer
unsigned long trig1Timer = 0;
unsigned long trig2Timer = 0;
unsigned long trig3Timer = 0;
unsigned long trig4Timer = 0;
unsigned long trig5Timer = 0;
unsigned long trig6Timer = 0;
unsigned long trig7Timer = 0;
unsigned long trig8Timer = 0;
unsigned long trig9Timer = 0;
// ################################################################################
// void DisplayOff()
// ################################################################################
void DisplayOff(){
u8g2.clearDisplay();
}
// ################################################################################
// Menu Functions
// ################################################################################
void showConfigScreen (char *text, unsigned int *variable, unsigned int minValue, unsigned int maxValue){
bool updateDisplay = true;
do
{
lastencoderPressed = encoderPressed;
encoderPressed = digitalRead(BUTTONENC);
newPosition = myEnc.read();
if ((newPosition - 3) / 4 > oldPosition / 4)
{
// encoder rotated clock wise
oldPosition = newPosition;
(*variable)++;
if((*variable) > maxValue){
(*variable) = maxValue;
}
updateDisplay = true;
}
else if ((newPosition + 3) / 4 < oldPosition / 4)
{
// encoder rotated counter clock wise
oldPosition = newPosition;
(*variable)--;
if((*variable) < minValue){
(*variable) = minValue;
}
updateDisplay = true;
}
if (updateDisplay){
u8g2.firstPage();
do {
u8g2.setCursor (0,12);
u8g2.println (text);
u8g2.setCursor (8, 28);
u8g2.print(">");
u8g2.setCursor (20, 28);
u8g2.print(*variable);
updateDisplay = false;
}while ( u8g2.nextPage() );
}
} while (!(!lastencoderPressed && encoderPressed));
}
void setMidiChannel(){
showConfigScreen("Set Midi Ch.:", &midiChannel, 1, 16);
MIDI.setInputChannel(midiChannel);
}
void setTrig1Note() {
showConfigScreen("Set Trig1 Note:", &trig1Note, 27, 93);
}
void setTrig2Note() {
showConfigScreen("Set Trig2 Note:", &trig2Note, 27, 93);
}
void setTrig3Note() {
showConfigScreen("Set Trig3 Note:", &trig3Note, 27, 93);
}
void setTrig4Note() {
showConfigScreen("Set Trig4 Note:", &trig4Note, 27, 93);
}
void setTrig5Note() {
showConfigScreen("Set Trig5 Note:", &trig5Note, 27, 93);
}
void setTrig6Note() {
showConfigScreen("Set Trig6 Note:", &trig6Note, 27, 93);
}
void setTrig7Note() {
showConfigScreen("Set Trig7 Note:", &trig7Note, 27, 93);
}
void setTrig8Note() {
showConfigScreen("Set Trig8 Note:", &trig8Note, 27, 93);
}
void setTrig9Note() {
showConfigScreen("Set Trig9 Note:", &trig9Note, 27, 93);
}
void setTriggerLenght() {
showConfigScreen("Set Trig Len:", &trigLength, 1, 100);
}
void saveEeprom() {
int address = 0;
EEPROM.put(address,midiChannel);
address += sizeof(midiChannel);
EEPROM.put(address,trig1Note);
address += sizeof(trig1Note);
EEPROM.put(address,trig2Note);
address += sizeof(trig2Note);
EEPROM.put(address,trig3Note);
address += sizeof(trig3Note);
EEPROM.put(address,trig4Note);
address += sizeof(trig4Note);
EEPROM.put(address,trig5Note);
address += sizeof(trig5Note);
EEPROM.put(address,trig6Note);
address += sizeof(trig6Note);
EEPROM.put(address,trig7Note);
address += sizeof(trig7Note);
EEPROM.put(address,trig8Note);
address += sizeof(trig8Note);
EEPROM.put(address,trig9Note);
address += sizeof(trig9Note);
EEPROM.put(address,trigLength);
EEPROM.put(100, 99);
}
void restoreDefaults() {
midiChannel = 10;
trig1Note = 35;
trig2Note = 36;
trig3Note = 38;
trig4Note = 39;
trig5Note = 45;
trig6Note = 47;
trig7Note = 48;
trig8Note = 50;
trig9Note = 42;
trigLength = 10;
saveEeprom();
u8g2.firstPage();
do {
u8g2.setCursor (0,12);
u8g2.println ("Defaults");
u8g2.setCursor (8, 28);
u8g2.println ("restored!");
}while ( u8g2.nextPage() );
delay (2000);
}
void saveSettings() {
saveEeprom();
u8g2.firstPage();
do {
u8g2.setCursor (0,12);
u8g2.println ("Settings Saved!");
} while ( u8g2.nextPage() );
delay (2000);
}
void loadSettings() {
// If found value 99 in address 100 there is valid data in eeprom
// if not load default values and save them to eeprom
int address = 0;
int tempValue;
EEPROM.get(100,tempValue);
if (tempValue != 99) {
restoreDefaults();
} else {
EEPROM.get(address,midiChannel);
address += sizeof(midiChannel);
EEPROM.get(address,trig1Note);
address += sizeof(trig1Note);
EEPROM.get(address,trig2Note);
address += sizeof(trig2Note);
EEPROM.get(address,trig3Note);
address += sizeof(trig3Note);
EEPROM.get(address,trig4Note);
address += sizeof(trig4Note);
EEPROM.get(address,trig5Note);
address += sizeof(trig5Note);
EEPROM.get(address,trig6Note);
address += sizeof(trig6Note);
EEPROM.get(address,trig7Note);
address += sizeof(trig7Note);
EEPROM.get(address,trig8Note);
address += sizeof(trig8Note);
EEPROM.get(address,trig9Note);
address += sizeof(trig9Note);
EEPROM.get(address,trigLength);
}
}
void exitMenu() {
menuMode = 0;
DisplayOff();
doUpdateMenu = false;
}
// menu items
const char *menuItems[] =
{
"MIDI Channel",
"TRIG1 Note",
"TRIG2 Note",
"TRIG3 Note",
"TRIG4 Note",
"TRIG5 Note",
"TRIG6 Note",
"TRIG7 Note",
"TRIG8 Note",
"TRIG9 Note",
"Trigger Length",
"Save Settings",
"Restore Defaults",
"<Exit Menu>",
};
// function pointers for menu items
void (*function_pointer[]) () =
{
setMidiChannel,
setTrig1Note,
setTrig2Note,
setTrig3Note,
setTrig4Note,
setTrig5Note,
setTrig6Note,
setTrig7Note,
setTrig8Note,
setTrig9Note,
setTriggerLenght,
saveSettings,
restoreDefaults,
exitMenu,
};
int menuLength = sizeof(menuItems) / 2;
int menuInLCD = 4;
int menuPosition = 0;
int menuPositionOnLCD = 0;
// ################################################################################
// void ShowWelcome()
// ################################################################################
void ShowWelcome(){
u8g2.firstPage();
do {
u8g2.setCursor (10,24);
u8g2.println ("MidiTrigger");
u8g2.setCursor (10,36);
u8g2.println ("V1.0");
}while ( u8g2.nextPage() );
}
// ################################################################################
// void setup
// ################################################################################
void setup() {
// configure pins
pinMode(TRIG1, OUTPUT);
pinMode(TRIG2, OUTPUT);
pinMode(TRIG3, OUTPUT);
pinMode(TRIG4, OUTPUT);
pinMode(TRIG5, OUTPUT);
pinMode(TRIG6, OUTPUT);
pinMode(TRIG7, OUTPUT);
pinMode(TRIG8, OUTPUT);
pinMode(TRIG9, OUTPUT);
pinMode(BUTTONENC, INPUT);
// load data from EEPROM
loadSettings();
// initialize MIDI
MIDI.begin(midiChannel);
// initialize display
u8g2.begin();
u8g2.clearBuffer();
u8g2.setFont(u8g2_font_pxplusibmvga8_tr);
// SPI Settings
// https://docs.arduino.cc/learn/communication/spi?_gl=1*1y7brsy*_ga*MTM3OTI0NDcyMS4xNjcyMTQ2MTI1*_ga_NEXN8H46L5*MTY3MzM3OTg5OC41LjEuMTY3MzM3OTkwOS4wLjAuMA..
//SPI.begin();
//SPI.setBitOrder(MSBFIRST); // bit order
//SPI.setClockDivider(SPI_CLOCK_DIV4); // (16MHz/4)
//SPI.setDataMode(SPI_MODE0);
// show welcome message on startup
ShowWelcome();
delay(2000);
DisplayOff();
}
// ################################################################################
// void UpdateMenu()
// ################################################################################
void UpdateMenu(){
// show menu
u8g2.firstPage();
do {
for(int m = 0; m <= menuLength; m++)
{
if(menuPosition < menuInLCD)
{
if(menuPosition == m) {
u8g2.drawRFrame (0, m * textHeight, lcdWidth, boxHeight, 2);
}
u8g2.setCursor(2, m * textHeight);
u8g2.println( menuItems[m-1] );
}
else
{
if(menuPosition == m) {
u8g2.drawRFrame(0, (m - (1 + menuPosition - menuInLCD)) * textHeight, lcdWidth, boxHeight, 2);
}
u8g2.setCursor(2, (m - (1 + menuPosition - menuInLCD)) * textHeight);
u8g2.println( menuItems[m-1]);
}
}
} while ( u8g2.nextPage() );
}
// ################################################################################
// void loop
// ################################################################################
void loop() {
// get encoder button
lastencoderPressed = encoderPressed;
encoderPressed = digitalRead(BUTTONENC);
if (!lastencoderPressed && encoderPressed)
{
timerDisplay = millis();
displayOn = HIGH;
switch (menuMode)
{
case 0:
menuMode = 1;
break;
case 1:
doUpdateMenu = true;
function_pointer[menuPosition]();
if (doUpdateMenu){
UpdateMenu();
}
doUpdateMenu = true;
break;
}
}
if (menuMode == 1){
// the encoder sends 4 clicks per turn
newPosition = myEnc.read();
if ((newPosition - 3) / 4 > oldPosition / 4)
{
// encoder was turned clock wise
timerDisplay = millis();
oldPosition = newPosition;
menuPosition++;
if(menuPosition >= (menuLength - 1)){
menuPosition = menuLength - 1;
}
UpdateMenu();
}
else if ((newPosition + 3) / 4 < oldPosition / 4)
{
// encoder was turned counter clock wise
timerDisplay = millis();
oldPosition = newPosition;
menuPosition--;
if(menuPosition < 0){
menuPosition = 0;
}
UpdateMenu();
}
if (millis() - timerDisplay > displayOnTime){
menuMode = 0;
DisplayOff();
}
}
// check all trigger timer values and set them LOW
long curMillis = (long)millis();
if (curMillis - trig1Timer > trigLength){
digitalWrite(TRIG1, LOW);
}
if (curMillis - trig2Timer > trigLength){
digitalWrite(TRIG2, LOW);
}
if (curMillis - trig3Timer > trigLength){
digitalWrite(TRIG3, LOW);
}
if (curMillis - trig4Timer > trigLength){
digitalWrite(TRIG4, LOW);
}
if (curMillis - trig5Timer > trigLength){
digitalWrite(TRIG5, LOW);
}
if (curMillis - trig6Timer > trigLength){
digitalWrite(TRIG6, LOW);
}
if (curMillis - trig7Timer > trigLength){
digitalWrite(TRIG7, LOW);
}
if (curMillis - trig8Timer > trigLength){
digitalWrite(TRIG8, LOW);
}
if (curMillis - trig9Timer > trigLength){
digitalWrite(TRIG9, LOW);
}
// handle midi events
if (MIDI.read()){
switch (MIDI.getType()) {
case (midi::NoteOn):
{
// get current tone number from incoming midi data
int currentNote = MIDI.getData1();
// test all configured tone numbers and set the trigger to HIGH
if (currentNote == (int)trig1Note){
trig1Timer = millis();
digitalWrite(TRIG1, HIGH);
}
if (currentNote == (int)trig2Note){
trig2Timer = millis();
digitalWrite(TRIG2, HIGH);
}
if (currentNote == (int)trig3Note){
trig3Timer = millis();
digitalWrite(TRIG3, HIGH);
}
if (currentNote == (int)trig4Note){
trig4Timer = millis();
digitalWrite(TRIG4, HIGH);
}
if (currentNote == (int)trig5Note){
trig5Timer = millis();
digitalWrite(TRIG5, HIGH);
}
if (currentNote == (int)trig6Note){
trig6Timer = millis();
digitalWrite(TRIG6, HIGH);
}
if (currentNote == (int)trig7Note){
trig7Timer = millis();
digitalWrite(TRIG7, HIGH);
}
if (currentNote == (int)trig8Note){
trig8Timer = millis();
digitalWrite(TRIG8, HIGH);
}
if (currentNote == (int)trig9Note){
trig9Timer = millis();
digitalWrite(TRIG9, HIGH);
}
break;
}
// unused midi events:
case midi::NoteOff:
case midi::PitchBend:
case midi::Clock:
case midi::SystemExclusive:
case midi::AfterTouchPoly:
case midi::ControlChange:
case midi::ProgramChange:
case midi::AfterTouchChannel:
case midi::TimeCodeQuarterFrame:
case midi::SongPosition:
case midi::SongSelect:
case midi::TuneRequest:
case midi::SystemExclusiveEnd:
case midi::Tick:
case midi::Start:
case midi::Continue:
case midi::Stop:
case midi::ActiveSensing:
case midi::SystemReset:
case midi::InvalidType:
case midi::Undefined_F4:
case midi::Undefined_F5:
case midi::Undefined_FD:
break;
}
}
}