tttapa / Control-Surface

Arduino library for creating MIDI controllers and other MIDI devices.
GNU General Public License v3.0
1.25k stars 139 forks source link

Example code: MIDI send to DAW to MIDI read and display on MAX7219 #122

Open 84Park opened 4 years ago

84Park commented 4 years ago

The attached .ino files send and receive SysEx messages that are coded and decoded to display numbers on 8-digit MAX7219 displays. One Arduino Micro clone is used to send the SysEx message to the DAW (VMPK app) and the DAW passes the SysEx message to a second Arduino Micro clone which displays the message as an 8 digit number on one or more MAX7219 displays. Each Micro is connected through USB to a Mac which hosts the DAW software. The attached code sends messages to two MAX7219 displays. I hope you find these examples useful.

/**
   SysEx-Send_Max7219-Message.ino

   Lowell Bahner
   2020-02-14

   Uses 2 Sparkfun Pro Micro boards, 1 to send MIDI message, 1 to receive MIDI message

   Micro1 sends SysEx message with embedded text[8+] chars to
   display on MAX7219 connected to Micro2, the receiving MIDI device.
   SysEx[1] contains 0x77 (arbitrary) and SysEx[2] contains arbitrary
   device code which is checked by the receiving MIDI device, so it
   displays on the desired device. Each SPI device uses a different CS
   pin, similar in concept to a MIDI channel.

   Serial monitor will display out going characters as integers.
   Midi Monitor app will display SysEx as multiple bytes in hex format.

   Example shows how to send MIDI System Exclusive messages.

*/

#include <Control_Surface.h>

// Instantiate the MIDI over USB interface
USBMIDI_Interface midi;

// Push button connected between pin 2 and ground.
// SysEx message is sent when pressed.
Button pushbutton = {2};

void setup() {
  Serial.begin(115200);
  pushbutton.begin(); // enables internal pull-up
  midi.begin();
}

void loop() {
  // Send a SysEx message when the push button is pressed

  // convert double value to char string.
  double value = 1234.567890;
  char txt[16]; // Buffer big enough for 8-character float decimal point plus null
  dtostrf(value, 6, 2, txt); // 6 total digits plus decimal plus 2 decimal digits --> txt[]

  // insert txt[0...7] into sysex[3...10+]. Decimal points require an additional byte each.
  // sysex[0] is start code, sysex[15] is end code
  // sysex[1] is arbitrary manufacturer code
  // sysex[2] is arbitrary device code for desired SPI output device on cs pin 10
  //
  uint8_t len = strlen(txt);
  uint8_t sysex[] = {0xF0, 0x77, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xF7};

  for (int i = 0; i < len; i++)
  {
    sysex[i + 3] = int8_t(txt[i]); // convert int8_t char to uint8_t sysex
  }

  // second display using cs=pin 9
  char txt1[16]; // Buffer big enough for 8-character float decimal point plus null
  dtostrf(value, 8, 4, txt1); // 8 total digits, decimal pt, with 4 decimal digits --> txt[]
  len = strlen(txt1);
  uint8_t sysex1[] = {0xF0, 0x77, 0x09, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xF7};

  for (int i = 0; i < len; i++)
  {
    sysex1[i + 3] = int8_t(txt1[i]); // convert int8_t char to uint8_t sysex
  }

  if (pushbutton.update() == Button::Falling) {
    Serial.println("SysEx from Device 10:");
    Serial.println(txt);
    for (int i = 0; i < 16; i++)
    {
      Serial.print(sysex[i]);
      Serial.print(" ");
    }
    Serial.println(); // send line feed <cr>

    Serial.println("SysEx from Device 9:");
    Serial.println(txt1);
    for (int i = 0; i < 16; i++)
    {
      Serial.print(sysex1[i]);
      Serial.print(" ");
    }
    Serial.println(); // send line feed <cr>

    // Serial output after buttonpush
    /*
      20:32:28.794 -> SysEx from Device 10:
      20:32:28.794 -> 1234.57
      20:32:28.794 -> 240 119 16 49 50 51 52 46 53 55 0 0 0 0 0 247
      20:32:28.794 -> SysEx from Device 9:
      20:32:28.794 -> 1234.5679
      20:32:28.794 -> 240 119 9 49 50 51 52 46 53 54 55 57 0 0 0 247
    */

    // send the SysEx message out the USB port
    midi.send(sysex);
    midi.send(sysex1);
  }

  // SysEx messages were sent.
  // Midi Monitor:
  // 03:32:48.132 From ProMicro musinou SysEx Unknown Manufacturer 16 bytes F0 77 10 31 32 33 34 2E 35 37 00 00 00 00 00 F7
  // 03:32:48.132 From ProMicro musinou SysEx Unknown Manufacturer 16 bytes F0 77 09 31 32 33 34 2E 35 36 37 39 00 00 00 F7

}
/**
    SysEx-Read-MAX7219-Message.ino

    Modified MIDI-Input-Callback.ino by PieterP
    Lowell Bahner 2020-02-15

    This code reads incoming SysEx message on a SparkFun Pro Micro with 32U4 USB
    attached to a Mac host USB port. The SysEx message is read and passes the
    8 digits (bytes) to one or more MAX7219 displays.
    Debug Serial was enabled in the Arduino Micro board.txt definition.

    A SysEx message originates from another ProMicro with 32U4 USB, attached to the
    Mac host through USB. The SysEx message includes 9+ bytes (8 digits plus a decimal point)
    to populate a MAX7219 display, plus a manufacturer code and device code. Set those
    to match the codes used by the sender code. By using different device codes, multiple
    MAX7219 displays can be driven by the reader Arduino using different cs pins.
    Arduinos on different USB ports with MAX7219 displays can also be run from one 
    sender Arduino.

    The SysEx-Send_MAX7219-Message.ino sender code runs on a ProMicro using a
    ProMicro musinou board definition which is just a renamed copy of the Micro definition
    used to differentiate the boards by name (Micro and ProMicro musinou).

    The SysEx message is logged on the Mac MIDI Monitor app.
    The VMPK DAW app on Mac is used to route the MIDI messages using USB between the
    two Arduinos. (vmpk:Edit:MIDI Connections:{Input:CoreMIDI:ProMicro musinou},
    {Output:CoreMIDI:Arduino Micro})
    The SysEx message includes 8+ bytes to display up to 8 digits on a MAX7219 display.

    ==============================

   MIDI-Input-Callback Written by PieterP, 2019-08-07
   https://github.com/tttapa/Control-Surface
*/

