Compare commits
160 commits
1a41e4ceca
...
630073928e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
630073928e | ||
|
|
7bc2c22137 | ||
|
|
7e770cbf77 | ||
|
|
b1617f45c0 | ||
|
|
1d9c0be330 | ||
|
|
2c41feee96 | ||
|
|
a2e020b270 | ||
|
|
47f6abd006 | ||
|
|
37bc3efc2e | ||
|
|
7b4724ef37 | ||
|
|
ecccef0f97 | ||
|
|
9e85cb3b1d | ||
|
|
731d558a40 | ||
|
|
4a65235852 | ||
|
|
a8df949ce8 | ||
|
|
50a80c8663 | ||
|
|
b511471d88 | ||
|
|
4ded8be2be | ||
|
|
8e5c806708 | ||
|
|
badab35bfa | ||
|
|
93ce9bafd0 | ||
|
|
a5061f09e2 | ||
|
|
8c4a860e5e | ||
|
|
4a8d1d591f | ||
|
|
33468dbbea | ||
|
|
5245001149 | ||
|
|
d042ee36aa | ||
|
|
17b0623a91 | ||
|
|
c68f71c6c1 | ||
|
|
827e699eb6 | ||
|
|
92fbb4d611 | ||
|
|
36e2ce083f | ||
|
|
b5e58cc887 | ||
|
|
468dac0357 | ||
|
|
d0b40ebdb0 | ||
|
|
b8f86aad8c | ||
|
|
12ba51a41d | ||
|
|
3e58d4a584 | ||
|
|
ed115c84cc | ||
|
|
caad768f18 | ||
|
|
19deb243f6 | ||
|
|
68b9ed4381 | ||
|
|
01098c9313 | ||
|
|
b941c83bbb | ||
|
|
f63888ae7d | ||
|
|
e64d5e9e68 | ||
|
|
6b1ca3f30c | ||
|
|
efeb5bd9a0 | ||
|
|
741b2bb361 | ||
|
|
94089b99ce | ||
|
|
5498a71984 | ||
|
|
98bbdbf933 | ||
|
|
dc43b91629 | ||
|
|
d0cd85a1f6 | ||
|
|
a2dd008f0f | ||
|
|
78e01710fc | ||
|
|
1cb45edbfa | ||
|
|
dfe357367f | ||
|
|
de68357084 | ||
|
|
7f169734f9 | ||
|
|
4897664e37 | ||
|
|
1d89239e50 | ||
|
|
5fbcdc61b4 | ||
|
|
aaf83208ba | ||
|
|
60816aaa16 | ||
|
|
5b77bb3393 | ||
|
|
5d3f364bf6 | ||
|
|
864be8b5ef | ||
|
|
55f7c76355 | ||
|
|
c2ce5dcc32 | ||
|
|
7108238be6 | ||
|
|
7d2ca76396 | ||
|
|
05d77d0c0b | ||
|
|
04f2fc03b1 | ||
|
|
9a9465a561 | ||
|
|
e6bb585fc5 | ||
|
|
aa7d2b3808 | ||
|
|
d4dcbd250a | ||
|
|
fbcbc15b4e | ||
|
|
cd82aacfbd | ||
|
|
43a90641f9 | ||
|
|
3104ac4d08 | ||
|
|
302def656c | ||
|
|
4e598267b1 | ||
|
|
29eb6afed0 | ||
|
|
91d20af691 | ||
|
|
fa47c885d7 | ||
|
|
4c72530731 | ||
|
|
b934f2a075 | ||
|
|
061549ca94 | ||
|
|
e31bd4ac51 | ||
|
|
46e6162a02 | ||
|
|
c728e20ea7 | ||
|
|
3ecc3501c5 | ||
|
|
b2eeccfe65 | ||
|
|
7d4c9b21be | ||
|
|
ae3a48fcaa | ||
|
|
cc9ce887d4 | ||
|
|
3467a5e51c | ||
|
|
a9b687312e | ||
|
|
276bd40a33 | ||
|
|
ad2246675f | ||
|
|
5757d31416 | ||
|
|
bcaf9e3de6 | ||
|
|
38db87a030 | ||
|
|
4ea0d71d5d | ||
|
|
61d78b9bdc | ||
|
|
0ba62420ab | ||
|
|
0801c0513d | ||
|
|
e24402ce66 | ||
|
|
7f20ce610f | ||
|
|
7e18810b42 | ||
|
|
25bebed7df | ||
|
|
306904b372 | ||
|
|
68cc24fb7c | ||
|
|
d7b100e02c | ||
|
|
3aaf28484d | ||
|
|
850cd6a4fb | ||
|
|
707f4512e8 | ||
|
|
b3dcb7d104 | ||
|
|
8050b9abf0 | ||
|
|
358da80f46 | ||
|
|
edb1544cd7 | ||
|
|
ee03f01b06 | ||
|
|
ebdb88118c | ||
|
|
a3eb8eb7b9 | ||
|
|
82f0da6a89 | ||
|
|
9302c61244 | ||
|
|
77463da388 | ||
|
|
cd6355021b | ||
|
|
4bbd6c264a | ||
|
|
09cc30d854 | ||
|
|
ebb9d88b81 | ||
|
|
a4b1e0b1b1 | ||
|
|
89d64222e6 | ||
|
|
233a175151 | ||
|
|
cac8adf51f | ||
|
|
0693ab1204 | ||
|
|
c8f9b34757 | ||
|
|
2e66f12041 | ||
|
|
e8aaefbe1f | ||
|
|
23dc2085ea | ||
|
|
53a77c75aa | ||
|
|
2d7ac8c6af | ||
|
|
2665750308 | ||
|
|
0179568e85 | ||
|
|
ef65e2dc0e | ||
|
|
0b680c1b78 | ||
|
|
900089c787 | ||
|
|
d0793f88f0 | ||
|
|
aaa3fcc591 | ||
|
|
7b738aef34 | ||
|
|
8eb2bb5518 | ||
|
|
0022e53ceb | ||
|
|
f93edddb30 | ||
|
|
f9e4c7cf9d | ||
|
|
4525a17d68 | ||
|
|
bf375a3f48 | ||
|
|
b389d222da | ||
|
|
7d2bf84a43 |
60 changed files with 12186 additions and 0 deletions
8
.gitignore
vendored
8
.gitignore
vendored
|
|
@ -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
3
.gitmodules
vendored
|
|
@ -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
5166
sensor/bgo-ssd.dxf
Normal file
File diff suppressed because it is too large
Load diff
1
src/.gitignore
vendored
Normal file
1
src/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
thhor.userrow
|
||||
150
src/Makefile
Normal file
150
src/Makefile
Normal 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
194
src/README.md
Normal 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
222
src/adc.c
Normal 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
45
src/adc.h
Normal 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
43
src/base85.c
Normal 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
4
src/base85.h
Normal 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
25
src/base85.py
Normal 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
132
src/base85_test.c
Normal 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
175
src/base85a.S
Normal 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
636
src/bate.c
Normal 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
128
src/bate.h
Normal 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
1
src/bch4369
Submodule
|
|
@ -0,0 +1 @@
|
|||
Subproject commit eebd4bb43039a2ecf63aa7c2fdbe1c4edf469393
|
||||
76
src/calib.py
Executable file
76
src/calib.py
Executable 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
289
src/cmd.c
Normal 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
6
src/cmd.h
Normal 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
448
src/cmd.py
Executable 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
158
src/cmdsocket.py
Executable 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
67
src/config.c
Normal 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
198
src/config.h
Normal 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
75
src/dose.c
Normal 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
11
src/dose.h
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
//
|
||||
// dose.h
|
||||
//
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Configuration
|
||||
|
||||
#include <stdint.h>
|
||||
#include <avr/io.h>
|
||||
|
||||
383
src/dose.py
Executable file
383
src/dose.py
Executable 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
30
src/eeprom.ld
Normal 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
364
src/flash.c
Normal 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
56
src/flash.h
Normal 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
113
src/flash.py
Normal 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
68
src/fpga.c
Normal 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
27
src/fpga.h
Normal 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
1
src/io.S
Normal file
|
|
@ -0,0 +1 @@
|
|||
#include <avr/io.h>
|
||||
445
src/iotn424.py
Normal file
445
src/iotn424.py
Normal 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
209
src/linear_regression.py
Executable 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
33
src/map.py
Normal 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
73
src/mul.py
Normal 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
63
src/ntc.py
Normal 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 = kβ
|
||||
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
132
src/pipe.c
Normal 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
47
src/pipe.h
Normal 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
64
src/pressure.py
Executable 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
20
src/prompt.py
Normal 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
17
src/pwm.c
Normal 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
40
src/pwm.h
Normal 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
8
src/rc.local
Executable 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
157
src/rtc.c
Normal 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
14
src/rtc.h
Normal 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
6
src/set_clock.py
Executable 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
267
src/spi.c
Normal 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
65
src/spi.h
Normal 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
48
src/thhor.c
Normal 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
55
src/turbo.awk
Executable 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
46
src/turbo.gpt
Executable 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
17
src/turbo.makefile
Executable 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
505
src/turbo.py
Executable 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
23
src/turbo.sh
Executable 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
194
src/uart.c
Normal 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
28
src/uart.h
Normal 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
132
src/uart.py
Executable 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
175
src/uart_tx.S
Normal 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue