Midterm Project


/*
Unified IO → Multi-Channel USB-MIDI (Nano 33 IoT / SAMD21)
Channel mapping:
CH 1: VL53L0X distance -> CC#1 (Mod Wheel), reversed (near=0, far=127)
CH 2: Buttons D5..D10 -> Notes (C4..A4) NoteOn/Off
CH 3: FSR A6 -> CC#11 (Expression)
CH 4: Pot A3 -> CC#74 (Brightness)
CH 5: Pot A2 -> CC#71 (Resonance, with simple noise smoothing)
VL53L0X enable/disable by BUTTON on D3:
- Hold D3 (LOW) to enable measuring
- Release D3 to disable and IMMEDIATELY send CC=0
*/
#include <Wire.h>
#include <VL53L0X.h>
#include "MIDIUSB.h"
// ---------- VL53L0X + control button ----------
VL53L0X sensor;
const int PIN_XSHUT = 2; // VL53L0X XSHUT
const int PIN_EN_BTN = 3; // Enable button (to GND, INPUT_PULLUP)
bool sensorOn = false;
// 距离映射范围(mm):近->0,远->127
const int D_NEAR = 50;
const int D_FAR = 600;
// ---------- Buttons D5..D10 ----------
const uint8_t BTN_PINS[] = {5, 6, 7, 8, 9, 10};
const uint8_t BTN_COUNT = sizeof(BTN_PINS) / sizeof(BTN_PINS[0]);
uint8_t btnPrev[6] = {HIGH, HIGH, HIGH, HIGH, HIGH, HIGH}; // INPUT_PULLUP
const uint8_t BTN_NOTES[] = {60, 62, 64, 65, 67, 69}; // C4..A4
// ---------- Analog inputs ----------
const int PIN_FSR = A6;
const int PIN_POT1 = A3;
const int PIN_POT2 = A2;
const int FSR_MIN = 450;
const int FSR_MAX = 900;
// ---------- MIDI config ----------
const uint8_t CH_VL53 = 1;
const uint8_t CH_BTN = 2;
const uint8_t CH_FSR = 3;
const uint8_t CH_POT1 = 4;
const uint8_t CH_POT2 = 5;
const uint8_t CC_VL53 = 1; // Mod Wheel
const uint8_t CC_FSR = 11; // Expression
const uint8_t CC_POT1 = 74; // Brightness
const uint8_t CC_POT2 = 71; // Resonance
// ---------- Throttle / Deadband ----------
const uint16_t ANALOG_SEND_INTERVAL_MS = 8; // ~125 Hz
const uint8_t CC_DEADBAND = 1; // 通用
const uint8_t CC_DEADBAND_POT2 = 2; // A2 单独更宽一点
uint32_t tLastFSR = 0, tLastPOT1 = 0, tLastPOT2 = 0, tLastVL = 0;
int lastFSRcc = -1, lastPOT1cc = -1, lastPOT2cc = -1, lastVLcc = -1;
uint8_t invalidCount = 0;
// ---------- A2 简单去噪参数 ----------
float pot2Filt = -1.0f;
const float POT2_ALPHA = 0.20f; // 0.1 更稳、0.3 更灵敏
// ---------- Utils ----------
static inline uint8_t clamp127(int v) {
if (v < 0) return 0;
if (v > 127) return 127;
return (uint8_t)v;
}
static inline int median3(int a, int b, int c) {
// 返回三个数的中位数(不分支版也行,这里直观可靠)
if (a > b) { int t=a; a=b; b=t; }
if (b > c) { int t=b; b=c; c=t; }
if (a > b) { int t=a; a=b; b=t; }
return b;
}
static inline int analogReadMedian3(uint8_t pin) {
int a = analogRead(pin);
delayMicroseconds(200);
int b = analogRead(pin);
delayMicroseconds(200);
int c = analogRead(pin);
return median3(a, b, c);
}
void hardResetVL() {
digitalWrite(PIN_XSHUT, LOW);
delay(10);
digitalWrite(PIN_XSHUT, HIGH);
delay(10);
}
bool enableVL() {
hardResetVL();
if (!sensor.init()) {
Serial.println("VL53L0X init failed");
return false;
}
sensor.setTimeout(200);
sensor.setMeasurementTimingBudget(20000); // 20ms ≈ 50Hz
delay(50);
sensorOn = true;
lastVLcc = -1;
Serial.println("VL53 ENABLED");
return true;
}
void disableVL() {
sensorOn = false;
digitalWrite(PIN_XSHUT, LOW);
Serial.println("VL53 DISABLED");
}
// ---------- MIDI helpers ----------
void midiSendCC(uint8_t ch, uint8_t cc, uint8_t val) {
midiEventPacket_t evt = {0x0B, (uint8_t)(0xB0 | ((ch - 1) & 0x0F)), cc, val};
MidiUSB.sendMIDI(evt);
}
void midiSendNoteOn(uint8_t ch, uint8_t note, uint8_t vel) {
midiEventPacket_t evt = {0x09, (uint8_t)(0x90 | ((ch - 1) & 0x0F)), note, vel};
MidiUSB.sendMIDI(evt);
}
void midiSendNoteOff(uint8_t ch, uint8_t note, uint8_t vel) {
midiEventPacket_t evt = {0x08, (uint8_t)(0x80 | ((ch - 1) & 0x0F)), note, vel};
MidiUSB.sendMIDI(evt);
}
void setup() {
Serial.begin(115200);
while (!Serial) {}
Wire.begin();
Wire.setClock(400000);
pinMode(PIN_XSHUT, OUTPUT);
pinMode(PIN_EN_BTN, INPUT_PULLUP);
digitalWrite(PIN_XSHUT, LOW);
for (uint8_t i = 0; i < BTN_COUNT; i++) {
pinMode(BTN_PINS[i], INPUT_PULLUP);
}
Serial.println("Hold D3 to enable VL53; release to disable (sends CC=0). USB-MIDI ready.");
}
void loop() {
// --- VL53 enable/disable by button ---
bool enablePressed = (digitalRead(PIN_EN_BTN) == LOW);
if (enablePressed && !sensorOn) {
enableVL();
} else if (!enablePressed && sensorOn) {
disableVL();
if (lastVLcc != 0) {
midiSendCC(CH_VL53, CC_VL53, 0);
lastVLcc = 0;
MidiUSB.flush();
}
}
const uint32_t now = millis();
// --- VL53 distance -> CC on CH1 (near=0, far=127) ---
if (sensorOn && (now - tLastVL >= 10)) {
tLastVL = now;
uint16_t dist = sensor.readRangeSingleMillimeters();
if (!sensor.timeoutOccurred() && dist < 8190) {
int d = constrain((int)dist, D_NEAR, D_FAR);
int cc = map(d, D_NEAR, D_FAR, 0, 127);
cc = clamp127(cc);
if (lastVLcc < 0 || abs(cc - lastVLcc) > CC_DEADBAND) {
lastVLcc = cc;
midiSendCC(CH_VL53, CC_VL53, cc);
}
}
}
// --- Buttons -> Notes on CH2 ---
for (uint8_t i = 0; i < BTN_COUNT; i++) {
uint8_t s = digitalRead(BTN_PINS[i]);
if (s != btnPrev[i]) {
btnPrev[i] = s;
uint8_t note = BTN_NOTES[i];
if (s == LOW) midiSendNoteOn(CH_BTN, note, 100);
else midiSendNoteOff(CH_BTN, note, 0);
}
}
// --- FSR -> CC on CH3 ---
if (now - tLastFSR >= ANALOG_SEND_INTERVAL_MS) {
tLastFSR = now;
int raw = analogRead(PIN_FSR);
int cc = map(raw, FSR_MIN, FSR_MAX, 0, 127);
cc = clamp127(cc);
if (lastFSRcc < 0 || abs(cc - lastFSRcc) > CC_DEADBAND) {
lastFSRcc = cc;
midiSendCC(CH_FSR, CC_FSR, cc);
}
}
// --- Pot1 A3 -> CC on CH4 ---
if (now - tLastPOT1 >= ANALOG_SEND_INTERVAL_MS) {
tLastPOT1 = now;
int raw = analogRead(PIN_POT1);
int cc = map(raw, 0, 1023, 0, 127);
cc = clamp127(cc);
if (lastPOT1cc < 0 || abs(cc - lastPOT1cc) > CC_DEADBAND) {
lastPOT1cc = cc;
midiSendCC(CH_POT1, CC_POT1, cc);
}
}
// --- Pot2 A2 -> CC on CH5 (median-of-3 + IIR smoothing) ---
if (now - tLastPOT2 >= ANALOG_SEND_INTERVAL_MS) {
tLastPOT2 = now;
// 1) median-of-3 去除尖峰
int rawMed = analogReadMedian3(PIN_POT2); // 0..1023
// 2) 一阶低通 IIR:filt += α*(raw - filt)
if (pot2Filt < 0) pot2Filt = rawMed; // 首次赋初值
pot2Filt += POT2_ALPHA * (rawMed - pot2Filt);
// 3) 映射到 0..127(使用浮点更平滑)
int cc = (int)lround((pot2Filt / 1023.0f) * 127.0f);
cc = clamp127(cc);
if (lastPOT2cc < 0 || abs(cc - lastPOT2cc) > CC_DEADBAND_POT2) {
lastPOT2cc = cc;
midiSendCC(CH_POT2, CC_POT2, (uint8_t)cc);
}
}
MidiUSB.flush();
delay(2);
}