Compare commits

..

160 commits

Author SHA1 Message Date
Stephan I. Böttcher
630073928e Merge branch 'master' of ssh://forge.bexus.org/Stephan/thhor_crs
This merges the src/ development (falbala) with the fpga/ development
from etsolo4 and blaulicht.
2026-03-18 11:06:38 +01:00
Stephan I. Böttcher
7bc2c22137 .gitignore thhor.userrow 2026-03-18 10:17:53 +01:00
Stephan I. Böttcher
7e770cbf77 make thhor: fuses and MCU 2026-03-18 10:16:57 +01:00
Stephan I. Böttcher
b1617f45c0 iotn424: add VPORT[AB] 4-byte summary 2026-03-18 10:16:05 +01:00
Stephan I. Böttcher
1d9c0be330 cmd('O'): power() 2026-03-18 10:14:50 +01:00
Stephan I. Böttcher
2c41feee96 merged the src subtree from turbo_dose
renamed hallo to thhor
cpu is ATtiny3224
2026-03-06 15:26:54 +01:00
Stephan I. Böttcher
a2e020b270 Add 'src/' from commit '47f6abd006'
git-subtree-dir: src
git-subtree-mainline: 066a1e4c40
git-subtree-split: 47f6abd006
2026-03-06 15:04:47 +01:00
Stephan I. Böttcher
47f6abd006 bch commits 2026-03-04 15:39:22 +01:00
Stephan I. Böttcher
37bc3efc2e uart: hide _reader() 2026-03-04 15:31:41 +01:00
Stephan I. Böttcher
7b4724ef37 flash debugging, no known issues left 2026-02-09 09:04:35 +01:00
Stephan I. Böttcher
ecccef0f97 flash fixes 2026-02-06 11:05:48 +01:00
Stephan I. Böttcher
9e85cb3b1d hallo: add flash 2026-02-04 21:41:19 +01:00
Stephan I. Böttcher
731d558a40 uart, adc, pwm debugged
hallo.c is a temporary slim version of dose.c.
With python support code.
The First nFET characteristic is recorded.
2026-02-04 21:19:21 +01:00
Stephan I. Böttcher
4a65235852 hallo: early debaug code, about to be removed 2026-02-03 16:00:14 +01:00
Stephan I. Böttcher
a8df949ce8 divmod85 implementation fixed and tested 2026-01-29 11:26:33 +01:00
Stephan I. Böttcher
50a80c8663 temporarily disable most of main() 2026-01-16 19:12:13 +01:00
Stephan I. Böttcher
b511471d88 flash_find_free() 2026-01-12 20:16:48 +01:00
Stephan I. Böttcher
4ded8be2be ADC_RTC flag 2026-01-12 20:14:31 +01:00
Stephan I. Böttcher
8e5c806708 spi: isr rework 2026-01-12 15:32:21 +01:00
Stephan I. Böttcher
badab35bfa Makefile: all: eeprom 2026-01-12 15:31:56 +01:00
Stephan I. Böttcher
93ce9bafd0 fpga_cmd: provide .cmd 2026-01-12 14:19:13 +01:00
Stephan I. Böttcher
a5061f09e2 The machine is complete, no space left for the main program 2026-01-09 21:00:02 +01:00
Stephan I. Böttcher
8c4a860e5e section_status, pipe, fpga, HAVE_<OPT> 2026-01-08 00:36:23 +01:00
Stephan I. Böttcher
4a8d1d591f parse_command refactor 2026-01-06 13:58:50 +01:00
Stephan I. Böttcher
33468dbbea fixes and cleanups 2026-01-06 00:11:01 +01:00
Stephan I. Böttcher
5245001149 _send_sstr26, save flash in cmd 2026-01-05 14:41:39 +01:00
Stephan I. Böttcher
d042ee36aa one more day … 2026-01-04 22:40:35 +01:00
Stephan I. Böttcher
17b0623a91 flash_stream complete 2026-01-04 12:04:00 +01:00
Stephan I. Böttcher
c68f71c6c1 coding for 36h straight 2026-01-03 00:30:48 +01:00
Stephan I. Böttcher
827e699eb6 src: dose.c 2026-01-02 07:04:46 +01:00
Stephan I. Böttcher
92fbb4d611 Merge branch 'master' of codeberg.org:SiB64/turbo_weather 2025-12-21 13:30:27 +01:00
Stephan I. Böttcher
36e2ce083f gpt change, late commit 2025-12-21 13:30:17 +01:00
Stephan
b5e58cc887 add set_clock.py 2025-07-07 21:31:35 +01:00
Stephan I. Böttcher
468dac0357 turbo startup scripts 2024-11-11 22:21:45 +01:00
Stephan I. Böttcher
d0b40ebdb0 turbo.py: fix ADC_mV global{}, fix clock 2024-11-11 22:20:54 +01:00
Stephan I. Böttcher
b8f86aad8c turbo.awk: clock speed precision 2024-11-11 22:19:18 +01:00
Stephan I. Böttcher
12ba51a41d turbo.py: misc fixes and improvements
- open/read files _and_ --tty
- more precision for clock fit parameters
- s/data[c]/Data[c]/
- clear old ADC_mV{} results.
  In case of noise, invalid entries may stay around and cause spurious
  warnings
- when reparsing files, prune original lines for checksumming
- when reparsing, silently drop parser output
2024-11-11 20:39:39 +01:00
Stephan I. Böttcher
3e58d4a584 turbo.gpt: fix ranges 2024-11-11 20:38:57 +01:00
Stephan I. Böttcher
ed115c84cc ntc.py: R(), VntcT() 2024-11-11 20:37:56 +01:00
Stephan I. Böttcher
caad768f18 turbo plotting 2024-10-16 00:23:57 +02:00
Stephan I. Böttcher
19deb243f6 Merge branch 'master' of codeberg.org:SiB64/turbo_weather 2024-10-14 14:42:35 +02:00
Stephan I. Böttcher
68b9ed4381 turbo.py: fix /VDD awk compat, fix ADC DIFF mode 2024-10-14 14:42:08 +02:00
Stephan I. Böttcher
01098c9313 turbo.py: wire up baud 2024-10-13 16:06:10 +02:00
Stephan I. Böttcher
b941c83bbb turbo.py: baud, python compat, clock reprocessing 2024-10-13 14:01:45 +02:00
Stephan I. Böttcher
f63888ae7d Merge branch 'master' of codeberg.org:SiB64/turbo_weather 2024-10-13 13:59:33 +02:00
Stephan I. Böttcher
e64d5e9e68 linear_regression: reset when x is nonmonotonous with decay 2024-10-13 13:58:09 +02:00
Stephan I. Böttcher
6b1ca3f30c linear_regression.py: -l, fixes 2024-10-13 13:53:59 +02:00
Stephan I. Böttcher
efeb5bd9a0 turbo.py: checkmark, less noise 2024-10-07 09:42:39 +02:00
Stephan I. Böttcher
741b2bb361 turbo.py: resp(), clear checksum 2024-10-06 17:42:12 +02:00
Stephan I. Böttcher
94089b99ce bate.c: clear uart_cks after line_preamble 2024-10-06 17:41:33 +02:00
Stephan I. Böttcher
5498a71984 cmd.py: fix -R 2024-10-06 17:07:15 +02:00
Stephan I. Böttcher
98bbdbf933 turbo.py: emit_adc_pretty() 2024-10-06 15:38:42 +02:00
Stephan I. Böttcher
dc43b91629 turbo.py: add config parsers 2024-10-06 10:20:00 +02:00
Stephan I. Böttcher
d0cd85a1f6 cmd.py: fix ADC_MODE 2024-10-06 10:19:13 +02:00
Stephan I. Böttcher
a2dd008f0f uart_cks: clear after send_cks() 2024-10-05 20:40:42 +02:00
Stephan I. Böttcher
78e01710fc uart_tx: s/call/rcall/ 2024-10-05 20:39:52 +02:00
Stephan I. Böttcher
1cb45edbfa cmdsocket: fix cmder.open() 2024-10-05 20:38:31 +02:00
Stephan I. Böttcher
dfe357367f cmd.py: help docu typo 2024-10-05 20:37:42 +02:00
Stephan I. Böttcher
de68357084 cmd.py: ttycmd, … 2024-10-05 17:53:24 +02:00
Stephan I. Böttcher
7f169734f9 new script cmd.py
use cmdcocket to send commands to turbo.py
2024-10-05 12:47:52 +02:00
Stephan I. Böttcher
4897664e37 cmdsocket: fix option -s 2024-09-23 22:10:47 +02:00
Stephan I. Böttcher
1d89239e50 turbo.py: --socket
open a cmd socket.  Forward messages from the socket to the tty.
2024-09-23 21:47:17 +02:00
Stephan I. Böttcher
5fbcdc61b4 cmdsocket: allow .poll() when closed 2024-09-23 21:45:03 +02:00
Stephan I. Böttcher
aaf83208ba New module cmdsocket.py
Use AF_UNIX sockets to send messages between/to python scripts.
2024-09-23 21:17:22 +02:00
Stephan I. Böttcher
60816aaa16 ntc: fix β 2024-09-23 21:17:01 +02:00
Stephan I. Böttcher
5b77bb3393 turbo.py: fix, flush
fix baudrate= parameter
flush out when tty
2024-09-22 22:52:28 +02:00
Stephan I. Böttcher
5d3f364bf6 add script turbo.py
The scripts reads from stdin, files or a tty.
Verifies the chhecksums.
Computes natual values.

TODO: more parsers
TODO: add socket to send commands to the tty.
2024-09-22 22:42:27 +02:00
Stephan I. Böttcher
864be8b5ef new module linear_regression.py 2024-09-22 22:41:26 +02:00
Stephan I. Böttcher
55f7c76355 new module ntc.py 2024-09-22 22:39:40 +02:00
Stephan I. Böttcher
c2ce5dcc32 message V was ambiguous 2024-09-22 22:38:58 +02:00
Stephan I. Böttcher
7108238be6 Add send_cks()
The ADC readings were not covered by the checksum.  Send a second 'Q'
line for those.
2024-09-21 12:43:39 +02:00
Stephan I. Böttcher
7d2ca76396 Add 'Q' uart_cks
When POWER_LINE is set, send a one byte checksum of the characters
from after the preamble up to including the Q.
2024-09-21 12:20:51 +02:00
Stephan I. Böttcher
05d77d0c0b Fix rfen(0)
Do not turn off rfen() when there is a trigger, waiting for mclk_delay.
2024-09-20 22:12:30 +02:00
Stephan I. Böttcher
04f2fc03b1 Add rfen_status()
The preamble was repeated mclk_delay times, unnecessarily.
Test the rfen status to enable/send only once.
2024-09-20 21:58:13 +02:00
Stephan I. Böttcher
9a9465a561 POWER_LINE: configurable preamble
The hardwired strig was not long enough and not 'high' enough.
User the last eight bytes of the userrow as preable string.
Send 0xff 0xff 0xff 0xff 0xff 0x55 0x0a.
The last byte must be zero, string terminator.
2024-09-19 19:34:44 +02:00
Stephan I. Böttcher
e6bb585fc5 add POWER_LINE
The RF receiver's output is AC-coupled to the discriminator.  At
`rfen(1)` the threshold is not well centered.  This patch send a few
characters before the real messages, when POWER_LINE is set.

The string is not (yet?) configurable. 0xaa 0xaa 0xff 0x0a.  The first
two characters are quick single bit switches.  The third character is
basically a high level, to add some delay with an idle line. Finally, a
line feed, to separate the noise from the message.
2024-09-18 23:33:44 +02:00
Stephan I. Böttcher
aa7d2b3808 add pressure.py 2024-07-31 09:58:35 +02:00
Stephan I. Böttcher
d4dcbd250a src doc: minor typos and fixes 2024-04-20 18:43:11 +02:00
Stephan I. Böttcher
fbcbc15b4e calib.py: remove bogus output char 2024-04-20 18:40:48 +02:00
Stephan I. Böttcher
cd82aacfbd Merge branch 'master' of codeberg.org:SiB64/turbo_weather 2024-04-20 18:28:46 +02:00
Stephan I. Böttcher
43a90641f9 documentation fixes after seeing it rendered 2024-04-18 18:16:01 +02:00
Stephan I. Böttcher
3104ac4d08 documantation, v08 config 2024-04-18 17:43:56 +02:00
Stephan I. Böttcher
302def656c remove a copy from blinkenlights 2024-04-18 16:08:51 +02:00
Stephan I. Böttcher
4e598267b1 All TODOs √ 2024-04-16 05:33:15 +00:00
Stephan I. Böttcher
29eb6afed0 bate: reduce standby current into pins
MCLK and DOUT randomly stick at high or low when the µC enters standby
mode.

MCLK TCA0 WO0 is properly disabled to keep the pin at low level.  A high
level pushes current into the pressure sensor.

DOUT input pullup is not enabled.  A low level on DOUT (LSB of the
temperature) pulls current from the pullup.
2024-04-15 19:53:21 +02:00
Stephan I. Böttcher
91d20af691 minor style fixes, review with Björn 2024-04-15 19:52:33 +02:00
Stephan I. Böttcher
fa47c885d7 make: explicit spi clk speed cpu_clk/64 2024-04-15 19:51:19 +02:00
Stephan I. Böttcher
4c72530731 make: build bate.eeprom with bate.hex 2024-04-15 15:56:36 +02:00
Stephan I. Böttcher
b934f2a075 bate: simplify config_period 2024-04-15 08:58:10 +02:00
Stephan I. Böttcher
061549ca94 bate: fix send_config 2024-04-15 08:47:57 +02:00
Stephan I. Böttcher
e31bd4ac51 oops … 2024-04-14 23:31:03 +02:00
Stephan I. Böttcher
46e6162a02 cmd: K set clock, D test data test 2024-04-14 23:28:45 +02:00
Stephan I. Böttcher
c728e20ea7 bate v0.7, config 2024-04-14 23:27:58 +02:00
Stephan I. Böttcher
3ecc3501c5 cmd: require a sig for C as well 2024-04-14 22:36:18 +02:00
Stephan I. Böttcher
b2eeccfe65 cmd: E and U for writing EEPROM and USERROW 2024-04-14 22:14:11 +02:00
Stephan I. Böttcher
7d4c9b21be uart: tx buffer 128 bytes → 256 bytes, stack size 128 bytes 2024-04-14 21:31:19 +02:00
Stephan I. Böttcher
ae3a48fcaa trigger: fix immediate 2024-04-14 20:32:57 +02:00
Stephan I. Böttcher
cc9ce887d4 cmd T, fix period trigger 2024-04-14 20:23:47 +02:00
Stephan I. Böttcher
3467a5e51c rtc: add debug counter 2024-04-14 20:21:46 +02:00
Stephan I. Böttcher
a9b687312e cmd: work now, add some assembler 2024-04-14 19:40:04 +02:00
Stephan I. Böttcher
276bd40a33 rtc: add debug counter 2024-04-14 19:38:27 +02:00
Stephan I. Böttcher
ad2246675f rtc: add debug counter 2024-04-14 19:35:54 +02:00
Stephan I. Böttcher
5757d31416 cmd: fix parse_hex_nibble(), better output, W command 2024-04-14 04:36:26 +02:00
Stephan I. Böttcher
bcaf9e3de6 uart: fix in rx_dismiss: 2024-04-14 04:35:10 +02:00
Stephan I. Böttcher
38db87a030 uart: mostly assembler, now
Most uart code is now in `uart_tx.S`.
Code close to the hardware stays in `uart.c` to use `io.h`.
The .S does not use the CPP, yet.
2024-04-14 03:36:55 +02:00
Stephan I. Böttcher
4ea0d71d5d uart: save a few instructions 2024-04-13 19:02:54 +02:00
Stephan I. Böttcher
61d78b9bdc uart: add missing RETI 2024-04-13 17:46:44 +02:00
Stephan I. Böttcher
0ba62420ab cmd: only accept lowercase hex digits
Not to confuse hex parameters with uppercase commands.
2024-04-13 17:45:08 +02:00
Stephan I. Böttcher
0801c0513d make: drop bate_CFLAGS 2024-04-13 17:44:04 +02:00
Stephan I. Böttcher
e24402ce66 cmd: C and M 2024-04-13 01:49:17 +02:00
Stephan I. Böttcher
7f20ce610f adc: fix and call send_calib_adc() 2024-04-12 18:58:12 +02:00
Stephan I. Böttcher
7e18810b42 adc: fix saddus() 2024-04-12 18:34:23 +02:00
Stephan I. Böttcher
25bebed7df uart: rework uart_busy
- implement `tx()` in asm, does not work yet!
- enable TXC interrupt, to be woken when the uart is done
  in order to power down.  Alias the DRE interrupt.
- Rework `send_char()` and `send_str()`.
- Remove …_nosleep() and `panik()`.
- Better account for volatile-ness of buffer pointers.
2024-04-12 16:41:46 +02:00
Stephan I. Böttcher
306904b372 bate: print eeprom in words 2024-04-12 16:40:57 +02:00
Stephan I. Böttcher
68cc24fb7c adc: calibrated output
- add calib values to `adc_conf`,
- double the size of `struct adc_conf`
- reduce the sape for `testdata`.
- implement the calibration with offset and scale.
2024-04-12 02:08:31 +02:00
Stephan I. Böttcher
d7b100e02c eeprom: forget about specifying the address or vars 2024-04-12 02:07:04 +02:00
Stephan I. Böttcher
3aaf28484d POWER_LED and POWER_SYDBY 2024-04-11 21:22:53 +02:00
Stephan I. Böttcher
850cd6a4fb uart: fix uart_busy(), we were powering down too early 2024-04-11 21:22:05 +02:00
Stephan I. Böttcher
707f4512e8 bate: output config improvements
- new config.cperiod: print configuration one in a while
- SEND_BATED: print `W` and `D` sensor words separately,
  send `W` with config.
- testdata: do not wait for config.period.
- clock: dorp `trigger` from `T` record,
  send it with debug `X` instead
- config version 4, sane order.
- adc `A`: do not send unused entries.
2024-04-11 17:02:51 +02:00
Stephan I. Böttcher
b3dcb7d104 adc: stop at first unused adc_conf entry 2024-04-11 11:30:17 +02:00
Stephan I. Böttcher
8050b9abf0 make isr variables volatile 2024-04-11 04:09:34 +02:00
Stephan I. Böttcher
358da80f46 move adc_conf and test_data into the EEPROM 2024-04-11 03:51:19 +02:00
Stephan I. Böttcher
edb1544cd7 print E adc_conf on boot 2024-04-10 18:20:41 +02:00
Stephan I. Böttcher
ee03f01b06 greeting improvement 2024-04-10 18:15:24 +02:00
Stephan I. Böttcher
ebdb88118c baye.config: add BC_SPI 2024-04-10 18:13:57 +02:00
Stephan I. Böttcher
a3eb8eb7b9 spi: replace bit banging with SPI0 2024-04-10 18:10:57 +02:00
Stephan I. Böttcher
82f0da6a89 calib: fix turbo test case[0] 2024-04-10 18:09:09 +02:00
Stephan I. Böttcher
9302c61244 bate: use 16-bit frames 2024-04-10 01:24:24 +02:00
Stephan I. Böttcher
77463da388 bate_wait: sleep() waiting for pin interrupt 2024-04-09 23:55:10 +02:00
Stephan I. Böttcher
cd6355021b uart: pin sense bothedges 2024-04-09 23:50:43 +02:00
Stephan I. Böttcher
4bbd6c264a adc: now it works w/ interrupts
The problem was: the TCA0 IRQ was busylooping

`send_char()` now sleeps.
Debug counters are cleared.
Config version 3 with acquisition period.
2024-04-09 23:19:08 +02:00
Stephan I. Böttcher
09cc30d854 adc: worknow, but w/o isr
The RESRDY vector is never called, neither the SAMPRDY, when enabled.
No idea why. We do polling now.
No idea what wakes the CPU from sleep with STOP_MCLK.
2024-04-09 19:08:23 +02:00
Stephan I. Böttcher
ebb9d88b81 uart: panic(), spaces in hexdump 2024-04-09 18:14:39 +02:00
Stephan I. Böttcher
a4b1e0b1b1 bate: fixes
- adc.h: adc_init(), disable inputs used for analog inputs.
- bate: PORTA → VPORTA, restore blinkenlight's bate_bit()
- config V2: add cpu_clk, remove config pointer indirection
- CLKCTRL: make extra sure we run at 10 MHz
    → regained telemetry at 2400 baud
- turn on LED while digitizing and transmitting
2024-04-09 00:43:34 +02:00
Stephan I. Böttcher
89d64222e6 Makefile: BV_VERS=2 2024-04-09 00:42:25 +02:00
Stephan I. Böttcher
233a175151 calib: fix bitsize of dT, add test case 2024-04-09 00:41:13 +02:00
Stephan I. Böttcher
cac8adf51f adc: fix globals, add adc_init() 2024-04-09 00:40:07 +02:00
Stephan I. Böttcher
0693ab1204 mul16sun: fix case n=0 2024-04-09 00:38:13 +02:00
Stephan I. Böttcher
c8f9b34757 adc: annotate asm savings in isr 2024-04-08 10:05:55 +02:00
Stephan I. Böttcher
2e66f12041 adc readout 2024-04-08 01:47:46 +02:00
Stephan I. Böttcher
e8aaefbe1f make %.id %.verify: -qq 2024-04-07 17:45:42 +02:00
Stephan I. Böttcher
23dc2085ea text_calib to SEND_HEX 2024-04-07 12:25:20 +02:00
Stephan I. Böttcher
53a77c75aa uart: send*(): pointers to const payload 2024-04-07 11:53:22 +02:00
Stephan I. Böttcher
2d7ac8c6af %.s: depend on %.o to capture dependencies from %.d 2024-04-07 11:52:03 +02:00
Stephan I. Böttcher
2665750308 make %.verify 2024-04-07 11:34:31 +02:00
Stephan I. Böttcher
0179568e85 make bate.config, individual vars 2024-04-07 10:54:10 +02:00
Stephan I. Böttcher
ef65e2dc0e decimal_str() 2024-04-07 01:09:55 +02:00
Stephan I. Böttcher
0b680c1b78 calib: tests and fixes 2024-04-06 21:42:18 +02:00
Stephan I. Böttcher
900089c787 uart: fix uart_break_p() polarity 2024-04-06 12:52:39 +02:00
Stephan I. Böttcher
d0793f88f0 calib: python test implementation 2024-04-06 12:29:30 +02:00
Stephan I. Böttcher
aaa3fcc591 mul: handcraftet __asm__ multiplication, buggy 2024-04-06 12:27:44 +02:00
Stephan I. Böttcher
7b738aef34 bate: uart, rtc fixes 2024-04-05 21:38:15 +02:00
Stephan I. Böttcher
8eb2bb5518 bate: calib 2024-04-04 23:31:49 +02:00
Stephan I. Böttcher
0022e53ceb bate: new features galore … 2024-04-04 20:48:00 +02:00
Stephan I. Böttcher
f93edddb30 bate: add uart.[ch], use send_hex() 2024-04-04 12:16:42 +02:00
Stephan I. Böttcher
f9e4c7cf9d avr: speed up the MCLK from 3.3MHz to 10 MHz 2024-04-03 21:55:54 +02:00
Stephan I. Böttcher
4525a17d68 enable the time TCA0 2024-04-03 19:30:39 +02:00
Stephan I. Böttcher
bf375a3f48 src/README 2024-04-03 06:04:57 +00:00
Stephan I. Böttcher
b389d222da Sensor reading from blinkenlights, ported to ATtiny424
This compiles with an up to date toolchain pulled from git.

