Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 158 additions & 0 deletions firmware/led_probe.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/* SPDX-License-Identifier: GPL-3.0-only
* OpenPocket — the LED map (issue #8): which bit of the 74HC595 (px_leds.h) lights which button?
*
* Built only with LED_PROBE 1 (openpocket.c). That build plays no sound. The knob steps through ALL 8 BITS, then
* BIT 0 .. BIT 7; the bits on screen blink at 2 Hz (the byte alternates between them and 0, so the one light that
* changes is theirs, whether it is lit by a 1 or by a 0: the others may stay lit) and the square at the top right is
* lit while they are 1.
* - Pressing the button whose light blinks writes it into the table at the bottom (FX, MOD, AMP, CAB, DLY, REV,
* HOME, the knob's PUSH, either footswitch); the same button again clears it. A photo of the table is the map.
* - Driven: PC5, PC6 and PC8 (the 595's shift clock, latch and data) and PC7, the lights' enable, low (px_leds.h
* leds_init, as the stock firmware). The first LED map and the LED hunts (git history) lit nothing because PC7
* stayed high: OpenPocket held it so as a screen line.
* - USB stays up, as in the normal build, so the web installer takes the pedal back to a normal OpenPocket or to
* the official firmware.
*
* Included by openpocket.c (LED_PROBE 1) and by tests/lpsim.c on the PC. The including file provides: PA_IN, PH_IN,
* PC_IN, ENC_MASK, PA_FX / PA_HOME / PA_FS_UP / PA_FS_DN, PH_PUSH / PH_MOD / PH_AMP, base_pa, base_ph, keys_decode,
* adc_read2, ADC_CH_KEYS, KEY_CAB / KEY_DLY / KEY_REV, QDEC, dbn_t / dbn_press, ms_now, fm1_delay_us, fm1_wdt_feed,
* px_usb_service, lcd_rect, ft_box / ft_width and the fonts (ui_text.h), b_str / b_num, guard, op_diag / ST_OK and
* the UI colours. */
#pragma once
#include <stdint.h>
#include "px_leds.h"

#ifndef LP_TEXT_HOOK /* tests/lpsim.c checks that every line fits its box */
#define LP_TEXT_HOOK(s, f, w) ((void)0)
#endif
#define LP_BLINK_MS 250u /* half a period: 2 Hz */
#define LP_NPOS 9u /* ALL 8 BITS, BIT 0 .. BIT 7 */

enum { LB_FX, LB_MOD, LB_AMP, LB_CAB, LB_DLY, LB_REV, LB_HOME, LB_PUSH, LB_FSR, LB_FSL, LB_N };
static const char *const LP_BTN[LB_N] = { "FX", "MOD", "AMP", "CAB", "DLY", "REV", "HOME", "PUSH", "R FOOT", "L FOOT" };

static struct {
uint32_t pos; /* 0 = all 8 bits, 1..8 = bit pos - 1 */
uint32_t t_blink, t_keys;
int hi, dirty, key;
int32_t acc;
uint8_t enc;
uint8_t map[8]; /* per bit: the button that lit, + 1 (0 = none yet) */
dbn_t b[LB_N];
} lp;

static uint8_t lp_mask(void) { return lp.pos ? (uint8_t)(1u << (lp.pos - 1u)) : 0xFFu; }
static void lp_show(void) { leds_write(lp.hi ? lp_mask() : 0u); }

