Config management mode, to send/receive config via usb midi sysex

This commit is contained in:
John Stäck 2019-07-29 15:44:43 +02:00
parent db4e4ac2f7
commit 2741ff5a27
8 changed files with 316 additions and 28 deletions

View file

@ -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;
}

View file

@ -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<length; ++i) {
@ -149,7 +150,6 @@ void dinMIDIsendSysex(const uint8_t data[], const uint8_t length) {
MIDI_SERIAL.write(0xF7); //Sysex end
}
void sendWLPower(const uint8_t level) {
uint8_t buf[6] = {
0x00, 0x21, 0x11, //Manufacturer id
@ -165,7 +165,6 @@ void sendWLPower(const uint8_t level) {
}
void sendWLChannel(const uint8_t channel) {
uint8_t buf[6] = {
0x00, 0x21, 0x11, //Manufacturer id
@ -180,3 +179,26 @@ void sendWLChannel(const uint8_t channel) {
dinMIDIsendSysex(buf, 6);
}
//Translate between "midi data" (only use 7 LSB per byte, big endian) and "teensy data" (little endian)
//Only 14 LSB of int value are used (2MSB are discarded), so only works for unsigned data 0-16383
//NOTE: This assumes code is running on a little-endian CPU, both for real device (Teensy) and simulator.
uint16_t midi16to14(uint16_t realdata) {
return (realdata & 0x3F80) >>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;
}

View file

@ -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

View file

@ -1,15 +1,18 @@
#include <Arduino.h>
#include <EEPROM.h>
#include <Adafruit_SSD1306.h>
#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<length) {
crc ^= message[pos++]; //Get next byte and increment position
for (uint8_t j=0; j<8; ++j) { //Mask off 8 next bits
crc = (crc >> 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<EEPROM_SIZE/2; idx++) {
uint16_t eepromval = readSetting(idx*2);
config_buffer_start[idx] = midi16to14(eepromval);
}
uint32_t checksum = crc32(sysex_data, checksum_pos);
/*
printf("CRC len: %d\n", checksum_pos);
printf("CRC32: %X | %u\n", checksum, checksum);
*/
*(uint32_t*)(sysex_data+checksum_pos) = midi32to28(checksum);
usbMIDI.sendSysEx(sysex_size, sysex_data);
free(sysex_data);
}
//Send a simple 3-byte message code as sysex
void sendSysexMessage(const char* messageCode) {
char sysexMessage[] = "vvvNuEVIccc"; //Placeholders for vendor and code
memcpy(sysexMessage, sysex_id, 3);
memcpy(sysexMessage+8, messageCode, 3);
usbMIDI.sendSysEx(11, (const uint8_t *)sysexMessage);
}
//Send EEPROM and firmware versions
void sendSysexVersion() {
char sysexMessage[] = "vvvNuEVIc04eevvvvvvvv"; //Placeholders for vendor and code
memcpy(sysexMessage, sysex_id, 3);
memcpy(sysexMessage+13, FIRMWARE_VERSION, min(strlen(FIRMWARE_VERSION), 8));
*(uint16_t*)(sysexMessage+11) = midi16to14(EEPROM_VERSION);
uint8_t message_length = 13+strlen(FIRMWARE_VERSION);
usbMIDI.sendSysEx(message_length, (const uint8_t *)sysexMessage);
}
extern Adafruit_SSD1306 display;
void configShowMessage(const char* message) {
display.fillRect(0,32,128,64,BLACK);
display.setCursor(0,32);
display.setTextColor(WHITE);
display.print(message);
display.display();
}
uint8_t* sysex_rcv_buffer = NULL;
void handleSysexChunk(const uint8_t *data, uint16_t length, bool last) {
size_t pos = 0;
if(!sysex_rcv_buffer) {
//Start out with an empty buffer
sysex_rcv_buffer = (uint8_t *)malloc(length);
} else {
//Increase size of current buffer
size_t pos = sizeof(sysex_rcv_buffer);
sysex_rcv_buffer = (uint8_t *)realloc(sysex_rcv_buffer, pos + length);
}
//Append this chunk to buffer
memcpy(sysex_rcv_buffer + pos, data, length);
//If it's the last one, call the regular handler to process it
if(last) {
handleSysex(sysex_rcv_buffer, pos+length);
free(sysex_rcv_buffer);
sysex_rcv_buffer = NULL;
}
}
void handleSysex(const uint8_t *data, uint8_t length) {
//Too short to even contain a 3-byte vendor id is not for us.
if(length<3) return;
//Verify vendor
if(strncmp((char*)data, sysex_id, 3)) return; //Silently ignore different vendor id
//Verify header. Min length is 3+5+3 bytes (vendor+header+message code)
if(length<11 || strncmp((char*)(data+3), "NuEVI", 3)) {
configShowMessage("Invalid message received.");
sendSysexMessage("e00");
return;
}
//Get message code
char messageCode[3];
strncpy(messageCode, (char*)(data+8), 3);
if(!strncmp(messageCode, "c00", 3)) { //Config dump request
configShowMessage("Sending config...");
sendSysexSettings();
configShowMessage("Config sent.");
} else if(!strncmp(messageCode, "c03", 3)) { //Version info request
configShowMessage("Sending version.");
sendSysexVersion();
} else {
configShowMessage("Unknown message.");
sendSysexMessage("e01"); //Unimplemented message code
}
}
void configModeSetup() {
statusLedFlash(500);
display.clearDisplay();
display.setCursor(0,0);
display.setTextColor(WHITE);
display.setTextSize(0);
display.println("Config mgmt");
display.println("Power off NuEVI");
display.println("to exit");
display.display();
usbMIDI.setHandleSystemExclusive(handleSysexChunk);
statusLedFlash(500);
configShowMessage("Ready.");
}
//"Main loop". Just sits and wait for midi messages and lets the sysex handler do all the work.
void configModeLoop() {
usbMIDI.read();
}

View file

@ -56,6 +56,7 @@
#define TRILL3_INTERVAL_ADDR 98
#define DAC_MODE_ADDR 100
#define EEPROM_SIZE 102
//DAC output modes
@ -121,11 +122,24 @@
#define DAC_MODE_FACTORY DAC_MODE_BREATH
void readEEPROM();
void readEEPROM(bool factoryReset);
void setBit(uint16_t &bitfield, const uint8_t pos, const uint16_t value);
uint16_t readSetting(uint16_t address);
void writeSetting(uint16_t address, uint16_t value);
uint16_t readSettingBounded(uint16_t address, uint16_t min, uint16_t max, uint16_t defaultValue);
//Functions for config management mode
void sendSysexSettings();
void sendSysexMessage(const char* messageCode);
void sendSysexVersion();
void handleSysex(uint8_t *data, uint8_t length);
uint32_t crc32(uint8_t *message, size_t length);
void configInitScreen();
void configShowMessage(const char* message);
void configModeSetup();
void configModeLoop();
#endif