TODO:
- Use the SPI hardware to talk to the sensor.
- Send results via UART hardware.
- Setup the watchdog.
- Control power to the RF transmitter
- Light the LED.
- Readout the ADCs, thermistors.
- Readout the internal temperature sensor.
2024-04-03 07:55:02 +02:00
Stephan I. Böttcher
7d2bf84a43 copy bate.c from blinkenlights 2024-04-02 15:34:19 +02:00
60 changed files with 12186 additions and 0 deletions

8
.gitignore vendored
View file

@ -17,7 +17,15 @@ thhor_crs.xy
gerber/*.pdf
gerber/*.odt
gerber/thhor_crs-*.png
*.o
*.d
*.eeprom
*.hex
*.map
pdfs/
sensor/*.pdf
*.vvp
vcd
*.log
sallen-key-pulse.hex

3
.gitmodules vendored
View file

@ -1,3 +1,6 @@
[submodule "src/bch4369"]
path = src/bch4369
url = git@codeberg.org:SiB64/bch4369
[submodule "fpga/solo"]
path = fpga/solo
url = ../solo_altera

5166
sensor/bgo-ssd.dxf Normal file

File diff suppressed because it is too large Load diff

1
src/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
thhor.userrow

150
src/Makefile Normal file
View file

@ -0,0 +1,150 @@
PROJ=thhor
PATH:=/usr/local/bin:$(PATH)
VPATH=.:bch4369
default: all
all: $(PROJ).hex $(PROJ)_all
thhor_all: thhor.eeprom
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
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
CC=avr-gcc -Wall -Wno-parentheses -MMD -std=c99 $(OPT) \
-mmcu=$(MCU) \
-funsigned-char \
-funsigned-bitfields \
-fpack-struct \
-fshort-enums \
-mtiny-stack \
-mint8
SN = $(SN_$(PROJ))
CFLAGS = $(CFLAGS_$(PROJ)) $(DEBUG) -I. -DSN="$(SN)"
C_FILES = $(C_FILES_$(PROJ))
S_FILES = $(S_FILES_$(PROJ))
OBJS = $(patsubst %.c, %.o, $(C_FILES)) $(patsubst %.S, %.o, $(S_FILES))
%.s: %.c
$(CC) $(CFLAGS) -S $<
%.o: %.c
$(CC) $(CFLAGS) -c $<
%.o: %.S
$(CC) $(CFLAGS) -c $<
%.m: %.S
$(CC) $(CFLAGS) -E -dM $< > $@
-include *.d
LDFLAGS = -Teeprom.ld
%.elf: %.o $(OBJS)
$(CC) $(CFLAGS) -Wl,-Map=$*.map,--cref $^ --output $@ $(LDFLAGS)
OBJCOPY = avr-objcopy
%.hex: %.elf
$(OBJCOPY) -O ihex -R .eeprom -R .eemap -R .userrow -R .uumap $< $@
%.eeprom: %.elf
$(OBJCOPY) -O ihex -j .eemap --change-section-lma .eemap=0 $< $@
%.userrow: %.elf
$(OBJCOPY) -O ihex -j .uumap --change-section-lma .uumap=0 $< $@
pMCU-attiny424 = t424
pMCU-attiny824 = t824
pMCU-attiny3224 = t3224
# WDT
fuse0_dose= 0x00
# BOD
fuse1_dose= 0x00
# OSC, 20 MHz
fuse2_dose= 0x7e
# ???
fuse4_dose= 0xff
# SYS0 (default 0xf6) RESET, EEPROM erase
fuse5_dose= 0xf7
# SYS1 startup time (64ms)
fuse6_dose= 0xff
# APPEND
fuse7_dose= 0x00
# BOOTEND
fuse8_dose= 0x00
fuses_dose =$(patsubst %, 0x%, 00 00 7e ff ff f7 ff 00 00)
fuses_thhor = $(fuses_dose)
AVRDUDEPROG = avrdude
AVRDUDE = $(AVRDUDEPROG)
AVRDUDE_PROGRAMMER = serialupdi
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
id: $(PROJ).id
%.id:
$(AD) -qq -U signature:v:"$(sig_$*)":m
ad: $(PROJ).ad
%.ad:
$(AD) -v -t
%.burn: %.hex %.id
$(AD) -U flash:v:$< || $(AD) -U flash:w:$<
%.verify: %.hex %.eeprom %.userrow
-$(AD) -qq -U fuses:v:"$(fuses_$*)":m
-$(AD) -qq -U userrow:v:$(word 3, $^)
-$(AD) -qq -U eeprom:v:$(word 2, $^)
-$(AD) -qq -U flash:v:$<
fuse: $(PROJ).fuse$F
%.fuse$F:
echo "$*: fuse$F = $(fuse$F_dose)"
[ -n "$(fuse$F_dose)" ] && $(AD) -B 5 -U fuse$F:w:$(fuse$F_dose):m
clean:
rm -f *.hex *.o *.s *.map *.elf *.d
# $(PROJ).hex: $(PROJ).eeprom $(PROJ).userrow
.PHONY: eeprom.eeprom
eeprom.eeprom:
$(AD) -U eeprom:r:$@
%.eeprom.burn: %.eeprom
$(AD) -U eeprom:v:$< || $(AD) -U eeprom:w:$<
%.userrow.burn: %.userrow
$(AD) -U userrow:v:$< || $(AD) -U userrow:w:$<

194
src/README.md Normal file
View file

@ -0,0 +1,194 @@
# Turbo Weather
## ATtiny424SS resources
- `TCA0-WO0-PB0`: generate the 32768kHz clock for the MS5534C
- `SPI0-PA1…3`: communication with the MS5534C
- `USART0-RxD/TxD-PB2…3`: transmission of data, receive commands
- `PORTB-PB1`: enable the RF power regulator
- `PORTA-PA5`: light up the LED
- `ADC0-PA4,6,7`: read Batterie, NTC, and RF power voltage, and internal sources.
- `PIT`: generate clock tick, to wake the µC once per second from deep sleep.
- `USERROW`: store persistent configuration
- `EEPROM`: store `ADC` readings configuration, store sensor test data records.
- `RAM`: 256 byte TX buffer, 16 bytes RX buffer, ~128 bytes stack.
- `FLASH`: pretty full. With lots of assembly, -O2 fits with -DDEBUG.
## Source files
- `bate.c`: main program, interface to the MS5534C
- `calib.c`: calculate the calibrated the pressure sensor readings.
- `adc.c`: configure and run the ADC, calculate calibrated results
- `mul.c`: handcraft 16-bit multiplication and decimal printing.
- `rtc.c`: configure the _periodic interrupt timer_ `PIT`.
- `spi.c`: configure the `SPI0` and run a 16-bit frame.
- `uart.c`: configure the USART0, provide Tx and Rx buffers for IO.
- `cmd.c`: parse and run simple commands received from the UART.
## Output records
The programm issues several types of output lines. Each type is
prefixed with a unique capital letter.
- `V`: sent at boot, a greeting and version.
- `B`: sent at boot, a single byte `RSTCTRL.RSTFR`, the reset reason.
- `S`: config record (`SEND_CONFIG`): hex dump of the `SIGROW`.
- `F`: config record (`SEND_CONFIG`): hex dump of the `FUSES`.
- `U`: config record (`SEND_CONFIG`): hex dump of the `USERROW`.
- `C`: config record (`SEND_CONFIG`): hex dump of the `config` structure in `RAM`.
- `E`: config record (`SEND_CONFIG`): hex dump of the ADC configuration in `EEPROM`.
- `W`: calibration record (`SEND_BATEW`): sensor calibration data (hex) read from the sensor.
- `D`: sensor record (`SEND_BATED`): sensor reading data (hex) acquired from the sensor.
- `P`: pressure record (`SEND_CALIB`): sensor readings in natural units.
- `A`: ADC readings (`SEND_ADC_HEX`): hex words, 16-bit range.
- `V`: ADC readings (`SEND_ADC_VOLT`): calibrated, natural units.
- `X`: Debug (`SEND_DEBUG`): hex dump of the `debug_data` structure.
- `R`: command reception.
Config records are sent at boot, with test data, and when enabled via
`SEND_CONFIG` with a subset of sensor readings. The config byte `confp`
is the number of readings sent in between without sending config records.
Debug data is only available when not disabled during compilation. Do
`make DEBUG=-DNODEBUG` to disable all debugging code and data.
The `SEND` flags are stored in byte[3] of the `config` structure to
enable the respective output records, with the bit positions
- `SEND_CONFIG = 0x01`
- `SEND_BATED = 0x02`
- `SEND_BATEW = 0x04`
- `SEND_CLOCK = 0x08`
- `SEND_CALIB = 0x10`
- `SEND_ADC_HEX = 0x20`
- `SEND_ADC_VOLT = 0x40`
- `SEND_DEBUG = 0x80`
## Configuration
At boot, the configuration is copied from the `USERROW` to the
`config` structure in `RAM`, if the first byte in the `USERROW` is
the magic `0xba`, and the second byte matches the version of the
`config` structure, currently `0x08`. Else, the defaults are used.
The config structure is
- `[0]`: `magic = 0xba`.
- `[1]`: `version = 0x08`.
- `[2]`: `triggers`: trigger enables.
- `[3]`: `send`: output data configuration.
- `[4]`: `power`: power management.
- `[5]`: `calib_test`: number of test records to send after a reset.
- `[6]`: `spi_div`: `SPI0.CTRLA.SPI_PRESC` \[÷64\]
- `[7]`: `mclk_delay`: number of `MCLK` ticks to wait before a reading.
- `[8]`: `period`: number of seconds-1 between readings.
- `[9]`: `confp`: number of readings without config records.
- `[10]`: `cpu_clk`: `CLKCTRL.MCLKCTRLB` \[÷2\]
- `[11]`: `mclk_period`: `TCA0.SINGLE.CMP0` \[÷76\]
- `[12]`: `baud_div`: two bytes little endian \[÷16667\]
- `[14]`: `uart_mode`: `USART0.CTRLB`
- `[15]`: `pit_period`: `RTC.CLKSEL` \[÷1024\]
- `[16]`: `immediate`: number of immediate readings at boot.
### Triggers
The `triggers` byte\[2\] enables various reasons to do a sensor reading.
- `TRIGGER_ONCE = 0x01`: one reading at boot.
- `TRIGGER_CONT = 0x02`: continuously read the sensor.
- `TRIGGER_UART = 0x04`: read the sensor when the UART Rx input toggles.
- `TRIGGER_CLOCK = 0x08`: read periodically, every `period`+1 _seconds_.
- `TRIGGER_BREAK = 0x10`: read continuously while the UART Rx input is low.
- `TRIGGER_IMMED = 0x20`: do any requested immediate readings.
_Seconds_ are define by the `PIT`. Those can be up to 8 real seconds
long, or much shorter. _Immediate_ readings can be requested at boot
or via the command `T`.
### Power
The bits in the power byte\[4\] are
- `POWER_DOWN = 0x01`: Enter `POWER_DOWN` sleep between readings.
- `POWER_DOWN_CLI = 0x02`: Disable interrupts before `POWER_DOWN`.
- `STOP_MCLK = 0x04`: Stop the `MCLK` after the reading.
- `POWER_LED = 0x08`: Turn on the LED during a reading.
- `POWER_STDBY = 0x10`: Enter `STANDBY` sleep between readings.
- `POWER_RX = 0x20`: Do not `POWER_DOWN` when the `UART` Rx is active
- `POWER_RF = 0x40`: Turn on the RF transmitter power.
- `POWER_LINE = 0x80`: Send a preamble after rfen()
`POWER_STDBY` is sufficient to reduce the power to a minimum.
`POWER_RX` does not seem to work. From `POWER_DOWN_CLI` we shall
never wake up, unless the `WDT` is enabled in the fuses. When the
`MCLK` is stopped after a reading, it will be truned on and
`mclk_delay` ticks must pass before communication with the sensor
resumes. That delay is probably not necessary. `POWER_RF` is
necessary to bias the NTC measured by ADC readings. The `POWER_LINE`
preamble charges the AC-coupled output of the RF receiver to
properly receive subsequent characters.
## Commands
Commandlines received via the UART must be in the format
- `«C» {[«space»]+ «hex»}+ [«space»]+ «linefeed»`
A command letter, uppercase, and up to seven (optionally space
separated) hex bytes, optionally followed by more space characters and
a linefeed. A hex character is one or two hex digits, letters `a``f`
_must_ be lowercase. Obviously, a single hex digit must be separated
with space characters from any following byte. No spaces are required
at all when all bytes are written with two digits. The Rx buffer can
accomodate commandlines up to 15 bytes long, including the newline
char.
The parser echos the received line, preceeded with the string `R>`.
When the command is valid, the answer bytes are sent prefixed with
`R!`. Invalid commands are answered with `R?`.
Most commands require a specific number of hex bytes as arguments. As
currently implemented, all commands echo all their arguments and one
additional byte.
### List of commands:
- `R «ccp»`: Reboot the µC. The argument byte must be `d8`, the
`CCP[IOREG]` key, to validate the reset.
- `C «key» «bytes»…`: write to the `config` structure in `RAM`
- `U «key» «bytes»…`: write to persistent config in the `USERROW`
- `E «key» «bytes»…`: write to `EEPROM`
Up to six «bytes» are written. The «key» must be `9d`, the
`CCP[SPM]` key, for the persistent stores, and `ba` for the `RAM`.
These commands return the old value of the first byte that was
written.
- `T «n»`: trigger «n» more immediate sensor readings.
- `M «aaaa»`: reads any memory address «aaaa» (two bytes, big endian)
and prints the byte.
- `W «aaaa» «bb»`: write a byte «bb» into any address «aaaa», return
the old value of that memory location.
- `K «bb» «bb» «bb» «bb»`: set the clock. The 32-bit time value must
be sent little endian.
- `D «n»`: process and send calibrated readings for «n» test data
records. The `EEPROM` has space for up to five data records.
## Toolchain
The ATtiny424 µC requires an up-to-date toolchain
- binutits: `./configure --target=avr --program-prefix=avr-`
- gcc: `../gcc/configure --program-prefix=avr- --with-avrlibc --target=avr --enable-languages=c --disable-nls`
- avr-libc: `./configure --host=avr`
## TODO (all done)
- √ Use the SPI hardware to talk to the sensor.
- √ Send results via UART hardware.
- × Setup the watchdog. √ Use PIT instead
- √ Control power to the RF transmitter
- √ Light the LED.
- √ Readout the ADCs, thermistors.
- √ Readout the internal temperature sensor.

222
src/adc.c Normal file
View file

@ -0,0 +1,222 @@
//
// adc.c
//
#include "config.h"
#include "adc.h"
#include <avr/interrupt.h>
#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_TRIGGER,
MODE_DIFF = ADC_TRIGGER | ADC_DIFF_bm,
};
__attribute__((section(".eeprom1")))
struct adc_conf adc_conf[N_ADC] = {
{ // V_CC / 10
.mode = MODE,
.ref = REF | ADC_REFSEL_1024MV_gc,
.inp = INP | ADC_MUXPOS_VDDDIV10_gc,
},
{ // Internal Temperature
.mode = MODE,
.ref = REF | ADC_REFSEL_1024MV_gc,
.inp = INP | ADC_MUXPOS_TEMPSENSE_gc,
},
#ifdef HAVE_nFETs
{ // VD1
.mode = MODE,
.ref = REF | ADC_REFSEL_1024MV_gc,
.inp = INP | ADC_D1,
},
{ // VD2
.mode = MODE,
.ref = REF | ADC_REFSEL_1024MV_gc,
.inp = INP | ADC_D2,
},
{ // VD1
.mode = MODE,
.ref = REF | ADC_REFSEL_2500MV_gc,
.inp = INP | ADC_D1,
},
{ // VD2
.mode = MODE,
.ref = REF | ADC_REFSEL_2500MV_gc,
.inp = INP | ADC_D2,
},
{ // VDG
.mode = MODE,
.ref = REF | ADC_REFSEL_2500MV_gc,
.inp = INP | ADC_G,
},
{ // VDG
.mode = MODE,
.ref = REF | ADC_REFSEL_VDD_gc,
.inp = INP | ADC_G,
},
#endif
};
section_status(adc.n) uint8_t adc_current;
section_status(adc.r) uint16_t adc_readings[N_ADC];
#ifdef HAVE_nFETs
section_status(pwm) uint16_t pwm_dutycycle;
#endif
static inline
void start_conversion(const struct adc_conf *c)
{
ADC.CTRLC = c->ref;
ADC.MUXNEG = c->inn;
ADC.MUXPOS = c->inp;
ADC.COMMAND = c->mode;
}
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),
conf_io(ADC.CTRLF, ADC_SAMPNUM_ACC256_gc | ADC_LEFTADJ_bm),
conf_io(ADC.INTCTRL, ADC_RESRDY_bm),
conf_prefix(EVSYS),
conf_io(EVSYS.CHANNEL0, EVSYS_CHANNEL0_TCA0_CMP1_LCMP1_gc),
conf_io(EVSYS.USERADC0START, 1),
};
void start_adc()
{
ADC.COMMAND = 0;
adc_current = 0;
start_conversion(adc_conf);
}
#if 0
ISR(ADC0_RESRDY_vect)
{
uint8_t i = adc_current;
if (i >= N_ADC)
goto stop;
adc_readings[i] = *(volatile uint16_t *) &ADC.RESULT;
while (++i < N_ADC) {
const struct adc_conf *c = adc_conf + i;
if (c->mode) {
start_conversion(c);
adc_current = i;
return;
}
}
stop:
ADC.COMMAND = 0;
adc_current = N_ADC;
}
#else
// This saved (at some earlier point of debugging)
// - 29 of 86 instructions,
// - 24 of 110 clk cycles in the common path
// - 9 of 14 bytes of stack space,
// - and avoids unlikely branches.
ISR(ADC0_RESRDY_vect, ISR_NAKED)
{
__asm__(
"push r24" "\n\t"
"in r24, __SREG__" "\n\t"
"push r24" "\n\t"
"push r25" "\n\t"
"push r30" "\n\t"
"push r31" "\n\t"
"lds r24, adc_current" "\n\t"
"cpi r24, %[NADC]" "\n\t"
"brcc 2f" "\n\t"
"mov r30, r24" "\n\t"
"ldi r31, 0" "\n\t"
"lsl r30" "\n\t"
"subi r30, lo8(-(adc_readings))" "\n\t"
"sbci r31, hi8(-(adc_readings))" "\n\t"
"lds r25, %[RESULT]" "\n\t"
"st Z, r25" "\n\t"
"lds r25, %[RESULT]+1" "\n\t"
"std Z+1, r25" "\n"
"1:" "\n\t"
"subi r24, -1" "\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"
"lsl r30" "\n\t"
"lsl r30" "\n\t"
"subi r30, lo8(-(adc_conf))" "\n\t"
"sbci r31, hi8(-(adc_conf))" "\n\t"
"ldd r25, Z+1" "\n\t"
"sts %[CTRLC], r25" "\n\t"
"ldd r25, Z+3" "\n\t"
"sts %[MUXNEG], r25" "\n\t"
"ldd r25, Z+2" "\n\t"
"sts %[MUXPOS], r25" "\n\t"
"ld r25, Z" "\n\t"
"tst r25" "\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"
"pop r25" "\n\t"
"pop r24" "\n\t"
"out __SREG__, r24" "\n\t"
"pop r24" "\n\t"
"reti" "\n"
: :
[NADC] "n" (N_ADC),
[RESULT] "n" (&ADC.RESULT),
[CTRLC] "n" (&ADC.CTRLC),
[MUXPOS] "n" (&ADC.MUXPOS),
[MUXNEG] "n" (&ADC.MUXNEG),
[COMMAND] "n" (&ADC.COMMAND)
);
}
#endif
void adc_start_stream(uint8_t flag)
{
#ifdef HAVE_nFETs
if (flag & ADC_PWM)
pwm_set(config.pwm_min, 1);
#endif
start_adc();
}
uint8_t adc_poll(uint8_t flags)
{
time(); // fill `clock`
uint8_t r = adc_busy();
if (r)
return r;
if (flags) {
if (flags & ADC_RTC && !rtc_cnt_tick())
return 1;
#ifdef HAVE_nFETs
if (flags & ADC_PWM)
pwm_step(config.pwm_max);
pwm_dutycycle = PWM.CMP0;
if (pwm_busy() || flags & ADC_CONV)
#endif
start_adc();
}
return 0;
}

45
src/adc.h Normal file
View file

@ -0,0 +1,45 @@
//
// adc.h
//
#include <avr/io.h>
#define ADC ADC0
struct adc_conf {
uint8_t mode;
uint8_t ref;
uint8_t inp;
uint8_t inn;
};
#ifdef HAVE_nFETs
#define N_ADC 8
#define ADC_D1 7
#define ADC_D1_PINCTRL PORTA.PIN7CTRL
#define ADC_D2 10
#define ADC_D2_PINCTRL PORTB.PIN1CTRL
#define ADC_G 6
#define ADC_G_PINCTRL PORTA.PIN6CTRL
#else
#define N_ADC 2
#endif
extern uint16_t adc_readings[N_ADC];
extern uint8_t adc_current;
void start_adc();
void init_adc();
static inline uint8_t adc_busy() { return ADC.COMMAND; }
extern struct adc_conf adc_conf[N_ADC];
uint8_t adc_poll(uint8_t flag);
void adc_start_stream(uint8_t flag);
enum adc_flags {
ADC_CONV = 1,
ADC_PWM = 2,
ADC_SIZE = 0x70, // opencoded in pipe.c
ADC_16 = 0x10,
ADC_32 = 0x20,
ADC_64 = 0x40,
ADC_RTC = 0x80,
};

43
src/base85.c Normal file
View file

@ -0,0 +1,43 @@
#include <stdint.h>
#include <base85.h>
uint32_t divmod85(uint32_t u, uint8_t *m);
uint32_t mul85(uint32_t u, uint8_t m);
uint8_t base85_error;
#ifdef BASE85C
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);
*--s = (u>>24) + 33;
return 5;
}
const uint8_t *base85_decode(const uint8_t *s, uint32_t *u)
{
uint32_t uu = 0;
uint8_t c;
for (int i=0; i<5; i++) {
while (1) {
c = *s++ - 33;
if (c >= 85)
goto error;
uu = mul85(uu, c);
break;
}
}
c = 0;
error:
base85_error = c;
*u = uu;
return s;
}
#endif

4
src/base85.h Normal file
View file

@ -0,0 +1,4 @@
extern uint8_t base85_error;
int base85_encode(uint32_t u, uint8_t *s);
const uint8_t *base85_decode(const uint8_t *s, uint32_t *u);

25
src/base85.py Normal file
View file

@ -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)

132
src/base85_test.c Normal file
View file

@ -0,0 +1,132 @@
#include <stdio.h>
#include <stdint.h>
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);
}

175
src/base85a.S Normal file
View file

@ -0,0 +1,175 @@
;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: 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
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 r19, '!'
adc r1, r19
st -X, r1
clr r1
ret
#ifndef BASE85C
.global base85_encode
base85_encode:
movw r26, r20
adiw r26, 5
st X, r1
rcall _divmod85
rcall _divmod85
rcall _divmod85
rcall _divmod85
subi r25, -'!'
st -X, r25
ret
#endif
; uint32_t mul85(uint32_t u, uint8_t m);
#ifdef BASE85C
.global mul85
mul85:
#endif
;; TODO for SPACE: inline this
;; big endian r22/r23/r24/r25 *= 85; … += r20
_mul85:
ldi r21, 85
mul r25, r21
movw r18, r0
mul r24, r21
movw r24, r0
mul r23, r21
movw r30, r0
mul r22, r21
add r18, r20
adc r24, r19
adc r30, r25
adc r31, r0
mov r25, r18
mov r23, r30
mov r22, r31
#ifdef BASE85C
clr r1
#endif
ret
#ifndef BASE85C
.global base85_decode
base85_decode:
push r17
push r22
push r23
movw r26, r24
clr r25
clr r24
clr r23
clr r22
ldi r17, 5
1:
ld r20, X+
subi r20, '!'
brcs 2f
cpi r20, 85
brcc 2f
rcall _mul85
subi r17, 1
brne 1b
clr r20
2:
sts base85_error, r20
pop r31
pop r30
st Z, r22
std Z+1, r23
std Z+2, r24
std Z+3, r25
movw r24, r26
pop r17
clr r1
ret
#endif

636
src/bate.c Normal file
View file

@ -0,0 +1,636 @@
//
// bate.c
//
// !!! int = int8_t
#include <stdint.h>
#include <string.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
#include <avr/wdt.h>
#include "bate.h"
#include "rtc.h"
#include "calib.h"
#include "mul.h"
#include "adc.h"
#include "spi.h"
#define Bit(x) (1<<(x))
////////////////////////////////////////////////////////////////////////////////
//
// LED on PA5
#define LED_P VPORTA
#define LED_B Bit(5)
static inline
void init_led()
{
LED_P.DIR |= LED_B;
}
static inline
void led(uint8_t on)
{
if (on)
LED_P.OUT |= LED_B;
else
LED_P.OUT &=~ LED_B;
}
////////////////////////////////////////////////////////////////////////////////
//
// RFEN on PB1, enable the LDO to power the RF transmitter
#define RFEN_P VPORTB
#define RFEN_B Bit(1)
static inline
void init_rfen()
{
RFEN_P.DIR |= RFEN_B;
}
static inline
void rfen(uint8_t on)
{
if (on)
RFEN_P.OUT |= RFEN_B;
else
RFEN_P.OUT &=~ RFEN_B;
}
static inline
uint8_t rfen_status()
{
return RFEN_P.OUT & RFEN_B;
}
/////////////////////////////////////////////////////////////////////////////
//
// BATE, read data from an MS5534C pressure sensor
#define BATE_PORT VPORTA
#define SCK_PORT 3
#define DOUT_PORT 2
#define DIN_PORT 1
// MCLK timer.
//
// The nominal f_{clk_per} is 10 MHz,
// f_{MCLK} needs to be 32768 Hz
// f_{tick} needs to be 2×f_{MCLK}
// Timer 1 TOP needs to be f_{clk_per}/65536
#ifndef PERIOD
# define PERIOD 76
#endif
// user TCA0 in 16 bit mode. SPLIT mode is also viable.
#define MCLK TCA0.SINGLE
// The CMP0 port seems to be randomly high after TCA disable
// The pin on the sensor draws about 30µA when high.
// Just toggling the ENABLE bit is insufficient.
static inline
void mclk(uint8_t on)
{
if (on) {
MCLK.CTRLB = TCA_SINGLE_CMP0EN_bm | TCA_SINGLE_WGMODE_FRQ_gc;
MCLK.CTRLA = TCA_SINGLE_CLKSEL_DIV2_gc | TCA_SINGLE_ENABLE_bm;
}
else {
MCLK.CTRLA = 0;
MCLK.CTRLB = 0;
}
}
static inline
void init_mclk(uint8_t p)
{
if (!p || p==0xff)
p = PERIOD;
VPORTB.DIR |= Bit(0); // MCLK
MCLK.INTCTRL = TCA_SINGLE_CMP0_bm;
MCLK.CMP0 = p;
mclk(1);
BATE_PORT.DIR |= Bit(DIN_PORT);
BATE_PORT.DIR |= Bit(SCK_PORT);
// DOUT input.
// The pullup draws 80µA in STDBY, when the sensor happens
// to end with DOUT=0. The attached logic analyser drew 30µA,
// when DOUT=1 so the STDBY current was toggling between 50µA and 100µA.
// 20µA goes into the LDO. Which we may want to remove.
# ifdef DOUT_PULLUP
# define PA2_PULLUP PORT_PULLUPEN_bm
PORTA.PIN2CTRL = PA2_PULLUP;
# else
# define PA2_PULLUP 0
# endif
}
ISR(PORTA_PORT_vect, ISR_NAKED)
{
__asm__ ("push r24" "\n\t"
"lds r24,%[stat]" "\n\t"
"sts %[stat], r24" "\n\t"
"pop r24" "\n\t"
"reti" "\n"
: : [stat] "n" (&VPORTA.INTFLAGS)
);
}
volatile uint8_t tick;
ISR(TCA0_CMP0_vect, ISR_NAKED)
{
__asm__ ("push r24" "\n\t"
"ldi r24, 0x10" "\n\t"
"sts %[flag], r24" "\n\t"
"sts tick, r24" "\n\t"
"pop r24" "\n\t"
"reti" "\n"
:
: [flag] "n" (&MCLK.INTFLAGS)
);
}
__attribute__ ((noinline, noclone))
static
void bate_wait()
{
uint16_t timeout = 3277; // 50ms
tick = 0;
while (BATE_PORT.IN & Bit(DOUT_PORT)) {
PORTA.PIN2CTRL = PA2_PULLUP | PORT_ISC_FALLING_gc;
sleep_cpu();
if (tick) {
tick = 0;
if (!--timeout) {
DEBUG_COUNTER(bate_timeout);
break;
}
}
}
PORTA.PIN2CTRL = PA2_PULLUP | PORT_ISC_INTDISABLE_gc;
}
static inline
uint8_t mclk_status()
{
return MCLK.CTRLA & TCA_SINGLE_ENABLE_bm;
}
union bate bate;
struct pressure pressure;
#ifdef BIT_BANG_BATE
// Spec:
// _____ _____ _____
// SCK ____/ \_____/ \_____/ \_____
// _____ _____ _____ __
// DIN -<_____>-----<_____>-----<_____>-----<__
// _____ ___________ ___________ __________
// DOUT _____X___________X___________X__________
//
// Impl:
// _______
// SCK _______/ \_
// _ _______________
// DIN _X_______________
//
// DOUT xxxxxSxxxxxxxxxxx
//
__attribute__ ((noinline, noclone))
static
uint8_t bate_bit(uint8_t r, uint8_t c, uint8_t ii)
{
#if 0
uint8_t clkl = 10;
uint8_t clkh = 30;
if (c & ii)
BATE_PORT.OUT |= Bit(DIN_PORT);
else
BATE_PORT.OUT &=~ Bit(DIN_PORT);
if (BATE_PORT.IN & Bit(DOUT_PORT))
r |= ii;
else
r &= ~ii;
while(clkl)
clkl--;
BATE_PORT.OUT |= Bit(SCK_PORT);
while(clkh)
clkh--;
BATE_PORT.OUT &=~ Bit(SCK_PORT);
#else
__asm__(
"and %[c], %[ii]" "\n\t"
"brne 1f" "\n\t"
"cbi %[PORT], %[DIN]" "\n\t"
"1:" "\n\t"
"breq 2f" "\n\t"
"sbi %[PORT], %[DIN]" "\n\t"
"2:" "\n\t"
"sbic %[PIN], %[DOUT]" "\n\t"
"or %[r], %[ii]" "\n\t"
"com %[ii]" "\n\t"
"sbis %[PIN], %[DOUT]" "\n\t"
"and %[r], %[ii]" "\n\t"
"nop" "\n\t"
"nop" "\n\t"
"nop" "\n\t"
"nop" "\n\t"
"nop" "\n\t"
"sbi %[PORT], %[SCK]" "\n\t"
"nop" "\n\t"
"nop" "\n\t"
"nop" "\n\t"
"nop" "\n\t"
"nop" "\n\t"
"nop" "\n\t"
"nop" "\n\t"
"nop" "\n\t"
"nop" "\n\t"
"nop" "\n\t"
"cbi %[PORT], %[SCK]" "\n"
:[r] "+r" (r),
[c] "+r" (c)
:[ii] "r" (ii),
[PORT] "n" (_SFR_IO_ADDR(BATE_PORT.OUT)),
[PIN] "n" (_SFR_IO_ADDR(BATE_PORT.IN)),
[DIN] "n" (DIN_PORT),
[DOUT] "n" (DOUT_PORT),
[SCK] "n" (SCK_PORT)
: "memory"
);
#endif
return r;
}
__attribute__ ((noinline, noclone))
static
uint16_t bate_frame(uint16_t d, uint8_t n)
{
n--;
uint8_t c0 = d;
uint8_t c1 = d >> 8;
uint8_t r0;
uint8_t r1;
// Force r0 and r1 to be cleared before the switch. Else, the
// compiler generates extra jumps to clear r0 for each target.
__asm__("clr %[r0]" "\n\t"
"clr %[r1]" "\n"
:[r0] "=r" (r0),
[r1] "=r" (r1)
);
switch (n) {
case 15: r1 = bate_bit(r1, c1, 1<<7);
case 14: r1 = bate_bit(r1, c1, 1<<6);
case 13: r1 = bate_bit(r1, c1, 1<<5);
case 12: r1 = bate_bit(r1, c1, 1<<4);
case 11: r1 = bate_bit(r1, c1, 1<<3);
case 10: r1 = bate_bit(r1, c1, 1<<2);
case 9: r1 = bate_bit(r1, c1, 1<<1);
case 8: r1 = bate_bit(r1, c1, 1<<0);
case 7: r0 = bate_bit(r0, c0, 1<<7);
case 6: r0 = bate_bit(r0, c0, 1<<6);
case 5: r0 = bate_bit(r0, c0, 1<<5);
case 4: r0 = bate_bit(r0, c0, 1<<4);
case 3: r0 = bate_bit(r0, c0, 1<<3);
case 2: r0 = bate_bit(r0, c0, 1<<2);
case 1: r0 = bate_bit(r0, c0, 1<<1);
case 0: r0 = bate_bit(r0, c0, 1<<0);
}
return (uint16_t)r1<<8 | r0;
}
static inline
void read_bate()
{
cli();
bate_frame(0xaaaa, 16);
bate_frame(0, 8);
bate_frame(0x3aa0, 16);
bate.W1 = bate_frame(0, 16);
bate_frame(0x3ac0, 16);
bate.W2 = bate_frame(0, 16);
bate_frame(0x3b20, 16);
bate.W3 = bate_frame(0, 16);
bate_frame(0x3b40, 16);
bate.W4 = bate_frame(0, 16);
bate_frame(0x0f40, 16);
sei();
bate_wait();
cli();
bate_frame(0,1);
bate.D1 = bate_frame(0, 16);
bate_frame(0x0f20, 16);
sei();
bate_wait();
cli();
bate_frame(0,1);
bate.D2 = bate_frame(0, 16);
sei();
}
#else // !BIT_BANG_BATE
static inline
void read_bate()
{
init_spi(config.spi_div);
spi_frame(0b0000000010101010);
spi_frame(0b1010101000000000);
spi_frame(0b0001110101010000);
bate.W1 = spi_frame(0);
spi_frame(0b0001110101100000);
bate.W2 = spi_frame(0);
spi_frame(0b0001110110010000);
bate.W3 = spi_frame(0);
spi_frame(0b0001110110100000);
bate.W4 = spi_frame(0);
spi_frame(0b0000111101000000);
bate_wait();
bate.D1 = spi_frame(0);
spi_frame(0b0000111100100000);
bate_wait();
bate.D2 = spi_frame(0);
spi_off();
}
#endif // !BIT_BANG_BATE
////////////////////////////////////////////////////////////////////////////////
//
// Configuration in USERROW
struct config config = {
.magic = USE_USERROW,
.version = USE_VERSION,
.triggers = TRIGGER_CLOCK | TRIGGER_IMMED,
.send = SEND_CONFIG | SEND_CLOCK | SEND_BATED | SEND_ADC_HEX,
.power = STOP_MCLK | POWER_STDBY | POWER_LED | POWER_RF | POWER_LINE,
.calib_test = 0,
.spi_div = SPI_PRESC_DIV64_gc,
.mclk_delay = 2,
.period = 9,
.confp = 30,
.cpu_clk = CLKCTRL_PDIV_2X_gc | 1,
.mclk_period = 0,
.baud_div = 0,
.uart_mode = UART_Tx | UART_Rx | UART_SFD,
.pit_period = RTC_CLKSEL_INT1K_gc,
.immediate = 5,
.pad = {},
.line_preable = "\xff\xff\xff\xff\xff\x55\n"
};
uint8_t immediate;
uint8_t test_calib;
static const struct config *userrow = (struct config *) & USERROW;
#ifdef DEBUG
struct debug debug_data, debug_data_copy;
void DEBUG_PRINT(uint8_t magic, uint8_t value)
{
cli();
memcpy(&debug_data_copy, &debug_data, sizeof(debug_data));
memset(&debug_data, 0, sizeof(debug_data));
sei();
debug_data_copy.magic = magic;
debug_data_copy.value = value;
send_hex('X', &debug_data_copy.value, sizeof(struct debug), 3);
}
#endif
////////////////////////////////////////////////////////////////////////////////
//
// main()
int main()
{
if (userrow->magic == USE_USERROW
&& userrow->version == USE_VERSION)
memcpy(&config, userrow, sizeof(struct config));
while (CLKCTRL.MCLKCTRLB != config.cpu_clk) {
CCP = CCP_IOREG_gc;
CLKCTRL.MCLKCTRLB = config.cpu_clk;
}
init_adc();
init_mclk(config.mclk_period);
init_led();
init_rfen();
init_uart(config.baud_div, config.uart_mode);
init_rtc(config.pit_period);
set_sleep_mode(SLEEP_MODE_IDLE);
sleep_enable();
sei();
uint8_t reset_source = RSTCTRL.RSTFR;
RSTCTRL.RSTFR = reset_source;
send_str("\nB Turbo Weather V0.9 ");
send_hex_byte(reset_source);
send_eol();
uint8_t n_adc = 0;
while (n_adc < N_ADC && adc_conf[n_adc].mode && adc_conf[n_adc].mode != 0xff)
n_adc++;
uint8_t trigger = TRIGGER_CONT | TRIGGER_ONCE;
uint8_t mclk_delay = 0;
uint8_t trigger_clock = 0;
uint8_t config_clock = 0;
uint8_t send_config = 0;
uint8_t uart_rx_delay = 0;
immediate = config.immediate;
test_calib = config.calib_test;
while (1) {
DEBUG_COUNTER(mainloops);
sleep_cpu();
set_sleep_mode(SLEEP_MODE_IDLE);
if (clock_tick) {
clock_tick = 0;
if (!trigger_clock--) {
trigger |= TRIGGER_CLOCK;
trigger_clock = config.period;
}
if (uart_rx_delay)
uart_rx_delay--;
}
if (config.send & SEND_ADC && adc_current <= N_ADC) {
if (adc_busy())
continue;
adc_current = 0xff;
if (config.send & SEND_ADC_HEX)
send_hex('A', (uint8_t *)adc_readings,
2*n_adc, 3);
if (config.send & SEND_ADC_VOLT) {
send_char('V');
for (uint8_t i=0; i<n_adc; i++)
send_calib_adc(i);
send_eol();
}
if (config.power & POWER_LINE) {
send_cks();
uart_cks = 0;
}
}
command();
if (uart_busy()) {
uart_tick();
continue;
}
wdt_reset();
if (uart_tick()) {
trigger |= TRIGGER_UART;
// We woke up for a blibb on Rx.
// Keep the power up for this second,
// to give the SFD a chance.
if (config.power & POWER_RX)
uart_rx_delay = 2;
}
if (uart_break_p())
trigger |= TRIGGER_BREAK;
if (immediate || test_calib)
trigger |= TRIGGER_IMMED;
if (!(trigger & config.triggers)) {
rfen(0);
led(0);
if (config.power & (POWER_DOWN|POWER_STDBY)) {
mclk(0);
if (config.power & POWER_DOWN && !uart_rx_delay) {
if (config.power & POWER_DOWN_CLI)
cli();
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
}
else
set_sleep_mode(SLEEP_MODE_STANDBY);
}
continue;
}
if (!rfen_status()) {
if (config.power & POWER_RF)
rfen(1);
if (config.power & POWER_LINE) {
send_str(config.line_preable);
uart_cks = 0;
}
}
if (config.power & POWER_LED)
led(1);
if (!mclk_status())
mclk_delay = config.mclk_delay;
mclk(1);
if (!tick)
continue;
tick = 0;
if (mclk_delay) {
mclk_delay--;
continue;
}
if (config.send & SEND_CONFIG && !config_clock--) {
send_config = 1;
config_clock = config.confp;
}
if (immediate)
immediate--;
read_bate();
if (config.power & STOP_MCLK)
mclk(0);
if (config.send & SEND_ADC)
start_adc();
if (send_config) {
send_hex('S', (uint8_t *)&SIGROW, sizeof(SIGROW_t), 0);
send_hex('F', (uint8_t *)&FUSE, sizeof(FUSE_t), 1);
send_hex('U', (uint8_t *)&USERROW, sizeof(USERROW_t), 1);
send_hex('C', (uint8_t *)&config, sizeof(struct config), 1);
send_hex('E', (uint8_t *)adc_conf, sizeof(adc_conf), 3);
}
if (config.send & SEND_CLOCK) {
send_str("T 0x");
cli();
uint32_t time = clock;
sei();
send_hex_long(time);
send_eol();
}
uint8_t send_test = 0;
const union bate *b = &bate;
if (test_calib) {
if (test_calib > N_TESTDATA)
test_calib = N_TESTDATA;
b = testdata + -- test_calib;
send_test = 1;
send_config = 1;
}
if (config.send & SEND_BATEW || send_config)
send_hex('W', b->b, 8, 3);
if (config.send & SEND_BATED || send_config)
send_hex('D', b->b+8, 4, 3);
if (config.send & SEND_CALIB || send_test) {
bate_calib(b, &pressure);
send_str("P ");
send_str(decimal_str(pressure.p, 1));
send_str(" mbar, ");
send_str(decimal_str(pressure.T-2732, 1));
send_str(" °C\n");
}
if (config.send & SEND_DEBUG) {
DEBUG_PRINT(0x77, trigger);
}
if (config.power & POWER_LINE) {
send_cks();
uart_cks = 0;
}
uart_tick();
trigger = TRIGGER_CONT;
send_config = 0;
}
}

128
src/bate.h Normal file
View file

@ -0,0 +1,128 @@
//
// bate.h
//
#include "uart.h"
////////////////////////////////////////////////////////////////////////////////
//
// Configuration
struct config {
uint8_t magic;
uint8_t version;
uint8_t triggers;
uint8_t send;
uint8_t power;
uint8_t calib_test;
uint8_t spi_div;
uint8_t mclk_delay;
uint8_t period;
uint8_t confp;
uint8_t cpu_clk;
uint8_t mclk_period;
uint16_t baud_div;
uint8_t uart_mode;
uint8_t pit_period;
uint8_t immediate;
uint8_t pad[7];
char line_preable[7];
uint8_t zero;
};
enum magic_flags {
USE_USERROW = 0xBA,
USE_VERSION = 0x09,
};
enum power_flags {
POWER_DOWN = 0x01,
POWER_DOWN_CLI = 0x02,
STOP_MCLK = 0x04,
POWER_LED = 0x08,
POWER_STDBY = 0x10,
POWER_RX = 0x20,
POWER_RF = 0x40,
POWER_LINE = 0x80,
};
enum send_flags {
SEND_CONFIG = 0x01,
SEND_BATED = 0x02,
SEND_BATEW = 0x04,
SEND_CLOCK = 0x08,
SEND_CALIB = 0x10,
SEND_ADC_HEX = 0x20,
SEND_ADC_VOLT = 0x40,
SEND_ADC = 0x60,
SEND_DEBUG = 0x80,
};
enum trigger_flags {
TRIGGER_ONCE = 0x01,
TRIGGER_CONT = 0x02,
TRIGGER_UART = 0x04,
TRIGGER_CLOCK = 0x08,
TRIGGER_BREAK = 0x10,
TRIGGER_IMMED = 0x20,
};
enum UART_flags { // USART0.CTRLB
UART_Tx = /* 0x40, */ USART_TXEN_bm,
UART_Rx = /* 0x80, */ USART_RXEN_bm,
UART_SFD = /* 0x10, */ USART_SFDEN_bm,
UART_ODME = /* 0x08, */ USART_ODME_bm,
UART_CLKx2 = /* 0x02, */ 1 << USART_RXMODE_0_bp,
UART_GAUTO = /* 0x04, */ 2 << USART_RXMODE_0_bp,
UART_LAUTO = /* 0x06, */ 3 << USART_RXMODE_0_bp,
};
extern struct config config;
extern uint8_t immediate;
extern uint8_t test_calib;
#ifndef NODEBUG
# define DEBUG
#endif
#ifdef DEBUG
#include <string.h>
struct debug {
uint8_t value;
uint8_t magic;
uint8_t tx_sleep;
uint8_t tx_irqs;
uint8_t rx_irqs;
union {
uint8_t adc_irqs;
uint8_t rx_char;
};
union {
uint8_t bate_timeout;
uint8_t rxhex;
};
union {
uint8_t spi_timeout;
uint8_t rxpar;
};
uint8_t mainloops;
uint8_t pit_irqs;
};
extern struct debug debug_data;
#define DEBUG_COUNTER(c) (debug_data.c ++)
#define DEBUG_POKE(c, v) (debug_data.c = v)
void DEBUG_PRINT(uint8_t magic, uint8_t value);
#define DEBUG_HEX(d, s) send_hex('X', (const uint8_t *)(d), s, 0)
#else // !DEBUG
#define DEBUG_COUNTER(c)
#define DEBUG_POKE(c, v)
static inline
void DEBUG_PRINT(uint8_t magic, uint8_t value) {}
#define DEBUG_HEX(d, s)
#endif // !DEBUG

