0
mirror of https://github.com/torvalds/GuitarPedal.git synced 2026-08-18 13:13:35 +00:00
Files
torvalds-GuitarPedal/Firmware/hardware.h
Linus Torvalds 0c1b9c3db3 Split Software/ into the four things it actually was
'Software' was the directory everything that was not KiCad ended up in,
which stopped describing anything a while ago - Validation and the web
app are software too.  Worse, it put the shared parts inside the
firmware, where they read as the firmware's own.

They are not.  Effects/ has three consumers built from it: the firmware,
Validation's bench, and the web app's controls, all generated from the
same POT: comments by gen_effects.py.  Audio/ has two - the bench
compiles the same biquads, the same envelope followers and the same
single_sample(), which is the whole reason a measurement on a
workstation says anything about the pedal.  Neither belongs under
Firmware/, so neither is under it any more:

  Effects/    one file per effect
  Audio/      the DSP they are built from, and the audio loop
  Firmware/   the rest of what runs on the pedal, and the submodules
  WebMIDI/    the web app
  scripts/    what the build runs
  Validation/ unchanged
  Hardware/, Documentation/, Images/

CMakeLists.txt and the wrapper Makefile move to the top with them,
because the build now consumes four of those directories and generates
into a fifth.  board.local and build/ come along; MIDI_CC_MAP.md is
generated into Documentation/ rather than into the old Software/ root.

scripts/ goes with the build rather than staying under the firmware,
because six of the ten had nothing to do with the firmware: gen_effects.py
reads Effects/ and writes to three different places, pow2/log2/quarter_sine
generate Audio/'s tables, check-readme.py compares Effects/ against the
README, and server.py serves the web app.  Four of them are invoked from
Validation, which was reaching into Firmware/ for tooling - the same
burying this commit is undoing.  The four that really are about the
firmware are ELF checks the top-level build drives anyway, and a second
scripts directory would only be a second place to look.

C includes say "Audio/foo.h" and the generated map says
"Effects/bar.h", with the repository root on the include path for both
the firmware and the bench.  Spelling the directory out rather than
relying on a bare name is what keeps Audio/cycles.h shimmable: a quoted
include searches the including file's own directory first.

The submodules are renamed as well as moved.  git mv updates their paths
but leaves the section names, and 'Software/pico-sdk' surviving in
.gitmodules would be the word this commit removes, still load-bearing.
That meant the nested modules under pico-sdk too - six .git files
pointing into .git/modules/Software - which is why 'git submodule update
--init --recursive' is worth running once after pulling this.

Verified rather than assumed: a clean configure and build, make check
(failing only on the missing-eeprom case it already failed on),
check-effects, all four analysis pages reproducing every series and
drawing every chart, and a flash to the board that still measures a
routed reverb where it did before.

One latent bug fell out of it.  bench/coeff declared only quarter_sine.h
of the three generated math tables, and Audio/util.h includes pow2.h and
log2.h as well - so building that target with an empty gen/ could never
have worked.  'make bench' builds bench/bench first, which generates all
three, so it stayed hidden until this rebuilt everything from nothing.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2026-08-11 13:48:26 -07:00

319 lines
11 KiB
C

