Seedbox v0.1 + firmware, generic kit/synth, ROOT/SCALE pages
Some checks failed
check / check (push) Has been cancelled
Some checks failed
check / check (push) Has been cancelled
First commit of the repo. Contents beyond the v0.1 simulator and layout: - Full Pico 2 W firmware (firmware/app, drivers, engine port, host test). - BANK page and 404 mode A/B removed; learned pads play exactly what was learned. - 404/MiniFreak renamed to KIT/SYNTH throughout (jacks, code, sim, docs, layout labels; wiring rebuilt). - ROLL returns to page select after a re-roll; a freshly learned track turns on. - LCD contrast 66. - ROOT and SCALE pages: 21 scales, AUTO = the seed picks; global and sticky. Intended music change: the scale table grew from 8 to 21 and AUTO picks from all of them, so every seed's scale changed. Vectors regenerated with npm run vectors, plus new key-override cases. Flash record is now version 2. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
45
firmware/drivers/encoder.c
Normal file
45
firmware/drivers/encoder.c
Normal file
@@ -0,0 +1,45 @@
|
||||
#include "encoder.h"
|
||||
#include "hardware/gpio.h"
|
||||
|
||||
#define PIN_A 10
|
||||
#define PIN_B 11
|
||||
#define REST 3 /* A = B = 1: both contacts open at a detent */
|
||||
#define DIR -1 /* as wired on the bench, the raw count runs down clockwise */
|
||||
|
||||
static volatile int32_t steps, clicks;
|
||||
static volatile int8_t acc; /* edges since the last rest */
|
||||
static volatile uint8_t prev;
|
||||
|
||||
/* index = previous AB << 2 | current AB; invalid (bounce) transitions count 0 */
|
||||
static const int8_t TABLE[16] = { 0,-1, 1, 0, 1, 0, 0,-1, -1, 0, 0, 1, 0, 1,-1, 0 };
|
||||
|
||||
static uint8_t read_ab(void){ return (uint8_t)((gpio_get(PIN_A) << 1) | gpio_get(PIN_B)); }
|
||||
|
||||
static void on_edge(uint gpio, uint32_t events){
|
||||
uint8_t cur;
|
||||
int8_t d;
|
||||
(void)events;
|
||||
if(gpio != PIN_A && gpio != PIN_B) return;
|
||||
cur = read_ab();
|
||||
d = TABLE[(prev << 2) | cur];
|
||||
steps += d; acc = (int8_t)(acc + d);
|
||||
prev = cur;
|
||||
if(cur == REST){
|
||||
/* a full click is 4 edges; accept 2 or more in case bounce hid some, ignore wiggles of 1 */
|
||||
if(acc >= 2) clicks += DIR;
|
||||
else if(acc <= -2) clicks -= DIR;
|
||||
acc = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void encoder_init(void){
|
||||
gpio_init(PIN_A); gpio_set_dir(PIN_A, GPIO_IN); gpio_pull_up(PIN_A);
|
||||
gpio_init(PIN_B); gpio_set_dir(PIN_B, GPIO_IN); gpio_pull_up(PIN_B);
|
||||
prev = read_ab();
|
||||
gpio_set_irq_enabled_with_callback(PIN_A, GPIO_IRQ_EDGE_RISE | GPIO_IRQ_EDGE_FALL, true, on_edge);
|
||||
gpio_set_irq_enabled(PIN_B, GPIO_IRQ_EDGE_RISE | GPIO_IRQ_EDGE_FALL, true);
|
||||
}
|
||||
|
||||
int32_t encoder_count(void){ return clicks; }
|
||||
int32_t encoder_steps(void){ return DIR * steps; }
|
||||
int encoder_ab(void){ return read_ab(); }
|
||||
15
firmware/drivers/encoder.h
Normal file
15
firmware/drivers/encoder.h
Normal file
@@ -0,0 +1,15 @@
|
||||
/* Quadrature encoder on GP10 (A) / GP11 (B), pull-ups, decoded in the GPIO interrupt with a state table.
|
||||
The bench encoder gives 4 edges per click and rests with both contacts open (A = B = 1). As wired, the raw
|
||||
count runs down when turned clockwise, so the driver flips it: + = clockwise.
|
||||
A click counts when the shaft comes back to rest, so jitter around a detent doesn't count. */
|
||||
#ifndef SEEDBOX_ENCODER_H
|
||||
#define SEEDBOX_ENCODER_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
void encoder_init(void);
|
||||
int32_t encoder_count(void); /* clicks, + = clockwise */
|
||||
int32_t encoder_steps(void); /* raw edges, for the bench */
|
||||
int encoder_ab(void); /* current A << 1 | B */
|
||||
|
||||
#endif
|
||||
50
firmware/drivers/inputs.c
Normal file
50
firmware/drivers/inputs.c
Normal file
@@ -0,0 +1,50 @@
|
||||
#include "inputs.h"
|
||||
#include "hardware/gpio.h"
|
||||
#include "pico/time.h"
|
||||
|
||||
static const uint8_t ROWS[3] = {2, 3, 5};
|
||||
static const uint8_t COLS[4] = {6, 7, 8, 9};
|
||||
static const uint8_t SWITCHES[3] = {13, 14, 12}; /* IN_SEED, IN_RUN, IN_PUSH */
|
||||
#define DEBOUNCE_MS 10 /* a change must hold this long to count */
|
||||
#define LOCKOUT_MS 40 /* then the input ignores further changes this long (worn switches chatter) */
|
||||
|
||||
/* (1,1) KICK (1,2) KICK 2 (1,3) SNARE (1,4) none; (2,1) HAT (2,2) HAT 16 (2,3) PERC (2,4) GHOST;
|
||||
(3,1) BASS (3,2) MEL A (3,3) MEL B (3,4) PAD */
|
||||
const int8_t INPUT_KEY_TRACK[12] = { 0, 1, 2, -1, 4, 5, 6, 3, 7, 8, 9, 10 };
|
||||
|
||||
static uint32_t state;
|
||||
static uint8_t count[IN_COUNT], lockout[IN_COUNT];
|
||||
|
||||
static void input_pullup(uint pin){ gpio_init(pin); gpio_set_dir(pin, GPIO_IN); gpio_pull_up(pin); }
|
||||
|
||||
void inputs_init(void){
|
||||
int i;
|
||||
for(i = 0; i < 3; i++) input_pullup(ROWS[i]);
|
||||
for(i = 0; i < 4; i++){ input_pullup(COLS[i]); gpio_put(COLS[i], 0); } /* idle: Hi-Z with pull-up (E9) */
|
||||
for(i = 0; i < 3; i++) input_pullup(SWITCHES[i]);
|
||||
}
|
||||
|
||||
static uint32_t raw_read(void){
|
||||
uint32_t raw = 0;
|
||||
int r, c;
|
||||
for(c = 0; c < 4; c++){
|
||||
gpio_set_dir(COLS[c], GPIO_OUT); /* output latch is already 0: drive low */
|
||||
busy_wait_us(5); /* settle */
|
||||
for(r = 0; r < 3; r++) if(!gpio_get(ROWS[r])) raw |= 1u << (r * 4 + c);
|
||||
gpio_set_dir(COLS[c], GPIO_IN); /* back to Hi-Z, pull-up holds it high */
|
||||
}
|
||||
for(r = 0; r < 3; r++) if(!gpio_get(SWITCHES[r])) raw |= 1u << (IN_SEED + r);
|
||||
return raw;
|
||||
}
|
||||
|
||||
uint32_t inputs_scan(void){
|
||||
uint32_t raw = raw_read();
|
||||
int i;
|
||||
for(i = 0; i < IN_COUNT; i++){
|
||||
uint32_t bit = 1u << i;
|
||||
if(lockout[i]){ lockout[i]--; count[i] = 0; continue; }
|
||||
if((raw & bit) != (state & bit)){ if(++count[i] >= DEBOUNCE_MS){ state ^= bit; count[i] = 0; lockout[i] = LOCKOUT_MS; } }
|
||||
else count[i] = 0;
|
||||
}
|
||||
return state;
|
||||
}
|
||||
17
firmware/drivers/inputs.h
Normal file
17
firmware/drivers/inputs.h
Normal file
@@ -0,0 +1,17 @@
|
||||
/* Button matrix (rows GP2 GP3 GP5, columns GP6–GP9), SEED (GP13), RUN (GP14), encoder push (GP12).
|
||||
All active-low with pull-ups (RP2350 erratum E9). Call inputs_scan() about once per millisecond. */
|
||||
#ifndef SEEDBOX_INPUTS_H
|
||||
#define SEEDBOX_INPUTS_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* bit index: matrix key = row * 4 + col (row 0..2, col 0..3), then the switches */
|
||||
enum { IN_MATRIX = 12, IN_SEED = 12, IN_RUN = 13, IN_PUSH = 14, IN_COUNT = 15 };
|
||||
|
||||
/* matrix key -> track index (engine order), -1 = no button. Same as PANEL in app.js. */
|
||||
extern const int8_t INPUT_KEY_TRACK[12];
|
||||
|
||||
void inputs_init(void);
|
||||
uint32_t inputs_scan(void); /* returns the debounced state, bit set = pressed / RUN on */
|
||||
|
||||
#endif
|
||||
37
firmware/drivers/midi_uart.c
Normal file
37
firmware/drivers/midi_uart.c
Normal file
@@ -0,0 +1,37 @@
|
||||
#include "midi_uart.h"
|
||||
#include "hardware/gpio.h"
|
||||
#include "hardware/uart.h"
|
||||
|
||||
#define PIN_OUT_KIT 0 /* UART0 TX */
|
||||
#define PIN_IN 1 /* UART0 RX, 6N137 output with a 1 kΩ pull-up on the board */
|
||||
#define PIN_OUT_SYNTH 4 /* UART1 TX */
|
||||
#define MIDI_BAUD 31250
|
||||
|
||||
static uart_inst_t *const PORTS[2] = { uart0, uart1 };
|
||||
|
||||
void midi_init(void){
|
||||
uart_init(uart0, MIDI_BAUD);
|
||||
uart_init(uart1, MIDI_BAUD);
|
||||
uart_set_format(uart0, 8, 1, UART_PARITY_NONE);
|
||||
uart_set_format(uart1, 8, 1, UART_PARITY_NONE);
|
||||
gpio_set_function(PIN_OUT_KIT, GPIO_FUNC_UART);
|
||||
gpio_set_function(PIN_IN, GPIO_FUNC_UART);
|
||||
gpio_set_function(PIN_OUT_SYNTH, GPIO_FUNC_UART); /* GP5 (UART1 RX) stays a matrix row */
|
||||
gpio_pull_up(PIN_IN);
|
||||
/* each MIDI current loop draws 6–8 mA, above the 4 mA default */
|
||||
gpio_set_drive_strength(PIN_OUT_KIT, GPIO_DRIVE_STRENGTH_12MA);
|
||||
gpio_set_drive_strength(PIN_OUT_SYNTH, GPIO_DRIVE_STRENGTH_12MA);
|
||||
}
|
||||
|
||||
void midi_send(int port, const uint8_t *bytes, int n){ uart_write_blocking(PORTS[port], bytes, (size_t)n); }
|
||||
|
||||
void midi_note_on(int port, int ch, int note, int vel){
|
||||
uint8_t m[3] = { (uint8_t)(0x90 | (ch & 15)), (uint8_t)(note & 127), (uint8_t)(vel & 127) };
|
||||
midi_send(port, m, 3);
|
||||
}
|
||||
void midi_note_off(int port, int ch, int note){
|
||||
uint8_t m[3] = { (uint8_t)(0x80 | (ch & 15)), (uint8_t)(note & 127), 0 };
|
||||
midi_send(port, m, 3);
|
||||
}
|
||||
|
||||
int midi_in_read(void){ return uart_is_readable(uart0) ? uart_getc(uart0) : -1; }
|
||||
15
firmware/drivers/midi_uart.h
Normal file
15
firmware/drivers/midi_uart.h
Normal file
@@ -0,0 +1,15 @@
|
||||
/* MIDI over the two UARTs: OUT KIT = UART0 TX (GP0), OUT SYNTH = UART1 TX (GP4), IN = UART0 RX (GP1). 31 250 baud, 8N1. */
|
||||
#ifndef SEEDBOX_MIDI_UART_H
|
||||
#define SEEDBOX_MIDI_UART_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
enum { MIDI_KIT = 0, MIDI_SYNTH = 1 };
|
||||
|
||||
void midi_init(void);
|
||||
void midi_send(int port, const uint8_t *bytes, int n); /* blocking */
|
||||
void midi_note_on(int port, int ch, int note, int vel); /* ch 0..15 */
|
||||
void midi_note_off(int port, int ch, int note);
|
||||
int midi_in_read(void); /* next byte from MIDI IN, or -1 */
|
||||
|
||||
#endif
|
||||
91
firmware/drivers/pcd8544.c
Normal file
91
firmware/drivers/pcd8544.c
Normal file
@@ -0,0 +1,91 @@
|
||||
#include "pcd8544.h"
|
||||
#include "hardware/gpio.h"
|
||||
#include "hardware/spi.h"
|
||||
#include "pico/time.h"
|
||||
#include <string.h>
|
||||
|
||||
#define PIN_CE 17
|
||||
#define PIN_SCK 18
|
||||
#define PIN_MOSI 19
|
||||
#define PIN_DC 20
|
||||
#define PIN_RST 21
|
||||
#define LCD_SPI spi0
|
||||
#define LCD_HZ 4000000
|
||||
|
||||
static uint8_t fb[LCD_W * LCD_H / 8]; /* PCD8544 layout: 6 banks of 8 rows, LSB at the top */
|
||||
|
||||
static void write(int data, const uint8_t *b, size_t n){
|
||||
gpio_put(PIN_DC, data);
|
||||
gpio_put(PIN_CE, 0);
|
||||
spi_write_blocking(LCD_SPI, b, n); /* returns once the bytes are shifted out */
|
||||
gpio_put(PIN_CE, 1);
|
||||
}
|
||||
static void cmd(uint8_t c){ write(0, &c, 1); }
|
||||
|
||||
void lcd_set_vop(int vop){
|
||||
cmd(0x21); /* extended instruction set */
|
||||
cmd((uint8_t)(0x80 | (vop & 0x7F)));
|
||||
cmd(0x20);
|
||||
}
|
||||
|
||||
void lcd_init(int vop){
|
||||
spi_init(LCD_SPI, LCD_HZ);
|
||||
spi_set_format(LCD_SPI, 8, SPI_CPOL_0, SPI_CPHA_0, SPI_MSB_FIRST);
|
||||
gpio_set_function(PIN_SCK, GPIO_FUNC_SPI);
|
||||
gpio_set_function(PIN_MOSI, GPIO_FUNC_SPI);
|
||||
gpio_init(PIN_CE); gpio_set_dir(PIN_CE, GPIO_OUT); gpio_put(PIN_CE, 1);
|
||||
gpio_init(PIN_DC); gpio_set_dir(PIN_DC, GPIO_OUT); gpio_put(PIN_DC, 0);
|
||||
gpio_init(PIN_RST); gpio_set_dir(PIN_RST, GPIO_OUT);
|
||||
gpio_put(PIN_RST, 0); sleep_ms(10); gpio_put(PIN_RST, 1); sleep_ms(10);
|
||||
cmd(0x21); /* extended */
|
||||
cmd((uint8_t)(0x80 | (vop & 0x7F)));/* Vop */
|
||||
cmd(0x04); /* temperature coefficient 0 */
|
||||
cmd(0x13); /* bias 1:48 */
|
||||
cmd(0x20); /* basic, horizontal addressing */
|
||||
cmd(0x0C); /* normal display */
|
||||
lcd_clear(); lcd_flush();
|
||||
}
|
||||
|
||||
void lcd_clear(void){ memset(fb, 0, sizeof fb); }
|
||||
|
||||
void lcd_px(int x, int y, int on){
|
||||
if(x < 0 || x >= LCD_W || y < 0 || y >= LCD_H) return;
|
||||
if(on) fb[(y >> 3) * LCD_W + x] |= (uint8_t)(1u << (y & 7));
|
||||
else fb[(y >> 3) * LCD_W + x] &= (uint8_t)~(1u << (y & 7));
|
||||
}
|
||||
void lcd_fill(int x, int y, int w, int h, int on){ int i, j; for(j = y; j < y + h; j++) for(i = x; i < x + w; i++) lcd_px(i, j, on); }
|
||||
|
||||
/* The 5 × 7 font from sim/app.js (FONT): five column bytes per glyph, bit 0 at the top. */
|
||||
typedef struct { char c; uint8_t col[5]; } glyph_t;
|
||||
static const glyph_t FONT[] = {
|
||||
{' ',{0,0,0,0,0}},{'+',{8,8,62,8,8}},{'-',{8,8,8,8,8}},{'.',{0,96,96,0,0}},{'/',{32,16,8,4,2}},{':',{0,54,54,0,0}},
|
||||
{'#',{20,127,20,127,20}},{'*',{20,8,62,8,20}},{'>',{0,65,34,20,8}},{'?',{2,1,81,9,6}},{'!',{0,0,95,0,0}},
|
||||
{'0',{62,81,73,69,62}},{'1',{0,66,127,64,0}},{'2',{66,97,81,73,70}},{'3',{33,65,69,75,49}},{'4',{24,20,18,127,16}},
|
||||
{'5',{39,69,69,69,57}},{'6',{60,74,73,73,48}},{'7',{1,113,9,5,3}},{'8',{54,73,73,73,54}},{'9',{6,73,73,41,30}},
|
||||
{'A',{126,17,17,17,126}},{'B',{127,73,73,73,54}},{'C',{62,65,65,65,34}},{'D',{127,65,65,34,28}},{'E',{127,73,73,73,65}},
|
||||
{'F',{127,9,9,9,1}},{'G',{62,65,73,73,122}},{'H',{127,8,8,8,127}},{'I',{0,65,127,65,0}},{'J',{32,64,65,63,1}},
|
||||
{'K',{127,8,20,34,65}},{'L',{127,64,64,64,64}},{'M',{127,2,12,2,127}},{'N',{127,4,8,16,127}},{'O',{62,65,65,65,62}},
|
||||
{'P',{127,9,9,9,6}},{'Q',{62,65,81,33,94}},{'R',{127,9,25,41,70}},{'S',{70,73,73,73,49}},{'T',{1,1,127,1,1}},
|
||||
{'U',{63,64,64,64,63}},{'V',{31,32,64,32,31}},{'W',{63,64,56,64,63}},{'X',{99,20,8,20,99}},{'Y',{7,8,112,8,7}},
|
||||
{'Z',{97,81,73,69,67}}
|
||||
};
|
||||
static const glyph_t *glyph(char c){
|
||||
size_t i;
|
||||
if(c >= 'a' && c <= 'z') c = (char)(c - 32);
|
||||
for(i = 0; i < sizeof FONT / sizeof FONT[0]; i++) if(FONT[i].c == c) return &FONT[i];
|
||||
return glyph('?');
|
||||
}
|
||||
void lcd_text_v(int x, int y, const char *s, int on){
|
||||
for(; *s; s++, x += 6){
|
||||
const glyph_t *g = glyph(*s);
|
||||
int col, row;
|
||||
for(col = 0; col < 5; col++) for(row = 0; row < 7; row++) if((g->col[col] >> row) & 1) lcd_px(x + col, y + row, on);
|
||||
}
|
||||
}
|
||||
void lcd_text(int x, int y, const char *s){ lcd_text_v(x, y, s, 1); }
|
||||
void lcd_box(int x, int y, int w, int h){ lcd_fill(x, y, w, 1, 1); lcd_fill(x, y + h - 1, w, 1, 1); lcd_fill(x, y, 1, h, 1); lcd_fill(x + w - 1, y, 1, h, 1); }
|
||||
|
||||
void lcd_flush(void){
|
||||
cmd(0x80); cmd(0x40); /* X = 0, Y = 0 */
|
||||
write(1, fb, sizeof fb);
|
||||
}
|
||||
19
firmware/drivers/pcd8544.h
Normal file
19
firmware/drivers/pcd8544.h
Normal file
@@ -0,0 +1,19 @@
|
||||
/* Nokia 5110 (PCD8544) on SPI0: SCK GP18, MOSI GP19, CE GP17, D/C GP20, RST GP21. 84 × 48, 504-byte framebuffer. */
|
||||
#ifndef SEEDBOX_PCD8544_H
|
||||
#define SEEDBOX_PCD8544_H
|
||||
|
||||
#define LCD_W 84
|
||||
#define LCD_H 48
|
||||
#define LCD_VOP_DEFAULT 66 /* contrast; varies between modules, so it's a setting. 66 suits the bench module. */
|
||||
|
||||
void lcd_init(int vop);
|
||||
void lcd_set_vop(int vop); /* 0..127 */
|
||||
void lcd_clear(void);
|
||||
void lcd_px(int x, int y, int on);
|
||||
void lcd_fill(int x, int y, int w, int h, int on);
|
||||
void lcd_box(int x, int y, int w, int h); /* 1 px outline */
|
||||
void lcd_text(int x, int y, const char *s); /* 5 × 7 font from app.js, 6 px per character, upper case */
|
||||
void lcd_text_v(int x, int y, const char *s, int on); /* draws only the glyph pixels, in on (0 = inverse) */
|
||||
void lcd_flush(void);
|
||||
|
||||
#endif
|
||||
31
firmware/drivers/pots.c
Normal file
31
firmware/drivers/pots.c
Normal file
@@ -0,0 +1,31 @@
|
||||
#include "pots.h"
|
||||
#include "hardware/adc.h"
|
||||
|
||||
#define SAMPLES 16
|
||||
#define HYSTERESIS 24 /* in 12-bit counts: 0.75 of a 7-bit step past the step's centre */
|
||||
|
||||
static int value[2], raw[2];
|
||||
|
||||
void pots_init(void){
|
||||
adc_init();
|
||||
adc_gpio_init(26); /* also turns off the digital input and pulls */
|
||||
adc_gpio_init(27);
|
||||
pots_update();
|
||||
value[0] = raw[0] >> 5; value[1] = raw[1] >> 5;
|
||||
}
|
||||
|
||||
void pots_update(void){
|
||||
int i, k;
|
||||
for(i = 0; i < 2; i++){
|
||||
int sum = 0, centre, d;
|
||||
adc_select_input((uint)i);
|
||||
for(k = 0; k < SAMPLES; k++) sum += adc_read();
|
||||
raw[i] = sum / SAMPLES;
|
||||
centre = value[i] * 32 + 16;
|
||||
d = raw[i] - centre;
|
||||
if(d > 16 + HYSTERESIS || d < -16 - HYSTERESIS) value[i] = raw[i] >> 5;
|
||||
}
|
||||
}
|
||||
|
||||
int pot_value(int pot){ return value[pot]; }
|
||||
int pot_raw(int pot){ return raw[pot]; }
|
||||
12
firmware/drivers/pots.h
Normal file
12
firmware/drivers/pots.h
Normal file
@@ -0,0 +1,12 @@
|
||||
/* ENERGY on ADC0 (GP26), DRIFT on ADC1 (GP27). Averaged, then 7-bit with hysteresis. */
|
||||
#ifndef SEEDBOX_POTS_H
|
||||
#define SEEDBOX_POTS_H
|
||||
|
||||
enum { POT_ENERGY = 0, POT_DRIFT = 1 };
|
||||
|
||||
void pots_init(void);
|
||||
void pots_update(void); /* call every few ms */
|
||||
int pot_value(int pot); /* 0..127 */
|
||||
int pot_raw(int pot); /* last averaged 12-bit reading, for the bench */
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user