1
src/bch4369 Submodule

@ -0,0 +1 @@
Subproject commit eebd4bb43039a2ecf63aa7c2fdbe1c4edf469393

76
src/calib.py Executable file
View file

@ -0,0 +1,76 @@
#!/usr/bin/python3
# encoding: UTF-8
import sys
def calibrate(Word, D):
print(f"""INPUT Words
W1 = 0x{Word[1]:04x}
W2 = 0x{Word[2]:04x}
W3 = 0x{Word[3]:04x}
W4 = 0x{Word[4]:04x}
D1 = 0x{D[1]:04x}
D2 = 0x{D[2]:04x}
""", file=sys.stderr)
C=[0]*7
C[1] = Word[1] >> 1
C[2] = ((Word[3] & 0x3f)<<6) | (Word[4] & 0x3f)
C[3] = Word[4]>>6
C[4] = Word[3]>>6
C[5] = ((Word[1] & 1)<<10) | (Word[2]>>6)
C[6] = Word[2] & 0x3f
print(f"""CAL Words
C1 = {C[1]}
C2 = {C[2]}
C3 = {C[3]}
C4 = {C[4]}
C5 = {C[5]}
C6 = {C[6]}
""", file=sys.stderr)
UT1 = 8*C[5]+20224
dT = D[2] - UT1
TEMPSENSE = C[6]+50
TEMP = 200 + dT*TEMPSENSE//1024
print(f"""Temperature
D2 = {D[2]}
UT1 = {UT1}
dT = {dT}
TSENSENSE = {TEMPSENSE}
TEMP = {TEMP*0.1:.1f} °C
""", file=sys.stderr)
TCO = C[4]-512
OFFT1 = C[2]*4
OFF = OFFT1 + (TCO*dT) // 4096
SENST1 = C[1] + 24576;
TCS = C[3]
SENS = SENST1 + (TCS*dT) // 1024
X = (SENS * (D[1]-7168)) // 16384 - OFF
P = X*10//32 + 2500
print(f"""Pressure
D1 = {D[1]} {D[1]-7168}
TCO = {TCO}
OFFT1 = {OFFT1}
OFF = {OFF}
TCS = {TCS}
SENST1= {SENST1}
SENS = {SENS} {SENS/2**14}
X = {X}
P = {P*0.1:.1f} mbar
""", file=sys.stderr)
return (TEMP,P)
for l in sys.stdin:
if l[0]!='P':
continue
Word=[None]
Word.extend([int(ll,0) for ll in l.split()[1:]])
calibrate(Word, Word[4:])