#ifndef HARDWARE_H
#define HARDWARE_H
//
// Bringing the board up, and finding out which board it is.
//
// include/board.h says *which pins*. This says *how to start them*: the
// i2s state machines and their DMA, the WS2812 program, one debounce
// state machine per switch, the PWM the LEDs are dimmed with, and the
// rotary encoder's quadrature decoder.
//
// It also probes what is on the i2c bus, which is a different question
// and lives here because it is the same one: what is actually out there.
// A fixed build cannot adapt to the board it lands on and does not try -
// the answer goes out in the identity reply so that "is this the board
// this firmware was built for" can be asked of a running pedal.
//
// Ordering note, since this is one translation unit and include order is
// program order: this has to come before midi/sysex.h, because the
// identity reply reports what probe_hardware() found.
//
static void init_i2s(void)
{
uint tx_offset, rx_offset;
tx_offset = pio_add_program(pio0, &i2s_tx_program);
rx_offset = pio_add_program(pio0, &i2s_rx_program);
i2s_tx_program_init(pio0, PIO0_I2S_TX_SM, tx_offset, I2S_BCLK);
i2s_rx_program_init(pio0, PIO0_I2S_RX_SM, rx_offset, I2S_BCLK);
dma_rx = dma_claim_unused_channel(true);
dma_channel_config c_rx = dma_channel_get_default_config(dma_rx);
channel_config_set_transfer_data_size(&c_rx, DMA_SIZE_32);
channel_config_set_read_increment(&c_rx, false);
channel_config_set_write_increment(&c_rx, true);
channel_config_set_dreq(&c_rx, pio_get_dreq(pio0, PIO0_I2S_RX_SM, false));
channel_config_set_ring(&c_rx, true, 7); // write wrap at 128 bytes (32 words)
dma_tx = dma_claim_unused_channel(true);
dma_channel_config c_tx = dma_channel_get_default_config(dma_tx);
channel_config_set_transfer_data_size(&c_tx, DMA_SIZE_32);
channel_config_set_read_increment(&c_tx, true);
channel_config_set_write_increment(&c_tx, false);
channel_config_set_dreq(&c_tx, pio_get_dreq(pio0, PIO0_I2S_TX_SM, true));
channel_config_set_ring(&c_tx, false, 7); // read wrap at 128 bytes (32 words)
pio_sm_clear_fifos(pio0, PIO0_I2S_RX_SM);
pio_sm_clear_fifos(pio0, PIO0_I2S_TX_SM);
// RX and TX start at the same point, together. But TX will
// fill up the PIO buffers and move ahead, while RX will be
// waiting for the first samples to come in, so it naturally
// falls behind.
//
// And "falls behind" is the same as "is ahead" in a circular
// buffer.
dma_channel_configure(dma_rx, &c_rx, i2s_dma_buf, &pio0->rxf[PIO0_I2S_RX_SM], 0xffffffff, false);
dma_channel_configure(dma_tx, &c_tx, &pio0->txf[PIO0_I2S_TX_SM], i2s_dma_buf, 0xffffffff, false);
dma_start_channel_mask((1u << dma_rx) | (1u << dma_tx));
}
static void init_ws2812(void)
{
#ifdef WS2812_GPIO
pixels_init();
#endif
}
// Initialize a pin for input, pulled up
static void init_sw_pin(PIO pio, int pin)
{
gpio_init(pin);
gpio_set_dir(pin, false);
gpio_pull_up(pin);
pio_gpio_init(pio, pin);
}
// I have no good way to detect USB when in USB host mode.
//
// In a perfect world, I would have a GPIO that would tell
// me whether the power is provided by the 9V guitar power
// supply or the USB line, but ...
static inline bool usb_is_connected(void)
{
return tud_ready();
}
// We use PIO1 for the switches.
//
// They share the same program, just a separate state machine
// for each pin - state machine N is switch id N, see switch.h.
static void switch_irq(void)
{
PIO pio = pio1;
for (int sw = 0; sw < NR_SWITCHES; sw++) {
if (pio_sm_is_rx_fifo_empty(pio, sw))
continue;
int bit = pio_sm_get(pio, sw) ? LONGPRESS(sw) : sw;
switch_val |= 1u << bit;
}
user_interaction = 1;
}
//
// What this firmware found itself running on.
//
// Probed once at boot, and the question it answers is not "which board
// is this" - the pin map already settled that at compile time, and a
// fixed build cannot adapt to landing on the wrong one anyway. It is
// the narrower question of what is on the far end of the FFC, which the
// build genuinely does not know and must not guess.
//
// The audio-jacks board is a separate board joined by a cable, and it
// comes in two flavours: a TAC5112, which was never wired for stereo,
// and a TAC5242, which was. Either can be paired with either MCU board,
// so which one is present is not a property of the build and cannot be.
// The TAC5112 needs its control registers set up over i2c0 regardless,
// so the firmware has to find out - and having found out, it can say so.
//
// **Mono against stereo is the difference a person actually notices**,
// and it is this one. It is not the codec's doing - both parts are
// stereo-capable - it is that the older board only ever routed one
// channel.
//
// The SH1106 screen on i2c1 is the same kind of statement: it belonged
// to a design that is gone, the code for it went with it, and the part
// still answers when addressed.
//
// The eeprom used to be probed here too, and is not any more. It was
// the scene store, which now lives in the RP2354's own flash; after that
// it survived a while as a hint about which board this was, and it was
// never a good one. It sat on whichever board happened to carry it
// across a couple of revisions, so its presence identified nothing, and
// the reading was not even stable - see the issue list.
//
// What gets reported is what was *observed*. Any inference from it -
// which board this is, how old - belongs to whoever is reading rather
// than in the wire format, so that being wrong about it later costs an
// app change and not a protocol one.
//
static struct {
bool legacy_codec; // TAC5112, 0x51 - the mono audio board
bool legacy_screen; // SH1106, 0x3c - a design that is gone
} hardware;
static bool i2c_probe(i2c_inst_t *i2c, uint8_t addr)
{
uint8_t byte;
// One byte, harmless to anything that does answer, and a timeout
// rather than a hang if the bus is being held down.
return i2c_read_timeout_us(i2c, addr, &byte, 1, false, 2000) == 1;
}
static void probe_hardware(void)
{
hardware.legacy_codec = i2c_probe(TAC5112_I2C);
hardware.legacy_screen = i2c_probe(SH1106_I2C);
//
// Say what we are before USB exists, because the name is part of
// how a person tells two pedals apart and the host may already be
// attached and waiting.
//
// The board name is compile-time and the channel count is not, so
// this is where the two meet. Two static strings rather than a
// buffer: there is exactly one bit to fold in.
//
usb_set_product(hardware.legacy_codec
? PEDAL_BOARD_NAME " mono Pedal"
: PEDAL_BOARD_NAME " stereo Pedal");
//
// An early board is merely old: the TAC5112 wants a little setup,
// which it gets, and that board never routed the second channel,
// so it is mono. Worth saying rather than fixing - and now that
// the product string carries it too, this is the louder half of
// the same fact rather than the only place it appears.
//
if (hardware.legacy_codec || hardware.legacy_screen)
report_status("Early board: mono only");
}
static void init_sw_pins(void)
{
PIO pio = pio1;
uint offset = pio_add_program(pio, &debounce_program);
//
// Same PIO program for every switch, one state machine each,
// walked in switch id order so that state machine N really is
// switch N. switch_irq() relies on that and has no other way
// to know which pin a fifo entry came from.
//
for (int sw = 0; sw < NR_SWITCHES; sw++) {
init_sw_pin(pio, switch_gpio[sw]);
debounce_program_init(pio, sw, offset, switch_gpio[sw]);
}
irq_set_exclusive_handler(PIO1_IRQ_0, switch_irq);
irq_set_enabled(PIO1_IRQ_0, true);
}
#ifndef WS2812_GPIO
static void init_one_pwm_pin(int pin)
{
unsigned int slice = pwm_gpio_to_slice_num(pin);
gpio_set_function(pin, GPIO_FUNC_PWM);
pwm_set_wrap(slice, PWM_WRAP);
pwm_set_gpio_level(pin, 0);
pwm_set_enabled(slice, true);
}
#endif
static void init_pwm_pins(void)
{
#ifdef WS2812_GPIO
//
// Nothing to dim. A board with smart LEDs has no PWM one - and on
// the usb-stomp board LED_GPIO is not an LED at all, it is the
// stomp switch, shorting to ground with no series resistor. So
// this must not run there, and neither must the boot lamp in
// main(). Both are left in place for the boards that do have it.
//
return;
#else
init_one_pwm_pin(LED_GPIO);
//
// Full, not off, and it stays that way until the first UI tick.
//
// main() lights this pin as a plain GPIO before anything else runs,
// and taking it over for PWM would drop it - so the level is put
// back up here and the LED stays on across the handover. What that
// buys is a lamp that means "started, not finished yet": it comes on
// at the first instruction and goes to its real brightness when
// set_led() first runs, which is inside the main loop.
//
// So a pedal that hangs during boot sits there lit, and a pedal that
// never got as far as main() sits there dark. See the boot comment
// in main() for why that distinction is the one worth having.
//
pwm_set_gpio_level(LED_GPIO, PWM_WRAP);
#endif
}
static void init_i2c_bus(i2c_inst_t *i2c, int kbps, int sda, int scl)
{
i2c_init(i2c, kbps * 1000);
gpio_set_function(sda, GPIO_FUNC_I2C);
gpio_set_function(scl, GPIO_FUNC_I2C);
gpio_pull_up(sda);
gpio_pull_up(scl);
}
//
// The one rotary encoder. Turning it changes the selected pot's value,
// and that is all a turn has ever meant to anything but the old EQ.
//
// Accumulated by the interrupt, drained by update_ui(). There used to
// be a second encoder for picking the effect; it is gone, and picking
// the effect is done over MIDI.
//
static volatile int rotary_value;
static void rotary_irq(void)
{
// Initial impossible previous value
static int prev_value = 4;
static const int lookup[32] = {
// CW: 00 -> 10 -> 11 -> 01 -> 00
[2] = 1, [11] = 1, [13] = 1, [4] = 1,
// CCW: 00 -> 01 -> 11 -> 10 -> 00
[1] = -1, [7] = -1, [14] = -1, [8] = -1
};
while (!pio_sm_is_rx_fifo_empty(pio2, ROTARY_SM)) {
int curr = pio_sm_get(pio2, ROTARY_SM) & 3;
int prev = prev_value;
int val = lookup[(prev << 2) | curr];
prev_value = curr;
if (!val)
continue;
rotary_value += val;
}
user_interaction = 1;
}
// We'll use a separate PIO program for the rotary
// encoder pins eventually
static void init_rotary_encoder(void)
{
PIO pio = pio2;
uint offset = pio_add_program(pio, &rotary_program);
// The program reads both pins of the quadrature pair starting
// at the one it is given, so A and B have to stay adjacent.
_Static_assert(ROTARY_B_GPIO == ROTARY_A_GPIO + 1,
"the quadrature pair has to be adjacent");
init_sw_pin(pio, ROTARY_A_GPIO);
init_sw_pin(pio, ROTARY_B_GPIO);
rotary_program_init(pio, ROTARY_SM, offset, ROTARY_A_GPIO);
irq_set_exclusive_handler(PIO2_IRQ_0, rotary_irq);
irq_set_enabled(PIO2_IRQ_0, true);
}
#endif