I made a tiny device that uses pressure from my mouth to control MIDI. I am investigating a few pressure and hall effect sensors for a much larger project I want to make. But I was really surprised by how reactive those pressure sensors are so I decided to make a full-blown MIDI controller from one of them.
Updates
- 2024-12-15: Made the prototype
Introduction
The main features are:
- Provide a MIDI CC message by USB-MIDI at 63 for ambient pressure going toward 0 if you suck on the tube, and 127 if you blow in it. It is a high resolution DAC, so you could totally use it for higher resolution controls.
- Provide feedback to the user through color (blue for suck, green for blow and red for over/under pressure)
- Small and decently robust
See it in action (video)
Elements
- RP2040 board (used a Waveshare RP2040-Zero)
- GZP6857D010KPP I2C pressure sensor
- silicone tubing from an IV drip set kit (exact diameter, hopefully mouth safe)
- PCL plastic (can melt in hot water) for the case (not the brand I bought, but Adafruit has some)
- Polyimide tape (Kapton tape)
Total cost: $10-ish
Schematic
Construction
I didn’t take pictures of the construction, but I basically dead-bugged the resistors and the capacitor behind the sensor, added short cables, rolled the whole thing in Polyimide tape and applied melted PCL in small bands except over the Boot and Reset buttons. I then smoothed the PCL with my soldering iron set at 100C and a bit of parchment paper. I use tape as it makes it easier to slice and break the PCL if you need to rework the circuits. If you apply it directly to a board, you will more than likely rip off some SMDs or not be able to solder anything without burning the plastic away.
Code
// Based on https://raw.githubusercontent.com/VictorDubois/XGZP6897D-Arduino/refs/heads/main/test_vacuum.ino
#define MIDI_MANUFACTURER "bjonnh.net"
#define MIDI_PRODUCT "suck-and-blow"
#define MIDI_SERIAL "78890"
#include <Arduino.h>
#include <Adafruit_TinyUSB.h>
#include <Adafruit_NeoPixel.h>
#include <MIDI.h>
#include <Wire.h>
Adafruit_USBD_MIDI usb_midi;
#define DEBUG 0
#define PIXEL_PIN 16
#define K 128 // see table in datasheet (we don't really care we don't need to have precision)
#define I2C_address 0x6D
#define TIMEOUT 5000
#define PRESSURE_RANGE_MIN -8000
#define PRESSURE_RANGE_MAX 8000
#define PRESSURE_RANGE_MIDDLE (PRESSURE_RANGE_MAX + PRESSURE_RANGE_MIN) / 2
#define SIGNAL_RANGE_MIN 0
#define SIGNAL_RANGE_MAX 127
MIDI_CREATE_INSTANCE(Adafruit_USBD_MIDI, usb_midi, MIDI);
Adafruit_NeoPixel strip(1, PIXEL_PIN, NEO_RGB + NEO_KHZ800);
int32_t convertValue(int32_t input) {
if (input < PRESSURE_RANGE_MIN) {
return SIGNAL_RANGE_MIN;
} else if (input > PRESSURE_RANGE_MAX) {
return SIGNAL_RANGE_MAX;
} else {
return map(input, PRESSURE_RANGE_MIN, PRESSURE_RANGE_MAX, SIGNAL_RANGE_MIN, SIGNAL_RANGE_MAX);
}
}
double readPressure() {
unsigned char pressure_H, pressure_M, pressure_L;
long int pressure_adc;
double pressure;
// We get both pressure and temp as we need to have the correction applied
write_one_byte(I2C_address, 0x30, 0x0A);
long timeout = 0;
while ((Read_One_Byte(I2C_address, 0x30) & 0x08) > 0) {
timeout++;
if (timeout > TIMEOUT) {
if (DEBUG) Serial.println("Timeout reading");
break;
}
}
pressure_H = Read_One_Byte(I2C_address, 0x06);
pressure_M = Read_One_Byte(I2C_address, 0x07);
pressure_L = Read_One_Byte(I2C_address, 0x08);
// Read ADC output Data of Pressure
pressure_adc = pressure_H * 65536 + pressure_M * 256 + pressure_L;
//Compute the value of pressure converted by ADC
if (pressure_adc > 8388608) {
pressure = (pressure_adc - 16777216) / K; //unit is Pa, select appropriate K value according to pressure range.
} else {
pressure = pressure_adc / K; //unit is Pa, select appropriate K value according to pressure range. //The conversion formula of calibrated pressure, its unit is Pa
}
return pressure;
}
void write_one_byte(uint8_t device_address, uint8_t addr, uint8_t thedata) {
Wire1.beginTransmission(device_address);
Wire1.write(addr);
Wire1.write(thedata);
Wire1.endTransmission();
}
uint8_t Read_One_Byte(uint8_t device_address, uint8_t addr) {
uint8_t nb_bytes = 1;
Wire1.beginTransmission(device_address);
Wire1.write(addr);
Wire1.endTransmission();
Wire1.requestFrom(device_address, nb_bytes);
return Wire1.read(); // Receive a byte as character
}
void setup() {
if (!TinyUSBDevice.isInitialized()) {
TinyUSBDevice.begin(0);
}
TinyUSBDevice.setManufacturerDescriptor(MIDI_MANUFACTURER);
TinyUSBDevice.setProductDescriptor(MIDI_PRODUCT);
TinyUSBDevice.setSerialDescriptor(MIDI_SERIAL);
MIDI.begin(MIDI_CHANNEL_OMNI);
// If already enumerated, additional class driverr begin() e.g msc, hid, midi won't take effect until re-enumeration
if (TinyUSBDevice.mounted()) {
TinyUSBDevice.detach();
delay(10);
TinyUSBDevice.attach();
}
if (DEBUG) Serial.begin(57600);
Wire1.setSCL(27);
Wire1.setSDA(26);
Wire1.begin();
Wire1.setClock(400000);
strip.begin();
strip.show();
delay(100);
}
void loop() {
double pressure = readPressure();
int32_t value = convertValue(pressure);
MIDI.sendControlChange(2, value, 1);
uint32_t color = strip.Color(255, 255, 255);
if (pressure > PRESSURE_RANGE_MAX || pressure < PRESSURE_RANGE_MIN) {
color = strip.Color(255, 0, 0);
} else if (pressure < PRESSURE_RANGE_MIDDLE) {
color = strip.Color(0, 0, map(pressure, PRESSURE_RANGE_MIN, 0, 255, 0));
} else if (pressure >= PRESSURE_RANGE_MIDDLE) {
color = strip.Color(0, map(pressure, 0, PRESSURE_RANGE_MAX, 0, 255), 0);
}
strip.setPixelColor(0, color);
strip.show();
if (DEBUG) {
if (Serial) {
Serial.print(SIGNAL_RANGE_MIN);
Serial.print(" ");
Serial.print(SIGNAL_RANGE_MAX);
Serial.print(" ");
Serial.print(pressure);
Serial.print(" ");
Serial.println(value);
}
}
}
Improvements (maybe one day)
- Add a button to stop it from sending data.
- Sysex and web interface for configuration (like I did for Dinoctopus)
Conclusion
This is an easy, in-a-day project you can make. It confirms that I probably want to use this type of sensors for my next project, so all is well. I’ll probably go with the analog version of the sensor so I can have more of them without the need to use a switch. Have fun.
Thanks
- https://github.com/VictorDubois/XGZP6897D-Arduino - for the I2C code to get values from the sensor
- Books and PS1 for the Polyimide tape and the resistors/capacitors