289
src/cmd.c Normal file
View file

@ -0,0 +1,289 @@
//
// cmd.c
//
#include "cmd.h"
#include "adc.h"
#include "flash.h"
#include "bch4369.h"
#include "base85.h"
#include "uart.h"
#include "pipe.h"
#ifdef HAVE_FPGA
#include "fpga.h"
#endif
uint8_t cmd_buffer[16];
uint8_t base85_str[6];
#if 0
void base85_send(const uint32_t *v)
{
base85_encode(*v, base85_str);
send_str((const char *)base85_str);
}
void base85_send_buffer(const uint8_t *buf)
{
base85_send((void*)(buf+0));
base85_send((void*)(buf+4));
base85_send((void*)(buf+8));
base85_send((void*)(buf+12));
}
#else
__attribute__((naked))
void base85_send_buffer(const uint8_t *buf)
{
// send_str26 does not gobble r18, r20, r21, r24.
__asm__(
""
"push r28" "\n\t"
"push r29" "\n\t"
"movw r28, r24" "\n\t"
"rcall _base85_send28" "\n\t"
"rcall _base85_send28" "\n\t"
"rcall _base85_send28" "\n\t"
"rcall _base85_send28" "\n\t"
"pop r29" "\n\t"
"pop r28" "\n\t"
"ret" "\n"
"_base85_send28:" "\n\t"
"ld r22, Y+" "\n\t"
"ld r23, Y+" "\n\t"
"ld r24, Y+" "\n\t"
"ld r25, Y+" "\n\t"
"ldi r20, lo8(base85_str)" "\n\t"
"ldi r21, hi8(base85_str)" "\n\t"
"rcall base85_encode" "\n\t"
"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<16; i+=4)
s = base85_decode(s, (uint32_t *)(cmd_buffer + i));
return s;
}
struct peak_poke {
uint8_t *addr;
uint8_t size;
uint8_t ccp;
uint8_t data[12];
};
static inline
uint8_t poke(struct peak_poke *p, uint8_t poke)
{
uint8_t s = p->size;
uint8_t *a = p->addr;
uint8_t n = s;
if (n > 12)
n = 12;
p->size = s - n;
p->addr = a + n;
if (!n)
return 0;
if (poke) {
uint8_t ccp = p->ccp;
if (ccp)
__asm__(
"out __CCP__, %[key] \n\t"
"sts %[ctrla], %[cmd] \n"
::
[ctrla] "n" (&NVMCTRL.CTRLA),
[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"
);
}
else
_memcopy(p->data, a, n);
return n;
}
const uint8_t *cmd_flags;
#if 0
static __attribute__((noinline))
uint8_t cmd_flag(uint8_t f)
{
if (*cmd_flags == f) {
cmd_flags++;
send_char(f);
return f;
}
return 0;
}
#else
static __attribute__((noinline, naked))
uint8_t cmd_flag(uint8_t f)
{
// _send_char22 does not gobble r24.
__asm__(
""
"lds r30, cmd_flags" "\n\t"
"lds r31, cmd_flags+1" "\n\t"
"ld r22, Z+" "\n\t"
"cp r22, r24" "\n\t"
"mov r24, r1" "\n\t"
"breq 1f" "\n\t"
"ret" "\n"
"1:" "\n\t"
"mov r24, r22" "\n\t"
"sts cmd_flags, r30" "\n\t"
"sts cmd_flags+1, r31" "\n\t"
"rjmp _send_char22" "\n"
);
}
#endif
uint8_t cmd_pending;
void parse_command(const uint8_t *s, uint8_t n)
{
// ^[A-Z][!-@]+( [!-u]{20}])?$
// cmd flags ␣base85
uint8_t r = 0;
uint8_t cmd = *s++;
if (cmd < 'A' || cmd > 'Z')
return;
send_char('#');
send_char(cmd);
uint8_t bflg = *s - '0';
uint8_t *bptr = cmd_buffer;
if (bflg <= 5) {
bptr = flash_buffer + 16*bflg;
bflg = 1<<bflg;
send_char(*s++);
}
else
bflg = 0;
cmd_flags = s;
while (*s > ' ' && *s < 'A')
s++;
uint8_t have_b = 0;
if (*s==' ') {
cmd_pending = 0;
s = base85_fill_buffer(s+1);
r = base85_error;
if (r)
goto error;
have_b = 1;
cmd_pending = cmd;
}
else if (cmd_flag('='))
have_b = cmd_pending == cmd;
if (*s != '\n')
goto error;
switch(cmd) {
case 'A':
r = adc_current;
if (cmd_flag('!'))
start_adc();
if (cmd_flag('<')) {
bptr = (void*)adc_readings;
goto send_buffer;
}
break;
case 'B':
if (cmd_flag('@'))
pipe.valid = 0;
r = pipe.valid;
if (have_b) {
if (cmd_flag('!') || ~r & bflg) {
memcpy(bptr, cmd_buffer, 16);
r = pipe.valid |= bflg;
}
else
goto error;
}
if (cmd_flag('%')) {
if (cmd_flag('@'))
bch4369_init(config.bch_salt);
bch4369_str(bptr, 16);
if (cmd_flag('!')) {
bch4369_fini();
memcpy(flash_buffer+64, bch_parity, 16);
r = pipe.valid |= 0x10;
}
}
if (cmd_flag('<')) {
if (cmd_flag('!')) goto send_buffer;
if (~r & bflg)
goto error;
pipe.valid &=~ bflg;
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 = spi_busy_p();
if (cmd_flag('<'))
goto send_buffer;
break;
#ifdef HAVE_FPGA
case 'O':
r = !!(PEN_VPORT.IN & (1<<PEN_PIN));
if (bflg == 1)
PEN_VPORT.OUT &=~ (1<<PEN_PIN);
else if (bflg == 2)
PEN_VPORT.OUT |= (1<<PEN_PIN);
break;
#endif
#ifndef THHOR
case 'P':
if (have_b)
pipe_config((void*)cmd_buffer);
else
r = pipe_poll();
break;
goto send_buffer;
break;
#ifdef HAVE_FPGA
case 'C':
if (cmd_flag('@')) {
fpga_reset();
break;
}
if (!have_b)
goto error;
fpga_cmd((void*)cmd_buffer);
goto send_buffer;
#endif
#endif // THHOR
case 'M':
if (!have_b)
goto error;
r = poke((void*)cmd_buffer, cmd_flag('!'));
send_buffer:
send_char(' ');
base85_send_buffer(bptr);
break;
default:
error:
send_char('?');
}
send_hex_byte_eol(r);
}

6
src/cmd.h Normal file
View file

@ -0,0 +1,6 @@
#include <stdint.h>
extern uint8_t base85_error;
void parse_command(const uint8_t *s, uint8_t n);
extern uint8_t cmd_buffer[16];

448
src/cmd.py Executable file
View file

@ -0,0 +1,448 @@
#! /usr/bin/python3
import sys, time, struct
import cmdsocket
class turbocmd(cmdsocket.cmder):
pace_delay = 0.1
pace_next = 0
def pace(self):
t = time.time()
if t < self.pace_next:
time.sleep(self.pace_next - t)
self.pace_next = t+self.pace_delay
def set_pace(seld, d):
self.pace_delay = d
def clock(self, t=None):
if t is None:
t = time.time()
tt = struct.unpack("4B", struct.pack("<L", int(t)))
self.write(b"K"+b"".join([b"%02x" % ttt for ttt in tt]))
SEND = {
"CONFIG": 0x01,
"BATED": 0x02,
"BATEW": 0x04,
"CLOCK": 0x08,
"CALIB": 0x10,
"ADC_HEX": 0x20,
"ADC_VOLT": 0x40,
"ADC": 0x60,
"DEBUG": 0x80,
}
POWER = {
"DOWN": 0x01,
"DOWN_CLI": 0x02,
"STOP_MCLK": 0x04,
"LED": 0x08,
"STDBY": 0x10,
"RX": 0x20,
"RF": 0x40,
"LINE": 0x80,
}
TRIGGER = {
"ONCE": 0x01,
"CONT": 0x02,
"UART": 0x04,
"CLOCK": 0x08,
"BREAK": 0x10,
"IMMED": 0x20,
}
UART = {
"TX": 0x40,
"RX": 0x80,
"SFD": 0x10,
"ODME": 0x08,
"CLKX2": 0x02,
"GAUTO": 0x04,
"LAUTO": 0x06,
}
REGS = {
"magic": (0x00, 1,),
"version": (0x01, 1,),
"triggers": (0x02, 1, TRIGGER),
"send": (0x03, 1, SEND),
"power": (0x04, 1, POWER),
"calib_test": (0x05, 1,),
"spi_div": (0x06, 1,),
"mclk_delay": (0x07, 1,),
"period": (0x08, 1,),
"confp": (0x09, 1,),
"cpu_clk": (0x0a, 1,),
"mclk_period": (0x0b, 1,),
"baud_div": (0x0c, 2,),
"uart_mode": (0x0e, 1, UART),
"pit_period": (0x0f, 1,),
"immediate": (0x10, 1,),
"pad": (0x11, 7,),
"line_preable": (0x18, 7,),
"zero": (0x1f, 1,),
}
def fconf(self, idx):
for k in self.REGS:
if self.REGS[k][0] == idx:
return self.REGS[k]
num_base = 0
def number(self, a, idx=None, kw=True):
"""return an index, value or bit
None, idx a config index symbol
-1, val a number
0, val a bit value symbol
bit values are searched for the provided index, or the next
if no index is provided, any bit value may be returned
"""
try:
return (-1, int(a, self.num_base))
except ValueError:
if not kw:
raise
if a.lower() in self.REGS:
return (None, self.REGS[a.lower()][0])
a = a.upper()
try:
return (idx, self.fconf(idx)[2][a])
except:
pass
if idx is not None:
idx += 1
return (idx, self.fconf(idx)[2][a])
for v in self.REGS.values():
try:
return (v[0], v[2][a])
except:
pass
# raise
return (None, self.REGS[a][0])
def config(self, cmd, magic, reg, *a):
keywords = cmd != b'E'
ii, idx = self.number(reg, kw=keywords)
b = [magic, idx]
partial = False
if ii is not None and ii >= 0:
b[1:1] = [ii]
partial = True
idx = ii
elif not a:
if cmd==b'C':
aa = self.find_symbol("config")
if cmd==b'U':
aa = self.find_symbol("USERROW")
if cmd==b'E':
aa = self.find_symbol("EEPROM")
self.write(b'M%04x' % (aa+idx))
return
for aa in a:
try:
ii, bb = self.number(aa, idx, kw=keywords)
except Exception as e:
raise ValueError(f"{aa}: invalid config item\n{e}")
if ii==idx:
# got a bit for this config byte
if not partial:
b.append(bb)
partial = True
else:
b[-1] |= bb
continue
if ii is None:
# got a config index
# those need to be consecutive
if bb == idx:
continue
if bb != idx+1:
raise IndexError(f"{aa} nonconsecutive configs")
if not partial:
b.append(0)
partial = False
idx = bb
continue
if ii == idx+1:
# got a bit for the next config byte
if not partial:
b.append(0)
b.append(bb)
partial = True
idx = ii
continue
if ii < 0:
# got a number. Either a byte or a word
b.append(bb & 0xff)
s = 1
if keywords:
try:
s = self.fconf(idx)[1]
except:
pass
if s==2:
b.append(bb >> 8)
if s==4:
b.append((bb >> 8) & 0xff)
b.append((bb >> 16) & 0xff)
b.append((bb >> 24) & 0xff)
idx += s
partial = False
self.write(cmd + b"".join([b"%02x" % (bb&0xff) for bb in b]))
def mem(self, a, v=None):
aa = a.split("+", 1)
a = self.find_symbol(aa[0])
if len(aa)==2:
a += int(aa[1], self.num_base)
if v is None:
self.write(b'M%04x' % a)
return
v = int(v, self.num_base)
self.write(b'W%04x %02x' % (a & 0xffff, v & 0xff))
def value(self, cmd, v):
v = int(v, self.num_base)
self.write(cmd + b'%02x' % (v & 0xff))
# ATtiny424 IO
symbols = {
"VPORTA": 0x0000,
"VPORTB": 0x0004,
"VPORTC": 0x0008,
"RSTCTRL": 0x0040,
"SLPCTRL": 0x0050,
"CLKCTRL": 0x0060,
"BOD": 0x0080,
"VREF": 0x00A0,
"WDT": 0x0100,
"CPUINT": 0x0110,
"CRCSCAN": 0x0120,
"RTC": 0x0140,
"EVSYS": 0x0180,
"CCL": 0x01C0,
"PORTA": 0x0400,
"PORTB": 0x0420,
"PORTC": 0x0440,
"PORTMUX": 0x05E0,
"ADC0": 0x0600,
"AC0": 0x0680,
"USART0": 0x0800,
"USART1": 0x0820,
"TWI0": 0x08A0,
"SPI0": 0x08C0,
"TCA0": 0x0A00,
"TCB0": 0x0A80,
"TCB1": 0x0A90,
"SYSCFG": 0x0F00,
"NVMCTRL": 0x1000,
"SIGROW": 0x1100,
"FUSE": 0x1280,
"LOCK_BIT": 0x128A,
"USERROW": 0x1300,
"EEPROM": 0x1400,
}
def find_symbol(self, s):
try:
return int(s, self.num_base)
except:
pass
if s not in self.symbols:
if not self.elf_read:
self.read_elf()
return self.symbols[s]
elf = "bate.elf"
elf_read = False
def read_elf(self):
self.elf_read = True
import subprocess
r = subprocess.run(["/usr/bin/nm", self.elf], text=True, stdout=subprocess.PIPE)
for l in r.stdout.split("\n"):
ll = l.split()
if len(ll)==3 and ll[1] in "BD":
self.symbols[ll[2]] = int(ll[0], 16) & 0xffff
ADC_MODE = {"DIFF": 0xd1, "NORM": 0x51}
ADC_REF = {"VDD": 0x50, "1V": 0x54, "2V": 0x55, "2.5V": 0x56, "4V": 0x57}
ADC_INP = {"GND": 0x30, "VDD": 0x31, "TEMP": 0x32, "RFP": 4, "NTC": 6, "BAT": 7}
ADC_CONF = {
"mode": (0, 1, ADC_MODE),
"ref": (1, 1, ADC_REF),
"inp": (2, 1, ADC_INP),
"inn": (3, 1, ADC_INP),
"offset": (4, 1, {}),
"shifts": (5, 1, {}),
"calib": (6, 2, {}),
}
def adc_conf(self, reg, *a):
"1 [mode] NORM [ref] 1V [inp] NTC [inn] RFP"
n = int(reg, 0)
r = None
rr = self.ADC_CONF["mode"]
b = []
for aa in a:
print(aa,n,r,rr,b)
if aa in self.ADC_CONF:
r = self.ADC_CONF[aa]
if not b:
rr = r
elif r[0] != rr[0]+len(b):
raise ValueError(f"{aa} adc registers must be consecutive")
continue
if r is None:
for rrr in self.ADC_CONF.values():
if rrr[0] == rr[0]+len(b):
r = rrr
break
if r is None:
raise ValueError(f"{aa} adc register undefined")
try:
i = int(aa, self.num_base)
except:
i = r[2][aa]
b.append(i & 0xff)
if r[1] == 2:
b.append(i >> 8)
r = None
if not b:
a = self.symbols["EEPROM"] + 8*n + rr[0]
self.write(b'M%04x' % (a & 0xffff))
return
b[:0] = [0x9d, 8*n + rr[0]]
self.write(b'E' + b"".join([b"%02x" % (bb & 0xff) for bb in b]))
class ttycmd(turbocmd):
def open(self, path=None):
if path == '-':
path = None
self.path = path
def close(self):
pass
def write(self, m):
self.pace()
m = self.format(m)
if self.path:
with open(self.path, "wb") as f:
f.write(m)
else:
sys.stdout.write(m.decode())
def usage():
print(sys.argv[0], """{options} {args}
format and send commands to the turbo weather station
-h print this help
-q be quiet
-v be verbose, echo commands to stderr
-s «path» open tty or socket
-x args are hexadecimal
-b «base» args are in base «base»
-p prefix messages with newline
-d «delay» (float) seconds to wait between messages
-R reboot the µC
-K set clock
-M «addr» read a byte
-T «n» do «n» immediate triggers
-D «n» do «n» testdata triggers
-W «» write a byte
-C «» write config
-U «» write userrow config
-E «» write to EEPROM
-A «» write/read ADC config in EEPROM
a single command option requiring args «» must be last.
config write commands are limitted to eight bytes, including address and magic
-C «reg» [«value»|{«bits»}]
-A «idx» «reg» «value»
config registers may be numbers or symbols
config values may be symbolic «bits» or numbers
adc registers are lowercare symbols
adc values may be numbers or symbols
values must match the consecutive sequence of registers
redundant matching register names may be interleaved
a config bit symbol may refer to the following register
a config bit symbol may implicitly select the inital register
""", file=sys.stderr)
def main(argv):
import getopt
options, args = getopt.getopt(argv, "hxb:pvqs:d:RKC:U:E:M:W:T:D:A:")
path = None
for o,v in options:
if o=="-s":
path = v
if o=="-h":
usage()
return
if path == '-':
path = None
if path is None or path.startswith('/dev/tty'):
cmd = ttycmd(path)
else:
cmd = turbocmd(path)
last_cmd = None
last_args = []
for o,v in options:
if last_cmd:
raise ValueError(f"{o}: command option after long command")
if o=="-x":
cmd.num_base = 16
if o=="-b":
cmd.num_base = int(v)
if o=="-p":
cmd.bol = b'\n'
if o=="-q":
cmd.verbose = False
if o=="-v":
cmd.verbose = True
if o=="-d":
cmd.set_pace(float(v))
if o=="-K":
cmd.clock()
if o=="-C":
last_cmd = cmd.config
last_args = [b'C', 0xba]
if o=="-U":
last_cmd = cmd.config
last_args = [b'U', 0x9d]
if o=="-E":
last_cmd = cmd.config
last_args = [b'E', 0x9d]
if o=="-M":
cmd.mem(v, None)
if o=="-W":
last_cmd = cmd.mem
if o=="-R":
cmd.value(b'R', '0xd8')
if o=="-D":
cmd.value(b'D', v)
if o=="-T":
cmd.value(b'T', v)
if o=="-A":
last_cmd = cmd.adc_conf
if v and last_cmd:
last_args.append(v)
last_args.extend(args)
if last_cmd:
last_cmd(*last_args)
if __name__=="__main__":
main(sys.argv[1:])

158
src/cmdsocket.py Executable file
View file

@ -0,0 +1,158 @@
#! /usr/bin/python3
import sys, os, socket
class cmd_socket:
def __init__(self, path=".cmd.socket", **a):
self.s = None
self.c = []
self.path = None
if path:
self.open(path, **a)
def close(self):
for c in self.c:
c.close()
self.c = []
self.s.close()
self.s = None
if self.path:
os.unlink(self.path)
class cmd_receiver(cmd_socket):
def open(self, path, blocking=False, force=False):
if force:
try:
os.unlink(path)
except FileNotFoundError:
pass
self.blocking=blocking
if self.s:
raise ValueError("socket is not closed")
self.path = path
self.s = socket.socket(socket.AF_UNIX)
self.s.bind(path)
self.s.listen()
self.s.setblocking(blocking)
self.c = []
self.m = b''
def poll(self):
if not self.s:
return
try:
c = self.s.accept()[0]
self.c.append(c)
c.setblocking(self.blocking)
self.s.setblocking(False)
except BlockingIOError:
pass
cc = False
for i, c in enumerate(self.c):
try:
m = c.recv(4096)
if not m:
c.close()
self.c[i] = None
cc = True
else:
self.exec(m)
except BlockingIOError:
pass
if cc:
self.c = [c for c in self.c if c]
if not self.c:
self.s.setblocking(self.blocking)
def exec(self, m):
if self.m:
m = self.m + m
mm = m.split(b'\n')
for m in mm[:-1]:
self.msg(m)
self.tail(mm[-1])
def tail(self, m):
self.m = m
if m:
print(f"socket {self.path}: got tail {m}", file=sys.stderr)
def msg(self, m):
print(f"socket {self.path}: got message {m}", file=sys.stderr)
class cmder(cmd_socket):
def open(self, path):
if self.s:
raise ValueError("socket is not closed")
self.s = socket.socket(socket.AF_UNIX)
self.s.connect(path)
self.path = path
eol = b'\n'
bol = b''
verbose = True
def format(self, m):
if isinstance(m, str):
m = m.encode()
if self.eol and m[-1:] != self.eol:
m += self.eol
if self.bol and m[:1] != self.bol:
m = self.bol + m
if self.verbose:
print(f"{self.path}.send({m})", file=sys.stderr)
return m
def write(self, m):
self.pace()
m = self.format(m)
if self.s:
self.s.send(m)
def pace(self):
pass
def main():
import getopt, time
options, messages = getopt.gnu_getopt(sys.argv[1:], "s:d:rfnN")
path = ()
delay = 0.1
receiver = False
force = False
eol = b'\n'
bol = None
for o,v in options:
if o=="-s":
path = (v,)
if o=="-d":
delay = float(v)
if o=="-r":
receiver = True
if o=="-f":
force = True
if o=="-n":
eol = None
if o=="-N":
bol = b'\n'
if receiver:
s = cmd_receiver(*path, blocking=True, force=force)
try:
while True:
s.poll()
except KeyboardInterrupt:
s.close()
return
s = cmder(*path)
s.eol = eol
s.bol = bol
for m in messages:
if delay:
time.sleep(delay)
s.write(m)
if __name__=="__main__":
main()

67
src/config.c Normal file
View file

@ -0,0 +1,67 @@
#include "config.h"
#include "flash.h"
#include "adc.h"
////////////////////////////////////////////////////////////////////////////////
//
// Configuration in USERROW
__attribute__((section(".userrow")))
const struct config config = {
.magic = MAGIC,
.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, // Buffer 1 Write
.read_array = 0x03 | FM_READ, // Continuous Array Read (Low-Frequency)
.read_buffer = {
[0] = 0xd1 | FM_READ, // Buffer 1 Read (Low-Frequency)
[1] = 0xd3 | FM_READ, // Buffer 2 Read (Low-Frequency)
},
.page_start = 0x0800,
.page_end = 0x1000,
#ifdef HAVE_nFETs
.pwm_min = 0x0000,
.pwm_max = 0xffff,
#endif
};
////////////////////////////////////////////////////////////////////////////////
//
// Configuration in EEPROM
struct_ioconf(port_config) = {
conf_prefix(PORTA),
conf_io(RxD_PINCTRL, PORT_PULLUPEN_bm),
#ifdef HAVE_nFETs
conf_io(ADC_D1_PINCTRL, PORT_ISC_INPUT_DISABLE_gc),
conf_io(ADC_D2_PINCTRL, PORT_ISC_INPUT_DISABLE_gc),
conf_io(ADC_G_PINCTRL, PORT_ISC_INPUT_DISABLE_gc),
#endif
#ifdef HAVE_FPGA
conf_io(nSTATUS_PINCTRL, PORT_PULLUPEN_bm),
conf_io(CRCERR_PINCTRL, PORT_PULLUPEN_bm),
#endif
conf_io(PORTA.OUT, Bit(SSEL_PIN)),
conf_io(PORTA.DIR, Bit(SSEL_PIN) | Bit(MOSI_PIN) | Bit(SCK_PIN)
#ifdef HAVE_nFETs
| Bit(DRAIN_PIN)
#endif
#ifdef HAVE_FPGA
| Bit(nCONFIG_PIN) | Bit(TxE_PIN)
#endif
),
conf_io(PORTB.OUT, Bit(TxD_PIN)),
conf_io(PORTB.DIR, Bit(TxD_PIN)
#ifdef HAVE_nFETs
| Bit(PWM_PIN)
#endif
#ifdef HAVE_FPGA
| Bit(TxE_PIN) | Bit(PEN_PIN)
#endif
),
};

198
src/config.h Normal file
View file

@ -0,0 +1,198 @@
#ifndef _CONFIG_H
#define _CONFIG_H
#include <avr/io.h>
#include <stdint.h>
// USERROW
struct config {
uint8_t magic;
uint8_t version;
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;
uint16_t read_buffer[2];
uint16_t page_start;
uint16_t page_end;
#ifdef HAVE_nFETs
uint16_t pwm_min;
uint16_t pwm_max;
#endif
};
enum magic_flags {
#ifdef HAVE_nFETs
MAGIC = 0xD0,
VERSION = 0x00,
#endif
#ifdef HAVE_FPGA
MAGIC = 0xC5,
VERSION = 0x01,
#endif
};
extern const struct config config;
// EEPROM
struct io_config {
uint8_t addr;
uint8_t val;
};
#define struct_ioconf(_n) struct io_config _n[] \
__attribute__((section(".eeprom9"), aligned(2)))
#define conf_prefix(_io) { 0xff, (uint16_t)&(_io) >> 8 }
#define conf_io(_io, _v) { (uint16_t)&(_io) & 0xff, (_v) }
#define conf_ioo(_io, _o, _v) { (uint16_t)&(_io) + _o & 0xff, (_v) }
#define conf_iow(_io, _v) conf_io(_io, (_v) & 0xff), conf_ioo(_io, 1, (uint16_t)(_v) >> 8)
extern struct io_config ee9_start[], ee9_end[];
#define Bit(x) (1<<(x))
#define SPI_VPORT VPORTA
#define SPI_PORT PORTA
#define MOSI_PIN 1
#define MISO_PIN 2
#define SCK_PIN 3
#define UART_VPORT VPORTB
#define UART_PORT PORTB
#define TxE_PIN 1
#define TxD_PIN 2
#define RxD_PIN 3
#define RxD_PINCTRL UART_PORT.PIN3CTRL
#ifdef HAVE_FPGA
#define SSEL_VPORT VPORTA
#define SSEL_PORT PORTA
#define SSEL_PIN 7
#define nCONFIG_VPORT VPORTA
#define nCONFIG_PORT PORTA
#define nCONFIG_PIN 6
#define nSTATUS_VPORT VPORTA
#define nSTATUS_PORT PORTA
#define nSTATUS_PIN 4
#define nSTATUS_PINCTRL nSTATUS_PORT.PIN4CTRL
#define CRCERR_VPORT VPORTA
#define CRCERR_PORT PORTA
#define CRCERR_PIN 5
#define CRCERR_PINCTRL CRCERR_PORT.PIN5CTRL
#define PEN_VPORT VPORTB
#define PEN_PORT PORTB
#define PEN_PIN 1
#else
#define SSEL_VPORT VPORTA
#define SSEL_PORT PORTA
#define SSEL_PIN 4
#endif
#ifdef HAVE_nFETs
#define DRAIN_VPORT VPORTA
#define DRAIN_PORT PORTA
#define DRAIN_PIN 5
#define PWM_VPORT VPORTB
#define PWM_PORT PORTB
#define PWM_PIN 0
#endif
#if 0
static inline
void apply_config()
{
uint8_t n = ee_end - ee_start;
struct io_config = ee_start;
uint8_t prefix = 0;
while (n--) {
const struct io_config *io = c++;
if (io->addr == 0xff)
prefix = io->val;
else {
uint8_t *p = (uint8_t*)((uint16_t)prefix << 8 | io->addr);
*p = io->val;
}
}
}
#else
static inline
void apply_config()
{
__asm__ volatile(
"ldi r30, lo8(ee9_start)" "\n\t"
"ldi r31, hi8(ee9_start)" "\n\t"
"ldi r24, lo8(ee9_size)" "\n\t"
"clr r25" "\n"
"1:" "\n\t"
"mov r27, r25" "\n\t"
"subi r24, 2" "\n\t"
"brcs 3f" "\n"
"2:" "\n\t"
"ld r26, Z+" "\n\t"
"ld r25, Z+" "\n\t"
"cpi r26, 0xff" "\n\t"
"breq 1b" "\n\t"
"st X, r25" "\n\t"
"subi r24, 2" "\n\t"
"brcc 2b" "\n"
"3:" "\n\t"
::: "r24", "r25", "r26", "r27", "r30", "r31");
}
#endif
#define section_status(_n) __attribute__((section(".bss."#_n)))
extern struct magic {
uint8_t magic;
uint8_t reset_source;
} magic;
#if 0
#define _memcopy memcpy
#define _memcopyyz memcpy
#else
// avoid the avr-libc memcpy.
// ¡ n must not be zero !
static inline
void _memcopy(uint8_t *d, uint8_t *s, uint8_t n)
{
__asm__ volatile ("\n"
"1:" "\n\t"
"ld r0, Z+" "\n\t"
"st X+, r0" "\n\t"
"dec %[N]" "\n\t"
"brne 1b" "\n"
: [D] "+x" (d),
[S] "+z" (s),
[N] "+r" (n)
:: "r0", "memory");
}
static inline
void _memcopyyz(uint8_t *d, uint8_t *s, uint8_t n)
{
__asm__ volatile ("\n"
"1:" "\n\t"
"ld r0, Z+" "\n\t"
"st Y+, r0" "\n\t"
"dec %[N]" "\n\t"
"brne 1b" "\n"
: [D] "+y" (d),
[S] "+z" (s),
[N] "+r" (n)
:: "r0", "memory");
}
#endif
#endif // _CONFIG_H

75
src/dose.c Normal file
View file

@ -0,0 +1,75 @@
//
// dose.c
//
// !!! int = int8_t
#include <stdint.h>
#include <string.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
#include <avr/wdt.h>
#include "config.h"
#include "dose.h"
#include "pipe.h"
#include "uart.h"
#include "rtc.h"
#include "cmd.h"
#include "adc.h"
#include "pwm.h"
////////////////////////////////////////////////////////////////////////////////
//
// main()
section_status(main) struct magic magic;
struct pipe_config cron_job[1] =
{
[0] = {
.pipe = {
.source = pipe_adc,
.dest = pipe_cmd,
.status = PS_OUT | PS_BCH,
.valid = 0,
.adc = ADC_64 | ADC_PWM | ADC_RTC,
},
},
};
int main()
{
while (CLKCTRL.MCLKCTRLB != config.cpu_clk) {
CCP = CCP_IOREG_gc;
CLKCTRL.MCLKCTRLB = config.cpu_clk;
}
sleep_enable();
magic.magic = config.magic;
while (magic.magic != MAGIC)
sleep_cpu();
apply_config();
magic.reset_source = RSTCTRL.RSTFR;
RSTCTRL.RSTFR = magic.reset_source;
send_str("\nV Turbo Dose V0.0");
send_hex_byte_eol(magic.reset_source);
// cron_job[0].page = flash_find_free();
// cron_job[0].npages = config.page_end - cron_job[0].page;
// if (config.cron & 1)
// pipe_config(cron_job);
while (1) {
sei();
sleep_cpu();
command();
// pipe_poll();
// if (config.cron & 2 && rtc_cnt_tick())
// pipe_config(cron_job);
}
}

11
src/dose.h Normal file
View file

@ -0,0 +1,11 @@
//
// dose.h
//
////////////////////////////////////////////////////////////////////////////////
//
// Configuration
#include <stdint.h>
#include <avr/io.h>

383
src/dose.py Executable file
View file

@ -0,0 +1,383 @@
#! /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
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=", "galois"])
tty = None
baud = 115200
out = None
debug = None
map_fn = "thhor.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 o == "--galois":
bch.load_galois()
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, s=s, d=d, ccp=ccp)
def peek(self, a=None, s=None, d=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 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=")
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("<HBB", a,s,ccp) + d + b'\0\0\0\0\0\0\0\0\0\0\0\0'
cc, nn, dd = self.cmd(c, d[:16])
if dd and nn:
aa,ss,cc = struct.unpack("<HBB", dd[:4])
print(f"PEEK: {aa-nn:04x}[{nn}+{ss}]: {b2hex(dd[4:4+nn])}", file=sys.stderr)
if name is not None and nn in FF:
d = struct.unpack(f"<{FF[nn]}", dd[4:4+nn])
print(f"{name}: {d[0]:#x}", file=sys.stderr)
return name, d
return cc, nn, dd
def more(self, flags=b''):
return self.cmd(self.last_cmd + b'=' + flags)
def read_mem(self, a, s=None):
cc, nn, dd = self.peek(a=a, s=s)
r = dd[4:4+nn]
while dd[2]:
cc, nn, dd = self.more()
r += dd[4:4+nn]
return r
_adc_conf = None
_sigrow = None
ADC_MUX = {
0: "GND",
6: "Gate",
7: "Drain1",
10: "Drain2",
0x30: "GND",
0x31: ("Vdd", 10, 0, 0),
0x32: "T",
0x33: "DACREF",
}
ADC_REF = {
0: (3.3, "VDD"),
4: (1.024,),
5: (2.048,),
6: (2.5,),
7: (4.096,),
}
def adc(self, start=False, read_conf=False):
if not self._adc_conf or read_conf:
self._adc_conf = self.read_mem("adc_conf")
self._sigrow = self.read_mem(0x1100, 0x24)
g = self._sigrow[0x20]
o = self._sigrow[0x21]
if o & 0x80:
o -= 0x100
o <<= 6
g /= 1 << 14
self.ADC_MUX[0x32] = ("T°C", g, o, 273)
c = "A!<" if start else "A<"
cc, nn, dd = self.cmd(c)
dd = struct.unpack("<8H", dd)
r = []
for i in range(nn):
pos = self._adc_conf[4*i+2] & 0x3f
ref = self._adc_conf[4*i+1] & 0x07
if ref in self.ADC_REF:
ref = self.ADC_REF[ref]
else:
ref = (1,)
a = dd[i]
if pos != 0x32:
a *= ref[0]/0x10000
if pos in self.ADC_MUX:
pos = self.ADC_MUX[pos]
else:
pos = f"Ain{pos}"
if isinstance(pos, tuple):
a = (a - pos[2])*pos[1] - pos[3]
pos = pos[0]
r.append((a, pos, ref))
print(f"{i} {pos:6s} {a:.4f} {ref!r}", file=sys.stderr)
return r
def nfet_scan(self, t=1, dcs=(0x1000,)):
r = []
for dc in range(*dcs):
self.poke("TCA0_SINGLE_CMP0", dc)
time.sleep(t)
self.cmd("A!")
time.sleep(1)
r.append((dc, self.adc()))
return r
def flash(self, op=None, abort=False, buf=False, **aa):
if op is None:
c = "F"
if abort:
c = "F!"
if buf:
c += "<"
return self.cmd(c)
r = self.cmd("F", flash_cmd.cmd_buffer(op, **aa))
if r[1]:
raise flash_cmd.Flash_Error(op)
return r
def write2fbuffer(self, d, op="WriteBuffer2"):
for i in range(0, 512, 16):
b = i>>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}<!")[2]
d += self.cmd(f"B4<!")[2]
self._last_page = d
if min(d) == 255:
print(f"FLASH: page is erased")
elif bch.check_page(d):
if not bch.galois:
raise bch.BCHError("galois module not loaded for Parity Error correction")
d = bch.fix_page(d)
return d[:512]
def read_flash(self, page):
print(f"FLASH: reading from {page=}")
self.flash("Transfer2", page=page)
self.flash_status(True)
return self.readfbuffer()
def read_flash2file(self, fn, page, n):
with open(fn, "wb") as f:
while n > 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()
def power(self, on=None):
if on==True:
c = 'O1'
elif on==False:
c = 'O0'
elif on is None:
c = 'O'
else:
raise ValueError(f".power expects True or False, got {on!r}")
r = self.cmd(c)
print(f"Power was {("off", "ON")[r[1]]}", file=sys.stderr)
return r
if tty:
tty = dose_cmd(tty, baud)
tty._export(globals())
tty._verbose = False
uart.set_prompt("GRETEL")
def b2hex(b, sep=" "):
return sep.join([f"{x:02x}" for x in b])

30
src/eeprom.ld Normal file
View file

@ -0,0 +1,30 @@
/*
// place section .eemap into the eeprom
// The symbols resolve to the direct map in data space.
*/
MEMORY
{
eemap : ORIGIN = 0x1400, LENGTH = 0x100
eedef : ORIGIN = 0x810000, LENGTH = 0x100
uumap : ORIGIN = 0x1300, LENGTH = 0x20
uudef : ORIGIN = 0x850000, LENGTH = 0x20
}
SECTIONS
{
.eemap 0x1400:
{
ee1_start = .;
*(.eeprom1)
ee1_end = .;
*(.eeprom)
ee9_start = .;
*(.eeprom9)
ee9_end = .;
ee9_size = ee9_end - ee9_start;
} >eemap AT >eedef
.uumap 0x1300:
{
*(.userrow)
} >uumap AT >uudef
}
INSERT BEFORE .eeprom

364
src/flash.c Normal file
View file

@ -0,0 +1,364 @@
/******************************************************
- opcode (hex)
- address
- pb page and byte address
- xb byte address
- px page address
- kx block address
- sx sector address
- xxx don't care
- padding
- 0 1 byte
- 00 2 bytes
- 0000 4 bytes
- data
- data bytes
- | no data bytes
read commands
- Main Memory Page Read D2 pb 0000
- Continuous Array Read (Low-Power Mode) 01 pb
- Continuous Array Read (Low-Frequency) 03 pb
- Continuous Array Read (High-Frequency) 0B pb 0
- Continuous Array Read (High-Frequency) 1B pb 00
- Continuous Array Read (Legacy Command) E8 pb 0000
- Buffer 1 Read (Low-Frequency) D1 xb
- Buffer 2 Read (Low-Frequency) D3 xb
- Buffer 1 Read (High-Frequency) D4 xb 0
- Buffer 2 Read (High-Frequency) D6 xb 0
write commands
- Buffer 1 Write 84 xb
- Buffer 2 Write 87 xb
- Buffer 1 Page Program with Erase 83 px |
- Buffer 2 Page Program with Erase 86 px |
- Buffer 1 Page Program w/o Erase 88 px |
- Buffer 2 Page Program w/o Erase 89 px |
- Page through Buffer 1 with Erase 82 pb
- Page through Buffer 2 with Erase 85 pb
- Byte/Page through Buffer 1 w/o Erase 02 pb
- Page Erase 81 px |
- Block Erase 50 kx |
- Sector Erase 7C sx |
- Chip Erase C7_94_80_9A |
- Program/Erase Suspend B0 |
- Program/Erase Resume D0 |
- Read-Modify-Write through Buffer 1 58 pbx
- Read-Modify-Write through Buffer 2 59 pbx
security commands
- Enable Sector Protection 3D_2A_7F_A9 |
- Disable Sector Protection 3D_2A_7F_9A |
- Erase Sector Protection Register 3D_2A_7F_CF |
- Program Sector Protection Register 3D_2A_7F_FC
- Read Sector Protection Register 32 xxx
- Sector Lockdown 3D_2A_7F_30
- Read Sector Lockdown Register 35 xxx
- Freeze Sector Lockdown 34_55_AA_40 |
- Program Security Register 9B_00_00_00
- Read Security Register 77 xxx
miscellanious
- Main Memory Page to Buffer 1 Transfer 53 px |
- Main Memory Page to Buffer 2 Transfer 55 px |
- Main Memory Page to Buffer 1 Compare 60 px |
- Main Memory Page to Buffer 2 Compare 61 px |
- Auto Page Rewrite through Buffer 1 58 px |
- Auto Page Rewrite through Buffer 2 59 px |
- Deep Power-Down B9 |
- Resume from Deep Power-Down AB |
- Ultra-Deep Power-Down 79 |
- Status Register Read D7
- Manufacturer and Device ID Read 9F
- Power of 2 (Binary) Page Size 3D_2A_80_A6 |
- Standard DataFlash Page Size 3D_2A_80_A7 |
- Software Reset F0_00_00_00 |
*/
#include "config.h"
#include "flash.h"
#include "cmd.h"
section_status(flash) uint8_t flash_cmd_buffer[4];
section_status(flash) uint8_t flash_status_bytes[2];
uint8_t flash_buffer[FB_SIZE];
uint8_t flash_cmd_na(uint16_t mode, uint16_t what)
{
return flash_cmd(mode, what, 0, 0);
}
uint8_t flash_cmd(uint16_t mode, uint16_t what, uint16_t page, uint16_t byte)
{
uint8_t spi_mode = SPI_FLASH | mode & FM_SPI;
uint8_t op = mode;
uint8_t size = what;
what >>= 8;
uint8_t s = spi_select(spi_mode);
if (s)
return s;
uint8_t *b = flash_cmd_buffer;
*b++ = op ;
switch (mode & FM_ADDR) {
case FM_528:
page <<= 1;
case FM_512:
byte |= page << 9;
page >>= 7;
case FM_SEC:
*b++ = page;
*b++ = byte>>8;
*b++ = byte;
}
uint8_t csize = b - flash_cmd_buffer;
uint8_t pads = 0;
switch (mode & FM_PAD) {
case FM_PAD4: pads = 2;
case FM_PAD2: pads += 1;
case FM_PAD1: pads += 1;
}
if (!(mode & FM_START)) {
spi.zsize = pads + size;
spi.zero = what;
spi_start_cmd(csize, flash_cmd_buffer);
return 0;
}
if (size > 80) {
// for read of the security register
pads += size-64;
size = 64;
}
spi.zsize = pads;
if (what + size <= 80)
b = flash_buffer + what;
else if (what>=96 && what+size <= 112)
b = cmd_buffer + (what-96);
else if (size <= 2)
b = flash_status_bytes;
switch (mode & FM_START) {
case FM_WRITE:
spi_start_write(csize, flash_cmd_buffer, size, b);
break;
case FM_READ:
spi_start_read(csize, flash_cmd_buffer, size, b);
break;
case FM_WAIT:
spi.mask = 0x80;
spi_start_read(csize, flash_cmd_buffer, 2, b);
break;
}
return 0;
}
struct flash_cmd {
uint16_t mode, what, page, byte;
};
uint8_t flash_submit_command(uint8_t *cmd)
{
struct flash_cmd *c = (void*)cmd;
return flash_cmd(c->mode, c->what, c->page, c->byte);
}
struct flash_stream fs;
__attribute__ ((noinline, noclone))
uint8_t flash_stream_submit(uint16_t mode, uint8_t size)
{
uint8_t b = fs.block;
uint16_t p = fs.page;
uint8_t r = fs.status & ~FS_Ready;
mode |= (uint16_t)config.flash_page_size << 8;
if (size) {
if (b & 8) {
b = 0;
fs.page = ++p;
fs.npages--;
}
else if (b==7 && r & FS_528)
size |= 16;
}
else if ((r & FS_Dir) == FS_Erase) {
fs.page += (uint16_t) b + 1;
fs.npages -= (uint16_t) b + 1;
b = 8;
}
fs.block = b+1;
uint8_t e = flash_cmd(mode, size, p, (uint16_t)(b&7) << 6);
if (e)
r |= FS_Ready; // FS_Error
r |= FS_Busy;
fs.status = r;
return e;
}
__attribute__ ((noinline, noclone))
uint8_t flash_stream_done()
{
uint8_t r = fs.status & FS_Error;
if (!r || r == FS_Error)
return 1;
if (fs.npages || !(fs.block & 8))
return 0;
return 1;
}
static inline
uint8_t flash_write_next_block()
{
return flash_stream_submit(config.write_buffer, 64);
}
static inline
uint8_t flash_read_next_block()
{
uint16_t mode;
if (fs.page & 0x1000)
mode = config.read_buffer[fs.page&1];
else
mode = config.read_array;
return flash_stream_submit(mode, 64);
}
static inline
uint8_t flash_erase_next_page()
{
uint16_t mode = 0x81; // Page Erase
uint8_t n = 0;
if (fs.page && !(fs.page & 0xff) && fs.npages & 0xff00) {
mode = 0x7c; // Sector 1…15 Erase
n = 0xff;
}
else if (!(fs.page & 7) && fs.npages >= 8) {
mode = 0x50; // Block Erase
n = 7;
}
fs.block = n;
return flash_stream_submit(mode, 0);
}
static inline
uint8_t flash_burn_page()
{
return flash_stream_submit(config.burn_page, 0);
}
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)
// flash is still busy burning
goto rd_status;
// not busy any more, move Bsy → Rdy
if (r & FS_Busy)
r |= FS_Ready;
goto ready;
}
if (!(r & FS_Busy))
goto ready;
if (rr) {
r |= FS_Error;
fs.status = r;
return r;
}
if (r & FS_Write) {
if (fs.block == 9) {
rd_status:
// request status bytes for pending Write or Error
r |= FS_StBsy;
fs.status = r;
flash_cmd_na(0xd7 | FM_READ, 0xf002);
return r;
}
}
ready:
r &= ~(FS_Busy | FS_StBsy);
if (rr)
r |= FS_Ack;
fs.status = r;
if (r & FS_Dir == FS_Write && fs.block == 8)
flash_burn_page();
else if (!flash_stream_done()) {
if (r & (FS_Dir|FS_Ack) == (FS_Read|FS_Ack))
flash_read_next_block();
else if (r & (FS_Dir|FS_Ack) == (FS_Write|FS_Ack))
flash_write_next_block();
else if (r & FS_Dir == FS_Erase)
flash_erase_next_page();
fs.status &=~ FS_Ack;
}
return fs.status;
}
uint8_t flash_start_stream(uint16_t page, uint16_t npages, uint8_t flags)
{
uint8_t r = flash_poll(0);
if ((r & FS_Error) == FS_Busy)
return FS_Error;
r = flags | FS_Ready;
if (config.flash_page_size != FM_528)
r &=~ FS_528;
fs.page = page;
fs.block = 0;
fs.npages = npages;
fs.status = r;
flash_status_bytes[0] = 0xff;
return flash_poll(0);
}
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 | 0x87, 176|0xff00, 0, i);
if (r)
return r;
while (spi_busy_p()) ;
}
return 0;
}
static inline
uint8_t flash_compare_buffer2(uint16_t p)
{
uint8_t r;
r = flash_cmd((uint16_t)config.flash_page_size << 8 | 0x61, 0, p, 0);
if (r)
return r;
while (spi_busy_p()) ;
r = flash_cmd_na(0xd7 | FM_WAIT, 0xf002);
if (r)
return r;
while (spi_busy_p()) ;
return (flash_status_bytes[0] | flash_status_bytes[1]) & 0x40;
}
uint16_t flash_find_free()
{
flash_memset_buffer2();
uint16_t a = config.page_start;
uint16_t e = config.page_end;
while (e > a) {
uint16_t p = (a+e)>>1;
if (flash_compare_buffer2(p))
a = p+1;
else
e = p;
}
fs.free = a;
return a;
}

