Files
soundcube-firmware/soundcube-firmware/soundcube-firmware.ino

264 lines
6.4 KiB
Arduino
Raw Normal View History

#include <ArduinoJson.h>
#include <ArduinoJson.hpp>
2025-05-27 13:13:29 +02:00
#include <TCA9555.h>
#include <AS5600.h>
#include <Adafruit_NeoPixel.h>
#include <PWMAudio.h>
#include <I2S.h>
#include <SPI.h>
#include <SD.h>
2025-05-27 13:13:29 +02:00
#define HAPTIC 1
#define AURAL 1
#define UI_SAMPLERATE 22050
I2S i2s(INPUT_PULLUP);
TCA9555 TCA(0x20, &Wire1);
AS5600 ENC(&Wire1);
PWMAudio ui_snd(8);
enum BUTTON {CVINL, CVINR, INL, INR, OUTR, OUTL, CVOUTR, CVOUTL, RIGHT, LEFT, SELECT, DEBUG1, DEBUG2, DEBUG3};
Adafruit_NeoPixel ui_pixels(74, 5, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel edge_pixels(11, 4, NEO_GRB + NEO_KHZ800);
2025-05-27 13:13:29 +02:00
volatile bool flag = false;
volatile bool click = false;
volatile bool amp = false;
int counter = 0;
bool buttons[16] = {false};
int16_t beep[UI_SAMPLERATE];
uint16_t beep_length = 0;
bool sd_card_detected = false;
struct Config {
int edge_color_r = 0;
int edge_color_g = 0;
int edge_color_b = 50;
};
Config config;
void loadConfiguration(Config& config) {
File file = SD.open("/config.txt");
JsonDocument doc;
DeserializationError error = deserializeJson(doc, file);
if(error) Serial.println(F("Failed to read file, using default configuration"));
config.edge_color_r = doc["edge"]["color"]["r"] | 0;
config.edge_color_g = doc["edge"]["color"]["g"] | 0;
config.edge_color_b = doc["edge"]["color"]["b"] | 50;
//strlcpy(config.hostname, doc["hostname"] | "example.com", sizeof(config.hostname));
file.close();
}
2025-05-27 13:13:29 +02:00
void pwm_audio_callback() {
while (ui_snd.availableForWrite()) {
if(click) {
ui_snd.write(beep[counter]);
counter++;
if(counter == beep_length) {
counter = 0;
click = false;
}
2025-05-27 13:13:29 +02:00
} else {
digitalWrite(7, LOW);
2025-05-27 13:13:29 +02:00
ui_snd.write(0);
counter = 0;
2025-05-27 13:13:29 +02:00
}
}
}
void tca_irq() {
flag = true;
}
void setup() {
Serial.begin();
delay(2000);
2025-05-27 13:13:29 +02:00
pinMode(21, INPUT_PULLUP);
sd_card_detected = !digitalRead(21);
delay(500);
bool sdInitialized = SD.begin(22, 23, 24);
delay(100);
if(!sdInitialized) sdInitialized = SD.begin(22, 23, 24); // hack to prevent SD card from not initializing after soft reset
if(sdInitialized) loadConfiguration(config);
if(sdInitialized && SD.exists("/click.wav")) {
File click = SD.open("/click.wav");
int click_bytes = 0;
int16_t maxv = 0;
while (click.available() || click_bytes == UI_SAMPLERATE-1) {
int16_t sample = click.read() * 32;
if(abs(sample) > maxv) maxv = sample;
beep[click_bytes] = sample;
click_bytes++;
}
click.close();
beep_length = click_bytes;
Serial.print("Read ");
Serial.print(beep_length);
Serial.print(" bytes from click.wav into beep array. maxV was ");
Serial.println(maxv);
} else {
for(int i = 0; i < UI_SAMPLERATE; i++){
float sine_pos = (2.0f * M_PI * 8000.0f * (float)i) / (float)UI_SAMPLERATE;
beep[i] = (int16_t)(sin(sine_pos) * 8000.0f);
}
Serial.println("Synthesized 8kHz sine into beep array.");
beep_length = UI_SAMPLERATE;
}
2025-05-27 13:13:29 +02:00
Serial.println("INIT WIRE");
Wire1.setSDA(2);
Wire1.setSCL(3);
Wire1.begin();
Serial.println("SUCCESS");
Serial.println("INIT TCA");
TCA.begin();
TCA.pinMode16(0xFFFF);
TCA.setPolarity16(0x0000);
Serial.println("SUCCESS");
Serial.println("INIT INTERRUPT");
pinMode(1, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(1), tca_irq, FALLING);
Serial.println("SUCCESS");
// Rotary Encoder
ENC.begin(); // set direction pin.
ENC.setDirection(AS5600_COUNTERCLOCK_WISE);
pinMode(6, OUTPUT); // Vibration Motor
pinMode(7, OUTPUT); // UI Amp Enable
ui_snd.onTransmit(pwm_audio_callback);
ui_snd.begin(UI_SAMPLERATE);
digitalWrite(7, LOW);
edge_pixels.begin();
delay(100);
ui_pixels.begin();
2025-05-27 13:13:29 +02:00
digitalWrite(6, HIGH);
delay(50);
digitalWrite(6, LOW);
2025-05-27 13:13:29 +02:00
// i2s.setDOUT(14);
// i2s.setDIN(15);
// i2s.setBCLK(16); //
// i2s.swapClocks();
// i2s.setMCLK(18);
// i2s.setBitsPerSample(16);
// i2s.setFrequency(48000);
// i2s.setSysClk(48000);
// i2s.begin();
// while (1) {
// int16_t l, r;
// i2s.read16(&l, &r);
// i2s.write16(l, r);
// }
}
2025-05-27 13:13:29 +02:00
int active = 0;
int active_led_ring = 0;
uint32_t lastTime = 0;
int32_t position = 0;
void loop() {
ui_pixels.clear(); // Set all pixel colors to 'off'
edge_pixels.clear(); // Set all pixel colors to 'off'
2025-05-27 13:13:29 +02:00
position = ENC.getCumulativePosition();
sd_card_detected = !digitalRead(21);
if(sd_card_detected) edge_pixels.setPixelColor(8, edge_pixels.Color(0,25,0));
if(!sd_card_detected) edge_pixels.setPixelColor(8, edge_pixels.Color(25,0,0));
// EDGE LEDs
for (int i = 0; i < 8; i++) {
edge_pixels.setPixelColor(i, edge_pixels.Color(config.edge_color_r, config.edge_color_g, config.edge_color_b));
}
// LED Ring leeren
2025-05-27 13:13:29 +02:00
for (int i = 0; i < 48; i++) {
ui_pixels.setPixelColor(i + 3, ui_pixels.Color(0, 0, 0));
2025-05-27 13:13:29 +02:00
}
// flag = true when a button is pushed
2025-05-27 13:13:29 +02:00
if (flag) {
int val = TCA.read16();
for(int i = 0; i < 16; i++){
buttons[i] = ~(val >> i) & 0x01;
}
// Make vibration
2025-05-27 13:13:29 +02:00
if (HAPTIC) {
digitalWrite(6, HIGH);
delay(50);
digitalWrite(6, LOW);
2025-05-27 13:13:29 +02:00
}
// Make beep
2025-05-27 13:13:29 +02:00
if (AURAL) {
digitalWrite(7, HIGH);
2025-05-27 13:13:29 +02:00
click = true;
}
// Flash led ring
2025-05-27 13:13:29 +02:00
for (int i = 0; i < 48; i++) {
ui_pixels.setPixelColor(i + 3, ui_pixels.Color(0, 15, 0));
2025-05-27 13:13:29 +02:00
}
// Switch through LED matrix
2025-05-27 13:13:29 +02:00
if(buttons[RIGHT]) active++;
if(buttons[LEFT]) active--;
if(active == 13) active = 0;
if(active == -1) active = 12;
flag = false;
2025-05-27 13:13:29 +02:00
}
// Rotary button LEDs
ui_pixels.setPixelColor(0, ui_pixels.Color(0, 0, 15));
ui_pixels.setPixelColor(1, ui_pixels.Color(0, 0, 15));
ui_pixels.setPixelColor(2, ui_pixels.Color(0, 0, 15));
2025-05-27 13:13:29 +02:00
// empty LED matrix
2025-05-27 13:13:29 +02:00
for (int i = 0; i < 13; i++) {
ui_pixels.setPixelColor(i + 3 + 48 + 4, ui_pixels.Color(0, 0, 0));
2025-05-27 13:13:29 +02:00
}
// set active LED matrix LED
ui_pixels.setPixelColor(active + 3 + 48 + 4, ui_pixels.Color(255, 255, 255));
2025-05-27 13:13:29 +02:00
if(position < 0) position += 4096;
active_led_ring = (position / 32) % 48;
// set active LED ring LED
ui_pixels.setPixelColor(active_led_ring + 3, ui_pixels.Color(255, 255, 255));
2025-05-27 13:13:29 +02:00
ui_pixels.show();
edge_pixels.show();
2025-05-27 13:13:29 +02:00
delay(1); // wait 1ms
2025-05-27 10:28:11 +02:00
}