From d1e6a031abb5e3881923e125ef20bb8c8360149a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20I=2E=20B=C3=B6ttcher?= Date: Thu, 29 Jan 2026 11:26:33 +0100 Subject: [PATCH 01/10] divmod85 implementation fixed and tested --- src/base85.c | 8 +-- src/base85_test.c | 132 +++++++++++++++++++++++++++++++++++++ src/base85a.S | 165 +++++++++++++++++++++++++++------------------- 3 files changed, 232 insertions(+), 73 deletions(-) create mode 100644 src/base85_test.c diff --git a/src/base85.c b/src/base85.c index d33b2b0..e92e478 100644 --- a/src/base85.c +++ b/src/base85.c @@ -12,10 +12,10 @@ int base85_encode(uint32_t u, uint8_t *s) { s += 5; *s = 0; - u = divmod85(u, s); - u = divmod85(u, s); - u = divmod85(u, s); - u = divmod85(u, s); + u = divmod85(u, --s); + u = divmod85(u, --s); + u = divmod85(u, --s); + u = divmod85(u, --s); *--s = (u>>24) + 33; return 5; } diff --git a/src/base85_test.c b/src/base85_test.c new file mode 100644 index 0000000..b534da2 --- /dev/null +++ b/src/base85_test.c @@ -0,0 +1,132 @@ + +#include +#include + +uint8_t m; +uint64_t f; + +static inline +uint32_t divmod85(uint32_t u) +{ + uint64_t uu = u; + uu += uu << 1; + uu += 1; + // f = (uu << 16) + (uu << 8) + uu + (uu >> 8) + (uu >> 16) + (uu >> 24); + f = (uu << 16) + (uu << 8) + uu + (uu >> 8) + (uu >> 16) + (uu >> 24) + (uu>>32); + f >>= 16; + m = ((f & 0xff) * 85 + 0x80) >> 8; + return f >> 8; +} + +int d = 0; + +typedef uint8_t r; +uint32_t divmod85avr(uint32_t u) +{ + +#define D(_m) if(d) printf("%-8s c=%x r0=%02x r1=%02x r18=%02x r19=%02x r20=%02x r21=%02x " \ + "r22=%02x r23=%02x r24=%02x r25=%02x r30=%02x r31=%02x\n", \ + _m, c, r0, r1, r18, r19, r20, r21, r23, r23, r24, r25, r30, r31) + +#define ADD(_x, _y) { _c = (_x + _y) >>8; _x += _y; c = _c & 1; D("ADD "#_x);} +#define ADC(_x, _y) { _c = (_x + _y + c) >>8; _x += _y + c; c = _c & 1; D("ADC "#_x);} +#define SUB(_x, _y) { _c = (_x - _y) >>8; _x -= _y; c = _c & 1; D("SUB "#_x);} +#define SBC(_x, _y) { _c = (_x - _y - c) >>8; _x -= _y + c; c = _c & 1; D("SBC "#_x);} +#define LSL(_x) { c = _x >> 7; _x <<= 1; D("LSL "#_x);} +#define ROL(_x) { _c = _x >> 7; _x <<= 1; _x |= c; c = _c; D("ROL "#_x);} +#define CLR(_x) { _x = 0; D("CLR "#_x);} +#define MOV(_x, _y) { _x = _y; D("MOV "#_x);} +#define MOVW(_x, _xh, _y, _yh) { _x = _y; _xh = _yh; D("MOVW "#_x);} +#define SEC {c=1; D("SEC");} +#define LDI(_x, _n) { _x = _n; D("LDI "#_x);} +#define MUL(_x, _y) { r0 = _x * _y; r1 = (_x * _y) >> 8; D("MUL "#_x);} + + r c, _c, r0, r18, r19, r20, r21, r30, r31; + r r1 = 0; + r r22 = u>>24; + r r23 = u>>16; + r r24 = u>>8; + r r25 = u; + + MOVW(r18,r19, r22,r23); + MOVW(r20,r21, r24,r25); + LSL(r21); + ROL(r20); + ROL(r19); + ROL(r18); + CLR(r0); + ROL(r0); + SEC; + ADC(r21, r25); + ADC(r20, r24); + ADC(r19, r23); + ADC(r18, r22); + ADC(r0, r1); + + MOV(r30, r21); + ADD(r30, r20); + CLR(r31); + ROL(r31); + ADD(r30, r19); + ADC(r31, r1); + ADD(r30, r18); + ADC(r31, r1); + ADD(r30, r0); + ADC(r31, r1); + + MOV(r22, r31); + ADD(r22, r30); + MOV(r18, r31); + ADC(r18, r30); + MOV(r25, r31); + ADC(r25, r1); + + SUB(r30, r21); + SBC(r31, r1); + ADD(r25, r30); + MOV(r24, r31); + ADC(r24, r1); + + SUB(r30, r20); + SBC(r31, r1); + ADD(r24, r30); + MOV(r23, r31); + ADC(r23, r1); + + SUB(r30, r19); + SBC(r31, r1); + ADD(r23, r30); + MOV(r22, r31); + ADC(r22, r0); + + LDI(r19, 85); + MUL(r18, r19); + LSL(r0); + LDI(r19, 0) + ADC(r1, r19); + m = r1; + CLR(r1); + return (r22<<24) | (r23<<16) | (r24<<8) | r25; +} + +int main() +{ + uint32_t u = 0; + int did_d = 0; + do { + if (!(u & 0xffff)) + fprintf(stderr, "%08x\r", u); + uint32_t nn = u / 85; + uint8_t mm = u % 85; + uint32_t n = divmod85avr(u); + if (nn != n || mm != m) { + printf("%08x = %08x × 85 + %02x : %08x %02x : %014lx\n", + u, nn, mm, n, m, f); + if (!did_d) { + did_d = d = 1; + divmod85avr(u); + d = 0; + } + } + } while (++u); +} diff --git a/src/base85a.S b/src/base85a.S index e6ef42f..b92119c 100644 --- a/src/base85a.S +++ b/src/base85a.S @@ -1,72 +1,97 @@ -; uint32_t divmod85(uint32_t u, uint8_t *m) - ; r26/r27 = m - ; r25 = A LSB, big endian - ; r24 = B - ; r23 = C - ; r22 = D MSB - ; r1 = undef - #ifdef BASE85C +;uint32_t divmod85(uint32_t u) +;{ +; uint64_t uu = u; +; f = (uu << 16) + (uu << 8) + uu + (uu >> 8) + (uu >> 16) + (uu >> 24); +; f += 0x0100; +; f &= 0xffffffffffff00LL; +; f += f << 1; +; m = (((f >> 16) & 0xff) * 85 + 0x80) >> 8; +; return f >> 24; +;} +; +; E,A,B,C,D = 3 × (r25,r24,r23,r22 big endian) +; 3*(A+B+C+D+E) = r30, r31 +; +; E D C B A +; E D C B A +; E D C B A +; E D C B +; E D C +; E D +; E +; ============= +; u u u u r - + +#ifdef BASE85C .global divmod85 divmod85: movw r26, r20 - # endif - - ;; ! C-ABI: gobbles r1, m=X -_divmod85: ; r25,r24,r23,r22 = A,B,C,D - mov r18, r25 - add r18, r24 - clr r21 - rol r21 ; r18,r21 = A+B - add r23, r18 - clr r31 - adc r31, r21 ; r23,r31 = A+B+C - add r21, r23 - clr r18 - adc r18, r31 ; r21,r18 = A+B>>8 + A+B+C - add r23, r22 - clr r20 - adc r31, r20 ; r23,r31 = A+B+C+D - add r18, r23 - adc r21, r31 ; r18,r21 = A+B>>16 + A+B+C>>8 + A+B+C+D - sub r23, r25 - sbc r31, r20 ; r23,r31 = B+C+D - add r21, r23 - adc r20, r31 ; r18,r21,r20 = … + B+C+D<<8 - sub r23, r24 - clr r30 - sbc r31, r30 ; r23,r31 = C+D - add r20, r23 - adc r31, r22 - rol r30 ; r18,r21,r20,r31,r30 = … + C+D<<16 + D<<24 - mov r19, r18 - movw r22, r20 - movw r24, r30 - lsl r19 - rol r21 +#endif + ;; ! C-ABI: m=X +_divmod85: ; r25,r24,r23,r22 = A,B,C,D + movw r18, r22 + movw r20, r24 + lsl r21 rol r20 + rol r19 + rol r18 + clr r0 + rol r0 + sec + adc r21, r25 + adc r20, r24 + adc r19, r23 + adc r18, r22 + adc r0, r1 ; r21,r20,r19,r18,r0 = 3u + 1 + + mov r30, r21 + add r30, r20 + clr r31 rol r31 - rol r30 - add r18, r19 - adc r25, r21 - adc r24, r20 - adc r23, r31 - adc r22, r30 + add r30, r19 + adc r31, r1 + add r30, r18 + adc r31, r1 + add r30, r0 + adc r31, r1 ; r30,r31 = A+B+C+D+E + + mov r22, r31 ; + add r22, r30 ; just for the carry bit + mov r18, r31 + adc r18, r30 ; r18 = r + mov r25, r31 + adc r25, r1 + + sub r30, r21 + sbc r31, r1 ; r30,r31 = B+C+D+E + add r25, r30 + mov r24, r31 + adc r24, r1 + + sub r30, r20 + sbc r31, r1 ; r30,r31 = C+D+E + add r24, r30 + mov r23, r31 + adc r23, r1 + + sub r30, r19 + sbc r31, r1 ; r30,r31 = D+E + add r23, r30 + mov r22, r31 + adc r22, r0 + ldi r19, 85 mul r18, r19 lsl r0 - ldi r18, 33 - adc r1, r18 - # ifdef BASE85C - st X, r1 - clr r1 - # else + ldi r19, '!' + adc r1, r19 st -X, r1 - # endif + clr r1 ret - - #ifndef BASE85C + +#ifndef BASE85C .global base85_encode base85_encode: movw r26, r20 @@ -76,20 +101,20 @@ base85_encode: rcall _divmod85 rcall _divmod85 rcall _divmod85 - subi r25, -33 - st X, r25 - ldi r24, 5 - clr r1 + subi r25, -'!' + st -X, r25 ret - # endif +#endif ; uint32_t mul85(uint32_t u, uint8_t m); - #ifdef BASE85C +#ifdef BASE85C .global mul85 mul85: - # endif +#endif ;; TODO for SPACE: inline this + + ;; big endian r22/r23/r24/r25 *= 85; … += r20 _mul85: ldi r21, 85 mul r25, r21 @@ -106,12 +131,12 @@ _mul85: mov r25, r18 mov r23, r30 mov r22, r31 - # ifdef BASE85C +#ifdef BASE85C clr r1 - # endif +#endif ret - #ifndef BASE85C +#ifndef BASE85C .global base85_decode base85_decode: @@ -125,7 +150,9 @@ base85_decode: clr r22 ldi r17, 5 1: - ld r20, Y+ + ld r20, X+ + subi r20, '!' + brcs 2f cpi r20, 85 brcc 2f rcall _mul85 @@ -145,4 +172,4 @@ base85_decode: clr r1 ret - # endif +#endif From 302b6bb8d7ac649186ee3631d57e777220905277 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20I=2E=20B=C3=B6ttcher?= Date: Tue, 3 Feb 2026 16:00:14 +0100 Subject: [PATCH 02/10] hallo: early debaug code, about to be removed --- src/hallo.c | 150 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 src/hallo.c diff --git a/src/hallo.c b/src/hallo.c new file mode 100644 index 0000000..7e703fc --- /dev/null +++ b/src/hallo.c @@ -0,0 +1,150 @@ + // +// dose.c +// + +// !!! int = int8_t + +#include +#include + +#include +#include + +#include "config.h" +#include "uart.h" +#include "pwm.h" + +//////////////////////////////////////////////////////////////////////////////// +// +// main() + +section_status(main) struct magic magic; + +__attribute__((noinline)) +void clk10(void) +{ +#define NOP "\tnop\n" + VPORTB.OUT &= 0xfe; + __asm__ volatile(NOP NOP NOP NOP NOP NOP NOP NOP NOP); + VPORTB.OUT |= 1; +} + +__attribute__((section(".eeprom"))) const uint16_t eemagic = 0x7a10; + +void printc(uint8_t c) +{ + while (!(USART0.STATUS & USART_DREIF_bm)); + USART0.TXDATAL = c; +} +void print(char *s) +{ + while (*s) + printc(*s++); +} +void printx(uint8_t c) +{ + c &= 0xf; + c += '0'; + if (c > '9') + c += 'a' - '9' + 1; + printc(c); +} +void print_hex(uint8_t *s, uint8_t n) +{ + while (n--) { + printc(' '); + printx(*s>>4); + printx(*s++); + } +} + +void parse_commandx(const uint8_t *s, uint8_t n) { + send_char('%'); + send_hex(s, n); + send_eol(); +} + +extern volatile uint8_t uart_rx_mes; + +int main() +{ + VPORTB.OUT = 0b00000101; + VPORTB.DIR = 0b00000101; + PORTB.PIN3CTRL = PORT_PULLUPEN_bm; + USART0.CTRLA = 0; + USART0.CTRLB = 0; + USART0.BAUD = 40000000/115200; + USART0.CTRLC = USART_CHSIZE_8BIT_gc; + USART0.CTRLB = USART_TXEN_bm | USART_RXEN_bm; + clk10(); + + while (CLKCTRL.MCLKCTRLB != config.cpu_clk) { + CCP = CCP_IOREG_gc; + CLKCTRL.MCLKCTRLB = config.cpu_clk; + } + + clk10(); + + sleep_enable(); + magic.magic = config.magic; + while (magic.magic != MAGIC || config.version != VERSION) + sleep_cpu(); + + VPORTB.IN = 1; + + static struct USART_struct u; + + u = USART0; + + VPORTB.IN = 1; + + print("\nUSART0 mm "); + print_hex((void*)&u, sizeof(u)); + print("\nVPORTS mm "); + print_hex((void*)0, 12); + print("\n.....\n"); + + VPORTB.IN = 1; + + apply_config(); + + PWM.CTRLA = 0; + + u = USART0; + +// VPORTA.DIR = 0b00000000; +// VPORTB.DIR = 0b00000101; + + VPORTB.IN = 1; + + print("\nUSART0 ac "); + print_hex((void*)&u, sizeof(u)); + print("\nVPORTS ac "); + print_hex((void*)0, 12); + print("\n.....\n"); + + VPORTB.IN = 1; + + magic.reset_source = RSTCTRL.RSTFR; + RSTCTRL.RSTFR = magic.reset_source; + send_str("\nV Turbo Hallo V0.0"); + send_hex_byte_eol(magic.reset_source); + + VPORTB.IN = 1; + + sei(); + send_str("B.OUT "); send_hex_byte_eol(PORTB.OUT); + send_str("CTRLA "); send_hex_byte_eol(u.CTRLA); + send_str("CTRLB "); send_hex_byte_eol(u.CTRLB); + send_str("CTRLB "); send_hex_byte_eol(u.CTRLC); + send_str("BAUDL "); send_hex_byte_eol(u.BAUD); + send_str("BAUDH "); send_hex_byte_eol(u.BAUD>>8); + + VPORTB.IN = 1; + + while (1) { + sei(); + sleep_cpu(); + command(); + } +} From 6fcdb271d31845e280f25de3a08e9129ab701bd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20I=2E=20B=C3=B6ttcher?= Date: Wed, 4 Feb 2026 21:09:07 +0100 Subject: [PATCH 03/10] update assy drawing --- gerber/turbo-bot.png | Bin 25793 -> 31610 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/gerber/turbo-bot.png b/gerber/turbo-bot.png index d63435101f36a8bddd25ec40787d7c6292cb79fb..e1245b1b96ed4e0b5e5894d412f4e0a9119c045d 100644 GIT binary patch literal 31610 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 literal 25793 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