#include <Control_Surface.h> // Include the Control Surface library

USBMIDI_Interface midi;

//int led17 = 17; // on board blue LED, usually pin 13 for most Arduinos.

// ProMicro SPI pins used by Control_Surface MAX7219
uint8_t cs10 = 10; // pin 10 as first Max7219 CS
uint8_t cs09 = 9; // pin 9 as second Max7219 CS
// uint8_t mosi = 16; // pin 16 is ProMicro MOSI
// uint8_t sck = 15; // pin 15 is ProMicro SCK
// uint8_t miso = 14; // pin 14 is ProMicro MISO

MAX7219SevenSegmentDisplay max72_10 = {cs10}; // display # 1
MAX7219SevenSegmentDisplay max72_09 = {cs09}; // display # 2

// function to display the SysEx message on each MAX7219 display
void display_max7219(uint8_t msg[]) {

  // the sysex message starts with "f0 77 XX ...", start byte, manufacturer code, device code
  if (msg[1] != 0x77) return; // check for desired manufacturer code

  if (msg[2] == 0x10) Serial.println("SysEx from Device 10:"); // device code 10
  if (msg[2] == 0x09) Serial.println("SysEx from Device 09:"); // device code 09

  char txt[16]; // array for the number as char array
  for (int i = 0; i < strlen(msg); i++)
  {
    txt[i] = int8_t(msg[i + 3]); // convert uint8_t sysex to int8_t char
    Serial.print(txt[i]);
    Serial.print(" ");
  }
  Serial.println(" ");

  // if the device code is accepted, display the txt on the device code display
  if (msg[2] == 0x10) max72_10.display(txt, 0); // device code 10
  if (msg[2] == 0x09) max72_09.display(txt, 0); // device code 09
}

// function to capture the incoming SysEx message
bool sysExMessageCallback(SysExMessage se) {

  // send the se.data to the display function
  // size_t len = se.length;
  // uint8_t cn = se.CN; // port is not implemented, just = 0

  display_max7219(se.data); // pass the data to MAX7219 function

  // send debug info to Serial Log
  Serial << F("System Exclusive message: ") << hex;
  for (size_t i = 0; i < se.length; ++i)
    Serial << se.data[i] << ' ';
  Serial << dec << F("on cable ") << se.CN << endl;
  return true; // Return true to indicate that handling is done,
  // and Control_Surface shouldn't handle it anymore.
  // If you want Control_Surface to handle it as well,
  // return false;
}

/*
  Serial output from Message Callback
  19:51:36.857 -> SysEx from Device 10:
  19:51:36.857 -> 1 2 3 4 . 5 7   
  19:51:36.857 -> System Exclusive message: f0 77 10 31 32 33 34 2e 35 37 00 00 00 00 00 f7 on cable 0
  19:51:36.857 -> f0 77 09 31 32 33 34 2e 35 36 37 39 00 00 00 f7
  19:51:36.857 -> SysEx from Device 09:
  19:51:36.857 -> 1 2 3 4 . 5 6 7 9   
  19:51:36.857 -> System Exclusive message: f0 77 09 31 32 33 34 2e 35 36 37 39 00 00 00 f7 on cable 0
*/

/*
  MIDI Monitor log:
  02:55:54.343  From ProMicro musinou SysEx Unknown Manufacturer 16 bytes F0 77 10 31 32 33 34 2E 35 37 00 00 00 00 00 F7
  02:55:54.343  From ProMicro musinou SysEx Unknown Manufacturer 16 bytes F0 77 09 31 32 33 34 2E 35 36 37 39 00 00 00 F7
  02:55:54.349  From VMPK Output      SysEx Unknown Manufacturer 16 bytes F0 77 10 31 32 33 34 2E 35 37 00 00 00 00 00 F7
  02:55:54.349  To Arduino Micro      SysEx Unknown Manufacturer 16 bytes F0 77 10 31 32 33 34 2E 35 37 00 00 00 00 00 F7
  02:55:54.349  From VMPK Output      SysEx Unknown Manufacturer 16 bytes F0 77 09 31 32 33 34 2E 35 36 37 39 00 00 00 F7
  02:55:54.349  To Arduino Micro      SysEx Unknown Manufacturer 16 bytes F0 77 09 31 32 33 34 2E 35 36 37 39 00 00 00 F7
*/

void setup() {
  Serial.begin(115200);
  max72_10.begin();
  max72_10.clear();
  max72_09.begin();
  max72_09.clear();

  Control_Surface.begin();
  Control_Surface.setMIDIInputCallbacks(nullptr,   //
                                        sysExMessageCallback,     //
                                        nullptr); //
}

void loop() {
  Control_Surface.loop();
}
tttapa commented 4 years ago

Thanks!