From 2741ff5a27d862d09d029bef3c2989cbafb6bc2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20St=C3=A4ck?= Date: Mon, 29 Jul 2019 15:44:43 +0200 Subject: [PATCH] Config management mode, to send/receive config via usb midi sysex --- NuEVI/NuEVI.ino | 43 ++++--- NuEVI/midi.cpp | 26 ++++- NuEVI/midi.h | 9 ++ NuEVI/settings.cpp | 213 ++++++++++++++++++++++++++++++++-- NuEVI/settings.h | 16 ++- simulation/include/Arduino.h | 4 +- simulation/src/nuevisim.cpp | 24 +++- simulation/src/simusbmidi.cpp | 9 ++ 8 files changed, 316 insertions(+), 28 deletions(-) diff --git a/NuEVI/NuEVI.ino b/NuEVI/NuEVI.ino index 2f22029..ff86900 100644 --- a/NuEVI/NuEVI.ino +++ b/NuEVI/NuEVI.ino @@ -236,6 +236,8 @@ byte subOctaveDouble = 0; Adafruit_MPR121 touchSensor = Adafruit_MPR121(); // This is the 12-input touch sensor FilterOnePole breathFilter; +bool configManagementMode = false; + //_______________________________________________________________________________________________ SETUP @@ -260,9 +262,19 @@ void setup() { pinMode(biteJumperGndPin, OUTPUT); //PBITE digitalWrite(biteJumperGndPin, LOW); //PBITE + bool factoryReset = !digitalRead(ePin) && !digitalRead(mPin); + configManagementMode = !factoryReset && !digitalRead(uPin) && !digitalRead(dPin); + + initDisplay(); //Start up display and show logo + + //If going into config management mode, stop here before we even touch the EEPROM. + if(configManagementMode) { + configModeSetup(); + return; + } //Read eeprom data into global vars - readEEPROM(); + readEEPROM(factoryReset); activePatch = patch; @@ -273,8 +285,6 @@ void setup() { } breathFilter.setFilter(LOWPASS, filterFreq, 0.0); // create a one pole (RC) lowpass filter - - initDisplay(); //Start up display and show logo biteJumper = !digitalRead(biteJumperPin); if (biteJumper){ @@ -329,6 +339,13 @@ void setup() { //_______________________________________________________________________________________________ MAIN LOOP void loop() { + + //If in config mgmt loop, do that and nothing else + if(configManagementMode) { + configModeLoop(); + return; + } + breathFilter.input(analogRead(breathSensorPin)); pressureSensor = constrain((int) breathFilter.output(), 0, 4095); // Get the filtered pressure sensor reading from analog pin A0, input from sensor MP3V5004GP readSwitches(); @@ -356,7 +373,7 @@ void loop() { bool bothPB = (pbUp > ((pitchbMaxVal + pitchbThrVal) / 2)) && (pbDn > ((pitchbMaxVal + pitchbThrVal) / 2)); bool brSuck = analogRead(breathSensorPin) < (breathCalZero - (bcasMode?900:800)); - + if ( (bothPB && legacy) || (brSuck && legacyBrAct && (bothPB || bcasMode)) @@ -364,9 +381,9 @@ void loop() { fingeredNoteUntransposed = patchLimit(fingeredNoteUntransposed + 1); - if (exSensor >= ((extracThrVal + extracMaxVal) / 2)) { // instant midi setting + if (exSensor >= ((extracThrVal + extracMaxVal) / 2)) { // instant midi setting if ((fingeredNoteUntransposed >= 73) && (fingeredNoteUntransposed <= 88)) { - MIDIchannel = fingeredNoteUntransposed - 72; // Mid C and up + MIDIchannel = fingeredNoteUntransposed - 72; // Mid C and up } } else { if (!pinkyKey) { // note number to patch number @@ -408,7 +425,7 @@ void loop() { doPatchUpdate = 1; } - if (!K1 && !K2 && K3 && !K4) { //send reverb pitchlatch value + if (!K1 && !K2 && K3 && !K4) { //send reverb pitchlatch value reverb = ((pitchlatch - 36) * 2); reverb = constrain(reverb, 0, 127); @@ -854,7 +871,7 @@ void pitch_bend() { vibSignal = vibSignal * 0.5; } } else { //lever vibrato - vibRead = touchRead(vibratoPin); // SENSOR PIN 15 - built in var cap + vibRead = touchRead(vibratoPin); // SENSOR PIN 15 - built in var cap if (vibRead < vibThr) { if (UPWD == vibDirection) { vibSignal = vibSignal * 0.5 + 0.5 * map(constrain(vibRead, (vibZero - vibMax), vibThr), vibThr, (vibZero - vibMax), 0, calculatedPBdepth * vibDepth[vibrato]); @@ -1031,7 +1048,7 @@ void portamento_() { if (biteJumper){ //PBITE (if pulled low with jumper, use pressure sensor instead of capacitive bite sensor) biteSensor=analogRead(bitePressurePin); // alternative kind bite sensor (air pressure tube and sensor) PBITE - } else { + } else { biteSensor = touchRead(bitePin); // get sensor data, do some smoothing - SENSOR PIN 17 - PCB PINS LABELED "BITE" (GND left, sensor pin right) } if (!vibControl){ @@ -1096,7 +1113,7 @@ void portOff() { //*********************************************************** void readSwitches() { - + // Read touch pads (MPR121), compare against threshold value bool touchKeys[12]; for (byte i = 0; i < 12; i++) { @@ -1123,11 +1140,11 @@ void readSwitches() { K6 = touchKeys[K6Pin]; K7 = touchKeys[K7Pin]; - pinkyKey = (touchRead(halfPitchBendKeyPin) > touch_Thr); // SENSOR PIN 1 - PCB PIN "S1" + pinkyKey = (touchRead(halfPitchBendKeyPin) > touch_Thr); // SENSOR PIN 1 - PCB PIN "S1" int qTransp = pinkyKey ? pinkySetting-12 : 0; - // Calculate midi note number from pressed keys + // Calculate midi note number from pressed keys fingeredNoteUntransposed = startNote - 2*K1 - K2 - 3*K3 //"Trumpet valves" @@ -1146,7 +1163,7 @@ void readSwitches() { if ((millis() - lastDeglitchTime) > deglitch) { // whatever the reading is at, it's been there for longer // than the debounce delay, so take it as the actual current state - fingeredNote = fingeredNoteRead; + fingeredNote = fingeredNoteRead; } lastFingering = fingeredNoteRead; } diff --git a/NuEVI/midi.cpp b/NuEVI/midi.cpp index 56e8d74..396d3e3 100644 --- a/NuEVI/midi.cpp +++ b/NuEVI/midi.cpp @@ -141,6 +141,7 @@ void dinMIDIsendProgramChange(uint8_t value, uint8_t ch) { midiSend2B((0xC0 | ch), value); } +// Send sysex commands to wireless module void dinMIDIsendSysex(const uint8_t data[], const uint8_t length) { MIDI_SERIAL.write(0xF0); //Sysex command for(int i=0; i>7 | (realdata & 0x007F) <<8; +} + +uint16_t midi14to16(uint16_t mididata) { + return (mididata & 0x7F00) >> 8 | (mididata & 0x007F) <<7 ; +} + +//This is a bit different. MSB of each byte is just discarded (instead of discarding MSB for whole value). Just used for CRC (easier to compare) +uint32_t midi32to28(uint32_t realdata) { + uint8_t* p = (uint8_t*)&realdata; + + uint32_t r=0; + for(int i=0; i<4; ++i) { + r = r<<8 | (p[i] & 0x7F); + } + return r; +} diff --git a/NuEVI/midi.h b/NuEVI/midi.h index 800d1db..d663169 100644 --- a/NuEVI/midi.h +++ b/NuEVI/midi.h @@ -1,6 +1,9 @@ #ifndef __MIDI_H #define __MIDI_H +//This is a completely made up "European" SysEx manufacturer ID. +static const char sysex_id[] = { 0x00, 0x3e, 0x7f }; + //Enable use of USB and serial MIDI #define USE_MIDI_USB #define USE_MIDI_SERIAL @@ -33,4 +36,10 @@ void dinMIDIsendSysex(const uint8_t data[], const uint8_t length); void sendWLPower(const uint8_t level); void sendWLChannel(const uint8_t channel); + +//Convert things between "regular data" and MIDI data (byte order and 7-bits-per-byte) +uint16_t midi16to14(uint16_t realdata); +uint16_t midi14to16(uint16_t mididata); +uint32_t midi32to28(uint32_t realdata); + #endif diff --git a/NuEVI/settings.cpp b/NuEVI/settings.cpp index 913e06f..109a959 100644 --- a/NuEVI/settings.cpp +++ b/NuEVI/settings.cpp @@ -1,15 +1,18 @@ #include #include +#include + #include "settings.h" #include "globals.h" #include "menu.h" #include "hardware.h" #include "config.h" +#include "midi.h" +#include "led.h" //Read settings from eeprom. Returns wether or not anything was written (due to factory reset or upgrade) -void readEEPROM() { - bool factoryReset = !digitalRead(ePin) && !digitalRead(mPin); +void readEEPROM(bool factoryReset) { // if stored settings are not for current version, or Enter+Menu are pressed at startup, they are replaced by factory settings uint16_t settingsVersion = readSetting(VERSION_ADDR); @@ -26,11 +29,11 @@ void readEEPROM() { writeSetting(BREATH_THR_ADDR, BREATH_THR_FACTORY); writeSetting(BREATH_MAX_ADDR, BREATH_MAX_FACTORY); if (digitalRead(biteJumperPin)){ //PBITE (if pulled low with jumper, pressure sensor is used instead of capacitive bite sensing) - writeSetting(PORTAM_THR_ADDR, PORTAM_THR_FACTORY); - writeSetting(PORTAM_MAX_ADDR, PORTAM_MAX_FACTORY); + writeSetting(PORTAM_THR_ADDR, PORTAM_THR_FACTORY); + writeSetting(PORTAM_MAX_ADDR, PORTAM_MAX_FACTORY); } else { - writeSetting(PORTAM_THR_ADDR, PORTPR_THR_FACTORY); - writeSetting(PORTAM_MAX_ADDR, PORTPR_MAX_FACTORY); + writeSetting(PORTAM_THR_ADDR, PORTPR_THR_FACTORY); + writeSetting(PORTAM_MAX_ADDR, PORTPR_MAX_FACTORY); } writeSetting(PITCHB_THR_ADDR, PITCHB_THR_FACTORY); writeSetting(PITCHB_MAX_ADDR, PITCHB_MAX_FACTORY); @@ -98,7 +101,7 @@ void readEEPROM() { writeSetting(VERSION_ADDR, EEPROM_VERSION); } - + // read all settings from EEPROM breathThrVal = readSettingBounded(BREATH_THR_ADDR, breathLoLimit, breathHiLimit, BREATH_THR_FACTORY); breathMaxVal = readSettingBounded(BREATH_MAX_ADDR, breathLoLimit, breathHiLimit, BREATH_MAX_FACTORY); @@ -199,3 +202,199 @@ uint16_t readSettingBounded(uint16_t address, uint16_t min, uint16_t max, uint16 } return val; } + + + + +//Functions to send and receive config (and other things) via USB MIDI SysEx messages +uint32_t crc32(uint8_t *message, size_t length) { + size_t pos=0; + uint32_t crc=0xFFFFFFFF; + + while (pos> 1) ^ (0xEDB88320 & -(crc & 1)); + } + } + return ~crc; +} + + +/* + +Send EEPROM config dump as sysex message. Message format is structured like this: + ++------------------------------------------------------------------------------------+ +| vendor(3) | "NuEVIc01" (8) | Payload size (2) | EEPROM data (variable) | crc32 (4) | ++------------------------------------------------------------------------------------+ + +Payload size is for the EEPROM data chunk (not including anything else before or after +CRC32 covers the entire buffer up to and including the eeprom data (but not the checksum itself) + +This currently operates under the assumption that the whole EEPROM chunk only consists of unsigned 16 bit ints, only using the range 0-16383 + +*/ +void sendSysexSettings() { + const char *header = "NuEVIc01"; //NuEVI config dump 01 + + //Build a send buffer of all the things + size_t sysex_size = 3 + strlen(header) + 2 + EEPROM_SIZE + 4; + uint8_t *sysex_data = (uint8_t*)malloc(sysex_size); + + //Positions (offsets) of parts in send buffer + int header_pos = 3; + int size_pos = header_pos + strlen(header); + int payload_pos = size_pos + 2; + int checksum_pos = payload_pos + EEPROM_SIZE; + + //SysEX manufacturer ID + memcpy(sysex_data, sysex_id, 3); + + //Header with command code + memcpy(sysex_data+header_pos, header, strlen(header)); + + //Payload length + *(uint16_t*)(sysex_data+size_pos) = midi16to14(EEPROM_SIZE); + + //Config data + uint16_t* config_buffer_start = (uint16_t*)(sysex_data+payload_pos); + + //Read one settings item at a time, change data format, and put in send buffer + for(uint16_t idx=0; idx, std::function); @@ -39,6 +39,8 @@ static const int scale = 3; static SDL_Window *window; +bool no_delay = false; + void _reboot_Teensyduino_() { // TODO: reboot @@ -81,6 +83,8 @@ uint8_t digitalRead(uint8_t pin) { void delay(unsigned int ms) { + if(no_delay) return; + uint32_t endTick = SDL_GetTicks() + ms; auto checktime = [endTick]() -> bool { return endTick > SDL_GetTicks(); }; SimLoop(checktime,NULL); @@ -508,7 +512,7 @@ static void SimLoop(std::function continue_predicate, std::function bool { return true; }, loop ); SimQuit(); @@ -583,7 +595,7 @@ static int SimInit() analogInputs[vMeterPin] = 3025; - // Initialize touch sensors to not be poked + // Initialize touch sensors to not be poked for(int i = 0; i < 12; ++i) { touchSensor.mockFilteredData(i, 4095); } @@ -616,6 +628,8 @@ int main(int argc, const char** argv) args::ValueFlag eepromFile(parser, "eeprom-write", "File to use for EEPROM data", {'e', "eeprom-file"}); args::Flag eepromWrite(parser, "eeprom-write", "Write EEPROM changes to file", {'w', "eeprom-write"}); args::Flag factoryReset(parser, "factory-reset", "Trigger factory reset", {'r', "factory-reset"}); + args::Flag configMode(parser, "config-mode", "Trigger config-management mode", {'c', "config-mode"}); + args::Flag nodelay(parser, "nodelay", "Skip all delays when running", {'n', "nodelay"}); parser.ParseCLI(argc, argv); @@ -628,5 +642,7 @@ int main(int argc, const char** argv) eepromFileName += "eeprom.bin"; } - return SimRun(eepromFileName, args::get(eepromWrite), args::get(factoryReset)); + no_delay = args::get(nodelay); + + return SimRun(eepromFileName, args::get(eepromWrite), args::get(factoryReset), args::get(configMode)); } diff --git a/simulation/src/simusbmidi.cpp b/simulation/src/simusbmidi.cpp index 42d1aa1..a240fc0 100644 --- a/simulation/src/simusbmidi.cpp +++ b/simulation/src/simusbmidi.cpp @@ -58,3 +58,12 @@ bool SimUsbMidi::read(uint8_t __unused channel) { return false; } +//Regular sysex handler +void SimUsbMidi::setHandleSystemExclusive(__unused void (*fptr) (const uint8_t *array, uint8_t size)) { + +} + +//"Chunked" sysex handler (teensy extension), for large messages +void SimUsbMidi::setHandleSystemExclusive(__unused void (*fptr) (const uint8_t *array, uint16_t size, bool last)) { + +}