diff --git a/firmware/led_probe.h b/firmware/led_probe.h new file mode 100644 index 0000000..35779a5 --- /dev/null +++ b/firmware/led_probe.h @@ -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 +#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()); +} diff --git a/firmware/openpocket.c b/firmware/openpocket.c index 2a1956f..9b3186e 100644 --- a/firmware/openpocket.c +++ b/firmware/openpocket.c @@ -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. @@ -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 @@ -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) ---- @@ -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) { @@ -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 @@ -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) @@ -641,6 +650,12 @@ 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 @@ -648,6 +663,7 @@ static void app_main(void) 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(); @@ -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) { diff --git a/firmware/px_leds.h b/firmware/px_leds.h new file mode 100644 index 0000000..e8a1b93 --- /dev/null +++ b/firmware/px_leds.h @@ -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 + +#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); +} diff --git a/firmware/px_lights.h b/firmware/px_lights.h new file mode 100644 index 0000000..3602558 --- /dev/null +++ b/firmware/px_lights.h @@ -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 +#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); } diff --git a/firmware/px_usb.h b/firmware/px_usb.h index 851134a..328edf4 100644 --- a/firmware/px_usb.h +++ b/firmware/px_usb.h @@ -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(); } @@ -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) { diff --git a/firmware/ui.h b/firmware/ui.h index 960d2e8..b530fce 100644 --- a/firmware/ui.h +++ b/firmware/ui.h @@ -464,11 +464,12 @@ static void ui_preset_step(int dd) if (ui_mode == M_EDIT) ui_mode = M_BLOCK; if ((int)ui_item >= ui_nitems()) ui_item = 0; } +/* the FX button's blocks, in its order (the FX light follows them: px_lights.h) */ +static const uint8_t UI_FXB[7] = {PX_B_DRIVE, PX_B_SFX, PX_B_SYN, PX_B_PITCH, PX_B_COMP, PX_B_GATE, PX_B_EQ}; static void ui_fx_cycle(void) /* FX: DRV -> SFX -> SYN -> PIT -> CMP -> GT -> EQ */ { - static const uint8_t FXB[7] = {PX_B_DRIVE, PX_B_SFX, PX_B_SYN, PX_B_PITCH, PX_B_COMP, PX_B_GATE, PX_B_EQ}; uint32_t nx = PX_B_DRIVE; - for (int i = 0; i < 7; i++) if (FXB[i] == ui_blk) nx = FXB[(i + 1) % 7]; + for (int i = 0; i < 7; i++) if (UI_FXB[i] == ui_blk) nx = UI_FXB[(i + 1) % 7]; ui_blk = nx; ui_mode = M_BLOCK; ui_item = 0; } diff --git a/tests/lightsim.c b/tests/lightsim.c new file mode 100644 index 0000000..66a3ff6 --- /dev/null +++ b/tests/lightsim.c @@ -0,0 +1,120 @@ +/* SPDX-License-Identifier: GPL-3.0-only + * OpenPocket — the button lights in the normal firmware (firmware/px_lights.h over px_leds.h, issue #8) on the PC, + * against fake port C registers and a model of the 74HC595 with its output enable on PC7: + * cc -O1 -Wall -Iengine -Ifirmware -o build/lightsim tests/lightsim.c && build/lightsim + * Checks: after leds_init the lights are enabled (PC7 low) with all of them dark; every one of the twelve blocks, + * alone, lights exactly its button (the FX group's seven lights FX); all on lights the six effect buttons and not + * HOME; HOME blinks at 2 Hz in the tuner and only there; bit 7 is never lit; the byte is written only when it + * changes; lights_off darkens all; only PC5, PC6, PC7 and PC8 are ever touched. */ +#include +#include +#include +#include "px_dsp.h" + +/* the FX button's group, written out again here (ui.h's UI_FXB is the firmware's): drive, SFX, synth, pitch, comp, + * gate, EQ */ +static const uint8_t UI_FXB[7] = { PX_B_DRIVE, PX_B_SFX, PX_B_SYN, PX_B_PITCH, PX_B_COMP, PX_B_GATE, PX_B_EQ }; + +/* ---- port C and the 595: DS PC8, SHCP PC5, STCP PC6, OE PC7 (low = outputs on); a 0 on an output lights it */ +enum { R_OUT = 0, R_DIR = 2, R_PU = 4, R_PD = 5 }; +static uint32_t pc[16], pc_start[16]; +static uint32_t lv_prev; +static uint8_t sr, q; +static unsigned long latches, waits; +static uint32_t pad(void) { uint32_t d = pc[R_DIR]; return (pc[R_OUT] & ~d) | (pc[R_PU] & d); } +static void edge(void) +{ + uint32_t lv = pad(), rise = lv & ~lv_prev; + if (rise & (1u << 5)) sr = (uint8_t)((sr << 1) | ((lv >> 8) & 1u)); + if (rise & (1u << 6)) { q = sr; latches++; } + lv_prev = lv; +} +static int enabled(void) { return !(pc[R_DIR] & (1u << 7)) && !(pad() & (1u << 7)); } +static uint8_t lit(void) { return enabled() ? (uint8_t)~q : 0u; } /* what the eye sees: 1 = lit */ +#define LEDS_PC(r) (pc[(r) / 4u]) +#define LEDS_WAIT() (waits++) +#define LEDS_EDGE() edge() +#include "px_lights.h" + +static int failed; +static void ok(int c, const char *what) { printf("%-66s %s\n", what, c ? "ok" : "FAIL"); if (!c) failed++; } +/* the LED map read on a BlackBox (2026-11-06), written out again here: bit 0 HOME, 1 FX, 2 AMP, 3 MOD, 4 DLY, 5 REV, + * 6 CAB, 7 none */ +enum { W_HOME = 0, W_FX = 1, W_AMP = 2, W_MOD = 3, W_DLY = 4, W_REV = 5, W_CAB = 6 }; +static uint8_t want_of(int b) /* the light a block alone should light */ +{ + for (int i = 0; i < 7; i++) if (UI_FXB[i] == b) return 1u << W_FX; + switch (b) { + case PX_B_AMP: return 1u << W_AMP; + case PX_B_MOD: return 1u << W_MOD; + case PX_B_DELAY: return 1u << W_DLY; + case PX_B_REVERB: return 1u << W_REV; + case PX_B_CAB: return 1u << W_CAB; + } + return 0u; +} +static int only_our_pins(void) +{ + for (int r = 0; r < 16; r++) { + uint32_t may = (r == R_OUT || r == R_DIR) ? 0x1E0u : (r == R_PU || r == R_PD) ? 0x160u : 0u; + if ((pc[r] ^ pc_start[r]) & ~may) return 0; + } + return 1; +} + +int main(void) +{ + static px_preset_t p; + memset(pc, 0, sizeof pc); + pc[R_OUT] = 0x5A5Au | 0x80u; pc[R_DIR] = 0xFFFFu & ~0x80u; /* PC7 an output, high, as lcd_init left it */ + pc[R_PU] = 0xFFFFu; + memcpy(pc_start, pc, sizeof pc); + lv_prev = pad(); sr = 0x5Au; q = 0x00u; /* a 595 holds garbage at power-on */ + + leds_init(); + ok(enabled() && lit() == 0u, "after leds_init: the lights enabled, all dark"); + + memset(&p, 0, sizeof p); + lights_update(&p, 0, 0); + ok(lit() == 0u && latches == 1u, "no block on: all dark, nothing written again"); + { + int good = 1, every = 1; + for (int b = 0; b < PX_NBLK; b++) { + memset(&p, 0, sizeof p); p.on[b] = 1; + lights_update(&p, 0, 0); + if (lit() != want_of(b)) { printf(" block %d lit %02X, wanted %02X\n", b, lit(), want_of(b)); good = 0; } + if (!want_of(b)) every = 0; + } + ok(good, "each block alone lights its own button"); + ok(every, "every block has a light (the FX group's seven: FX)"); + } + memset(&p, 0, sizeof p); + for (int b = 0; b < PX_NBLK; b++) p.on[b] = 1; + lights_update(&p, 0, 0); + ok(lit() == 0x7Eu, "all on: FX AMP MOD DLY REV CAB lit, HOME dark"); + { + unsigned long l0 = latches; + for (uint32_t t = 0; t < 2000u; t++) lights_update(&p, 0, t); + ok(latches == l0, "nothing changes: nothing written"); + } + { + int flips = 0, others = 1; uint8_t last = lit(); + for (uint32_t t = 0; t < 2000u; t++) { + lights_update(&p, 1, t); + if ((lit() ^ last) & (1u << W_HOME)) flips++; + if ((lit() & 0x7Eu) != 0x7Eu) others = 0; + last = lit(); + } + ok(flips >= 7 && flips <= 9, "tuner: HOME blinks at 2 Hz"); + ok(others, "tuner: the effect lights stay as they are"); + lights_update(&p, 0, 0); + ok(!(lit() & (1u << W_HOME)), "tuner closed: HOME dark"); + } + ok(!(lit() & 0x80u) && (q & 0x80u), "bit 7 never lit"); + lights_off(); + ok(lit() == 0u && enabled(), "lights_off: all dark"); + ok(only_our_pins(), "only PC5, PC6, PC7 and PC8 touched"); + ok(waits > 0u, "the 595 timing waits are used"); + printf("\n%s\n", failed ? "lights tests FAILED" : "all lights tests passed"); + return failed ? 1 : 0; +} diff --git a/tests/lpsim.c b/tests/lpsim.c new file mode 100644 index 0000000..204f39c --- /dev/null +++ b/tests/lpsim.c @@ -0,0 +1,310 @@ +/* SPDX-License-Identifier: GPL-3.0-only + * OpenPocket — the LED map (firmware/led_probe.h) and the lights' driver (px_leds.h, issue #8) on the PC, against + * fake GPIO registers and a model of the 74HC595 with its output enable on PC7: + * cc -O1 -Wall -Wno-unused-function -Ifirmware -o build/lpsim tests/lpsim.c && build/lpsim build/lp + * Checks: the driver alone (leds_init from pins with pulls, PC7 an input: PC5/6/8 outputs without pulls, the all-off byte FF + * latched while PC7 is high, then PC7 low; leds_write puts each byte on the outputs, MSB first). The LED map: only + * PC5, PC6, PC8 and PC7 are ever changed (every other register bit of PA, PB, PC and PH as at start); PC7 ends an + * output, low; every shift clock edge and the latch's rise have a wait before them, DS is an output and the latch is + * low while shifting; the byte on the outputs is always the one the screen names, blinking at 2 Hz between the bits + * on screen and 0, lit right after a turn; the knob wraps both ways over the 9 positions; each button writes itself + * into the table, again clears it, another replaces it, nothing is written on ALL 8 BITS; the analog keys (CAB, DLY, + * REV) are read every 10 ms; USB is serviced; the boot is not counted as failed; every line of text fits its box. + * Writes PPM screenshots to OUTDIR. */ +#include +#include +#include + +enum { RED = 0xF800, GRN = 0x07E0, BLU = 0x001F, WHT = 0xFFFF, BLK = 0x0000, CYN = 0x07FF, YEL = 0xFFE0, ORG = 0xFD20 }; +enum { UI_BG = 0x0841, UI_PANEL = 0x18E3, UI_SEL = 0xFB00, UI_ACC = 0xFD20, UI_TXT = 0xEF7D, UI_DIM = 0x8410, UI_OK = 0x4E6B, UI_OFF = 0x2124 }; + +/* ---- screen: as tests/uisim.c */ +static uint16_t fb[240 * 240]; +static uint32_t wx, wy, ww, wi; +static void lcd_win(uint32_t x, uint32_t y, uint32_t w, uint32_t h) { (void)h; wx = x; wy = y; ww = w; wi = 0; } +static void lcd_px(uint16_t c) +{ + uint32_t x = wx + wi % ww, y = wy + wi / ww; + if (x < 240u && y < 240u) fb[y * 240u + x] = c; + wi++; +} +static void lcd_end(void) { } +static void lcd_rect(uint32_t x, uint32_t y, uint32_t w, uint32_t h, uint16_t c) +{ + if (w == 0u || h == 0u || x >= 240u || y >= 240u) return; + if (x + w > 240u) w = 240u - x; + if (y + h > 240u) h = 240u - y; + for (uint32_t j = 0; j < h; j++) for (uint32_t i = 0; i < w; i++) fb[(y + j) * 240u + x + i] = c; +} +static void fm1_wdt_feed(void) { } + +/* ---- the pedal around the LED map: gpio[port p (0 A, 1 B, 2 C, 7 H)][register r / 4] */ +enum { R_OUT = 0, R_DIR = 2, R_PU = 4, R_PD = 5 }; /* register index: OUT +0, DIR +8, PU +0x10, PD +0x14 */ +static uint32_t gpio[8][16]; +static uint32_t pa_in, ph_in, pc_in; /* the buttons as pressed, the knob */ +static uint32_t sim_pa_in(void) /* as the pad reads: an output pin reads back its own level */ +{ + uint32_t dir = gpio[0][R_DIR]; + return (pa_in & dir) | (gpio[0][R_OUT] & ~dir); +} +#define PA_IN sim_pa_in() +#define PH_IN ph_in +#define PC_IN pc_in +#define ENC_MASK 0x3u +#define PA_FX (1u << 0) /* as openpocket.c */ +#define PA_HOME (1u << 1) +#define PA_FS_UP (1u << 2) +#define PA_FS_DN (1u << 3) +#define PH_PUSH ((1u << 4) | (1u << 6)) +#define PH_MOD ((1u << 7) | (1u << 8)) +#define PH_AMP (1u << 9) +static uint32_t base_pa, base_ph; +enum { KEY_NONE = 0, KEY_CAB, KEY_DLY, KEY_REV }; +#define ADC_CH_KEYS 9u +static int sim_key; /* the analog key held down */ +static unsigned long adc_calls; +static int32_t adc_read2(uint32_t ch) { if (ch == ADC_CH_KEYS) adc_calls++; return sim_key; } +static int keys_decode(int32_t v) { return (int)v; } +static const int8_t QDEC[16] = {0,-1,1,0, 1,0,0,-1, -1,0,0,1, 0,1,-1,0}; +typedef struct { uint8_t stab, last, cnt; } dbn_t; +static int dbn_press(dbn_t *d, int raw) /* as openpocket.c */ +{ + if (raw == d->last) { if (d->cnt < 5u) d->cnt++; } + else { d->cnt = 0; d->last = (uint8_t)raw; } + if (d->cnt >= 3u && raw != d->stab) { d->stab = (uint8_t)raw; return raw; } + return 0; +} +static uint32_t sim_ms; +static uint32_t ms_now(void) { return sim_ms; } +static unsigned long usb_calls; +static void px_usb_service(uint32_t now) { (void)now; usb_calls++; } +static struct { uint32_t magic, pending, failed; } guard; +enum { ST_OK = 8 }; +static struct { uint32_t stage; } op_diag; +static void b_num(char *d, uint32_t v) +{ + char t[10]; int n = 0; + while (*d) d++; + do { t[n++] = (char)('0' + v % 10u); v /= 10u; } while (v && n < 9); + while (n) *d++ = t[--n]; + *d = 0; +} +static void b_str(char *d, const char *s) { while (*d) d++; while (*s) *d++ = *s++; *d = 0; } + +/* ---- the 74HC595, from its data sheet: DS is shifted in on SHCP's rising edge (the first bit in ends at Q7), the + * outputs take the shift register on STCP's rising edge. A pin's level: its OUT bit when an output, else its pull. */ +#define M_DS (1u << 8) +#define M_SH (1u << 5) +#define M_ST (1u << 6) +#define M_EN (1u << 7) +static uint32_t lv_prev; +static uint8_t sr, q; +static unsigned long waits, w_sh, latches; +static int e_wait, e_latch, e_dir; +static uint32_t pc_level(void) { uint32_t d = gpio[2][R_DIR]; return (gpio[2][R_OUT] & ~d) | (gpio[2][R_PU] & d); } +static void sim_wait(void) { waits++; } +static int en_falls, en_rises; /* PC7, the output enable (low = on): its edges */ +static uint8_t q_at_enable = 0x5Au; /* the outputs' byte when PC7 went low */ +static unsigned long latches_at_enable; +static void sim_edge(void) +{ + uint32_t lv = pc_level(), rise = lv & ~lv_prev, fall = ~lv & lv_prev; + if (fall & M_EN) { en_falls++; q_at_enable = q; latches_at_enable = latches; } + if (rise & M_EN) en_rises++; + if (rise & M_SH) { + if (waits == w_sh) e_wait++; /* no wait while SHCP was low (or since the start) */ + if (lv & M_ST) e_latch++; /* shifting with the latch high: the outputs would ripple */ + if (gpio[2][R_DIR] & M_DS) e_dir++; /* DS not driven */ + sr = (uint8_t)((sr << 1) | ((lv & M_DS) ? 1u : 0u)); + w_sh = waits; + } + if (fall & M_SH) { if (waits == w_sh) e_wait++; w_sh = waits; } /* no wait while SHCP was high */ + if (rise & M_ST) { if (waits == w_sh) e_wait++; q = sr; latches++; } /* a wait after the last shift too */ + lv_prev = lv; +} +#define LEDS_PC(r) (gpio[2][(r) / 4u]) +#define LEDS_WAIT() sim_wait() +#define LEDS_EDGE() sim_edge() + +static int failed, clipped; +#define LP_TEXT_HOOK(s, f, w) do { \ + if (ft_width((f), (s)) > (w) || strlen(s) > 40u) { printf("text too wide: \"%s\"\n", (s)); clipped++; } \ + } while (0) +#include "ui_text.h" +#include "led_probe.h" + +/* ---- the checks */ +static const int PORTS[4] = { 0, 1, 2, 7 }; +static uint32_t snap[8][16]; +static int want, want2 = -1; /* the position the test expects (0 all, 1..8 bit want - 1); mid-turn either */ +static void ok(int c, const char *what) { if (!c) { printf("FAIL: %s\n", what); failed++; } } +static void reset_gpio(void) /* a distinctive start; PC5, PC6, PC8 inputs with pull-ups, as booted */ +{ + memset(gpio, 0, sizeof gpio); + for (int k = 0; k < 4; k++) { + int p = PORTS[k]; + gpio[p][R_OUT] = 0x5A5Au ^ (uint32_t)(k * 0x1111); + gpio[p][R_DIR] = 0xFFFFu & ~(0x0101u << k); + gpio[p][R_PD] = 0x3030u >> k; + gpio[p][R_PU] = 0xFFFFu & ~gpio[p][R_PD]; + } + gpio[1][R_DIR] &= ~0x00E2u; /* the screen pins PB1/5/6/7 and PC7: outputs, PC7 high */ + gpio[2][R_DIR] &= ~(1u << 7); gpio[2][R_OUT] |= 1u << 7; + gpio[2][R_DIR] |= M_DS | M_SH | M_ST; gpio[2][R_PU] |= M_DS | M_SH | M_ST; gpio[2][R_PD] &= ~(M_DS | M_SH | M_ST); + gpio[0][R_DIR] |= 0x000Fu; /* the buttons are inputs, as input_init leaves them */ + gpio[7][R_DIR] |= PH_PUSH | PH_MOD | PH_AMP; + memcpy(snap, gpio, sizeof snap); + pa_in = ph_in = pc_in = 0; sim_key = KEY_NONE; + base_pa = sim_pa_in() & 0xFFFFu; base_ph = ph_in; /* as input_baseline */ + lv_prev = pc_level(); + sr = 0x5Au; q = 0xA5u; /* a 595 holds garbage at power-on */ +} +static int only_595_pins(void) /* every register bit as at start, but PC5/6/8's OUT, DIR, PU, PD, PC7's OUT, DIR */ +{ + for (int k = 0; k < 4; k++) + for (int r = 0; r < 16; r++) { + uint32_t may = (PORTS[k] == 2 && (r == R_OUT || r == R_DIR || r == R_PU || r == R_PD)) ? (M_DS | M_SH | M_ST) : 0u; + if (PORTS[k] == 2 && (r == R_OUT || r == R_DIR)) may |= M_EN; /* PC7: the lights' enable */ + if ((gpio[PORTS[k]][r] ^ snap[PORTS[k]][r]) & ~may) return 0; + } + return 1; +} +static uint8_t mask_of(int pos) { return pos ? (uint8_t)(1u << (pos - 1)) : 0xFFu; } +static void run(uint32_t ms) /* the loop for ms milliseconds, checking every pass */ +{ + for (uint32_t t = 0; t < ms; t++) { + sim_ms++; + lp_step(ms_now()); + if (!only_595_pins()) { ok(0, "a pin other than PC5 / PC6 / PC8 was changed"); return; } + if (q != 0u && q != mask_of(want) && !(want2 >= 0 && q == mask_of(want2))) { + ok(0, "the 595 shows a byte other than the bits on screen"); return; + } + } +} +static void turn(int dir) /* one detent: 0 1 3 2 0 is one step forward (the main loop's sign) */ +{ + static const uint8_t FWD[4] = { 1, 3, 2, 0 }, BACK[4] = { 2, 3, 1, 0 }; + want2 = dir > 0 ? (want + 1) % 9 : (want + 8) % 9; + for (int k = 0; k < 4; k++) { pc_in = (pc_in & ~3u) | (dir > 0 ? FWD[k] : BACK[k]); run(2); } + want = want2; want2 = -1; + ok(lp.pos == (uint32_t)want, "the knob moves one position per detent"); + ok(q == mask_of(want), "lit right after a turn"); +} +static void go(int pos) { for (int k = 0; k < 9 && want != pos; k++) turn(+1); } +static void press_pa(uint32_t m) { pa_in ^= m; run(20); pa_in ^= m; run(20); } +static void press_ph(uint32_t m) { ph_in ^= m; run(20); ph_in ^= m; run(20); } +static void press_key(int k) { sim_key = k; run(60); sim_key = KEY_NONE; run(60); } +static int map(int bit) { return lp.map[bit] ? lp.map[bit] - 1 : -1; } +static void shot(const char *dir, const char *name) +{ + static int n; + char path[512]; + snprintf(path, sizeof path, "%s/%02d_%s.ppm", dir, n++, name); + FILE *f = fopen(path, "wb"); + if (!f) { printf("cannot write %s\n", path); failed++; return; } + fprintf(f, "P6\n240 240\n255\n"); + for (int i = 0; i < 240 * 240; i++) { + uint16_t c = fb[i]; + uint8_t rgb[3] = { (uint8_t)(((c >> 11) & 31u) * 255u / 31u), (uint8_t)(((c >> 5) & 63u) * 255u / 63u), + (uint8_t)((c & 31u) * 255u / 31u) }; + fwrite(rgb, 1, 3, f); + } + fclose(f); +} + +int main(int argc, char **argv) +{ + const char *dir = argc > 1 ? argv[1] : "build/lp"; + + /* the driver alone (px_leds.h): from inputs with pulls (PC8's a pull-down), then bytes MSB first */ + reset_gpio(); + gpio[2][R_PU] &= ~M_DS; gpio[2][R_PD] |= M_DS; + gpio[2][R_DIR] |= M_EN; gpio[2][R_PU] |= M_EN; /* PC7 untouched here: an input with its pull-up */ + memcpy(snap, gpio, sizeof snap); + lv_prev = pc_level(); + leds_init(); + ok(q == 0xFFu && leds_byte == 0xFFu && latches == 1u, "leds_init: the all-off byte FF shifted out"); + ok(!(gpio[2][R_DIR] & (M_DS | M_SH | M_ST)) && !(gpio[2][R_PU] & (M_DS | M_SH | M_ST)) && + !(gpio[2][R_PD] & (M_DS | M_SH | M_ST)), "leds_init: PC5, PC6, PC8 outputs, no pulls"); + ok(en_falls == 1 && q_at_enable == 0xFFu && latches_at_enable == 1u && !(gpio[2][R_OUT] & M_EN) && + !(gpio[2][R_DIR] & M_EN), "leds_init: PC7 an output, low, once the byte is latched"); + { + static const uint8_t V[5] = { 0xA5u, 0x01u, 0x80u, 0x3Cu, 0x00u }; + int good = 1; + for (int i = 0; i < 5; i++) { leds_write(V[i]); if (q != V[i] || leds_byte != V[i]) good = 0; } + ok(good, "leds_write: each byte on the outputs, MSB first"); + } + ok(only_595_pins(), "the driver touches only PC5, PC6, PC8 and PC7"); + latches = 0; en_falls = en_rises = 0; latches_at_enable = 0; q_at_enable = 0x5Au; + + /* the LED map */ + reset_gpio(); + guard.pending = 1; guard.failed = 1; op_diag.stage = 3; + lp_init(); + ok(guard.pending == 0u && guard.failed == 0u && op_diag.stage == ST_OK, "not counted as a failed boot"); + ok(!(gpio[2][R_DIR] & (M_DS | M_SH | M_ST)), "PC5, PC6, PC8 are outputs"); + ok(!(gpio[2][R_PU] & (M_DS | M_SH | M_ST)) && !(gpio[2][R_PD] & (M_DS | M_SH | M_ST)), "without pulls"); + ok(latches == 2u && q == 0xFFu, "the all-off byte FF at start (as stock), then ALL 8 BITS lit"); + ok(en_falls == 1 && en_rises == 0 && q_at_enable == 0xFFu && latches_at_enable == 1u, + "the lights on (PC7 low) once, after the all-off byte is latched"); + ok(!(gpio[2][R_DIR] & M_EN) && !(gpio[2][R_OUT] & M_EN), "PC7 an output, low"); + ok(only_595_pins(), "nothing else touched by the start"); + run(5); + ok(usb_calls > 0u, "USB serviced"); + shot(dir, "all_bits"); + + { /* 2 Hz: 8 changes in 2 s, always the bits on screen or 0 */ + int flips = 0; uint8_t last = q; + for (int t = 0; t < 2000; t++) { run(1); if (q != last) { flips++; last = q; } } + ok(flips >= 7 && flips <= 9, "2 Hz: 8 changes in 2 s"); + } + { /* each bit alone, MSB first (an LSB-first driver would light bit 7 - n) */ + for (int b = 0; b < 8; b++) { turn(+1); run(300); } + ok(want == 8, "at BIT 7"); + turn(+1); + ok(lp.pos == 0u, "past BIT 7: ALL 8 BITS"); + turn(-1); + ok(lp.pos == 8u && q == 0x80u, "back from ALL: BIT 7"); + turn(+1); + } + { /* the table: every button, again clears, another replaces, none on ALL */ + unsigned long a0 = adc_calls; uint32_t t0 = sim_ms; + press_key(KEY_DLY); + ok(memcmp(lp.map, "\0\0\0\0\0\0\0\0", 8) == 0, "nothing written on ALL 8 BITS"); + unsigned long n = adc_calls - a0, t = (sim_ms - t0) / 10u; + ok(n + 1u >= t && n <= t + 1u, "the keys every 10 ms"); + go(1); + shot(dir, "bit0"); + press_pa(PA_FX); + ok(map(0) == LB_FX, "FX on BIT 0"); + shot(dir, "bit0_fx"); + press_pa(PA_FX); + ok(map(0) < 0, "FX again clears it"); + press_pa(PA_FX); + go(2); press_pa(PA_FS_UP); + ok(map(1) == LB_FSR, "right footswitch on BIT 1"); + go(3); press_pa(PA_FS_DN); + ok(map(2) == LB_FSL, "left footswitch on BIT 2"); + go(4); press_ph(PH_AMP); + ok(map(3) == LB_AMP, "AMP on BIT 3"); + go(5); press_key(KEY_CAB); + ok(map(4) == LB_CAB, "CAB (analog) on BIT 4"); + go(6); press_ph(1u << 6); + ok(map(5) == LB_PUSH, "PUSH (PH6 alone) on BIT 5"); + go(7); press_key(KEY_REV); press_pa(PA_HOME); + ok(map(6) == LB_HOME, "HOME replaces REV on BIT 6"); + go(8); press_ph(1u << 8); + ok(map(7) == LB_MOD, "MOD (PH8 alone) on BIT 7"); + ok(map(0) == LB_FX && map(3) == LB_AMP, "the table keeps the others"); + shot(dir, "map"); + go(0); + press_pa(PA_FX); + ok(map(0) == LB_FX, "a press on ALL changes nothing"); + } + ok(e_wait == 0, "a wait on each side of every shift clock edge"); + ok(e_latch == 0, "the latch is low while shifting"); + ok(e_dir == 0, "DS is driven while shifting"); + ok(clipped == 0, "every line fits its box"); + printf("LED map: %s (%d failures, %lu latches, %lu USB passes)\n", failed ? "FAIL" : "ok", failed, latches, usb_calls); + return failed ? 1 : 0; +} diff --git a/tests/run_tests.sh b/tests/run_tests.sh index ff82108..902b256 100755 --- a/tests/run_tests.sh +++ b/tests/run_tests.sh @@ -4,6 +4,8 @@ # - engine: every block x model x extreme knob, signal / silence / overload, with the pedal's own libm (px_math.c): # no NaN; long runs (3 s pluck + 8 s silence): nothing runs away (only delays at FDBK 100 hold on, by design) # - preset store: save / reload / power cut at every step of a save (firmware/px_store.h on a simulated flash) +# - LED map + lights driver (led_probe.h, px_leds.h): PC7 enable after the first byte, the 595 timing, the table +# - button lights (px_lights.h): each block lights its button, FX for its group, HOME blinks in the tuner # - packages: tools/pxpkg.py rebuilds the official PocketX_016 app area byte for byte (needs the official file) # - installer: docs/pxota.js + docs/pxpkg.js against a simulated pedal running the real update code (ota.c, ldr_core.c) # - page: docs/index.html in headless Chromium (needs Playwright: npm i playwright && npx playwright install chromium) @@ -18,6 +20,11 @@ echo "== preset store" $CC -O1 -Iengine tests/test_store.c engine/px_dsp.c firmware/px_math.c -lm -o build/test_store && build/test_store | tail -2 echo "== screen (every page fully drawn)" mkdir -p build/ui && $CC -O1 -Wall -Wno-unused-function -Iengine -Ifirmware -o build/uisim tests/uisim.c engine/px_dsp.c firmware/px_math.c -lm && build/uisim build/ui > build/ui/log.txt && if grep -q "never drawn" build/ui/log.txt; then grep "never drawn" build/ui/log.txt; echo "screen: FAIL"; else echo "screen: $(wc -l < build/ui/log.txt) pages ok (build/ui/*.ppm)"; fi +echo "== LED map and the lights driver (LED_PROBE build, issue #8)" +mkdir -p build/lp && $CC -O1 -Wall -Wno-unused-function -Ifirmware -o build/lpsim tests/lpsim.c && build/lpsim build/lp +echo "== button lights in the normal firmware (px_lights.h, issue #8)" +$CC -O1 -Wall -Wno-unused-function -Iengine -Ifirmware -o build/lightsim tests/lightsim.c +build/lightsim > build/lightsim.log || { cat build/lightsim.log; exit 1; }; tail -1 build/lightsim.log echo "== simulated pedal" $CC -O1 -Wall -Wno-unused-function -o build/devsim tests/devsim.c if [ -n "$1" ]; then echo "== packages"; python3 tests/test_pxpkg.py "$1"; fi