56
src/flash.h Normal file
View file

@ -0,0 +1,56 @@
#ifndef _FLASH_H
#define _FLASH_H
#include "spi.h"
enum flash_mode_bits {
FM_PAD1 = 0x0100,
FM_PAD2 = 0x0200,
FM_PAD4 = 0x0300,
FM_PAD = 0x0300,
FM_512 = 0x0400,
FM_528 = 0x0800,
FM_SEC = 0x0c00,
FM_ADDR = 0x0c00,
FM_WRITE = 0x1000,
FM_READ = 0x2000,
FM_WAIT = 0x3000,
FM_START = 0x3000,
FM_CONT = (uint16_t)SPI_CONT << 8,
FM_SPI = FM_CONT,
FM_NSTR = 0x8000,
};
uint8_t flash_cmd_na(uint16_t mode, uint16_t what);
uint8_t flash_cmd(uint16_t mode, uint16_t what, uint16_t page, uint16_t byte);
#define FB_SIZE 80
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_poll(uint8_t rr);
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
uint8_t status; // FS_… flags
} fs;
enum {
FS_Ready = 1, // ready for the next buffer
FS_Busy = 2, // processing …
FS_Error = 3, // Aborted
FS_Read = 4, // array read
FS_Write = 8, // buffer write and burn
FS_Erase = 12, // Erase
FS_Dir = 12, // Mask for the last three
FS_Ack = 32, // Next buffer is provided, continue …
FS_StBsy = 64, // Waiting to Flash status register
FS_528 = 128, // do 528 byte pages.
};
#endif