static void lp_hex2(char *d, uint32_t v)
{
static const char H[] = "0123456789ABCDEF";
while (*d) d++;
d[0] = H[(v >> 4) & 15u]; d[1] = H[v & 15u]; d[2] = 0;
}
static void lp_text(uint32_t x, uint32_t y, uint32_t w, const char *s, const op_font *f, uint16_t col, int align)
{
LP_TEXT_HOOK(s, f, w);
ft_box(x, y, w, s, f, col, UI_BG, align);
}
static void lp_dot(void) { lcd_rect(222, 10, 14, 14, lp.hi ? UI_ACC : UI_PANEL); }
static void lp_draw(void)
{
char l[44];
uint32_t bit = lp.pos ? lp.pos - 1u : 0u;
int mapped = lp.pos && lp.map[bit];
lp_text(20, 6, 200, "LED MAP", &FT_MD, UI_ACC, 1);
lp_dot();
l[0] = 0;
if (lp.pos) { b_str(l, "BIT "); b_num(l, bit); } else b_str(l, "ALL 8 BITS");
lp_text(0, 36, 240, l, &FT_NM, UI_TXT, 1);
l[0] = 0;
b_str(l, "BYTE "); lp_hex2(l, lp_mask()); b_str(l, " / 00, TWICE A SECOND");
lp_text(0, 74, 240, l, &FT_SM, UI_ACC, 1);
l[0] = 0;
if (!lp.pos) b_str(l, "EVERY BUTTON LIGHT SHOULD BLINK");
else if (mapped) { b_str(l, "BIT "); b_num(l, bit); b_str(l, " LIGHTS: "); b_str(l, LP_BTN[lp.map[bit] - 1u]); }
else b_str(l, "PRESS THE BUTTON THAT BLINKS");
lp_text(0, 96, 240, l, &FT_SM, mapped ? UI_OK : UI_TXT, 1);
lp_text(0, 114, 240, !lp.pos ? "KNOB: ONE BIT AT A TIME" : mapped ? "SAME BUTTON AGAIN: CLEAR" : "KNOB: NEXT BIT",
&FT_SM, UI_DIM, 1);
for (uint32_t i = 0; i < 8u; i++) { /* the map: bits 0-3 left, 4-7 right */
uint16_t col = (lp.pos && i == bit) ? UI_ACC : lp.map[i] ? UI_TXT : UI_DIM;
l[0] = 0;
b_str(l, "BIT "); b_num(l, i); b_str(l, ": "); b_str(l, lp.map[i] ? LP_BTN[lp.map[i] - 1u] : "--");
lp_text(i < 4u ? 24u : 136u, 140u + (i & 3u) * 18u, 100u, l, &FT_SM, col, 0);
}
lp_text(0, 218, 240, "SQUARE LIT = BITS AT 1", &FT_SM, UI_DIM, 1);
}

static void lp_init(void)
{
lp.pos = 0;
lp.hi = 1;
lp.acc = 0;
lp.key = KEY_NONE;
lp.enc = (uint8_t)(PC_IN & ENC_MASK);
for (int i = 0; i < 8; i++) lp.map[i] = 0;
for (int i = 0; i < LB_N; i++) lp.b[i].stab = lp.b[i].last = lp.b[i].cnt = 0;
guard.pending = 0; /* a reset here is not a failed boot */
guard.failed = 0; /* (no UBOOT, no safe mode screen) */
op_diag.stage = ST_OK;
leds_init();
lp.t_blink = lp.t_keys = ms_now();
lp_show();
lcd_rect(0, 0, 240, 240, UI_BG);
lp.dirty = 1;
}