77{cZ6(p?i%@ya{0OPRlP2quwNXE(3q8ai1!^ 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 B Date: Wed, 4 Feb 2026 21:19:21 +0100 Subject: [PATCH 04/10] uart, adc, pwm debugged hallo.c is a temporary slim version of dose.c. With python support code. The First nFET characteristic is recorded. --- .gitignore | 2 + src/Makefile | 15 +- src/adc.c | 35 ++-- src/base85.py | 25 +++ src/cmd.c | 34 ++-- src/config.h | 7 +- src/dose.py | 208 +++++++++++++++++++++++ src/hallo.c | 111 +------------ src/io.S | 1 + src/iotn424.py | 443 +++++++++++++++++++++++++++++++++++++++++++++++++ src/map.py | 33 ++++ src/prompt.py | 20 +++ src/rtc.c | 11 +- src/uart.c | 10 +- src/uart.h | 3 - src/uart.py | 115 +++++++++++++ src/uart_tx.S | 3 +- 17 files changed, 916 insertions(+), 160 deletions(-) create mode 100644 src/base85.py create mode 100755 src/dose.py create mode 100644 src/io.S create mode 100644 src/iotn424.py create mode 100644 src/map.py create mode 100644 src/prompt.py create mode 100755 src/uart.py diff --git a/.gitignore b/.gitignore index abf7b0f..9ffcb3d 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,5 @@ src/data BCH-Codes/ *.odt *.userrow +*.m +src/data diff --git a/src/Makefile b/src/Makefile index a28ac24..6c621b1 100644 --- a/src/Makefile +++ b/src/Makefile @@ -6,8 +6,15 @@ PATH:=/usr/local/bin:$(PATH) default: all all: $(PROJ).hex $(PROJ)_all -dose_all: dose.eeprom dose.userrow +hallo_all: hallo.eeprom +CFLAGS_hallo = -DHAVE_nFETs -DSEND_HEX -DHALLO +MCU_hallo = attiny424 +SN_hallo = 1 +C_FILES_hallo = config.c uart.c cmd.c base85.c rtc.c adc.c pwm.c +S_FILES_hallo = uart_tx.S base85a.S + +dose_all: dose.eeprom dose.userrow SN_dose = 1 MCU_dose = $(MCU_$(VAR)) MCU_nFETs = attiny424 @@ -48,6 +55,9 @@ OBJS = $(patsubst %.c, %.o, $(C_FILES)) $(patsubst %.S, %.o, $(S_FILES)) %.o: %.S $(CC) $(CFLAGS) -c $< +%.m: %.S + $(CC) $(CFLAGS) -E -dM $< > $@ + -include *.d LDFLAGS = -Teeprom.ld @@ -97,6 +107,7 @@ AVRDUDE_PORT = /dev/ttyUSB1 AD = $(AVRDUDE) -p $(pMCU-$(MCU)) -P $(AVRDUDE_PORT) -c $(AVRDUDE_PROGRAMMER) sig_dose = 0x1e 0x92 0x2c +sig_hallo = 0x1e 0x92 0x2c id: $(PROJ).id %.id: @@ -132,3 +143,5 @@ eeprom.eeprom: %.eeprom.burn: %.eeprom $(AD) -U eeprom:v:$< || $(AD) -U eeprom:w:$< +%.userrow.burn: %.userrow + $(AD) -U userrow:v:$< || $(AD) -U userrow:w:$< diff --git a/src/adc.c b/src/adc.c index d9cf8a9..03e45b6 100644 --- a/src/adc.c +++ b/src/adc.c @@ -8,12 +8,18 @@ #include "pwm.h" #include "rtc.h" +#ifdef ADC_IMMEDIATE +# define ADC_TRIGGER ADC_MODE_BURST_SCALING_gc | ADC_START_IMMEDIATE_gc +#else +# define ADC_TRIGGER ADC_MODE_SERIES_SCALING_gc | ADC_START_EVENT_TRIGGER_gc +#endif + enum adc_conf_parameter { INP = ADC_VIA_ADC_gc, INN = ADC_VIA_ADC_gc, REF = 10 << ADC_TIMEBASE_gp, - MODE = ADC_MODE_SERIES_SCALING_gc | ADC_START_EVENT_TRIGGER_gc, - MODE_DIFF = ADC_MODE_BURST_SCALING_gc | ADC_START_IMMEDIATE_gc | ADC_DIFF_bm, + MODE = ADC_TRIGGER, + MODE_DIFF = ADC_TRIGGER | ADC_DIFF_bm, }; __attribute__((section(".eeprom1"))) @@ -81,7 +87,7 @@ struct_ioconf(adc_config) = { conf_prefix(ADC), conf_io(ADC.CTRLA, ADC_ENABLE_bm), conf_io(ADC.CTRLB, ADC_PRESC_DIV10_gc), - conf_io(ADC.CTRLE, 250 << ADC_SAMPDUR_gp), + conf_io(ADC.CTRLE, 250), conf_io(ADC.CTRLF, ADC_SAMPNUM_ACC256_gc | ADC_LEFTADJ_bm), conf_io(ADC.INTCTRL, ADC_RESRDY_bm), conf_prefix(EVSYS), @@ -91,8 +97,6 @@ struct_ioconf(adc_config) = { void start_adc() { - if (!adc_conf->mode) - return; ADC.COMMAND = 0; adc_current = 0; start_conversion(adc_conf); @@ -114,7 +118,7 @@ ISR(ADC0_RESRDY_vect) } } stop: - ADC.CTRLA = 0; + ADC.COMMAND = 0; adc_current = N_ADC; } @@ -136,7 +140,7 @@ ISR(ADC0_RESRDY_vect, ISR_NAKED) "push r31" "\n\t" "lds r24, adc_current" "\n\t" "cpi r24, %[NADC]" "\n\t" - "brcc 3f" "\n\t" + "brcc 2f" "\n\t" "mov r30, r24" "\n\t" "ldi r31, 0" "\n\t" "lsl r30" "\n\t" @@ -148,7 +152,9 @@ ISR(ADC0_RESRDY_vect, ISR_NAKED) "std Z+1, r25" "\n" "1:" "\n\t" "subi r24, -1" "\n\t" - "cpi r24, %[NADC]" "\n\t" + "cpi r24, %[NADC]" "\n" + "2:" "\n\t" + "clr r25" "\n\t" "brcc 3f" "\n\t" "mov r30, r24" "\n\t" "ldi r31, 0" "\n\t" @@ -164,9 +170,9 @@ ISR(ADC0_RESRDY_vect, ISR_NAKED) "sts %[MUXPOS], r25" "\n\t" "ld r25, Z" "\n\t" "tst r25" "\n\t" - "breq 1b" "\n\t" - "sts %[COMMAND], r25" "\n" - "2:" "\n\t" + "breq 1b" "\n" + "3:" "\n\t" + "sts %[COMMAND], r25" "\n\t" "sts adc_current, r24" "\n\t" "pop r31" "\n\t" "pop r30" "\n\t" @@ -175,16 +181,9 @@ ISR(ADC0_RESRDY_vect, ISR_NAKED) "out __SREG__, r24" "\n\t" "pop r24" "\n\t" "reti" "\n" - "3:" "\n\t" - "clr r24" "\n\t" - "sts %[COMMAND], r24" "\n\t" - "sts %[CTRLA], r24" "\n\t" - "ldi r24, %[NADC]" "\n\t" - "rjmp 2b" "\n" : : [NADC] "n" (N_ADC), [RESULT] "n" (&ADC.RESULT), - [CTRLA] "n" (&ADC.CTRLA), [CTRLC] "n" (&ADC.CTRLC), [MUXPOS] "n" (&ADC.MUXPOS), [MUXNEG] "n" (&ADC.MUXNEG), diff --git a/src/base85.py b/src/base85.py new file mode 100644 index 0000000..43b4339 --- /dev/null +++ b/src/base85.py @@ -0,0 +1,25 @@ + +import struct + +def base85_encode(b): + uu = list(struct.unpack(f">{len(b)//4}I", b)) + uu.reverse() + r = [] + for u in uu: + for i in range(5): + r[0:0] = [u % 85 + 33] + u //= 85 + return bytes(r) + +def base85_decode(s): + uu = []; + for j in range(0,len(s),5): + u = 0 + for i in range(5): + m = ord(s[j+i:j+i+1]) - 33; + if m < 0 or m >= 85: + raise ValueError(f"invalid base85 char {s}[{i}]") + u *= 85 + u += m + uu.append(u) + return struct.pack(f">{len(uu)}I", *uu) diff --git a/src/cmd.c b/src/cmd.c index e3be56e..4834b7c 100644 --- a/src/cmd.c +++ b/src/cmd.c @@ -58,14 +58,13 @@ void base85_send_buffer(const uint8_t *buf) "rjmp _send_str26" "\n" ); } - #endif static inline const uint8_t *base85_fill_buffer(const uint8_t *s) { base85_error = 0; - for (int i=0; !base85_error && i= N_ADC) { - if (cmd_flag('<')) - base85_send_buffer((void*)adc_readings); - if (cmd_flag('!')) - start_adc(); + if (cmd_flag('!')) + start_adc(); + if (cmd_flag('<')) { + bptr = (void*)adc_readings; + goto send_buffer; } break; +#ifndef HALLO case 'B': if (cmd_flag('@')) pipe.valid = 0; @@ -246,11 +248,13 @@ void parse_command(const uint8_t *s, uint8_t n) fpga_cmd((void*)cmd_buffer); goto send_buffer; #endif +#endif // HALLO case 'M': if (!have_b) goto error; r = poke((void*)cmd_buffer, cmd_flag('!')); send_buffer: + send_char(' '); base85_send_buffer(bptr); break; default: diff --git a/src/config.h b/src/config.h index 155a483..b4f3369 100644 --- a/src/config.h +++ b/src/config.h @@ -13,7 +13,6 @@ struct config { uint8_t cron; uint8_t flash_page_size; uint16_t burn_page; - uint16_t erase_page; uint16_t write_buffer; uint16_t read_array; uint16_t read_buffer[2]; @@ -28,7 +27,7 @@ struct config { enum magic_flags { #ifdef HAVE_nFETs MAGIC = 0xD0, - VERSION = 0x01, + VERSION = 0x00, #endif #ifdef HAVE_FPGA MAGIC = 0xC5, @@ -131,9 +130,9 @@ void apply_config() "ldi r30, lo8(ee9_start)" "\n\t" "ldi r31, hi8(ee9_start)" "\n\t" "ldi r24, lo8(ee9_size)" "\n\t" - "clr r26" "\n" + "clr r25" "\n" "1:" "\n\t" - "mov r27, r26" "\n\t" + "mov r27, r25" "\n\t" "subi r24, 2" "\n\t" "brcs 3f" "\n" "2:" "\n\t" diff --git a/src/dose.py b/src/dose.py new file mode 100755 index 0000000..b005b6b --- /dev/null +++ b/src/dose.py @@ -0,0 +1,208 @@ +#! /usr/bin/ipython3 --profile=turbo_dose + +import sys, time, getopt, fileinput, struct +import uart +from base85 import base85_encode, base85_decode +from map import memmap +from iotn424 import SFR + +options, files = getopt.gnu_getopt(sys.argv[1:], "F:o:M:", ["debug", "tty=", "output=", "map="]) + +tty = "/dev/ttyUSB1" +baud = 115200 +out = None +debug = None +map_fn = "hallo.map" + +def Debug(e, *a): + if debug: + import traceback + sys.stdout.flush() + print("xdebug", a, repr(e), file=sys.stderr) + traceback.print_exception(e, limit=-2, file=sys.stderr) + +for o,v in options: + + if o == "--debug": + debug = True + + if o in "-F --tty": + tty = v + v = v.split(",", 1) + if v[1:]: + tty = v[0] + baud = int(v[1]) + do_clock = True + + if o in "--output": + if out: + raise ValueError("cannot have multiple --outputs") + if v=="-": + out = sys.stdout + elif v=="--": + out = sys.stderr + else: + out = open(v, "a") + + if o in "-M --map": + map_fn = v + +if not out: + out = sys.stdout + +if len(files)==1: + if "/dev/tty" in files[0]: + tty = files[0] + files = [] + +mmap = memmap(map_fn) + +class dose_cmd(uart.uart): + + def cmd(self, c, d=None, timeout=0.2): + if not isinstance(c, bytes): + c = c.encode() + if d: + c += b" " + base85_encode(d) + self.flush() + self.ucmd(c) + r = self.resp(timeout) + while r and (r[0] != b'#'[0] or r[1] != c[0]): + r = self.resp(timeout) + d = None + if not r: + return r, d + r = r.split() + e = int(r[-1], 16) + if len(r)==3: + d = base85_decode(r[1]) + if self.verbose: + print(f"{r[0]}[{r[2]}] {"".join([f"{b:02x}" for b in d])}", file=sys.stderr) + self.last_cmd = c[0:1] + return r[0], e, d + + def poke(self, a, d, s=None, ccp=0): + return self.peek(a, d, s, ccp) + + def peek(self, a=None, d=None, s=None, ccp=0): + name = None + if a in SFR: + name = a + a, ss = SFR[a] + if s is None: + s = ss + if a in mmap: + a, ss = mmap[a] + if ss and s is None: + s = ss + if s is None and isinstance(d, int): + s = d + d = None + FF = {1: "B", 2: "H", 4:"I"} + if a is None: + cc, nn, dd = self.cmd(b"M=") + else: + if d is None: + c = b"M" + d = b'' + if s is None: + s = 12 + else: + c = b'M!' + if isinstance(d, int) and s in FF: + d = struct.pack(f"<{FF[s]}", d) + else: + d = bytes(d) + if s is None: + s = len(d) + d = struct.pack(" '9') - c += 'a' - '9' + 1; - printc(c); -} -void print_hex(uint8_t *s, uint8_t n) -{ - while (n--) { - printc(' '); - printx(*s>>4); - printx(*s++); - } -} - -void parse_commandx(const uint8_t *s, uint8_t n) { - send_char('%'); - send_hex(s, n); - send_eol(); -} - -extern volatile uint8_t uart_rx_mes; - int main() { - VPORTB.OUT = 0b00000101; - VPORTB.DIR = 0b00000101; - PORTB.PIN3CTRL = PORT_PULLUPEN_bm; - USART0.CTRLA = 0; - USART0.CTRLB = 0; - USART0.BAUD = 40000000/115200; - USART0.CTRLC = USART_CHSIZE_8BIT_gc; - USART0.CTRLB = USART_TXEN_bm | USART_RXEN_bm; - clk10(); - while (CLKCTRL.MCLKCTRLB != config.cpu_clk) { CCP = CCP_IOREG_gc; CLKCTRL.MCLKCTRLB = config.cpu_clk; } - - clk10(); sleep_enable(); magic.magic = config.magic; while (magic.magic != MAGIC || config.version != VERSION) sleep_cpu(); - VPORTB.IN = 1; - - static struct USART_struct u; - - u = USART0; - - VPORTB.IN = 1; - - print("\nUSART0 mm "); - print_hex((void*)&u, sizeof(u)); - print("\nVPORTS mm "); - print_hex((void*)0, 12); - print("\n.....\n"); - - VPORTB.IN = 1; - apply_config(); - - PWM.CTRLA = 0; - - u = USART0; - -// VPORTA.DIR = 0b00000000; -// VPORTB.DIR = 0b00000101; - - VPORTB.IN = 1; - - print("\nUSART0 ac "); - print_hex((void*)&u, sizeof(u)); - print("\nVPORTS ac "); - print_hex((void*)0, 12); - print("\n.....\n"); - - VPORTB.IN = 1; - magic.reset_source = RSTCTRL.RSTFR; + RSTCTRL.RSTFR = magic.reset_source; send_str("\nV Turbo Hallo V0.0"); send_hex_byte_eol(magic.reset_source); - VPORTB.IN = 1; - - sei(); - send_str("B.OUT "); send_hex_byte_eol(PORTB.OUT); - send_str("CTRLA "); send_hex_byte_eol(u.CTRLA); - send_str("CTRLB "); send_hex_byte_eol(u.CTRLB); - send_str("CTRLB "); send_hex_byte_eol(u.CTRLC); - send_str("BAUDL "); send_hex_byte_eol(u.BAUD); - send_str("BAUDH "); send_hex_byte_eol(u.BAUD>>8); - - VPORTB.IN = 1; - while (1) { sei(); sleep_cpu(); diff --git a/src/io.S b/src/io.S new file mode 100644 index 0000000..cf51b78 --- /dev/null +++ b/src/io.S @@ -0,0 +1 @@ +#include diff --git a/src/iotn424.py b/src/iotn424.py new file mode 100644 index 0000000..f62cb93 --- /dev/null +++ b/src/iotn424.py @@ -0,0 +1,443 @@ + +SFR = { + "USART0_TXDATAL": (0x0802, 1), + "PORTB_PIN0CTRL": (0x0430, 1), + "ADC0_WINHTH": (0x061F, 1), + "ADC0_WINHTL": (0x061E, 1), + "RTC_PITCTRLA": (0x0150, 1), + "USERROW_USERROW13": (0x130D, 1), + "USART0_RXDATAH": (0x0801, 1), + "USART0_RXDATAL": (0x0800, 1), + "USERROW_USERROW17": (0x1311, 1), + "TCA0_SPLIT_LCMP0": (0x0A28, 1), + "PORTB_DIRCLR": (0x0422, 1), + "TCA0_SINGLE_CMP1H": (0x0A2B, 1), + "USERROW_USERROW19": (0x1313, 1), + "PORTB_OUTSET": (0x0425, 1), + "USART0_BAUDH": (0x0809, 1), + "USART0_BAUDL": (0x0808, 1), + "TCA0_SPLIT_LCNT": (0x0A20, 1), + "TWI0_SCTRLB": (0x08AA, 1), + "PORTC_PORTCTRL": (0x044A, 1), + "PORTB_INTFLAGS": (0x0429, 1), + "CCL_LUT0CTRLA": (0x01C8, 1), + "CCL_LUT0CTRLB": (0x01C9, 1), + "CCL_LUT0CTRLC": (0x01CA, 1), + "TCA0_SPLIT_CTRLECLR": (0x0A04, 1), + "NVMCTRL_INTFLAGS": (0x1004, 1), + "CPUINT_CTRLA": (0x0110, 1), + "EVSYS_USERTCB1COUNT": (0x01B3, 1), + "VPORTA_OUT": (0x0001, 1), + "BOD_INTFLAGS": (0x008A, 1), + "ADC0_PGACTRL": (0x060B, 1), + "CLKCTRL_OSC20MCALIBA": (0x0071, 1), + "BOD_VLMCTRLA": (0x0088, 1), + "TCA0_SPLIT_LPER": (0x0A26, 1), + "USERROW_USERROW21": (0x1315, 1), + "USERROW_USERROW25": (0x1319, 1), + "PORTC_OUTSET": (0x0445, 1), + "USERROW_USERROW29": (0x131D, 1), + "EVSYS_USERTCB1CAPT": (0x01B2, 1), + "SLPCTRL_CTRLA": (0x0050, 1), + "ADC0_WINLTL": (0x061C, 1), + "TCA0_SPLIT_CTRLB": (0x0A01, 1), + "TCA0_SPLIT_CTRLD": (0x0A03, 1), + "TCA0_SINGLE_CMP0H": (0x0A29, 1), + "TCA0_SINGLE_CMP0L": (0x0A28, 1), + "PORTC_DIR": (0x0440, 1), + "TCA0_SINGLE_CMP1L": (0x0A2A, 1), + "ADC0_INTFLAGS": (0x0605, 1), + "USERROW_USERROW2": (0x1302, 1), + "USART1_DBGCTRL": (0x082B, 1), + "TWI0_MDATA": (0x08A8, 1), + "CCL_TRUTH0": (0x01CB, 1), + "PORTB_PIN3CTRL": (0x0433, 1), + "TCA0_SINGLE_CMP2H": (0x0A2D, 1), + "PORTA_PIN0CTRL": (0x0410, 1), + "TCA0_SINGLE_CMP2L": (0x0A2C, 1), + "CCL_LUT1CTRLA": (0x01CC, 1), + "CCL_LUT1CTRLC": (0x01CE, 1), + "SIGROW_SERNUM0": (0x1103, 1), + "SIGROW_SERNUM1": (0x1104, 1), + "SIGROW_SERNUM2": (0x1105, 1), + "SIGROW_SERNUM3": (0x1106, 1), + "SIGROW_SERNUM4": (0x1107, 1), + "SIGROW_SERNUM5": (0x1108, 1), + "SIGROW_SERNUM6": (0x1109, 1), + "SIGROW_SERNUM7": (0x110A, 1), + "SIGROW_SERNUM8": (0x110B, 1), + "SIGROW_SERNUM9": (0x110C, 1), + "TCA0_SINGLE_PERBUFH": (0x0A37, 1), + "TCA0_SINGLE_PERBUFL": (0x0A36, 1), + "USERROW_USERROW31": (0x131F, 1), + "TCB0_CTRLA": (0x0A80, 1), + "TCB0_CTRLB": (0x0A81, 1), + "CPUINT_LVL0PRI": (0x0112, 1), + "VPORTA_IN": (0x0002, 1), + "EVSYS_USERTCB0CAPT": (0x01B0, 1), + "PORTC_OUT": (0x0444, 1), + "RTC_PITINTCTRL": (0x0152, 1), + "PORTB_IN": (0x0428, 1), + "TCA0_SINGLE_CTRLB": (0x0A01, 1), + "ADC0_TEMP0": (0x0618, 1), + "PORTB_DIR": (0x0420, 1), + "ADC0_TEMP2": (0x061A, 1), + "GPIO0": (0x001C, 1), + "GPIO1": (0x001D, 1), + "GPIO3": (0x001F, 1), + "RTC_CNTH": (0x0149, 1), + "EVSYS_CHANNEL0": (0x0190, 1), + "EVSYS_CHANNEL1": (0x0191, 1), + "EVSYS_CHANNEL2": (0x0192, 1), + "EVSYS_CHANNEL3": (0x0193, 1), + "EVSYS_CHANNEL4": (0x0194, 1), + "EVSYS_CHANNEL5": (0x0195, 1), + "PORTC_DIRSET": (0x0441, 1), + "CCL_LUT3CTRLA": (0x01D4, 1), + "TWI0_SADDRMASK": (0x08AE, 1), + "TCA0_SINGLE_CTRLECLR": (0x0A04, 1), + "PORTA_DIRCLR": (0x0402, 1), + "PORTC_PIN2CTRL": (0x0452, 1), + "CCP": (0x0034, 1), + "CLKCTRL_OSC20MCALIBB": (0x0072, 1), + "USART0_CTRLA": (0x0805, 1), + "TWI0_SCTRLA": (0x08A9, 1), + "TCB1_CCMPL": (0x0A9C, 1), + "PORTC_IN": (0x0448, 1), + "SIGROW_DEVICEID2": (0x1102, 1), + "TCB1_INTCTRL": (0x0A95, 1), + "USART0_TXPLCTRL": (0x080D, 1), + "FUSE_WDTCFG": (0x1280, 1), + "CCL_LUT2CTRLB": (0x01D1, 1), + "CCL_LUT2CTRLC": (0x01D2, 1), + "PORTB_OUT": (0x0424, 1), + "PORTMUX_USARTROUTEA": (0x05E2, 1), + "CLKCTRL_OSC32KCTRLA": (0x0078, 1), + "TWI0_SDATA": (0x08AD, 1), + "PORTA_DIR": (0x0400, 1), + "PORTA_IN": (0x0408, 1), + "ADC0_DBGCTRL": (0x0607, 1), + "TCB1_CNTL": (0x0A9A, 1), + "PORTA_PIN1CTRL": (0x0411, 1), + "TCB0_TEMP": (0x0A89, 1), + "TCA0_SINGLE_CTRLA": (0x0A00, 1), + "TCA0_SINGLE_CTRLC": (0x0A02, 1), + "TCA0_SINGLE_CTRLD": (0x0A03, 1), + "ADC0_MUXPOS": (0x060C, 1), + "TCB1_INTFLAGS": (0x0A96, 1), + "EVSYS_USERADC0START": (0x01A8, 1), + "NVMCTRL_ADDRH": (0x1009, 1), + "NVMCTRL_ADDRL": (0x1008, 1), + "TWI0_DBGCTRL": (0x08A2, 1), + "RTC_PITDBGCTRL": (0x0155, 1), + "SIGROW_OSCCAL20M1": (0x111B, 1), + "VREF_CTRLA": (0x00A0, 1), + "TCA0_SPLIT_INTCTRL": (0x0A0A, 1), + "TCA0_SINGLE_CMP0BUFH": (0x0A39, 1), + "AC0_INTCTRL": (0x0686, 1), + "TCA0_SINGLE_CNTL": (0x0A20, 1), + "TCA0_SPLIT_HCMP0": (0x0A29, 1), + "TCA0_SPLIT_HCMP1": (0x0A2B, 1), + "TCA0_SPLIT_HCMP2": (0x0A2D, 1), + "TCA0_SINGLE_CMP2BUFH": (0x0A3D, 1), + "TCA0_SINGLE_CMP2BUFL": (0x0A3C, 1), + "USART1_TXPLCTRL": (0x082D, 1), + "PORTA_OUT": (0x0404, 1), + "CLKCTRL_MCLKSTATUS": (0x0063, 1), + "PORTC_PIN4CTRL": (0x0454, 1), + "PORTC_OUTCLR": (0x0446, 1), + "CCL_LUT3CTRLB": (0x01D5, 1), + "WDT_CTRLA": (0x0100, 1), + "SIGROW_DEVICEID0": (0x1100, 1), + "SIGROW_DEVICEID1": (0x1101, 1), + "TCB0_CNTL": (0x0A8A, 1), + "USERROW_USERROW1": (0x1301, 1), + "PORTMUX_CCLROUTEA": (0x05E1, 1), + "PORTC_PIN7CTRL": (0x0457, 1), + "ADC0_CTRLB": (0x0601, 1), + "ADC0_CTRLC": (0x0602, 1), + "ADC0_CTRLD": (0x0603, 1), + "ADC0_CTRLE": (0x0608, 1), + "ADC0_CTRLF": (0x0609, 1), + "RTC_INTCTRL": (0x0142, 1), + "RTC_PERL": (0x014A, 1), + "GPIO2": (0x001E, 1), + "PORTA_PIN7CTRL": (0x0417, 1), + "LOCKBIT_LOCKBIT": (0x128A, 1), + "SIGROW_OSCCAL20M0": (0x111A, 1), + "TCB1_TEMP": (0x0A99, 1), + "FUSE_SYSCFG1": (0x1286, 1), + "PORTB_DIRSET": (0x0421, 1), + "PORTC_OUTTGL": (0x0447, 1), + "ADC0_CTRLA": (0x0600, 1), + "TCA0_SPLIT_LCMP1": (0x0A2A, 1), + "TCA0_SPLIT_LCMP2": (0x0A2C, 1), + "TCA0_SINGLE_CMP1BUFH": (0x0A3B, 1), + "TCA0_SINGLE_CMP1BUFL": (0x0A3A, 1), + "USART1_TXDATAL": (0x0822, 1), + "ADC0_TEMP1": (0x0619, 1), + "TCA0_SPLIT_INTFLAGS": (0x0A0B, 1), + "PORTMUX_SPIROUTEA": (0x05E3, 1), + "TCA0_SPLIT_CTRLESET": (0x0A05, 1), + "TCA0_SINGLE_CTRLFSET": (0x0A07, 1), + "SPI0_INTCTRL": (0x08C2, 1), + "PORTC_PIN6CTRL": (0x0456, 1), + "NVMCTRL_STATUS": (0x1002, 1), + "CCL_TRUTH1": (0x01CF, 1), + "CCL_TRUTH2": (0x01D3, 1), + "CLKCTRL_MCLKLOCK": (0x0062, 1), + "SYSCFG_REVID": (0x0F01, 1), + "USERROW_USERROW10": (0x130A, 1), + "USERROW_USERROW11": (0x130B, 1), + "USERROW_USERROW12": (0x130C, 1), + "USERROW_USERROW14": (0x130E, 1), + "USERROW_USERROW15": (0x130F, 1), + "USERROW_USERROW16": (0x1310, 1), + "USERROW_USERROW18": (0x1312, 1), + "USART1_CTRLA": (0x0825, 1), + "USART1_CTRLB": (0x0826, 1), + "USART1_CTRLC": (0x0827, 1), + "USART1_CTRLD": (0x082A, 1), + "USERROW_USERROW20": (0x1314, 1), + "USERROW_USERROW22": (0x1316, 1), + "USERROW_USERROW23": (0x1317, 1), + "USERROW_USERROW24": (0x1318, 1), + "USERROW_USERROW26": (0x131A, 1), + "USERROW_USERROW27": (0x131B, 1), + "USERROW_USERROW28": (0x131C, 1), + "VPORTC_OUT": (0x0009, 1), + "CCL_LUT2CTRLA": (0x01D0, 1), + "USERROW_USERROW30": (0x131E, 1), + "TCA0_SINGLE_CMP0BUFL": (0x0A38, 1), + "PORTA_OUTSET": (0x0405, 1), + "FUSE_OSCCFG": (0x1282, 1), + "ADC0_STATUS": (0x0606, 1), + "CPU_SREG": (0x003F, 1), + "AC0_CTRLA": (0x0680, 1), + "TCA0_SINGLE_PERH": (0x0A27, 1), + "EVSYS_USEREVSYSEVOUTA": (0x01A9, 1), + "EVSYS_USEREVSYSEVOUTB": (0x01AA, 1), + "TCA0_SINGLE_PERL": (0x0A26, 1), + "TCA0_SPLIT_DBGCTRL": (0x0A0E, 1), + "PORTB_PIN7CTRL": (0x0437, 1), + "TCB0_INTCTRL": (0x0A85, 1), + "PORTC_PIN5CTRL": (0x0455, 1), + "AC0_STATUS": (0x0687, 1), + "CLKCTRL_OSC20MCTRLA": (0x0070, 1), + "TCB0_INTFLAGS": (0x0A86, 1), + "ADC0_SAMPLEL": (0x0614, 1), + "RTC_DBGCTRL": (0x0145, 1), + "CLKCTRL_MCLKCTRLA": (0x0060, 1), + "CLKCTRL_MCLKCTRLB": (0x0061, 1), + "USART1_BAUDH": (0x0829, 1), + "USART1_BAUDL": (0x0828, 1), + "VPORTB_IN": (0x0006, 1), + "GPIO_GPIOR0": (0x001C, 1), + "GPIO_GPIOR1": (0x001D, 1), + "GPIO_GPIOR2": (0x001E, 1), + "GPIO_GPIOR3": (0x001F, 1), + "BOD_INTCTRL": (0x0089, 1), + "CPU_SPH": (0x003E, 1), + "CPU_SPL": (0x003D, 1), + "SIGROW_TEMPSENSE0": (0x1120, 1), + "SIGROW_TEMPSENSE1": (0x1121, 1), + "CLKCTRL_XOSC32KCTRLA": (0x007C, 1), + "AC0_DACREF": (0x0684, 1), + "ADC0_SAMPLEH": (0x0615, 1), + "CRCSCAN_STATUS": (0x0122, 1), + "RTC_PERH": (0x014B, 1), + "TCA0_SINGLE_CTRLESET": (0x0A05, 1), + "TCA0_SINGLE_CTRLFCLR": (0x0A06, 1), + "PORTC_INTFLAGS": (0x0449, 1), + "PORTA_DIRSET": (0x0401, 1), + "PORTB_OUTTGL": (0x0427, 1), + "TCB0_STATUS": (0x0A87, 1), + "PORTB_PIN6CTRL": (0x0436, 1), + "TCA0_SINGLE_CNTH": (0x0A21, 1), + "USERROW_USERROW0": (0x1300, 1), + "USERROW_USERROW3": (0x1303, 1), + "USERROW_USERROW4": (0x1304, 1), + "USERROW_USERROW5": (0x1305, 1), + "USERROW_USERROW7": (0x1307, 1), + "USERROW_USERROW8": (0x1308, 1), + "USERROW_USERROW9": (0x1309, 1), + "TCB1_EVCTRL": (0x0A94, 1), + "TCB1_DBGCTRL": (0x0A98, 1), + "SIGROW_OSCCAL16M0": (0x1118, 1), + "SIGROW_OSCCAL16M1": (0x1119, 1), + "EVSYS_USERUSART1IRDA": (0x01AD, 1), + "RTC_CLKSEL": (0x0147, 1), + "TCA0_SINGLE_INTCTRL": (0x0A0A, 1), + "USART0_DBGCTRL": (0x080B, 1), + "TCA0_SPLIT_HCNT": (0x0A21, 1), + "WDT_STATUS": (0x0101, 1), + "PORTMUX_TCAROUTEA": (0x05E4, 1), + "USART1_STATUS": (0x0824, 1), + "GPIOR0": (0x001C, 1), + "GPIOR1": (0x001D, 1), + "GPIOR2": (0x001E, 1), + "GPIOR3": (0x001F, 1), + "TCA0_SPLIT_HPER": (0x0A27, 1), + "RSTCTRL_RSTFR": (0x0040, 1), + "CCL_INTFLAGS": (0x01C7, 1), + "PORTB_OUTCLR": (0x0426, 1), + "TWI0_SSTATUS": (0x08AB, 1), + "BOD_STATUS": (0x008B, 1), + "VPORTC_INTFLAGS": (0x000B, 1), + "PORTB_PIN5CTRL": (0x0435, 1), + "SPH": (0x003E, 1), + "SPL": (0x003D, 1), + "PORTC_PIN3CTRL": (0x0453, 1), + "TCB1_CTRLA": (0x0A90, 1), + "EVSYS_USERTCA0CNTA": (0x01AE, 1), + "EVSYS_USERTCA0CNTB": (0x01AF, 1), + "NVMCTRL_INTCTRL": (0x1003, 1), + "CRCSCAN_CTRLA": (0x0120, 1), + "PORTB_DIRTGL": (0x0423, 1), + "ADC0_MUXNEG": (0x060D, 1), + "NVMCTRL_CTRLA": (0x1000, 1), + "NVMCTRL_CTRLB": (0x1001, 1), + "USART1_EVCTRL": (0x082C, 1), + "PORTC_DIRTGL": (0x0443, 1), + "CCL_CTRLA": (0x01C0, 1), + "USART1_RXPLCTRL": (0x082E, 1), + "RTC_INTFLAGS": (0x0143, 1), + "FUSE_SYSCFG0": (0x1285, 1), + "AC0_MUXCTRLA": (0x0682, 1), + "TWI0_MSTATUS": (0x08A5, 1), + "SPI0_CTRLA": (0x08C0, 1), + "SPI0_CTRLB": (0x08C1, 1), + "TCB0_CNTH": (0x0A8B, 1), + "PORTB_PORTCTRL": (0x042A, 1), + "EVSYS_USERCCLLUT0A": (0x01A0, 1), + "EVSYS_SWEVENTA": (0x0180, 1), + "PORTA_PIN6CTRL": (0x0416, 1), + "EVSYS_USERCCLLUT0B": (0x01A1, 1), + "CCL_LUT1CTRLB": (0x01CD, 1), + "EVSYS_USERCCLLUT1A": (0x01A2, 1), + "EVSYS_USERCCLLUT1B": (0x01A3, 1), + "PORTB_PIN4CTRL": (0x0434, 1), + "EVSYS_USERCCLLUT2A": (0x01A4, 1), + "EVSYS_USERCCLLUT2B": (0x01A5, 1), + "EVSYS_USERCCLLUT3A": (0x01A6, 1), + "EVSYS_USERCCLLUT3B": (0x01A7, 1), + "RTC_PITSTATUS": (0x0151, 1), + "TCA0_SINGLE_INTFLAGS": (0x0A0B, 1), + "TWI0_MBAUD": (0x08A6, 1), + "TCB0_EVCTRL": (0x0A84, 1), + "TCB1_CTRLB": (0x0A91, 1), + "TCB0_CCMPH": (0x0A8D, 1), + "TCB0_CCMPL": (0x0A8C, 1), + "PORTA_OUTTGL": (0x0407, 1), + "USART1_TXDATAH": (0x0823, 1), + "TCA0_SINGLE_EVCTRL": (0x0A09, 1), + "TCA0_SINGLE_DBGCTRL": (0x0A0E, 1), + "BOD_CTRLA": (0x0080, 1), + "USART1_RXDATAH": (0x0821, 1), + "USART1_RXDATAL": (0x0820, 1), + "TWI0_CTRLA": (0x08A0, 1), + "VPORTB_OUT": (0x0005, 1), + "RTC_TEMP": (0x0144, 1), + "PORTB_PIN1CTRL": (0x0431, 1), + "VPORTB_INTFLAGS": (0x0007, 1), + "ADC0_RESULT0": (0x0610, 1), + "ADC0_RESULT1": (0x0611, 1), + "ADC0_RESULT2": (0x0612, 1), + "PORTA_PIN5CTRL": (0x0415, 1), + "CCL_TRUTH3": (0x01D7, 1), + "CPUINT_LVL1VEC": (0x0113, 1), + "CCL_INTCTRL0": (0x01C5, 1), + "TCB1_CNTH": (0x0A9B, 1), + "PORTC_PIN1CTRL": (0x0451, 1), + "SPI0_DATA": (0x08C4, 1), + "USART0_STATUS": (0x0804, 1), + "CPU_CCP": (0x0034, 1), + "RTC_CTRLA": (0x0140, 1), + "RTC_STATUS": (0x0141, 1), + "PORTA_INTFLAGS": (0x0409, 1), + "CPUINT_STATUS": (0x0111, 1), + "BOD_CTRLB": (0x0081, 1), + "USART0_RXPLCTRL": (0x080E, 1), + "CRCSCAN_CTRLB": (0x0121, 1), + "PORTA_PORTCTRL": (0x040A, 1), + "TCB0_DBGCTRL": (0x0A88, 1), + "RTC_PITINTFLAGS": (0x0153, 1), + "PORTC_DIRCLR": (0x0442, 1), + "PORTA_DIRTGL": (0x0403, 1), + "CCL_SEQCTRL0": (0x01C1, 1), + "CCL_SEQCTRL1": (0x01C2, 1), + "PORTA_PIN4CTRL": (0x0414, 1), + "VPORTC_DIR": (0x0008, 1), + "TCB1_STATUS": (0x0A97, 1), + "FUSE_BODCFG": (0x1281, 1), + "PORTB_PIN2CTRL": (0x0432, 1), + "TWI0_MADDR": (0x08A7, 1), + "FUSE_BOOTEND": (0x1288, 1), + "EVSYS_USERUSART0IRDA": (0x01AC, 1), + "RTC_CALIB": (0x0146, 1), + "TWI0_MCTRLA": (0x08A3, 1), + "TWI0_MCTRLB": (0x08A4, 1), + "PORTC_PIN0CTRL": (0x0450, 1), + "ADC0_WINLTH": (0x061D, 1), + "RSTCTRL_SWRR": (0x0041, 1), + "PORTMUX_TCBROUTEA": (0x05E5, 1), + "VPORTC_IN": (0x000A, 1), + "TCA0_SINGLE_TEMP": (0x0A0F, 1), + "TCA0_SPLIT_CTRLA": (0x0A00, 1), + "FUSE_APPEND": (0x1287, 1), + "RTC_CMPH": (0x014D, 1), + "RTC_CMPL": (0x014C, 1), + "TCA0_SPLIT_CTRLC": (0x0A02, 1), + "CCL_LUT3CTRLC": (0x01D6, 1), + "RTC_CNTL": (0x0148, 1), + "USERROW_USERROW6": (0x1306, 1), + "VPORTA_INTFLAGS": (0x0003, 1), + "PORTA_OUTCLR": (0x0406, 1), + "PORTMUX_EVSYSROUTEA": (0x05E0, 1), + "PORTA_PIN3CTRL": (0x0413, 1), + "VPORTB_DIR": (0x0004, 1), + "ADC0_INTCTRL": (0x0604, 1), + "NVMCTRL_DATAH": (0x1007, 1), + "VPORTA_DIR": (0x0000, 1), + "VREF_CTRLB": (0x00A1, 1), + "NVMCTRL_DATAL": (0x1006, 1), + "TWI0_SADDR": (0x08AC, 1), + "USART0_CTRLB": (0x0806, 1), + "USART0_CTRLC": (0x0807, 1), + "USART0_CTRLD": (0x080A, 1), + "EVSYS_USERTCB0COUNT": (0x01B1, 1), + "TCB1_CCMPH": (0x0A9D, 1), + "SPI0_INTFLAGS": (0x08C3, 1), + "ADC0_COMMAND": (0x060A, 1), + "ADC0_RESULT3": (0x0613, 1), + "SREG": (0x003F, 1), + "PORTA_PIN2CTRL": (0x0412, 1), + "USART0_EVCTRL": (0x080C, 1), + "USART0_TXDATAH": (0x0803, 1), + "ADC0_SAMPLE": (0x0614, 2), + "TCA0_SINGLE_CMP0": (0x0A28, 2), + "TCA0_SINGLE_CMP1": (0x0A2A, 2), + "TCA0_SINGLE_CNT": (0x0A20, 2), + "RTC_CMP": (0x014C, 2), + "RTC_CNT": (0x0148, 2), + "CPU_SP": (0x003D, 2), + "TCA0_SINGLE_CMP2": (0x0A2C, 2), + "TCA0_SINGLE_CMP2BUF": (0x0A3C, 2), + "NVMCTRL_DATA": (0x1006, 2), + "TCA0_SINGLE_CMP1BUF": (0x0A3A, 2), + "NVMCTRL_ADDR": (0x1008, 2), + "USART0_BAUD": (0x0808, 2), + "TCA0_SINGLE_CMP0BUF": (0x0A38, 2), + "TCB0_CCMP": (0x0A8C, 2), + "RTC_PER": (0x014A, 2), + "TCB1_CNT": (0x0A9A, 2), + "TCB1_CCMP": (0x0A9C, 2), + "USART1_BAUD": (0x0828, 2), + "TCB0_CNT": (0x0A8A, 2), + "ADC0_WINHT": (0x061E, 2), + "ADC0_WINLT": (0x061C, 2), + "SP": (0x003D, 2), + "TCA0_SINGLE_PERBUF": (0x0A36, 2), + "TCA0_SINGLE_PER": (0x0A26, 2), + "ADC0_RESULT": (0x0610, 4), +} diff --git a/src/map.py b/src/map.py new file mode 100644 index 0000000..d19a4c2 --- /dev/null +++ b/src/map.py @@ -0,0 +1,33 @@ + +class memmap(dict): + + def __init__(self, fn="hallo.map"): + self.parsemap(fn) + + def parsemap(self, fn): + with open(fn) as f: + laddr = None + for l in f: + try: + ll = l.split() + if l[:1] == "." and len(ll) >= 3: + section = ll[0] + saddr = int(ll[1], 0) & 0xffff + slen = int(ll[2], 0) + self[section] = (saddr, slen) + laddr = saddr + last = None + continue + if l[:1] != " ": + continue + if len(ll)==2 and ll[0][:2] == "0x" or len(ll)==4 and ll[2]=="=" and ll[3]==".": + cur = ll[1] + addr = int(ll[0],0) & 0xffff + self[cur] = (addr,0) + if last is not None: + self[last] = (self[last][0], addr-laddr) + last = cur + laddr = addr + except Exception as e: + from sys import stderr + print(f"{repr(e)}\n{l}", file=stderr) diff --git a/src/prompt.py b/src/prompt.py new file mode 100644 index 0000000..f6573c6 --- /dev/null +++ b/src/prompt.py @@ -0,0 +1,20 @@ +import sys + +def set_prompt(prompt): + sys.ps1 = prompt + "> " + sys.ps2 = prompt + ". " + try: + ip=get_ipython() + from IPython.terminal.prompts import Prompts, Token + class myprompts(Prompts): + def in_prompt_tokens(self, *wtf): + return [(Token, prompt+'> ')] + def continuation_prompt_tokens(self, *wtf): + return [(Token, prompt+'. ')] + def out_prompt_tokens(self, *wtf): + return [(Token, prompt+'= ')] + def rewrite_prompt_tokens(self, *wtf): + return [(Token, prompt+'- ')] + ip.prompts=myprompts(ip) + except: + pass diff --git a/src/rtc.c b/src/rtc.c index 0d04c75..ee3486a 100644 --- a/src/rtc.c +++ b/src/rtc.c @@ -25,7 +25,7 @@ uint8_t rtc_cnt_tick() struct_ioconf(rtc_config) = { conf_prefix(RTC), - conf_iow(RTC.CMP, 3600), + conf_iow(RTC.CMP, 10), // rtc_cnt_tick() period [s] conf_io(RTC.CLKSEL, RTC_CLKSEL_INT1K_gc), conf_io(RTC.PITINTCTRL, 1), conf_io(RTC.PITCTRLA, RTC_PERIOD_CYC1024_gc | RTC_PITEN_bm), @@ -43,8 +43,7 @@ ISR(RTC_PIT_vect, ISR_NAKED) "ldi r24, 1" "\n\t" "sts %[flag], r24" "\n\t" "sts pit_tick, r24" "\n\t" - "ldi r24, " "\n\t" - "sts %[flag], r24" "\n\t" + "pop r24" "\n\t" "reti" "\n" ::[flag] "n" (&RTC.PITINTFLAGS) ); @@ -66,11 +65,11 @@ ISR(RTC_CNT_vect, ISR_NAKED) "push r26" "\n\t" "lds r24, %[CMPL]" "\n\t" "lds r25, %[CMPH]" "\n\t" - "lds r26, config+3" "\n\t" + "lds r26, rtc_config+3" "\n\t" "add r24, r26" "\n\t" "sts %[CMPL], r24" "\n\t" - "lds r26, config+5" "\n\t" - "add r25, r26" "\n\t" + "lds r26, rtc_config+5" "\n\t" + "adc r25, r26" "\n\t" "sts %[CMPH], r25" "\n\t" "pop r26" "\n\t" "pop r25" "\n\t" diff --git a/src/uart.c b/src/uart.c index 0772547..0ac11c9 100644 --- a/src/uart.c +++ b/src/uart.c @@ -10,10 +10,10 @@ struct_ioconf(uart_config) = { conf_prefix(USART0), - conf_iow(USART0.BAUD, UART_BAUD), - conf_io(USART0.CTRLB, UART_MODE & 0xff), - conf_io(USART0.CTRLC, UART_MODE >> 8), - conf_io(USART0.CTRLA,USART_RXCIE_bm), + conf_iow(USART0.BAUD, 40000000/115200), // 115200 baud + conf_io(USART0.CTRLC, USART_CHSIZE_8BIT_gc), + conf_io(USART0.CTRLB, USART_TXEN_bm | USART_RXEN_bm), + conf_io(USART0.CTRLA, USART_RXCIE_bm), }; // `uart_tx` buffer size must be a power of 2, max 256. @@ -163,7 +163,7 @@ ISR(USART0_RXC_vect, ISR_NAKED) " push r31 \n" " lds r24, %[DH] \n" " lds r30, uart_rx_s \n" - " and r24, r30 \n" + " or r24, r30 \n" " sts uart_rx_s, r24 \n" " lds r24, %[DL] \n" " lds r30, uart_rx_w \n" diff --git a/src/uart.h b/src/uart.h index cf1b6aa..6438874 100644 --- a/src/uart.h +++ b/src/uart.h @@ -4,9 +4,6 @@ #include "config.h" -#define UART_MODE 0x03c0 -#define UART_BAUD 5555 // 115200 baud - void init_uart(uint16_t mode, uint16_t div); void send_char(uint8_t c); diff --git a/src/uart.py b/src/uart.py new file mode 100755 index 0000000..47e8b34 --- /dev/null +++ b/src/uart.py @@ -0,0 +1,115 @@ +#! /usr/bin/ipython3 --profile=uart + +import sys, serial, threading, struct +from prompt import set_prompt + +class uart(threading.Thread): + + def __init__(self, port, baudrate=115200, parity='N'): + self.baudrate=baudrate + self.serial = serial.Serial(baudrate=baudrate, parity=parity, timeout=10.0, + xonxoff=False, rtscts=False, dsrdtr=False) + self.serial.port = port + self.portname = port.split('/')[-1] + self.serial.open() + self.write = self.serial.write + self.responses = [] + self.readbuffer = b"" + self.reader_lock = threading.Lock() + self.resp_ready = threading.Condition() + self.alive = True + threading.Thread.__init__(self, target=self.reader) + self.daemon = True + self.start() + + def reader(self): + data = b"" + while self.alive: + data += self.serial.read_until(b'\n') + data += self.serial.read_all() + if data and self.reader_lock.acquire(False): + self.readbuffer += data + data = b"" + self.parser() + self.reader_lock.release() + + def parse(self): + if self.reader_lock.acquire(False): + self.parser() + self.reader_lock.release() + + def kill(self): + self.alive = False + self.join() + + def parser(self): + "do something with received chars in .readbuffer" + if not self.resp_ready.acquire(False): + return + lines = self.readbuffer.split(b'\n') + self.readbuffer = lines[-1] + lines[-1:] = [] + + for ll in lines: + if ll: + self.parse_line(ll) + if self.responses: + self.resp_ready.notify() + self.resp_ready.release() + + verbose = True + + def parse_line(self, l): + self.responses.append(l) + if self.verbose: + try: + s = l.decode() + print(f"{self.portname}<- {s}", file=sys.stderr) + except: + print(f"{self.portname}<- {repr(l)}", file=sys.stderr) + + def resp(self, timeout=10, blocking=True): + self.parse() + if not self.resp_ready.acquire(blocking): + return b"" + if blocking and not self.responses: + self.resp_ready.wait(timeout) + l = b"" + if self.responses: + l = self.responses[0] + self.responses[:1] = [] + self.resp_ready.release() + return l + + def flush(self): + r = [] + while self.responses: + r.append(self.resp()) + return r + + def ucmd(self, c): + if not isinstance(c, bytes): + c = c.encode() + if c[-1:] != b'\n': + c = c + b'\n' + if self.verbose: + print(f"{self.portname}-> {c.decode().rstrip()}", file=sys.stderr) + self.write(c) + + def cmd(self, c): + self.ucmd(c) + +if __name__=="__main__": + import getopt + oo,ff = getopt.getopt(sys.argv[1:], "F:", ["tty="]) + port = "/dev/ttyUSB1" + baud = 115200 + for o,v in oo: + if o=="-F" or v=="--tty": + v = v.split(",") + port = v[0] + if len(v)>1: + baud = int(v[1]) + u = uart(port, baud) + c = u.ucmd + set_prompt(u.portname) diff --git a/src/uart_tx.S b/src/uart_tx.S index 8ae5c60..45b3a8c 100644 --- a/src/uart_tx.S +++ b/src/uart_tx.S @@ -14,6 +14,7 @@ .global _send_str26 .global uart_busy .global send_hex + .global send_eol .global command // `tx()` and `put_char()` do not gobble r18, r20, r21, r22, r24, r26, and r27. @@ -101,7 +102,7 @@ send_hex: ld r24, X+ rcall send_hex_byte subi r18, 1 - brcc 1b + brne 1b 9: ret #endif From 13407292ddf09db7beeab4b1bbf1940de7e9c6eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20I=2E=20B=C3=B6ttcher?= Date: Wed, 4 Feb 2026 21:41:19 +0100 Subject: [PATCH 05/10] hallo: add flash --- src/Makefile | 2 +- src/cmd.c | 2 +- src/config.h | 2 +- src/hallo.c | 3 ++- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Makefile b/src/Makefile index 6c621b1..1eaa8ad 100644 --- a/src/Makefile +++ b/src/Makefile @@ -11,7 +11,7 @@ hallo_all: hallo.eeprom CFLAGS_hallo = -DHAVE_nFETs -DSEND_HEX -DHALLO MCU_hallo = attiny424 SN_hallo = 1 -C_FILES_hallo = config.c uart.c cmd.c base85.c rtc.c adc.c pwm.c +C_FILES_hallo = config.c uart.c cmd.c base85.c rtc.c adc.c pwm.c spi.c flash.c bch4369.c S_FILES_hallo = uart_tx.S base85a.S dose_all: dose.eeprom dose.userrow diff --git a/src/cmd.c b/src/cmd.c index 4834b7c..47dbc09 100644 --- a/src/cmd.c +++ b/src/cmd.c @@ -194,7 +194,6 @@ void parse_command(const uint8_t *s, uint8_t n) goto send_buffer; } break; -#ifndef HALLO case 'B': if (cmd_flag('@')) pipe.valid = 0; @@ -229,6 +228,7 @@ void parse_command(const uint8_t *s, uint8_t n) else r = flash_poll(cmd_flag('!')); break; +#ifndef HALLO case 'P': if (have_b) pipe_config((void*)cmd_buffer); diff --git a/src/config.h b/src/config.h index b4f3369..d877e4c 100644 --- a/src/config.h +++ b/src/config.h @@ -27,7 +27,7 @@ struct config { enum magic_flags { #ifdef HAVE_nFETs MAGIC = 0xD0, - VERSION = 0x00, + VERSION = 0x01, #endif #ifdef HAVE_FPGA MAGIC = 0xC5, diff --git a/src/hallo.c b/src/hallo.c index 4621243..c2baa0b 100644 --- a/src/hallo.c +++ b/src/hallo.c @@ -12,12 +12,13 @@ #include "config.h" #include "uart.h" -#include "pwm.h" +#include "pipe.h" //////////////////////////////////////////////////////////////////////////////// // // main() +section_status(pipe) struct pipe pipe; section_status(main) struct magic magic; int main() From 2aab85975a492b401465b3936dd11b5e3c7b27fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20I=2E=20B=C3=B6ttcher?= Date: Fri, 6 Feb 2026 11:05:48 +0100 Subject: [PATCH 06/10] flash fixes --- src/cmd.c | 11 +++--- src/dose.py | 9 ++++- src/flash.py | 105 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/spi.c | 3 +- 4 files changed, 120 insertions(+), 8 deletions(-) create mode 100644 src/flash.py diff --git a/src/cmd.c b/src/cmd.c index 47dbc09..d1c5443 100644 --- a/src/cmd.c +++ b/src/cmd.c @@ -159,11 +159,9 @@ void parse_command(const uint8_t *s, uint8_t n) uint8_t bflg = *s - '0'; uint8_t *bptr = cmd_buffer; if (bflg <= 5) { -#ifndef HALLO bptr = flash_buffer + 16*bflg; -#endif bflg = 1<> 7; - if (mm & 0x80) - m ^= *--pp; - *p = m; - b = bb; - } - } -} - -void bch4369_str(uint8_t *b, uint8_t n) -{ - do - bch4369(*b++); - while (--n); -} - -#else - -__attribute__ ((noinline, naked)) -void bch4369(uint8_t d) -{ - __asm__ __volatile__( - "" - "ldi r30, lo8(bch_parity)" "\n\t" - "ldi r31, hi8(bch_parity)" "\n\t" - "ld r25, Z" "\n\t" - "bst r25, 7" "\n\t" // copy MSB of parity → T-bit - "ldi r20, 8" "\n\t" // loop r20 = 8 → 1 - "1:" "\n\t" - "adiw r30, 16" "\n\t" - "ldi r26, lo8(bch_genpoly+16)" "\n\t" - "ldi r27, hi8(bch_genpoly+16)" "\n\t" - "rol r24" "\n\t" // move next input bit → r25.7 - "ror r25" "\n\t" - "2:" "\n\t" - "rol r25" "\n\t" // Move r25.7 → C-bit - "ld r25, -Z" "\n\t" // Rotage input bit into parity byte - "rol r25" "\n\t" - "brtc 3f" "\n\t" // if T-bit: - "ld r0, -X" "\n\t" // xor with gen_poly - "eor r25, r0" "\n\t" - "3:" "\n\t" - "st Z, r25" "\n\t" - "ror r25" "\n\t" // save C-bit → r25.7 and MSB → r25.6 - "cpi r30, lo8(bch_parity)" "\n\t" - "brne 2b" "\n\t" - "bst r25, 6" "\n\t" // copy MSB r25.6 → T-bit - "subi r20, 1" "\n\t" - "brne 1b" "\n\t" - "ret" "\n" - ); -} - -__attribute__ ((noinline, naked)) -void bch4369_str(const uint8_t *b, uint8_t n) -{ - // _bch4369 preserves r22, r23, r18, r19 - __asm__ __volatile__ ( - "" - "movw r18, r28" "\n\t" - "movw r28, r24" "\n" - "1:" "\n\t" - "ld r24, Y+" "\n\t" - "rcall bch4369" "\n\t" - "subi r22, 1" "\n\t" - "brne 1b" "\n\t" - "mov r28, r18" "\n\t" - "ret" "\n" - ); -} - -#endif diff --git a/src/bch4369.h b/src/bch4369.h deleted file mode 100644 index e213d75..0000000 --- a/src/bch4369.h +++ /dev/null @@ -1,24 +0,0 @@ - -#include -#include -extern uint8_t bch_parity[16]; -void bch4369(uint8_t d); -static inline void bch4369_init() { memset(bch_parity, 0 , 16); } -void bch4369_str(const uint8_t *b, uint8_t n); - -static inline -uint8_t *bch4369_stri(uint8_t *b, uint8_t n) -{ - __asm__ volatile( - "1:" "\n\t" - "ld r24, Y+" "\n\t" - "rcall bch4369" "\n\t" - "subi %[N], 1" "\n\t" - "brne 1b" "\n" - : [N] "+r" (n), [B] "+y" (b) - :: "r0", - "r20", "r21", "r24", "r25", - "r26", "r27", "r30", "r31" - ); - return b; -} diff --git a/src/cmd.c b/src/cmd.c index d1c5443..64d2c3b 100644 --- a/src/cmd.c +++ b/src/cmd.c @@ -89,18 +89,27 @@ uint8_t poke(struct peak_poke *p, uint8_t poke) if (!n) return 0; if (poke) { - if (p->ccp) - NVMCTRL.CTRLA = NVMCTRL_CMD_PAGEBUFCLR_gc; - _memcopy(a, p->data, n); - if (p->ccp && !(0x8000 & (uint16_t)a)) + uint8_t ccp = p->ccp; + if (ccp) __asm__( - "out %[ccp], %[key] \n\t" + "out __CCP__, %[key] \n\t" "sts %[ctrla], %[cmd] \n" - :: [ccp] "n" (&CCP), + :: [ctrla] "n" (&NVMCTRL.CTRLA), - [key] "r" (p->ccp), + [key] "r" (ccp), + [cmd] "r" (NVMCTRL_CMD_PAGEBUFCLR_gc) + : "memory" + ); + _memcopy(a, p->data, n); + if (ccp && !(0x8000 & (uint16_t)a)) + __asm__( + "out __CCP__, %[key] \n\t" + "sts %[ctrla], %[cmd] \n" + :: + [ctrla] "n" (&NVMCTRL.CTRLA), + [key] "r" (ccp), [cmd] "r" (NVMCTRL_CMD_PAGEERASEWRITE_gc) - : "memory", "r0" + : "memory" ); } else @@ -198,19 +207,20 @@ void parse_command(const uint8_t *s, uint8_t n) r = pipe.valid; if (have_b) { if (cmd_flag('!') || ~r & bflg) { - pipe.valid |= bflg; memcpy(bptr, cmd_buffer, 16); + r = pipe.valid |= bflg; } else goto error; } if (cmd_flag('%')) { if (cmd_flag('@')) - memset(bch_parity, 0, 16); + bch4369_init(config.bch_salt); bch4369_str(bptr, 16); if (cmd_flag('!')) { + bch4369_fini(); memcpy(flash_buffer+64, bch_parity, 16); - pipe.valid |= 0x10; + r = pipe.valid |= 0x10; } } if (cmd_flag('<')) { @@ -221,11 +231,15 @@ void parse_command(const uint8_t *s, uint8_t n) goto send_buffer; } break; + case 'D': + flash_find_free(); + bptr = (void*)&fs; + goto send_buffer; case 'F': if (have_b) r = flash_submit_command(cmd_buffer); else - r = flash_poll(cmd_flag('!')); + r = spi_busy_p(); if (cmd_flag('<')) goto send_buffer; break; diff --git a/src/config.c b/src/config.c index 740907d..25ee61e 100644 --- a/src/config.c +++ b/src/config.c @@ -13,12 +13,13 @@ const struct config config = { .version = VERSION, .cpu_clk = CLKCTRL_PDIV_2X_gc | 1, // 10MHz (max @ 3V) .flash_page_size = FM_528 >> 8, + .bch_salt = 1, .burn_page = 0x88, // Buffer 1 Page Program w/o Erase - .write_buffer = 0x84 | FM_WRITE>>8, // Buffer 1 Write - .read_array = 0x03 | FM_READ>>8, // Continuous Array Read (Low-Frequency) + .write_buffer = 0x84 | FM_WRITE, // Buffer 1 Write + .read_array = 0x03 | FM_READ, // Continuous Array Read (Low-Frequency) .read_buffer = { - [0] = 0xd1 | FM_READ>>8, // Buffer 1 Read (Low-Frequency) - [1] = 0xd3 | FM_READ>>8, // Buffer 2 Read (Low-Frequency) + [0] = 0xd1 | FM_READ, // Buffer 1 Read (Low-Frequency) + [1] = 0xd3 | FM_READ, // Buffer 2 Read (Low-Frequency) }, .page_start = 0x0800, .page_end = 0x1000, diff --git a/src/config.h b/src/config.h index d877e4c..39d346d 100644 --- a/src/config.h +++ b/src/config.h @@ -12,6 +12,7 @@ struct config { uint8_t cpu_clk; uint8_t cron; uint8_t flash_page_size; + uint8_t bch_salt; uint16_t burn_page; uint16_t write_buffer; uint16_t read_array; @@ -27,7 +28,7 @@ struct config { enum magic_flags { #ifdef HAVE_nFETs MAGIC = 0xD0, - VERSION = 0x01, + VERSION = 0x00, #endif #ifdef HAVE_FPGA MAGIC = 0xC5, diff --git a/src/dose.py b/src/dose.py index 13daf0d..f625a9c 100755 --- a/src/dose.py +++ b/src/dose.py @@ -5,12 +5,14 @@ import uart from base85 import base85_encode, base85_decode from map import memmap from iotn424 import SFR -import flash_cmd -flash = flash_cmd.flash() +import flash as flash_cmd +flash_cmd = flash_cmd.flash_cmd() +sys.path[1:1] = ["./bch4369"] +from bch4369 import bch -options, files = getopt.gnu_getopt(sys.argv[1:], "F:o:M:", ["debug", "tty=", "output=", "map="]) +options, files = getopt.gnu_getopt(sys.argv[1:], "F:o:M:", ["debug", "tty=", "output=", "map=", "galois"]) -tty = "/dev/ttyUSB1" +tty = None baud = 115200 out = None debug = None @@ -49,6 +51,9 @@ for o,v in options: if o in "-M --map": map_fn = v + if o == "--galois": + bch.load_galois() + if not out: out = sys.stdout @@ -78,15 +83,15 @@ class dose_cmd(uart.uart): e = int(r[-1], 16) if len(r)==3: d = base85_decode(r[1]) - if self.verbose: + if self._verbose: print(f"{r[0]}[{r[2]}] {"".join([f"{b:02x}" for b in d])}", file=sys.stderr) self.last_cmd = c[0:1] return r[0], e, d def poke(self, a, d, s=None, ccp=0): - return self.peek(a, d, s, ccp) + return self.peek(a, s=s, d=d, ccp=ccp) - def peek(self, a=None, d=None, s=None, ccp=0): + def peek(self, a=None, s=None, d=None, ccp=0): name = None if a in SFR: name = a @@ -97,9 +102,15 @@ class dose_cmd(uart.uart): a, ss = mmap[a] if ss and s is None: s = ss - if s is None and isinstance(d, int): - s = d - d = None + if isinstance(a, str): + print(f"PEEK: unknown addr {a}", file=sys.stderr) + for k in mmap.keys(): + if a in k: + print(f"PEEK: in RAM we have {k}", file=sys.stderr) + for k in SFR.keys(): + if a in k: + print(f"PEEK: IO SFR we have {k}", file=sys.stderr) + raise ValueError(a) FF = {1: "B", 2: "H", 4:"I"} if a is None: cc, nn, dd = self.cmd(b"M=") @@ -120,7 +131,8 @@ class dose_cmd(uart.uart): d = struct.pack(">4 + if not b: + c = "B0@%@" + elif not (b&3): + c = "B0@%" + elif b==31: + c = "B3%!" + else: + c = f"B{b&3}%" + self.cmd(c, d=d[i:i+16]) + if (b&3) == 3: + if b==31: + s = 80 + else: + s = 64 + self.flash(op, byte=i & 0x1c0, size=s)[1] + self.wait_for_spi() + + def wait_for_spi(self): + while True: + r = self.cmd("F")[1] + if not r: + break + print(f"SPI busy {r:02x}", file=sys.stderr) + + def readfbuffer(self, op="ReadBuffer2"): + d = b'' + for i in range(0, 512, 16): + b = i>>4 + if (b&3) == 0: + if b==28: + s = 80 + else: + s = 64 + self.flash(op, byte=i, size=s) + self.wait_for_spi() + d += self.cmd(f"B{b&3} 0: + f.write(self.read_flash(page)) + page += 1 + n -= 1 + + def flash_status(self, blocking=False): + r = (0,0,bytes(2)) + while not r[2][0] & 0x80: + self.flash("Status", what="cmdbuf") + r = self.cmd("F<") + while r[1]: + r = self.cmd("F<") + if not blocking: + break + return tuple(r[2][:2]) + + def write_flash(self, page, d): + print(f"FLASH: writing to {page=}") + self.write2fbuffer(d) + self.flash("Program2", page=page) + self.flash_status(True) + + def write_file2flash(self, page, fn): + n = 0 + with open(fn, "rb") as f: + while True: + b =f.read(512) + if not b: + break + if len(b)<512: + b += bytes(512-len(b)) + self.write_flash(page+n, b) + n += 1 + return n + + def erase_flash_sector(self, page, op="EraseSector"): + """ + op="EraseSector" (default) + page=0: pages 0 … 7 + page=8: pages 8 … 0xff + page=0x100: pages 0x100 … 0x1ff + … + op="ErasePage", page=n + op="EraseBlock", page=n [n&~7 … n|7] + op="EraseChip" + """ + self.flash(op, page=page) + self.flash_status(True) + + def flash_power(self, on=True): + op = "PowerUp" if on else "PowerDown" + self.flash(op) + + def flash_Id(self): + self.flash("Id", what="cmdbuf") + while True: + r = self.cmd("F<") + if not r[1]: + break + i = r[2][:5] + print(f"FLASH chip {b2hex(i)}", file=sys.stderr) + return i + + def memsetfbuffer(self, byte=0, what=0xff, size=528, op=0x0887): + for i in range(byte, byte+size, 255): + s = byte+size - i + if s > 255: + s = 255 + self.flash(op, size=s, what=what, byte=i) + self.wait_for_spi() if tty: tty = dose_cmd(tty, baud) - uart.set_prompt("TurboD") + tty._export(globals()) + tty._verbose = False + +uart.set_prompt("TurboD") + +def b2hex(b, sep=" "): + return sep.join([f"{x:02x}" for x in b]) + diff --git a/src/flash.c b/src/flash.c index c2958e6..2739ce0 100644 --- a/src/flash.c +++ b/src/flash.c @@ -130,7 +130,7 @@ uint8_t flash_cmd(uint16_t mode, uint16_t what, uint16_t page, uint16_t byte) spi_start_cmd(csize, flash_cmd_buffer); return 0; } - if (size >= 128) { + if (size > 80) { // for read of the security register pads += size-64; size = 64; @@ -253,11 +253,13 @@ uint8_t flash_burn_page() uint8_t flash_poll(uint8_t rr) { uint8_t r = fs.status; + if (spi_busy_p()) + return r; if ((r & FS_Error) == FS_Error) return r; if (r & FS_StBsy) { // status bytes arrived - if (flash_status_bytes[0] & 0x80) + if (~flash_status_bytes[0] & 0x80) // flash is still busy burning goto rd_status; // not busy any more, move Bsy → Rdy @@ -288,8 +290,6 @@ ready: if (rr) r |= FS_Ack; fs.status = r; - if (spi_busy_p()) - return r; if (r & FS_Dir == FS_Write && fs.block == 8) flash_burn_page(); else if (!flash_stream_done()) { @@ -324,7 +324,7 @@ static inline uint8_t flash_memset_buffer2() { for (uint16_t i=0; i<528; i += 176) { - uint8_t r = flash_cmd((uint16_t)config.flash_page_size << 8 | 0xff87, 176, 0, i); + uint8_t r = flash_cmd((uint16_t)config.flash_page_size << 8 | 0x87, 176|0xff00, 0, i); if (r) return r; while (spi_busy_p()) ; @@ -355,9 +355,10 @@ uint16_t flash_find_free() while (e > a) { uint16_t p = (a+e)>>1; if (flash_compare_buffer2(p)) - a = p; + a = p+1; else e = p; } + fs.free = a; return a; } diff --git a/src/flash.h b/src/flash.h index 8ce5e61..c509eee 100644 --- a/src/flash.h +++ b/src/flash.h @@ -33,6 +33,7 @@ uint16_t flash_find_free(); extern struct flash_stream { + uint16_t free; // first free page uint16_t page; // page address of buffer number uint16_t npages; // more pages to read uint8_t block; // next block to read 0…9 diff --git a/src/flash.py b/src/flash.py index b8f55bd..c5e5cf3 100644 --- a/src/flash.py +++ b/src/flash.py @@ -3,6 +3,9 @@ import struct class flash_cmd: + class Flash_Error(IOError): + pass + M = { 0: 0x0000, 1: 0x0100, @@ -35,8 +38,8 @@ class flash_cmd: "ReadLegacy": (0xe8 | A | M["read"] | M[4],), "ReadBuffer1": (0xd1 | A | M["read"] | M[0],), "ReadBuffer2": (0xd3 | A | M["read"] | M[0],), - "ReadBufHF1": (0xd1 | A | M["read"] | M[1],), - "ReadBufHF2": (0xd3 | A | M["read"] | M[1],), + "ReadBufHF1": (0xd4 | A | M["read"] | M[1],), + "ReadBufHF2": (0xd6 | A | M["read"] | M[1],), "WriteBuffer1": (0x84 | A | M["write"],), "WriteBuffer2": (0x87 | A | M["write"],), "ProgErase1": (0x83 | A,), @@ -63,10 +66,11 @@ class flash_cmd: WHAT = { "buffer": 0, "cmdbuf": 96, + "cmd": 96+8, "status": 128, } - def cmd_buffer(self, op, mode=0, size=None, page=0, byte=0, what="buffer"): + def cmd_buffer(self, op, mode=0, size=None, page=0, byte=0, what=0, data=None): if op in self.OP: opp = self.OP[op] op = opp[0] @@ -96,10 +100,14 @@ class flash_cmd: size = self.pagesize - byte if what+size > 80: size = 80 - what + elif data is not None and not what and size <= 8: + what = self.WHAT["cmd"] + if data is None: + data = 0 size |= what << 8; if self.verbose: from sys import stderr - print("Flash cmd", *map(hex,(op, size, page, byte)), file=stderr) - return struct.pack("<4H", op, size, page, byte) + print("Flash cmd", *map(hex,(op, size, page, byte, data)), file=stderr) + return struct.pack("<4HQ", op, size, page, byte, data) verbose = False diff --git a/src/spi.c b/src/spi.c index dd16632..b1425ac 100644 --- a/src/spi.c +++ b/src/spi.c @@ -216,7 +216,7 @@ ISR(SPI0_INT_vect, ISR_NAKED) [AIFLGS] "n" (SPI_TXCIF_bm | SPI_RXCIF_bm | SPI_DREIF_bm), [ICTRL] "n" (&SPI.INTCTRL), [DATA] "n" (&SPI.DATA), - [SPORT] "n" (_SFR_IO_ADDR(SSEL_VPORT)), + [SPORT] "n" (_SFR_IO_ADDR(SSEL_VPORT.OUT)), [SSEL] "n" (SSEL_PIN) ); } diff --git a/src/spi.h b/src/spi.h index 79d7478..e1e01cc 100644 --- a/src/spi.h +++ b/src/spi.h @@ -46,14 +46,13 @@ uint8_t spi_abort() static inline uint8_t spi_busy_p() { - return SPI.INTFLAGS & SPI_TXCIF_bm; + return SPI.INTCTRL & SPI_TXCIF_bm; } static inline void spi_start() { - SPI.INTCTRL = SPI_DREIF_bm; - // the ISR will immediately set SPI_TXCIF_bm. + SPI.INTCTRL = SPI_DREIF_bm | SPI_TXCIF_bm| SPI_RXCIF_bm; } static inline void barrier() { __asm__("":::"memory"); } diff --git a/src/uart.py b/src/uart.py index 47e8b34..e6fc13d 100755 --- a/src/uart.py +++ b/src/uart.py @@ -57,11 +57,11 @@ class uart(threading.Thread): self.resp_ready.notify() self.resp_ready.release() - verbose = True + _verbose = True def parse_line(self, l): self.responses.append(l) - if self.verbose: + if self._verbose: try: s = l.decode() print(f"{self.portname}<- {s}", file=sys.stderr) @@ -92,13 +92,30 @@ class uart(threading.Thread): c = c.encode() if c[-1:] != b'\n': c = c + b'\n' - if self.verbose: + if self._verbose: print(f"{self.portname}-> {c.decode().rstrip()}", file=sys.stderr) self.write(c) def cmd(self, c): self.ucmd(c) + def _export(self, scope=None, prefix=""): + """usage: ..._export(globals()) + return a dict with all names in self that + do not begin with an '_' + and are not all uppercase. + The scope is updated with the dict. + """ + r = { + prefix+k: getattr(self, k) + for k in dir(self) + if k.upper() != k + and k[0] != '_' + } + if scope: + scope.update(r) + return r + if __name__=="__main__": import getopt oo,ff = getopt.getopt(sys.argv[1:], "F:", ["tty="]) @@ -110,6 +127,6 @@ if __name__=="__main__": port = v[0] if len(v)>1: baud = int(v[1]) - u = uart(port, baud) - c = u.ucmd - set_prompt(u.portname) + tty = uart(port, baud) + tty._export(globals()) + set_prompt(tty.portname) From 8a56b61f799d5f4b503ffd19a7ba988531f0d350 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20I=2E=20B=C3=B6ttcher?= Date: Wed, 4 Mar 2026 15:31:41 +0100 Subject: [PATCH 08/10] uart: hide _reader() --- src/uart.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uart.py b/src/uart.py index e6fc13d..e8bdba3 100755 --- a/src/uart.py +++ b/src/uart.py @@ -18,11 +18,11 @@ class uart(threading.Thread): self.reader_lock = threading.Lock() self.resp_ready = threading.Condition() self.alive = True - threading.Thread.__init__(self, target=self.reader) + threading.Thread.__init__(self, target=self._reader) self.daemon = True self.start() - def reader(self): + def _reader(self): data = b"" while self.alive: data += self.serial.read_until(b'\n') From cce9ded5a295ab1df2a66fd79989531d075aecc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20I=2E=20B=C3=B6ttcher?= Date: Wed, 4 Mar 2026 15:39:22 +0100 Subject: [PATCH 09/10] bch commits --- src/bch4369 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bch4369 b/src/bch4369 index 7f5400f..eebd4bb 160000 --- a/src/bch4369 +++ b/src/bch4369 @@ -1 +1 @@ -Subproject commit 7f5400fa08129800f103be9a4f3caeeb677c5219 +Subproject commit eebd4bb43039a2ecf63aa7c2fdbe1c4edf469393 From 18f6ba9c2fc51e462d71bbb56b373c60a9dec688 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20I=2E=20B=C3=B6ttcher?= Date: Tue, 9 Jun 2026 10:59:12 +0200 Subject: [PATCH 10/10] replace BOM with _dose version --- gerber/{turbo_bom.txt => turbo_dose_bom.txt} | 24 ++++++++------------ 1 file changed, 10 insertions(+), 14 deletions(-) rename gerber/{turbo_bom.txt => turbo_dose_bom.txt} (51%) diff --git a/gerber/turbo_bom.txt b/gerber/turbo_dose_bom.txt similarity index 51% rename from gerber/turbo_bom.txt rename to gerber/turbo_dose_bom.txt index 1ed7ed0..f938f31 100644 --- a/gerber/turbo_bom.txt +++ b/gerber/turbo_dose_bom.txt @@ -1,30 +1,26 @@ # PcbBOM Version 1.0 -# Date: So 21 Apr 2024 10:57:09 GMT UTC -# Author: +# Date: Di 09 Jun 2026 08:58:28 GMT UTC +# Author: Stephan Boettcher # Title: TURBO - PCB BOM # Quantity, Description, Value, RefDes # -------------------------------------------- C0603 -BYP_100nF 1/1 C4 -C0603 100nF 6/6 C1 C10 C11 C12 C2 C3 +C0603 100nF 6/6 C11 C12 C30 C32 C33 C34 C0603 SMD-LED 1/1 D1 C0603.fp -SD:_10kΩ 1/1 R13 -C0603.fp 10MΩ 1/1 R9 -C0603.fp 10kΩ 4/4 R11 R5 R7 R8 -C0603.fp 10kΩ_NTC 1/1 R6 -C0603.fp 1MΩ 1/1 R10 +C0603.fp 100kΩ 7/7 R30 R31 R32 R33 R34 R35 R36 +C0603.fp 10kΩ 1/1 R11 C0603.fp 2.2MΩ 1/1 R4 -C0603.fp 220kΩ 1/1 R2 C0603.fp 3.3MΩ 1/1 R3 -C0603.fp 330kΩ 1/1 R1 C0603.fp ∞Ω 1/1 R12 +C0603.fp ∞Ω/0Ω 1/1 R10 KEYSTONE-1025-7 6V 1/1 B1 -MS5534C MS5534C 1/1 U2 -P1206 10µF 11/11 C20 C21 C22 C23 C24 C25 C26 C27 C28 C29 - C30 +P1206 10µF 4/4 C20 C26 C29 C31 SIL_100_3 HE_100_1×3 1/1 J1 -SIL_100_4 WRL-10534 1/1 U3 +SIL_100_4 SIL4 1/1 U3 SOIC_150_14 ATtiny4x4SS 1/1 U1 -SOT23_5 LT1761-SD 1/1 U4 +SOIC_150_8 AT45DB161E 1/1 U2 +SOT23_3 2N7002 2/2 Q1 Q2 SOT23_5 LT1761-SD_/_-BYP 1/1 U5 SUBD9_PINS D9_pigtail 1/1 CONN1 gseboard turbo 1/1 BOARD