Assignment Overview
Learning Objectives
Work Completed
Detailed Documentation
Code written using Cursor IDE and compiled/uploaded with Arduino IDE
/*
* QPAD21 MIDI Piano Controller
*
* Touch pads play piano notes and display the corresponding keys
* Simple MIDI output for testing with Hairless MIDI
*
* Developed in Cursor IDE, compiled with Arduino IDE
*/
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Adafruit_FreeTouch.h>
// Display configuration
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
#define SCREEN_ADDRESS 0x3C
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// Hardware pins
#define PIN_LED 15
#define N_TOUCH 6
#define THRESHOLD 750
// Touch pad configuration
uint8_t touch_pins[N_TOUCH] = {2, 3, 4, 5, 6, 7};
Adafruit_FreeTouch* touch_devices[N_TOUCH];
// Touch state tracking
int touch_values[N_TOUCH] = {0, 0, 0, 0, 0, 0};
bool pin_touched_now[N_TOUCH] = {false, false, false, false, false, false};
bool pin_touched_past[N_TOUCH] = {false, false, false, false, false, false};
// Debouncing
unsigned long last_touch_time[N_TOUCH] = {0, 0, 0, 0, 0, 0};
#define DEBOUNCE_DELAY 300 // 300ms debounce delay
// Piano notes (MIDI note numbers) - matches Microsoft GS Wavetable Synth
// These are the standard piano notes that sound good on Microsoft GS Wavetable Synth
byte piano_notes[N_TOUCH] = {60, 62, 64, 65, 67, 69}; // C4, D4, E4, F4, G4, A4
String note_names[N_TOUCH] = {"C4", "D4", "E4", "F4", "G4", "A4"};
// MIDI Configuration
#define MIDI_CHANNEL 1
void update_touch() {
Ptc *ptc = ((Ptc *)PTC);
for (int i = 0; i < N_TOUCH; i++) {
touch_devices[i]->begin();
touch_values[i] = touch_devices[i]->measure();
ptc->CTRLA.bit.ENABLE = 0;
ptc->CTRLA.bit.SWRESET = 1;
pin_touched_past[i] = pin_touched_now[i];
pin_touched_now[i] = touch_values[i] > THRESHOLD;
}
}
void send_midi_note(byte note, byte velocity) {
// Send MIDI Note On message
Serial.write(0x90 | (MIDI_CHANNEL - 1)); // Note On, Channel 1
Serial.write(note);
Serial.write(velocity);
}
void send_midi_note_off(byte note) {
// Send MIDI Note Off message
Serial.write(0x80 | (MIDI_CHANNEL - 1)); // Note Off, Channel 1
Serial.write(note);
Serial.write((byte)0); // Velocity 0
}
void handle_touch_events() {
unsigned long current_time = millis();
for (int i = 0; i < N_TOUCH; i++) {
// Note On when touched
if (pin_touched_now[i] && !pin_touched_past[i] && (current_time - last_touch_time[i] > DEBOUNCE_DELAY)) {
last_touch_time[i] = current_time;
send_midi_note(piano_notes[i], 127); // Send Note On
digitalWrite(PIN_LED, HIGH);
}
// Note Off when released
if (!pin_touched_now[i] && pin_touched_past[i]) {
send_midi_note_off(piano_notes[i]); // Send Note Off
digitalWrite(PIN_LED, LOW);
}
}
}
void draw_piano_keyboard() {
display.clearDisplay();
// Draw title
display.setTextSize(1);
display.setCursor(0, 0);
display.print("QPAD21 Piano");
// Piano keyboard dimensions
int key_width = 18;
int key_height = 30;
int start_x = 5;
int start_y = 15;
// Draw white keys (6 keys)
for (int i = 0; i < N_TOUCH; i++) {
int x = start_x + (i * key_width);
int y = start_y;
// Draw key background
if (pin_touched_now[i]) {
// Key pressed - filled rectangle
display.fillRect(x, y, key_width - 1, key_height, SSD1306_WHITE);
// Draw note name in black (inverted)
display.setTextColor(SSD1306_BLACK);
display.setCursor(x + 2, y + 8);
display.print(note_names[i]);
display.setTextColor(SSD1306_WHITE);
} else {
// Key not pressed - outline rectangle
display.drawRect(x, y, key_width - 1, key_height, SSD1306_WHITE);
// Draw note name in white
display.setTextColor(SSD1306_WHITE);
display.setCursor(x + 2, y + 8);
display.print(note_names[i]);
}
}
// Draw black keys (sharps) - smaller keys on top
int black_key_width = 12;
int black_key_height = 20;
// Draw black keys for sharps (C#, D#, F#, G#)
int black_keys[] = {0, 1, 3, 4}; // Q0, Q1, Q3, Q4 have sharps
String black_note_names[] = {"C#", "D#", "F#", "G#"};
for (int i = 0; i < 4; i++) {
int key_index = black_keys[i];
int x = start_x + (key_index * key_width) + (key_width / 2) - (black_key_width / 2);
int y = start_y;
// Draw black key
display.fillRect(x, y, black_key_width, black_key_height, SSD1306_WHITE);
// Draw note name in black
display.setTextColor(SSD1306_BLACK);
display.setCursor(x + 1, y + 5);
display.print(black_note_names[i]);
display.setTextColor(SSD1306_WHITE);
}
// Draw touch pad indicators at bottom
display.setCursor(0, 50);
display.print("Q0 Q1 Q2 Q3 Q4 Q5");
// Draw pressed indicators
display.setCursor(0, 58);
for (int i = 0; i < N_TOUCH; i++) {
if (pin_touched_now[i]) {
display.print(" * ");
} else {
display.print(" - ");
}
}
display.display();
}
void setup() {
// Initialize serial for MIDI output
Serial.begin(31250);
// Initialize touch devices
for (int i = 0; i < N_TOUCH; i++) {
touch_devices[i] = new Adafruit_FreeTouch(touch_pins[i], OVERSAMPLE_1, RESISTOR_100K, FREQ_MODE_NONE);
}
// Initialize LED
pinMode(PIN_LED, OUTPUT);
digitalWrite(PIN_LED, LOW);
// Initialize display
if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
// Display initialization failed - flash LED
for (int i = 0; i < 10; i++) {
digitalWrite(PIN_LED, HIGH);
delay(100);
digitalWrite(PIN_LED, LOW);
delay(100);
}
}
// Initial display
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(10, 25);
display.println("QPAD21 Piano");
display.setCursor(10, 35);
display.println("Ready!");
display.display();
delay(1000);
}
void loop() {
// Update touch sensors
update_touch();
// Handle touch events
handle_touch_events();
// Update display
draw_piano_keyboard();
// Small delay
delay(50);
}
Images
Reflection
Links