From 367479409b53fe5d18bc566c9900746e2755be89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20I=2E=20B=C3=B6ttcher?= Date: Sun, 29 Mar 2026 10:29:44 +0200 Subject: [PATCH 01/12] Linker script: fix section overlap Define MEMORY for .eemap and .uumap not overlapping .text. With more than 4kBytes of flash, the .text segment overlaps .eemap, .uumap at the real addresses. Add high bits to the mappings, which are ignored. The mappings are now ar 0x80xxxx, which is defines for data/RAM, which must not overlap with anything but .text. --- src/eeprom.ld | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/eeprom.ld b/src/eeprom.ld index 0654d25..b72db90 100644 --- a/src/eeprom.ld +++ b/src/eeprom.ld @@ -4,14 +4,14 @@ */ MEMORY { - eemap : ORIGIN = 0x1400, LENGTH = 0x100 - eedef : ORIGIN = 0x810000, LENGTH = 0x100 - uumap : ORIGIN = 0x1300, LENGTH = 0x20 - uudef : ORIGIN = 0x850000, LENGTH = 0x20 + eemap (rw!x) : ORIGIN = 0x801400, LENGTH = 0x100 + eedef (rw!x) : ORIGIN = 0x810000, LENGTH = 0x100 + uumap (rw!x) : ORIGIN = 0x801300, LENGTH = 0x020 + uudef (rw!x) : ORIGIN = 0x850000, LENGTH = 0x020 } SECTIONS { - .eemap 0x1400: + .eemap 0x801400: { ee1_start = .; *(.eeprom1) @@ -22,7 +22,7 @@ SECTIONS ee9_end = .; ee9_size = ee9_end - ee9_start; } >eemap AT >eedef - .uumap 0x1300: + .uumap 0x801300: { *(.userrow) } >uumap AT >uudef From b3bb5396deea1b876ce80aee708f61d6b4b8550a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20I=2E=20B=C3=B6ttcher?= Date: Sun, 29 Mar 2026 10:41:55 +0200 Subject: [PATCH 02/12] Makefile: link order The AVR HK packets are basically dumps of the named .bss segments. The link order defines the layout of those packets. Packet sizes are 16, 32 or 64 bytes. Important things go first, larger packets contain extra things. A linker script could define the link order, but we cannot control that easily. --- src/Makefile | 44 +++++++++++++++++--------------------------- 1 file changed, 17 insertions(+), 27 deletions(-) diff --git a/src/Makefile b/src/Makefile index 5d8d444..60c61e7 100644 --- a/src/Makefile +++ b/src/Makefile @@ -11,22 +11,13 @@ thhor_all: thhor.eeprom thhor.userrow CFLAGS_thhor = -Ibch4369 -DHAVE_FPGA -DSEND_HEX -DTHHOR MCU_thhor = attiny3224 SN_thhor = 1 -C_FILES_thhor = config.c uart.c cmd.c base85.c rtc.c adc.c pwm.c spi.c flash.c bch4369.c pipe.c fpga.c +# Link order defines ADC packet composition +# main() is first, with .bss.magic +# config.c includes .eeprom `port_config`, that should go second +# .bss: rtc, adc, flash, uart, spi, pipe +C_FILES_thhor = config.c rtc.c adc.c flash.c uart.c cmd.c base85.c pwm.c spi.c bch4369.c pipe.c fpga.c S_FILES_thhor = uart_tx.S base85a.S -dose_all: dose.eeprom dose.userrow -SN_dose = 1 -MCU_dose = $(MCU_$(VAR)) -MCU_nFETs = attiny424 -MCU_FPGA = attiny3224 - -VAR=nFETs -C_FILES_nFETs = -C_FILES_FPGA = fpga.c -CFLAGS_dose = -DHAVE_$(VAR) -C_FILES_dose = config.c rtc.c adc.c pwm.c $(C_FILES_$(VAR)) uart.c cmd.c pipe.c base85.c bch4369.c spi.c flash.c -S_FILES_dose = uart_tx.S base85a.S - MCU = $(MCU_$(PROJ)) OPT = -Os @@ -82,23 +73,24 @@ pMCU-attiny824 = t824 pMCU-attiny3224 = t3224 # WDT -fuse0_dose= 0x00 +fuse0_thhor= 0x00 # BOD -fuse1_dose= 0x00 +fuse1_thhor= 0x00 # OSC, 20 MHz -fuse2_dose= 0x7e -# ??? -fuse4_dose= 0xff +fuse2_thhor= 0x7e +# Reserved +fuse3_thhor= 0xff +# Reserved +fuse4_thhor= 0xff # SYS0 (default 0xf6) RESET, EEPROM erase -fuse5_dose= 0xf7 +fuse5_thhor= 0xf7 # SYS1 startup time (64ms) -fuse6_dose= 0xff +fuse6_thhor= 0xff # APPEND -fuse7_dose= 0x00 +fuse7_thhor= 0x00 # BOOTEND -fuse8_dose= 0x00 -fuses_dose =$(patsubst %, 0x%, 00 00 7e ff ff f7 ff 00 00) -fuses_thhor = $(fuses_dose) +fuse8_thhor= 0x00 +fuses_thhor =$(patsubst %, 0x%, 00 00 7e ff ff f7 ff 00 00) AVRDUDEPROG = avrdude AVRDUDE = $(AVRDUDEPROG) @@ -107,8 +99,6 @@ AVRDUDE_PORT = /dev/ttyUSB0 AD = $(AVRDUDE) -p $(pMCU-$(MCU)) -P $(AVRDUDE_PORT) -c $(AVRDUDE_PROGRAMMER) - - sig_dose = 0x1e 0x92 0x2c sig_thhor = 0x1e 0x95 0x28 From 291578557148e3b48dc72491ea8587b24b8fe747 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20I=2E=20B=C3=B6ttcher?= Date: Sun, 29 Mar 2026 10:48:44 +0200 Subject: [PATCH 03/12] TODO comment: speed up spi writes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FPGA config takes 5µs per Byte. The ISR could be faster (and shorter) when the `wdata` is moved to `cmd` after czise and zsize are zero. --- src/spi.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/spi.c b/src/spi.c index b1425ac..85cf3fa 100644 --- a/src/spi.c +++ b/src/spi.c @@ -31,6 +31,11 @@ ISR(SPI0_INT_vect) d = spi.zero; } else if (spi.wsize) { + // TODO: + // spi.csize = spi.wsize; + // spi.wsize = 0; + // spi.cmd = spi.wdata; + // goto repeat; spi.wsize--; d = *spi.wdata++; } From 890aabf955345faca811a816ce8b8a7d654c7dba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20I=2E=20B=C3=B6ttcher?= Date: Sun, 29 Mar 2026 10:53:32 +0200 Subject: [PATCH 04/12] new inline flash_current_block() --- src/flash.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/flash.h b/src/flash.h index c509eee..46af975 100644 --- a/src/flash.h +++ b/src/flash.h @@ -28,6 +28,7 @@ uint8_t flash_cmd(uint16_t mode, uint16_t what, uint16_t page, uint16_t byte); extern uint8_t flash_buffer[FB_SIZE]; uint8_t flash_submit_command(uint8_t *cmd); uint8_t flash_start_stream(uint16_t page, uint16_t npages, uint8_t flags); +uint8_t flash_stream_done(); uint8_t flash_poll(uint8_t rr); uint16_t flash_find_free(); @@ -53,4 +54,6 @@ enum { FS_528 = 128, // do 528 byte pages. }; +static inline uint8_t flash_current_block() { return fs.block; } + #endif From 51d3e09dd3ad914998e561361a8674dc57779548 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20I=2E=20B=C3=B6ttcher?= Date: Sun, 29 Mar 2026 10:55:06 +0200 Subject: [PATCH 05/12] uart: new inline command_pending() --- src/uart.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/uart.h b/src/uart.h index 6438874..f06a422 100644 --- a/src/uart.h +++ b/src/uart.h @@ -26,3 +26,10 @@ void send_hex_long(uint32_t b) extern uint8_t uart_rx_err; extern uint8_t uart_rx_errors; +extern volatile uint8_t uart_rx_mes; + +static inline +uint8_t command_pending() +{ + return uart_rx_mes; +} From e63764042f2ea4748816278d5f09cf1e0cfcf8f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20I=2E=20B=C3=B6ttcher?= Date: Sun, 29 Mar 2026 10:58:38 +0200 Subject: [PATCH 06/12] FPGA: status and pipe support --- src/config.h | 43 ++++++++++++++++- src/fpga.c | 132 +++++++++++++++++++++++++++++++++++++++++++-------- src/fpga.h | 18 ++++++- 3 files changed, 169 insertions(+), 24 deletions(-) diff --git a/src/config.h b/src/config.h index 39d346d..3412198 100644 --- a/src/config.h +++ b/src/config.h @@ -23,6 +23,8 @@ struct config { uint16_t pwm_min; uint16_t pwm_max; #endif + uint16_t fpga_config_page; + uint16_t fpga_config_count; }; enum magic_flags { @@ -32,7 +34,7 @@ enum magic_flags { #endif #ifdef HAVE_FPGA MAGIC = 0xC5, - VERSION = 0x01, + VERSION = 0x00, #endif }; @@ -159,6 +161,8 @@ extern struct magic { #if 0 #define _memcopy memcpy #define _memcopyyz memcpy +#define _memcopyzy memcpy +#define _memcmpyz memcmp #else // avoid the avr-libc memcpy. // ¡ n must not be zero ! @@ -192,6 +196,43 @@ void _memcopyyz(uint8_t *d, uint8_t *s, uint8_t n) :: "r0", "memory"); } +static inline +void _memcopyzy(uint8_t *d, uint8_t *s, uint8_t n) +{ + __asm__ volatile ("\n" + "1:" "\n\t" + "ld r0, Y+" "\n\t" + "st Z+, r0" "\n\t" + "dec %[N]" "\n\t" + "brne 1b" "\n" + : [D] "+z" (d), + [S] "+y" (s), + [N] "+r" (n) + :: "r0", "memory"); +} + +static inline +uint8_t _memcmpyz(uint8_t *d, uint8_t *s, uint8_t n) +{ + uint8_t r; + __asm__ volatile ( + "\n" + "1:" "\n\t" + "ld %[R], Y+" "\n\t" + "ld r0, Z+" "\n\t" + "sub %[R], r0" "\n\t" + "breq 1f" "\n\t" + "dec %[N]" "\n\t" + "brne 1b" "\n" + "1:" "\n" + : [D] "+x" (d), + [S] "+y" (s), + [N] "+r" (n), + [R] "=r" (r) + : + : "r0", "memory"); + return r; +} #endif diff --git a/src/fpga.c b/src/fpga.c index 07d549d..fca5552 100644 --- a/src/fpga.c +++ b/src/fpga.c @@ -2,6 +2,7 @@ #include "pipe.h" #include "fpga.h" #include "spi.h" +#include uint8_t fpga_reset_poll() { @@ -12,6 +13,18 @@ uint8_t fpga_reset_poll() return r; } +uint8_t fpga_status() +{ + uint8_t r = fpga_power(); + // all three altera pins must be on the same PORT + if (r) + r |= CRCERR_VPORT.IN & + ( 1<n; uint8_t z = c->z; - if (fpga_reset_poll()) { - c->n = n | fpga_busy; + if (fpga_status() != as_configured) { + c->n = n | fpga_dead; return; } if (n & fpga_abort && spi_abort()) { @@ -41,7 +54,7 @@ void fpga_cmd(struct fpga_cmd *c) return; c->n = n | fpga_submitted; - spi_select(n & fpga_config ? SPI_CONFIG : 0); + spi_select(n & fpga_configure ? SPI_CONFIG : 0); spi.csize = n &= fpga_size; spi.zero = z & 0x80; // send 0x0000 or 0x8080 spi.cmd = c->d; @@ -62,25 +75,102 @@ void fpga_cmd(struct fpga_cmd *c) spi_start(); } -void fpga_start(uint8_t write) +struct pipe_fpga_cmd pipe_fpga_cmd; + +uint8_t fpga_start_write() { - uint8_t mode = 0; - if (pipe.fpga.zero == SPI_CONFIG) - mode = SPI_CONFIG; + uint8_t mode = pipe.fpga.status & SPI_CONFIG; spi_select(mode); - _memcopy(&spi.csize, &pipe.fpga.csize, 6); - spi.cmd = pipe.fpga.cmd; spi.wdata = flash_buffer; - spi.rdata = flash_buffer; - uint8_t n = 64; - if (n > pipe.fpga.size) - n = pipe.fpga.size; - pipe.fpga.size -= n; - if (!n) - return; - if (write) - spi.wsize = n; - else - spi.rsize = n; - spi_start(); + uint8_t n = 32; + if (pipe.fpga.count) + pipe.fpga.count--; + else { + uint8_t nn = pipe.fpga.size; + if (nn < n) + n = nn; + pipe.fpga.size -= n; + } + spi.wsize = n<<1; + if (n) + spi_start(); + return n; +} + +uint8_t fpga_start_read() +{ + /************************************************************ + * Send `counts` commands to the FPGA. + * Read `size` Words from each command. + * Fill the `flash_buffer` with 32 Words. + * AS_CONT: + * Continue reading Words after the buffer was used. + * User resets spi.fpga.val when done. + * Return the number of Words to be read now. + ***********************************************************/ + + if (spi_select(0)) + return 0; + if (!pipe.fpga.val) + memset(flash_buffer, 0, 64); + uint8_t n = 64 - pipe.fpga.val; + if (!n) + return n; + spi.rdata = flash_buffer + pipe.fpga.val; + n >>= 1; + if (~pipe.fpga.status & AS_CONT) { + if (!pipe.fpga.count) + return 0; + pipe.fpga.count--; + spi.cmd = pipe_fpga_cmd.cmd; + _memcopy((void*)&spi.csize, (void*)&pipe_fpga_cmd, 6); + pipe.fpga.pos = 0; + pipe.fpga.status |= AS_CONT; + } + uint8_t nn = pipe.fpga.size - pipe.fpga.pos; + if (nn && nn < n) + n = nn; + pipe.fpga.pos += n; + if (pipe.fpga.pos == pipe.fpga.size) + pipe.fpga.status &=~ AS_CONT; + spi.rsize = n << 1; + pipe.fpga.val += spi.rsize; + spi_start(); + return n; +} + +uint8_t fpga_pipe_ready() +{ + /************************************************************ + * Return the number of valid Words read in to the buffer, + * when + * - the FPGA is alive, and + * - the buffer is full, or + * - there is nothing more to read. + * Reset pipe.fpga.val, when returning nonzero, + * assuming those Words will be used now. + ***********************************************************/ + + uint8_t r = pipe.fpga.val; + if (fpga_status() != as_configured) + return 0; + if (r >= 64 || !pipe.fpga.count && ~pipe.fpga.status & AS_CONT) + pipe.fpga.val = 0; + return r; +} + +const struct pipe pipe_config_fpga_config = { + .source = pipe_flash, + .dest = pipe_fpga, + .fpga = { + .status = AS_CONFIG, + }, +}; + +void fpga_config(uint16_t page, uint16_t count) +{ + fpga_reset(); + pipe = pipe_config_fpga_config; + pipe.fpga.count = count; + flash_start_stream(page, count>>8, FS_Read|FS_528); } diff --git a/src/fpga.h b/src/fpga.h index 109d992..7576d36 100644 --- a/src/fpga.h +++ b/src/fpga.h @@ -13,7 +13,8 @@ enum fpga_flags { fpga_aborted = 0x10, fpga_busy = 0x20, fpga_submitted = 0x40, - fpga_config = 0x80, + fpga_configure = 0x80, + fpga_dead = 0x80, fpga_wait_nonzero = 0x01, }; @@ -21,8 +22,21 @@ enum fpga_flags { void fpga_reset(); uint8_t fpga_reset_poll(); void fpga_cmd(struct fpga_cmd *c); -void fpga_start(uint8_t write); +uint8_t fpga_start_read(); +uint8_t fpga_start_write(); +uint8_t fpga_pipe_ready(); +void fpga_config(uint16_t page, uint16_t count); static inline uint8_t fpga_power() { return !!(PEN_VPORT.IN & (1< Date: Sun, 29 Mar 2026 10:59:39 +0200 Subject: [PATCH 07/12] .gitignore *.s --- src/.gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/src/.gitignore b/src/.gitignore index 2f8c9ea..2921dc0 100644 --- a/src/.gitignore +++ b/src/.gitignore @@ -1 +1,2 @@ thhor.userrow +*.s From 6b668578ea8ef1b421b403fcd5f2cf438b9c0a36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20I=2E=20B=C3=B6ttcher?= Date: Sun, 29 Mar 2026 15:43:10 +0200 Subject: [PATCH 08/12] fpga_start_read() requires configured FPGA --- src/fpga.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/fpga.c b/src/fpga.c index fca5552..f16ca49 100644 --- a/src/fpga.c +++ b/src/fpga.c @@ -111,6 +111,8 @@ uint8_t fpga_start_read() if (spi_select(0)) return 0; + if (fpga_status() != as_configured) + return 0; if (!pipe.fpga.val) memset(flash_buffer, 0, 64); uint8_t n = 64 - pipe.fpga.val; From 6a2f5dc0269f1f1e4140b1af67838f1796931278 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20I=2E=20B=C3=B6ttcher?= Date: Sun, 29 Mar 2026 15:46:53 +0200 Subject: [PATCH 09/12] fpga_pipe_ready() fix return when not ready --- src/fpga.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/fpga.c b/src/fpga.c index f16ca49..8b7a770 100644 --- a/src/fpga.c +++ b/src/fpga.c @@ -158,6 +158,8 @@ uint8_t fpga_pipe_ready() return 0; if (r >= 64 || !pipe.fpga.count && ~pipe.fpga.status & AS_CONT) pipe.fpga.val = 0; + else + r = 0; return r; } From 36ffd2c4f6b865bc463fa6d6fc7d0e855c65c3a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20I=2E=20B=C3=B6ttcher?= Date: Sun, 29 Mar 2026 16:00:03 +0200 Subject: [PATCH 10/12] pipe: major development, fixes --- src/pipe.c | 199 ++++++++++++++++++++++++++++++++++++++++++----------- src/pipe.h | 38 ++++++---- 2 files changed, 186 insertions(+), 51 deletions(-) diff --git a/src/pipe.c b/src/pipe.c index 4643230..edd4c60 100644 --- a/src/pipe.c +++ b/src/pipe.c @@ -11,51 +11,120 @@ section_status(pipe) struct pipe pipe; +__attribute__((noinline)) +uint8_t pipe_busy() +{ + // Return true if anything is due to irq + // call this with cli() before sei();sleep() + if (spi_busy_p()) return 1; + if (flash_poll(0) & FS_Busy) return 1; + if (adc_poll(0)) return 1; + return 0; +} + uint8_t pipe_poll() { + /************************************************************ + * Drive data in `flash_buffer` from `source` to `dest` + * `pipe_cmd`: + * data comes and goes via cmd("B…") + * `pipe.valid` controls if a buffer was fully transfered + * `pipe_flash`: + * read or write pages from,to flash + * `PS_528`: use all 528 bytes + * `PS_BCH`: compute or check the BCH codes + * `pipe_adc`: (source) + * Fill the buffer with ADC readings and further status + * `pipe_fpga`: + * Configure the FPGA + * Write to the FPGA + * Read from the FPGA, with commands + ***********************************************************/ + + // Return, if + // any hardware is busy, + // an FS_Error occured, or + // the data goes nowhere. uint8_t r = pipe.status; - uint8_t fl = flash_poll(0); - if (spi_busy_p() || fl & FS_Busy || adc_poll(0)) - goto done; + uint8_t dest = pipe.dest; + + if (pipe_busy() || !dest) + return r; #ifdef HAVE_FPGA - if (fpga_reset_poll()) - goto done; + // we need to wait at least until the FPGA raises nCONFIG + if (dest & pipe_fpga && fpga_reset_poll()) + return r; #endif + + uint8_t valid = pipe.valid; + if (r & PS_OUT) { - if (pipe.dest & pipe_cmd && pipe.valid & 0x1f) + // Sending the buffer to `dest`. + // Return if we are not done sending to all destinations. + + if (dest & pipe_cmd && valid & 0x1f) // cmd did not drain the buffer, yet goto done; - if (pipe.dest & pipe_flash && !(fl & FS_Ready)) - // flash did not finish successfully - goto done; - if (pipe.source & pipe_adc && !adc_poll(pipe.adc)) + if (dest & pipe_flash && ~fs.status & FS_Ready) + // flash did not finish yet, successfully goto done; // fpga is OUT when the spi is ready. // We are done with this buffer - pipe.valid = 0; - if (pipe.source & pipe_flash) + + r &=~ PS_OUT; + + // Return if next ADC reading is not yet due. + // Come back here, until is is. + + if (pipe.source & pipe_adc && !adc_poll(pipe.adc)) + goto done; + + // Continue the flash stream. + // PS_BLK is the 64-Bytes buffer number in the page + + valid = 0; + if (pipe.source & pipe_flash) { + r &= ~ PS_BLK; + r |= flash_current_block() & PS_BLK; flash_poll(1); + } #ifdef HAVE_FPGA - else if (pipe.source & pipe_fpga) - fpga_start(0); + // Continue the FPGA stream. + + else if (pipe.source & pipe_fpga) { + if (~fpga_start_read()) + pipe.source &=~ pipe_fpga; + } #endif - r &=~ (PS_OUT|4); - r++; goto done; } + // Waiting for the buffer to fill + + // For 528 bytes pages, the last buffer must be 80 Bytes, + // i.e., five cmd_buffers á 16 Bytes uint8_t bflgs = 0x0f; if ((r & PS_5) == PS_5) bflgs = 0x1f; + // Data from the ADC is in named .bss segments. We send + // 16, 32, or 64 Bytes from the .bss segement for ead ADC reading. + // The first named .bss segment is `magic`. Observe the link order + // in the Makefile. + // + // Fun with assembly. + // `n` is the number of bytes, [16, 32, 64] + // `o` is the addr in the flash_buffer + // `v` are the currently valid cmd_buffer flags + // `f` are the fresh valid cmd_buffer flags if (pipe.source & pipe_adc) { uint8_t n = pipe.adc & 0x70; if (!n) goto adc_done; uint8_t f = n-1; uint8_t o = -16; - uint8_t v = pipe.valid; + uint8_t v = valid; #if 0 uint8_t c; do { @@ -84,49 +153,101 @@ uint8_t pipe_poll() f = 0xff; n = 64-o; } - pipe.valid |= f>>4; + valid |= f>>4; _memcopy(flash_buffer+o, (void*)&magic, n); } adc_done: - if (pipe.source & pipe_flash && fl & FS_Ready) - pipe.valid = bflgs; + // When the flash is done, flag the buffer valid, + // including BCH Bytes. + if (pipe.source & pipe_flash && fs.status & FS_Ready) + valid = bflgs; #ifdef HAVE_FPGA - else if (pipe.source & pipe_fpga) - pipe.valid = 0x0f; + // The FPGA delivers 64 Bytes. + else if (pipe.source & pipe_fpga && fpga_pipe_ready()) + valid = 0x0f; #endif - if (~pipe.valid & 0x0f) + // The buffer is not here, yet. Nothing we can do now. + if (~valid & 0x0f) goto done; + + // The buffer shall include BCH Bytes. if (r & PS_BCH) { - if (!(r&3)) + // First buffer of a page, clear the parity + if (!(r & PS_BLK)) bch4369_init(config.bch_salt); + // Add the current buffer to the parity uint8_t *bend = bch4369_stri(flash_buffer, 64); - if (!(~r & 3)) { - // reuse Y=bend - _memcopyyz(bend, bch_parity, 16); - pipe.valid = 0x01f; + // Last buffer of the page: + if (!(~r & PS_BLK)) { + bch4369_fini(); + valid = 0x01f; + // When the Flash is not the source, + // copy the computed parity into the buffer. + if (~pipe.source & pipe_flash) + _memcopyyz(bend, bch_parity, 16); + // When the page as read from flash has invalid parity, + // stop writing to the FPGA. + else if (_memcmpyz(bch_parity, bend, 16)) { + dest &=~ pipe_fpga; + r |= PS_ERR; + } } } - if (~pipe.valid & bflgs) + // We still do not have all data we need + if (~valid & bflgs) goto done; -#ifdef HAVE_FPGA - if (pipe.dest & pipe_fpga) - fpga_start(1); -#endif - + // The buffer is full, send it. r |= PS_OUT; + +#ifdef HAVE_FPGA + // Resume the FPGA stream + if (dest & pipe_fpga & ~fpga_start_write()) + dest &=~ pipe_fpga; + else +#endif + // Resume the flash stream + if (dest & pipe_flash) { + r &= ~ PS_BLK; + r |= flash_current_block() & PS_BLK; + flash_poll(1); + if (flash_stream_done()) + dest &=~ pipe_flash; + } + done: + pipe.dest = dest; + pipe.valid = valid; pipe.status = r; return r; } -void pipe_config(const struct pipe_config *c) +void pipe_config(const struct pipe_config *c, const struct pipe_fpga_cmd *a) { - pipe = c->pipe; - if ((pipe.source | pipe.dest) & pipe_flash) - flash_start_stream(c->page, c->npages, c->flash); + /************************************************************ + * cmd("B0!", «fpga-cmd») optionally configure an FPGA cmd + * cmd("P0:, «pipe-cfg») with `AS_CMD` in `.status` + ***********************************************************/ + + if (c->pipe.dest) { + pipe = c->pipe; + if ((pipe.source | pipe.dest) & pipe_flash) + flash_start_stream(c->page, c->npages, c->flash); + } + else { + // new source (NOT flash) + pipe.source = c->pipe.source; + } if (pipe.source & pipe_adc) adc_start_stream(pipe.adc); +#ifdef HAVE_FPGA + if (pipe.source & pipe_fpga) { + if (a && pipe.fpga.status & AS_CMD) + _memcopyzy((void*)&pipe_fpga_cmd, (void*)a, sizeof(*a)); + else + memset(&pipe_fpga_cmd, 0, sizeof(*a)); + fpga_start_read(); + } +#endif } - diff --git a/src/pipe.h b/src/pipe.h index bb23d65..9d9c3f7 100644 --- a/src/pipe.h +++ b/src/pipe.h @@ -8,10 +8,15 @@ enum pipe_ports { pipe_fpga = 8, PS_OUT = 0x80, - PS_BCH = 0x10, - PS_528 = 0x08, - PS_BLK = 0x03, + PS_ERR = 0x40, + PS_BCH = 0x20, + PS_528 = 0x10, + PS_BLK = 0x07, PS_5 = PS_BLK | PS_528, // need 16 bytes more + + AS_CONT = 1, + AS_CONFIG = SPI_CONFIG, // = 2 + AS_CMD = 4, }; extern @@ -23,14 +28,11 @@ struct pipe { uint8_t adc; #ifdef HAVE_FPGA struct { - uint16_t size; - uint8_t cmd[4]; - uint8_t csize; - uint8_t zsize; - uint8_t isize; - uint8_t zero; - uint8_t wait; - uint8_t mask; + uint8_t status; // AS_… + uint16_t count; // number of cmds(read), 64 Bytes(write) + uint8_t size; // Words per cmd (read), extra Words (write), 0==256 + uint8_t val; // Valid Bytes in Buffer (read) + uint8_t pos; // Words read from current cmd (read) } fpga; #endif } pipe; @@ -42,6 +44,18 @@ struct pipe_config { uint8_t flash; }; +extern +struct pipe_fpga_cmd { + uint8_t csize; + uint8_t zsize; + uint8_t isize; + uint8_t zero; + uint8_t wait; + uint8_t mask; + uint8_t cmd[10]; +} pipe_fpga_cmd; + +uint8_t pipe_busy(); uint8_t pipe_poll(); void pipe_cron(); -void pipe_config(const struct pipe_config *c); +void pipe_config(const struct pipe_config *c, const struct pipe_fpga_cmd *a); From 66525141bdc7e4278ac2db363ae1842e9a102939 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20I=2E=20B=C3=B6ttcher?= Date: Sun, 29 Mar 2026 16:01:40 +0200 Subject: [PATCH 11/12] cmd: "P" Pipe and "O" fpga confif --- src/cmd.c | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/src/cmd.c b/src/cmd.c index dd3edc5..b687341 100644 --- a/src/cmd.c +++ b/src/cmd.c @@ -244,17 +244,38 @@ void parse_command(const uint8_t *s, uint8_t n) goto send_buffer; break; case 'P': + if (cmd_flag('!')) + pipe.dest = 0; if (have_b) - pipe_config((void*)cmd_buffer); + pipe_config((void*)cmd_buffer, (void*)bptr); r = pipe_poll(); break; #ifdef HAVE_FPGA case 'O': r = fpga_power(); - if (bflg == 1) + if (bflg & 1) + // "O1" power off fpga_power_off(); - else if (bflg == 2) + if (bflg >= 2) { + // "O2" power on fpga_power_on(); + if (!r) { + fpga_reset(); + break; + } + } + if (bflg <= 2) + break; + if (bflg <= 8 && r==as_configured) + // "O2": configure if unconfigured + break; + if (bflg & 8) { + // "O3": power of if unconfigured + fpga_power_off(); + break; + } + // "O4" configure unconditionally + fpga_config(config.fpga_config_page, config.fpga_config_count); break; case 'C': if (cmd_flag('@')) { From 93e1c3230e70b1b0f38585bf0c1c9619e0322692 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20I=2E=20B=C3=B6ttcher?= Date: Sun, 29 Mar 2026 16:02:54 +0200 Subject: [PATCH 12/12] main: race free sleep() --- src/thhor.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/thhor.c b/src/thhor.c index 32f291a..e6a2869 100644 --- a/src/thhor.c +++ b/src/thhor.c @@ -40,8 +40,17 @@ int main() send_hex_byte_eol(magic.reset_source); while (1) { + // The pipe may become idle before we reach sleep. + // Make sure we have nothing to do before we sleep. + // The sleep command will execute even when an irq + // is pending. It will wake us immediately. + cli(); + if (!command_pending() && (!pipe.dest || pipe_busy())) { + sei(); + sleep_cpu(); + } sei(); - sleep_cpu(); command(); + pipe_poll(); } }