113
src/flash.py Normal file
View file

@ -0,0 +1,113 @@
import struct
class flash_cmd:
class Flash_Error(IOError):
pass
M = {
0: 0x0000,
1: 0x0100,
2: 0x0200,
4: 0x0300,
512: 0x0400,
528: 0x0800,
"addr": 0x0c00,
"secure": 0x0c00,
"write": 0x1000,
"read": 0x2000,
"wait": 0x3000,
"start": 0x3000,
}
def __init__(self, pagesize=528):
self.set_pagesize(pagesize)
def set_pagesize(self, pagesize):
self.pagesize = pagesize
M = self.M
A = M[pagesize]
self.A = A
self.OP = {
"ReadPage": (0xd2 | A | M["read"] | M[4],),
"ReadLPower": (0x01 | A | M["read"] | M[0],),
"Read": (0x03 | A | M["read"] | M[0],),
"ReadHFreq1": (0x0b | A | M["read"] | M[1],),
"ReadHFreq2": (0x1b | A | M["read"] | M[2],),
"ReadLegacy": (0xe8 | A | M["read"] | M[4],),
"ReadBuffer1": (0xd1 | A | M["read"] | M[0],),
"ReadBuffer2": (0xd3 | A | M["read"] | M[0],),
"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,),
"ProgErase2": (0x86 | A,),
"Program1": (0x88 | A,),
"Program2": (0x89 | A,),
"Transfer1": (0x53 | A,),
"Transfer2": (0x55 | A,),
"Compare1": (0x60 | A,),
"Compare2": (0x61 | A,),
"ErasePage": (0x81 | A,),
"EraseBlock": (0x50 | A,),
"EraseSector": (0x7c | A,),
"EraseChip": (0xc7 | M["secure"], 0x9a8094),
"Status": (0xd7 | M["read"], 2),
"PowerDown": (0xb9,),
"PowerUp": (0xab,),
"Id": (0x9f | M["read"], 5),
"PageSize512": (0x3d | M["secure"], 0xa6802a),
"PageSize528": (0x3d | M["secure"], 0xa7802a),
"Reset": (0xf0 | M["secure"], 0x0),
}
WHAT = {
"buffer": 0,
"cmdbuf": 96,
"cmd": 96+8,
"status": 128,
}
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]
if len(opp) > 1:
if (op & self.M["addr"]) == self.M["secure"]:
page = opp[1] >> 16
byte = opp[1] & 0xffff;
elif op & self.M["read"] and size is None:
size = opp[1]
if mode:
op = op & 0xff | mode & 0xff00
if what in self.WHAT:
what = self.WHAT[what]
if size is None:
size = 0
if op & self.M["start"]:
if what >=112:
size = 2
elif what >= 96:
size = 112 - what
elif what < 80:
if byte == 448 and self.pagesize == 528:
size = 80
else:
size = 64
if size + byte > self.pagesize:
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, data)), file=stderr)
return struct.pack("<4HQ", op, size, page, byte, data)
verbose = False

68
src/fpga.c Normal file
View file

@ -0,0 +1,68 @@
#include "pipe.h"
#include "fpga.h"
#include "spi.h"
void fpga_cmd(struct fpga_cmd *c)
{
if (fpga_reset_poll()) {
c->n |= 0x20; // busy flag
return;
}
if (c->n & 1 && spi_abort()) {
c->n |= 0x10; // aborted flag
return;
}
else if (spi_busy_p()) {
c->n |= 0x20; // busy flag
return;
}
c->n &=~ 0x10; // not busy
if (c->n & 0x40)
return;
c->n |= 0x40; // submitted flag
uint8_t n = c->n & 0x0e; // send up to seven cmd words
uint8_t z = c->z & 0x7e; // and up to 63 zeros
spi_select(0);
spi.zero = c->n & 0x80; // send nop: 0x8080 (please), or zeros
spi.csize = n;
spi.cmd = c->d;
spi.rdata = c->d;
if (c->z & 0x80) {
spi.isize = n + z>>3 & 0x0e; // ignore cmd ± (z[6:4])
spi.zsize = spi.rsize = z & 0x0e; // read x[3:1] words
spi.mask = 0xff; // start reading at the first nonzero byte after cmd
}
else {
spi.zsize = z; // send z zeros/nop after cmd
spi.rsize = 14; // save the last 7 words returned
if (n + z > 14)
spi.isize = n + z - 14;
else
spi.isize = 0;
}
spi_start();
}
void fpga_start(uint8_t write)
{
uint8_t mode = 0;
if (pipe.fpga.zero == SPI_CONFIG)
mode = 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();
}

27
src/fpga.h Normal file
View file

@ -0,0 +1,27 @@
#include "config.h"
struct fpga_cmd {
uint8_t n;
uint8_t z;
uint8_t d[14];
};
static inline
void fpga_reset()
{
nCONFIG_VPORT.OUT |= (1<<nCONFIG_PIN);
}
static inline
uint8_t fpga_reset_poll()
{
uint8_t r = !(nSTATUS_VPORT.IN & (1 << nSTATUS_PIN));
if (r)
nCONFIG_VPORT.OUT |= 1 << nCONFIG_PIN;
return r;
}
void fpga_cmd(struct fpga_cmd *c);
void fpga_start(uint8_t write);

1
src/io.S Normal file
View file

@ -0,0 +1 @@
#include <avr/io.h>

445
src/iotn424.py Normal file
View file

@ -0,0 +1,445 @@
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),
"VPORTB": (0x0004, 4),
"ADC0_INTCTRL": (0x0604, 1),
"NVMCTRL_DATAH": (0x1007, 1),
"VPORTA_DIR": (0x0000, 1),
"VPORTA": (0x0000, 4),
"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),
}

209
src/linear_regression.py Executable file
View file

@ -0,0 +1,209 @@
#! /usr/bin/python3
import sys, numpy
class linreg:
def __init__(self, p=1, ny=1,
xoff=0, yoff=0,
decay=None, xdecay=None,
logscale=False):
self.P = p
self.p = numpy.arange(2*p+1)
self.x = numpy.zeros((2*p+1,))
self.y = numpy.zeros((ny, p+1))
self.yy = numpy.zeros((ny,))
self.xoff = xoff
self.yoff = yoff
self.decay = decay
self.xdecay = xdecay
self.N = 0
self.logscale = logscale
Ai = numpy.arange(p+1)
self.Ai = Ai+Ai.reshape((-1,1))
if xoff is True:
self.Bi = self.pascal()
def pascal(self):
p = self.p.shape[0]
Bi = numpy.zeros((p,p,p), dtype=int)
for n in range(p):
Bi[n,0,n] = 1
Bi[n,n,0] = 1
for n in range(1,p):
for k in range(1,n):
Bi[n,k,n-k] = Bi[n-1,k-1,n-k] + Bi[n-1,k, n-k-1]
return Bi
def add(self, x, y, w=1.0, decay=None):
y = numpy.array(y, dtype=float).reshape((-1,))
if self.logscale:
y = numpy.log(y)
if decay is None:
if self.N and self.xdecay is not None:
if x <= self.lastx:
decay = 1
self.N = 0
else:
decay = 1 - numpy.exp((self.lastx - x)/self.xdecay)
else:
decay = self.decay
self.lastx = x
if decay is not None:
self.x *= 1 - decay
self.y *= 1 - decay
self.yy *= 1 - decay
if self.xoff is True:
if self.N:
self.transform_x(x - self.x_origin)
self.x_origin = x
if self.yoff is True:
if self.N:
self.transform_y(y - self.y_origin)
self.y_origin = y
else:
self.y[:, 0] += w * y
self.yy += w * y*y
self.x[0] += w
self.N += 1
return
self.N += 1
x -= self.xoff
y -= self.yoff
x = w * numpy.power(x, self.p)
self.x += x
self.y += y[:,numpy.newaxis] * x[:self.P+1]
self.yy += w * y*y
def solve(self):
if self.N <= self.P:
return
try:
return numpy.linalg.solve(self.x[self.Ai], self.y[...,numpy.newaxis])[...,0]
except numpy.linalg.LinAlgError:
print(self.N, self.x[self.Ai], self.y, file=sys.stderr)
def solve_p(self, p):
if self.N <= p or p > self.P:
return
Ai = self.Ai[:p+1, :p+1]
y = self.y[:, :p+1, numpy.newaxis]
try:
return numpy.linalg.solve(self.x[Ai], y)
except numpy.linalg.LinAlgError:
print(self.x[Ai], y, file=sys.stderr)
def transform_x(self, x):
p = numpy.power(-x, self.p).reshape((-1,1))
Bi = self.Bi @ p
xx = self.x[numpy.newaxis,:] @ Bi
self.x = xx[:,0,0]
yy = self.y[:,numpy.newaxis,numpy.newaxis,:] @ Bi[:self.P+1,:self.P+1]
self.y = yy[...,0,0]
def transform_y(self, y):
self.yy += self.x[0]*y*y - 2*y*self.y[:,0]
self.y -= y[:,numpy.newaxis] * self.x[:self.P+1]
def print_result(self, r, fmt="%.12g"):
if r is None:
print("")
return
dx = self.xoff
dy = self.yoff
if dx is True:
dx = self.x_origin
if dy is True:
dy = self.y_origin
r[:,0] += dy
if self.logscale:
r[:,0] = numpy.exp(r[:,0])
dy = numpy.exp(dy)
dy += numpy.zeros(self.yy.shape)
for i, rr in enumerate(r):
print(i, fmt % dx, fmt % dy[i], " ".join([fmt % rrr for rrr in rr]))
def strtonum(s):
try:
return float(s)
except:
pass
return int(s, 0)
def main():
import sys, getopt, fileinput
options, files = getopt.gnu_getopt(sys.argv[1:], "lp:n:x:y:w:d:X:Y:D:T:I")
p = 1
ny = 1
ix = 0
iy = 1
iw = None
id = None
w = 1.0
d = None
I = False
args = {}
for o, v in options:
if o=="-p":
args["p"] = int(v)
if o=="-n":
ny = int(v)
args["ny"] = ny
if o=="-X":
if v=="R":
args["xoff"] = True
else:
args["xoff"] = int(v)
if o=="-Y":
if v=="R":
args["xoff"] = True
args["yoff"] = True
else:
args["yoff"] = int(v)
if o=="-D":
args["decay"] = float(v)
if o=="-T":
args["xdecay"] = float(v)
if o=="-l":
args["logscale"] = True
if o=="-x":
ix = int(v)
if o=="-y":
iy = int(v)
if o=="-w":
iw = int(v)
if o=="-d":
id = int(v)
if o=="-I":
I = True
LR = linreg(**args)
for l in fileinput.input(files):
ll = l.split()
try:
x = strtonum(ll[ix])
y = numpy.array([strtonum(lll) for lll in ll[iy:iy+ny]])
if iw is not None:
w = strtonum(ll[iw])
if id is not None:
d = strtonum(ll[id])
except:
continue
LR.add(x, y, w, d)
if I:
LR.print_result(LR.solve())
if not I:
LR.print_result(LR.solve())
if __name__=="__main__":
main()

33
src/map.py Normal file
View file

@ -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)

73
src/mul.py Normal file
View file

@ -0,0 +1,73 @@
def mul(a,b):
return (a & 0xff) * (b & 0xff)
def mulsu(a,b):
a &= 0xff
if a & 0x80:
a |= 0xff00
return (a * (b&0xff)) & 0xffff
def mul16su(a, b):
a &= 0xffff
b &= 0xffff
ah = a>>8
al = a & 0xff
bh = b>>8
bl = b & 0xff
r = mulsu(ah, bh) << 16
r |= mul(al, bl)
rr = mulsu(ah, bl)
if rr & 0x8000:
r -= 0x1000000
#c = r & 0x1000000
r += rr << 8
#if c != (r & 0x1000000):
# r += 0x1000000
r += mul(al, bh) << 8
return r & 0xffffffff
def test():
errors = 0
for s in range(2):
for l in range(4):
for a in range(64, 128):
for b in range(64, 128):
for c in range(9):
for d in range(10):
e = a << (8-c)
if l & 1:
e ^= 0xff >> c
if (s):
e = -e
f = b << (9-d)
if l & 2:
f ^= 0x1ff >> c
r = mul16su(e, f)
rr = (e*f) & 0xffffffff
if r != rr:
print("ERROR", e,f,hex(r),hex(rr))
errors += 1
else:
print(e,f,r)
print(errors, "Errors")
return errors
def full():
errors = 0
for u in range(0x10000):
print(u)
for s in range(0x8000):
r = mul16su(s, u)
rr = (s*u) & 0xffffffff
if r != rr:
print("ERROR", s, u, hex(r), hex(rr))
errors += 1
for s in range(0x8000):
r = mul16su(-s, u)
rr = (-s*u) & 0xffffffff
if r != rr:
print("ERROR", -s, u, hex(r), hex(rr))
errors += 1
print(errors, "Errors")
return errors

63
src/ntc.py Normal file
View file

@ -0,0 +1,63 @@
import math
class ntc:
"""Calculate Temperature from NTC readings
Negative Temperature Coefficient thermistors.
The resitance follows the law of Arrhenius
W =
R = R₂₅ exp(β/T₂₅) exp(-β/T)
The NTC is biases via resistor R₁ from Voltage V₁.
"""
β = 3940.0 # K
T25 = 298.16 # K
T0 = 273.16 # K
R25 = 10 # kΩ
R1 = 10 # kΩ
V1 = 1
res = 1/0x1000
log = math.log
def Rntc(self, a, V1=None):
"""return NTC Resistance from ADC reading
a: ADC reading,
V1: full scale reading
"""
if not V1:
V1 = self.V1
a /= V1
if a < self.res:
a = 1
if a > 1 - self.res:
a = 1 - self.res
return self.R1 / (1/a - 1)
def TntcR(self, R):
"""return Temperature from NTC Resistance"""
return self.β / (self.log(R/self.R25) + self.β/self.T25) - self.T0
def Tntc(self, a, V1=None):
"""return Temperature from ADC reading"""
return self.TntcR(self.Rntc(a, V1))
def TntcB(self, a, b):
"""Bridge mode
a: bridge voltage (ADC_T - ADC_V)
b: reference branch voltage (ADC_V)
see `turbo.sch`
"""
return self.Tntc(a + b, b * (1 + self.R1/self.R25))
def R(self, T):
"""R(T)"""
return self.R25*math.exp(self.β/(T + self.T0) - self.β/self.T25)
def VntcT(self, T, V1=None):
"""V(T)"""
if not V1:
V1 = self.V1
return V1 / (1 + self.R1/self.R(T))

132
src/pipe.c Normal file
View file

@ -0,0 +1,132 @@
#include "pipe.h"
#include "bch4369.h"
#include "adc.h"
#ifdef HAVE_FPGA
#include "fpga.h"
#endif
#include <string.h>
section_status(pipe) struct pipe pipe;
uint8_t pipe_poll()
{
uint8_t r = pipe.status;
uint8_t fl = flash_poll(0);
if (spi_busy_p() || fl & FS_Busy || adc_poll(0))
goto done;
#ifdef HAVE_FPGA
if (fpga_reset_poll())
goto done;
#endif
if (r & PS_OUT) {
if (pipe.dest & pipe_cmd && pipe.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))
goto done;
// fpga is OUT when the spi is ready.
// We are done with this buffer
pipe.valid = 0;
if (pipe.source & pipe_flash)
flash_poll(1);
#ifdef HAVE_FPGA
else if (pipe.source & pipe_fpga)
fpga_start(0);
#endif
r &=~ (PS_OUT|4);
r++;
goto done;
}
uint8_t bflgs = 0x0f;
if ((r & PS_5) == PS_5)
bflgs = 0x1f;
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;
#if 0
uint8_t c;
do {
o += 16;
f <<= 1;
c = v & 1;
v >>= 1;
} while (c); // please use the carry bit!
#else
// saves one instruction
__asm__ volatile
("\n"
"1: \n\t"
"subi %[O], -16 \n\t"
"lsl %[F] \n\t"
"lsr %[V] \n\t"
"brcs 1b \n"
: [O] "+r" (o),
[F] "+r" (f),
[V] "+r" (v)
);
#endif
if (o > 48)
goto adc_done;
if (o + n >= 64) {
f = 0xff;
n = 64-o;
}
pipe.valid |= f>>4;
_memcopy(flash_buffer+o, (void*)&magic, n);
}
adc_done:
if (pipe.source & pipe_flash && fl & FS_Ready)
pipe.valid = bflgs;
#ifdef HAVE_FPGA
else if (pipe.source & pipe_fpga)
pipe.valid = 0x0f;
#endif
if (~pipe.valid & 0x0f)
goto done;
if (r & PS_BCH) {
if (!(r&3))
bch4369_init();
uint8_t *bend = bch4369_stri(flash_buffer, 64);
if (!(~r & 3)) {
// reuse Y=bend
_memcopyyz(bend, bch_parity, 16);
pipe.valid = 0x01f;
}
}
if (~pipe.valid & bflgs)
goto done;
#ifdef HAVE_FPGA
if (pipe.dest & pipe_fpga)
fpga_start(1);
#endif
r |= PS_OUT;
done:
pipe.status = r;
return r;
}
void pipe_config(const struct pipe_config *c)
{
pipe = c->pipe;
if ((pipe.source | pipe.dest) & pipe_flash)
flash_start_stream(c->page, c->npages, c->flash);
if (pipe.source & pipe_adc)
adc_start_stream(pipe.adc);
}

47
src/pipe.h Normal file
View file

@ -0,0 +1,47 @@
#include "flash.h"
enum pipe_ports {
pipe_cmd = 1,
pipe_adc = 2,
pipe_flash = 4,
pipe_fpga = 8,
PS_OUT = 0x80,
PS_BCH = 0x10,
PS_528 = 0x08,
PS_BLK = 0x03,
PS_5 = PS_BLK | PS_528, // need 16 bytes more
};
extern
struct pipe {
uint8_t source;
uint8_t dest;
uint8_t status;
uint8_t valid;
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;
} fpga;
#endif
} pipe;
struct pipe_config {
struct pipe pipe;
uint16_t page;
uint16_t npages;
uint8_t flash;
};
uint8_t pipe_poll();
void pipe_cron();
void pipe_config(const struct pipe_config *c);

64
src/pressure.py Executable file
View file

@ -0,0 +1,64 @@
#!/usr/bin/python3
# encoding: UTF-8
import sys, time
debug = 0
def calibrate(Word, D):
C=[0]*7
C[1] = Word[1] >> 1
C[2] = ((Word[3] & 0x3f)<<6) | (Word[4] & 0x3f)
C[3] = Word[4]>>6
C[4] = Word[3]>>6
C[5] = ((Word[1] & 1)<<10) | (Word[2]>>6)
C[6] = Word[2] & 0x3f
if debug:
sys.stdout.write("""
C1 = %d
C2 = %d
C3 = %d
C4 = %d
C5 = %d
C6 = %d
""" % tuple(C[1:7]))
UT1 = 8*C[5]+20224
dT = D[2] - UT1
TEMP = 200 + dT*(C[6]+50)/1024
if debug:
print(f"{D[3]=} {UT1=} {dT=} {TEMP=:.2f} °C", file=sys.stderr)
OFF = C[2]*4 + ((C[4]-512)*dT)/4096
SENS = C[1] + (C[3]*dT)/1024 + 24576
X = (SENS * (D[1]-7168))/16384 - OFF
P = X*10/32 + 2500
if debug:
print(f"{D[1]=} {OFF=:.2f} {SENS=:.2f} {P=:.2f}", file=sys.stderr)
return (TEMP,P)
def main():
data = [0]*7
import fileinput
for l in fileinput.input(errors='backslashreplace'):
if l[0]=='T':
t = int(l.split()[1],0)
print(f"{time.time():.1f} {t} {time.strftime('%FT%TZ', time.gmtime(t))}")
if l[0]=='W':
data[1:5] = [int(ll,16) for ll in l.split()[1:]]
elif l[0]=='D':
data[5:] = [int(ll,16) for ll in l.split()[1:]]
if data[1]:
T,p = calibrate(data, data[4:])
print(f"{T/10:.2f} °C, {p/10:.2f} mbar")
elif l[0] in "PV":
print(l.strip())
if __name__=="__main__":
main()