static void lp_step(uint32_t now) /* one pass of the loop */
{
fm1_wdt_feed();
px_usb_service(now); /* the web installer, as in the normal build */
{ /* the knob: one detent, one step (the main loop's decoding) */
uint8_t s = (uint8_t)(PC_IN & ENC_MASK);
if (s != lp.enc) {
int dd = 0;
lp.acc -= QDEC[(lp.enc << 2) | s];
lp.enc = s;
if (lp.acc >= 4) { lp.acc -= 4; dd = 1; } else if (lp.acc <= -4) { lp.acc += 4; dd = -1; }
if (dd) {
lp.pos = dd > 0 ? (lp.pos + 1u) % LP_NPOS : (lp.pos + LP_NPOS - 1u) % LP_NPOS;
lp.hi = 1;
lp.t_blink = now;
lp_show();
lp.dirty = 1;
}
}
}
{ /* the buttons (pressed = differs from the idle state); CAB / DLY / REV are the analog keypad, every 10 ms */
uint32_t pa = (PA_IN & 0xFFFFu) ^ base_pa, ph = (PH_IN & 0xFFFFu) ^ base_ph;
int hit = -1, keys = now - lp.t_keys >= 10u;
int raw[LB_N] = {
(pa & PA_FX) != 0, (ph & PH_MOD) != 0, (ph & PH_AMP) != 0, 0, 0, 0,
(pa & PA_HOME) != 0, (ph & PH_PUSH) != 0, (pa & PA_FS_UP) != 0, (pa & PA_FS_DN) != 0 };
if (keys) { lp.t_keys = now; lp.key = keys_decode(adc_read2(ADC_CH_KEYS)); }
raw[LB_CAB] = lp.key == KEY_CAB; raw[LB_DLY] = lp.key == KEY_DLY; raw[LB_REV] = lp.key == KEY_REV;
for (int i = 0; i < LB_N; i++) {
if ((i == LB_CAB || i == LB_DLY || i == LB_REV) && !keys) continue;
if (dbn_press(&lp.b[i], raw[i]) && hit < 0) hit = i;
}
if (hit >= 0 && lp.pos) {
uint8_t *m = &lp.map[lp.pos - 1u];
*m = (*m == (uint8_t)(hit + 1)) ? 0u : (uint8_t)(hit + 1);
lp.dirty = 1;
}
}
if (now - lp.t_blink >= LP_BLINK_MS) {
lp.t_blink = now;
lp.hi = !lp.hi;
lp_show();
lp_dot();
}
if (lp.dirty) { lp.dirty = 0; lp_draw(); }
}

static void led_probe_run(void)
{
lp_init();
for (;;) lp_step(ms_now());
}
23 changes: 20 additions & 3 deletions firmware/openpocket.c
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* SPDX-License-Identifier: GPL-3.0-only
* OpenPocket X — Milestone 1 (boot + screen) — Donner Pocket X.
* SCREEN (reverse-engineered from the stock firmware): SOFTWARE SPI on PORT B
* CS = PB1, D/C = PB5, CLK = PB6, DO = PB7 (+ PC7 driven, reset/backlight?)
* CS = PB1, D/C = PB5, CLK = PB6, DO = PB7 (PC7 is not the screen's: the button lights' enable, px_leds.h)
* (the FM-1 used port C: that was the cause of the black screen.)
* Init sequence + 40-line offset: taken verbatim from the Pocket X firmware.
* Safety net: 2 failed boots -> UBOOT (re-flashable), + backup fm1-1.bin.
Expand Down Expand Up @@ -144,7 +144,7 @@ static void lcd_init(void)
PB_DIR &= ~(CS | DC | CLK | DO); /* outputs */
PC_DIR &= ~PC7;
PB_OUT |= CS; PB_OUT &= ~CLK; /* CS high, CLK low */
PC_OUT |= PC7; /* PC7 high (reset released / backlight?) */
PC_OUT |= PC7; /* PC7 high: the button lights off (their enable, low = on: px_leds.h) */
#if LCD_HW
lspi_init();
#endif
Expand Down Expand Up @@ -306,6 +306,7 @@ static void fm1_fault(const fm1_crash_t *c)
#define OP_VERSION "v" OP_VERSION_NUM /* shown at boot: changes with every released build */
#define PROBE 0 /* 1 = GPIO diagnostic: shows the pins that change at the bottom */
#define ADC_PROBE 0 /* 1 = ADC diagnostic: shows the channels that move at the bottom (C2:512) */
#define LED_PROBE 0 /* 1 = LED map (issue #8, led_probe.h): the knob picks a bit of the lights' 595, it blinks; no sound */
#define PA_PD (*(volatile uint32_t *)0x50014u)

