12 Commits

4 changed files with 429 additions and 310 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
build

45
README.md Normal file
View File

@ -0,0 +1,45 @@
# Soundcube Firmware
## Install new firmware
1. Go to [Releases](https://code.w4d.dev/W4D/soundcube-firmware/releases)
2. Download latest `soundcube-firmware.ino.uf2` file
3. On your Soundcube Board: Press and hold the outer most white button as seen from the USB-C socket
4. While holding the button press the inner most white button once
- the board will go into bootloader mode and appear as USB thumb drive
6. Copy the `soundcube-firmware.ino.uf2` file to the thumb drive and wait for the board to restart
## config.txt
Json formatted config file. For now only edge led color working.
```
{
"edge": {
"color":{
"r":50,
"g":0,
"b":0
}
}
}
```
## click.wav (not played correctly)
Put a file called `click.wav` (not longer than a few seconds) in the root of the SD card and it will play when you press a button.
## Code something yourself
1. Download [Arduino IDE](https://www.arduino.cc/en/software/)
2. Install and open "Preferences"
3. Follow these [instructions](https://github.com/earlephilhower/arduino-pico?tab=readme-ov-file#installation)
- No need to change checkboxes, only paste the URL and hit OK.
4. Install **Raspberry Pi Pico** boards ([HowTo install boards](https://support.arduino.cc/hc/en-us/articles/360016119519-Add-boards-to-Arduino-IDE))
5. Install libraries ([HowTo install libraries](https://docs.arduino.cc/software/ide-v2/tutorials/ide-v2-installing-a-library/)): Adafruit Neopixel, AS5600 (Rob Tillaart), TCA9555 (Rob Tillaart)
6. Choose **Generic RP2350** as board and the correct port (Mac: /dev/cu.usbmodemXXXX, PC: COMXX Serial Port)
7. Go to **Tools -> Flash Size** and choose **16MB (no FS)**
8. Make your changes in the code
9. Hit Upload Button (Arduino IDE compiles and uploads it automatically)

View File

@ -1,11 +1,16 @@
#include <ArduinoJson.h>
#include <ArduinoJson.hpp>
#include <TCA9555.h> #include <TCA9555.h>
#include <AS5600.h> #include <AS5600.h>
#include <Adafruit_NeoPixel.h> #include <FastLED.h>
#include <PWMAudio.h> #include <PWMAudio.h>
#include <I2S.h> #include <I2S.h>
#include <SPI.h>
#include <SD.h>
#define HAPTIC 0 #define HAPTIC 1
#define AURAL 0 #define AURAL 1
#define UI_SAMPLERATE 22050 #define UI_SAMPLERATE 22050
I2S i2s(INPUT_PULLUP); I2S i2s(INPUT_PULLUP);
@ -17,8 +22,8 @@ PWMAudio ui_snd(8);
enum BUTTON {CVINL, CVINR, INL, INR, OUTR, OUTL, CVOUTR, CVOUTL, RIGHT, LEFT, SELECT, DEBUG1, DEBUG2, DEBUG3}; enum BUTTON {CVINL, CVINR, INL, INR, OUTR, OUTL, CVOUTR, CVOUTL, RIGHT, LEFT, SELECT, DEBUG1, DEBUG2, DEBUG3};
Adafruit_NeoPixel edge_pixels(8, 4, NEO_GRB + NEO_KHZ800); CRGB ui_leds[74];
Adafruit_NeoPixel ui_pixels(72, 5, NEO_GRB + NEO_KHZ800); CRGB edge_leds[11];
volatile bool flag = false; volatile bool flag = false;
volatile bool click = false; volatile bool click = false;
@ -29,16 +34,48 @@ int counter = 0;
bool buttons[16] = {false}; bool buttons[16] = {false};
int16_t beep[UI_SAMPLERATE]; 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();
}
void pwm_audio_callback() { void pwm_audio_callback() {
while (ui_snd.availableForWrite()) { while (ui_snd.availableForWrite()) {
if(click) { if(click) {
ui_snd.write(beep[counter]); ui_snd.write(beep[counter]);
} else {
ui_snd.write(0);
}
counter++; counter++;
if(counter == UI_SAMPLERATE) counter = 0; if(counter == beep_length) {
counter = 0;
click = false;
}
} else {
digitalWrite(7, LOW);
ui_snd.write(0);
counter = 0;
}
} }
} }
@ -47,14 +84,47 @@ void tca_irq() {
} }
void setup() { void setup() {
Serial.begin();
delay(2000);
FastLED.addLeds<NEOPIXEL, 4>(edge_leds, 11);
FastLED.addLeds<NEOPIXEL, 5>(ui_leds, 74);
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++){ for(int i = 0; i < UI_SAMPLERATE; i++){
float sine_pos = (2.0f * M_PI * 8000.0f * (float)i) / (float)UI_SAMPLERATE; float sine_pos = (2.0f * M_PI * 8000.0f * (float)i) / (float)UI_SAMPLERATE;
beep[i] = (int16_t)(sin(sine_pos) * 8000.0f); beep[i] = (int16_t)(sin(sine_pos) * 8000.0f);
} }
Serial.println("Synthesized 8kHz sine into beep array.");
Serial.begin(); beep_length = UI_SAMPLERATE;
delay(2000); }
Serial.println("INIT WIRE"); Serial.println("INIT WIRE");
Wire1.setSDA(2); Wire1.setSDA(2);
@ -74,7 +144,8 @@ void setup() {
Serial.println("SUCCESS"); Serial.println("SUCCESS");
// Rotary Encoder // Rotary Encoder
ENC.begin(4); // set direction pin. ENC.begin(); // set direction pin.
ENC.setDirection(AS5600_COUNTERCLOCK_WISE);
pinMode(6, OUTPUT); // Vibration Motor pinMode(6, OUTPUT); // Vibration Motor
pinMode(7, OUTPUT); // UI Amp Enable pinMode(7, OUTPUT); // UI Amp Enable
@ -84,8 +155,9 @@ void setup() {
digitalWrite(7, LOW); digitalWrite(7, LOW);
edge_pixels.begin(); // INITIALIZE NeoPixel strip object (REQUIRED) digitalWrite(6, HIGH);
ui_pixels.begin(); // INITIALIZE NeoPixel strip object (REQUIRED) delay(50);
digitalWrite(6, LOW);
// i2s.setDOUT(14); // i2s.setDOUT(14);
// i2s.setDIN(15); // i2s.setDIN(15);
@ -103,84 +175,85 @@ void setup() {
// i2s.write16(l, r); // i2s.write16(l, r);
// } // }
} }
int active = 0; int active = 0;
int active_led_ring = 0;
uint32_t lastTime = 0; uint32_t lastTime = 0;
int32_t position = 0; int32_t position = 0;
void loop() { void loop() {
edge_pixels.clear(); // Set all pixel colors to 'off'
ui_pixels.clear(); // Set all pixel colors to 'off'
position = ENC.getCumulativePosition(); position = ENC.getCumulativePosition();
// if (millis() - lastTime >= 100) sd_card_detected = !digitalRead(21);
// { if(sd_card_detected) edge_leds[8] = CRGB(0,25,0);
// lastTime = millis(); if(!sd_card_detected) edge_leds[8] = CRGB(25,0,0);
// Serial.print(ENC.getCumulativePosition());
// Serial.print("\t");
// Serial.println(ENC.getRevolutions());
// }
for (int i = 0; i < 48; i++) { // EDGE LEDs
ui_pixels.setPixelColor(i + 3, edge_pixels.Color(0, 0, 0)); for (int i = 0; i < 8; i++) {
edge_leds[i] = CRGB(config.edge_color_r, config.edge_color_g, config.edge_color_b);
} }
// LED Ring leeren
for (int i = 0; i < 48; i++) {
ui_leds[i + 3] = CRGB(0, 0, 0);
}
// flag = true when a button is pushed
if (flag) { if (flag) {
int val = TCA.read16(); int val = TCA.read16();
Serial.println(val, BIN);
for(int i = 0; i < 16; i++){ for(int i = 0; i < 16; i++){
buttons[i] = ~(val >> i) & 0x01; buttons[i] = ~(val >> i) & 0x01;
Serial.print(buttons[i]);
Serial.print(" ");
} }
Serial.println();
flag = false; // Make vibration
if (HAPTIC) { if (HAPTIC) {
digitalWrite(6, HIGH); digitalWrite(6, HIGH);
delay(50);
digitalWrite(6, LOW);
} }
// Make beep
if (AURAL) { if (AURAL) {
digitalWrite(7, HIGH); digitalWrite(7, HIGH);
click = true; click = true;
delay(50);
click = false;
digitalWrite(7, LOW);
}
if(HAPTIC){
if(!AURAL) delay(50);
digitalWrite(6, LOW);
} }
// Flash led ring
for (int i = 0; i < 48; i++) { for (int i = 0; i < 48; i++) {
ui_pixels.setPixelColor(i + 3, edge_pixels.Color(0, 15, 0)); ui_leds[i + 3] = CRGB(0, 15, 0);
} }
// Switch through LED matrix
if(buttons[RIGHT]) active++; if(buttons[RIGHT]) active++;
if(buttons[LEFT]) active--; if(buttons[LEFT]) active--;
if(active == 13) active = 0; if(active == 13) active = 0;
if(active == -1) active = 12; if(active == -1) active = 12;
}
// 11111110 11111111 button right
// EDGE LEDs flag = false;
for (int i = 0; i < 8; i++) {
edge_pixels.setPixelColor(i, edge_pixels.Color(15, 15, 15));
} }
// Rotary button LEDs // Rotary button LEDs
ui_pixels.setPixelColor(0, edge_pixels.Color(0, 0, 15)); ui_leds[0] = CRGB(0, 0, 15);
ui_pixels.setPixelColor(1, edge_pixels.Color(0, 0, 15)); ui_leds[1] = CRGB(0, 0, 15);
ui_pixels.setPixelColor(2, edge_pixels.Color(0, 0, 15)); ui_leds[2] = CRGB(0, 0, 15);
// empty LED matrix
for (int i = 0; i < 13; i++) { for (int i = 0; i < 13; i++) {
ui_pixels.setPixelColor(i + 3 + 48 + 4, edge_pixels.Color(0, 0, 0)); ui_leds[i + 3 + 48 + 4] = CRGB(0, 0, 0);
} }
ui_pixels.setPixelColor(active + 3 + 48 + 4, edge_pixels.Color(255, 255, 255)); // set active LED matrix LED
ui_leds[active + 3 + 48 + 4] = CRGB(255, 255, 255);
// put your main code here, to run repeatedly: if(position < 0) position += 4096;
active_led_ring = (position / 32) % 48;
edge_pixels.show(); // Set all pixel colors to 'off' // set active LED ring LED
ui_pixels.show(); // Set all pixel colors to 'off' ui_leds[active_led_ring + 3] = CRGB(255, 255, 255);
delay(1); FastLED.show();
delay(1); // wait 1ms
} }