20
src/prompt.py Normal file
View file

@ -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

17
src/pwm.c Normal file
View file

@ -0,0 +1,17 @@
// pwm.c
#include "pwm.h"
struct_ioconf(pwm_config) = {
conf_prefix(PWM),
#ifdef HAVE_nFETs
conf_io(PWM.CTRLB, TCA_SINGLE_WGMODE_SINGLESLOPE_gc | TCA_SINGLE_CMP0EN_bm),
#else
conf_io(PWM.CTRLB, TCA_SINGLE_WGMODE_SINGLESLOPE_gc),
#endif
conf_iow(PWM.PER, 0x0fff),
conf_iow(PWM.CMP0, 0xffff),
conf_iow(PWM.CMP1, 0x00ff),
conf_io(PWM.CTRLA, TCA_SINGLE_ENABLE_bm),
};

40
src/pwm.h Normal file
View file

@ -0,0 +1,40 @@
#include "config.h"
#define PWM TCA0.SINGLE
#ifdef HAVE_nFETs
static inline
void pwm_set(uint16_t dc, uint8_t d)
{
PWM.CMP0 = dc;
if (d)
DRAIN_VPORT.OUT |= 1 << DRAIN_PIN;
else
DRAIN_VPORT.OUT &=~ (1 << DRAIN_PIN);
}
static inline
void pwm_bias()
{
pwm_set(0xffff, 0);
}
static inline
void pwm_step(uint16_t max)
{
uint16_t c = PWM.CMP0+1;
if (c > max)
pwm_bias();
else
pwm_set(c, 1);
}
static inline
uint8_t pwm_busy()
{
return DRAIN_VPORT.OUT & (1 << DRAIN_PIN);
}
#endif

8
src/rc.local Executable file
View file

@ -0,0 +1,8 @@
#! /bin/bash -v
date --utc +"Turbo reboot at %FT%TZ"
SRC=~stephan/turbo_weather/src
[ -d "$SRC" ] || exit 0
mount /data/blaulicht
[ -d /data/blaulicht/temperature ] || exit 0
sudo -inu stephan screen -S turbo -d -m $SRC/turbo.sh

157
src/rtc.c Normal file
View file

@ -0,0 +1,157 @@
//
// rtc.c
//
// !!! int = int8_t
#include "config.h"
#include "rtc.h"
#define Bit(x) (1<<(x))
section_status(rtc) uint32_t clock;
volatile uint16_t clockh;
volatile uint8_t pit_tick;
volatile uint8_t rtc_tick;
uint8_t rtc_cnt_tick()
{
cli();
uint8_t r = rtc_tick;
rtc_tick = 0;
sei();
return r;
}
struct_ioconf(rtc_config) = {
conf_prefix(RTC),
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),
conf_iow(RTC.PER, 0xffff),
conf_io(RTC.CTRLA, RTC_PRESCALER_DIV1024_gc | RTC_RUNSTDBY_bm | RTC_RTCEN_bm),
conf_io(RTC.INTCTRL, RTC_CMP_bm | RTC_OVF_bm),
};
#if 1
ISR(RTC_PIT_vect, ISR_NAKED)
{
__asm__ (
"push r24" "\n\t"
"ldi r24, 1" "\n\t"
"sts %[flag], r24" "\n\t"
"sts pit_tick, r24" "\n\t"
"pop r24" "\n\t"
"reti" "\n"
::[flag] "n" (&RTC.PITINTFLAGS)
);
}
ISR(RTC_CNT_vect, ISR_NAKED)
{
__asm__ (
"push r24" "\n\t"
"in r24, __SREG__" "\n\t"
"push r24" "\n\t"
"lds r24, %[flag]" "\n\t"
"sts %[flag], r24" "\n\t"
"sts rtc_tick, r24" "\n\t"
"sbrs r24, 1" "\n\t"
"rjmp 1f" "\n\t"
"push r24" "\n\t"
"push r25" "\n\t"
"push r26" "\n\t"
"lds r24, %[CMPL]" "\n\t"
"lds r25, %[CMPH]" "\n\t"
"lds r26, rtc_config+3" "\n\t"
"add r24, r26" "\n\t"
"sts %[CMPL], r24" "\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"
"pop r24" "\n"
"1:" "\n\t"
"sbrs r24, 0" "\n\t"
"rjmp 2f" "\n\t"
"lds r24, clockh" "\n\t"
"subi r24, -1" "\n\t"
"sts clockh, r24" "\n\t"
"lds r24, clockh+1" "\n\t"
"sbci r24, -1" "\n\t"
"sts clockh+1, r24" "\n\t"
"2:" "\n\t"
"pop r24" "\n\t"
"out __SREG__, r24" "\n"
"pop r24" "\n\t"
"reti" "\n"
::
[flag] "n" (&RTC.INTFLAGS),
[CMPL] "n" (&RTC.CMP),
[CMPH] "n" (&RTC.CMP+1)
);
}
#else
ISR(RTC_PIT_vect)
{
clock_tick = 1;
RTC.PITINTFLAGS = 1;
}
ISR(RTC_CNT_vect)
{
uint8_t flag = RTC.INTFLAGS;
RTC.INTFLAGS = flag;
rtc_tick = flag;
if (flag & RTC_CNT_bm)
RTC.CMP += rtc_config[1].val | rtc_config[2].val << 8;
if (flag & RTC_OVF_bm)
clock += 1;
return;
}
#endif
#if 0
uint32_t time()
{
uint16_t cl, ch;
do {
cli();
cl = RTC.CNT;
ch = clockh;
sei();
} while (cl != RTC.CMP);
return clock = cl | (uint32_t)ch << 16;
}
#else
__attribute__((naked))
uint32_t time()
{
__asm__ volatile("\n"
"1:" "\n\t"
"cli" "\n\t"
"lds r22, %[CNT]" "\n\t"
"lds r23, %[CNT]+1" "\n\t"
"lds r24, clockh" "\n\t"
"lds r25, clockh+1" "\n\t"
"sei" "\n\t"
"lds r18, %[CNT]" "\n\t"
"lds r19, %[CNT]+1" "\n\t"
"cp r18, r22" "\n\t"
"cpc r19, r23" "\n\t"
"brne 1b" "\n\t"
"ldi r30, lo8(clock)" "\n\t"
"ldi r31, hi8(clock)" "\n\t"
"st Z+, r22" "\n\t"
"st Z+, r23" "\n\t"
"st Z+, r24" "\n\t"
"st Z+, r25" "\n\t"
"ret" "\n"
:: [CNT] "n" (&RTC.CNT)
: "r18", "r19", "r30", "r31", "memory"
);
}
#endif

14
src/rtc.h Normal file
View file

@ -0,0 +1,14 @@
//
// rtc.h
//
#include <stdint.h>
#include <avr/interrupt.h>
extern uint32_t clock;
volatile extern uint16_t clockh;
volatile extern uint8_t pit_tick;
volatile extern uint8_t rtc_tick;
uint32_t time();
uint8_t rtc_cnt_tick();

6
src/set_clock.py Executable file
View file

@ -0,0 +1,6 @@
#! /usr/bin/python3
import time
t = int(time.time())
t = "".join([f"{(t>>b)&0xff:02x}" for b in range(0,32,8)])
print(f"\nK {t}")

267
src/spi.c Normal file
View file

@ -0,0 +1,267 @@
//
// spi.c
//
// ! int = int8_t
#include "spi.h"
#include <string.h>
section_status(spi) struct spi_job spi;
struct_ioconf(spi_config) = {
conf_prefix(SPI),
conf_io(SPI.CTRLB, SPI_SSD_bm | SPI_BUFEN_bm),
conf_io(SPI.CTRLA, SPI_MASTER_bm | SPI_ENABLE_bm | SPI_SPEED),
};
#if 0
ISR(SPI0_INT_vect)
{
uint8_t d;
uint8_t ifg = SPI.INTFLAGS;
if (ifg & SPI_DREIF_bm) {
repeat:
if (spi.csize) {
spi.csize--;
d = *spi.cmd++;
}
else if (spi.zsize) {
spi.zsize--;
d = spi.zero;
}
else if (spi.wsize) {
spi.wsize--;
d = *spi.wdata++;
}
else {
SPI.INTCTRL = SPI_TXCIF_bm | SPI_RXCIF_bm;
goto done_write;
}
SPI.DATA = d;
// clear a stray TXCIF, at high SCK rate
SPI.INTFLAGS = SPI_TXCIF_bm;
SPI.INTCTRL = SPI_TXCIF_bm | SPI_DREIF_bm | SPI_RXCIF_bm;
done_write:
ifg = SPI.INTFLAGS;
}
if (ifg & SPI_RXCIF_bm) {
d = SPI.DATA; // clears the IF flag
if (spi.isize)
spi.isize --;
else {
if (spi.mask) {
if ((d & spi.mask) == spi.wait) {
spi.zsize++;
goto cont;
}
spi.mask = 0;
}
if (spi.rsize) {
spi.rsize--;
*spi.rdata++ = d;
}
}
cont:
ifg = SPI.INTFLAGS;
if (ifg & SPI_DREIF_bm)
// This may prevent this ISR to terminate
// before the job is done at high clock rate.
// DRE is kept set when all bytes were sent.
// With just the right clock rate, we may send
// two bytes per interrupt.
goto repeat;
}
if (ifg & SPI_TXCIF_bm) {
if (!(spi.mode & SPI_CONT))
SSEL_VPORT.OUT |= 1 << SSEL_PIN;
SPI.INTCTRL = SPI_RXCIF_bm;
}
}
#else
ISR(SPI0_INT_vect, ISR_NAKED)
{
__asm__ volatile(
"push r24" "\n\t"
"in r24, __SREG__" "\n\t"
"push r24" "\n\t"
"push r25" "\n\t"
"push r26" "\n\t"
"push r30" "\n\t"
"push r31" "\n\t"
"lds r24, %[IFLGS]" "\n\t"
"sbrs r24, %[DRE]" "\n\t"
"rjmp 3f" "\n"
"5:" "\n"
"lds r25, %[CSZ]" "\n\t"
"subi r25, 1" "\n\t"
"brcs 1f" "\n\t"
"sts %[CSZ], r25" "\n\t"
"lds r30, %[CMD]" "\n\t"
"lds r31, %[CMD]+1" "\n\t"
"ld r25, Z+" "\n\t"
"sts %[CMD], r30" "\n\t"
"sts %[CMD]+1, r31" "\n\t"
"rjmp 2f" "\n"
"1:" "\n\t"
"lds r25, %[ZSZ]" "\n\t"
"subi r25, 1" "\n\t"
"brcs 1f" "\n\t"
"sts %[ZSZ], r25" "\n\t"
"lds r25, %[ZERO]" "\n\t"
"rjmp 2f" "\n"
"1:" "\n\t"
"lds r25, %[WSZ]" "\n\t"
"subi r25, 1" "\n\t"
"ldi r26, %[RIFLGS]" "\n\t"
"brcs 1f" "\n\t"
"sts %[WSZ], r25" "\n\t"
"lds r30, %[WD]" "\n\t"
"lds r31, %[WD]+1" "\n\t"
"ld r25, Z+" "\n\t"
"sts %[WD], r30" "\n\t"
"sts %[WD]+1, r31" "\n"
"2:" "\n\t"
"sts %[DATA], r25" "\n\t"
"ldi r25, 1<<%[TXC]" "\n\t"
"sts %[IFLGS], r25" "\n\t"
"ldi r26, %[AIFLGS]" "\n"
"1:" "\n\t"
"sts %[ICTRL], r26" "\n\t"
"lds r24, %[IFLGS]" "\n"
"3:" "\n\t"
"sbrs r24, %[RXC]" "\n\t"
"rjmp 3f" "\n\t"
"lds r25, %[DATA]" "\n\t"
"lds r26, %[ISZ]" "\n\t"
"subi r26, 1" "\n\t"
"brcs 1f " "\n\t"
"sts %[ISZ], r26" "\n\t"
"rjmp 4f" "\n"
"1:" "\n\t"
"lds r26, %[MASK]" "\n\t"
"cpi r26, 0" "\n\t"
"breq 1f" "\n\t"
"and r26, r25" "\n\t"
"lds r24, %[WAIT]" "\n\t"
"cp r24, r26" "\n\t"
"brne 2f" "\n\t"
"lds r25, %[ZSZ]" "\n\t"
"subi r25, -1" "\n\t"
"sts %[ZSZ], r25" "\n\t"
"rjmp 4f" "\n"
"2:" "\n\t"
"clr r26" "\n\t"
"sts %[MASK], r26" "\n"
"1:" "\n\t"
"lds r26, %[RSZ]" "\n\t"
"subi r26, 1" "\n\t"
"brcs 4f" "\n\t"
"sts %[RSZ], r26" "\n\t"
"lds r30, %[RD]" "\n\t"
"lds r31, %[RD]+1" "\n\t"
"st Z+, r25" "\n\t"
"sts %[RD], r30" "\n\t"
"sts %[RD]+1, r31" "\n"
"4:" "\n\t"
"lds r24, %[IFLGS]" "\n\t"
"sbrc r24, %[DRE]" "\n\t"
"rjmp 5b" "\n\t"
"3:" "\n\t"
"sbrs r24, %[TXC]" "\n\t"
"rjmp 3f" "\n\t"
"lds r25, %[MODE]" "\n\t"
"sbrs r25, 7" "\n\t"
"sbi %[SPORT], %[SSEL]" "\n\t"
"ldi r25, 1<<%[RXC]" "\n\t"
"sts %[ICTRL], r25" "\n"
"3:" "\n\t"
"pop r31" "\n\t"
"pop r30" "\n\t"
"pop r26" "\n\t"
"pop r25" "\n\t"
"pop r24" "\n\t"
"out __SREG__, r24" "\n\t"
"pop r24" "\n\t"
"reti" "\n"
::
[CSZ] "m" (spi.csize),
[ZSZ] "m" (spi.zsize),
[WSZ] "m" (spi.wsize),
[ISZ] "m" (spi.isize),
[RSZ] "m" (spi.rsize),
[CMD] "m" (spi.cmd),
[ZERO] "m" (spi.zero),
[WD] "m" (spi.wdata),
[RD] "m" (spi.rdata),
[MODE] "m" (spi.mode),
[MASK] "m" (spi.mask),
[WAIT] "m" (spi.wait),
[IFLGS] "n" (&SPI.INTFLAGS),
[TXC] "n" (SPI_TXCIF_bp),
[DRE] "n" (SPI_DREIF_bp),
[RXC] "n" (SPI_RXCIF_bp),
[RIFLGS] "n" (SPI_TXCIF_bm | SPI_RXCIF_bm),
[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.OUT)),
[SSEL] "n" (SSEL_PIN)
);
}
#endif
uint8_t spi_select(uint8_t mode)
{
uint8_t s = spi_busy_p();
if (s)
return s;
memset(&spi, 0, sizeof(spi));
spi.mode = mode;
if (mode & SPI_FLASH)
SSEL_VPORT.OUT &=~ (1 << SSEL_PIN);
else
SSEL_VPORT.OUT |= 1 << SSEL_PIN;
if (mode & SPI_CONFIG)
SPI.CTRLA |= SPI_DORD_bm;
else
SPI.CTRLA &=~ SPI_DORD_bm;
return 0;
}
void spi_start_cmd(uint8_t csize, uint8_t *cmd)
{
spi.cmd = cmd;
barrier();
spi.csize = csize;
spi_start();
}
void spi_start_write(uint8_t csize, uint8_t *cmd, uint8_t wsize, uint8_t *wdata)
{
spi.wdata = wdata;
barrier();
spi.wsize = wsize;
spi_start_cmd(csize, cmd);
}
void spi_start_read(uint8_t csize, uint8_t *cmd, uint8_t rsize, uint8_t *rdata)
{
spi.rdata = rdata;
spi.isize = csize + spi.zsize;
barrier();
spi.zsize += rsize;
spi.rsize = rsize;
spi_start_cmd(csize, cmd);
}

65
src/spi.h Normal file
View file

@ -0,0 +1,65 @@
//
// spi.h
//
#ifndef _SPI_H
#define _SPI_H
#include "config.h"
#include <avr/interrupt.h>
#define SPI SPI0
#define SPI_SPEED (SPI_CLK2X_bm | SPI_PRESC_DIV4_gc)
extern
struct spi_job {
uint8_t mode;
uint8_t csize; // pipe.fpga.…
uint8_t zsize; //
uint8_t isize; //
uint8_t zero; //
uint8_t wait; //
uint8_t mask; //
uint8_t rsize;
uint8_t wsize;
const uint8_t *cmd;
const uint8_t *wdata;
uint8_t *rdata;
} spi;
enum spi_mode_bits {
SPI_FLASH = 0x01,
SPI_CONFIG = 0x02,
SPI_CONT = 0x80,
};
static inline
uint8_t spi_abort()
{
cli();
uint8_t f = SPI.INTFLAGS;
SPI.INTFLAGS = 0;
sei();
return f;
}
static inline
uint8_t spi_busy_p()
{
return SPI.INTCTRL & SPI_TXCIF_bm;
}
static inline
void spi_start()
{
SPI.INTCTRL = SPI_DREIF_bm | SPI_TXCIF_bm| SPI_RXCIF_bm;
}
static inline void barrier() { __asm__("":::"memory"); }
void init_spi(uint8_t spi_div);
uint8_t spi_select(uint8_t mode);
void spi_start_cmd(uint8_t csize, uint8_t *cmd);
void spi_start_write(uint8_t csize, uint8_t *cmd, uint8_t wsize, uint8_t *wdata);
void spi_start_read(uint8_t csize, uint8_t *cmd, uint8_t rsize, uint8_t *rdata);
#endif

48
src/thhor.c Normal file
View file

@ -0,0 +1,48 @@
//
// thhor.c
//
// int = int8_t
#include <stdint.h>
#include <string.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
#include "config.h"
#include "uart.h"
#include "pipe.h"
////////////////////////////////////////////////////////////////////////////////
//
// main()
section_status(pipe) struct pipe pipe;
section_status(main) struct magic magic;
int main()
{
while (CLKCTRL.MCLKCTRLB != config.cpu_clk) {
CCP = CCP_IOREG_gc;
CLKCTRL.MCLKCTRLB = config.cpu_clk;
}
sleep_enable();
magic.magic = config.magic;
while (magic.magic != MAGIC || config.version != VERSION)
sleep_cpu();
apply_config();
magic.reset_source = RSTCTRL.RSTFR;
RSTCTRL.RSTFR = magic.reset_source;
send_str("\nV THHOR CRS V0.0");
send_hex_byte_eol(magic.reset_source);
while (1) {
sei();
sleep_cpu();
command();
}
}

55
src/turbo.awk Executable file
View file

@ -0,0 +1,55 @@
#! /usr/bin/gawk -f
/^T/ {
if (NF >= 7) {
phase = 1
split($0, T)
}
}
/^p / {
if (phase==1 && NF==5) {
phase = 2
split($0, P)
} else
phase = 0
}
/^Q/ {
if ($2 != "✓")
phase = 0
else if (phase==2)
phase = 3
else
emit()
}
/^A/ {
if (phase==3)
phase = 4
else
phase = 0
}
/^a / {
V[$2] = $3
A[$2] = $9
}
function emit() {
if (phase != 4) {
phase = 0
return
}
phase = 0
tT = T[3]
tU = T[4]
tF = T[3]+T[5]
dt = T[6]
p = P[2]
Tp = P[4]
Tc = A["TEMP/1V"]
Tb = A["NTC-RFP/1V"]
Tn = A["NTC/2.5V"]
Vbat = A["BAT/1V"]
Vdd = A["VDD/1V"]
Vrf = A["RFP/2.5V"]
printf "%.0f %.1f %.1f %.6f %.2f %.2f %.2f %.2f %.2f %.4f %.4f %.4f\n", \
tT, tU, tF, dt, p, Tp, Tc, Tn, Tb, Vbat, Vdd, Vrf
}

46
src/turbo.gpt Executable file
View file

@ -0,0 +1,46 @@
#! /usr/local/bin/gnuplot
# -*- gnuplot -*-
set encoding utf8
set timestamp font ",8" textcolor "gray"
if (ARG1 ne "") { fn=ARG1 }
if (ARG2 ne "") { set xrange [system("date +%s -d ".ARG2)<*:] }
set grid x
set format x "" time
set ytics nomirror
set y2tics
# set margin 12,12,0,0
set multiplot title fn noenh layout 3,1 margin char 12,13,5,2 spacing char 1
set ylab "Δt [s]"
set y2lab "dt/dt_{UTC}"
set y2ra [0.98<*:*<1.02]
set yra [*:*]
plot fn u 2:($1-$2) t "clock offset" w l, \
"" u 2:($3-$2) t "clock offset fit" w l, \
"" u 2:4 axis x1y2 t "clock speed" w l
set yra [*:*]
set y2ra [*:*]
set xrange restore
set ylab "p [mbar]"
set y2lab "T [°C]"
plot fn u 2:7 axis x1y2 t "T_{CPU}" w l, \
"" u 2:8 axis x1y2 t "T_{NTC}" w l, \
"" u 2:9 axis x1y2 t "T_{BRIDGE}" w l lt 7, \
"" u 2:5 t "pressure" w l lw 2, \
"" u 2:6 axis x1y2 t "T_{SENSOR}" w l lw 2
set format x "%H:%M\n%d. %b\n%Y" time
set ylab "U [V]"
set y2lab "U_{BAT} [V]"
plot fn u 2:10 axis x1y2 t "U_{BAT}" w l, \
"" u 2:11 t "U_{DD}" w l, \
"" u 2:12 t "U_{RF}" w l
unset multiplot

17
src/turbo.makefile Executable file
View file

@ -0,0 +1,17 @@
#! /usr/bin/make -f
DIR=data
default: $(DIR)/turbo.png $(DIR)/turbo.svg
%.data: %.txt
./turbo.awk $< > $@
FROM = -1week
DATA = data/turbo.data
PNG = pngcairo size 1200,900 font ",10"
$(DIR)/turbo.png: $(DATA)
gnuplot -e 'set term $(PNG);set out "$@"' -c turbo.gpt $< $(FROM)
SVG = size 1200,900 name "Turbo"
$(DIR)/turbo.svg: $(DATA)
gnuplot -e 'set term svg $(SVG);set out "$@"' -c turbo.gpt $< $(FROM)