/* ---- Controls mapped on the real pedal (diagnostic) ----
Expand Down Expand Up @@ -335,7 +336,7 @@ static void input_init(void)
PH_DIE |= 0xFFFFu; PH_PU |= 0xFFFFu;
PB_DIE |= 0xFF1Du; PB_PU |= 0xFF1Du; /* except PB1/5/6/7 (screen) */
#endif
PC_DIR &= ~PC7; PC_OUT |= PC7; /* PC7 drives the screen: always an output at 1 */
PC_DIR &= ~PC7; PC_OUT |= PC7; /* PC7, the button lights' enable: high = off until px_leds.h turns them on */
}
static void input_baseline(void)
{
Expand Down Expand Up @@ -536,6 +537,8 @@ static const int8_t QDEC[16] = {0,-1,1,0, 1,0,0,-1, -1,0,0,1, 0,1,-1,0};

#if !AUDIO_PROBE
#include "ui.h"
#include "px_lights.h" /* the button lights follow the blocks (issue #8; px_leds.h drives them) */
#define PX_LIGHTS_OFF() lights_off()
#include "px_usb.h" /* USB-MIDI + update entry (web installer) */
#include "px_store.h" /* the user's presets in flash */
#if !AUDIO_PROBE
Expand All @@ -544,6 +547,12 @@ static uint32_t ed_flags(void) { return (a_bypass ? 1u : 0u) | (a_c1 ? 2u : 0u);
#include "px_editor.h" /* the web editor (docs/EDITOR-PROTOCOL.md) */
#endif
#endif
#if LED_PROBE
#if AUDIO_PROBE
#error "LED_PROBE needs the normal build: USB (the web installer) is the way back"
#endif
#include "led_probe.h" /* the LED map (issue #8) */
#endif

/* P3_RST_SRC as read at boot: why the chip last reset (fm1_sys.h) */
static void rst_names(char *l, uint32_t r)
Expand Down Expand Up @@ -641,13 +650,20 @@ static void app_main(void)
keys_capture_idle();
op_stage(ST_PRESET, "PRESET...");
load_preset(cur_preset);
#if LED_PROBE
px_usb_start(); /* USB-MIDI + update entry: the web installer is the way back */
px_store_boot();
fm1_irq_enable_all(); /* audio_start does it in the normal build; here no audio is started */
led_probe_run(); /* never returns */
#endif
#if AUDIO_PROBE
audio_probe_run();
#else
a_hold = 1; /* DSP bypassed until the UI is up and 1.5 s have passed */
px_usb_start(); /* USB-MIDI device + update entry (web installer) */
px_store_boot(); /* the saved presets; start on the last one saved */
audio_start(); /* guitar -> DSP engine -> output, amp enabled */
leds_init(); /* the button lights: all off loaded, then on (PC7 low), as stock */
op_stage(ST_UI, "SCREEN...");
lcd_rect(0, 0, 240, 240, UI_BG);
ui_invalidate();
Expand All @@ -661,6 +677,7 @@ static void app_main(void)
#if !AUDIO_PROBE
px_usb_service(now); /* web installer: handshake, update, UBOOT soft key */
audio_poll(now); /* the sound follows the UI (preset, settings, volume) */
lights_update(&PRESET, ui_mode == M_TUNER, now); /* the button lights follow the blocks */
if (ui_mode != M_TUNER) { /* DSP load at the bottom right: "C1 42%" (2nd core), "C0" or BYP */
static uint32_t t_cpu;
if (now - t_cpu >= 500u) {
Expand Down
91 changes: 91 additions & 0 deletions firmware/px_leds.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/* SPDX-License-Identifier: GPL-3.0-only
* OpenPocket — the button lights (issue #8): one 74HC595 shift register, 8 outputs, on port C, and PC7, the lights'
* enable.
*
* Pins and timing as the stock firmware drives them (BlackBox_016, _020 and _021 have the same routine, read with
* JieLi's objdump; isod89 read it first, and finds PocketX_016's the same):
* DS (data) = PC8, SHCP (shift clock) = PC5, STCP (latch) = PC6.
* PC7 = the lights' enable, low = on (the 595's OE, by how the stock code uses it). The 595 init sets PC7 high
* with the three pins and loads the first byte; then the lights go on with PC7 low: in 016 / 020 as the first
* step of the screen init, which leaves it low, in 021 right after that first byte. 021 also sets PC7 high again
* on its way off. OpenPocket used to keep PC7 high as a screen line: it is not one (the stock screen runs with it
* low), and with it high the lights stay dark whatever the 595 holds. Every probe before this one kept it high.
* A byte, MSB first: STCP low; for each bit SHCP low, DS = the bit, wait, SHCP high, wait; then STCP high, whose
* rising edge puts the byte on the outputs. Stock waits about 4 to 8 us at each step: LEDS_WAIT is 5 us.
* Stock sends its byte inverted (DS = 1 for a 0 bit): its logical byte has bit 0 a blink signal (a timer), bits
* 1..6 the six effect lights, bit 7 always 0; at power-off it writes logical 0, FF on the wire. leds_write here
* takes the byte as it goes on the wire.
* Not run on a pedal with PC7 low yet.
*
* Every edge is a read-modify-write of PORTC OUT, so these are called from the main loop only (no interrupt writes
* port C). tests/lpsim.c checks the bit order and the waits against a model of the 595. */
#pragma once
#include <stdint.h>

#ifndef LEDS_PC /* PORTC register r: OUT +0x00, DIR +0x08 (1 input, 0 output), PU +0x10, PD +0x14 */
#define LEDS_PC(r) (*(volatile uint32_t *)(0x50080u + (uint32_t)(r)))
#endif
#ifndef LEDS_WAIT
#define LEDS_WAIT() fm1_delay_us(5u)
#endif
#ifndef LEDS_EDGE /* tests/lpsim.c: the 595 model sees each edge */
#define LEDS_EDGE() ((void)0)
#endif
#define LEDS_DS (1u << 8) /* PC8: data */
#define LEDS_SH (1u << 5) /* PC5: shift clock, DS is taken on its rising edge */
#define LEDS_ST (1u << 6) /* PC6: latch, the outputs change on its rising edge */
#define LEDS_PINS (LEDS_DS | LEDS_SH | LEDS_ST)
#define LEDS_EN (1u << 7) /* PC7: the lights' enable, low = on */
#define LEDS_OFF 0xFFu /* the stock's power-off byte on the wire (logical 0, inverted) */

static uint8_t leds_byte; /* what the outputs show now */

static void leds_pin(uint32_t m, uint32_t hi)
{
if (hi) LEDS_PC(0x00) |= m;
else LEDS_PC(0x00) &= ~m;
LEDS_EDGE();
}
/* The pins are parameters so that a test build can try other roles (led_probe.h); the driver is leds_init /
* leds_write with the pins above. */
static void leds_shift(uint8_t v, uint32_t ds, uint32_t sh, uint32_t st)
{
leds_pin(st, 0u);
for (int i = 7; i >= 0; i--) {
leds_pin(sh, 0u);
leds_pin(ds, ((uint32_t)v >> i) & 1u);
LEDS_WAIT();
leds_pin(sh, 1u);
LEDS_WAIT();
}
leds_pin(st, 1u);
}
static void leds_setup(uint32_t ds, uint32_t sh, uint32_t st)
{
uint32_t m = ds | sh | st;
LEDS_PC(0x10) &= ~m; /* no pull-ups (the boot leaves them on) */
LEDS_PC(0x14) &= ~m; /* no pull-downs */
LEDS_PC(0x00) = (LEDS_PC(0x00) & ~m) | sh; /* SHCP 1, STCP 0, DS 0, as stock */
LEDS_PC(0x08) &= ~m; /* outputs */
LEDS_EDGE();
LEDS_WAIT(); /* SHCP high for a while, even if it was low */
}
static void leds_write(uint8_t v)
{
leds_shift(v, LEDS_DS, LEDS_SH, LEDS_ST);
leds_byte = v;
}
static void leds_enable(uint32_t on) /* PC7 an output: low = lights on */
{
if (on) LEDS_PC(0x00) &= ~LEDS_EN;
else LEDS_PC(0x00) |= LEDS_EN;
LEDS_PC(0x08) &= ~LEDS_EN;
LEDS_EDGE();
}
static void leds_init(void) /* as stock: load with the lights off, then on */
{
leds_enable(0u);
leds_setup(LEDS_DS, LEDS_SH, LEDS_ST);
leds_write(LEDS_OFF);
leds_enable(1u);
}
39 changes: 39 additions & 0 deletions firmware/px_lights.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/* SPDX-License-Identifier: GPL-3.0-only
* OpenPocket — what the button lights show (issue #8). px_leds.h drives them; this decides them, once per main-loop
* pass, from the preset being played:
* FX lit while any block of the FX button's group is on (UI_FXB: drive, SFX, synth,
* pitch, comp, gate, EQ)
* AMP, MOD, DLY, REV, CAB lit while their block is on
* HOME blinks (2 Hz) while the tuner is open, dark otherwise
* The bits are the LED map's, read on a BlackBox (2026-11-06): 0 HOME, 1 FX, 2 AMP, 3 MOD, 4 DLY, 5 REV, 6 CAB,
* 7 none: the panel's order. On the wire a 0 lights, so the byte goes out inverted (as the stock firmware sends
* it), and only when it changes. Not yet: a setting to switch the lights off (it belongs with the settings store
* isod89/openpocket#10 adds).
* Included by openpocket.c (after ui.h: UI_FXB) and by tests/lightsim.c. */
#pragma once
#include <stdint.h>
#include "px_leds.h"

enum { LT_HOME, LT_FX, LT_AMP, LT_MOD, LT_DLY, LT_REV, LT_CAB }; /* the light on each bit */
#define LT_BLINK_MS 250u /* the tuner's HOME blink: half a period */

static uint8_t lights_of(const px_preset_t *p, int tuner, uint32_t now) /* the lit lights, 1 = lit */
{
uint8_t v = 0;
for (int i = 0; i < 7; i++)
if (p->on[UI_FXB[i]]) v |= 1u << LT_FX;
if (p->on[PX_B_AMP]) v |= 1u << LT_AMP;
if (p->on[PX_B_MOD]) v |= 1u << LT_MOD;
if (p->on[PX_B_DELAY]) v |= 1u << LT_DLY;
if (p->on[PX_B_REVERB]) v |= 1u << LT_REV;
if (p->on[PX_B_CAB]) v |= 1u << LT_CAB;
if (tuner && !((now / LT_BLINK_MS) & 1u)) v |= 1u << LT_HOME;
return v;
}
static void lights_show(uint8_t lit) /* written only when the byte changes */
{
uint8_t wire = (uint8_t)~lit;
if (wire != leds_byte) leds_write(wire);
}
static void lights_update(const px_preset_t *p, int tuner, uint32_t now) { lights_show(lights_of(p, tuner, now)); }
static void lights_off(void) { lights_show(0u); }
5 changes: 5 additions & 0 deletions firmware/px_usb.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ void fm1_timer5_irq(void) /* 10 kHz; USB at 2 kHz */
}
extern void isr_timer5(void);

#ifndef PX_LIGHTS_OFF /* the button lights off while an update runs (openpocket.c: px_lights.h) */
#define PX_LIGHTS_OFF() ((void)0)
#endif

/* ---- ota.c hooks ---- */
static uint32_t ota_now_ms(void) { return ms_now(); }
static void ota_idle(void) { fm1_wdt_feed(); }
Expand Down Expand Up @@ -78,6 +82,7 @@ static int ota_fread(uint32_t off, void *p, uint32_t n)
static void ota_show(uint32_t step, int32_t code)
{
static const char *const STEP[] = {"", "PACKAGE", "CHECK", "LOADER", "CONFIRM", "RESTART"};
PX_LIGHTS_OFF();
lcd_rect(0, 0, 240, 240, UI_BG);
ft_box(0, 92, 240, "UPDATE", &FT_MD, UI_ACC, UI_BG, 1);
if (step < 9u) {
Expand Down
Loading
Loading