505
src/turbo.py Executable file
View file

@ -0,0 +1,505 @@
#! /usr/bin/python3
import sys, time, getopt, serial, fileinput
import pressure, ntc, linear_regression, cmdsocket
options, files = getopt.gnu_getopt(sys.argv[1:], "xF:s:o:c", ["debug", "tty=", "noise", "clock", "socket=", "output="])
tty = None
baud = 2400
socket = None
out = None
do_noise = False
do_clock = False
debug = False
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":
if tty:
raise ValueError("can only do one tty")
tty = v
v = v.split(",", 1)
if v[1:]:
tty = v[0]
baud = int(v[1])
do_clock = True
if o in "--clock":
do_clock = True
# BUG: matches -n
if o in "-x --noise":
do_noise = True
if o in "--socket":
socket = v
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 not out:
out = sys.stdout
if len(files)==1:
if "/dev/tty" in files[0]:
tty = files[0]
files = []
inp = []
if tty:
inp = [serial.Serial(port=tty, baudrate=baud)]
if files or not inp:
inp[0:0] = [fileinput.input(files, mode="rb")]
checksum = 0
def add_checksum(line):
global checksum
checksum += sum(line)
# noise(line, "s")
def noise(line, prefix="x"):
if not do_noise:
return False
trunc = ""
print(prefix, repr(line), file=out)
return False
def echo(line, *a):
try:
print(line.strip().decode(), *a, file=out)
return True
except:
pass
return noise(line)
def check_sum(line):
global checksum
try:
rcs = int(line.split()[0][1:], 16)
except:
return noise(line)
if rcs > 0xff:
return noise(line)
add_checksum(line[:1])
lcs = checksum & 0xff
checksum = 0
if lcs==rcs:
echo(line, "")
emit()
else:
echo(line, f"{lcs:02x}", "Checksum Error")
return False
Values = {}
NTC = ntc.ntc()
def voltages(line):
ll = line.split()
try:
vv = {
"Tcpu": float(ll[1]),
"Vcpu": float(ll[2]),
"Vbat": float(ll[3]),
"dVntc": float(ll[4]),
"Vntc": float(ll[5]),
"cVntc": int(ll[6], 16),
"Vrf": float(ll[7]),
"cVrf": int(ll[8], 16),
}
except:
return noise(line)
Values.update(vv)
emit_voltages(**vv)
return echo(line)
def emit_voltages(Tcpu, Vcpu, Vbat, dVntc, Vntc, cVntc, Vrf, cVrf):
Tntc = NTC.TntcB(dVntc, Vrf)
Tntc2 = NTC.TntcB(cVntc-cVrf, cVrf)
print(f"v {Tcpu:.1f}°C"
f" {Tntc:.2f}°C"
f" Bat {Vbat:.3f}V"
f" Vcc {Vcpu:.3f}V"
f" Vrf {Vrf/500:.3f}V",
file=out)
freq = linear_regression.linreg(p=2, xoff=True, yoff=True, xdecay=600)
def clock(line):
ll = line.split()
try:
c = int(ll[1], 16)
except:
return noise(line, "t")
s = None
t = None
if do_clock:
t = time.time()
try:
t = float(ll[3])
except:
pass
if t is not None:
freq.add(t, c)
s = freq.solve()
if s is None:
if t is None:
t = time.time()
return echo(line, f"{c} {t:.1f}")
return echo(ll[0]+b' '+ll[1], f"{c} {t:.1f}", *("%.6g" % ss for ss in s[0]))
Data = {}
def data(line):
ll = line.split()
try:
c = ll[0]
d = [int(l, 16) for l in ll[1:]]
if ll[1:]:
Data[c] = d
emit_data(c)
return echo(line)
except Exception as e:
Debug(e, line)
return noise(line, "h")
def sigrow(line):
try:
c = line[:1]
Data[c] = [int(line[i:i+2], 16) for i in range(1,len(line.strip()),2)]
emit_data(c)
return echo(line)
except Exception as e:
Debug(e, line)
return noise(line, "h")
def emit_data(c):
if c not in emitters:
return
try:
return emitters[c](c, Data[c])
except Exception as e:
Debug(e, c)
def emit_pressure(c, cc):
W = [None]
D = [None]
W.extend(Data[b'W'])
D.extend(Data[b'D'])
if len(W) != 5 or len(D) != 3:
return
try:
T, p = pressure.calibrate(W, D)
except Exception as e:
print(f"pressure.calibrate failed {e}", file=sys.stderr)
print(f"p {p/10:.2f} mbar, {T/10:.2f} °C")
from cmd import turbocmd
millivolts = {
"VDD": 1000,
"1V": 1024,
"2V": 2048,
"2.5V": 2500,
"4V": 4096,
}
ADC_mV = {}
def emit_adc(c, cc):
if len(cc) > 8:
return
ADC_mV.clear()
for n, a in enumerate(cc):
C = ADC_CONFIG[n]
if C is None:
continue
if C["mode"] == turbocmd.ADC_MODE["DIFF"]:
if a & 0x8000:
a -= 0x10000
a *= 2
elif C["mode"] != turbocmd.ADC_MODE["NORM"]:
continue
REF = None
for k, i in turbocmd.ADC_REF.items():
if C["ref"] == i:
REF = k
mV = millivolts[k]
if REF is None:
continue
A = a * mV / 0x10000
inp = "?"
inn = ""
for k, i in turbocmd.ADC_INP.items():
if C["inp"] == i:
inp = k
if C["mode"] == turbocmd.ADC_MODE["DIFF"]:
if C["inn"] == i:
inn = "-"+k
inp = inp+inn+"/"+REF
ADC_mV[inp] = (A, mV)
emit_adc_pretty()
def emit_adc_pretty():
for k, a in ADC_mV.items():
A, mV = a
units = "V ref"
pretty = ""
kk = k.split("/")
if kk[0] == "TEMP":
try:
T = (A/mV*0x10000 - TEMP_CAL["offset"]) * TEMP_CAL["gain"] - 273.16
pretty = f" Tcpu {T:.2f} °C"
except Exception as e:
Debug(e, k)
if kk[0] == "NTC":
try:
AA, x = ADC_mV["RFP/"+kk[1]]
T = NTC.TntcB(A-AA, AA)
pretty = f" Tntc {T:.2f} °C"
except Exception as e:
Debug(e, k)
if kk[0] == "NTC-RFP":
try:
AA, x = ADC_mV["RFP/2.5V"]
T = NTC.TntcB(A, AA)
pretty = f" Tntc {T:.2f} °C"
except Exception as e:
Debug(e, k)
if kk[0] == "VDD":
pretty = f" Vdd {A/100:.4f} V"
if kk[0] == "BAT":
pretty = f" Vbat {A/1000*11:.4f} V"
if k == "RFP/2.5V":
pretty = f" Vrf {A/500:.4f} V"
if kk[1] == "VDD":
units=". Vdd"
try:
AA, x = ADC_mV["VDD/1V"]
mV = AA*10
pretty += f" {kk[0]} {A*mV/1e6:.4f} V"
except Exception as e:
Debug(e, k)
try:
AA, x = ADC_mV[kk[0]+"/2.5V"]
pretty += f" Vdd {AA/A:.4f} V"
except Exception as e:
Debug(e, k)
print(f"a {k:10} {A/1000:.5f} {units} {mV/1000:.3f} V{pretty}")
def emit_configuration(c, cc):
c = c.lower().decode()
if len(cc) != 32:
return
for r,v in turbocmd.REGS.items():
a = v[0]
b = cc[a]
if v[1] == 2:
b |= cc[a+1] << 8
if len(v) >= 3:
bb = []
for k,i in v[2].items():
if not ~b & i:
bb.append(k)
print(c, r, bb)
continue
if v[1] < 3:
print(c, r, "%02x" % b)
continue
print(c, r, " ".join([f"0x{b:02x}" for b in cc[a:a+v[1]]]))
ADC_CONFIG = [None]*8
def emit_eeprom(c, cc):
if len(cc) < 32:
return
for n in range(8):
bb = [""]*7
aa = {}
for r,v in turbocmd.ADC_CONF.items():
a = 8*n + v[0]
b = cc[a//2]
if v[1]==1:
if a&1:
b >>= 8
b &= 0xff
bb[v[0]] = f"{b}"
aa[r] = b
for k, i in v[2].items():
if b==i:
bb[v[0]] = k
ADC_CONFIG[n] = aa
print("e", n, " ".join(bb))
SIGROW = {
"DEVID": (0, 3),
"SERNO": (3, 10),
"OSCCAL": (0x18, 4),
"TEMPSENS": (0x20, 2),
}
DEVID = {
0x1e922a: "ATtiny427",
0x1e922b: "ATtiny426",
0x1e922c: "ATtiny424",
0x1e9327: "ATtiny827",
0x1e9328: "ATtiny826",
0x1e9329: "ATtiny824",
}
def devid():
cc = Data[b'S'][2::-1]
return sum([b << (8*i) for i,b in enumerate(cc)])
TEMP_CAL = {"gain": 0, "offset": 0}
def emit_sigrow(c, cc):
if len(cc)<34:
return
for r,v in SIGROW.items():
print(f"s {r} {' '.join([f'{i:02x}' for i in cc[v[0]:v[0]+v[1]]])}")
i = devid()
if i in DEVID:
print(f"s DEVICE 0x{i:06x} {DEVID[i]} SN {bytes(cc[3:13])}")
v = SIGROW["TEMPSENS"]
g = cc[v[0]]
o = cc[v[0]+1]
if o & 0x80:
o -= 0x100
o <<= 6
g /= 1 << (6+8)
TEMP_CAL.update({"gain": g, "offset": o})
print(f"s ADC_CAL offset {o} gain {g:.7f}")
FUSES = {
"WDT": (0, {"PERIOD": (0,0xf), "WINDOW": (4,0xf)}),
"BOD": (1, {"SLEEP": (0,3), "ACTIVE": (2,3), "SAMPFREQ": (4,1), "LVL": (5,7)}),
"OSC": (2, {"OSCLOCK": (7,1), "FREQSEL": (0,1)}),
"SYS0": (5, {"EESAVE": (0,1), "RSTPIN": (2,3), "TOUTDIS": (4,1), "CRCSRC": (6,3)}),
"SYS1": (6, {"SUT": (0,7)}),
"APPEND": (7, {}),
"BOOTEND": (8, {}),
}
def emit_fuses(c, cc):
if len(cc) != 9:
return
for r,v in FUSES.items():
b = cc[v[0]]
bb = f"f {v[0]} {r} 0x{b:02x}"
for k,i in v[1].items():
bb += f" {k}={(b >> i[0]) & i[1]}"
print(bb)
emitters = {
b'C': emit_configuration,
b'U': emit_configuration,
b'E': emit_eeprom,
b'S': emit_sigrow,
b'F': emit_fuses,
b'D': emit_pressure,
b'A': emit_adc,
}
def resp(line):
ll = line.split()
if line[1] not in b'!?>':
return noise(line)
echo(line)
checksum = 0
return False
def emit():
pass
processes = {
b'A': data,
b'B': echo,
b'C': data,
b'D': data,
b'E': data,
b'F': data,
b'P': echo,
b'Q': check_sum,
b'R': resp,
b'S': sigrow,
b'T': clock,
b'U': data,
b'V': voltages,
b'W': data,
b'X': echo,
}
splits = {
b'Q': 1,
b'T': 2,
}
class batecmd(cmdsocket.cmd_receiver):
def msg(self, m):
echo(b'r '+m)
inp.write(b'\n' + m + b'\n')
cmd = batecmd(path=None)
if socket:
if not tty:
raise ValueError("cannot have socket without tty")
cmd.open(socket, force=True)
while inp:
try:
line = inp[0].readline()
except KeyboardInterrupt:
break
if not line:
inp[:1] = []
continue
line_key = line[0:1]
is_noise = line_key not in processes
if not is_noise and min(line.strip()) < 32:
is_noise = True
if line_key in b"apcufse":
continue
if is_noise:
if do_noise:
noise(line)
check_sum = 0
continue
if processes[line_key](line):
if line_key in splits:
line = b" ".join(line.split()[:splits[line_key]])+b"\n"
add_checksum(line)
if tty:
out.flush()
cmd.poll()

23
src/turbo.sh Executable file
View file

@ -0,0 +1,23 @@
#! /bin/bash
TTY=/dev/ttyUSB0,1200
DATA=/data/blaulicht/temperature
NEWDATA=$(date --utc +$DATA/turbo-%FT%TZ.txt)
OLDDATA=$(ls -1 $DATA/turbo-????-??-??*.txt | tail -1)
LINKDATA=$DATA/turbo.txt
SRC=~/turbo_weather/src
[ -e $NEWDATA ] && exit
if [ "$(stat -c %h $LINKDATA)" -eq "2" ]
then
rm -vf $LINKDATA
touch $NEWDATA || exit 0
ln -v $NEWDATA $LINKDATA
fi
echo $NEWDATA
echo $OLDDATA
$SRC/turbo.py --tty="$TTY" $OLDDATA | tee --append "$NEWDATA"

194
src/uart.c Normal file
View file

@ -0,0 +1,194 @@
//
// uart.c
//
// !!! int = int8_t
#include "uart.h"
#include <avr/sleep.h>
#include <avr/interrupt.h>
struct_ioconf(uart_config) = {
conf_prefix(USART0),
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.
uint8_t uart_tx[32];
#define uart_tx_m (sizeof(uart_tx) - 1)
volatile uint8_t uart_tx_w;
volatile uint8_t uart_tx_r;
#if 0
__attribute__ ((noinline, noclone))
void tx()
{
// read volatile memory once, for speed
uint8_t r = uart_tx_r;
uint8_t w = uart_tx_w;
while (w - r) {
if (!(USART0.STATUS & USART_DREIF_bm)) {
USART0.CTRLA |= USART_DREIE_bm;
uart_tx_r = r;
return;
}
USART0.TXDATAL = uart_tx[r++ & uart_tx_m];
USART0.STATUS |= USART_TXCIF_bm
}
uart_tx_r = r;
USART0.CTRLA &=~ USART_DREIE_bm;
}
ISR(USART0_DRE_vect)
{
tx();
}
#else
__attribute__ ((noinline, naked))
void tx()
{
// This uses only three registers, to save stack in the ISR.
__asm__("\n"
" lds r25, %[CTRLA] \n"
" andi r25, ~(1<<%[DRE]) \n"
" rjmp 2f \n"
"1: \n"
" mov r31, r30 ; \n"
" subi r31, 0xff ; r++ & uart_tx_m \n"
" sts uart_tx_r, r31 \n"
" andi r30, 0x1f ; \n"
" clr r31 \n"
" subi r30, lo8(-(uart_tx)) \n"
" sbci r31, hi8(-(uart_tx)) \n"
" ld r30, Z \n"
" sts %[TXDATA], r30 \n"
" ldi r30, 1<<%[TXC] \n"
" sts %[STATUS], r30 \n"
"2: \n"
" lds r30, uart_tx_r \n"
" lds r31, uart_tx_w \n"
" cp r30, r31 \n"
" breq 3f \n"
" lds r31, %[STATUS] \n"
" sbrc r31, %[DRE] \n"
" rjmp 1b \n"
" ori r25, 1<<%[DRE] \n"
"3: \n"
" sts %[CTRLA], r25 \n"
" ret \n"
:
:
[TXC] "n" (USART_TXCIF_bp),
[DRE] "n" (USART_DREIF_bp),
[CTRLA] "n" (&USART0.CTRLA),
[STATUS] "n" (&USART0.STATUS),
[TXDATA] "n" (&USART0.TXDATAL)
: "r25", "r30", "r31", "memory"
);
}
ISR(USART0_DRE_vect, ISR_NAKED)
{
// This saves five instructions and two bytes stack (r0, r1).
// OTOH, the C implementation of tx() is not bad either,
// if we are doing this asm, then we do it agressively.
__asm__("\n"
" push r25 \n"
" in r25, __SREG__ \n"
" push r25 \n"
" push r30 \n"
" push r31 \n"
" rcall tx \n"
" pop r31 \n"
" pop r30 \n"
" pop r25 \n"
" out __SREG__, r25 \n"
" pop r25 \n"
" reti \n"
);
}
#endif
ISR(USART0_TXC_vect, ISR_ALIASOF(USART0_DRE_vect));
uint8_t uart_rx[32];
#define uart_rx_m (sizeof(uart_rx) - 1)
section_status(uart.w) volatile uint8_t uart_rx_w;
section_status(uart.h) volatile uint8_t uart_rx_s;
section_status(uart.m) volatile uint8_t uart_rx_mes;
section_status(uart.e) uint8_t uart_rx_err;
section_status(uart.ee) uint8_t uart_rx_errors;
#if 0
ISR(USART0_RXC_vect)
{
if (USART0.STATUS & USART_RXCIF_bm) {
uart_rx_s |= USART0.RXDATAH;
uint8_t w = uart_rx_w;
if (w<=uart_rx_m) {
uint8_t c = USART0.RXDATAL;
uart_rx[w++] = c;
if (!uart_rx_mes && c=='\n')
uart_rx_mes = w;
uart_rx_w = w;
}
}
}
#else
ISR(USART0_RXC_vect, ISR_NAKED)
{
// The loop will in all cases execute exactly once.
// No need to avoid load and stores inside the loop.
// The volatileness is also irrelevant, we are the ISR,
// nobody else is looking.
// We could just have the ISR be called again
// in the unusual case there is backlog in the FIFO,
// and not loop here.
// We may not even need to test the RXCIF bit.
// Let's do that …
__asm__(
" push r24 \n"
" in r24, __SREG__ \n"
" push r24 \n"
" push r30 \n"
" push r31 \n"
" lds r24, %[DH] \n"
" lds r30, uart_rx_s \n"
" or r24, r30 \n"
" sts uart_rx_s, r24 \n"
" lds r24, %[DL] \n"
" lds r30, uart_rx_w \n"
" cpi r30, 0x20 \n"
" brcc 1f \n"
" ldi r31, 0 \n"
" subi r30, lo8(-(uart_rx)) \n"
" sbci r31, hi8(-(uart_rx)) \n"
" st Z+, r24 \n"
" subi r30, lo8(uart_rx) \n"
" sts uart_rx_w, r30 \n"
" cpi r24, '\n' \n"
" brne 1f \n"
" lds r24, uart_rx_mes \n"
" tst r24 \n"
" brne 1f \n"
" sts uart_rx_mes, r30 \n"
"1: \n"
" pop r31 \n"
" pop r30 \n"
" pop r24 \n"
" out __SREG__, r24 \n"
" pop r24 \n"
" reti \n"
:: [DL] "n" (&USART0.RXDATAL), [DH] "n" (&USART0.RXDATAH)
);
}
#endif

28
src/uart.h Normal file
View file

@ -0,0 +1,28 @@
//
// uart.h
//
#include "config.h"
void init_uart(uint16_t mode, uint16_t div);
void send_char(uint8_t c);
void send_eol();
void send_str(const char *s);
void send_hex_byte(uint8_t b);
void send_hex_byte_eol(uint8_t b);
void send_hex_word(uint16_t b);
void send_hex(const void *s, uint8_t n);
uint8_t uart_busy();
void command(void);
void parse_command(const uint8_t *s, uint8_t n);
static inline
void send_hex_long(uint32_t b)
{
send_hex_word(b >> 16);
send_hex_word(b);
}
extern uint8_t uart_rx_err;
extern uint8_t uart_rx_errors;

132
src/uart.py Executable file
View file

@ -0,0 +1,132 @@
#! /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)
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="])
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])
tty = uart(port, baud)
tty._export(globals())
set_prompt(tty.portname)

175
src/uart_tx.S Normal file
View file

@ -0,0 +1,175 @@
//
// uart_tx.S
//
// avoid quite a few push and pops and jumps
#include <avr/io.h>
.global send_hex_word
.global send_hex_byte
.global send_hex_byte_eol
.global send_char
.global _send_char22
.global send_str
.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.
// r18, r26, and 27 must be preseved in the hex functions
// , r20, r21, r24, must be preserved in `uart_busy()` and `put_char()'
send_hex_byte_eol:
ldi r22, ' '
rcall _send_char22
rcall send_hex_byte
send_eol:
ldi r22, '\n'
rcall _send_char22
uart_busy:
cli
rcall tx ; gobbles r25, r30, and r31
sei
lds r25, USART0_STATUS
andi r25, SPI_TXCIF_bm
9:
ret
#ifdef HEX_WORD
send_hex_word:
mov r20, r24
mov r24, r25
rcall send_hex_byte
mov r24, r20
#endif
send_hex_byte:
mov r21, r24
swap r24
rcall send_hex_nibble
mov r24, r21
send_hex_nibble:
andi r24, 0x0f
subi r24, -'0'
cpi r24, '9'+1
brlo send_char
subi r24, '9'+1-'A'
send_char:
mov r22, r24
rjmp _send_char22
1:
rcall uart_busy
sleep
_send_char22:
// non-global, non-C
// arg: char r22
lds r23, uart_tx_r
lds r30, uart_tx_w
ldi r19, 1
add r19, r30
sub r23, r19
andi r30, 0x1f // uart_tx_m
ldi r31, 0
subi r30, lo8(-(uart_tx))
sbci r31, hi8(-(uart_tx))
subi r23, 0xe0
brcs 1b
st Z, r22
sts uart_tx_w, r19
9:
ret
send_str:
movw r26, r24
rjmp _send_str26
1:
__send_char22_str26:
rcall _send_char22
_send_str26:
ld r22, X+
tst r22
brne 1b
9:
ret
#ifdef SEND_HEX
send_hex:
movw r26, r24
mov r18, r22
1:
ld r24, X+
rcall send_hex_byte
subi r18, 1
brne 1b
9:
ret
#endif
command:
;; when any frame errors occured, dismiss the buffer
lds r22, uart_rx_s
andi r22, 0x46
brne uart_errs_p
;; when there is no message, check for overflow
lds r22, uart_rx_mes
tst r22
breq uart_full_p
;; when the buffer was dismissed for errors, skip this command
push r22
lds r24, uart_rx_err
sts uart_rx_err, r1
lds r25, uart_rx_errors
or r25, r24
sts uart_rx_errors, r25
tst r24
brne 2f
ldi r24, lo8(uart_rx)
ldi r25, hi8(uart_rx)
rcall parse_command
2:
pop r24
rx_dismiss:
cli
lds r18, uart_rx_w
clr r19
clr r20
sub r18, r24
breq 3f
brcs 3f
ldi r26, lo8(uart_rx)
ldi r27, hi8(uart_rx)
movw r30, r26
add r30, r24
adc r31, r20
1:
ld r25, Z+
st X+, r25
subi r19, -1
cpi r25, '\n'
brne 2f
tst r20
brne 2f
mov r20, r19
2:
subi r18, 1
brne 1b
3:
sts uart_rx_mes, r20
sts uart_rx_w, r19
sei
9:
ret
uart_full_p:
lds r22, uart_rx_w
andi r22, 0x20
breq 9b
uart_errs_p:
lds r24, uart_rx_err
or r24, r22
rx_dismiss_buffer:
sts uart_rx_err, r24
lds r24, uart_rx_w
rjmp rx_dismiss