Commit ab1e8ceeafc9912b2708c85f8ab9f72b55be4ba5 test-software/nano-1284/release/v2024-10-28_141638 test-software/nano-328/release/v2024-10-28_141640 test-software/nano-644/release/v2024-10-28_141636
receivedMon, 28. Oct 2024, 14:35:35 (by user sx)
Mon, 28 Oct 2024 13:35:35 +0000 (14:35 +0100)
authorManfred Steiner <sx@htl-kaindorf.at>
Mon, 28 Oct 2024 13:35:26 +0000 (14:35 +0100)
committerManfred Steiner <sx@htl-kaindorf.at>
Mon, 28 Oct 2024 13:35:26 +0000 (14:35 +0100)
64 files changed:
software/test-software/.gdb_history [new file with mode: 0644]
software/test-software/.gdbinit [new file with mode: 0644]
software/test-software/.gitignore [new file with mode: 0644]
software/test-software/.vscode/c_cpp_properties.json [new file with mode: 0644]
software/test-software/.vscode/launch.json [new file with mode: 0644]
software/test-software/.vscode/settings.json [new file with mode: 0644]
software/test-software/.vscode/tasks.json [new file with mode: 0644]
software/test-software/LICENSE [new file with mode: 0644]
software/test-software/Makefile [new file with mode: 0644]
software/test-software/README.md [new file with mode: 0644]
software/test-software/create-release [new file with mode: 0755]
software/test-software/nano-1284/Makefile [new file with mode: 0644]
software/test-software/nano-1284/release/v2024-10-28_141638/test-software_nano-1284p_12mhz.elf [new file with mode: 0755]
software/test-software/nano-1284/src [new symlink]
software/test-software/nano-328/Makefile [new file with mode: 0644]
software/test-software/nano-328/release/v2024-10-28_141640/test-software_nano-328p_16mhz.elf [new file with mode: 0755]
software/test-software/nano-328/src [new symlink]
software/test-software/nano-644/Makefile [new file with mode: 0644]
software/test-software/nano-644/release/v2024-10-28_141636/test-software_nano-644_12mhz.elf [new file with mode: 0755]
software/test-software/nano-644/src [new symlink]
software/test-software/release/v2024-08-18_1103/test_2024-07-23_nano-644.elf [new file with mode: 0755]
software/test-software/src/adafruit/bme280.cpp [new file with mode: 0644]
software/test-software/src/adafruit/bme280.h [new file with mode: 0644]
software/test-software/src/adafruit/ens160.cpp [new file with mode: 0644]
software/test-software/src/adafruit/ens160.h [new file with mode: 0644]
software/test-software/src/adafruit/sensor.h [new file with mode: 0644]
software/test-software/src/i2cmaster.cpp [new file with mode: 0644]
software/test-software/src/i2cmaster.hpp [new file with mode: 0644]
software/test-software/src/i2cslave.cpp [new file with mode: 0644]
software/test-software/src/i2cslave.hpp [new file with mode: 0644]
software/test-software/src/main.cpp [new file with mode: 0644]
software/test-software/src/main.hpp [new file with mode: 0644]
software/test-software/src/units/cc1101.cpp [new file with mode: 0644]
software/test-software/src/units/cc1101.hpp [new file with mode: 0644]
software/test-software/src/units/encoder.cpp [new file with mode: 0644]
software/test-software/src/units/encoder.hpp [new file with mode: 0644]
software/test-software/src/units/i2c.cpp [new file with mode: 0644]
software/test-software/src/units/i2c.hpp [new file with mode: 0644]
software/test-software/src/units/ieee485.cpp [new file with mode: 0644]
software/test-software/src/units/ieee485.hpp [new file with mode: 0644]
software/test-software/src/units/lcd.cpp [new file with mode: 0644]
software/test-software/src/units/lcd.hpp [new file with mode: 0644]
software/test-software/src/units/led.cpp [new file with mode: 0644]
software/test-software/src/units/led.hpp [new file with mode: 0644]
software/test-software/src/units/modbus.cpp [new file with mode: 0644]
software/test-software/src/units/modbus.hpp [new file with mode: 0644]
software/test-software/src/units/motor.cpp [new file with mode: 0644]
software/test-software/src/units/motor.hpp [new file with mode: 0644]
software/test-software/src/units/portexp.cpp [new file with mode: 0644]
software/test-software/src/units/portexp.hpp [new file with mode: 0644]
software/test-software/src/units/poti.cpp [new file with mode: 0644]
software/test-software/src/units/poti.hpp [new file with mode: 0644]
software/test-software/src/units/r2r.cpp [new file with mode: 0644]
software/test-software/src/units/r2r.hpp [new file with mode: 0644]
software/test-software/src/units/rgb.cpp [new file with mode: 0644]
software/test-software/src/units/rgb.hpp [new file with mode: 0644]
software/test-software/src/units/rtc8563.cpp [new file with mode: 0644]
software/test-software/src/units/rtc8563.hpp [new file with mode: 0644]
software/test-software/src/units/seg7.cpp [new file with mode: 0644]
software/test-software/src/units/seg7.hpp [new file with mode: 0644]
software/test-software/src/units/switch.cpp [new file with mode: 0644]
software/test-software/src/units/switch.hpp [new file with mode: 0644]
software/test-software/src/units/uart1.cpp [new file with mode: 0644]
software/test-software/src/units/uart1.hpp [new file with mode: 0644]

diff --git a/software/test-software/.gdb_history b/software/test-software/.gdb_history
new file mode 100644 (file)
index 0000000..3339046
--- /dev/null
@@ -0,0 +1,9 @@
+target remote :1234
+layout split
+stepi
+quit
+target remote :1234
+layout split
+stepi
+b *main+9
+quit
diff --git a/software/test-software/.gdbinit b/software/test-software/.gdbinit
new file mode 100644 (file)
index 0000000..139597f
--- /dev/null
@@ -0,0 +1,2 @@
+
+
diff --git a/software/test-software/.gitignore b/software/test-software/.gitignore
new file mode 100644 (file)
index 0000000..a959910
--- /dev/null
@@ -0,0 +1,4 @@
+.depend
+**/build
+**/dist
+**/sim
diff --git a/software/test-software/.vscode/c_cpp_properties.json b/software/test-software/.vscode/c_cpp_properties.json
new file mode 100644 (file)
index 0000000..3a57c79
--- /dev/null
@@ -0,0 +1,18 @@
+{
+    "configurations": [
+        {
+            "name": "Linux AVR",
+            "includePath": [
+                "/usr/lib/avr/include/**",
+                "/usr/lib/gcc/avr/**"
+            ],
+            "defines": [],
+            "compilerPath": "/usr/bin/avr-gcc",
+            "compilerArgs": [ "-mmcu=atmega644p", "-DF_CPU=12000000", "-Os" ],
+            "cStandard": "gnu11",
+            "cppStandard": "gnu++11",
+            "intelliSenseMode": "linux-gcc-x64"
+        }
+    ],
+    "version": 4
+}
diff --git a/software/test-software/.vscode/launch.json b/software/test-software/.vscode/launch.json
new file mode 100644 (file)
index 0000000..f29cf2e
--- /dev/null
@@ -0,0 +1,37 @@
+{
+    // Use IntelliSense to learn about possible attributes.
+    // Hover to view descriptions of existing attributes.
+    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+    "version": "0.2.0",
+    "configurations": [
+        {
+            "name": "Build",
+            // "request": "launch",
+            "type": "node-terminal",
+            "preLaunchTask": "build"
+        },{
+            "name": "Flash",
+            // "request": "launch",
+            "type": "node-terminal",
+            "preLaunchTask": "flash"
+        },{
+            "name": "Clean",
+            // "request": "launch",
+            "type": "node-terminal",
+            "preLaunchTask": "clean"
+        },{
+            // es muss mit simuc --board arduino dist/programm.elf der Simulator 
+            // gestartet werden. Dessen gdb-stub Ã¶ffnet auf localhost:1234 einen Port
+            "name": "Debug (simuc)",
+            "type": "cppdbg",
+            "request": "launch",
+            "program": "${workspaceFolder}/sim/atmega328p.elf",
+            "cwd": "${workspaceFolder}",
+            "externalConsole": false,
+            "MIMode": "gdb",
+            "miDebuggerPath": "/usr/bin/avr-gdb",
+            "miDebuggerServerAddress": ":1234",
+            "preLaunchTask": "build"
+        }
+    ]
+}
diff --git a/software/test-software/.vscode/settings.json b/software/test-software/.vscode/settings.json
new file mode 100644 (file)
index 0000000..58539af
--- /dev/null
@@ -0,0 +1,29 @@
+{
+    "[c]": {
+        "editor.insertSpaces": true,
+        "editor.tabSize": 3,
+        "editor.detectIndentation": false
+    },
+    "[cpp]": {
+        "editor.insertSpaces": true,
+        "editor.tabSize": 3,
+        "editor.detectIndentation": false
+    },
+    "[h]": {
+        "editor.insertSpaces": true,
+        "editor.tabSize": 3,
+        "editor.detectIndentation": false
+    },
+    "[hpp]": {
+        "editor.insertSpaces": true,
+        "editor.tabSize": 3,
+        "editor.detectIndentation": false
+    },
+    "cSpell.words": [],
+    "cSpell.ignorePaths": [
+        "**/*.json", "**/*.c", "**/*.h", "**/*.cpp", "**/*.hpp", "**/Makefile"
+    ],
+    "java.project.sourcePaths": [
+        "src/units"
+    ]
+}
diff --git a/software/test-software/.vscode/tasks.json b/software/test-software/.vscode/tasks.json
new file mode 100644 (file)
index 0000000..74fb1c7
--- /dev/null
@@ -0,0 +1,23 @@
+{
+    // See https://go.microsoft.com/fwlink/?LinkId=733558
+    // for the documentation about the tasks.json format
+    "version": "2.0.0",
+    "tasks": [{
+        "label": "build",
+        "type": "shell",
+        "command": "make",
+        "problemMatcher":[
+            "$gcc"
+        ]
+    },{
+        "label": "clean",
+        "type": "shell",
+        "command": "make",
+        "args": [ "clean" ],
+    },{
+        "label": "flash",
+        "type": "shell",
+        "command": "make",
+        "args": [ "flash" ],
+    }]
+}
\ No newline at end of file
diff --git a/software/test-software/LICENSE b/software/test-software/LICENSE
new file mode 100644 (file)
index 0000000..c9565b9
--- /dev/null
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2024 Manfred Steiner (Manfred.Steiner@gmx.at)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/software/test-software/Makefile b/software/test-software/Makefile
new file mode 100644 (file)
index 0000000..f5eaedd
--- /dev/null
@@ -0,0 +1,22 @@
+.PHONY: all release clean
+
+SEP = --------------------------------------------------------
+
+all:
+       @printf "\n\e[1;36m$(SEP)\nNano-644\n$(SEP)\e[m\n"
+       -@make -C nano-644
+       @printf "\n\e[1;36m$(SEP)\n Nano-1284\n$(SEP)\e[m\n"
+       -@make -C nano-1284
+       @printf "\n\e[1;36m$(SEP)\n Arduino Nano\n$(SEP)\e[m\n"
+       -@make -C nano-328
+
+release: 
+       -@make -C nano-644 release
+       -@make -C nano-1284 release
+       -@make -C nano-328 release
+
+clean:
+       @make -C nano-644 clean
+       @make -C nano-1284 clean
+       @make -C nano-328 clean
+
diff --git a/software/test-software/README.md b/software/test-software/README.md
new file mode 100644 (file)
index 0000000..c8ef394
--- /dev/null
@@ -0,0 +1,22 @@
+# Test-Software for Nano-X-Base
+
+This software supports:  
+* Arduino Nano (ATmega328P @16MHz)
+* Nano-644 (ATmega644P @12MHz)
+* Nano-1284 (ATmega1284P @12MHz)
+
+For the Nano-X-Base hardware version:  
+* V1a
+* V2a
+
+## System requirements
+
+Makefiles and scripts are designed for running in a Linux system.
+It is tested on a Debian system.
+
+## Build
+
+* To build all files use `make`
+* To create release folder use `make release`
+* To clean this folder use `make clean`  
+  (release files are not removed)
diff --git a/software/test-software/create-release b/software/test-software/create-release
new file mode 100755 (executable)
index 0000000..225277f
--- /dev/null
@@ -0,0 +1,86 @@
+#!/bin/sh
+#set -x
+
+while [ ! -z "$1" ]; do
+   DIR="$1"
+   FILE="$2"
+
+   #echo "  creating release folder for file $FILE in directory $DIR ..."
+
+   if [ ! -d "$DIR" ]; then
+      printf "  [1;31mERROR: missing target directory $DIR\e[m\n"
+      exit 1
+   fi
+   
+   if [ -z "$FILE" ] || [ ! -f "$FILE" ]; then
+      printf "  [1;31mERROR: missing file $FILE\e[m\n"
+      exit 2
+   fi
+
+   shift
+   shift
+
+   DATE_OBJDUMP=$(avr-objdump -tT "$FILE" 2>&1 | grep MAIN_CPP_DATE)
+   if [ -z "$DATE_OBJDUMP" ]; then
+      printf "  [1;31mERROR ($FILE -> %DIR): symbol MAIN_CPP_DATE not found\e[m\n"
+      exit 3
+   fi
+   
+   TIME_OBJDUMP=$(avr-objdump -tT "$FILE" 2>&1 | grep MAIN_CPP_TIME)
+   if [ -z "$DATE_OBJDUMP" ]; then
+      printf "  [1;31mERROR ($FILE -> %DIR): symbol MAIN_CPP_TIME not found\e[m\n"
+      exit 4
+   fi
+   
+   DATE_OFFSET_HEX="0x$(echo "$DATE_OBJDUMP" | cut -d " " -f 1)"
+   TIME_OFFSET_HEX="0x$(echo "$TIME_OBJDUMP" | cut -d " " -f 1)"
+   DATE_OFFSET=$(( $(printf "%d" $DATE_OFFSET_HEX) + 148 ))
+   TIME_OFFSET=$(( $(printf "%d" $TIME_OFFSET_HEX) + 148 ))
+   
+   DATE_STRINGS=$(strings -a -t d "$FILE" | grep "$(printf "%7s" "$DATE_OFFSET")")
+   TIME_STRINGS=$(strings -a -t d "$FILE" | grep "$(printf "%7s" "$TIME_OFFSET")")
+   
+   DATE_MONTH_STRING=$(echo $DATE_STRINGS | cut -d' ' -f2)
+   case "$DATE_MONTH_STRING" in
+     Jan) DATE_MONTH="01";;
+     Feb) DATE_MONTH="02";;
+     Mar) DATE_MONTH="03";;
+     Apr) DATE_MONTH="04";;
+     May) DATE_MONTH="05";;
+     Jun) DATE_MONTH="06";;
+     Jul) DATE_MONTH="07";;
+     Aug) DATE_MONTH="08";;
+     Sep) DATE_MONTH="09";;
+     Oct) DATE_MONTH="10";;
+     Nov) DATE_MONTH="11";;
+     Dec) DATE_MONTH="12";;
+     *) printf "  [1;31mERROR ($FILE -> %DIR): invalidate date in file $FILE\e[m\n"; exit 5;;
+   esac
+   
+   DATE_DAY=$(echo $DATE_STRINGS | cut -d' ' -f3)
+   DATE_YEAR=$(echo $DATE_STRINGS | cut -d' ' -f4)
+   TIME_VALUE=$(echo $TIME_STRINGS | cut -d' ' -f2-)
+   TIME_HOUR=$(echo $TIME_VALUE | cut -d ':' -f1)
+   TIME_MINUTE=$(echo $TIME_VALUE | cut -d ':' -f2)
+   TIME_SECOND=$(echo $TIME_VALUE | cut -d ':' -f3)
+   
+   NAME=$(printf "v%4d-%02d-%02d_%02d%02d%02d" $DATE_YEAR $DATE_MONTH $DATE_DAY $TIME_HOUR $TIME_MINUTE $TIME_SECOND | egrep ^v[0-9]{4}-[0-9]{2}-[0-9]{1,2}_[0-9]{2}[0-9]{2}[0-9]{2}$)
+   if [ -z "$NAME" ]; then
+      printf "  [1;31mERROR ($FILE -> %DIR): cannot create release name\e[m\n"
+      exit 6
+   fi
+   FILENAME=$(echo "$FILE" | rev | cut -d"/" -f1 | rev)
+   if [ -d "$DIR/$NAME" ] && [ -f "$DIR/$NAME/$FILENAME" ]; then
+      echo "  OK: release already done ($FILE -> $DIR/$NAME)"
+   else
+      test -d "$DIR/$NAME" || mkdir "$DIR/$NAME"
+      cp -a "$FILE" "$DIR/$NAME/"
+      if [ $? = 0 ]; then
+         echo "  OK ($FILE -> $DIR/$NAME)"
+      else
+         printf "  [1;31mERROR ($FILE -> %DIR)\e[m\n"
+      fi
+   fi
+
+done
+
diff --git a/software/test-software/nano-1284/Makefile b/software/test-software/nano-1284/Makefile
new file mode 100644 (file)
index 0000000..6a29271
--- /dev/null
@@ -0,0 +1,128 @@
+.PHONY: all info flash flash0 flash1 flash2 picocom picocom0 picocom1 picocom2 release clean
+$(shell mkdir -p dist >/dev/null)
+$(shell mkdir -p build >/dev/null)
+$(shell mkdir -p sim >/dev/null)
+$(shell mkdir -p sim/build >/dev/null)
+$(shell mkdir -p release/sim >/dev/null)
+
+NAME=test-software_nano-1284p_12mhz
+SRC= $(wildcard src/*.c src/*.cpp src/*/*.c src/*/*.cpp) 
+HDR= $(wildcard src/*.h src/*.hpp src/*/*.h src/*/*.hpp) 
+OBJ_CPP = $(SRC:src/%.cpp=build/%.o)
+OBJ = $(OBJ_CPP:src/%.c=build/%.o)
+OBJ_SIM_CPP = $(SRC:src/%.cpp=sim/build/%.o)
+OBJ_SIM = $(OBJ_SIM_CPP:src/%.c=sim/build/%.o)
+
+DEVICE=atmega1284p
+AVRDUDE_DEVICE=m1284p
+
+CC= avr-g++
+CFLAGS= -Wall -mmcu=$(DEVICE) -Os -DF_CPU=12000000 -c
+LFLAGS= -Wall -mmcu=$(DEVICE) -Os -DF_CPU=12000000 -Wl,-u,vfprintf -lprintf_flt -lm
+
+CFLAGS_SIM= -Wall -mmcu=$(DEVICE) -Og -DF_CPU=12000000 -g -c -c
+LFLAGS_SIM= -Wall -mmcu=$(DEVICE) -Og -DF_CPU=12000000 -g -Wl,-u,vfprintf -lprintf_flt -lm
+
+# ------------------------------------------------
+
+all: dist/$(NAME).elf dist/$(NAME).s dist/$(NAME).hex sim/$(NAME).elf sim/$(NAME).s info
+
+isp-fuse-$(AVRDUDE_DEVICE):
+       avrdude -c usbasp -p $(AVRDUDE_DEVICE) -U lfuse:w:0xFF:m -U hfuse:w:0xD9:m -U efuse:w:0xFF:m -U lock:w:0xFF:m
+
+info:
+       @avr-size --mcu=$(DEVICE) --format=avr dist/$(NAME).elf
+
+.depend: $(SRC) $(HDR)
+       $(CC) -mmcu=$(DEVICE) -MM $(SRC) | sed --regexp-extended 's/^(.*\.o)\: src\/(.*)(\.cpp|\.c) (.*)/build\/\2\.o\: src\/\2\3 \4/g' > .depend
+
+-include .depend
+
+dist/$(NAME).elf: .depend $(OBJ)
+       $(CC) $(LFLAGS) -o $@ $(OBJ)
+
+dist/%.s: dist/%.elf
+       avr-objdump -d $< > $@
+
+dist/%.hex: dist/%.elf
+       avr-objcopy -O ihex $(HEX_FLASH_FLAGS) $< $@
+
+sim/$(NAME).elf: .depend $(OBJ_SIM)
+               $(CC) $(LFLAGS_SIM) -o $@ $(OBJ_SIM)
+
+# ensure that __DATE__ and __TIME__ macros are up to date
+build/main.o: src/main.cpp $(SRC) $(HDR)
+       @mkdir -p $(dir $@)
+       $(CC) $(CFLAGS) -o $@ $<
+
+build/%.o: src/%.c
+       @mkdir -p $(dir $@)
+       $(CC) $(CFLAGS) -o $@ $<
+
+build/%.o: src/%.cpp
+       @mkdir -p $(dir $@)
+       $(CC) $(CFLAGS) -o $@ $<
+
+sim/build/%.o: src/%.c
+       @mkdir -p $(dir $@)
+       $(CC) $(CFLAGS_SIM) -o $@ $<
+
+sim/build/%.o: src/%.cpp
+       @mkdir -p $(dir $@)
+       $(CC) $(CFLAGS_SIM) -o $@ $<
+
+sim/%.s: sim/%.elf
+       avr-objdump -d $< > $@
+
+simuc: sim/$(NAME).elf
+       simuc --board nano-1284 $<
+
+gdb: sim/$(NAME).elf
+       avr-gdb $<
+
+
+flash: dist/$(NAME).elf all
+       avrdude -c arduino -P /dev/ttyUSB0 -p $(AVRDUDE_DEVICE) -e -U flash:w:$<
+
+flash0: dist/$(NAME).elf all
+       avrdude -c arduino -P /dev/ttyUSB0 -p $(AVRDUDE_DEVICE) -e -U flash:w:$<
+
+flash1: dist/$(NAME).elf all
+       avrdude -c arduino -P /dev/ttyUSB1 -p $(AVRDUDE_DEVICE) -e -U flash:w:$<
+
+flash2: dist/$(NAME).elf all
+       avrdude -c arduino -P /dev/ttyUSB2 -p $(AVRDUDE_DEVICE) -e -U flash:w:$<
+
+
+picocom:
+       # picocom sends CR for ENTER -> convert cr (\r) to lf (\n)
+       picocom -b 115200 --omap crlf --raise-dtr /dev/ttyUSB0
+
+picocom0:
+       picocom -b 115200 --omap crlf --raise-dtr /dev/ttyUSB0
+
+picocom1:
+       picocom -b 115200 --omap crlf --raise-dtr /dev/ttyUSB1
+
+picocom2:
+       picocom -b 115200 --omap crlf --raise-dtr /dev/ttyUSB2
+
+
+isp-1284p:
+       avrdude -c usbasp -p $(AVRDUDE_DEVICE)
+
+isp-flash-1284p: dist/$(NAME).elf all
+       avrdude -c usbasp -p $(AVRDUDE_DEVICE) -e -U flash:w:$<
+
+flash-1284p: dist/$(NAME).elf all
+       avrdude -c arduino -p $(AVRDUDE_DEVICE) -P /dev/ttyUSB0 -b 115200 -e -U flash:w:$<
+
+release: dist/$(NAME).elf sim/$(NAME).elf
+       ../create-release release $(word 1, $^) release/sim $(word 2, $^)
+
+clean:
+       @rm -r dist
+       @rm -r build
+       @rm -r sim
+       @find . -type f -name ".depend" -exec rm {} \;
+       @echo "clean done"
diff --git a/software/test-software/nano-1284/release/v2024-10-28_141638/test-software_nano-1284p_12mhz.elf b/software/test-software/nano-1284/release/v2024-10-28_141638/test-software_nano-1284p_12mhz.elf
new file mode 100755 (executable)
index 0000000..03b31cc
Binary files /dev/null and b/software/test-software/nano-1284/release/v2024-10-28_141638/test-software_nano-1284p_12mhz.elf differ
diff --git a/software/test-software/nano-1284/src b/software/test-software/nano-1284/src
new file mode 120000 (symlink)
index 0000000..5cd551c
--- /dev/null
@@ -0,0 +1 @@
+../src
\ No newline at end of file
diff --git a/software/test-software/nano-328/Makefile b/software/test-software/nano-328/Makefile
new file mode 100644 (file)
index 0000000..ec86437
--- /dev/null
@@ -0,0 +1,129 @@
+.PHONY: all info flash flash0 flash1 flash2 picocom picocom0 picocom1 picocom2 release clean
+$(shell mkdir -p dist >/dev/null)
+$(shell mkdir -p build >/dev/null)
+$(shell mkdir -p sim >/dev/null)
+$(shell mkdir -p sim/build >/dev/null)
+$(shell mkdir -p release/sim >/dev/null)
+
+NAME=test-software_nano-328p_16mhz
+SRC= $(wildcard src/*.c src/*.cpp src/*/*.c src/*/*.cpp) 
+HDR= $(wildcard src/*.h src/*.hpp src/*/*.h src/*/*.hpp) 
+OBJ_CPP = $(SRC:src/%.cpp=build/%.o)
+OBJ = $(OBJ_CPP:src/%.c=build/%.o)
+OBJ_SIM_CPP = $(SRC:src/%.cpp=sim/build/%.o)
+OBJ_SIM = $(OBJ_SIM_CPP:src/%.c=sim/build/%.o)
+
+DEVICE=atmega328p
+AVRDUDE_DEVICE=m328p
+
+CC= avr-g++
+CFLAGS= -Wall -mmcu=$(DEVICE) -Os -DF_CPU=16000000 -c
+LFLAGS= -Wall -mmcu=$(DEVICE) -Os -DF_CPU=16000000 -Wl,-u,vfprintf -lprintf_flt -lm
+
+CFLAGS_SIM= -Wall -mmcu=$(DEVICE) -Og -DF_CPU=16000000 -g -c -c
+LFLAGS_SIM= -Wall -mmcu=$(DEVICE) -Og -DF_CPU=16000000 -g -Wl,-u,vfprintf -lprintf_flt -lm
+
+
+all: dist/$(NAME).elf dist/$(NAME).s dist/$(NAME).hex sim/$(NAME).elf sim/$(NAME).s info
+
+isp-fuse-$(AVRDUDE_DEVICE):
+       avrdude -c usbasp -p $(AVRDUDE_DEVICE) -U lfuse:w:0xFF:m -U hfuse:w:0xD9:m -U efuse:w:0xFF:m -U lock:w:0xFF:m
+
+# ------------------------------------------------
+
+info:
+       @avr-size --mcu=$(DEVICE) --format=avr dist/$(NAME).elf
+
+.depend: $(SRC) $(HDR)
+       $(CC) -mmcu=$(DEVICE) -MM $(SRC) | sed --regexp-extended 's/^(.*\.o)\: src\/(.*)(\.cpp|\.c) (.*)/build\/\2\.o\: src\/\2\3 \4/g' > .depend
+
+-include .depend
+
+dist/$(NAME).elf: .depend $(OBJ)
+       $(CC) $(LFLAGS) -o $@ $(OBJ)
+
+dist/%.s: dist/%.elf
+       avr-objdump -d $< > $@
+
+dist/%.hex: dist/%.elf
+       avr-objcopy -O ihex $(HEX_FLASH_FLAGS) $< $@
+
+sim/$(NAME).elf: .depend $(OBJ_SIM)
+               $(CC) $(LFLAGS_SIM) -o $@ $(OBJ_SIM)
+
+# ensure that __DATE__ and __TIME__ macros are up to date
+build/main.o: src/main.cpp $(SRC) $(HDR)
+       @mkdir -p $(dir $@)
+       $(CC) $(CFLAGS) -o $@ $<
+
+build/%.o: src/%.c
+       @mkdir -p $(dir $@)
+       $(CC) $(CFLAGS) -o $@ $<
+
+build/%.o: src/%.cpp
+       @mkdir -p $(dir $@)
+       $(CC) $(CFLAGS) -o $@ $<
+
+sim/build/%.o: src/%.c
+       @mkdir -p $(dir $@)
+       $(CC) $(CFLAGS_SIM) -o $@ $<
+
+sim/build/%.o: src/%.cpp
+       @mkdir -p $(dir $@)
+       $(CC) $(CFLAGS_SIM) -o $@ $<
+
+sim/%.s: sim/%.elf
+       avr-objdump -d $< > $@
+
+simuc: sim/$(NAME).elf
+       simuc --board nano-1284 $<
+
+gdb: sim/$(NAME).elf
+       avr-gdb $<
+
+
+flash: dist/$(NAME).elf all
+       avrdude -c arduino -P /dev/ttyUSB0 -p $(AVRDUDE_DEVICE) -e -U flash:w:$<
+
+flash0: dist/$(NAME).elf all
+       avrdude -c arduino -P /dev/ttyUSB0 -p $(AVRDUDE_DEVICE) -e -U flash:w:$<
+
+flash1: dist/$(NAME).elf all
+       avrdude -c arduino -P /dev/ttyUSB1 -p $(AVRDUDE_DEVICE) -e -U flash:w:$<
+
+flash2: dist/$(NAME).elf all
+       avrdude -c arduino -P /dev/ttyUSB2 -p $(AVRDUDE_DEVICE) -e -U flash:w:$<
+
+
+picocom:
+       # picocom sends CR for ENTER -> convert cr (\r) to lf (\n)
+       picocom -b 115200 --omap crlf --raise-dtr /dev/ttyUSB0
+
+picocom0:
+       picocom -b 115200 --omap crlf --raise-dtr /dev/ttyUSB0
+
+picocom1:
+       picocom -b 115200 --omap crlf --raise-dtr /dev/ttyUSB1
+
+picocom2:
+       picocom -b 115200 --omap crlf --raise-dtr /dev/ttyUSB2
+
+
+isp-1284p:
+       avrdude -c usbasp -p $(AVRDUDE_DEVICE)
+
+isp-flash-1284p: dist/$(NAME).elf all
+       avrdude -c usbasp -p $(AVRDUDE_DEVICE) -e -U flash:w:$<
+
+flash-1284p: dist/$(NAME).elf all
+       avrdude -c arduino -p $(AVRDUDE_DEVICE) -P /dev/ttyUSB0 -b 115200 -e -U flash:w:$<
+
+release: dist/$(NAME).elf sim/$(NAME).elf
+       ../create-release release $(word 1, $^) release/sim $(word 2, $^)
+
+clean:
+       @rm -r dist
+       @rm -r build
+       @rm -r sim
+       @find . -type f -name ".depend" -exec rm {} \;
+       @echo "clean done"
diff --git a/software/test-software/nano-328/release/v2024-10-28_141640/test-software_nano-328p_16mhz.elf b/software/test-software/nano-328/release/v2024-10-28_141640/test-software_nano-328p_16mhz.elf
new file mode 100755 (executable)
index 0000000..480cd66
Binary files /dev/null and b/software/test-software/nano-328/release/v2024-10-28_141640/test-software_nano-328p_16mhz.elf differ
diff --git a/software/test-software/nano-328/src b/software/test-software/nano-328/src
new file mode 120000 (symlink)
index 0000000..5cd551c
--- /dev/null
@@ -0,0 +1 @@
+../src
\ No newline at end of file
diff --git a/software/test-software/nano-644/Makefile b/software/test-software/nano-644/Makefile
new file mode 100644 (file)
index 0000000..72f0f07
--- /dev/null
@@ -0,0 +1,127 @@
+.PHONY: all info flash flash0 flash1 flash2 picocom picocom0 picocom1 picocom2 release clean
+$(shell mkdir -p dist >/dev/null)
+$(shell mkdir -p build >/dev/null)
+$(shell mkdir -p sim >/dev/null)
+$(shell mkdir -p sim/build >/dev/null)
+$(shell mkdir -p release/sim >/dev/null)
+
+NAME=test-software_nano-644_12mhz
+SRC= $(wildcard src/*.c src/*.cpp src/*/*.c src/*/*.cpp) 
+HDR= $(wildcard src/*.h src/*.hpp src/*/*.h src/*/*.hpp) 
+OBJ_CPP = $(SRC:src/%.cpp=build/%.o)
+OBJ = $(OBJ_CPP:src/%.c=build/%.o)
+OBJ_SIM_CPP = $(SRC:src/%.cpp=sim/build/%.o)
+OBJ_SIM = $(OBJ_SIM_CPP:src/%.c=sim/build/%.o)
+
+DEVICE=atmega644p
+AVRDUDE_DEVICE=m644p
+
+CC= avr-g++
+CFLAGS= -Wall -mmcu=$(DEVICE) -Os -DF_CPU=12000000 -c
+LFLAGS= -Wall -mmcu=$(DEVICE) -Os -DF_CPU=12000000 -Wl,-u,vfprintf -lprintf_flt -lm
+
+CFLAGS_SIM= -Wall -mmcu=$(DEVICE) -Og -DF_CPU=12000000 -g -c -c
+LFLAGS_SIM= -Wall -mmcu=$(DEVICE) -Og -DF_CPU=12000000 -g -Wl,-u,vfprintf -lprintf_flt -lm
+
+# ------------------------------------------------
+
+all: dist/$(NAME).elf dist/$(NAME).s dist/$(NAME).hex sim/$(NAME).elf sim/$(NAME).s info
+
+isp-fuse-$(AVRDUDE_DEVICE):
+       avrdude -c usbasp -p $(AVRDUDE_DEVICE) -U lfuse:w:0xFF:m -U hfuse:w:0xD9:m -U efuse:w:0xFF:m -U lock:w:0xFF:m
+info:
+       @avr-size --mcu=$(DEVICE) --format=avr dist/$(NAME).elf
+
+.depend: $(SRC) $(HDR)
+       $(CC) -mmcu=$(DEVICE) -MM $(SRC) | sed --regexp-extended 's/^(.*\.o)\: src\/(.*)(\.cpp|\.c) (.*)/build\/\2\.o\: src\/\2\3 \4/g' > .depend
+
+-include .depend
+
+dist/$(NAME).elf: .depend $(OBJ)
+       $(CC) $(LFLAGS) -o $@ $(OBJ)
+
+dist/%.s: dist/%.elf
+       avr-objdump -d $< > $@
+
+dist/%.hex: dist/%.elf
+       avr-objcopy -O ihex $(HEX_FLASH_FLAGS) $< $@
+
+sim/$(NAME).elf: .depend $(OBJ_SIM)
+               $(CC) $(LFLAGS_SIM) -o $@ $(OBJ_SIM)
+
+# ensure that __DATE__ and __TIME__ macros are up to date
+build/main.o: src/main.cpp $(SRC) $(HDR)
+       @mkdir -p $(dir $@)
+       $(CC) $(CFLAGS) -o $@ $<
+
+build/%.o: src/%.c
+       @mkdir -p $(dir $@)
+       $(CC) $(CFLAGS) -o $@ $<
+
+build/%.o: src/%.cpp
+       @mkdir -p $(dir $@)
+       $(CC) $(CFLAGS) -o $@ $<
+
+sim/build/%.o: src/%.c
+       @mkdir -p $(dir $@)
+       $(CC) $(CFLAGS_SIM) -o $@ $<
+
+sim/build/%.o: src/%.cpp
+       @mkdir -p $(dir $@)
+       $(CC) $(CFLAGS_SIM) -o $@ $<
+
+sim/%.s: sim/%.elf
+       avr-objdump -d $< > $@
+
+simuc: sim/$(NAME).elf
+       simuc --board nano-1284 $<
+
+gdb: sim/$(NAME).elf
+       avr-gdb $<
+
+
+flash: dist/$(NAME).elf all
+       avrdude -c arduino -P /dev/ttyUSB0 -p $(AVRDUDE_DEVICE) -e -U flash:w:$<
+
+flash0: dist/$(NAME).elf all
+       avrdude -c arduino -P /dev/ttyUSB0 -p $(AVRDUDE_DEVICE) -e -U flash:w:$<
+
+flash1: dist/$(NAME).elf all
+       avrdude -c arduino -P /dev/ttyUSB1 -p $(AVRDUDE_DEVICE) -e -U flash:w:$<
+
+flash2: dist/$(NAME).elf all
+       avrdude -c arduino -P /dev/ttyUSB2 -p $(AVRDUDE_DEVICE) -e -U flash:w:$<
+
+
+picocom:
+       # picocom sends CR for ENTER -> convert cr (\r) to lf (\n)
+       picocom -b 115200 --omap crlf --raise-dtr /dev/ttyUSB0
+
+picocom0:
+       picocom -b 115200 --omap crlf --raise-dtr /dev/ttyUSB0
+
+picocom1:
+       picocom -b 115200 --omap crlf --raise-dtr /dev/ttyUSB1
+
+picocom2:
+       picocom -b 115200 --omap crlf --raise-dtr /dev/ttyUSB2
+
+
+isp-1284p:
+       avrdude -c usbasp -p $(AVRDUDE_DEVICE)
+
+isp-flash-1284p: dist/$(NAME).elf all
+       avrdude -c usbasp -p $(AVRDUDE_DEVICE) -e -U flash:w:$<
+
+flash-1284p: dist/$(NAME).elf all
+       avrdude -c arduino -p $(AVRDUDE_DEVICE) -P /dev/ttyUSB0 -b 115200 -e -U flash:w:$<
+
+release: dist/$(NAME).elf sim/$(NAME).elf
+       ../create-release release $(word 1, $^) release/sim $(word 2, $^)
+
+clean:
+       @rm -r dist
+       @rm -r build
+       @rm -r sim
+       @find . -type f -name ".depend" -exec rm {} \;
+       @echo "clean done"
diff --git a/software/test-software/nano-644/release/v2024-10-28_141636/test-software_nano-644_12mhz.elf b/software/test-software/nano-644/release/v2024-10-28_141636/test-software_nano-644_12mhz.elf
new file mode 100755 (executable)
index 0000000..0aa1e2f
Binary files /dev/null and b/software/test-software/nano-644/release/v2024-10-28_141636/test-software_nano-644_12mhz.elf differ
diff --git a/software/test-software/nano-644/src b/software/test-software/nano-644/src
new file mode 120000 (symlink)
index 0000000..5cd551c
--- /dev/null
@@ -0,0 +1 @@
+../src
\ No newline at end of file
diff --git a/software/test-software/release/v2024-08-18_1103/test_2024-07-23_nano-644.elf b/software/test-software/release/v2024-08-18_1103/test_2024-07-23_nano-644.elf
new file mode 100755 (executable)
index 0000000..5605578
Binary files /dev/null and b/software/test-software/release/v2024-08-18_1103/test_2024-07-23_nano-644.elf differ
diff --git a/software/test-software/src/adafruit/bme280.cpp b/software/test-software/src/adafruit/bme280.cpp
new file mode 100644 (file)
index 0000000..1836c56
--- /dev/null
@@ -0,0 +1,512 @@
+#include "bme280.h"
+#include <util/delay.h>
+#include <stdio.h>
+
+Adafruit_BME280 theBME280;
+Adafruit_BME280_Temp bm280TempSensor;
+Adafruit_BME280_Pressure bm280PressureSensor;
+Adafruit_BME280_Humidity bm280HumiditySensor;
+
+Adafruit_BME280::Adafruit_BME280() {
+       static I2cMaster i2cDevice;
+       t_fine_adjust = 0;
+       temp_sensor = &bm280TempSensor;
+       pressure_sensor = &bm280PressureSensor;
+       humidity_sensor = &bm280HumiditySensor;
+       i2c_dev = &i2cDevice;
+}
+
+bool Adafruit_BME280::begin (uint8_t addr) {
+       if (!i2c_dev->begin(addr)) {
+               return false;
+       }
+       return init();
+}
+
+bool Adafruit_BME280::init() {
+       _sensorID = read8(BME280_REGISTER_CHIPID);
+       if (_sensorID != 0x60) {
+               return false;
+       }
+       write8(BME280_REGISTER_SOFTRESET, 0xB6);
+       _delay_ms(10); // wait for chip to wake up.
+
+       // if chip is still reading calibration, delay
+       while (isReadingCalibration()) {
+               _delay_ms(10);
+       }
+
+       readCoefficients(); // read trimming parameters, see DS 4.2.2
+       setSampling(); // use defaults
+       _delay_ms(100);
+
+       return true;
+}
+
+/*!
+ *   @brief  setup sensor with given parameters / settings
+ *
+ *   This is simply a overload to the normal begin()-function, so SPI users
+ *   don't get confused about the library requiring an address.
+ *   @param mode the power mode to use for the sensor
+ *   @param tempSampling the temp samping rate to use
+ *   @param pressSampling the pressure sampling rate to use
+ *   @param humSampling the humidity sampling rate to use
+ *   @param filter the filter mode to use
+ *   @param duration the standby duration to use
+ */
+void Adafruit_BME280::setSampling(sensor_mode mode,
+                                                                                        sensor_sampling tempSampling,
+                                                                                        sensor_sampling pressSampling,
+                                                                                        sensor_sampling humSampling,
+                                                                                        sensor_filter filter,
+                                                                                        standby_duration duration) {
+  _measReg.mode = mode;
+  _measReg.osrs_t = tempSampling;
+  _measReg.osrs_p = pressSampling;
+
+  _humReg.osrs_h = humSampling;
+  _configReg.filter = filter;
+  _configReg.t_sb = duration;
+  _configReg.spi3w_en = 0;
+
+  // making sure sensor is in sleep mode before setting configuration
+  // as it otherwise may be ignored
+  write8(BME280_REGISTER_CONTROL, MODE_SLEEP);
+
+  // you must make sure to also set REGISTER_CONTROL after setting the
+  // CONTROLHUMID register, otherwise the values won't be applied (see
+  // DS 5.4.3)
+  write8(BME280_REGISTER_CONTROLHUMID, _humReg.get());
+  write8(BME280_REGISTER_CONFIG, _configReg.get());
+  write8(BME280_REGISTER_CONTROL, _measReg.get());
+}
+
+/*!
+ *   @brief  Writes an 8 bit value over I2C or SPI
+ *   @param reg the register address to write to
+ *   @param value the value to write to the register
+ */
+void Adafruit_BME280::write8(uint8_t reg, uint8_t value) {
+  uint8_t buffer[2];
+  buffer[1] = value;
+  if (i2c_dev) {
+        buffer[0] = reg;
+        i2c_dev->write(buffer, 2);
+  }
+}
+
+/*!
+ *   @brief  Reads an 8 bit value over I2C or SPI
+ *   @param reg the register address to read from
+ *   @returns the data byte read from the device
+ */
+uint8_t Adafruit_BME280::read8(uint8_t reg) {
+  uint8_t buffer[1];
+  if (i2c_dev) {
+        buffer[0] = uint8_t(reg);
+        i2c_dev->write_then_read(buffer, 1, buffer, 1);
+  }
+  return buffer[0];
+}
+
+/*!
+ *   @brief  Reads a 16 bit value over I2C or SPI
+ *   @param reg the register address to read from
+ *   @returns the 16 bit data value read from the device
+ */
+uint16_t Adafruit_BME280::read16(uint8_t reg) {
+  uint8_t buffer[2];
+
+  if (i2c_dev) {
+        buffer[0] = uint8_t(reg);
+        i2c_dev->write_then_read(buffer, 1, buffer, 2);
+  }
+  return uint16_t(buffer[0]) << 8 | uint16_t(buffer[1]);
+}
+
+/*!
+ *   @brief  Reads a signed 16 bit little endian value over I2C or SPI
+ *   @param reg the register address to read from
+ *   @returns the 16 bit data value read from the device
+ */
+uint16_t Adafruit_BME280::read16_LE(uint8_t reg) {
+  uint16_t temp = read16(reg);
+  return (temp >> 8) | (temp << 8);
+}
+
+/*!
+ *   @brief  Reads a signed 16 bit value over I2C or SPI
+ *   @param reg the register address to read from
+ *   @returns the 16 bit data value read from the device
+ */
+int16_t Adafruit_BME280::readS16(uint8_t reg) { return (int16_t)read16(reg); }
+
+/*!
+ *   @brief  Reads a signed little endian 16 bit value over I2C or SPI
+ *   @param reg the register address to read from
+ *   @returns the 16 bit data value read from the device
+ */
+int16_t Adafruit_BME280::readS16_LE(uint8_t reg) {
+  return (int16_t)read16_LE(reg);
+}
+
+/*!
+ *   @brief  Reads a 24 bit value over I2C
+ *   @param reg the register address to read from
+ *   @returns the 24 bit data value read from the device
+ */
+uint32_t Adafruit_BME280::read24(uint8_t reg) {
+  uint8_t buffer[3];
+
+  if (i2c_dev) {
+        buffer[0] = uint8_t(reg);
+        i2c_dev->write_then_read(buffer, 1, buffer, 3);
+  }
+  return uint32_t(buffer[0]) << 16 | uint32_t(buffer[1]) << 8 |
+                       uint32_t(buffer[2]);
+}
+
+/*!
+ *  @brief  Take a new measurement (only possible in forced mode)
+        @returns true in case of success else false
+ */
+bool Adafruit_BME280::takeForcedMeasurement(void) {
+  bool return_value = false;
+  // If we are in forced mode, the BME sensor goes back to sleep after each
+  // measurement and we need to set it to forced mode once at this point, so
+  // it will take the next measurement and then return to sleep again.
+  // In normal mode simply does new measurements periodically.
+  if (_measReg.mode == MODE_FORCED) {
+        return_value = true;
+        // set to forced mode, i.e. "take next measurement"
+        write8(BME280_REGISTER_CONTROL, _measReg.get());
+        // Store current time to measure the timeout
+        uint32_t timeout_start = millis();
+        // wait until measurement has been completed, otherwise we would read the
+        // the values from the last measurement or the timeout occurred after 2 sec.
+        while (read8(BME280_REGISTER_STATUS) & 0x08) {
+               // In case of a timeout, stop the while loop
+               if ((millis() - timeout_start) > 2000) {
+                 return_value = false;
+                 break;
+               }
+               _delay_ms(1);
+        }
+  }
+  return return_value;
+}
+
+/*!
+ *   @brief  Reads the factory-set coefficients
+ */
+void Adafruit_BME280::readCoefficients(void) {
+  _bme280_calib.dig_T1 = read16_LE(BME280_REGISTER_DIG_T1);
+  _bme280_calib.dig_T2 = readS16_LE(BME280_REGISTER_DIG_T2);
+  _bme280_calib.dig_T3 = readS16_LE(BME280_REGISTER_DIG_T3);
+
+  _bme280_calib.dig_P1 = read16_LE(BME280_REGISTER_DIG_P1);
+  _bme280_calib.dig_P2 = readS16_LE(BME280_REGISTER_DIG_P2);
+  _bme280_calib.dig_P3 = readS16_LE(BME280_REGISTER_DIG_P3);
+  _bme280_calib.dig_P4 = readS16_LE(BME280_REGISTER_DIG_P4);
+  _bme280_calib.dig_P5 = readS16_LE(BME280_REGISTER_DIG_P5);
+  _bme280_calib.dig_P6 = readS16_LE(BME280_REGISTER_DIG_P6);
+  _bme280_calib.dig_P7 = readS16_LE(BME280_REGISTER_DIG_P7);
+  _bme280_calib.dig_P8 = readS16_LE(BME280_REGISTER_DIG_P8);
+  _bme280_calib.dig_P9 = readS16_LE(BME280_REGISTER_DIG_P9);
+
+  _bme280_calib.dig_H1 = read8(BME280_REGISTER_DIG_H1);
+  _bme280_calib.dig_H2 = readS16_LE(BME280_REGISTER_DIG_H2);
+  _bme280_calib.dig_H3 = read8(BME280_REGISTER_DIG_H3);
+  _bme280_calib.dig_H4 = ((int8_t)read8(BME280_REGISTER_DIG_H4) << 4) |
+                                                                (read8(BME280_REGISTER_DIG_H4 + 1) & 0xF);
+  _bme280_calib.dig_H5 = ((int8_t)read8(BME280_REGISTER_DIG_H5 + 1) << 4) |
+                                                                (read8(BME280_REGISTER_DIG_H5) >> 4);
+  _bme280_calib.dig_H6 = (int8_t)read8(BME280_REGISTER_DIG_H6);
+}
+
+/*!
+ *   @brief return true if chip is busy reading cal data
+ *   @returns true if reading calibration, false otherwise
+ */
+bool Adafruit_BME280::isReadingCalibration(void) {
+  uint8_t const rStatus = read8(BME280_REGISTER_STATUS);
+
+  return (rStatus & (1 << 0)) != 0;
+}
+
+/*!
+ *   @brief  Returns the temperature from the sensor
+ *   @returns the temperature read from the device
+ */
+float Adafruit_BME280::readTemperature(void) {
+  int32_t var1, var2;
+
+  int32_t adc_T = read24(BME280_REGISTER_TEMPDATA);
+  if (adc_T == 0x800000) // value in case temp measurement was disabled
+        return NAN;
+  adc_T >>= 4;
+
+  var1 = (int32_t)((adc_T / 8) - ((int32_t)_bme280_calib.dig_T1 * 2));
+  var1 = (var1 * ((int32_t)_bme280_calib.dig_T2)) / 2048;
+  var2 = (int32_t)((adc_T / 16) - ((int32_t)_bme280_calib.dig_T1));
+  var2 = (((var2 * var2) / 4096) * ((int32_t)_bme280_calib.dig_T3)) / 16384;
+
+  t_fine = var1 + var2 + t_fine_adjust;
+
+  int32_t T = (t_fine * 5 + 128) / 256;
+
+  return (float)T / 100;
+}
+
+/*!
+ *   @brief  Returns the pressure from the sensor
+ *   @returns the pressure value (in Pascal) read from the device
+ */
+float Adafruit_BME280::readPressure(void) {
+  int64_t var1, var2, var3, var4;
+
+  readTemperature(); // must be done first to get t_fine
+
+  int32_t adc_P = read24(BME280_REGISTER_PRESSUREDATA);
+  if (adc_P == 0x800000) // value in case pressure measurement was disabled
+        return NAN;
+  adc_P >>= 4;
+
+  var1 = ((int64_t)t_fine) - 128000;
+  var2 = var1 * var1 * (int64_t)_bme280_calib.dig_P6;
+  var2 = var2 + ((var1 * (int64_t)_bme280_calib.dig_P5) * 131072);
+  var2 = var2 + (((int64_t)_bme280_calib.dig_P4) * 34359738368);
+  var1 = ((var1 * var1 * (int64_t)_bme280_calib.dig_P3) / 256) +
+                       ((var1 * ((int64_t)_bme280_calib.dig_P2) * 4096));
+  var3 = ((int64_t)1) * 140737488355328;
+  var1 = (var3 + var1) * ((int64_t)_bme280_calib.dig_P1) / 8589934592;
+
+  if (var1 == 0) {
+        return 0; // avoid exception caused by division by zero
+  }
+
+  var4 = 1048576 - adc_P;
+  var4 = (((var4 * 2147483648UL) - var2) * 3125) / var1;
+  var1 = (((int64_t)_bme280_calib.dig_P9) * (var4 / 8192) * (var4 / 8192)) /
+                       33554432;
+  var2 = (((int64_t)_bme280_calib.dig_P8) * var4) / 524288;
+  var4 = ((var4 + var1 + var2) / 256) + (((int64_t)_bme280_calib.dig_P7) * 16);
+
+  float P = var4 / 256.0;
+
+  return P;
+}
+
+/*!
+ *  @brief  Returns the humidity from the sensor
+ *  @returns the humidity value read from the device
+ */
+float Adafruit_BME280::readHumidity(void) {
+  int32_t var1, var2, var3, var4, var5;
+
+  readTemperature(); // must be done first to get t_fine
+
+  int32_t adc_H = read16(BME280_REGISTER_HUMIDDATA);
+  if (adc_H == 0x8000) // value in case humidity measurement was disabled
+        return NAN;
+
+  var1 = t_fine - ((int32_t)76800);
+  var2 = (int32_t)(adc_H * 16384);
+  var3 = (int32_t)(((int32_t)_bme280_calib.dig_H4) * 1048576);
+  var4 = ((int32_t)_bme280_calib.dig_H5) * var1;
+  var5 = (((var2 - var3) - var4) + (int32_t)16384) / 32768;
+  var2 = (var1 * ((int32_t)_bme280_calib.dig_H6)) / 1024;
+  var3 = (var1 * ((int32_t)_bme280_calib.dig_H3)) / 2048;
+  var4 = ((var2 * (var3 + (int32_t)32768)) / 1024) + (int32_t)2097152;
+  var2 = ((var4 * ((int32_t)_bme280_calib.dig_H2)) + 8192) / 16384;
+  var3 = var5 * var2;
+  var4 = ((var3 / 32768) * (var3 / 32768)) / 128;
+  var5 = var3 - ((var4 * ((int32_t)_bme280_calib.dig_H1)) / 16);
+  var5 = (var5 < 0 ? 0 : var5);
+  var5 = (var5 > 419430400 ? 419430400 : var5);
+  uint32_t H = (uint32_t)(var5 / 4096);
+
+  return (float)H / 1024.0;
+}
+
+/*!
+ *   Calculates the altitude (in meters) from the specified atmospheric
+ *   pressure (in hPa), and sea-level pressure (in hPa).
+ *   @param  seaLevel      Sea-level pressure in hPa
+ *   @returns the altitude value read from the device
+ */
+float Adafruit_BME280::readAltitude(float seaLevel) {
+  // Equation taken from BMP180 datasheet (page 16):
+  //  http://www.adafruit.com/datasheets/BST-BMP180-DS000-09.pdf
+
+  // Note that using the equation from wikipedia can give bad results
+  // at high altitude. See this thread for more information:
+  //  http://forums.adafruit.com/viewtopic.php?f=22&t=58064
+
+  float atmospheric = readPressure() / 100.0F;
+  return 44330.0 * (1.0 - pow(atmospheric / seaLevel, 0.1903));
+}
+
+/*!
+ *   Calculates the pressure at sea level (in hPa) from the specified
+ * altitude (in meters), and atmospheric pressure (in hPa).
+ *   @param  altitude      Altitude in meters
+ *   @param  atmospheric   Atmospheric pressure in hPa
+ *   @returns the pressure at sea level (in hPa) from the specified altitude
+ */
+float Adafruit_BME280::seaLevelForAltitude(float altitude, float atmospheric) {
+  // Equation taken from BMP180 datasheet (page 17):
+  //  http://www.adafruit.com/datasheets/BST-BMP180-DS000-09.pdf
+
+  // Note that using the equation from wikipedia can give bad results
+  // at high altitude. See this thread for more information:
+  //  http://forums.adafruit.com/viewtopic.php?f=22&t=58064
+
+  return atmospheric / pow(1.0 - (altitude / 44330.0), 5.255);
+}
+
+/*!
+ *   Returns Sensor ID found by init() for diagnostics
+ *   @returns Sensor ID 0x60 for BME280, 0x56, 0x57, 0x58 BMP280
+ */
+uint32_t Adafruit_BME280::sensorID(void) { return _sensorID; }
+
+/*!
+ *   Returns the current temperature compensation value in degrees Celsius
+ *   @returns the current temperature compensation value in degrees Celsius
+ */
+float Adafruit_BME280::getTemperatureCompensation(void) {
+  return float((t_fine_adjust * 5) >> 8) / 100.0;
+};
+
+/*!
+ *  Sets a value to be added to each temperature reading. This adjusted
+ *  temperature is used in pressure and humidity readings.
+ *  @param  adjustment  Value to be added to each temperature reading in Celsius
+ */
+void Adafruit_BME280::setTemperatureCompensation(float adjustment) {
+  // convert the value in C into and adjustment to t_fine
+  t_fine_adjust = ((int32_t(adjustment * 100) << 8)) / 5;
+};
+
+
+/**************************************************************************/
+/*!
+        @brief  Gets the sensor_t data for the BME280's temperature sensor
+*/
+/**************************************************************************/
+void Adafruit_BME280_Temp::getSensor(sensor_t *sensor) {
+  /* Clear the sensor_t object */
+  memset(sensor, 0, sizeof(sensor_t));
+
+  /* Insert the sensor name in the fixed length char array */
+  strncpy(sensor->name, "BME280", sizeof(sensor->name) - 1);
+  sensor->name[sizeof(sensor->name) - 1] = 0;
+  sensor->version = 1;
+  sensor->sensor_id = _sensorID;
+  sensor->type = SENSOR_TYPE_AMBIENT_TEMPERATURE;
+  sensor->min_delay = 0;
+  sensor->min_value = -40.0; /* Temperature range -40 ~ +85 C  */
+  sensor->max_value = +85.0;
+  sensor->resolution = 0.01; /*  0.01 C */
+}
+
+/**************************************************************************/
+/*!
+        @brief  Gets the temperature as a standard sensor event
+        @param  event Sensor event object that will be populated
+        @returns True
+*/
+/**************************************************************************/
+bool Adafruit_BME280_Temp::getEvent(sensors_event_t *event) {
+  /* Clear the event */
+  memset(event, 0, sizeof(sensors_event_t));
+
+  event->version = sizeof(sensors_event_t);
+  event->sensor_id = _sensorID;
+  event->type = SENSOR_TYPE_AMBIENT_TEMPERATURE;
+  event->timestamp = millis();
+  event->temperature = theBME280.readTemperature();
+  return true;
+}
+
+/**************************************************************************/
+/*!
+        @brief  Gets the sensor_t data for the BME280's pressure sensor
+*/
+/**************************************************************************/
+void Adafruit_BME280_Pressure::getSensor(sensor_t *sensor) {
+  /* Clear the sensor_t object */
+  memset(sensor, 0, sizeof(sensor_t));
+
+  /* Insert the sensor name in the fixed length char array */
+  strncpy(sensor->name, "BME280", sizeof(sensor->name) - 1);
+  sensor->name[sizeof(sensor->name) - 1] = 0;
+  sensor->version = 1;
+  sensor->sensor_id = _sensorID;
+  sensor->type = SENSOR_TYPE_PRESSURE;
+  sensor->min_delay = 0;
+  sensor->min_value = 300.0; /* 300 ~ 1100 hPa  */
+  sensor->max_value = 1100.0;
+  sensor->resolution = 0.012; /* 0.12 hPa relative */
+}
+
+/**************************************************************************/
+/*!
+        @brief  Gets the pressure as a standard sensor event
+        @param  event Sensor event object that will be populated
+        @returns True
+*/
+/**************************************************************************/
+bool Adafruit_BME280_Pressure::getEvent(sensors_event_t *event) {
+  /* Clear the event */
+  memset(event, 0, sizeof(sensors_event_t));
+
+  event->version = sizeof(sensors_event_t);
+  event->sensor_id = _sensorID;
+  event->type = SENSOR_TYPE_PRESSURE;
+  event->timestamp = millis();
+  event->pressure = theBME280.readPressure() / 100; // convert Pa to hPa
+  return true;
+}
+
+/**************************************************************************/
+/*!
+        @brief  Gets the sensor_t data for the BME280's humidity sensor
+*/
+/**************************************************************************/
+void Adafruit_BME280_Humidity::getSensor(sensor_t *sensor) {
+  /* Clear the sensor_t object */
+  memset(sensor, 0, sizeof(sensor_t));
+
+  /* Insert the sensor name in the fixed length char array */
+  strncpy(sensor->name, "BME280", sizeof(sensor->name) - 1);
+  sensor->name[sizeof(sensor->name) - 1] = 0;
+  sensor->version = 1;
+  sensor->sensor_id = _sensorID;
+  sensor->type = SENSOR_TYPE_RELATIVE_HUMIDITY;
+  sensor->min_delay = 0;
+  sensor->min_value = 0;
+  sensor->max_value = 100; /* 0 - 100 %  */
+  sensor->resolution = 3;  /* 3% accuracy */
+}
+
+/**************************************************************************/
+/*!
+        @brief  Gets the humidity as a standard sensor event
+        @param  event Sensor event object that will be populated
+        @returns True
+*/
+/**************************************************************************/
+bool Adafruit_BME280_Humidity::getEvent(sensors_event_t *event) {
+  /* Clear the event */
+  memset(event, 0, sizeof(sensors_event_t));
+
+  event->version = sizeof(sensors_event_t);
+  event->sensor_id = _sensorID;
+  event->type = SENSOR_TYPE_RELATIVE_HUMIDITY;
+  event->timestamp = millis();
+  event->relative_humidity = theBME280.readHumidity();
+  return true;
+}
diff --git a/software/test-software/src/adafruit/bme280.h b/software/test-software/src/adafruit/bme280.h
new file mode 100644 (file)
index 0000000..aa5aa72
--- /dev/null
@@ -0,0 +1,373 @@
+// https://github.com/adafruit/Adafruit_BME280_Library
+
+/*!
+ * @file Adafruit_BME280.h
+ *
+ * Designed specifically to work with the Adafruit BME280 Breakout
+ * ----> http://www.adafruit.com/products/2650
+ *
+ * These sensors use I2C or SPI to communicate, 2 or 4 pins are required
+ * to interface.
+ *
+ * Adafruit invests time and resources providing this open source code,
+ * please support Adafruit and open-source hardware by purchasing
+ * products from Adafruit!
+ *
+ * Written by Kevin "KTOWN" Townsend for Adafruit Industries.
+ *
+ * BSD license, all text here must be included in any redistribution.
+ * See the LICENSE file for details.
+ *
+ */
+
+#ifndef __BME280_H__
+#define __BME280_H__
+
+// #include "Arduino.h"
+
+// #include <Adafruit_I2CDevice.h>
+// #include <Adafruit_SPIDevice.h>
+// #include <Adafruit_Sensor.h>
+
+
+#include "../i2cmaster.hpp"
+#include "../main.hpp"
+// #define byte uint8_t
+
+
+
+#include <stdint.h>
+#include <string.h>
+#include "sensor.h"
+
+/*!
+ *  @brief  default I2C address
+ */
+#define BME280_ADDRESS (0x77)           // Primary I2C Address
+                                        /*!
+                                         *  @brief  alternate I2C address
+                                         */
+#define BME280_ADDRESS_ALTERNATE (0x76) // Alternate Address
+
+/*!
+ *  @brief Register addresses
+ */
+enum {
+  BME280_REGISTER_DIG_T1 = 0x88,
+  BME280_REGISTER_DIG_T2 = 0x8A,
+  BME280_REGISTER_DIG_T3 = 0x8C,
+
+  BME280_REGISTER_DIG_P1 = 0x8E,
+  BME280_REGISTER_DIG_P2 = 0x90,
+  BME280_REGISTER_DIG_P3 = 0x92,
+  BME280_REGISTER_DIG_P4 = 0x94,
+  BME280_REGISTER_DIG_P5 = 0x96,
+  BME280_REGISTER_DIG_P6 = 0x98,
+  BME280_REGISTER_DIG_P7 = 0x9A,
+  BME280_REGISTER_DIG_P8 = 0x9C,
+  BME280_REGISTER_DIG_P9 = 0x9E,
+
+  BME280_REGISTER_DIG_H1 = 0xA1,
+  BME280_REGISTER_DIG_H2 = 0xE1,
+  BME280_REGISTER_DIG_H3 = 0xE3,
+  BME280_REGISTER_DIG_H4 = 0xE4,
+  BME280_REGISTER_DIG_H5 = 0xE5,
+  BME280_REGISTER_DIG_H6 = 0xE7,
+
+  BME280_REGISTER_CHIPID = 0xD0,
+  BME280_REGISTER_VERSION = 0xD1,
+  BME280_REGISTER_SOFTRESET = 0xE0,
+
+  BME280_REGISTER_CAL26 = 0xE1, // R calibration stored in 0xE1-0xF0
+
+  BME280_REGISTER_CONTROLHUMID = 0xF2,
+  BME280_REGISTER_STATUS = 0XF3,
+  BME280_REGISTER_CONTROL = 0xF4,
+  BME280_REGISTER_CONFIG = 0xF5,
+  BME280_REGISTER_PRESSUREDATA = 0xF7,
+  BME280_REGISTER_TEMPDATA = 0xFA,
+  BME280_REGISTER_HUMIDDATA = 0xFD
+};
+
+/**************************************************************************/
+/*!
+    @brief  calibration data
+*/
+/**************************************************************************/
+typedef struct {
+  uint16_t dig_T1; ///< temperature compensation value
+  int16_t dig_T2;  ///< temperature compensation value
+  int16_t dig_T3;  ///< temperature compensation value
+
+  uint16_t dig_P1; ///< pressure compensation value
+  int16_t dig_P2;  ///< pressure compensation value
+  int16_t dig_P3;  ///< pressure compensation value
+  int16_t dig_P4;  ///< pressure compensation value
+  int16_t dig_P5;  ///< pressure compensation value
+  int16_t dig_P6;  ///< pressure compensation value
+  int16_t dig_P7;  ///< pressure compensation value
+  int16_t dig_P8;  ///< pressure compensation value
+  int16_t dig_P9;  ///< pressure compensation value
+
+  uint8_t dig_H1; ///< humidity compensation value
+  int16_t dig_H2; ///< humidity compensation value
+  uint8_t dig_H3; ///< humidity compensation value
+  int16_t dig_H4; ///< humidity compensation value
+  int16_t dig_H5; ///< humidity compensation value
+  int8_t dig_H6;  ///< humidity compensation value
+} bme280_calib_data;
+/*=========================================================================*/
+
+class Adafruit_BME280;
+
+/** Adafruit Unified Sensor interface for temperature component of BME280 */
+class Adafruit_BME280_Temp : public Adafruit_Sensor {
+public:
+  /** @brief Create an Adafruit_Sensor compatible object for the temp sensor
+      @param parent A pointer to the BME280 class */
+  Adafruit_BME280_Temp() { _sensorID = 280; }
+  bool getEvent(sensors_event_t *);
+  void getSensor(sensor_t *);
+
+private:
+  int _sensorID;
+};
+
+/** Adafruit Unified Sensor interface for pressure component of BME280 */
+class Adafruit_BME280_Pressure : public Adafruit_Sensor {
+public:
+  /** @brief Create an Adafruit_Sensor compatible object for the pressure sensor
+      @param parent A pointer to the BME280 class */
+  Adafruit_BME280_Pressure() { _sensorID = 280; }
+  bool getEvent(sensors_event_t *);
+  void getSensor(sensor_t *);
+
+private:
+  int _sensorID;
+};
+
+/** Adafruit Unified Sensor interface for humidity component of BME280 */
+class Adafruit_BME280_Humidity : public Adafruit_Sensor {
+public:
+  /** @brief Create an Adafruit_Sensor compatible object for the humidity sensor
+      @param parent A pointer to the BME280 class */
+  Adafruit_BME280_Humidity() { _sensorID = 280;}
+  bool getEvent(sensors_event_t *);
+  void getSensor(sensor_t *);
+
+private:
+  int _sensorID;
+};
+
+/**************************************************************************/
+/*!
+    @brief  Class that stores state and functions for interacting with BME280 IC
+*/
+/**************************************************************************/
+class Adafruit_BME280 {
+public:
+  /**************************************************************************/
+  /*!
+      @brief  sampling rates
+  */
+  /**************************************************************************/
+  enum sensor_sampling {
+    SAMPLING_NONE = 0b000,
+    SAMPLING_X1 = 0b001,
+    SAMPLING_X2 = 0b010,
+    SAMPLING_X4 = 0b011,
+    SAMPLING_X8 = 0b100,
+    SAMPLING_X16 = 0b101
+  };
+
+  /**************************************************************************/
+  /*!
+      @brief  power modes
+  */
+  /**************************************************************************/
+  enum sensor_mode {
+    MODE_SLEEP = 0b00,
+    MODE_FORCED = 0b01,
+    MODE_NORMAL = 0b11
+  };
+
+  /**************************************************************************/
+  /*!
+      @brief  filter values
+  */
+  /**************************************************************************/
+  enum sensor_filter {
+    FILTER_OFF = 0b000,
+    FILTER_X2 = 0b001,
+    FILTER_X4 = 0b010,
+    FILTER_X8 = 0b011,
+    FILTER_X16 = 0b100
+  };
+
+  /**************************************************************************/
+  /*!
+      @brief  standby duration in ms
+  */
+  /**************************************************************************/
+  enum standby_duration {
+    STANDBY_MS_0_5 = 0b000,
+    STANDBY_MS_10 = 0b110,
+    STANDBY_MS_20 = 0b111,
+    STANDBY_MS_62_5 = 0b001,
+    STANDBY_MS_125 = 0b010,
+    STANDBY_MS_250 = 0b011,
+    STANDBY_MS_500 = 0b100,
+    STANDBY_MS_1000 = 0b101
+  };
+
+  // constructors
+  Adafruit_BME280();
+  
+  bool begin(uint8_t addr = BME280_ADDRESS);
+  bool init();
+
+  void setSampling(sensor_mode mode = MODE_NORMAL,
+                   sensor_sampling tempSampling = SAMPLING_X16,
+                   sensor_sampling pressSampling = SAMPLING_X16,
+                   sensor_sampling humSampling = SAMPLING_X16,
+                   sensor_filter filter = FILTER_OFF,
+                   standby_duration duration = STANDBY_MS_0_5);
+
+  bool takeForcedMeasurement(void);
+  float readTemperature(void);
+  float readPressure(void);
+  float readHumidity(void);
+
+  float readAltitude(float seaLevel);
+  float seaLevelForAltitude(float altitude, float pressure);
+  uint32_t sensorID(void);
+
+  float getTemperatureCompensation(void);
+  void setTemperatureCompensation(float);
+
+protected:
+  I2cMaster *i2c_dev = NULL; ///< Pointer to I2C bus interface
+  // Adafruit_SPIDevice *spi_dev = NULL; ///< Pointer to SPI bus interface
+
+  Adafruit_BME280_Temp *temp_sensor;
+  Adafruit_BME280_Pressure *pressure_sensor;
+  Adafruit_BME280_Humidity *humidity_sensor;
+
+  void readCoefficients(void);
+  bool isReadingCalibration(void);
+
+  void write8(uint8_t reg, uint8_t value);
+  uint8_t read8(uint8_t reg);
+  uint16_t read16(uint8_t reg);
+  uint32_t read24(uint8_t reg);
+  int16_t readS16(uint8_t reg);
+  uint16_t read16_LE(uint8_t reg); // little endian
+  int16_t readS16_LE(uint8_t reg); // little endian
+
+  uint8_t _i2caddr;  //!< I2C addr for the TwoWire interface
+  int32_t _sensorID; //!< ID of the BME Sensor
+  int32_t t_fine; //!< temperature with high resolution, stored as an attribute
+                  //!< as this is used for temperature compensation reading
+                  //!< humidity and pressure
+
+  int32_t t_fine_adjust; //!< add to compensate temp readings and in turn
+                         //!< to pressure and humidity readings
+
+  bme280_calib_data _bme280_calib; //!< here calibration data is stored
+
+  /**************************************************************************/
+  /*!
+      @brief  config register
+  */
+  /**************************************************************************/
+  struct config {
+    // inactive duration (standby time) in normal mode
+    // 000 = 0.5 ms
+    // 001 = 62.5 ms
+    // 010 = 125 ms
+    // 011 = 250 ms
+    // 100 = 500 ms
+    // 101 = 1000 ms
+    // 110 = 10 ms
+    // 111 = 20 ms
+    unsigned int t_sb : 3; ///< inactive duration (standby time) in normal mode
+
+    // filter settings
+    // 000 = filter off
+    // 001 = 2x filter
+    // 010 = 4x filter
+    // 011 = 8x filter
+    // 100 and above = 16x filter
+    unsigned int filter : 3; ///< filter settings
+
+    // unused - don't set
+    unsigned int none : 1;     ///< unused - don't set
+    unsigned int spi3w_en : 1; ///< unused - don't set
+
+    /// @return combined config register
+    unsigned int get() { return (t_sb << 5) | (filter << 2) | spi3w_en; }
+  };
+  config _configReg; //!< config register object
+
+  /**************************************************************************/
+  /*!
+      @brief  ctrl_meas register
+  */
+  /**************************************************************************/
+  struct ctrl_meas {
+    // temperature oversampling
+    // 000 = skipped
+    // 001 = x1
+    // 010 = x2
+    // 011 = x4
+    // 100 = x8
+    // 101 and above = x16
+    unsigned int osrs_t : 3; ///< temperature oversampling
+
+    // pressure oversampling
+    // 000 = skipped
+    // 001 = x1
+    // 010 = x2
+    // 011 = x4
+    // 100 = x8
+    // 101 and above = x16
+    unsigned int osrs_p : 3; ///< pressure oversampling
+
+    // device mode
+    // 00       = sleep
+    // 01 or 10 = forced
+    // 11       = normal
+    unsigned int mode : 2; ///< device mode
+
+    /// @return combined ctrl register
+    unsigned int get() { return (osrs_t << 5) | (osrs_p << 2) | mode; }
+  };
+  ctrl_meas _measReg; //!< measurement register object
+
+  /**************************************************************************/
+  /*!
+      @brief  ctrl_hum register
+  */
+  /**************************************************************************/
+  struct ctrl_hum {
+    /// unused - don't set
+    unsigned int none : 5;
+
+    // pressure oversampling
+    // 000 = skipped
+    // 001 = x1
+    // 010 = x2
+    // 011 = x4
+    // 100 = x8
+    // 101 and above = x16
+    unsigned int osrs_h : 3; ///< pressure oversampling
+
+    /// @return combined ctrl hum register
+    unsigned int get() { return (osrs_h); }
+  };
+  ctrl_hum _humReg; //!< hum register object
+};
+
+extern Adafruit_BME280 theBME280;
+
+#endif
diff --git a/software/test-software/src/adafruit/ens160.cpp b/software/test-software/src/adafruit/ens160.cpp
new file mode 100644 (file)
index 0000000..29e3704
--- /dev/null
@@ -0,0 +1,374 @@
+/*
+  ScioSense_ENS160.h - Library for the ENS160 sensor with I2C interface from ScioSense
+  2023 Mar 23  v6      Christoph Friese        Bugfix measurement routine, prepare next release
+  2021 Nov 25   v5     Martin Herold           Custom mode timing fixed
+  2021 Feb 04  v4      Giuseppe de Pinto       Custom mode fixed
+  2020 Apr 06  v3      Christoph Friese        Changed nomenclature to ScioSense as product shifted from ams
+  2020 Feb 15  v2      Giuseppe Pasetti        Corrected firmware flash option
+  2019 May 05  v1      Christoph Friese        Created
+  based on application note "ENS160 Software Integration.pdf" rev 0.01
+*/
+
+#include "ens160.h"
+#include "math.h"
+#include <util/delay.h>
+#include <stdio.h>
+#include <avr/io.h>
+
+ScioSense_ENS160::ScioSense_ENS160 () {
+   _revENS16x = 0;
+
+   //Isotherm, HP0 252°C / HP1 350°C / HP2 250°C / HP3 324°C / measure every 1008ms
+       _seq_steps[0][0] = 0x7c;
+   _seq_steps[0][1] = 0x0a;
+   _seq_steps[0][2] = 0x7e;
+   _seq_steps[0][3] = 0xaf;
+   _seq_steps[0][4] = 0xaf;
+   _seq_steps[0][5] = 0xa2;
+   _seq_steps[0][6] = 0x00;
+   _seq_steps[0][7] = 0x80;
+}
+
+bool ScioSense_ENS160::begin () {
+   i2cDevice.begin(ENS160_I2CADDR_1);
+       _delay_ms(ENS160_BOOTING);
+       if (reset()) {
+      if (checkPartID()) {
+         if (setMode(ENS160_OPMODE_IDLE)) {
+            if (clearCommand()) {
+               if (getFirmware()) {
+                  return true;
+               }
+            }
+         }
+      }
+   }
+   return false;
+       
+}
+
+bool ScioSense_ENS160::write8 (uint8_t reg, uint8_t value) {
+   uint8_t buffer[2];
+   buffer[1] = value;
+   buffer[0] = reg;
+   return i2cDevice.write(buffer, 2);
+}
+
+bool ScioSense_ENS160::read8 (uint8_t reg, uint8_t *value) {
+   uint8_t buffer[1];
+   buffer[0] = uint8_t(reg);
+   return i2cDevice.write_then_read(buffer, 1, value, 1);
+}
+
+bool ScioSense_ENS160::read16 (uint8_t reg, uint16_t *value) {
+   uint8_t buffer[1];
+   buffer[0] = uint8_t(reg);
+   return i2cDevice.write_then_read(buffer, 1, (uint8_t *)value, 2);
+}
+
+bool ScioSense_ENS160::read16LE (uint8_t reg, uint16_t *value) {
+  uint16_t tmp;
+  if (read16(reg, &tmp)) {
+      *value = ((tmp & 0xff) << 8) | (tmp >> 8);
+      return true;
+  }
+  return false;
+}
+
+bool ScioSense_ENS160::readBytes (uint8_t reg, uint8_t *bytes, uint8_t len) {
+   uint8_t buffer[1];
+   buffer[0] = uint8_t(reg);
+   return i2cDevice.write_then_read(buffer, 1, buffer, len);
+}
+
+
+// Sends a reset to the ENS160. Returns false on I2C problems.
+bool ScioSense_ENS160::reset () {
+       if (write8(ENS160_REG_OPMODE, ENS160_OPMODE_RESET)) {
+      _delay_ms(ENS160_BOOTING);
+      return true;
+   }
+       _delay_ms(ENS160_BOOTING);
+       return false;
+}
+
+// Reads the part ID and confirms valid sensor
+bool ScioSense_ENS160::checkPartID () {
+       uint16_t part_id;
+       
+   read16(ENS160_REG_PART_ID, &part_id);
+       _delay_ms(ENS160_BOOTING);
+
+       if (part_id == ENS160_PARTID) {
+      _revENS16x = 0;
+      return true;
+
+   } else if (part_id == ENS161_PARTID) {
+      _revENS16x = 1;
+      return true;
+   }
+       
+       return false;
+}
+
+// Initialize idle mode and confirms 
+bool ScioSense_ENS160::clearCommand () {
+       uint8_t status;
+       
+       if (write8(ENS160_REG_COMMAND, ENS160_COMMAND_NOP)) {
+          if (write8(ENS160_REG_COMMAND, ENS160_COMMAND_CLRGPR)) {
+             _delay_ms(ENS160_BOOTING);
+         if (read8(ENS160_REG_DATA_STATUS, &status)) {
+            return true;
+         }
+      }
+   }
+       _delay_ms(ENS160_BOOTING);
+       return false;
+}
+
+// Read firmware revisions
+bool ScioSense_ENS160::getFirmware () {
+       uint8_t i2cbuf[3];
+       
+       if (clearCommand()) {
+      _delay_ms(ENS160_BOOTING);
+      if (write8(ENS160_REG_COMMAND, ENS160_COMMAND_GET_APPVER)) {
+         if (readBytes(ENS160_REG_GPR_READ_4, i2cbuf, 3)) {
+            _fw_ver_major = i2cbuf[0];
+            _fw_ver_minor = i2cbuf[1];
+            _fw_ver_build = i2cbuf[2];
+            _revENS16x = this->_fw_ver_major > 6 ? 1 : 0;
+            _delay_ms(ENS160_BOOTING);
+            return true;
+         }
+      }
+   }
+   _delay_ms(ENS160_BOOTING);
+       return false;
+}
+
+// Set operation mode of sensor
+bool ScioSense_ENS160::setMode (uint8_t mode) {
+       //LP only valid for rev>0
+       if ((mode == ENS160_OPMODE_LP) and (_revENS16x == 0)) {
+      return false;
+   }
+       if (write8(ENS160_REG_OPMODE, mode)) {
+      _delay_ms(ENS160_BOOTING);
+      return true;
+   }
+   _delay_ms(ENS160_BOOTING);
+   return false;
+}
+
+// Initialize definition of custom mode with <n> steps
+bool ScioSense_ENS160::initCustomMode (uint16_t stepNum) {
+       if (stepNum > 0) {
+               _stepCount = stepNum;
+               if (setMode(ENS160_OPMODE_IDLE)) {
+         if (clearCommand()) {
+            if (write8(ENS160_REG_COMMAND, ENS160_COMMAND_SETSEQ)) {
+               _delay_ms(ENS160_BOOTING);
+               return true;
+            }
+         }
+      }
+       }
+       _delay_ms(ENS160_BOOTING);
+       return false;
+}
+
+// Add a step to custom measurement profile with definition of duration, enabled data acquisition and temperature for each hotplate
+bool ScioSense_ENS160::addCustomStep (uint16_t time, bool measureHP0, bool measureHP1, bool measureHP2, bool measureHP3, uint16_t tempHP0, uint16_t tempHP1, uint16_t tempHP2, uint16_t tempHP3) {
+       uint8_t seq_ack;
+       uint8_t temp;
+
+       _delay_ms(ENS160_BOOTING);
+
+       temp = (uint8_t)(((time / 24) - 1) << 6); 
+       if (measureHP0) {
+      temp = temp | 0x20;
+   }
+       if (measureHP1) {
+      temp = temp | 0x10;
+   }
+       if (measureHP2) {
+      temp = temp | 0x08;
+   }
+       if (measureHP3) {
+      temp = temp | 0x04;
+   }
+       if (!write8(ENS160_REG_GPR_WRITE_0, temp)) {
+      return false;
+   }
+   temp = (uint8_t)(((time / 24) - 1) >> 2); 
+       if (!write8(ENS160_REG_GPR_WRITE_1, temp)) {
+      return false;
+   }
+   if (!write8(ENS160_REG_GPR_WRITE_2, (uint8_t)(tempHP0 / 2))) {
+      return false;
+   }
+       if (!write8(ENS160_REG_GPR_WRITE_3, (uint8_t)(tempHP1 / 2))) {
+      return false;
+   }
+       if (write8(ENS160_REG_GPR_WRITE_4, (uint8_t)(tempHP2 / 2))) {
+      return false;
+   }
+       if (write8(ENS160_REG_GPR_WRITE_5, (uint8_t)(tempHP3 / 2))) {
+      return false;
+   }
+
+       if (write8(ENS160_REG_GPR_WRITE_6, (uint8_t)(_stepCount - 1))) {
+      return false;
+   }
+
+   if (_stepCount == 1) {
+      if (!write8(ENS160_REG_GPR_WRITE_7, 128)) {
+         return false;
+      }
+   } else {
+      if (!write8(ENS160_REG_GPR_WRITE_7, 0)) {
+         return false;
+      }
+   }
+   _delay_ms(ENS160_BOOTING);
+
+   if (!read8(ENS160_REG_GPR_READ_7, &seq_ack)) {
+      return false;
+   }
+       _delay_ms(ENS160_BOOTING);
+       
+       if ((ENS160_SEQ_ACK_COMPLETE | _stepCount) != seq_ack) {
+               _stepCount++;
+               return false;
+       }
+
+       return true;
+}
+
+bool ScioSense_ENS160::readStatus (uint8_t *status) {
+   return read8(ENS160_REG_DATA_STATUS, status);
+}
+
+bool ScioSense_ENS160::readData (ENS160_DATA *data) {
+   uint8_t buffer[1] = { 0x21 };
+   return i2cDevice.write_then_read(buffer, 1, (uint8_t *)data, sizeof(ENS160_DATA));
+}
+       
+// Perform prediction measurement and stores result in internal variables
+bool ScioSense_ENS160::measure (bool waitForNew) {
+       uint8_t i2cbuf[8];
+       uint8_t status;
+
+       // Set default status for early bail out
+       if (waitForNew) {
+               do {
+         if (!read8(ENS160_REG_DATA_STATUS, &status)) {
+            return false;
+         }
+         _delay_ms(1);
+               } while (!IS_NEWDAT(status));
+       } else {
+               if (!read8(ENS160_REG_DATA_STATUS, &status)) {
+         return false;
+      }
+       }
+       
+
+       // Read predictions
+       if (IS_NEWDAT(status)) {
+               if (!readBytes(ENS160_REG_DATA_AQI, i2cbuf, 7)) {
+         return false;
+      }
+      return false;
+               _data_aqi = i2cbuf[0];
+               _data_tvoc = i2cbuf[1] | ((uint16_t)i2cbuf[2] << 8);
+               _data_eco2 = i2cbuf[3] | ((uint16_t)i2cbuf[4] << 8);
+               if (_revENS16x > 0) {
+         _data_aqi500 = ((uint16_t)i2cbuf[5]) | ((uint16_t)i2cbuf[6] << 8);
+      } else {
+         _data_aqi500 = 0;
+      }
+      return true;
+       }
+       
+       return false;
+}
+
+// Perfrom raw measurement and stores result in internal variables
+bool ScioSense_ENS160::measureRaw (bool waitForNew) {
+       uint8_t i2cbuf[8];
+       uint8_t status;
+
+       // Set default status for early bail out
+       if (waitForNew) {
+               do {
+                       _delay_ms(1);
+                       if (!read8(ENS160_REG_DATA_STATUS, &status)) {
+            return false;
+         }
+               } while (!IS_NEWGPR(status));
+       } else {
+               if (!read8(ENS160_REG_DATA_STATUS, &status)) {
+         return false;
+      }
+       }
+       
+       if (IS_NEWGPR(status)) {
+               
+               // Read raw resistance values
+               if (!readBytes(ENS160_REG_GPR_READ_0, i2cbuf, 8)) {
+         return false;
+      }
+      _hp0_rs = CONVERT_RS_RAW2OHMS_F((uint32_t)(i2cbuf[0] | ((uint16_t)i2cbuf[1] << 8)));
+      _hp1_rs = CONVERT_RS_RAW2OHMS_F((uint32_t)(i2cbuf[2] | ((uint16_t)i2cbuf[3] << 8)));
+      _hp2_rs = CONVERT_RS_RAW2OHMS_F((uint32_t)(i2cbuf[4] | ((uint16_t)i2cbuf[5] << 8)));
+      _hp3_rs = CONVERT_RS_RAW2OHMS_F((uint32_t)(i2cbuf[6] | ((uint16_t)i2cbuf[7] << 8)));
+       
+               // Read baselines
+               if (!readBytes(ENS160_REG_DATA_BL, i2cbuf, 8)) {
+         return false;
+      }
+               _hp0_bl = CONVERT_RS_RAW2OHMS_F((uint32_t)(i2cbuf[0] | ((uint16_t)i2cbuf[1] << 8)));
+               _hp1_bl = CONVERT_RS_RAW2OHMS_F((uint32_t)(i2cbuf[2] | ((uint16_t)i2cbuf[3] << 8)));
+               _hp2_bl = CONVERT_RS_RAW2OHMS_F((uint32_t)(i2cbuf[4] | ((uint16_t)i2cbuf[5] << 8)));
+               _hp3_bl = CONVERT_RS_RAW2OHMS_F((uint32_t)(i2cbuf[6] | ((uint16_t)i2cbuf[7] << 8)));
+
+               if (!read8(ENS160_REG_DATA_MISR, i2cbuf)) {
+         return false;
+      }
+               _misr = i2cbuf[0];
+      return true;
+       }
+       
+       return false;
+}
+
+
+// Writes t (degC) and h (%rh) to ENV_DATA. Returns false on I2C problems.
+bool ScioSense_ENS160::set_envdata (float t, float h) {
+       uint16_t t_data = (uint16_t)((t + 273.15f) * 64.0f);
+       uint16_t rh_data = (uint16_t)(h * 512.0f);
+       return this->set_envdata210(t_data, rh_data);
+}
+
+// Writes t and h (in ENS210 format) to ENV_DATA. Returns false on I2C problems.
+bool ScioSense_ENS160::set_envdata210 (uint16_t t, uint16_t h) {
+       //uint16_t temp;
+       uint8_t trh_in[4];
+       
+       //temp = (uint16_t)((t + 273.15f) * 64.0f);
+       trh_in[0] = t & 0xff;
+       trh_in[1] = (t >> 8) & 0xff;
+       
+       //temp = (uint16_t)(h * 512.0f);
+       trh_in[2] = h & 0xff;
+       trh_in[3] = (h >> 8) & 0xff;
+       
+       if (!i2cDevice.writeByteAndBuffer(ENS160_REG_TEMP_IN, trh_in, 4)) {
+      return false;
+   }
+       
+       return true;
+}
diff --git a/software/test-software/src/adafruit/ens160.h b/software/test-software/src/adafruit/ens160.h
new file mode 100644 (file)
index 0000000..7f26ba1
--- /dev/null
@@ -0,0 +1,188 @@
+/*
+  ScioSense_ENS160.h - Library for the ENS160 sensor with I2C interface from ScioSense
+  2023 Mar 23  v6      Christoph Friese        Bugfix measurement routine, prepare next release
+  2021 July 29 v4      Christoph Friese        Changed nomenclature to ScioSense as product shifted from ams
+  2020 Apr 06  v3      Christoph Friese        Changed nomenclature to ScioSense as product shifted from ams
+  2020 Feb 15  v2      Giuseppe Pasetti        Corrected firmware flash option
+  2019 May 05  v1      Christoph Friese        Created
+  based on application note "ENS160 Software Integration.pdf" rev 0.01
+*/
+
+#ifndef __SCIOSENSE_ENS160_H_
+#define __SCIOSENSE_ENS160_H_
+
+#include "../i2cmaster.hpp"
+#include <stdint.h>
+// #define byte uint8_t
+
+// #if (ARDUINO >= 100)
+//     #include "Arduino.h"
+// #else
+//     #include "WProgram.h"
+// #endif
+
+// #include <Wire.h>
+
+// Chip constants
+#define ENS160_PARTID                          0x0160
+#define ENS161_PARTID                          0x0161
+#define ENS160_BOOTING                         10
+
+// 7-bit I2C slave address of the ENS160
+#define ENS160_I2CADDR_0               0x52            //ADDR low
+#define ENS160_I2CADDR_1               0x53            //ADDR high
+
+// ENS160 registers for version V0
+#define ENS160_REG_PART_ID             0x00            // 2 byte register
+#define ENS160_REG_OPMODE              0x10
+#define ENS160_REG_CONFIG              0x11
+#define ENS160_REG_COMMAND             0x12
+#define ENS160_REG_TEMP_IN             0x13
+#define ENS160_REG_RH_IN               0x15
+#define ENS160_REG_DATA_STATUS         0x20
+#define ENS160_REG_DATA_AQI            0x21
+#define ENS160_REG_DATA_TVOC           0x22
+#define ENS160_REG_DATA_ECO2           0x24                    
+#define ENS160_REG_DATA_BL             0x28
+#define ENS160_REG_DATA_T              0x30
+#define ENS160_REG_DATA_RH             0x32
+#define ENS160_REG_DATA_MISR           0x38
+#define ENS160_REG_GPR_WRITE_0         0x40
+#define ENS160_REG_GPR_WRITE_1         ENS160_REG_GPR_WRITE_0 + 1
+#define ENS160_REG_GPR_WRITE_2         ENS160_REG_GPR_WRITE_0 + 2
+#define ENS160_REG_GPR_WRITE_3         ENS160_REG_GPR_WRITE_0 + 3
+#define ENS160_REG_GPR_WRITE_4         ENS160_REG_GPR_WRITE_0 + 4
+#define ENS160_REG_GPR_WRITE_5         ENS160_REG_GPR_WRITE_0 + 5
+#define ENS160_REG_GPR_WRITE_6         ENS160_REG_GPR_WRITE_0 + 6
+#define ENS160_REG_GPR_WRITE_7         ENS160_REG_GPR_WRITE_0 + 7
+#define ENS160_REG_GPR_READ_0          0x48
+#define ENS160_REG_GPR_READ_4          ENS160_REG_GPR_READ_0 + 4
+#define ENS160_REG_GPR_READ_6          ENS160_REG_GPR_READ_0 + 6
+#define ENS160_REG_GPR_READ_7          ENS160_REG_GPR_READ_0 + 7
+
+//ENS160 data register fields
+#define ENS160_COMMAND_NOP             0x00
+#define ENS160_COMMAND_CLRGPR          0xCC
+#define ENS160_COMMAND_GET_APPVER      0x0E 
+#define ENS160_COMMAND_SETTH           0x02
+#define ENS160_COMMAND_SETSEQ          0xC2
+
+#define ENS160_OPMODE_RESET            0xF0
+#define ENS160_OPMODE_DEP_SLEEP                0x00
+#define ENS160_OPMODE_IDLE             0x01
+#define ENS160_OPMODE_STD              0x02
+#define ENS160_OPMODE_LP               0x03    
+#define ENS160_OPMODE_CUSTOM           0xC0
+
+#define ENS160_BL_CMD_START            0x02
+#define ENS160_BL_CMD_ERASE_APP                0x04
+#define ENS160_BL_CMD_ERASE_BLINE      0x06
+#define ENS160_BL_CMD_WRITE            0x08
+#define ENS160_BL_CMD_VERIFY           0x0A
+#define ENS160_BL_CMD_GET_BLVER                0x0C
+#define ENS160_BL_CMD_GET_APPVER       0x0E
+#define ENS160_BL_CMD_EXITBL           0x12
+
+#define ENS160_SEQ_ACK_NOTCOMPLETE     0x80
+#define ENS160_SEQ_ACK_COMPLETE                0xC0
+
+#define IS_ENS160_SEQ_ACK_NOT_COMPLETE(x)      (ENS160_SEQ_ACK_NOTCOMPLETE == (ENS160_SEQ_ACK_NOTCOMPLETE & (x)))
+#define IS_ENS160_SEQ_ACK_COMPLETE(x)          (ENS160_SEQ_ACK_COMPLETE == (ENS160_SEQ_ACK_COMPLETE & (x)))
+
+#define ENS160_DATA_STATUS_NEWDAT      0x02
+#define ENS160_DATA_STATUS_NEWGPR      0x01
+
+#define IS_NEWDAT(x)                   (ENS160_DATA_STATUS_NEWDAT == (ENS160_DATA_STATUS_NEWDAT & (x)))
+#define IS_NEWGPR(x)                   (ENS160_DATA_STATUS_NEWGPR == (ENS160_DATA_STATUS_NEWGPR & (x)))
+#define IS_NEW_DATA_AVAILABLE(x)       (0 != ((ENS160_DATA_STATUS_NEWDAT | ENS160_DATA_STATUS_NEWGPR ) & (x)))
+
+#define CONVERT_RS_RAW2OHMS_I(x)       (1 << ((x) >> 11))
+#define CONVERT_RS_RAW2OHMS_F(x)       (pow (2, (float)(x) / 2048))
+
+typedef struct {
+   uint8_t aqi;
+   uint16_t tvoc;
+   uint16_t eco2;
+} ENS160_DATA;
+
+class ScioSense_ENS160 {
+               
+       public:
+          ScioSense_ENS160();
+               
+               void                            setI2C(uint8_t sda, uint8_t scl);                               // Function to redefine I2C pins
+               
+               bool                            begin();                                        // Init I2C communication, resets ENS160 and checks its PART_ID. Returns false on I2C problems or wrong PART_ID.
+               uint8_t                         revENS16x()     { return this->_revENS16x; }                    // Report version of sensor (0: ENS160, 1: ENS161)
+               bool                            setMode(uint8_t mode);                                          // Set operation mode of sensor
+
+               bool                            initCustomMode(uint16_t stepNum);                               // Initialize definition of custom mode with <n> steps
+               bool                            addCustomStep(uint16_t time, bool measureHP0, bool measureHP1, bool measureHP2, bool measureHP3, uint16_t tempHP0, uint16_t tempHP1, uint16_t tempHP2, uint16_t tempHP3);
+                                                                                                                                                                                       // Add a step to custom measurement profile with definition of duration, enabled data acquisition and temperature for each hotplate
+                                                                                                                                                                                       
+      bool  readData (ENS160_DATA *data);
+      bool  readStatus(uint8_t *status);
+               bool                            measure(bool waitForNew);                               // Perform measurement and stores result in internal variables
+               bool                            measureRaw(bool waitForNew);                            // Perform raw measurement and stores result in internal variables
+               bool                            set_envdata(float t, float h);                                  // Writes t (degC) and h (%rh) to ENV_DATA. Returns "0" if I2C transmission is successful
+               bool                            set_envdata210(uint16_t t, uint16_t h);                         // Writes t and h (in ENS210 format) to ENV_DATA. Returns "0" if I2C transmission is successful
+               uint8_t                         getMajorRev()   { return this->_fw_ver_major; }                 // Get major revision number of used firmware
+               uint8_t                         getMinorRev()   { return this->_fw_ver_minor; }                 // Get minor revision number of used firmware
+               uint8_t                         getBuild()              { return this->_fw_ver_build; }         // Get build revision number of used firmware
+
+               uint8_t                         getAQI()                { return this->_data_aqi; }             // Get AQI value of last measurement 
+               uint16_t                        getTVOC()               { return this->_data_tvoc; }            // Get TVOC value of last measurement 
+               uint16_t                        geteCO2()               { return this->_data_eco2; }            // Get eCO2 value of last measurement 
+               uint16_t                        getAQI500()             { return this->_data_aqi500; }          // Get AQI500 value of last measurement 
+               uint32_t                        getHP0()                { return this->_hp0_rs; }               // Get resistance of HP0 of last measurement
+               uint32_t                        getHP1()                { return this->_hp1_rs; }               // Get resistance of HP1 of last measurement
+               uint32_t                        getHP2()                { return this->_hp2_rs; }               // Get resistance of HP2 of last measurement
+               uint32_t                        getHP3()                { return this->_hp3_rs; }               // Get resistance of HP3 of last measurement
+               uint32_t                        getHP0BL()              { return this->_hp0_bl; }               // Get baseline resistance of HP0 of last measurement
+               uint32_t                        getHP1BL()              { return this->_hp1_bl; }               // Get baseline resistance of HP1 of last measurement
+               uint32_t                        getHP2BL()              { return this->_hp2_bl; }               // Get baseline resistance of HP2 of last measurement
+               uint32_t                        getHP3BL()              { return this->_hp3_bl; }               // Get baseline resistance of HP3 of last measurement
+               uint8_t                         getMISR()               { return this->_misr; }                 // Return status code of sensor
+
+       private:
+      I2cMaster i2cDevice;
+               bool                            reset();                                                        // Sends a reset to the ENS160. Returns false on I2C problems.
+               bool                            checkPartID();                                                  // Reads the part ID and confirms valid sensor
+               bool                            clearCommand();                                                 // Initialize idle mode and confirms 
+               bool                            getFirmware();                                                  // Read firmware revisions
+               
+               uint8_t                         _revENS16x;                                                     // ENS160 or ENS161 connected? (FW >7)
+       
+               uint8_t                         _fw_ver_major;
+               uint8_t                         _fw_ver_minor;
+               uint8_t                         _fw_ver_build;
+
+               uint16_t                        _stepCount;                                                     // Counter for custom sequence
+
+               uint8_t                         _data_aqi;
+               uint16_t                        _data_tvoc;
+               uint16_t                        _data_eco2;
+               uint16_t                        _data_aqi500;
+               uint32_t                        _hp0_rs;
+               uint32_t                        _hp0_bl;
+               uint32_t                        _hp1_rs;
+               uint32_t                        _hp1_bl;
+               uint32_t                        _hp2_rs;
+               uint32_t                        _hp2_bl;
+               uint32_t                        _hp3_rs;
+               uint32_t                        _hp3_bl;
+               uint16_t                        _temp;
+               int                             _slaveaddr;                                                     // Slave address of the ENS160
+               uint8_t                         _misr;
+               
+               uint8_t _seq_steps[1][8];
+
+      bool write8(uint8_t reg, uint8_t value);
+      bool read8 (uint8_t reg, uint8_t *value);
+      bool read16 (uint8_t reg, uint16_t *value);
+      bool read16LE (uint8_t reg, uint16_t *value);
+      bool readBytes (uint8_t reg, uint8_t *bytes, uint8_t len);
+};
+
+
+#endif
diff --git a/software/test-software/src/adafruit/sensor.h b/software/test-software/src/adafruit/sensor.h
new file mode 100644 (file)
index 0000000..ac7e454
--- /dev/null
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software< /span>
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* Update by K. Townsend (Adafruit Industries) for lighter typedefs, and
+ * extended sensor support to include color, voltage and current */
+
+#ifndef _ADAFRUIT_SENSOR_H
+#define _ADAFRUIT_SENSOR_H
+
+#ifndef ARDUINO
+#include <stdint.h>
+#elif ARDUINO >= 100
+#include "Arduino.h"
+#include "Print.h"
+#else
+#include "WProgram.h"
+#endif
+
+/* Constants */
+#define SENSORS_GRAVITY_EARTH (9.80665F) /**< Earth's gravity in m/s^2 */
+#define SENSORS_GRAVITY_MOON (1.6F)      /**< The moon's gravity in m/s^2 */
+#define SENSORS_GRAVITY_SUN (275.0F)     /**< The sun's gravity in m/s^2 */
+#define SENSORS_GRAVITY_STANDARD (SENSORS_GRAVITY_EARTH)
+#define SENSORS_MAGFIELD_EARTH_MAX                                             \
+  (60.0F) /**< Maximum magnetic field on Earth's surface */
+#define SENSORS_MAGFIELD_EARTH_MIN                                             \
+  (30.0F) /**< Minimum magnetic field on Earth's surface */
+#define SENSORS_PRESSURE_SEALEVELHPA                                           \
+  (1013.25F) /**< Average sea level pressure is 1013.25 hPa */
+#define SENSORS_DPS_TO_RADS                                                    \
+  (0.017453293F) /**< Degrees/s to rad/s multiplier                            \
+                  */
+#define SENSORS_RADS_TO_DPS                                                    \
+  (57.29577793F) /**< Rad/s to degrees/s  multiplier */
+#define SENSORS_GAUSS_TO_MICROTESLA                                            \
+  (100) /**< Gauss to micro-Tesla multiplier */
+
+/** Sensor types */
+typedef enum {
+  SENSOR_TYPE_ACCELEROMETER = (1), /**< Gravity + linear acceleration */
+  SENSOR_TYPE_MAGNETIC_FIELD = (2),
+  SENSOR_TYPE_ORIENTATION = (3),
+  SENSOR_TYPE_GYROSCOPE = (4),
+  SENSOR_TYPE_LIGHT = (5),
+  SENSOR_TYPE_PRESSURE = (6),
+  SENSOR_TYPE_PROXIMITY = (8),
+  SENSOR_TYPE_GRAVITY = (9),
+  SENSOR_TYPE_LINEAR_ACCELERATION =
+      (10), /**< Acceleration not including gravity */
+  SENSOR_TYPE_ROTATION_VECTOR = (11),
+  SENSOR_TYPE_RELATIVE_HUMIDITY = (12),
+  SENSOR_TYPE_AMBIENT_TEMPERATURE = (13),
+  SENSOR_TYPE_OBJECT_TEMPERATURE = (14),
+  SENSOR_TYPE_VOLTAGE = (15),
+  SENSOR_TYPE_CURRENT = (16),
+  SENSOR_TYPE_COLOR = (17),
+  SENSOR_TYPE_TVOC = (18),
+  SENSOR_TYPE_VOC_INDEX = (19),
+  SENSOR_TYPE_NOX_INDEX = (20),
+  SENSOR_TYPE_CO2 = (21),
+  SENSOR_TYPE_ECO2 = (22),
+  SENSOR_TYPE_PM10_STD = (23),
+  SENSOR_TYPE_PM25_STD = (24),
+  SENSOR_TYPE_PM100_STD = (25),
+  SENSOR_TYPE_PM10_ENV = (26),
+  SENSOR_TYPE_PM25_ENV = (27),
+  SENSOR_TYPE_PM100_ENV = (28),
+  SENSOR_TYPE_GAS_RESISTANCE = (29),
+  SENSOR_TYPE_UNITLESS_PERCENT = (30),
+  SENSOR_TYPE_ALTITUDE = (31)
+} sensors_type_t;
+
+/** struct sensors_vec_s is used to return a vector in a common format. */
+typedef struct {
+  union {
+    float v[3]; ///< 3D vector elements
+    struct {
+      float x; ///< X component of vector
+      float y; ///< Y component of vector
+      float z; ///< Z component of vector
+    };         ///< Struct for holding XYZ component
+    /* Orientation sensors */
+    struct {
+      float roll; /**< Rotation around the longitudinal axis (the plane body, 'X
+                     axis'). Roll is positive and increasing when moving
+                     downward. -90 degrees <= roll <= 90 degrees */
+      float pitch;   /**< Rotation around the lateral axis (the wing span, 'Y
+                        axis'). Pitch is positive and increasing when moving
+                        upwards. -180 degrees <= pitch <= 180 degrees) */
+      float heading; /**< Angle between the longitudinal axis (the plane body)
+                        and magnetic north, measured clockwise when viewing from
+                        the top of the device. 0-359 degrees */
+    };               ///< Struct for holding roll/pitch/heading
+  };                 ///< Union that can hold 3D vector array, XYZ components or
+                     ///< roll/pitch/heading
+  int8_t status;     ///< Status byte
+  uint8_t reserved[3]; ///< Reserved
+} sensors_vec_t;
+
+/** struct sensors_color_s is used to return color data in a common format. */
+typedef struct {
+  union {
+    float c[3]; ///< Raw 3-element data
+    /* RGB color space */
+    struct {
+      float r;   /**< Red component */
+      float g;   /**< Green component */
+      float b;   /**< Blue component */
+    };           ///< RGB data in floating point notation
+  };             ///< Union of various ways to describe RGB colorspace
+  uint32_t rgba; /**< 24-bit RGBA value */
+} sensors_color_t;
+
+/* Sensor event (36 bytes) */
+/** struct sensor_event_s is used to provide a single sensor event in a common
+ * format. */
+typedef struct {
+  int32_t version;   /**< must be sizeof(struct sensors_event_t) */
+  int32_t sensor_id; /**< unique sensor identifier */
+  int32_t type;      /**< sensor type */
+  int32_t reserved0; /**< reserved */
+  int32_t timestamp; /**< time is in milliseconds */
+  union {
+    float data[4];              ///< Raw data */
+    sensors_vec_t acceleration; /**< acceleration values are in meter per second
+                                   per second (m/s^2) */
+    sensors_vec_t
+        magnetic; /**< magnetic vector values are in micro-Tesla (uT) */
+    sensors_vec_t orientation; /**< orientation values are in degrees */
+    sensors_vec_t gyro;        /**< gyroscope values are in rad/s */
+    float temperature; /**< temperature is in degrees centigrade (Celsius) */
+    float distance;    /**< distance in centimeters */
+    float light;       /**< light in SI lux units */
+    float pressure;    /**< pressure in hectopascal (hPa) */
+    float relative_humidity; /**< relative humidity in percent */
+    float current;           /**< current in milliamps (mA) */
+    float voltage;           /**< voltage in volts (V) */
+    float tvoc;              /**< Total Volatile Organic Compounds, in ppb */
+    float voc_index; /**< VOC (Volatile Organic Compound) index where 100 is
+                          normal (unitless) */
+    float nox_index; /**< NOx (Nitrogen Oxides) index where 100 is normal
+                          (unitless) */
+    float CO2;       /**< Measured CO2 in parts per million (ppm) */
+    float eCO2;      /**< equivalent/estimated CO2 in parts per million (ppm
+                        estimated from some other measurement) */
+    float pm10_std;  /**< Standard Particulate Matter <=1.0 in parts per million
+                        (ppm) */
+    float pm25_std;  /**< Standard Particulate Matter <=2.5 in parts per million
+                        (ppm) */
+    float pm100_std; /**< Standard Particulate Matter <=10.0 in parts per
+                        million (ppm) */
+    float pm10_env;  /**< Environmental Particulate Matter <=1.0 in parts per
+                        million (ppm) */
+    float pm25_env;  /**< Environmental Particulate Matter <=2.5 in parts per
+                        million (ppm) */
+    float pm100_env; /**< Environmental Particulate Matter <=10.0 in parts per
+                        million (ppm) */
+    float gas_resistance;   /**< Proportional to the amount of VOC particles in
+                               the air (Ohms) */
+    float unitless_percent; /**<Percentage, unit-less (%) */
+    sensors_color_t color;  /**< color in RGB component values */
+    float altitude; /**< Distance between a reference datum and a point or
+                       object, in meters. */
+  };                ///< Union for the wide ranges of data we can carry
+} sensors_event_t;
+
+/* Sensor details (40 bytes) */
+/** struct sensor_s is used to describe basic information about a specific
+ * sensor. */
+typedef struct {
+  char name[12];     /**< sensor name */
+  int32_t version;   /**< version of the hardware + driver */
+  int32_t sensor_id; /**< unique sensor identifier */
+  int32_t type;      /**< this sensor's type (ex. SENSOR_TYPE_LIGHT) */
+  float max_value;   /**< maximum value of this sensor's value in SI units */
+  float min_value;   /**< minimum value of this sensor's value in SI units */
+  float resolution; /**< smallest difference between two values reported by this
+                       sensor */
+  int32_t min_delay; /**< min delay in microseconds between events. zero = not a
+                        constant rate */
+} sensor_t;
+
+/** @brief Common sensor interface to unify various sensors.
+ * Intentionally modeled after sensors.h in the Android API:
+ * https://github.com/android/platform_hardware_libhardware/blob/master/include/hardware/sensors.h
+ */
+class Adafruit_Sensor {
+public:
+  // Constructor(s)
+  Adafruit_Sensor() {}
+//   virtual ~Adafruit_Sensor() {}
+
+//   // These must be defined by the subclass
+
+//   /*! @brief Whether we should automatically change the range (if possible) for
+//      higher precision
+//       @param enabled True if we will try to autorange */
+//   virtual void enableAutoRange(bool enabled) {
+//     (void)enabled; /* suppress unused warning */
+//   };
+
+//   /*! @brief Get the latest sensor event
+//       @returns True if able to fetch an event */
+//   virtual bool getEvent(sensors_event_t *) = 0;
+//   /*! @brief Get info about the sensor itself */
+//   virtual void getSensor(sensor_t *) = 0;
+
+//   void printSensorDetails(void);
+};
+
+#endif
diff --git a/software/test-software/src/i2cmaster.cpp b/software/test-software/src/i2cmaster.cpp
new file mode 100644 (file)
index 0000000..ca883d6
--- /dev/null
@@ -0,0 +1,155 @@
+#include <avr/io.h>
+#include <avr/pgmspace.h>
+#include <stdio.h>
+
+#include "i2cmaster.hpp"
+
+I2cMaster::I2cMaster () {
+   address = 0;
+   timer = 0;
+}
+
+void I2cMaster::tick1ms () {
+   if (timer > 0) {
+      timer--;
+   }
+}
+
+bool I2cMaster::begin (uint8_t addr) {
+   this->address = addr;
+   // TWBR = 13; // 100kHz (TWPS1:0 = 00), TWBR = (F_CPU - 16 * 100000) / (2 * 100000 * 4);
+   TWBR = 100; // 50kHz (TWPS1:0 = 00), TWBR = (F_CPU - 16 * 50000) / (2 * 50000 * 4);
+   TWCR = (1 << TWEN);
+   return true;
+}
+
+void I2cMaster::end () {
+   TWCR = (1 << TWEN);
+   TWBR = 0;
+}
+
+bool I2cMaster::read (uint8_t *buffer, uint8_t len) {
+   if (start(true)) {
+      if (readBytes(buffer, len)) {
+         if (stop()) {
+            return true;
+         }
+      }
+   }
+   return false;
+}
+
+
+bool I2cMaster::write (const uint8_t *buffer, uint8_t len) {
+   if (start(false)) {
+      if (writeBytes(buffer, len, false)) {
+         if (stop()) {
+            return true;
+         }
+      }
+   }
+   return false;
+}
+
+bool I2cMaster::write_P (const uint8_t *buffer, uint8_t len) {
+   if (start(false)) {
+      if (writeBytes(buffer, len, true)) {
+         if (stop()) {
+            return true;
+         }
+      }
+   }
+   return false;
+}
+
+
+bool I2cMaster::writeByteAndBuffer (uint8_t byte, const uint8_t *buffer, uint8_t len) {
+   if (start(false)) {
+      do {
+         TWDR = byte;
+         TWCR = (1 << TWINT) | (1 << TWEN); // send byte
+         while (!(TWCR & (1 << TWINT))) {}; // wait until last action done
+         if ((TWSR & 0xf8) != 0x28) {
+            return false;
+         }
+         byte = *buffer++;
+      } while (len-- > 0);
+      return true;
+   }
+   return false;
+}
+
+
+bool I2cMaster::write_then_read (const uint8_t *write_buffer, uint8_t write_len, uint8_t *read_buffer, uint8_t read_len) {
+   if (start(false)) {
+      if (writeBytes(write_buffer, write_len, false)) {
+         if (start(true)) {
+            if (readBytes(read_buffer, read_len)) {
+               if (stop()) {
+                  return true;
+               }
+            }
+         }
+      }
+   }
+   return false;
+}
+
+// -------------------------------------------------------------
+
+bool I2cMaster::readBytes (uint8_t *buffer, uint8_t len) {
+   while (len-- > 0) {
+      if (len > 0) {
+         TWCR = (1 << TWEA) | (1 << TWINT) | (1 << TWEN); // read data byte with ACK enabled
+      } else {
+         TWCR = (1 << TWINT) | (1 << TWEN); // read data byte with ACK disabled
+      }
+      while (!(TWCR & (1 << TWINT))) {}; // wait until last action done
+      uint8_t sr = TWSR & 0xf8;
+      if ((len > 0 && sr != 0x50) || (len == 0 && sr != 0x58)) {
+         return false;
+      }
+      *buffer++ = TWDR;
+   }
+   return true;
+}
+
+bool I2cMaster::writeBytes (const uint8_t *buffer, uint8_t len, bool fromFlash) {
+   while (len-- > 0) {
+      // printf_P(PSTR("[wB:len=%d, byte=%02x]"), len + 1, *buffer);
+      TWDR = fromFlash ? pgm_read_byte(buffer++) : *buffer++;
+      TWCR = (1 << TWINT) | (1 << TWEN); // send data byte
+      timer = 5;
+      while (timer > 0 && !(TWCR & (1 << TWINT))) {}; // wait until last action done
+      if (!timer || (TWSR & 0xf8) != 0x28) {
+         return false;
+      }
+   }
+   return true;
+}
+
+bool I2cMaster::start (bool read) {
+   TWCR = (1 << TWINT) | (1 << TWSTA) | (1 << TWEN); // send START condition
+   timer = 5;
+   while (timer > 0 && !(TWCR & (1 << TWINT))) {}; // wait until last action done
+   uint8_t sr = TWSR & 0xf8;
+   if (!timer || (sr != 0x08 && sr != 0x10)) {
+      return false;
+   }
+   TWDR = (address << 1) | (read ? 1 : 0); // address + R/nW
+   TWCR = (1 << TWINT) | (1 << TWEN); // send address/RW
+   timer = 5;
+   while (timer > 0 && !(TWCR & (1 << TWINT))) {}; // wait until last action done
+   sr = TWSR & 0xf8;
+   if (!timer || (!read && sr != 0x18) || (read && sr != 0x40)) {
+      return false;
+   }
+   return true;
+}
+
+bool I2cMaster::stop () {
+   TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWSTO);
+   while (TWCR & ( 1 << TWSTO));
+   return true;
+}
+
diff --git a/software/test-software/src/i2cmaster.hpp b/software/test-software/src/i2cmaster.hpp
new file mode 100644 (file)
index 0000000..89b1e46
--- /dev/null
@@ -0,0 +1,30 @@
+#ifndef I2C_MASTER
+#define I2C_MASTER
+
+#include <stdint.h>
+
+class I2cMaster {
+   public:
+      static void end ();
+   
+   public:
+      I2cMaster ();
+      void tick1ms ();
+      bool begin (uint8_t addr);
+      bool read (uint8_t *buffer, uint8_t len);
+      bool write (const uint8_t *buffer, uint8_t len);
+      bool write_P (const uint8_t *buffer, uint8_t len);
+      bool write_then_read (const uint8_t *write_buffer, uint8_t write_len, uint8_t *read_buffer, uint8_t read_len);
+      bool writeByteAndBuffer (uint8_t byte, const uint8_t *buffer, uint8_t len);
+
+   private:
+      uint8_t address;
+      uint8_t timer;
+      bool start (bool read);
+      bool stop ();
+      bool writeBytes (const uint8_t *buffer, uint8_t len, bool fromFlash);
+      bool writeBytes_P (const uint8_t *buffer);
+      bool readBytes (uint8_t *buffer, uint8_t len);
+};
+
+#endif
\ No newline at end of file
diff --git a/software/test-software/src/i2cslave.cpp b/software/test-software/src/i2cslave.cpp
new file mode 100644 (file)
index 0000000..2bf6ac2
--- /dev/null
@@ -0,0 +1,92 @@
+#include <avr/io.h>
+#include <stdio.h>
+#include <util/atomic.h>
+
+#include "i2cslave.hpp"
+
+I2cSlave::I2cSlave () {
+   timer = 0;
+   fromMaster.rIndex = 0;
+   fromMaster.wIndex = 0;
+   toMaster.rIndex = 0;
+   toMaster.wIndex = 0;
+}
+
+void I2cSlave::tick1ms () {
+   if (timer > 0) {
+      timer--;
+   }
+}
+
+bool I2cSlave::begin (uint8_t addr, bool acceptGeneralCalls) {
+   if (addr > 127) {
+      return false;
+   }
+   TWAR = addr << 1 | (acceptGeneralCalls ? 1 : 0);
+   TWBR = 100; // 50kHz (TWPS1:0 = 00), TWBR = (F_CPU - 16 * 50000) / (2 * 50000 * 4);
+   TWCR = (1 << TWEA) | (1 << TWEN) | (1 << TWIE);
+   return true;
+}
+
+void I2cSlave::end () {
+   TWCR = (1 << TWEN);
+   TWBR = 0;
+}
+
+int I2cSlave::read () {
+   return getByte(fromMaster);
+}
+
+void I2cSlave::write (uint8_t byte) {
+   putByte(toMaster, byte);
+}
+
+
+void I2cSlave::putByte (RingBuffer& buffer, uint8_t byte) {
+   ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
+      buffer.data[buffer.wIndex++] = byte;
+      if (buffer.wIndex >= sizeof(buffer.data)) {
+         buffer.wIndex = 0;
+      }
+      if (buffer.wIndex == buffer.rIndex) {
+         buffer.rIndex++;
+         if (buffer.rIndex >= sizeof(buffer.data)) {
+            buffer.rIndex = 0;
+         }
+      }
+   }
+}
+
+int I2cSlave::getByte (RingBuffer& buffer) {
+   uint8_t b;
+   ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
+      if (buffer.rIndex == buffer.wIndex) {
+         return EOF;
+      }
+      b = buffer.data[buffer.rIndex++];
+      if (buffer.rIndex >= sizeof(buffer.data)) {
+         buffer.rIndex = 0;
+      }
+   }
+   return b;
+}
+
+void I2cSlave::handleTWIIsr () {
+   uint8_t sr = TWSR & 0xf8;
+   switch (sr) {
+      case 0x80: { // Previously addressed with own SLA+W; data has been received; ACK has been returned
+         putByte(fromMaster, TWDR);
+         TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWIE); // no TWEA -> only one byte accepted
+         break;
+      }
+      case 0xa8: { // Own SLA+R has been received; ACK has been returned
+         int response = getByte(toMaster);;
+         TWDR = response < 0 ? 0x00 : (uint8_t)response;
+         TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWIE); // no TWEA -> only one byte accepted
+         break;
+      }
+      default: TWCR = (1 << TWINT) | (1 << TWEA) | (1 << TWEN) | (1 << TWIE); break;
+   }
+  
+}
+
diff --git a/software/test-software/src/i2cslave.hpp b/software/test-software/src/i2cslave.hpp
new file mode 100644 (file)
index 0000000..2fe2dc7
--- /dev/null
@@ -0,0 +1,32 @@
+#ifndef I2C_SLAVE
+#define I2C_SLAVE
+
+#include <stdint.h>
+
+class I2cSlave {
+   private:
+      typedef struct {
+         uint8_t rIndex;
+         uint8_t wIndex;
+         uint8_t data[8];
+   } RingBuffer;
+
+   public:
+      I2cSlave ();
+      void tick1ms ();
+      bool begin (uint8_t addr, bool acceptGeneralCalls);
+      void end ();
+      void handleTWIIsr ();
+      int read ();
+      void write (uint8_t byte);
+
+   private:
+      uint8_t timer;
+      RingBuffer fromMaster;
+      RingBuffer toMaster;
+      void putByte (RingBuffer& buffer, uint8_t byte);
+      int getByte (RingBuffer& buffer);
+
+};
+
+#endif
\ No newline at end of file
diff --git a/software/test-software/src/main.cpp b/software/test-software/src/main.cpp
new file mode 100644 (file)
index 0000000..35f309f
--- /dev/null
@@ -0,0 +1,412 @@
+#include <avr/io.h>
+#include <avr/pgmspace.h>
+#include <avr/interrupt.h>
+#include <util/delay.h>
+
+#include <stdio.h>
+#include <util/atomic.h>
+
+#include "main.hpp"
+#include "units/encoder.hpp"
+#include "units/i2c.hpp"
+#include "units/led.hpp"
+#include "units/ieee485.hpp"
+#include "units/led.hpp"
+#include "units/lcd.hpp"
+#include "units/switch.hpp"
+#include "units/rgb.hpp"
+#include "units/seg7.hpp"
+#include "units/poti.hpp"
+#include "units/r2r.hpp"
+#include "units/motor.hpp"
+#include "units/portexp.hpp"
+#include "units/uart1.hpp"
+#include "units/modbus.hpp"
+#include "units/rtc8563.hpp"
+#include "units/cc1101.hpp"
+
+const char MAIN_CPP_DATE[] PROGMEM = __DATE__;
+const char MAIN_CPP_TIME[] PROGMEM = __TIME__;
+#ifdef __AVR_ATmega328P__
+   const char MAIN_CPP_PART_NAME[] PROGMEM = "ATmega328P";
+#endif
+#ifdef __AVR_ATmega644P__
+   const char MAIN_CPP_PART_NAME[] PROGMEM = "ATmega644P";
+#endif
+#ifdef __AVR_ATmega1284P__
+   const char MAIN_CPP_PART_NAME[] PROGMEM = "ATmega1284P";
+#endif
+
+const char PSTR_DIVIDER[] PROGMEM = "\n====================================\n ";
+const char PSTR_LINEFEED[] PROGMEM = "\n";
+const char PSTR_ERROR[] PROGMEM = "ERROR";
+const char PSTR_Done[] PROGMEM = "Done";
+
+uint8_t hardwareVersion = 0;
+
+extern "C" {
+   void __cxa_pure_virtual () {}
+   int  __cxa_guard_acquire(uint8_t *g) { return 0; }
+   void __cxa_guard_release(uint8_t *g) {}
+   void __cxa_guard_abort(uint8_t *g) {}
+   void __gxx_personality_sj0 () {}
+   void __cxa_rethrow () {}
+   void __cxa_begin_catch () {}
+   void __cxa_end_catch () {}
+
+   int uart_putchar(char c, FILE *stream) {
+      if (c == '\n') {
+         uart_putchar('\r', stream);
+      }
+      if (stream == stdout) {
+         loop_until_bit_is_set(UCSR0A, UDRE0);
+         UDR0 = c;
+      } 
+      return 0;
+   }
+
+
+   uint64_t volatile systemMillis = 0;
+   uint8_t volatile uartBuffer[32];
+   uint8_t volatile rIndex = 0;
+   uint8_t volatile wIndex = 0;
+
+   int uart_getchar (FILE *stream) {
+    // if (rIndex == wIndex) {
+    //     // nothing in buffer
+    //     return EOF;
+    // }
+    // printf_P(PSTR(" r%d"), rIndex);
+    while (rIndex == wIndex) {
+        // wait for character
+    }
+    
+    // don't use "char c" because german special characters would lead to negative return -> stream error
+    // char c = uartBuffer[rIndex++];
+    
+    uint8_t c = uartBuffer[rIndex++];
+    // printf_P(PSTR("(%02x) "), c);
+    if (c == '\r') {
+      c = '\n';
+    }
+    putchar(c); // echo on terminal
+    return c;
+   }
+
+   static FILE mystdout = { 0, 0, _FDEV_SETUP_WRITE , 0, 0, uart_putchar, NULL, 0 };
+   static FILE mystdin  = { 0, 0, _FDEV_SETUP_READ  , 0, 0, NULL, uart_getchar, 0 };
+
+   #if defined(__AVR_ATmega644P__) || defined(__AVR_ATmega1284P__)
+      static volatile uint16_t timerFlashGreenLed = 0;   
+      static volatile uint16_t timerFlashRedLed = 0;
+      void flashRedLed (uint16_t ms) { 
+         ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
+            if (timerFlashRedLed == 0) {
+               PORTC |= (1 << PC2);
+               if (ms < 0xff80) {
+                  ms += 0x80; // at least 128ms OFF after
+               }
+               timerFlashRedLed = ms;
+            }
+         }
+      }
+      void flashGreenLed (uint16_t ms) { 
+         ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
+            if (timerFlashGreenLed == 0) {
+               PORTC |= (1 << PC4);
+               if (ms < 0xff80) {
+                  ms += 0x80; // at least 128ms OFF after
+               }
+               timerFlashGreenLed = ms;
+            }
+         }
+      }
+   #endif
+   
+   static volatile uint32_t timer1ms = 0;
+   static volatile int keyUart0 = EOF;
+
+   Led led;
+   Switch sw;
+   Rgb rgb;
+   Seg7 seg7;
+   Poti poti;
+   Encoder encoder;
+   R2r r2r;
+   Motor motor;
+   PortExp portExp;
+   Lcd lcd;
+   Uart1 uart1;
+   Modbus modbus;
+   Ieee485 ieee485;
+   I2c i2cSparkfun(I2c::SparkFunEnvCombo);
+   I2c i2cMaster(I2c::Master);
+   I2c i2cSlave(I2c::Slave);
+   Rtc8563 rtc8563(Rtc8563::NORMAL);
+   Cc1101 cc1101Send(Cc1101::Send);
+   Cc1101 cc1101Receive(Cc1101::Receive);
+}
+
+uint8_t detectHardwareVersion () {
+   ADMUX = (1 << ADLAR) | (1 << REFS0) | 7; // ADC7, VREF=AVCC=3.3V
+   ADCSRA = (1 << ADEN) | 7; // ADC Enable, Prescaler 128
+   ADCSRA |= (1 << ADSC); // start ADC
+   while (ADCSRA & (1 << ADSC)) {} // wait for result
+   hardwareVersion = 0; // unknown board version
+   // printf("Hardware-Version: ADC7H = %d\n", ADCH);
+   #if defined(__AVR_ATmega644P__) || defined(__AVR_ATmega1284P__)
+      
+      if (ADCH >= 0xf4) {
+         hardwareVersion = 1;
+      } else if (ADCH >= 0xe0) {
+         hardwareVersion = 2;
+      }
+   #endif
+   #if defined(__AVR_ATmega328P__)
+      if (ADCH < 0xd8 && ADCH >= 0xc0) {
+         hardwareVersion = 1;
+      } else if (ADCH < 0xd8 && ADCH >= 0xb0) {
+         hardwareVersion = 2;
+      }
+   #endif
+
+   if (hardwareVersion < 1 || hardwareVersion > 2) {
+      #ifdef __AVR_ATmega644P__
+         printf_P(PSTR("\nInvalid Hardware-Version: ADC7H = %d (ATmega644P, 3.3V)\n"), ADCH);
+      #elif __AVR_ATmega1284P__
+         printf_P(PSTR("\nInvalid Hardware-Version: ADC7H = %d (ATmega1284P, 3.3V)\n"), ADCH);
+      #elif __AVR_ATmega328P__
+         printf_P(PSTR("\nInvalid Hardware-Version: ADC7H = %d (ATmega328P, 5V)\n"), ADCH);
+      #endif
+   } else {
+      printf_P(PSTR("\n\nHardware %d detected (ADC7H=0x%02X)"), hardwareVersion, ADCH);
+   }
+
+   ADMUX = 0;
+   ADCSRA = 0;
+   return hardwareVersion;
+}
+
+void setTimer (uint32_t ms) {
+   ATOMIC_BLOCK(ATOMIC_FORCEON) {
+      timer1ms = ms;
+   }
+}
+
+int wait (uint32_t ms) {
+   setTimer(ms);
+   do {
+      ATOMIC_BLOCK(ATOMIC_FORCEON) {
+         ms = timer1ms;
+      }
+   } while (ms > 0 && keyUart0 == EOF);
+   return keyUart0;
+}
+
+int waitAndReadKey (uint32_t ms) {
+   keyUart0 = EOF;
+   int key = wait(ms);
+   keyUart0 = EOF;
+   return key;
+}
+
+int main () {
+
+   #if defined(__AVR_ATmega644P__) || defined(__AVR_ATmega1284P__)
+   // Nano-644 LEDs (Green, Orange, Red)
+   DDRC |= (1 << PC7) | (1 << PC4) | (1 << PC3) | (1 << PC2);
+   PORTC &= ~((1 << PC7) | (1 << PC4) | (1 << PC3) | (1 << PC2));
+
+   // Nano-644 push button SW2
+   DDRC &= ~(1 << PC5); 
+   PORTC |= (1 << PC5); // enable internal pullup resistor
+   #endif
+
+   #ifdef __AVR_ATmega328P__
+   DDRB |= (1 << PB5);
+   PORTB &= ~(1 << PB5);
+   #endif
+
+   // UART0 interface on Nano-644
+   UCSR0A = (1 << U2X0);
+   UCSR0B = (1 << RXCIE0) | (1 << RXEN0) | (1 <<TXEN0);
+   UCSR0C = (1 << UCSZ01) | ( 1<< UCSZ00);
+   UBRR0H = 0;
+   UBRR0L = F_CPU / 8 / 115200 - 1;
+
+   TCCR2A = (1 << WGM21);
+   TCCR2B = (1 << CS21); // CTC, F_CPU/8=1.5MHz
+   OCR2A = 150; // 150/1.5E6 = 0.1ms
+   TIMSK2 = (1 << OCIE2A);
+
+   stdout = &mystdout;
+   stdin = &mystdin;
+
+   sei();
+
+   #if defined(__AVR_ATmega644P__) || defined(__AVR_ATmega1284P__)
+      TestUnit *unit[] = {
+         &led, &sw, &rgb, &seg7, &poti, &encoder, &r2r, &motor, &portExp, &lcd, &uart1, &modbus, &ieee485,
+         &i2cMaster, &i2cSlave, &i2cSparkfun, &rtc8563, &cc1101Send, &cc1101Receive
+      };
+   #endif
+
+   #ifdef __AVR_ATmega328P__
+      TestUnit *unit[] = {
+         &led, &sw, &rgb, &seg7, &poti, &encoder, &r2r, &motor, &portExp, &lcd,
+         &i2cMaster, &i2cSlave, &i2cSparkfun
+      };
+   #endif
+
+   detectHardwareVersion();
+
+   while (1) {
+      uint16_t i;
+      char s[4];
+      do {
+         printf_P(PSTR_LINEFEED);
+         printf_P(PSTR_DIVIDER);
+         printf_P(MAIN_CPP_PART_NAME); printf_P(PSTR(" / "));
+         printf_P(MAIN_CPP_DATE); printf_P(PSTR(" / "));
+         printf_P(MAIN_CPP_TIME);
+         printf_P(PSTR_DIVIDER);
+         if (hardwareVersion >= 1 && hardwareVersion <= 2) {
+            printf_P(PSTR("Hardware: %d"), hardwareVersion); 
+         } else {
+            printf_P(PSTR("ERROR: Invalid Hardware (%d)"), hardwareVersion); 
+            for(;;);
+         }
+         printf_P(PSTR_DIVIDER);
+         printf_P(PSTR_LINEFEED);
+         printf_P(PSTR("Available units:\n\n"));
+         for (i = 0; i < sizeof(unit) / sizeof(unit[0]); i++) {
+            TestUnit *pu = unit[i];
+            printf_P(PSTR("%3x ... "), i);
+            printf_P(pu->getName());
+            printf_P(PSTR_LINEFEED);
+         }
+         printf_P(PSTR("\nSelect unit: "));
+         rIndex = 0; wIndex = 0;
+         fgets(s, sizeof(s), stdin);
+      } while (sscanf(s, "%x", &i) != 1 || i < 0 || i >= sizeof(unit) / sizeof(unit[0]) );
+      
+      TestUnit *pu = unit[i];
+      printf_P(PSTR("\n\n[")); printf_P(pu->getName()); printf_P(PSTR("]: "));
+      keyUart0 = EOF;
+
+      pu->init();
+      for (uint8_t subtest = 0; subtest < 0xff; subtest++) {
+         printf_P(PSTR("\n%4d: "), subtest);
+         if (pu->run(subtest) < 0) {
+            break;
+         }
+         if (keyUart0 == 27) {
+            keyUart0 = EOF;   
+            break;
+         }
+         keyUart0 = EOF;
+      }
+      pu->cleanup();
+   }
+}
+
+uint64_t millis () {
+   volatile uint64_t millis = systemMillis;
+   ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
+      millis = systemMillis;
+   }
+   return millis;
+}
+
+#ifndef USART0_RX_vect
+   #define USART0_RX_vect USART_RX_vect
+#endif
+ISR (USART0_RX_vect) {
+   uint8_t b = UDR0;
+   keyUart0 = b;
+   uartBuffer[wIndex++] = b;
+   // printf_P(PSTR(" w%d(%02x)"), wIndex, b);
+   if (wIndex == rIndex) {
+      // buffer overflow, kick out oldest byte
+      rIndex++;
+   }
+}
+
+#ifdef USART1_RX_vect
+   ISR (USART1_RX_vect) {
+      uint8_t b = UDR1;
+      if (modbus.enabled) {
+         modbus.handleRxByte(b);
+      }
+      if (uart1.enabled) {
+         uart1.handleRxByte(b);
+      }
+      if (ieee485.enabled) {
+         ieee485.handleRxByte(b);
+      }
+   }
+#endif
+
+ISR (TWI_vect) {
+   if (i2cMaster.enabled) {
+      i2cMaster.handleTwiIrq();
+   } else if (i2cSlave.enabled) {
+      i2cSlave.handleTwiIrq();
+   } else if (i2cSparkfun.enabled) {
+      i2cSparkfun.handleTwiIrq();
+   } else {
+      TWCR = (1 << TWINT); // clear interrupt request bit and disable TWI
+   }
+}
+
+ISR (TIMER2_COMPA_vect) { // every 100us
+   static uint16_t timer500ms = 0;
+   static uint8_t timer100us = 0;
+
+   if (encoder.enabled) {
+      encoder.tick100us();
+   }
+   if (motor.enabled) {
+      motor.tick100us();
+   }
+
+   timer100us++;
+   if (timer100us >= 10) {
+      timer100us = 0;
+      if (timer1ms > 0) {
+         timer1ms--;
+      }
+      systemMillis++;
+      i2cMaster.tick1ms();
+      i2cSlave.tick1ms();
+      i2cSparkfun.tick1ms();
+      cc1101Send.tick1ms();
+      cc1101Receive.tick1ms();
+      #if defined(__AVR_ATmega644P__) || defined(__AVR_ATmega1284P__)
+         if (timerFlashRedLed > 0) {
+            if (--timerFlashRedLed < 128) {
+               PORTC &= ~(1 << PC2); // red LED
+            }
+         }
+         if (timerFlashGreenLed > 0) {
+            if (--timerFlashGreenLed < 128 ) {
+               PORTC &= ~(1 << PC4); // green LED
+            }
+         }
+      #endif
+   }
+
+   timer500ms++;
+   if (timer500ms >= 5000) {
+      #if defined(__AVR_ATmega644P__) || defined(__AVR_ATmega1284P__)
+         PORTC ^= (1 << PC3); // orange LED blinking
+      #endif
+      #ifdef __AVR_ATmega328P__
+         if (!seg7.enabled) {
+            PORTB ^= (1 << PB5); // LED L
+         }
+      #endif
+      timer500ms = 0;
+   }
+}
+
diff --git a/software/test-software/src/main.hpp b/software/test-software/src/main.hpp
new file mode 100644 (file)
index 0000000..7eeff16
--- /dev/null
@@ -0,0 +1,38 @@
+#ifndef MAIN_HPP
+#define MAIN_HPP
+
+#include <stdint.h>
+#include <avr/pgmspace.h>
+
+#define ENTER '\r'
+#define CTRLC '\003'
+#define ESCAPE 0x1b
+
+extern int wait (uint32_t ms);
+extern int waitAndReadKey (uint32_t ms);
+extern uint64_t millis ();
+
+extern uint8_t hardwareVersion;
+
+extern const char PSTR_DIVIDER[];
+extern const char PSTR_LINEFEED[];
+extern const char PSTR_ERROR[];
+extern const char PSTR_Done[];
+
+#if defined(__AVR_ATmega644P__) || defined(__AVR_ATmega1284P__)
+   extern "C" {
+      extern void flashRedLed (uint16_t ms);
+      extern void flashGreenLed (uint16_t ms);
+   }
+#endif
+
+
+class TestUnit {
+   public:
+      virtual int8_t run (uint8_t subtest) = 0;
+      virtual void init () = 0;
+      virtual void cleanup () = 0;
+      virtual PGM_P getName () = 0;
+};
+
+#endif
\ No newline at end of file
diff --git a/software/test-software/src/units/cc1101.cpp b/software/test-software/src/units/cc1101.cpp
new file mode 100644 (file)
index 0000000..aebe713
--- /dev/null
@@ -0,0 +1,753 @@
+#include <stdio.h>
+#include <avr/io.h>
+#include <avr/pgmspace.h>
+#include <util/delay.h>
+
+#include "cc1101.hpp"
+#include "../main.hpp"
+
+// 868MHz Modem E07-900MM1DS (chipset CC1101)
+// https://jlcpcb.com/partdetail/Chengdu_Ebyte_ElecTech-E07900MM10S/C5844212
+
+#ifdef __AVR_ATmega328P__
+
+   // Arduino-Nano-5V
+   // ------------------------------------
+   // not available
+
+   void Cc1101::init () {}
+   void Cc1101::cleanup () {}
+   void Cc1101::setChipEnableLow () {}
+   void Cc1101::setChipEnableHigh () {}
+   int8_t Cc1101::run (uint8_t subtest) { return -1; }
+   PGM_P Cc1101::getName () { return PSTR("?"); }
+
+#endif
+
+
+#if defined(__AVR_ATmega644P__) || defined(__AVR_ATmega1284P__)
+
+   // Nano-644
+   // --------------------------------------------------------
+   // PA4 ... nCS
+   // PB4 ... nSS (not used, but must be high, otherwise AVR SPI master not working)
+   // PB5 ... MOSI
+   // PB6 ... MISO
+   // PB7 ... SCK
+
+   // --------------------------------------------------------
+
+   // Output power table from CC1101 datasheet page 59
+   const Cc1101::PATABLE_INIT_ITEM_t PMEM_CC1101_PATABLE_INIT[] PROGMEM = {
+      { 0xc6, "9.6" }, // 9.6 dBm / 29.4mA (default value)
+      { 0xc0, "11"  }, //  11 dBm / 34.2mA
+      { 0xc5, "10"  }, //  10 dBm / 30.0mA
+      { 0xcd, "7"   }, //   7 dBm / 25.8mA
+      { 0x86, "5"   }, //   5 dBm / 19.9mA
+      { 0x50, "0"   }, //   0 dBm / 16.8mA
+      { 0x37, "-6"  }, //  -6 dBm / 16.4mA
+      { 0x26, "-10"  }, //  10 dBm / 14.5mA
+      { 0x1d, "-15"  }, //  15 dBm / 13.3mA
+      { 0x17, "-20"  }, //  20 dBm / 12.6mA
+      { 0x03, "-30"  }, //  30 dBm / 12.0mA
+   }; 
+   const Cc1101::Register_t PMEM_CC1101_REGISTER_INIT PROGMEM = { 
+      /* 0x00: iocfg2    0x0b (----) */ { Cc1101::CHIP_NOT_READY, 0, 0 }, // GDO2
+      /* 0x01: iocfg1    0x2e (----) */ { Cc1101::HIGH_IMPEDANCE_WHEN_CHIP_SELECT_HIGH, 0, 0 }, // GDO1/MISO
+      /* 0x02: iocfg0    0x06 (----) */ { Cc1101::SYNC_SENT_OR_RECEIVED_UNTIL_END_OF_PACKET, 0, 0 },  // GDO0 -> PA5
+      /* 0x03: fifothr   0x47 (0x07) */ { 7, 0, 1, 0 }, // fifo_thr = 0b111, close_in_rx=0, adc_retention = 1, (bit7=0)
+      /* 0x04: sync1     0x7a (0xd3) */ 0x7a,
+      /* 0x05: sync0     0x0e (0x91) */ 0x0e,
+      /* 0x06: pktlen    0x14 (0x3d) */ PACKET_LENGTH,
+      /* 0x07: pktctrl1  0x04 (----) */ { Cc1101::NO_ADDR_CHECK, 1, 0, 0, 0 }, // adr_chk=0b00, append_status=1, crc_autoflush=0, (bit4=0), pqt=0b000
+      /* 0x08: pktctrl0  0x05 (----) */ { Cc1101::FIXED, 1, 0, Cc1101::NORMAL_USE_FIFO, 0, 0 }, // length_config=0b01, crc_en=1, (bit3=0), pkt_format=0b00, white_data=0, (bit7=0)
+      /* 0x09: addr      0x00 (----) */ 0x00,
+      /* 0x0a: channr    0x00 (----) */ 0x00,
+      /* 0x0b: fsctrl1   0x06 (0x08) */ { 6, 0, 0 }, // frqu_if=6, (bit5=0), (bit76=0b00)
+      /* 0x0c: fsctrl0   0x00 (----) */ { 0 }, // freqoff = 0
+      /* 0x0d: frequ2    0x21 (----) */ 0x21,
+      /* 0x0e: frequ1    0x62 (----) */ 0x62,
+      /* 0x0f: frequ0    0x76 (----) */ 0x76,
+      /* 0x10: mdmcfg4   0xca (0x5b) */ { 0x0a, 0, 3 }, // drate_e=0x0a, chanbw_m=0, chanbw_e=3
+      /* 0x11: mdmcfg3   0xf8 (----) */ { 0xf8 }, // drate_m=0xf8
+      /* 0x12: mdmcfg2   0x16 (0x03) */ { Cc1101::SYNC_16_16_CARRIER_SENSE, 0, Cc1101::GFSK, 0}, // sync_mode=0b110, manchester_en=0, mod_format=0b001, dem_dcfilt_off=0
+      /* 0x13: mdmcfg1   0x22 (----) */ { 2, 0, Cc1101::FOUR, 0 }, // chanspc_e=0b10, bit32=0, num_preamble=0b010, fec_en=0
+      /* 0x14: mdmcfg0   0xf8 (----) */ { 0xf8 }, // chanspc_m = 0x08
+      /* 0x15: deviatn   0x40 (0x47) */ { 0, 0, 4, 0 }, // deviation_m=0, (bit3=0), deviation_e=4, (bit7=0)
+      /* 0x16: mcsm2     0x07 (----) */ { 7, 0, 0, 0 }, // rx_time=7 (NA), rx_time_qual=0, rx_time_rssi=0, (bit76=0b00)
+      /* 0x17: mcsm1     0x30 (----) */ { Cc1101::TXOFF_IDLE, Cc1101::RXOFF_IDLE, Cc1101::RSSI_BELOW_THRESHOLD__UNLESS_RECEIVE_PACKET, 0 }, // mcsm1 (txoff_mode=0b00, rxoff_mode=0b00, cca_mode=0b11, (bit76=0b00) )
+      /* 0x18: mcsm0     0x18 (----) */ { 0, 0, 2, Cc1101::IDLE_TO_RX_OR_TX, 0 }, // xosc_force_on=0, pin_ctrl_en=0, po_timeout=2 (149-155us), fs_autocal=0b01, (bit76=0b00)
+      /* 0x19: foccfg    0x16 (0x1d) */ 0x16,
+      /* 0x1a: bscfg     0x6c (0x1c) */ 0x6c,
+      /* 0x1b: agcctrl2  0x43 (0xc7) */ 0x43,
+      /* 0x1c: agcctrl1  0x49 (0x00) */ 0x49,
+      /* 0x1d: agcctrl0  0x91 (0xb2) */ 0x91,
+      /* 0x1e: worevt1   0x87 (----) */ 0x87,
+      /* 0x1f: worevt0   0x6b (----) */ 0x6b,
+      /* 0x20: worctrl   0xfb (0xf8) */ 0xfb,
+      /* 0x21: frend1    0x56 (0xb6) */ 0x56,
+      /* 0x22: frend0    0x10 (----) */ { 0, 0, 1, 0 }, // pa_power = 0, (bit3 = 0), lodiv_buf_current = 1, (bit76=0b00)
+      /* 0x23: fscal3    0xe9 (0xea) */ 0xe9,
+      /* 0x24: fscal2    0x2a (----) */ 0x2a,
+      /* 0x25: fscal1    0x00 (----) */ 0x00,
+      /* 0x26: fscal0    0x1f (0x11) */ 0x1f,
+      /* 0x27: rcctrl1   0x41 (----) */ 0x41,
+      /* 0x28: rcctrl0   0x00 (----) */ 0x00,
+      /* 0x29: fstest    0x59 (----) */ 0x59,
+      /* 0x2a: ptest     0x7f (----) */ 0x7f,
+      /* 0x2b: agctest   0x3f (----) */ 0x3f,
+      /* 0x2c: test2     0x81 (0x88) */ 0x81,
+      /* 0x2d: test1     0x35 (0x31) */ 0x35,
+      /* 0x2e: test0     0x09 (0x0b) */ 0x09
+   };
+
+   // --------------------------------------------------------------------------------------
+
+   int8_t Cc1101::runSend (uint8_t subtest) {
+      if (subtest == 0) {
+         bool ok = true;
+         GDOx_CFG_t cfg0 = SYNC_SENT_OR_RECEIVED_UNTIL_END_OF_PACKET;
+         ok &= writeRegister(0x02, cfg0);
+         return ok;
+      } else if (subtest > 1) {
+         return -1;
+      }
+
+      printf_P(PSTR(" use + and - for power change (other key -> back to default)"));
+
+      uint16_t cnt = 0;
+      uint8_t paIndex = 0;
+      uint8_t pa_power;
+      int key = EOF;
+      do {
+         // if ((cnt & 0x0f) == 0) {
+         //    paIndex = (cnt >> 4) & 0x07;
+         //    union { FREND0_t value; uint8_t byte; } frend0 = { paIndex, 0, 1, 0 };
+         //    printf_P(PSTR("\n switch power "));
+         //    if (writeRegister(FREND0, frend0.byte )) {
+         //       printf_P(PSTR("to %d (FREND0 = 0x%02X)"), frend0.value.pa_power, frend0.byte);
+         //    } else {
+         //       printf_P(PSTR("fails"));
+         //    }
+         // }
+         if (key != EOF) {
+            uint8_t max = (sizeof(PMEM_CC1101_PATABLE_INIT) / sizeof(PMEM_CC1101_PATABLE_INIT[0])) - 1;
+            switch (key) {
+               case '+': if (paIndex == 0) paIndex = 1; else if (paIndex > 1) paIndex--; break;
+               case '-': if (paIndex == 0) paIndex = max; else if (paIndex < max) paIndex++; break;
+               default: paIndex = 0; break; // default value
+            }
+
+            const PATABLE_INIT_ITEM_t *values = PMEM_CC1101_PATABLE_INIT;
+            
+            memcpy_P(&pa_power, &((values[paIndex]).pa_power), 1);
+            PGM_P info = (values[paIndex]).dbm;
+            printf_P(PSTR("\n switch power to ")); printf_P(info);
+            printf_P(PSTR("dBm "));
+            if (writeRegister(0x3e, pa_power )) {
+               printf_P(PSTR("(PATABLE = 0x%02X)"), pa_power);
+            } else {
+               printf_P(PSTR("fails"));
+            }
+         }
+
+         MainRadioControlState state = UNKNOWN;
+         uint8_t data[PACKET_LENGTH];
+         printf_P(PSTR("\n [%04x]: state="), cnt++);
+         if (!readStatusRegister(MARCSTATE, (uint8_t *)&state)) {
+            printf_P(PSTR("E1"));
+         } else {
+            printf_P(PSTR("0x%02x"), state);
+         }
+         data[0] = pa_power;
+         printf_P(PSTR(" --> send %d bytes (HEX: %02X"), sizeof(data), paIndex);
+         for (uint8_t i = 1; i < sizeof(data); i++) {
+            data[i] = uint8_t((cnt + i - 1) & 0xff);
+            printf_P(PSTR(" %02X"), data[i]);
+         }
+         printf_P(PSTR("] -> "));
+
+         if (!sendData(data, sizeof(data))) {
+            flashRedLed(100);
+            printf_P(PSTR("E1"));
+            continue;
+         }
+         flashGreenLed(100);
+         key = waitAndReadKey(2000);
+         printf_P(PSTR("OK"));
+         
+      } while (key == EOF || (key != ESCAPE));
+
+      return 0;
+   }
+
+   int8_t Cc1101::runReceive (uint8_t subtest) {
+      if (subtest == 0) {
+         bool ok = true;
+         GDOx_CFG_t cfg0 = SYNC_SENT_OR_RECEIVED_UNTIL_END_OF_PACKET;
+         ok &= writeRegister(0x02, cfg0);
+         return ok;
+      } else if (subtest > 1) {
+         return -1;
+      }
+
+      int key = EOF;
+      uint16_t cnt = 0;
+      MainRadioControlState state;
+      while (key == EOF) {
+         printf_P(PSTR("\n [%04x] => start ... "), cnt++);
+         strobe(SRX);
+         do {
+            state = UNKNOWN;
+            if (!readStatusRegister(MARCSTATE, (uint8_t *)&state)) {
+               printf_P(PSTR("E1"));
+               _delay_ms(500);
+               break;
+            }
+            if (wait(0) != EOF) {
+               printf_P(PSTR("cancelled"));
+               _delay_ms(500);
+               break;
+            }
+            if (state == IDLE) {
+               printf_P(PSTR("? (IDLE)"));
+               _delay_ms(500);
+               break;
+            }
+
+         } while (state != RX && state != RXFIFO_OVERFLOW);
+
+         if (state != RX && state != RXFIFO_OVERFLOW) {
+            continue;
+         }
+         printf_P(PSTR("OK, receive ... "));
+
+         uint8_t length;
+         uint8_t lastLength = 0xff;
+         do {
+            uint8_t data[PACKET_LENGTH];
+            if (!readStatusRegister(MARCSTATE, (uint8_t *)&state)) {
+               printf_P(PSTR("E2"));
+               _delay_ms(500);
+               continue;
+            }
+            if (wait(0) != EOF) {
+               printf_P(PSTR("cancelled"));
+               _delay_ms(500);
+               return -1;
+            }
+            if (!readStatusRegister(RXBYTES, (uint8_t *)&length)) {
+               printf_P(PSTR("E2"));
+               _delay_ms(500);
+               continue;
+            }
+            if (lastLength != length) {
+               lastLength = length;
+            }
+            if (length >= PACKET_LENGTH) {
+               if (receiveData(data, &length, sizeof(data))) {
+                  if (length > 0) {
+                     flashGreenLed(100);
+                     printf_P(PSTR(" -> "));
+                     if (status.receivePacketStatusValid) {
+                        printf_P(PSTR(" RSSI=%d, LQI=%d, CRC "), status.receivedPacketStatus.value.rssi, status.receivedPacketStatus.value.lqi);
+                        if (status.receivedPacketStatus.value.crcOk) {
+                           printf_P(PSTR("OK, "));
+                        } else {
+                           printf_P(PSTR("ERROR, "));
+                        }
+                     }
+                     printf_P(PSTR("%d data bytes (HEX): "), length);
+                     for (uint8_t i = 0; i < length; i++) {
+                        printf_P(PSTR(" %02X"), data[i]);
+                     }
+                     printf_P(PSTR(" ... "));
+                  }
+               }
+            }
+
+         } while (state == RX || state == RXFIFO_OVERFLOW);
+
+         printf_P(PSTR("done (state=0x%02x)"), state);
+      }
+      return -1;
+   }
+
+   int8_t Cc1101::runTest (uint8_t subtest) {
+      if (subtest > 0) {
+         return -1;
+      }
+      while (wait(0) == EOF) {}
+      return -1;
+   }
+
+
+   int8_t Cc1101::run (uint8_t subtest) {
+      switch (mode) {
+         case Send: return runSend(subtest);
+         case Receive: return runReceive(subtest);
+         case Test: return runTest(subtest);
+         default: return -1;
+      }
+   }
+
+   uint8_t Cc1101::sendSpiByte (uint8_t b) {
+      SPDR = b;
+      while (!(SPSR & (1<<SPIF))) {}
+      return SPDR;
+   }
+
+   void Cc1101::triggerOn () {
+      PORTB |= (1 << PB0);
+   }
+
+   void Cc1101::triggerOff () {
+      PORTB &= ~(1 << PB0);
+   }
+
+   void Cc1101::triggerToggle () {
+      PORTB ^= (1 << PB0);
+   }
+
+   PGM_P Cc1101::getName () {
+      switch (mode) {
+         case Send: return PSTR("CC-1101-Send");
+         case Receive: return PSTR("CC-1101-Receive");
+         case Test: return PSTR("CC-1101-Test");
+         default: return PSTR("CC-1101-?");
+      }
+   }
+
+   void Cc1101::init () {
+
+      #ifdef __AVR_ATmega1284P__
+         // Nano-1284
+         DDRC |= (1 << PC6);
+         PORTC |= (1 << PC6); // modem voltage 3.3V ON
+      #endif
+
+      // trigger for debugging
+      PORTB &= ~(1 << PB0);
+      DDRB |= ( 1 << PB0);
+
+      PRR0 &= (1 << PRSPI);
+      
+      // nCs
+      PORTA |= (1 << PA4);
+      DDRA |= (1 << PA4);
+      // PORTB/DDRB must be configured before SPCR !!
+      
+      PORTB |= (1 << PB4); // nSS must be HIGH, otherwise SPI master will not become active!!
+      DDRB &= ~(1 << PB6); // MISO
+      DDRB |= (1 << PB7) | (1 << PB5) | (1 << PB4); // SPI SCK (=PB7) and SPI MOSI (=PB5)
+
+      // SPCR |= (1 << SPE) | (1 << MSTR) | (1 << SPR1) | (1 << SPR0); // SPI enable , Master, f=12MHz/128=93,75kHz
+      SPCR = (1 << SPE) | (1 << MSTR); // SPI enable , Master, f=12MHz/4 = 3MHz
+
+      // GDO0
+      PORTA |= (1 << PA5);
+      DDRA &= ~(1 << PA5);
+
+      printf_P(PSTR(" reset CC1101 ... "));
+      if (!resetCC1101()) {
+         printf_P(PSTR("fails"));
+         return;
+      }
+      printf_P(PSTR("OK, init register ... "));;
+      if (!initRegister()) {
+         printf_P(PSTR("fails"));
+         return;
+      }
+      printf_P(PSTR("OK, init PATABLE ... "));;
+      if (!initPaTable()) {
+         printf_P(PSTR("fails"));
+         return;
+      }
+      printf_P(PSTR("OK"));
+
+      debugPrint.byte = 0xff; // print all
+      printRegisters();
+      debugPrint.byte = 0x00; // print nothing
+
+   }
+
+   void Cc1101::cleanup () {
+      // trigger for debugging
+      PORTB &= ~(1 << PB0);
+      DDRB &= ~( 1 << PB0);
+      
+      SPCR = 0;
+      PORTB &= ~(1 << PB4);
+      DDRB &= ~((1 << PB7) | (1 << PB6) | (1 << PB5) | (1 << PB4));
+      DDRA &= ~((1 << PA5) | (1 << PA4));
+      PORTA &= ~((1 << PA5) | (1 << PA4));
+
+      #ifdef __AVR_ATmega1284P__
+         // Nano-1284
+         DDRC &= ~(1 << PC6);
+         PORTC &= ~(1 << PC6); // modem voltage 3.3V OFF
+      #endif
+   }
+
+   void Cc1101::setChipEnableLow () {
+      PORTA &= ~(1 << PA4);
+   }
+
+   void Cc1101::setChipEnableHigh () {
+      PORTA |= (1 << PA4);
+   }
+
+   bool Cc1101::isMisoHigh () {
+      return (PINB & (1 << PB6)) != 0;
+   }
+
+   bool Cc1101::isGd0High () {
+      return (PINA & (1 << PA5)) != 0;
+   }
+
+   bool Cc1101::waitForMisoLow () {
+      timer = 10;
+      while (isMisoHigh()) {
+         if (timer == 0) {
+            return false;
+         }
+      }
+      return true;
+   }
+
+   bool Cc1101::waitForGD0High () {
+      timer = 100;
+      while (!isGd0High()) {
+         if (timer == 0) {
+            return false;
+         }
+      }
+      return true;
+   }
+
+   bool Cc1101::waitForGD0Low () {
+      timer = 100;
+      while (isGd0High()) {
+         if (timer == 0) {
+            return false;
+         }
+      }
+      return true;
+   }
+
+   bool Cc1101::strobe (StrobeCommand_t command) {
+          bool ok = true;
+      setChipEnableLow();
+      ok &= waitForMisoLow();
+          sendSpiByte(command & 0x3f);
+      setChipEnableHigh();
+          return ok;
+   }
+
+   bool Cc1101::writeRegister (uint8_t addr, uint8_t value) {
+      bool ok = true;
+      
+      setChipEnableLow();
+      ok &= waitForMisoLow();
+      sendSpiByte(addr);
+      status.spiResponse.byte = sendSpiByte(value);
+      status.spiResponseValid = 1;
+      setChipEnableHigh();
+      
+      if (debugPrint.print.writeRegister) {
+         printf_P(PSTR("\n [writeRegister(0x%02x, 0x%02x) -> "), addr, value);
+         if (ok && status.receivePacketStatusValid) {
+            printf_P(PSTR("status=0x%02x]"), status.spiResponse.byte);
+         } else {
+            printf_P(PSTR("ERR]"));
+         }
+      }
+
+      return ok;
+   }
+
+   bool Cc1101::writeRegisters (uint8_t addr, uint8_t *buffer, uint8_t length) {
+      bool ok = true;
+      uint8_t l = length;
+      uint8_t *p = buffer;
+
+      addr = (addr & 0x3f) | 0x40; // write burst
+      setChipEnableLow();
+      ok &= waitForMisoLow();
+      sendSpiByte(addr);
+      while (length-- > 0) {
+         sendSpiByte(*buffer++);
+      }
+      setChipEnableHigh();
+
+      if (debugPrint.print.writeRegisters) {
+         printf_P(PSTR("\n [writeRegisters(0x%02x): "), addr & 0x3f);
+         while (l-- > 0) {
+            printf_P(PSTR(" 0x%02x"), *p++);
+         }
+         printf_P(PSTR(" -> "));
+         if (ok && status.receivePacketStatusValid) {
+            printf_P(PSTR("status=0x%02x]"), status.spiResponse.byte);
+         } else {
+            printf_P(PSTR("ERR]"));
+         }
+      }
+      
+      return ok;
+   }
+
+
+   bool Cc1101::readRegister (uint8_t addr, uint8_t *value) {
+      bool ok = true;
+      addr = (addr & 0x3f) | 0x80;
+      setChipEnableLow();
+      ok &= waitForMisoLow();
+      status.spiResponse.byte = sendSpiByte(addr);
+      status.spiResponseValid = 1;
+      *value = sendSpiByte(0);
+      setChipEnableHigh();
+
+      if (debugPrint.print.readRegister) {
+         printf_P(PSTR("\n [readRegister(0x%02x) -> "), addr & 0x3f);
+         if (ok && status.spiResponseValid) {
+            printf_P(PSTR("0x%02x,status=0x%02x]"), *value, status.spiResponse.byte);
+         } else {
+            printf_P(PSTR("ERR]"));
+         }
+      }
+      return ok;
+   }
+   
+   bool Cc1101::readRegisters (uint8_t addr, uint8_t *buffer, uint8_t length) {
+      bool ok = true;
+
+      uint8_t l = length;
+      uint8_t *p = buffer;
+
+      addr |= 0xc0; // read burst
+      setChipEnableLow();
+      ok &= waitForMisoLow();
+      sendSpiByte(addr);
+      while (length-- > 0) {
+         *buffer++ = sendSpiByte(0);
+      }
+      setChipEnableHigh();
+      
+      if (debugPrint.print.readRegisters) {
+         printf_P(PSTR("\n [readRegisters(0x%02x, ..., %d)] -> "), addr & 0x3f, l);
+         if (ok) {
+            while (l-- > 0) {
+               printf_P(PSTR(" 0x%02x"), *p++);
+            }
+            printf_P(PSTR("]"));
+         } else {
+            printf_P(PSTR("ERR]"));
+         }
+      }
+      
+      return ok;
+   }
+
+   bool Cc1101::readStatusRegister (StatusAddress_t addr, uint8_t *value) {
+       if (addr < 0x30 || addr > 0x3d) {
+         return false;
+      } else {
+         return readRegisters(addr, value, 1);
+      }
+   }
+
+   bool Cc1101::sendData (uint8_t *buffer, uint8_t length) {
+      timer = 10;
+      uint8_t ok = true;
+      uint8_t txbytes;
+      uint8_t states[16]; uint8_t statesIndex = 0;
+      // ok &= writeRegister(0x3f, length);
+      ok &= writeRegisters(0x3f, buffer, length);
+      ok &= readStatusRegister(TXBYTES, &txbytes);
+      ok &= (txbytes == 20);
+      if (ok) {
+         ok &= strobe(STX);    // start sending bytes
+         // ok &= waitForGD0High(); // wait for start of SYNC
+         if (ok) {
+            uint8_t lastState = 0;
+            uint8_t state;
+            do {
+               ok &= readStatusRegister(MARCSTATE, &state);
+               if (state != lastState && statesIndex < 16) {
+                  states[statesIndex++] = state;
+               }
+               lastState = state;
+            } while (ok && state != 0x13 && timer > 0);
+            ok &= state == 0x13; // check if in state TX
+         }
+         if (ok) {
+            uint8_t lastTxbytes = 20;
+            do {
+               ok &= readStatusRegister(TXBYTES, &txbytes);
+               if (lastTxbytes != txbytes) {
+                  lastTxbytes = txbytes;
+               }
+            } while (ok && txbytes > 0 && timer > 0);
+            ok &= txbytes == 0;
+         }
+      }
+      if (!ok) {
+         strobe(SFTX); // flush TXfifo
+      } else {
+         uint8_t state;
+         uint8_t lastState = 0;
+         do {
+            ok &= readStatusRegister(MARCSTATE, &state);
+            if (lastState != state) {
+               lastState = state;
+            }
+         } while (ok && state != 0x01 && timer > 0);
+         ok &= state == 0x01; // check if in state IDLE
+      }
+      // ok &= waitForGD0Low(); // wait for end of package
+
+      if (ok) {
+         printf_P(PSTR(" States["));
+         for (uint8_t i = 0; i < statesIndex; i++) {
+            printf_P(PSTR(" 0x%02X"), states[i]);
+         }
+         printf_P(PSTR("] "));
+      }
+
+      return ok;
+   }
+
+   bool Cc1101::receiveData (uint8_t *buffer, uint8_t *receivedLength, uint8_t maxBufferSize) {
+      bool ok = true;
+      STATUS_RXBYTES_t status;
+      uint8_t lastLength = 0;
+      uint8_t length = 0;
+      *receivedLength = 0;
+      timer = 50;
+
+      this->status.receivePacketStatusValid = 0;
+      ok &= readStatusRegister(RXBYTES, &status.byte);
+      if (ok && status.rxbytes.num_rxbytes > 0) {
+         length = status.rxbytes.num_rxbytes;
+         
+         do {
+            _delay_us(400); // 20 Bytes in 4ms -> 200us/Byte -> CC1101 datasheet page 56: "twice that of which RF bytes are recived"
+            lastLength = length;
+            ok &= readStatusRegister(RXBYTES, &status.byte);
+            if (ok) {
+               // printf_P(PSTR(" [rxbytes=%d] "), status.byte);
+               length = status.byte & 0x7f;
+            }
+            ok &= timer > 0;
+         } while (ok && lastLength != length);
+         
+         if (ok) {
+            uint8_t extraBytesCount = this->status.receivePacketStatusEnabled ? 2 : 0;
+            if ((PACKET_LENGTH + extraBytesCount) != length) {
+               printf_P(PSTR(" ERROR[receive %d bytes, expect %d bytes] "), *receivedLength, PACKET_LENGTH + extraBytesCount);
+               *receivedLength = 0;
+               ok = false;
+            } else {
+               printf_P(PSTR(" OK[receive %d bytes] "), length);
+               *receivedLength = PACKET_LENGTH < maxBufferSize ? length - extraBytesCount : maxBufferSize;
+               ok &= readRegisters(0xff, buffer, *receivedLength);
+               if (!ok) {
+                  *receivedLength = 0;
+               } else {
+                  length -= *receivedLength;
+                  if (length > extraBytesCount) {
+                     printf_P(PSTR(" [WARN: buffer to small] "));
+                     while (length > extraBytesCount) {
+                        uint8_t byte;
+                        ok &= readRegister(0xff, &byte);
+                        ok = false;
+                        length--;
+                     }
+                  }
+                  if (length > 0) {
+                     ok &= readRegisters(0xff, this->status.receivedPacketStatus.byte, 2);
+                     if (ok) {
+                        this->status.receivePacketStatusValid = 1;
+                     }                     
+                  }
+               }
+            }
+         }
+      }
+      ok &= strobe(SFRX);
+     
+      return ok;
+   }
+
+
+   void Cc1101::printRegisters () {
+      const Register_t *regValues = &PMEM_CC1101_REGISTER_INIT;
+      printf_P(PSTR("\n"));
+      for (uint8_t addr = 0; addr < sizeof(Register_t); addr++) {
+         bool ok = true;
+         uint8_t regValue, value;
+         memcpy_P(&regValue, ((const uint8_t *)regValues) + addr, 1);
+         ok &= readRegister(addr, &value);
+         if (value != regValue) { printf_P(PSTR(" != 0x%02x"), regValue); }
+      }
+      uint8_t data[8];
+      for (uint8_t addr = 0x30; addr <= 0x3d; addr++) {
+         readStatusRegister((StatusAddress_t)addr, data);
+      }
+      readRegisters(0x3e, data, 8); // PATABLE
+   }
+
+   bool Cc1101::resetCC1101 () {
+      bool ok = true;
+      setChipEnableLow();
+      _delay_us(10);
+      setChipEnableHigh();
+      _delay_us(50);
+      setChipEnableLow();
+      ok &= waitForMisoLow();
+      ok &= strobe(SRES);
+      ok &= waitForMisoLow();
+      
+
+      return ok;
+   }
+
+   bool Cc1101::initRegister () {
+      const Register_t *regValues = &PMEM_CC1101_REGISTER_INIT;
+      bool ok = true;
+      // DebugPrint_t tmp = debugPrint.print;
+      // debugPrint.byte = 0xff; // print all
+      // printRegisters();
+
+      for (uint8_t addr = 0; ok && addr < sizeof(Register_t); addr++) {
+         uint8_t regValue;
+         memcpy_P(&regValue, ((const uint8_t *)regValues) + addr, 1);
+         ok &= writeRegister(addr, regValue);
+         if ((addr == 0x07) && (regValue & 0x04)) {
+            status.receivePacketStatusEnabled = true;
+         }
+      }
+
+      // debugPrint.print = tmp;
+      return ok;
+   }
+
+   bool Cc1101::initPaTable () {
+      const PATABLE_INIT_ITEM_t *values = PMEM_CC1101_PATABLE_INIT;
+      uint8_t patable[8];
+      uint8_t initValuesIndex = 0;
+      for (uint8_t i = 0; i < 8; i++) {
+         memcpy_P(&patable[i], &(values[initValuesIndex++].pa_power), 1);
+         if (initValuesIndex >= (sizeof(PMEM_CC1101_PATABLE_INIT) / sizeof(PATABLE_INIT_ITEM_t))) {
+            initValuesIndex = 0;
+         }
+         if (debugPrint.print.writePaTable) {
+            printf_P(PSTR("\nPTABLE %d: 0x%02x"), i, patable[i]);
+         }
+      }
+      return writeRegisters(0x3e, patable, 8);
+   }
+
+#endif
+
+
diff --git a/software/test-software/src/units/cc1101.hpp b/software/test-software/src/units/cc1101.hpp
new file mode 100644 (file)
index 0000000..a861f09
--- /dev/null
@@ -0,0 +1,281 @@
+#ifndef CC1101_HPP
+#define CC1101_HPP
+
+#include <stdint.h>
+#include "../main.hpp"
+#include <avr/pgmspace.h>
+
+class Cc1101 : public TestUnit {
+   public:
+      #define PACKET_LENGTH 20
+
+   public:
+      typedef enum { Test, Send, Receive  } Cc1101Mode;
+      typedef enum { _IDLE = 0, _RX = 1, _TX = 2, _FSTXON = 3, _CALIBRATE = 4, _SETTLING = 5, _RXFIFO_OVFL = 6, _TXFIFO_UNFL = 7 } StatusState_t;
+      typedef struct { uint8_t fifoBytes:4; StatusState_t state:3; uint8_t chipNotReady:1; } Status_t;
+      typedef struct { int8_t rssi:8; uint8_t lqi:7; uint8_t crcOk:1; } ReceivedPacketStatus_t; // CC1101 datasheet page 37
+      union {
+         struct {
+            union { 
+               Status_t value;
+               uint8_t byte;
+            } spiResponse;
+            union {
+               ReceivedPacketStatus_t value;
+               uint8_t byte [2];
+            } receivedPacketStatus;
+            uint8_t spiResponseValid: 1;
+            uint8_t receivePacketStatusEnabled:1;
+            uint8_t receivePacketStatusValid:1;
+         };
+         uint32_t dword;
+      } status;
+
+   public:
+      Cc1101 (Cc1101Mode mode) { timer = 0; this->mode = mode; status.dword = 0; }
+      virtual void init ();
+      virtual void cleanup ();
+      virtual int8_t run (uint8_t subtest);
+      virtual PGM_P getName ();
+   
+   public:
+      void tick1ms () { if (timer > 0) timer--; };
+
+   private:
+      typedef enum { SRES = 0x30, SCAL = 0x33, SRX = 0x34, STX = 0x35, SFRX = 0x3a, SFTX = 0x3b, SIDLE = 0x36 } StrobeCommand_t;
+      typedef enum { MARCSTATE = 0x35, TXBYTES = 0x3A, RXBYTES = 0x3B } StatusAddress_t;
+      typedef struct { uint8_t writeRegister:1; uint8_t writeRegisters:1; uint8_t readRegister:1; uint8_t readRegisters:1; uint8_t writePaTable:1; } DebugPrint_t;
+
+   private:
+      int8_t runSend (uint8_t subtest);
+      int8_t runReceive (uint8_t subtest);
+      int8_t runTest (uint8_t subtest);
+      uint8_t sendSpiByte (uint8_t b);
+      void triggerOn();
+      void triggerOff();
+      void triggerToggle ();
+      void setChipEnableLow ();
+      void setChipEnableHigh ();
+      bool isMisoHigh ();
+      bool isGd0High ();
+      bool waitForMisoLow ();
+      bool waitForGD0High ();
+      bool waitForGD0Low ();
+      bool strobe (StrobeCommand_t strobe);
+      bool writeRegister (uint8_t addr, uint8_t value);
+      bool writeRegisters (uint8_t addr, uint8_t *buffer, uint8_t length);
+      bool readRegister (uint8_t addr, uint8_t *value);
+      bool readRegisters (uint8_t addr, uint8_t *buffer, uint8_t length);
+      bool readStatusRegister (StatusAddress_t addr, uint8_t *value);
+      bool sendData (uint8_t *buffer, uint8_t length);
+      bool receiveData (uint8_t *buffer, uint8_t *receivedLength, uint8_t maxBufferSize);
+      void printRegisters ();
+
+      bool resetCC1101 ();
+      bool initRegister ();
+      bool initPaTable ();
+      
+
+   private:
+      Cc1101Mode mode;
+      uint8_t timer;
+      union {
+         DebugPrint_t print;
+         uint8_t byte;
+      } debugPrint;
+      
+
+   public:
+      typedef enum {
+         IOCFG2 = 0x00, IOCFG1 = 0x01, IOCFG0 = 0x02,
+         FIFOTHR = 0x03,
+         SYNC1 = 0x04, SYNC0 = 0x05,
+         PKTLEN = 0x06,
+         PKTCTRL1 = 0x07, PKTCTRL0 = 0x08,
+         ADDR = 0x00, CHANNR = 0x0a,
+         FSCTRL1 = 0x0b, FSCTRL0 = 0x0c,
+         FREQU2 = 0x0d, FREQU1 = 0x0e, FREQU0 = 0x0f,
+         MDMCFG4 = 0x10, MDMCFG3 = 0x11, MDMCFG2 = 0x12, MDMCFG1 = 0x13, MDMCFG0 = 0x14,
+         DEVIATN = 0x15,
+         MCSM2 = 0x16, MCSM1 = 0x17, MCSM0 = 0x18,
+         FOCCFG = 0x19, BSCFG = 0x1a,
+         AGCCTRL2 = 0x1b, AGCCTRL1 = 0x1c, AGCCTRL0 = 0x1d,
+         WOREVT1 = 0x1e, WOREVT0 = 0x1f, WORCTRL = 0x20,
+         FREND1 = 0x21, FREND0 = 0x22,
+         FSCAL3 = 0x23, FSCAL2 = 0x24, FSCAL1 = 0x25, FSCAL0 = 0x26,
+         RCCTRL1 = 0x27, RCCTRL0 = 0x28,
+         FSTEST = 0x29, PTEST = 0x2a, AGCTEST = 0x2b,
+         TEST2 = 0x2c, TEST1 = 0x2d, TEST0 = 0x2e
+      } RegisterAddress_t;
+
+      typedef enum {
+         RX_FIFO_FILLED_OR_ABOVE_THRESHHOLD = 0x00,
+         RX_FIFO_FILLED_OR_ABOVE_THRESHHOLD_OR_END_OF_PACKAGE_UNTIL_FIFO_EMPTY = 0x01,
+         TX_FIFO_FILLED_OR_ABOVE_THRESHHOLD = 0x02,
+         TX_FIFO_FULL_UNTIL_BELOW_THRESHHOLD = 0x03,
+         RX_FIFO_OVERFLOW_UNTIL_FIFO_FLUSHED = 0x04,
+         TX_FIFO_UNDERFLOW_UNTIL_FIFO_FLUSHED = 0x05,
+         SYNC_SENT_OR_RECEIVED_UNTIL_END_OF_PACKET = 0x06,
+         PACKET_RECEIVED_WITH_CRC_OK_UNTIL_FIRST_BYTE_READ_FROM_RXFIFO = 0x07,
+         PREAMBLE_QUALITY_REACHED_UNTIL_REENTER_RX = 0x08,
+         RSSI_LEVEL_BELOW_THRESHOLD = 0x09,
+         LOCK_DETECTOR_OUTPUT = 0x0a,
+         SERIAL_CLOCK = 0x0b,
+         SERIAL_SYNCHRONOUS_DATA_OUTPUT = 0x0c,
+         SERIAL_ASYNC_DATA_OUTPUT = 0x0d,
+         CARRIER_DETECTED_UNTIL_ENTER_IDLE = 0x0e,
+         CRC_OK_UNTIL_REENTER_RX = 0x0f,
+         RX_HARD_DATA_1 = 0x16,
+         RX_HARD_DATA_0 = 0x17,
+         PA_PD = 0x1b,
+         LNA_PD = 0x1c,
+         RX_SYMBOL_TICK = 0x1d,
+         WAKEUP_ON_RECEIVE_EVENT0 = 0x24,
+         WAKEUP_ON_RECEIVE_EVENT1 = 0x25,
+         CLK_256 = 0x26,
+         CLK_32K = 0x27,
+         CHIP_NOT_READY = 0x29,
+         XOSC_STABLE = 0x2b,
+         HIGH_IMPEDANCE_WHEN_CHIP_SELECT_HIGH = 0x2e,
+         HW_TO_0 = 0x2f,
+         CLK_XOSC = 0x30,
+         CLK_XOSC_DIV_1P5 = 0x31,
+         CLK_XOSC_DIV_2 = 0x32,
+         CLK_XOSC_DIV_3 = 0x33,
+         CLK_XOSC_DIV_4 = 0x34,
+         CLK_XOSC_DIV_6 = 0x35,
+         CLK_XOSC_DIV_8 = 0x36,
+         CLK_XOSC_DIV_12 = 0x37,
+         CLK_XOSC_DIV_16 = 0x38,
+         CLK_XOSC_DIV_24 = 0x39,
+         CLK_XOSC_DIV_32 = 0x3a,
+         CLK_XOSC_DIV_48 = 0x3b,
+         CLK_XOSC_DIV_64 = 0x3c,
+         CLK_XOSC_DIV_96 = 0x3d,
+         CLK_XOSC_DIV_128 = 0x3e,
+         CLK_XOSC_DIV_192 = 0x3f
+      } GDOx_CFG_t;
+
+      typedef enum { 
+         SLEEP = 0,
+         IDLE = 1,
+         XOFF = 2,
+         MANCAL_VCOON = 3,
+         MANCAL_REGON = 4,
+         MANCAL = 5,
+         FS_WAKEUP_VCOON = 6,
+         FS_WAKEUP_REGON = 7,
+         CALIBRATE_START = 8,
+         SETTLING_BWBOOST = 9,
+         SETTLING_FS_LOCK = 10,
+         SETTLIN_IFADCON = 11,
+         CALIBRATE_END = 12,
+         RX = 13,
+         RX_END = 14,
+         RX_RST = 15,
+         TXRX_SETTLING = 16,
+         RXFIFO_OVERFLOW = 17,
+         FXTXON = 18,
+         TX = 19,
+         TX_END = 20,
+         RXTX_SETTLING = 21,
+         TXFIFO_UNDERFLOW = 22,
+         UNKNOWN = 255
+      } MainRadioControlState;
+
+      typedef struct { uint8_t pa_power; const char dbm[4]; } PATABLE_INIT_ITEM_t;
+
+      typedef enum { NO_ADDR_CHECK = 0, CHECK_NO_BROADCAST = 1, CHECK_WITH_BROADCAST_0 = 2, CHECK_WITH_BROADCAST_0_AND_255 = 3 } ADR_CHK_t;
+      typedef enum { FIXED = 0, VARIABLE = 1, INFINITE = 2 } LENGTH_CONFIG_t;
+      typedef enum { NORMAL_USE_FIFO = 0, SYNC_SERIAL = 1, RANDOM_TX = 2, ASYNC_SERIAL = 3 } PKT_FORMAT_t;
+      typedef enum { FSK2 = 0, GFSK = 1, ASK_OOK = 3, FSK4 = 4, MSK = 7 } MOD_FORMAT_t;
+      typedef enum { NO_SYNC = 0, SYNC_15_16 = 1, SYNC_16_16 = 2, SYNC_30_32 = 3, CARRIER_SENSE = 4, SYNC_15_16_CARRIER_SENSE = 5, SYNC_16_16_CARRIER_SENSE = 6 , SYNC_30_326_CARRIER_SENSE = 7} SYNC_MODE_t;
+      typedef enum { TWO = 0, THREE = 1, FOUR = 2, SIX = 3, EIGHT = 4, TWELVE = 5, SIXTEEN = 6, TWENTYFOUR = 7 } NUM_PREAMBLE_t;
+      typedef enum { TXOFF_IDLE = 0, TXOFF_FSTXON = 1, STAY_IN_TX = 2, TXOFF_RX = 3 } TXOFF_MODE_t;
+      typedef enum { RXOFF_IDLE = 0, RXOFF_FSTXON = 1, RXOFF_TX = 2, STAY_IN_RX = 3 } RXOFF_MODE_t;
+      typedef enum { ALWAYS = 0, RSSI_BELOW_THRESHOLD = 1, UNLESS_RECEIVE_PACKET = 2, RSSI_BELOW_THRESHOLD__UNLESS_RECEIVE_PACKET = 3 } CCA_MODE_t;
+      typedef enum { NEVER = 0, IDLE_TO_RX_OR_TX = 1, RX_OR_TX_TO_IDLE = 2, RX_OR_TX_TO_IDLE_EVERY_4_TIME = 3 } FS_AUTOCAL_t;
+
+      typedef struct { GDOx_CFG_t gdo0_cfg:6; uint8_t gdo0_inv:1; uint8_t bit7:1; } IOCFG0_t;
+      typedef struct { GDOx_CFG_t gdo1_cfg:6; uint8_t gdo1_inv:1; uint8_t bit7:1; } IOCFG1_t;
+      typedef struct { GDOx_CFG_t gdo2_cfg:6; uint8_t gdo2_inv:1; uint8_t bit7:1; } IOCFG2_t;
+      typedef struct { uint8_t fifo_thr:4; uint8_t close_in_rx:2; uint8_t adc_retention:1; uint8_t bit7:1; } FIFOTHR_t;
+      typedef struct { ADR_CHK_t adr_chk:2; uint8_t append_status:1; uint8_t crc_autoflush:1; uint8_t bit4:1; uint8_t pqt:3; } PKTCTRL1_t;
+      typedef struct { LENGTH_CONFIG_t length_config:2; uint8_t crc_en:1; uint8_t bit3:1; PKT_FORMAT_t pkt_format:2; uint8_t white_data:1; uint8_t bit7:1; } PKTCTRL0_t;
+      typedef struct { uint8_t frequ_if:5; uint8_t bit5:1; uint8_t bit76:2; } FSCTRL1_t;
+      typedef struct { uint8_t frequoff:8; } FSCTRL0_t;
+      typedef struct { uint8_t drate_e:4; uint8_t chanbw_m:2; uint8_t chanbw_e:2; } MDMCFG4_t;
+      typedef struct { uint8_t drate_m:8; } MDMCFG3_t;
+      typedef struct { SYNC_MODE_t sync_mode:3; uint8_t manchester_en:1; MOD_FORMAT_t mod_format:3; uint8_t dem_dcfilt_off:1; } MDMCFG2_t;
+      typedef struct { uint8_t chanspc_e:2; uint8_t bit32:2; NUM_PREAMBLE_t num_preamble:3; uint8_t fec_en:1; } MDMCFG1_t;
+      typedef struct { uint8_t chanspc_m:8; } MDMCFG0_t;
+      typedef struct { uint8_t deviation_m:3; uint8_t bit3:1; uint8_t deviation_e:3; uint8_t bit7:1; } DEVIATN_t;
+      typedef struct { uint8_t rx_time:3; uint8_t rx_time_qual:1; uint8_t rx_time_rssi:1; uint8_t bit765:3; } MCSM2_t;
+      typedef struct { TXOFF_MODE_t txoff_mode:2; RXOFF_MODE_t rxoff_mode:2; CCA_MODE_t cca_mode:2; uint8_t bit76:2; } MCSM1_t;
+      typedef struct { uint8_t xosc_force_on:1; uint8_t pin_ctrl_en:1; uint8_t po_timeout:2; FS_AUTOCAL_t fs_autocal:2; uint8_t bit76:2; } MCSM0_t;
+      typedef struct { uint8_t pa_power:3; uint8_t bit3:1; uint8_t lodiv_buf_current:2; uint8_t bit76:2; } FREND0_t;
+
+      typedef union {
+         uint8_t byte;
+         struct {
+            uint8_t num_rxbytes:7;
+            uint8_t rxfifo_overflow:1;
+         } rxbytes;
+      } STATUS_RXBYTES_t;
+
+      typedef struct {
+         IOCFG2_t iocfg2;
+         IOCFG1_t iocfg1;
+         IOCFG0_t iocfg0;
+         FIFOTHR_t fifothr;
+         uint8_t sync1;
+         uint8_t sync0;
+         uint8_t pktlen;
+         PKTCTRL1_t pktctrl1;
+         PKTCTRL0_t pktctrl0;
+         uint8_t addr;
+         uint8_t channr;
+         FSCTRL1_t fsctrl1;
+         FSCTRL0_t fsctrl0;
+         uint8_t frequ2;
+         uint8_t frequ1;
+         uint8_t frequ0;
+         MDMCFG4_t mdmcfg4;
+         MDMCFG3_t mdmcfg3;
+         MDMCFG2_t mdmcfg2;
+         MDMCFG1_t mdmcfg1;
+         MDMCFG0_t mdmcfg0;
+         DEVIATN_t deviatn;
+         MCSM2_t mcsm2;
+         MCSM1_t mcsm1;
+         MCSM0_t mcsm0;
+         uint8_t foccfg;
+         uint8_t bscfg;
+         uint8_t agcctrl2;
+         uint8_t agcctrl1;
+         uint8_t agcctrl0;
+         uint8_t worevt1;
+         uint8_t worevt0;
+         uint8_t worctrl;
+         uint8_t frend1;
+         FREND0_t frend0;
+         uint8_t fscal3;
+         uint8_t fscal2;
+         uint8_t fscal1;
+         uint8_t fscal0;
+         uint8_t rcctrl1;
+         uint8_t rcctrl0;
+         uint8_t fstest;
+         uint8_t ptest;
+         uint8_t agctest;
+         uint8_t test2;
+         uint8_t test1;
+         uint8_t test0;
+
+      } Register_t;
+
+
+
+};
+
+#endif
\ No newline at end of file
diff --git a/software/test-software/src/units/encoder.cpp b/software/test-software/src/units/encoder.cpp
new file mode 100644 (file)
index 0000000..7b33b76
--- /dev/null
@@ -0,0 +1,138 @@
+#include <stdio.h>
+#include <avr/io.h>
+
+#include "encoder.hpp"
+#include "../main.hpp"
+
+// Encoder signals on rotation clockwise 1 step:
+// A -----____------  one char app. 1ms..2ms (rotation speed)
+// B -------___-----  
+//                    one step when: A = 0, B= 1->0
+
+// Encoder signals on rotation counterclockwise 1 step:
+// A -----____------  one char app. 1ms..2ms (rotation speed)
+// B --______-----  
+//                    one step when: A = 0, B= 0->1
+
+#if defined(__AVR_ATmega644P__) || defined(__AVR_ATmega1284P__)
+
+   // Nano-644
+   // ---------------------------------------------------------------
+   // PB0/T0   ... Encoder A
+   // PB1/T1   ... Encoder B
+   // PB2/INT2 ... push switch of encoder (pushed = 0)
+
+   void Encoder::init () {
+      DDRB &= ~((1 << PB2) | (1 << PB1) | (1 << PB0));
+      PORTB |= (1 << PORTB2) | (1 << PORTB1) | (1 << PORTB0); // enable pullup
+      enabled = 1;
+   }
+   
+   void Encoder::cleanup () {
+      enabled = 0;
+      DDRB &= ~((1 << PB2) | (1 << PB1) | (1 << PB0));
+      PORTB &= ~((1 << PORTB2) | (1 << PORTB1) | (1 << PORTB0));
+   }
+
+   bool Encoder::isPressed () {
+      return (PINB & (1 << PB2)) == 0;
+   }
+
+   bool Encoder::getA () {
+      return (PINB & (1 << PB0)) == 0;
+   }
+
+   bool Encoder::getB () {
+      return (PINB & (1 << PB1)) == 0;
+   }
+
+
+#endif
+
+#ifdef __AVR_ATmega328P__
+
+   // Arduino-Nano-5V
+   // ---------------------------------------------------------------
+   // PD4/T0  ... Encoder A
+   // PB0     ... Encoder B
+   // PD7     ... push switch of encoder (pushed = 0)
+
+   void Encoder::init () {
+      DDRB &= ~(1 << PB0);
+      DDRD &= ~((1 << PD7) | (1 << PD4));
+      PORTB |= (1 << PB0); // enable pullup
+      PORTD |= (1 << PD7) | (1 << PD4); // enable pullup
+      enabled = 1;
+   }
+   
+   void Encoder::cleanup () {
+      enabled = 0;
+      PORTB &= ~(1 << PB0);
+      PORTD &= ~((1 << PD7) | (1 << PD4));
+      DDRB &= ~(1 << PB0);
+      DDRD &= ~((1 << PD7) | (1 << PD4));
+   }
+
+   bool Encoder::isPressed () {
+      return (PIND & (1 << PD7)) == 0;
+   }
+
+   bool Encoder::getA () {
+      return (PIND & (1 << PD4)) == 0;
+   }
+
+   bool Encoder::getB () {
+      return (PINB & (1 << PB0)) == 0;
+   }
+
+#endif
+
+
+int8_t Encoder::run (uint8_t subtest) {
+   switch (subtest) {
+      case 0: {
+         while (wait(10) == EOF) {
+            printf_P(PSTR("\r  => Encoder (push to clear): "));
+            printf_P(PSTR("%5d (0x%02x) "), count, (uint8_t)count);
+            if (isPressed()) {
+               reset();
+            }
+         }
+         return 0;
+      }
+   }
+
+   return -1;
+}
+
+struct EncoderState {
+   int8_t a:1; // signal A
+   int8_t b:1; // signal B
+};
+
+void Encoder::tick100us () {
+   static EncoderState lastState = { 1, 1 };
+   static EncoderState lastStableState = { 1, 1 };
+
+   if (!enabled) {
+      count = 0;
+      return;
+   }
+   EncoderState nextState;
+   nextState.a = getA() ? 1 : 0;
+   nextState.b = getB() ? 1 : 0;
+   if (nextState.a == lastState.a && nextState.b == lastState.b) {
+      if (lastStableState.a == 0 && nextState.b != lastStableState.b) {
+         if (nextState.b == 0) {
+            count = count < 127 ? count + 1 : 127;
+         } else {
+            count = count > -128 ? count - 1 : -128;
+         }
+      }
+      lastStableState.a = nextState.a;
+      lastStableState.b = nextState.b;
+   }
+   lastState.a = nextState.a;
+   lastState.b = nextState.b;
+}
+
diff --git a/software/test-software/src/units/encoder.hpp b/software/test-software/src/units/encoder.hpp
new file mode 100644 (file)
index 0000000..9b0861b
--- /dev/null
@@ -0,0 +1,28 @@
+#ifndef ENCODER_HPP
+#define ENCODER_PP
+
+#include <stdint.h>
+#include "../main.hpp"
+#include <avr/pgmspace.h>
+
+class Encoder : public TestUnit {
+   public:
+      uint8_t enabled;
+      int8_t count;
+
+   public:
+      Encoder () { reset(); enabled = 0; };
+      virtual void init ();
+      virtual void cleanup ();
+      virtual int8_t run (uint8_t subtest);
+      virtual PGM_P getName () { return PSTR("Encoder"); }
+      void reset () { count = 0; }
+      void tick100us ();
+      bool isPressed ();
+
+   private:
+      bool getA ();
+      bool getB ();
+};
+
+#endif
\ No newline at end of file
diff --git a/software/test-software/src/units/i2c.cpp b/software/test-software/src/units/i2c.cpp
new file mode 100644 (file)
index 0000000..60dd22d
--- /dev/null
@@ -0,0 +1,216 @@
+#include <stdio.h>
+#include <avr/io.h>
+#include <avr/pgmspace.h>
+#include <util/delay.h>
+#include <math.h>
+
+#include "i2c.hpp"
+#include "../adafruit/bme280.h"
+#include "../main.hpp"
+
+// Sparkfun https://www.sparkfun.com/products/22858
+// ENS160 address 0x53 (air quality sensor)
+// BME280 address 0x77 /humidity/temperature sensor)
+//    register 0xfe: humidity_7:0
+//    register 0xfd: humidity_15:8
+
+PGM_P I2c::getName () {
+   switch (mode) {
+      case SparkFunEnvCombo: return PSTR("I2C-Sparkfun Env-Combo");
+      case Master: return PSTR("I2C-Master");
+      case Slave: return PSTR("I2C-Slave");
+   }
+   return "?";
+}
+
+void I2c::init () {
+   TWBR = 13; // 100kHz (TWPS1:0 = 00), TWBR = (F_CPU - 16 * 100000) / (2 * 100000 * 4);
+   TWBR = 28; // 50kHz (TWPS1:0 = 00), TWBR = (F_CPU - 16 * 50000) / (2 * 50000 * 4);
+   TWBR = 100; // 50kHz (TWPS1:0 = 00), TWBR = (F_CPU - 16 * 50000) / (2 * 50000 * 4);
+   TWCR = (1 << TWEN);
+   ADMUX = (1 << ADLAR) | (1 << REFS0); // ADC0, VREF=AVCC=3.3V
+   ADCSRA = (1 << ADEN) | 7; // ADC Enable, Prescaler 128
+   enabled = true;
+}
+
+void I2c::cleanup () {
+   enabled = false;
+   TWCR = (1 << TWEN);
+   TWBR = 0;
+   ADMUX = 0;
+   ADCSRA = 0;
+}
+
+int8_t I2c::run (uint8_t subtest) {
+   if (subtest == 0 && mode == I2c::SparkFunEnvCombo) {
+      printf_P(PSTR(" BM280 ... "));
+      if (!bm280.begin()) {
+         printf_P(PSTR("E1"));
+         return -1;
+      }
+      printf_P(PSTR("OK, ENS160 ... "));
+      if (!ens160.begin()) {
+         printf_P(PSTR("E2"));
+         return -1;
+      }
+      if (!ens160.setMode(ENS160_OPMODE_STD)) {
+         printf_P(PSTR("E3"));
+         return -1;
+      }
+      if (!ens160.set_envdata(25.0, 65)) {
+         printf_P(PSTR("E4"));
+         return -1;
+      }
+
+      printf_P(PSTR("OK"));
+      float accTemp = 0, accHumidity = 0;
+      int8_t accCount = -1;
+
+      do {
+         // BME280 
+         float p = bm280.readPressure();
+         printf_P(PSTR("\n => BM280: P= %.3fbar"), (double)p / 100000.0);
+         float t = bm280.readTemperature();
+         printf_P(PSTR(", T= %.2f°C"), (double)t);
+         float h = bm280.readHumidity();
+         printf_P(PSTR(", H= %.2f%%"), (double)h);
+
+         if (accCount >= 0 && !isnanf(h) && !isnan(t)) {
+            accTemp += t;
+            accHumidity += h;
+            accCount++;
+         }
+
+         bm280.setSampling(
+            Adafruit_BME280::MODE_NORMAL,
+            Adafruit_BME280::SAMPLING_X16,
+            Adafruit_BME280::SAMPLING_X16,
+            Adafruit_BME280::SAMPLING_X16,
+            Adafruit_BME280::FILTER_OFF,
+            Adafruit_BME280::STANDBY_MS_1000
+         );
+
+         // ENS160 only activated every 32s to avoid wrong temperature measuerment
+         // if ES160 would be continously active, the temperature would be 4°C higher
+         // -> ES160 has not enough distance to BM280
+         // This solution causes only a +0.3°C higher temperatur value
+         if (accCount < 0 || accCount >= 32) {
+            printf_P(PSTR("  | ENS160 ("));
+            if (accCount > 0) { 
+               h = accHumidity / accCount;
+               t = accTemp / accCount;
+               accTemp = 0;
+               accHumidity = 0;
+            }
+            accCount = 0;            
+            if (!ens160.set_envdata(t, h)) {
+               printf_P(PSTR("E1)"));
+            } else {
+               printf_P(PSTR("%.1f°C/%.1f%%): "), (double)t, (double)h);
+               if (!ens160.setMode(ENS160_OPMODE_STD)) {
+                  printf_P(PSTR("E2"));
+               } else {
+                  for (uint8_t i = 0; i < 100; i++) {
+                     _delay_ms(15);
+                     uint8_t status;
+                     if (ens160.readStatus(&status)) {
+                        if (status & ENS160_DATA_STATUS_NEWDAT) {
+                           ENS160_DATA data;
+                           if (ens160.readData(&data)) {
+                              printf_P(PSTR(" aqi=%d("), data.aqi);
+                              switch(data.aqi) {
+                                 case 1: printf_P(PSTR("excellent")); break;
+                                 case 2: printf_P(PSTR("good")); break;
+                                 case 3: printf_P(PSTR("moderate")); break;
+                                 case 4: printf_P(PSTR("poor")); break;
+                                 case 5: printf_P(PSTR("unhealthy")); break;
+                                 default: printf_P(PSTR("?")); break;
+                              }
+                              printf_P(PSTR("), tvoc=%dppb"), data.tvoc);
+                              printf_P(PSTR(", eco2=%d("), data.eco2);
+                              if (data.eco2 < 400) {
+                                 printf_P(PSTR("?"));
+                              } else if (data.eco2 < 600) {
+                                 printf_P(PSTR("excellent"));
+                              } else if (data.eco2 < 800) {
+                                 printf_P(PSTR("good"));
+                              } else if (data.eco2 < 1000) {
+                                 printf_P(PSTR("fair"));
+                              } else if (data.eco2 < 1500) {
+                                 printf_P(PSTR("poor"));
+                              } else {
+                                 printf_P(PSTR("bad"));
+                              }
+                              printf_P(PSTR(")"));
+                           }
+                           break;
+                        }
+                     }
+                  }
+               }
+               if (!ens160.setMode(ENS160_OPMODE_IDLE)) {
+                  printf_P(PSTR("E3"));
+               }
+            }
+         }
+
+      } while (wait(1000) == EOF);
+
+   } else if (subtest == 0 && mode == I2c::Master) {
+      if (!master.begin(0x01)) {
+         printf_P(PSTR("E1"));
+         return -1;
+      }
+      do {
+         uint8_t buffer[1];
+         // read poti
+         ADCSRA |= (1 << ADSC); // start ADC
+         while (ADCSRA & (1 << ADSC)) {} // wait for result
+         buffer[0] = ADCH;
+         printf_P(PSTR("\n I2C-MASTER: to slave: 0x%02x"), buffer[0]);
+         if (!master.write(buffer, 1)) {
+            printf_P(PSTR(" -> ERROR"));
+         }
+         printf_P(PSTR(",  from slave: "));
+         if (master.read(buffer, 1)) {
+            printf_P(PSTR("0x%02x"), buffer[0]);   
+         } else {
+            printf_P(PSTR(" -> ERROR"));
+         }
+      } while (wait(1000) == EOF);
+      master.end();
+
+   } else if (subtest == 0 && mode == I2c::Slave) {
+      if (!slave.begin(0x01, false)) {
+         printf_P(PSTR("E1"));
+         return -1;
+      }
+      do {
+         int fromMaster = slave.read();
+         if (fromMaster != EOF) {
+            ADCSRA |= (1 << ADSC); // start ADC
+            while (ADCSRA & (1 << ADSC)) {} // wait for result
+            slave.write(ADCH);
+            printf_P(PSTR("\n I2C SLAVE: from master: 0x%02x -> to master: 0x%02x"), fromMaster, ADCH);
+         }
+      } while (wait(0) == EOF);
+      slave.end();
+
+   } else {
+      printf_P(PSTR("end"));
+      return -1;
+   }
+   wait(500);
+   return 0;
+}
+
+void I2c::handleTwiIrq () {
+   if (mode == I2c::Slave) {
+      DDRD |= (1 << PD7);
+      PORTD |= (1 << PD7);
+      slave.handleTWIIsr();
+      PORTD &= ~(1 << PD7);
+   } else {
+      TWCR |= (1 << TWINT); // clear Interrupt Request
+   }
+}
diff --git a/software/test-software/src/units/i2c.hpp b/software/test-software/src/units/i2c.hpp
new file mode 100644 (file)
index 0000000..2148cc0
--- /dev/null
@@ -0,0 +1,43 @@
+#ifndef I2C_HPP
+#define I2C_HPP
+
+#include <stdint.h>
+#include "../main.hpp"
+#include "../adafruit/bme280.h"
+#include "../adafruit/ens160.h"
+#include "../i2cmaster.hpp"
+#include "../i2cslave.hpp"
+
+
+class I2c : public TestUnit {
+   public:
+      typedef enum I2cMode { SparkFunEnvCombo, Master, Slave } I2cMode;
+
+   private:
+      I2cMode mode;
+      Adafruit_BME280 bm280;
+      ScioSense_ENS160 ens160;
+      I2cMaster master;
+      I2cSlave slave;
+
+   public:
+      bool enabled;
+
+   public:
+      I2c (I2cMode mode) { enabled = false; this->mode = mode; }
+      void tick1ms () { master.tick1ms(); slave.tick1ms(); }
+      virtual void init ();
+      virtual void cleanup ();
+      virtual int8_t run (uint8_t subtest);
+      virtual PGM_P getName ();
+      void handleTwiIrq ();
+      // uint16_t startRead (uint8_t address);
+      // uint16_t startWrite (uint8_t address);
+      // void stop ();
+      // uint16_t writeByte (uint8_t data);
+      // uint16_t writeData (uint8_t size, const uint8_t *data);
+      // uint16_t readData (uint8_t size, uint8_t *data);
+      // int32_t compensateBm280T (int32_t adcT);
+};
+
+#endif
\ No newline at end of file
diff --git a/software/test-software/src/units/ieee485.cpp b/software/test-software/src/units/ieee485.cpp
new file mode 100644 (file)
index 0000000..8fcb67a
--- /dev/null
@@ -0,0 +1,111 @@
+#include <stdio.h>
+#include <avr/io.h>
+#include <util/delay.h>
+#include <util/atomic.h>
+
+#include "ieee485.hpp"
+#include "../main.hpp"
+
+#ifdef __AVR_ATmega328P__
+
+// Nano-328P
+// ------------------------------------
+// IEE485 not supported (no UART1)
+
+void Ieee485::init () {}
+void Ieee485::cleanup () {}
+int8_t Ieee485::run (uint8_t subtest) {
+   return -1;
+}
+
+#endif
+
+
+#if defined(__AVR_ATmega644P__) || defined(__AVR_ATmega1284P__)
+
+// Nano-644
+// ------------------------------------
+// PB0 ... nRE .. Read enable
+// PB1 ...  DE .. Data enable
+
+#define SET_nRE  (PORTB |= (1 << PB0))
+#define CLR_nRE  (PORTB &= ~(1 << PB0))
+#define SET_DE   (PORTB |= (1 << PB1))
+#define CLR_DE   (PORTB &= ~(1 << PB1))
+
+void Ieee485::init () {
+   // Poti
+   ADMUX = (1 << ADLAR) | (1 << REFS0); // ADC0, VREF=AVCC=3.3V
+   ADCSRA = (1 << ADEN) | 7; // ADC Enable, Prescaler 128
+
+   // Modbus
+   SET_nRE;
+   CLR_DE;
+   DDRB |= (1 << PB1) | (1 << PB0);
+
+   // UART1 interface on Nano-644
+   PORTD |= (1 << PD2); // enable RxD1 pullup
+   UCSR1A = (1 << U2X1);
+   UCSR1B = (1 << RXCIE1) | (1 << RXEN1) | (1 <<TXEN1);
+   UCSR1C = (1 << UCSZ11) | ( 1<< UCSZ10);
+   // UCSR1C |= (1 <<UPM11); // even Parity
+   // UCSR1C |= (1 << UPM11) | (1 << UPM10); // odd Parity
+   UBRR1H = 0;
+   UBRR1L = F_CPU / 8 / 9600 - 1;
+
+   enabled = 1;
+}
+
+void Ieee485::cleanup () {
+   enabled = 0;
+
+   ADMUX = 0;
+   ADCSRA = 0;
+
+   UCSR1A = 0;
+   UCSR1B = 0;
+   UCSR1C = 0;
+   UBRR1H = 0;
+   UBRR1L = 0;
+   PORTD &= ~(1 << PD2);
+   DDRB &= ~((1 << PB1) | (1 << PB0));
+   PORTB &= ~((1 << PB1) | (1 << PB0));
+}
+
+int8_t Ieee485::run (uint8_t subtest) {
+   if (subtest == 0) {
+      CLR_nRE; CLR_DE;
+      while (wait(500) == EOF) {
+         ADCSRA |= (1 << ADSC); // start ADC
+         while (ADCSRA & (1 << ADSC)) {} // wait for result
+         UCSR1A |= (1 << TXC1);
+         SET_nRE;
+         SET_DE;
+         UDR1 = ADCH;
+         while ((UCSR1A & (1 << TXC1)) == 0) {}
+         CLR_DE;
+         CLR_nRE;
+         printf_P(PSTR("\n  => send Byte 0x%02x"), ADCH);
+         int b;
+         ATOMIC_BLOCK(ATOMIC_FORCEON) {
+            b = receivedByte;
+            receivedByte = -1; 
+         }
+         if (b >= 0) {
+            printf_P(PSTR("\n  => receive Byte: 0x%02x"), b);
+         }
+      }
+
+   } else {
+      printf_P(PSTR("end"));
+      return -1;
+   }
+
+   return 0;
+}
+
+void Ieee485::handleRxByte (uint8_t b) {
+   receivedByte = b;
+}
+
+#endif
\ No newline at end of file
diff --git a/software/test-software/src/units/ieee485.hpp b/software/test-software/src/units/ieee485.hpp
new file mode 100644 (file)
index 0000000..ffbb15c
--- /dev/null
@@ -0,0 +1,22 @@
+#ifndef IEEE485_HPP
+#define IEEE485_HPP
+
+#include <stdint.h>
+#include "../main.hpp"
+#include <avr/pgmspace.h>
+
+class Ieee485 : public TestUnit {
+   public:
+      uint8_t  enabled;
+      int16_t  receivedByte;
+
+   public:
+      Ieee485 () { enabled = 0; receivedByte = -1; }
+      virtual void init ();
+      virtual void cleanup ();
+      virtual int8_t run (uint8_t subtest);
+      virtual const char *getName () { return PSTR("IEEE485"); }
+      void handleRxByte (uint8_t);
+};
+
+#endif
\ No newline at end of file
diff --git a/software/test-software/src/units/lcd.cpp b/software/test-software/src/units/lcd.cpp
new file mode 100644 (file)
index 0000000..e779c16
--- /dev/null
@@ -0,0 +1,443 @@
+#include <stdio.h>
+#include <avr/io.h>
+#include <util/delay.h>
+
+#include "lcd.hpp"
+#include "../main.hpp"
+
+#if defined(__AVR_ATmega644P__) || defined(__AVR_ATmega1284P__)
+
+   // Nano-644 / Nano-1284
+   // ---------------------------------------------------------------
+   
+   // Nano-X-Base    V1a   V2a     Beschreibung
+   // --------------------------------- -------
+   // BL ........... PC0  PC0     Backlight ON(=1) / OFF(=0)
+   // E  ........... PA3   PA3     LCD Enable (Verbindung via J25 erforderlich)
+   // R/W .......... PD6   PD6     Read/Write: Read=1, Write=0
+   // RS ........... PD7   PD7     Register Select: Command=0, Data=1
+   // Data ......... PB7:0 PB7:0   Achtung von 5V LCD nicht lesen!
+
+   void Lcd::init () {
+      DDRA |=  (1 << PA3) | (1 << PA0);
+      DDRB = 0xff;
+      DDRD |= (1 << PD7) | (1 << PD6);
+      initLcd();
+      printf_P(PSTR("init LCD ("));
+      if (status == 1) {
+         printf_P(PSTR("OK)"));
+      } else {
+         printf_P(PSTR("ERROR %d)"), status);
+      }
+      setBacklightOn();
+   }
+
+   void Lcd::cleanup () {
+      clear();
+      PORTA &= ~((1 << PA3) | (1 << PA0));
+      DDRA &=  ~((1 << PA3) | (1 << PA0));
+      PORTB = 0;
+      DDRB = 0;
+      PORTD &= ~((1 << PD7) | (1 << PD6));
+      DDRD &= ~((1 << PD7) | (1 << PD6));
+   }
+
+   void Lcd::setRS () { PORTD |= (1 << PD7); }
+   void Lcd::clrRS () { PORTD &= ~(1 << PD7); }
+   void Lcd::setRW () { PORTD |= (1 << PD6); }
+   void Lcd::clrRW () { PORTD &= ~(1 << PD6); }
+   void Lcd::setE ()  { PORTA |= (1 << PA3); }
+   void Lcd::clrE ()  { PORTA &= ~(1 << PA3); }
+   void Lcd::dataDirectionOut ()  { DDRB = 0xff; }
+
+   void Lcd::dataDirectionIn ()  {
+      if (hardwareVersion == 1) {
+         // read back not allowed (missing level shifter 5V -> 3.3V)
+         DDRB = 0xff;
+      } else {
+         DDRB = 0x00;
+      }
+   }
+
+   uint8_t Lcd::getData () {
+      if (hardwareVersion == 1) {
+         // read back not allowed (missing level shifter 5V -> 3.3V)
+         _delay_ms(1);
+         return 0x00; // bit 8 (busy) = 0
+      } else {
+         return PINB;
+      }
+   }
+
+   void Lcd::setData (uint8_t data) {
+      PORTB = data;
+   }
+
+   void Lcd::setBacklightOn () {
+      PORTA |= (1 << PA0);
+   }
+
+   void Lcd::setBacklightOff () {
+      PORTA &= ~(1 << PA0);
+   }
+
+#endif
+
+#ifdef __AVR_ATmega328P__
+
+   // Arduino Nano (5V)
+   // ---------------------------------------------------------------
+
+   // Nano-X-Base    V1a  V2a     Beschreibung
+   // --------------------------------- -------
+   // BL ........... PC0  PC0     Backlight ON(=1) / OFF(=0)
+   // E  ........... PC3  PC3     LCD Enable (Verbindung via J25 erforderlich)
+   // R/W .......... PD3  PD3     Read/Write: Read=1, Write=0
+   // RS ........... PD2  PD2     Register Select: Command=0, Data=1
+   // Data0 ........ PD4  PD4
+   // Data1 .........PB0  PB0
+   // Data2 .........PD7  PD7
+   // Data3 .........PD6  PD6
+   // Data4 .........PB2  PB2
+   // Data5 .........PB3  PB3
+   // Data6 .........PB4  PB4
+   // Data7 .........PB5  PB5
+
+   // PC3 ..... E    --> LCD Enable (Verbindung via J25 erforderlich)
+   // PD3 ..... R/W  --> Read/Write: Read=1, Write=0
+   // PD2 ..... RS   --> Register Select: Command=0, Data=1
+
+
+   void Lcd::init () {
+      clrRW();
+      clrRS();
+      clrE();
+      setData(0);
+      DDRB |= (1 << PB5) | (1 << PB4) | (1 << PB3) | (1 << PB2) | (1 << PB0);
+      DDRC |= (1 << PC3) | (1 << PC0);
+      DDRD |= (1 << PD7) | (1 << PD6) | (1 << PD4) | (1 << PD3) | (1 << PD2);
+      initLcd();
+      printf_P(PSTR("init LCD ("));
+      if (status == 1) {
+         printf_P(PSTR("OK)"));
+      } else {
+         printf_P(PSTR("ERROR %d)"), status);
+      }
+      setBacklightOn();
+   }
+   
+   void Lcd::cleanup () {
+      clear();
+      PORTB &= ~((1 << PB5) | (1 << PB4) | (1 << PB3) | (1 << PB2) | (1 << PB0));
+      PORTC &= ~((1 << PC3) | (1 << PC0));
+      PORTD &= ~((1 << PD7) | (1 << PD6) | (1 << PD4) | (1 << PD3) | (1 << PD2));
+      DDRB &= ~((1 << PB5) | (1 << PB4) | (1 << PB3) | (1 << PB2) | (1 << PB0));
+      DDRC &= ~((1 << PC3) | (1 << PC0));
+      DDRD &= ~((1 << PD7) | (1 << PD6) | (1 << PD4) | (1 << PD3) | (1 << PD2));
+   }
+
+   void Lcd::setRS () { PORTD |= (1 << PD2); }
+   void Lcd::clrRS () { PORTD &= ~(1 << PD2); }
+   void Lcd::setRW () { PORTD |= (1 << PD3); }
+   void Lcd::clrRW () { PORTD &= ~(1 << PD3); }
+   void Lcd::setE ()  { PORTC |= (1 << PC3); }
+   void Lcd::clrE ()  { PORTC &= ~(1 << PC3); }
+
+   void Lcd::setData (uint8_t data) {
+      if (data & 0x01) PORTD |= (1 << PD4); else PORTD &= ~((1 << PD4));
+      if (data & 0x02) PORTB |= (1 << PB0); else PORTB &= ~((1 << PB0));
+      if (data & 0x04) PORTD |= (1 << PD7); else PORTD &= ~((1 << PD7));
+      if (data & 0x08) PORTD |= (1 << PD6); else PORTD &= ~((1 << PD6));
+      if (data & 0x10) PORTB |= (1 << PB2); else PORTB &= ~((1 << PB2));
+      if (data & 0x20) PORTB |= (1 << PB3); else PORTB &= ~((1 << PB3));
+      if (data & 0x40) PORTB |= (1 << PB4); else PORTB &= ~((1 << PB4));
+      if (data & 0x80) PORTB |= (1 << PB5); else PORTB &= ~((1 << PB5));
+   }
+
+   void Lcd::dataDirectionOut ()  {
+      if (!mode4Bit) {
+         DDRB |= (1 << PB0);
+         DDRD |= ((1 << PD7) | (1 << PD6) | (1 << PD4));
+      }
+      DDRB |= ((1 << PB5) | (1 << PB4) | (1 << PB3) | (1 << PB2));
+   }
+
+   void Lcd::dataDirectionIn ()  {
+      if (hardwareVersion == 1) {
+         // read back not allowed (missing level shifter 5V -> 3.3V)
+         dataDirectionOut();
+      } else {
+         if (!mode4Bit) {
+            DDRB &= ~(1 << PB0);
+            DDRD &= ~((1 << PD7) | (1 << PD6) | (1 << PD4));
+         }
+         DDRB &= ~((1 << PB5) | (1 << PB4) | (1 << PB3) | (1 << PB2));
+      }
+   }
+
+   uint8_t Lcd::getData () {
+      if (hardwareVersion == 1) {
+         // read back not allowed (missing level shifter 5V -> 3.3V)
+         _delay_ms(1);
+         return 0x00; // bit 8 (busy) = 0
+      } else {
+         uint8_t b = 0;
+         b |= ((PIND & (1 << PD4)) != 0) << 0;
+         b |= ((PINB & (1 << PB0)) != 0) << 1;
+         b |= ((PIND & (1 << PD7)) != 0) << 2;
+         b |= ((PIND & (1 << PD6)) != 0) << 3;
+         b |= ((PINB & (1 << PB2)) != 0) << 4;
+         b |= ((PINB & (1 << PB3)) != 0) << 5;
+         b |= ((PINB & (1 << PB4)) != 0) << 6;
+         b |= ((PINB & (1 << PB5)) != 0) << 7;
+         return b;
+      }
+   }
+
+   void Lcd::setBacklightOn () {
+      PORTC |= (1 << PC0);
+   }
+
+   void Lcd::setBacklightOff () {
+      PORTC &= ~(1 << PC0);
+   }
+
+#endif   
+
+// Befehle für das Display
+
+#define DISP_CLEAR  0b00000001   // Display clear   
+#define DISP_ON     0b00001111   // Display on      
+#define DISP_OFF    0b00001011   // Display off     
+#define CURSOR_ON   0b00001111   // Cursor on       
+#define CURSOR_OFF  0b00001101   // Cursor off      
+#define BLINK_ON    0b00001111   // Cursor Blink    
+#define BLINK_OFF   0b00001110   // Cursor No Blink 
+
+#define LCD_PULSE_LENGTH 15
+#define LCD_CMD_DISPLAY_CLEAR  0x01  // Display clear
+#define LCD_CMD_CURSOR_HOME    0x02  // Move cursor digit 1
+#define LCD_CMD_SET_ENTRY_MODE 0x04  // Entry Mode Set
+#define LCD_CMD_DISPLAY_ON_OFF 0x08  // Display on/off
+#define LCD_CMD_SHIFT          0x10  // Display shift
+#define LCD_CMD_SET_MODE4BIT   0x20  // 4/8 Bits...
+#define LCD_CMD_SET_CGRAM_ADDR 0x40  // Character Generator ROM
+#define LCD_CMD_SET_DDRAM_ADDR 0x80  // Display Data RAM
+#define LCD_BUSY_FLAG          0x80
+
+
+
+int8_t Lcd::run (uint8_t subtest) {
+   if (subtest == 0) {
+      for (uint8_t i = 0; i < 20 * 4; i++) {
+         char c = (char)(i + 32);
+         if (i % 20 == 0) {
+            setCursor(i / 20, 0);
+         }
+         putChar(c);
+         waitOnReady();
+      }
+      printf_P(PSTR("LCD "));
+      if (status == 1) {
+         printf_P(PSTR("OK"));
+      } else {
+         printf_P(PSTR("ERROR(%d)"), status);
+      }
+      while (wait(1) == EOF) {
+      }
+
+   } else {
+      printf_P(PSTR("end"));
+      return -1;
+   }
+   wait(500);
+   return 0;
+}
+
+
+void Lcd::initLcd () {
+
+   setData(0x00);
+   dataDirectionOut();
+   
+   _delay_ms(16); // min 15ms warten für Reset des Displays
+   status = 0;
+   for (uint8_t i = 0; i < 4; i++) {
+      if (mode4Bit) {
+         setRegister(LCD_CMD_SET_MODE4BIT | 0x08); // 4 Bit, 2/4 Zeilen, 5x7
+      } else {
+         setRegister(0x08); // 8 Bit, 2 Zeilen, 5x7
+      }
+      if (i == 0) {
+         _delay_ms(5);
+      } else {
+         _delay_us(100);
+      }
+   }
+
+   setRegister(LCD_CMD_DISPLAY_ON_OFF | 0x04); // display on, cursor off
+   if (!isReady(50)) {
+      status = -1;
+      return;
+   }
+
+   setRegister(LCD_CMD_DISPLAY_ON_OFF | 0x04); // display on, cursor off
+   if (!isReady(50)) {
+      status = -3;
+      return;
+   }
+
+   setRegister(LCD_CMD_DISPLAY_CLEAR);
+   if (!isReady(1200)) {
+      status = -4;
+      return;
+   }
+
+   status = 1;
+}
+
+void Lcd::setRegister (uint8_t cmd) {
+   clrRW();
+   clrRS();
+   writeData(cmd);
+}
+
+void Lcd::writeData (uint8_t data) {
+   clrE();
+   dataDirectionOut();
+   if (mode4Bit) {
+      setData(data & 0xf0); // send High-Nibble
+      setE();
+      _delay_us(LCD_PULSE_LENGTH);
+      clrE();
+      _delay_us(1);
+      setData(data << 4); // send Low-Nibble
+   } else {
+      setData(data); // send data byte
+   }
+   setE();
+   _delay_us(LCD_PULSE_LENGTH);
+   clrE();
+   _delay_us(1);
+}
+
+void Lcd::setDRAddr (uint8_t address) {
+   waitOnReady();
+   setRegister(LCD_CMD_SET_DDRAM_ADDR | address);
+   waitOnReady();
+}
+
+void Lcd::waitOnReady () {
+   if (isReady(50) == 0) {
+      status = -6;
+   }
+}
+
+bool Lcd::isReady (uint16_t us) {
+   if (status < 0) {
+      return false;
+   }
+   if (hardwareVersion == 1) {
+      // read back not allowed (missing level shifter)
+      _delay_ms(1);
+      return true;
+   }
+
+   uint8_t busy;
+   dataDirectionIn();
+   setData(0xff); // enable internal pull up 
+
+   do {
+      uint8_t data = 0;
+      setRW();
+      clrRS();
+
+      _delay_us(1);
+      setE();
+      _delay_us(LCD_PULSE_LENGTH);
+      data = getData() & 0xf0; // High Nibble
+      clrE();
+
+      _delay_us(1);
+      setE();
+      _delay_us(LCD_PULSE_LENGTH);
+      data |= getData() >> 4; // Low Nibble
+      
+      clrE();
+      _delay_us(1);
+      clrRW();
+
+      busy = data & LCD_BUSY_FLAG;
+      us = (us >= 11) ? us - 11 : 0;
+   } while (us > 0 && busy);
+
+   if (status == 1 && busy) {
+      status = -5;
+   }
+
+   setData(0x00);
+   dataDirectionOut();
+
+   return busy == 0;   
+}
+
+
+void Lcd::setDisplayOn () {
+   if (status != 1) {
+      return;
+   }
+   waitOnReady();
+   setRegister(LCD_CMD_DISPLAY_ON_OFF | 0x04); // display on
+   waitOnReady();
+}
+
+void Lcd::setDisplayOff () {
+   if (status != 1) {
+      return;
+   }
+   waitOnReady();
+   setRegister(LCD_CMD_DISPLAY_ON_OFF); // display off
+   waitOnReady();
+}
+
+void Lcd::clear () {
+   if (status != 1) {
+      return;
+   }
+   waitOnReady();
+   setRegister(LCD_CMD_DISPLAY_CLEAR);
+   waitOnReady();
+}
+
+void Lcd::setCursor (uint8_t rowIndex, uint8_t columnIndex) {
+   if (status != 1 || columnIndex >= 20) {
+      return;
+   }
+   uint8_t b;
+   switch (rowIndex) {
+      case 0: b = 0x00 + columnIndex; break;
+      case 1: b = 0x40 + columnIndex; break;
+      case 2: b = 0x14 + columnIndex; break;
+      case 3: b = 0x54 + columnIndex; break;
+      default: return;
+   }
+   setDRAddr(b);
+   waitOnReady();
+}
+
+void Lcd::putChar (char c) {
+   if (status != 1) {
+      return;
+   }
+   clrRW();
+   setRS();
+   writeData(c);   
+   clrRS();
+   waitOnReady();
+}
+
+void Lcd::puts (const char * str) {
+   while (*str && status == 1) {
+      putChar(*str++);
+   }
+}
\ No newline at end of file
diff --git a/software/test-software/src/units/lcd.hpp b/software/test-software/src/units/lcd.hpp
new file mode 100644 (file)
index 0000000..47a30cc
--- /dev/null
@@ -0,0 +1,48 @@
+#ifndef LCD_HPP
+#define LCD_HPP
+
+#include <stdint.h>
+#include "../main.hpp"
+#include <avr/pgmspace.h>
+
+class Lcd : public TestUnit {
+   public:
+      Lcd () { mode4Bit = hardwareVersion == 1 ? false : true; status = 0; };
+      virtual void init ();
+      virtual void cleanup ();
+      virtual int8_t run (uint8_t subtest);
+      virtual PGM_P getName () { return PSTR("Lcd"); }
+   
+   private:
+      bool mode4Bit;
+      int8_t status;
+
+      void initLcd ();
+      void setRegister (uint8_t cmd);
+      void setDRAddr (uint8_t address);
+      void writeData (uint8_t data);
+      void waitOnReady ();
+      bool isReady (uint16_t us);
+      void setDisplayOn ();
+      void setDisplayOff ();
+      void clear ();
+      void setCursor (uint8_t rowIndex, uint8_t columnIndex);
+      void putChar (char c);
+      void puts (const char * str);
+
+      void setRS ();
+      void clrRS ();
+      void setRW ();
+      void clrRW ();
+      void setE ();
+      void clrE ();
+      uint8_t getData ();
+      void setData (uint8_t data);
+      void dataDirectionIn ();
+      void dataDirectionOut ();
+      void setBacklightOn ();
+      void setBacklightOff ();
+
+};
+
+#endif
\ No newline at end of file
diff --git a/software/test-software/src/units/led.cpp b/software/test-software/src/units/led.cpp
new file mode 100644 (file)
index 0000000..e908c21
--- /dev/null
@@ -0,0 +1,139 @@
+#include <stdio.h>
+#include <avr/io.h>
+#include <util/delay.h>
+
+#include "led.hpp"
+#include "../main.hpp"
+
+#if defined(__AVR_ATmega644P__) || defined(__AVR_ATmega1284P__)
+
+   // Nano-644 / Nano 1284
+   // ---------------------------------------------------------------
+   
+   // Nano-X-Base    V1a  V2a
+   // -----------------------
+   // Red            PD4  PD7
+   // Orange/Yellow  PD5  PD6
+   // Green          PD6  PD5
+   // Blue           PD7  PD4
+
+   void Led::init () {
+      PORTD &= ~((1 << PD7) | (1 << PD6) | (1 << PD5) | (1 << PD4));
+      DDRD |= (1 << PD7) | (1 << PD6) | (1 << PD5) | (1 << PD4);
+   }
+
+   void Led::cleanup () {
+      DDRD &= ~((1 << PD7) | (1 << PD6) | (1 << PD5) | (1 << PD4));
+      PORTD &= ~((1 << PD7) | (1 << PD6) | (1 << PD5) | (1 << PD4));
+   }
+
+   void Led::setLed (LED led, bool on) {
+      if (on) {
+         switch(led) {
+            case RED: PORTD |= (1 << PD4); break;
+            case ORANGE: PORTD |= (1 << PD5); break;
+            case GREEN: PORTD |= (1 << PD6); break;
+            case BLUE: PORTD |= (1 << PD7); break;
+         }
+      } else {
+         switch(led) {
+            case RED: PORTD &= ~(1 << PD4); break;
+            case ORANGE: PORTD &= ~(1 << PD5); break;
+            case GREEN: PORTD &= ~(1 << PD6); break;
+            case BLUE: PORTD &= ~(1 << PD7); break;
+         }
+      }
+   }
+
+   void Led::ledToggle (LED led) {
+      switch(led) {
+         case RED: PORTD ^= (1 << PD4); break;
+         case ORANGE: PORTD ^= (1 << PD5); break;
+         case GREEN: PORTD ^= (1 << PD6); break;
+         case BLUE: PORTD ^= (1 << PD7); break;
+      }
+   }
+
+#endif
+
+#ifdef __AVR_ATmega328P__
+
+   // Arduino-Nano-5V
+   // ---------------------------------------------------------------
+
+   // Nano-X-Base    V1a  V2a
+   // -----------------------
+   // Red            PD5  PD2
+   // Orange/Yellow  PB1  PD3
+   // Green          PD3  PB1
+   // Blue           PD2  PD5
+
+   void Led::init () {
+      PORTD &= ~((1 << PD5) | (1 << PD3) | (1 << PD2));
+      PORTB &= ~(1 << PB1);
+      DDRD |= (1 << PD5) | (1 << PD3) | (1 << PD2);
+      DDRB |= (1 << PB1);
+   }
+
+   void Led::cleanup () {
+      DDRD &= ~((1 << PD5) | (1 << PD3) | (1 << PD2));
+      DDRB &= ~(1 << PB1);
+      PORTD &= ~((1 << PD5) | (1 << PD3) | (1 << PD2));
+      PORTB &= ~(1 << PB1);
+   }
+
+   void Led::setLed (LED led, bool on) {
+      if (on) {
+         switch(led) {
+            case RED: PORTD |= (1 << PD5); break;
+            case ORANGE: PORTB |= (1 << PB1); break;
+            case GREEN: PORTD |= (1 << PD3); break;
+            case BLUE: PORTD |= (1 << PD2); break;
+         }
+      } else {
+         switch(led) {
+            case RED: PORTD &= ~(1 << PD5); break;
+            case ORANGE: PORTB &= ~(1 << PB1); break;
+            case GREEN: PORTD &= ~(1 << PD3); break;
+            case BLUE: PORTD &= ~(1 << PD2); break;
+         }
+      }
+   }
+
+   void Led::ledToggle (LED led) {
+      switch(led) {
+         case RED: PORTD ^= (1 << PD5); break;
+         case ORANGE: PORTB ^= (1 << PB1); break;
+         case GREEN: PORTD ^= (1 << PD3); break;
+         case BLUE: PORTD ^= (1 << PD2); break;
+       }
+   }
+
+#endif
+
+void Led::ledOn (LED led) {
+   setLed(led, true);
+}
+
+void Led::ledOff (LED led) {
+   setLed(led, false);
+}
+
+
+int8_t Led::run (uint8_t subtest) {
+   if (subtest <= 15) {
+      subtest = (subtest) % 4;
+      switch (subtest) {
+         case 0: ledOff(BLUE); ledOn(RED); break;
+         case 1: ledOff(RED); ledOn(ORANGE); break;
+         case 2: ledOff(ORANGE); ledOn(GREEN); break;
+         case 3: ledOff(GREEN); ledOn(BLUE); break;
+      }
+      printf_P(PSTR("Test LED D%d"), subtest + 1);
+      wait(500);
+      return 0;
+   }
+   
+   return -1;
+}
+
diff --git a/software/test-software/src/units/led.hpp b/software/test-software/src/units/led.hpp
new file mode 100644 (file)
index 0000000..780827f
--- /dev/null
@@ -0,0 +1,25 @@
+#ifndef LED_HPP
+#define LED_HPP
+
+#include <stdint.h>
+#include "../main.hpp"
+#include <avr/pgmspace.h>
+
+class Led : public TestUnit {
+   public:
+      enum LED { RED, ORANGE, GREEN, BLUE };
+
+   public:
+      Led () {};
+      virtual void init ();
+      virtual void cleanup ();
+      virtual int8_t run (uint8_t subtest);
+      virtual PGM_P getName () { return PSTR("Led"); }
+
+      void setLed (LED led, bool on);
+      void ledOn (LED led);
+      void ledOff (LED led);
+      void ledToggle (LED led);
+};
+
+#endif
\ No newline at end of file
diff --git a/software/test-software/src/units/modbus.cpp b/software/test-software/src/units/modbus.cpp
new file mode 100644 (file)
index 0000000..abbe36d
--- /dev/null
@@ -0,0 +1,160 @@
+#include <stdio.h>
+#include <avr/io.h>
+#include <util/delay.h>
+
+#include "modbus.hpp"
+#include "../main.hpp"
+
+
+#ifdef __AVR_ATmega328P__
+void Modbus::init () {}
+void Modbus::cleanup () {}
+int8_t Modbus::run (uint8_t subtest) { return -1; }
+void Modbus::handleRxByte (uint8_t b) {}
+#endif
+
+#if defined(__AVR_ATmega644P__) || defined(__AVR_ATmega1284P__)
+
+// PB0 ... nRE .. Read enable
+// PB1 ...  DE .. Data enable
+
+#define SET_nRE  (PORTB |= (1 << PB0))
+#define CLR_nRE  (PORTB &= ~(1 << PB0))
+#define SET_DE   (PORTB |= (1 << PB1))
+#define CLR_DE   (PORTB &= ~(1 << PB1))
+
+
+void Modbus::init () {
+}
+
+void Modbus::cleanup () {
+   enabled = 0;
+   UCSR1A = 0;
+   UCSR1B = 0;
+   UCSR1C = 0;
+   UBRR1H = 0;
+   UBRR1L = 0;
+   PORTD &= ~(1 << PD2);
+   DDRB &= ~((1 << PB1) | (1 << PB0));
+   PORTB &= ~((1 << PB1) | (1 << PB0));
+}
+
+int8_t Modbus::run (uint8_t subtest) {
+   if (subtest == 0) {
+      SET_nRE;
+      CLR_DE;
+      DDRB |= (1 << PB1) | (1 << PB0);
+
+      // UART1 interface on Nano-644
+      PORTD |= (1 << PD2); // enable RxD1 pullup
+      UCSR1A = (1 << U2X1);
+      UCSR1B = (1 << RXCIE1) | (1 << RXEN1) | (1 <<TXEN1);
+      UCSR1C = (1 << UCSZ11) | ( 1<< UCSZ10);
+      // UCSR1C |= (1 <<UPM11); // even Parity
+      // UCSR1C |= (1 << UPM11) | (1 << UPM10); // odd Parity
+      UBRR1H = 0;
+      UBRR1L = F_CPU / 8 / 9600 - 1;
+
+      enabled = 1;
+      printf_P(PSTR("init"));
+
+   } else if (subtest >= 1 && subtest <= 4) {
+      uint8_t nre, de, b;
+      switch (subtest) {
+         case 1: nre = 1; de = 0; b = 0x01; break;
+         case 2: nre = 1; de = 1; b = 0x8e; break;
+         case 3: nre = 0; de = 0; b = 0x55; break;
+         case 4: nre = 0; de = 1; b = 0xaa; break;
+         default: return -1;
+      }
+      printf_P(PSTR(" DE=%u, nRE=%u send 0x%02x... "), de, nre, b);
+      if (nre) {
+         SET_nRE;
+      } else {
+         CLR_nRE;
+      }
+      if (de) {
+         SET_DE;
+      } else {
+         CLR_DE;
+      }
+      _delay_us(100);
+      receivedBytes = 0;
+      UDR1 = b;
+      _delay_ms(1);
+      if (receivedBytes > 0) {
+         printf_P(PSTR("0x%02x received"), received[0]);
+         receivedBytes = 0;
+      } else {
+         printf_P(PSTR("no byte received"));
+      }
+      printf_P(PSTR(" ... press key to proceed"));
+      while (wait(0xffffffff) == EOF) {}
+      CLR_DE;
+      SET_nRE;
+
+   } else if (subtest == 5) {
+      static uint8_t frame[] = { 0x01, 0x04, 0x00, 0x00, 0x00, 0x02, 0x71, 0xcb };
+      printf_P(PSTR("Modbus: lese Spannung von Eastron SDM-230 (Einphasenzähler)"));
+      SET_DE;
+      CLR_nRE;
+      _delay_us(100);
+      do {
+         SET_DE;
+         receivedBytes = 0;
+         for (uint8_t i = 0; i < sizeof(frame); i++) {
+            UCSR1A |= (1 << TXC1);
+            UDR1 = frame[i];
+            while ((UCSR1A & (1 <<UDRE1)) == 0) {}
+         }
+         while ((UCSR1A & (1 << TXC1)) == 0) {}
+         CLR_DE;
+         printf_P(PSTR("\n => Sending:"));
+         for (uint8_t i = 0; i < sizeof(frame); i++) {
+            printf_P(PSTR(" 0x%02x"), frame[i]);
+         }
+         int k = wait(100);
+      
+         printf_P(PSTR("\n       RxD1:"));
+         if (receivedBytes == 0) {
+            printf_P(PSTR("?"));
+         } else {
+            for (uint8_t i = 0; i < receivedBytes; i++) {
+               if (i == sizeof(frame)) {
+                  printf_P(PSTR("  "));
+               }
+               printf_P(PSTR(" 0x%02x"), received[i]);
+            }
+         }
+         if (receivedBytes >= 16) {
+            union {
+               uint8_t b[4];
+               float value;
+            } f;
+            f.b[0] = received[14];
+            f.b[1] = received[13];
+            f.b[2] = received[12];
+            f.b[3] = received[11];
+            printf_P(PSTR(" -> %4.8fV\n"), (double)f.value);
+         }
+         if (k != EOF) {
+            break;
+         }
+         
+      } while (wait(1000) == EOF);
+
+   } else {
+      printf_P(PSTR("end"));
+      return -1;
+   }
+   wait(500);
+   return 0;
+}
+
+void Modbus::handleRxByte (uint8_t b) {
+   if (receivedBytes < sizeof(received)) {
+      received[receivedBytes++] = b;
+   }
+}
+
+#endif
\ No newline at end of file
diff --git a/software/test-software/src/units/modbus.hpp b/software/test-software/src/units/modbus.hpp
new file mode 100644 (file)
index 0000000..44b6a9d
--- /dev/null
@@ -0,0 +1,24 @@
+#ifndef MODBUS_HPP
+#define MODBUS_HPP
+
+#include <stdint.h>
+#include "../main.hpp"
+#include <avr/pgmspace.h>
+
+class Modbus : public TestUnit {
+   public:
+      uint8_t  enabled;
+      uint8_t  receivedBytes;
+      uint8_t  received[16];
+
+
+   public:
+      Modbus () { enabled = 0; receivedBytes = 0; }
+      virtual void init ();
+      virtual void cleanup ();
+      virtual int8_t run (uint8_t subtest);
+      virtual PGM_P getName () { return PSTR("Modbus"); }
+      void handleRxByte (uint8_t);
+};
+
+#endif
\ No newline at end of file
diff --git a/software/test-software/src/units/motor.cpp b/software/test-software/src/units/motor.cpp
new file mode 100644 (file)
index 0000000..7f8fbf9
--- /dev/null
@@ -0,0 +1,221 @@
+#include <stdio.h>
+#include <avr/io.h>
+#include <util/atomic.h>
+
+#include "motor.hpp"
+#include "../main.hpp"
+
+#if defined(__AVR_ATmega644P__) || defined(__AVR_ATmega1284P__)
+
+   // Nano-644
+   // ---------------------------------------------------------------
+   // PB0 ..... rotation-sensor
+   // PB2 ..... nFault
+   // PB3 ..... PWM (OC0A)
+   // PB4 ..... EN
+   // PA3 ..... SW3 -> push button for Motor enable control
+
+   #define ADC0K 64 
+
+   void Motor::init () {
+      DDRD |= (1 << PD7); // sensor signal toggle on PD7 (LED D4 red)
+      ADMUX = (1 << ADLAR) | (1 << REFS0); // ADC0, VREF=AVCC=3.3V
+      ADCSRA = (1 << ADEN) | 7; // ADC Enable, Prescaler 128
+      TCCR0A = (1 << COM0A1) | (1 << WGM01) | (1 << WGM00); // Fast PWM on OC0A
+      // TCCR0B = (1 << CS02) | ( 1 << CS00); // f = 12 MHz / 1024 = 11,71875 kHz -> fPWM=45Hz
+      TCCR0B = (1 << CS02); // f = 12 MHz / 256 = 46,875 kHz -> fPWM=183,1Hz
+      DDRB |= (1 << PB4) | (1 << PB3); // Motor enable
+      PORTA |= (1 << PORTA3); // push button for Motor enable control
+      setEnable();
+      enabled = 1;
+   }
+
+   void Motor::cleanup () {
+      DDRD &= ~(1 << PD7);
+      ADMUX = 0;
+      ADCSRA = 0;
+      TCCR0A = 0;
+      TCCR0B = 0;
+      DDRB &= ~((1 << PB4) | (1 << PB3));
+      PORTA &= ~(1 << PORTA3);
+      enabled = 0;
+   }
+
+   bool Motor::isSW3Pressed () {
+      return (PINA & (1 << PC3)) == 0;
+   }
+
+   void Motor::clearEnable () {
+      PORTB &= ~(1 << PB4);
+   }
+
+   void Motor::setEnable () {
+      PORTB |= (1 << PB4);
+   }
+
+   bool Motor::isFaultLow () {
+      return (PINB & (1 << PB2)) == 0;
+   }
+
+   bool Motor::isSensorHigh () {
+      return (PINB & (1 << PB0)) != 0;
+   }
+
+   void Motor::toggleD4 () {
+      PORTD ^= (1 << PD7);
+   }
+
+#endif 
+
+#ifdef __AVR_ATmega328P__
+
+   // Arduino-Nano-5V
+   // ---------------------------------------------------------------
+   // PD4       ..... rotation-sensor
+   // PD7       ..... nFault
+   // PD6/OC0A  ..... PWM
+   // PB2       ..... EN
+   // PC3       ..... SW3 -> push button for Motor enable control
+
+   #define ADC0K 91
+
+   void Motor::init () {
+      DDRD |= (1 << PD2); // sensor signal toggle on PD2 (LED D4 red)
+      ADMUX = (1 << ADLAR) | (1 << REFS0); // ADC0, VREF=AVCC=5V
+      ADCSRA = (1 << ADEN) | 7; // ADC Enable, Prescaler 128
+      TCCR0A = (1 << COM0A1) | (1 << WGM01) | (1 << WGM00); // Fast PWM on OC0A
+      // TCCR0B = (1 << CS02) | ( 1 << CS00); // f = 16 MHz / 1024 = 15,625 kHz -> fPWM=61.04Hz
+      TCCR0B = (1 << CS02); // f = 16 MHz / 256 = 62.5 kHz -> fPWM=244.14Hz
+      DDRB |= (1 << PB2);
+      DDRC &= ~(1 << PC3);
+      DDRD |= (1 << PD6);
+      DDRD &= ~((1 << PD4) | (1 << PD7));
+      PORTC |= ( 1 << PC3);
+      PORTD |= (1 << PD7) | (1 << PD5); 
+      setEnable();
+      enabled = 1;
+   }
+
+   void Motor::cleanup () {
+      DDRD &= ~(1 << PD2);
+      enabled = 0;
+      ADMUX = 0;
+      ADCSRA = 0;
+      TCCR0A = 0;
+      TCCR0B = 0;
+      clearEnable();
+      DDRB &= ~((1 << PB2));
+      DDRC &= ~(1 << PC3);
+      DDRD &= ~((1 << PD7) | (1 << PD6) | (1 << PD4));
+      PORTC &= ~( 1 << PC3);
+      PORTD &= ~((1 << PD7) | (1 << PD6));
+   }
+
+   bool Motor::isSW3Pressed () {
+      return (PINC & (1 << PC3)) == 0;
+   }
+
+   void Motor::clearEnable () {
+      PORTB &= ~(1 << PB2);
+   }
+
+   void Motor::setEnable () {
+      PORTB |= (1 << PB2);
+   }
+
+   bool Motor::isFaultLow () {
+      return (PIND & (1 << PD7)) == 0;
+   }
+
+   bool Motor::isSensorHigh () {
+      return (PIND & (1 << PD4)) != 0;
+   }
+
+   void Motor::toggleD4 () {
+      PORTD ^= (1 << PD2);
+   }
+
+#endif
+
+int8_t Motor::run (uint8_t subtest) {
+   switch (subtest) {
+      case 0: {
+         printf_P(PSTR("\n"));
+         while (wait(10) == EOF) {
+
+            printf_P(PSTR("\r SW3=%d->"), isSW3Pressed() ? 0 : 1);
+            if (isSW3Pressed()) {
+               clearEnable();
+               printf_P(PSTR("EN=0"));
+            } else {
+               setEnable();
+               printf_P(PSTR("EN=1"));
+            }
+
+            ADCSRA |= (1 << ADSC); // start ADC
+            while (ADCSRA & (1 << ADSC)) {} // wait for result
+            printf_P(PSTR("\r  => ADC0=%3d"), ADCH);
+
+            ADMUX = (1 << ADLAR) | (1 << REFS1) | (1 << REFS0) | 2; // ADC2, VREF=2.5V
+
+            int16_t x = ((int16_t)(ADCH) - 5) * ADC0K / 64;
+            if (x < 0) x = 0; else if (x > 255) x = 255;
+            uint8_t dutyCycle = 0xff - (uint8_t)x;
+            if (dutyCycle <= 1) {
+               dutyCycle = 0;
+            } else if (dutyCycle > 254) {
+               dutyCycle = 255;
+            }
+            OCR0A = dutyCycle;
+            printf_P(PSTR("  PWM/OC0A=%3d"), dutyCycle);
+
+            ADCSRA |= (1 << ADSC); // start ADC
+            while (ADCSRA & (1 << ADSC)) {} // wait for result
+            printf_P(PSTR(" ADC2=%3d"), ADCH);
+            ADMUX = (1 << ADLAR) | (1 << REFS0); // ADC0, VREF=AVCC=3.3V
+
+            printf_P(PSTR(" nFAULT=%d"), isFaultLow() ? 0 : 1);
+            printf_P(PSTR(" SENSOR=%d "), isSensorHigh());
+            uint16_t timer;
+            ATOMIC_BLOCK(ATOMIC_FORCEON) {
+               timer = rpmTimer;
+            }
+            float rpm = 60.0 / (float)timer / 0.0001;
+            if (timer > 0) {
+               printf_P(PSTR("  n= %5d U/min (T=%04x)"), (int)rpm, timer);
+            } else {
+               printf_P(PSTR("  no rotation (T=%04x)  "), timer);
+            }
+
+         }
+         return 0;
+      }
+   }
+
+   return -1;
+}
+
+void Motor::tick100us () {
+   static uint16_t timerH = 0;
+   static uint16_t timerL = 0;
+   static bool lastSensorHigh = false;
+
+   bool sensorHigh = isSensorHigh();
+   if (!sensorHigh && sensorHigh != lastSensorHigh && timerL > 2) {
+      rpmTimer = timerL + timerH;
+      timerL = 0;
+      timerH = 0;
+      toggleD4();
+   }
+   if (sensorHigh) {
+      timerH = timerH < 0x4000 ? timerH + 1 : 0x4000;
+   } else {
+      timerL = timerL < 0x4000 ? timerL + 1 : 0x4000;
+   }
+   if (timerH >= 0x4000 || timerL >= 0x4000) {
+      rpmTimer = 0; // no ratation detected
+   }
+   lastSensorHigh = sensorHigh;
+}
+
+
diff --git a/software/test-software/src/units/motor.hpp b/software/test-software/src/units/motor.hpp
new file mode 100644 (file)
index 0000000..2cbd15a
--- /dev/null
@@ -0,0 +1,30 @@
+#ifndef MOTOR_HPP
+#define MOTOR_HPP
+
+#include <stdint.h>
+#include "../main.hpp"
+#include <avr/pgmspace.h>
+
+class Motor : public TestUnit {
+   public:
+      uint8_t  enabled;
+      uint16_t rpmTimer;
+
+   public:
+      Motor () { enabled = 0; rpmTimer = 0; };
+      virtual void init ();
+      virtual void cleanup ();
+      virtual int8_t run (uint8_t subtest);
+      virtual PGM_P getName () { return PSTR("Motor"); }
+      void tick100us ();
+
+   private:
+      bool isSW3Pressed ();
+      void clearEnable ();
+      void setEnable ();
+      bool isFaultLow ();
+      bool isSensorHigh ();
+      void toggleD4 ();
+};
+
+#endif
\ No newline at end of file
diff --git a/software/test-software/src/units/portexp.cpp b/software/test-software/src/units/portexp.cpp
new file mode 100644 (file)
index 0000000..a153589
--- /dev/null
@@ -0,0 +1,195 @@
+#include <stdio.h>
+#include <avr/io.h>
+#include <util/delay.h>
+
+#include "portexp.hpp"
+#include "../main.hpp"
+
+// Port-Expander MCP23S17
+
+// SN-Print Stecker IO16
+// MCP23S17 | IO16 (MEGA2560) | Ampel-Print |   | MCP23S17 | IO16 (MEGA2560) | Ampel-Print |
+// ------------------------------------------   --------------------------------------------
+//     GPA0 |   IO16O7 (PA7)  | Taster RU   |   |     GPB0 |   IO16U7 (PC7)  | Taster LU   |
+//     GPA1 |   IO16O6 (PA6)  | Taster RO   |   |     GPB1 |   IO16U6 (PC6)  | Taster LO   |
+//     GPA2 |   IO16O5 (PA5)  | U-Gruen     |   |     GPB2 |   IO16U5 (PC5)  | R-Gruen     |
+//     GPA3 |   IO16O4 (PA4)  | U-Gelb      |   |     GPB3 |   IO16U4 (PC4)  | R-Gelb      |
+//     GPA4 |   IO16O3 (PA3)  | U-Rot       |   |     GPB4 |   IO16U3 (PC3)  | R-Rot       |
+//     GPA5 |   IO16O2 (PA2)  | L-Gruen     |   |     GPB5 |   IO16U2 (PC2)  | O-Gruen     |
+//     GPA6 |   IO16O1 (PA1)  | L-Gelb      |   |     GPB6 |   IO16U1 (PC1)  | O-Gelb      |
+//     GPA7 |   IO16O0 (PA0)  | L-Rot       |   |     GPB7 |   IO16U0 (PC0)  | O-Rot       |
+
+#if defined(__AVR_ATmega644P__) || defined(__AVR_ATmega1284P__)
+
+   // Nano-644 / Nano1284
+   // --------------------------------------------------------
+   // PA7 ... nCS
+   // PB5 ... MOSI
+   // PB6 ... MISO
+   // PB7 ... SCK
+
+   void PortExp::init () {
+      PRR0 &= (1 << PRSPI);
+      PORTA |= (1 << PA7);
+      DDRA |= (1 << PA7); // SPI nCS
+      // PORTB/DDRB must be configured before SPCR !!
+      PORTB |= (1 << PB4); // nSS must be HIGH, otherwise SPI master will not become active!!
+      DDRB |= (1 << PB7) | (1 << PB5) | (1 << PB4); // SPI SCK (=PB7) and SPI MOSI (=PB5)
+      DDRB &= ~(1 << PB6);
+
+      SPCR |= (1 << SPE) | (1 << MSTR) | (1 << SPR1) | (1 << SPR0); // SPI enable , Master, f=12MHz/128=93,75kHz
+      // SPCR = (1 << SPE) | (1 << MSTR); // SPI enable , Master, f=12MHz/4 = 3MHz
+   }
+
+   void PortExp::cleanup () {
+      DDRB &= ~(1 << PB6); // // SPI MISO (=PB6)
+      DDRB &= ~((1 << PB7) | (1 << PB5)); // SPI SCK (=PB7) and SPI MOSI (=PB5)
+      DDRA &= ~(1 << PA7);
+      PORTA &= ~(1 << PA7); // SPI nCS
+      SPCR = 0;
+   }
+
+   void PortExp::setChipEnable () {
+      PORTA &= ~(1 << PA7);
+   }
+
+   void PortExp::clearChipEnable () {
+      PORTA |= (1 << PA7);
+   }
+
+
+#endif
+
+#ifdef __AVR_ATmega328P__
+
+   // Arduino-Nano-5V
+   // ------------------------------------
+   // PC1 ... nCS    (MANUAL (!) connection PA1 - PA7 required)
+   // PB3 ... MOSI
+   // PB4 ... MISO
+   // PB5 ... SCK
+
+   void PortExp::init () {
+      PRR &= (1 << PRSPI);
+      PORTC |= (1 << PC1);
+      DDRC |= (1 << PC1); // SPI nCS
+      // PORTB/DDRB must be configured before SPCR !!
+      PORTB |= (1 << PB2); // nSS must be HIGH, otherwise SPI master will not become active!!
+      DDRB |= (1 << PB5) | (1 << PB3) | (1 << PB2); // SPI SCK (=PB5), SPI MOSI (=PB3), SPI nSS (=PB2)
+
+      SPCR |= (1 << SPE) | (1 << MSTR) | (1 << SPR1) | (1 << SPR0); // SPI enable , Master, f=16MHz/128=125kHz
+      // SPCR = (1 << SPE) | (1 << MSTR); // SPI enable , Master, f=12MHz/4 = 3MHz
+
+      if (hardwareVersion == 2) {
+         PORTD |= (1 << PD7); // nCS when V2a/JP39.2-3 jumpered
+         DDRD |= (1 << PD7);
+      }
+   }
+
+   void PortExp::cleanup () {
+      PORTC &= ~(1 << PC1);
+      DDRC &= ~(1 << PC1);
+      PORTB &= ~(1 << PB2);
+      DDRB &= ~((1 << PB5) | (1 << PB3) | (1 << PB2));
+      SPCR = 0;
+   }
+
+   void PortExp::setChipEnable () {
+      if (hardwareVersion == 2) {
+         PORTD &= ~(1 << PD7);
+      }
+   }
+
+   void PortExp::clearChipEnable () {
+      if (hardwareVersion == 2) {
+         PORTD |= (1 << PD7);
+      }
+   }
+
+#endif
+
+
+
+int8_t PortExp::writeByte (uint8_t addr, uint8_t b) {
+   // no response via SPI MISO because SO stays tristate on write cycle
+   setChipEnable();
+   SPDR = 0x40; // WRITE BYTE
+   while ((SPSR & (1 << SPIF)) == 0) {}
+   SPDR = addr; // register address
+   while ((SPSR & (1 << SPIF)) == 0) {}
+   SPDR = b; // value
+   while ((SPSR & (1 << SPIF)) == 0) {}
+   clearChipEnable();
+
+   _delay_us(5);
+   return 0;
+}
+
+uint8_t PortExp::readByte (uint8_t addr) {
+   // response via SPI MISO only on third byte
+   setChipEnable();
+   SPDR = 0x41; // write "READ BYTE"
+   while ((SPSR & (1 << SPIF)) == 0) {}
+   SPDR = addr; // write "register address"
+   while ((SPSR & (1 << SPIF)) == 0) {}
+   SPDR = 0; // additional 8 clocks to get response from port expander
+   while ((SPSR & (1 << SPIF)) == 0) {}
+   clearChipEnable();
+   return SPDR;
+}
+
+void PortExp::checkResponse (uint8_t address, uint8_t response, uint8_t desired) {
+   printf_P(PSTR(" (read 0x%02x -> 0x%02x"), address, response);
+   if (response != desired) {
+      printf_P(PSTR(" ERROR"));
+      if (response == 0xff) {
+         printf_P(PSTR(" JP39.2/3 jumpered (left)?"));
+      }
+   } else {
+      printf_P(PSTR(" OK"));
+   }
+   printf_P(PSTR(")"));
+}
+
+
+int8_t PortExp::run (uint8_t subtest) {
+   #ifdef __AVR_ATmega328P__
+      if (hardwareVersion == 1) {
+         printf_P(PSTR("ERROR - nCS not controlable\n"));
+         return -1;
+      }
+   #endif
+   if (subtest == 0) {
+      while (wait(500) == EOF) {
+         printf_P(PSTR("\n => start ..."));
+         for (uint8_t i = 0; i < 8; i++) {
+            writeByte(0, ~(1 << i)); // IODIRA (Bank = 0)
+            writeByte(0x12, (1 << i)); // GPIOA (Bank = 0)
+            printf_P(PSTR("\n  Bank0 - GPA%d = 1"), i);
+            checkResponse (0x12, readByte(0x12), (1 << i));
+            wait(200);
+            writeByte(0x12, 0); // GPIOA (Bank = 0)
+            printf_P(PSTR("\n  Bank0 - GPA%d = 0"), i);
+            checkResponse (0x12, readByte(0x12), 0);
+            writeByte(0, 0xff); // IODIRA (Bank = 0)
+            wait(200);
+         }
+         for (uint8_t i = 0; i < 8; i++) {
+            writeByte(1, ~(1 << i)); // IODIRB (Bank = 0)
+            writeByte(0x13, (1 << i)); // GPIOB (Bank = 0)
+            printf_P(PSTR("\n  Bank0 - GPB%d = 1"), i);
+            checkResponse (0x13, readByte(0x13), (1 << i));
+            wait(200);
+            writeByte(0x13, 0); // GPIOB (Bank = 0)
+            printf_P(PSTR("\n  Bank0 - GPB%d = 0"), i);
+            checkResponse (0x13, readByte(0x13), 0);
+            writeByte(1, 0xff); // IODIRB (Bank = 0)
+            wait(200);
+         }
+      }
+      return 0;
+   }
+   
+   return -1;
+}
+
diff --git a/software/test-software/src/units/portexp.hpp b/software/test-software/src/units/portexp.hpp
new file mode 100644 (file)
index 0000000..8705662
--- /dev/null
@@ -0,0 +1,24 @@
+#ifndef PORTEXP_HPP
+#define PORTEXP_HPP
+
+#include <stdint.h>
+#include "../main.hpp"
+#include <avr/pgmspace.h>
+
+class PortExp : public TestUnit {
+   public:
+      PortExp () {};
+      virtual void init ();
+      virtual void cleanup ();
+      virtual int8_t run (uint8_t subtest);
+      virtual PGM_P getName () { return PSTR("PortExp"); }
+   
+   private:
+      void setChipEnable ();
+      void clearChipEnable ();
+      int8_t writeByte (uint8_t addr, uint8_t b);
+      uint8_t readByte (uint8_t addr);
+      void checkResponse (uint8_t address, uint8_t response, uint8_t desired);
+};
+
+#endif
\ No newline at end of file
diff --git a/software/test-software/src/units/poti.cpp b/software/test-software/src/units/poti.cpp
new file mode 100644 (file)
index 0000000..94fc5a4
--- /dev/null
@@ -0,0 +1,34 @@
+#include <stdio.h>
+#include <avr/io.h>
+
+#include "poti.hpp"
+#include "../main.hpp"
+
+void Poti::init () {
+   ADMUX = (1 << REFS0); // ADC0, VREF=AVCC=3.3V
+   ADCSRA = (1 << ADEN) | 7; // ADC Enable, Prescaler 128
+}
+
+void Poti::cleanup () {
+   ADMUX = 0;
+   ADCSRA = 0;
+}
+
+int8_t Poti::run (uint8_t subtest) {
+   switch (subtest) {
+      case 0: {
+         printf_P(PSTR("\n"));
+         while (wait(10) == EOF) {
+            printf_P(PSTR("\r  => Measure ADC0: "));
+            ADCSRA |= (1 << ADSC); // start ADC
+            while (ADCSRA & (1 << ADSC)) {} // wait for result
+            printf_P(PSTR("%4d (0x%03x)"), ADC, ADC);
+         }
+         return 0;
+      }
+   }
+
+   return -1;
+}
+
+
diff --git a/software/test-software/src/units/poti.hpp b/software/test-software/src/units/poti.hpp
new file mode 100644 (file)
index 0000000..b13dd29
--- /dev/null
@@ -0,0 +1,17 @@
+#ifndef POTI_HPP
+#define POTI_PP
+
+#include <stdint.h>
+#include "../main.hpp"
+#include <avr/pgmspace.h>
+
+class Poti : public TestUnit {
+   public:
+      Poti () {};
+      virtual void init ();
+      virtual void cleanup ();
+      virtual int8_t run (uint8_t subtest);
+      virtual PGM_P getName () { return PSTR("Poti"); }
+};
+
+#endif
\ No newline at end of file
diff --git a/software/test-software/src/units/r2r.cpp b/software/test-software/src/units/r2r.cpp
new file mode 100644 (file)
index 0000000..54664b1
--- /dev/null
@@ -0,0 +1,48 @@
+#include <stdio.h>
+#include <avr/io.h>
+
+#include "r2r.hpp"
+#include "../main.hpp"
+
+#if defined(__AVR_ATmega644P__) || defined(__AVR_ATmega1284P__)
+   // AVCC=3.3, POTI Vmax=3.3V
+   #define K 1.0
+#endif
+
+#ifdef __AVR_ATmega328P__
+   // AVCC=4.7V, POTI Vmax=3.3V
+   #define K 1.32
+#endif
+
+void R2r::init () {
+   ADMUX = (1 << REFS0) | 2; // ADC2, VREF=AVCC (=3.3V for Nano-644/1284, =5V for Arduino Nano)
+   ADCSRA = (1 << ADEN) | 7; // ADC Enable, Prescaler 128
+}
+
+void R2r::cleanup () {
+   ADMUX = 0;
+   ADCSRA = 0;
+}
+
+int8_t R2r::run (uint8_t subtest) {
+   switch (subtest) {
+      case 0: {
+         printf_P(PSTR("\n"));
+         while (wait(10) == EOF) {
+            printf_P(PSTR("\r  => Measure ADC2: "));
+            ADCSRA |= (1 << ADSC); // start ADC
+            while (ADCSRA & (1 << ADSC)) {} // wait for result
+            printf_P(PSTR("%4d (0x%03x)"), ADC, ADC);
+            // uint8_t sw = (uint8_t)( ((float)(ADC) + 4.0) / 64.0 * K );
+            float swf = ((float)(ADC) * K) / 64.8 + 0.55;
+            uint8_t sw = (uint8_t)swf ;
+            printf_P(PSTR(" %3.1f => SW9:6 = %d %d% d %d  "), swf, sw >> 3, (sw >> 2) & 0x01, (sw >> 1) & 0x01, sw & 0x01 );
+         }
+         return 0;
+      }
+   }
+
+   return -1;
+}
+
+
diff --git a/software/test-software/src/units/r2r.hpp b/software/test-software/src/units/r2r.hpp
new file mode 100644 (file)
index 0000000..84e97e6
--- /dev/null
@@ -0,0 +1,17 @@
+#ifndef R2R_HPP
+#define R2R_PP
+
+#include <stdint.h>
+#include "../main.hpp"
+#include <avr/pgmspace.h>
+
+class R2r : public TestUnit {
+   public:
+      R2r () {};
+      virtual void init ();
+      virtual void cleanup ();
+      virtual int8_t run (uint8_t subtest);
+      virtual PGM_P getName () { return PSTR("R2R"); }
+};
+
+#endif
\ No newline at end of file
diff --git a/software/test-software/src/units/rgb.cpp b/software/test-software/src/units/rgb.cpp
new file mode 100644 (file)
index 0000000..3f69cbd
--- /dev/null
@@ -0,0 +1,152 @@
+#include <stdio.h>
+#include <avr/io.h>
+
+#include "rgb.hpp"
+#include "../main.hpp"
+
+#if defined(__AVR_ATmega644P__) || defined(__AVR_ATmega1284P__)
+
+   // Nano-644
+   // ---------------------------------------------------------------
+   // PB0 ..... Red (inverse logic -> 0 = ON)
+   // PB1 ..... Green (inverse logic -> 0 = ON)
+   // PB2 ..... Blue (inverse logic -> 0 = ON)
+
+   void Rgb::init () {
+      ledOff(RED);
+      ledOff(GREEN);
+      ledOff(BLUE);
+      DDRB |= (1 << PB2) | (1 << PB1) | (1 << PB0);
+   }
+
+   void Rgb::cleanup () {
+      ledOff(RED);
+      ledOff(GREEN);
+      ledOff(BLUE);
+      DDRB &= ~((1 << PB2) | (1 << PB1) | (1 << PB0));
+   }
+
+   void Rgb::setLed (LED led, bool on) {
+      if (on) {
+         switch(led) {
+            case RED: PORTB &= ~(1 << PB0); break;
+            case GREEN: PORTB &= ~(1 << PB1); break;
+            case BLUE: PORTB &= ~(1 << PB2); break;
+         }
+      } else {
+         switch(led) {
+            case RED: PORTB |= (1 << PB0); break;
+            case GREEN: PORTB |= (1 << PB1); break;
+            case BLUE: PORTB |= (1 << PB2); break;
+         }
+      }
+   }
+
+   void Rgb::ledToggle (LED led) {
+      switch(led) {
+         case RED: PORTB ^= (1 << PB0); break;
+         case GREEN: PORTB ^= (1 << PB1); break;
+         case BLUE: PORTB ^= (1 << PB2); break;
+       }
+   }
+
+
+#endif
+
+#ifdef __AVR_ATmega328P__
+
+   // Arduino-Nano-5V
+   // ---------------------------------------------------------------
+   // PD4 ..... Red (inverse logic -> 0 = ON)
+   // PB0 ..... Green (inverse logic -> 0 = ON)
+   // PD7 ..... Blue (inverse logic -> 0 = ON)
+
+   void Rgb::init () {
+      ledOff(RED);
+      ledOff(GREEN);
+      ledOff(BLUE);
+      DDRB |= (1 << PB0);
+      DDRD |= (1 << PD7) | (1 << PD4);
+   }
+
+   void Rgb::cleanup () {
+      ledOff(RED);
+      ledOff(GREEN);
+      ledOff(BLUE);
+      DDRB &= ~(1 << PB0);
+      DDRD &= ~((1 << PD7) | (1 << PD4));
+   }
+
+   void Rgb::setLed (LED led, bool on) {
+      if (on) {
+         switch (led) {
+            case RED: PORTD &= ~(1 << PD4); break;
+            case GREEN: PORTB &= ~(1 << PB0); break;
+            case BLUE: PORTD &= ~(1 << PD7); break;
+         }
+      } else {
+         switch (led) {
+            case RED: PORTD |= (1 << PD4); break;
+            case GREEN: PORTB |= (1 << PB0); break;
+            case BLUE: PORTD |= (1 << PD7); break;
+         }
+      }
+   }
+
+   void Rgb::ledToggle (LED led) {
+      switch (led) {
+         case RED: PORTD ^= (1 << PD4); break;
+         case GREEN: PORTB ^= ~(1 << PB0); break;
+         case BLUE: PORTD ^= (1 << PD7); break;
+       }
+   }
+
+#endif
+
+void Rgb::ledOn (LED led) {
+   setLed(led, true);
+}
+
+void Rgb::ledOff (LED led) {
+   setLed(led, false);
+}
+
+int8_t Rgb::run (uint8_t subtest) {
+   switch (subtest) {
+      case 0: {
+         ledOn(RED);
+         printf_P(PSTR("Red"));
+         wait(3000);
+         ledOff(RED);
+         return 0;
+      }
+
+      case 1: {
+         ledOn(GREEN);
+         printf_P(PSTR("Green"));
+         wait(3000);
+         ledOff(GREEN);
+         return 0;
+      }
+
+      case 2: {
+         ledOn(BLUE);
+         printf_P(PSTR("Blue"));
+         wait(3000);
+         ledOff(BLUE);
+         return 0;
+      }
+
+      case 3: {
+         ledOn(RED); ledOn(GREEN); ledOn(BLUE);
+         printf_P(PSTR("All"));
+         wait(3000);
+         ledOff(RED); ledOff(GREEN); ledOff(BLUE);
+         return 0;
+      }
+   }
+
+   return -1;
+}
+
+
diff --git a/software/test-software/src/units/rgb.hpp b/software/test-software/src/units/rgb.hpp
new file mode 100644 (file)
index 0000000..12e9da4
--- /dev/null
@@ -0,0 +1,25 @@
+#ifndef RGB_HPP
+#define RGB_PP
+
+#include <stdint.h>
+#include "../main.hpp"
+#include <avr/pgmspace.h>
+
+class Rgb : public TestUnit {
+   public:
+      enum LED { RED, GREEN, BLUE };
+
+   public:
+      Rgb () {};
+      virtual void init ();
+      virtual void cleanup ();
+      virtual int8_t run (uint8_t subtest);
+      virtual PGM_P getName () { return PSTR("Rgb"); }
+
+      void setLed (LED led, bool on);
+      void ledOn (LED led);
+      void ledOff (LED led);
+      void ledToggle (LED led);
+};
+
+#endif
\ No newline at end of file
diff --git a/software/test-software/src/units/rtc8563.cpp b/software/test-software/src/units/rtc8563.cpp
new file mode 100644 (file)
index 0000000..6ae6497
--- /dev/null
@@ -0,0 +1,253 @@
+#include <stdio.h>
+#include <avr/io.h>
+#include <avr/pgmspace.h>
+#include <util/delay.h>
+#include <math.h>
+
+#include "rtc8563.hpp"
+#include "../adafruit/bme280.h"
+#include "../main.hpp"
+
+// RTC BME653EMA on Nano-644
+
+const char PSTR_WEEKDAYS[] PROGMEM = "So\0Mo\0Di\0Mi\0Do\0Fr\0Sa\0";
+
+// const uint8_t CONFIG[] PROGMEM = {
+//    /* config       -> */ 0x00, 0x00,
+//    /* enable nINT  -> */ 0x01, 0x01, // TIE = 1
+//    /* set clock    -> */ 0x02, 0x00, 0x00, 0x08, 0x16, 0x02, 0x08, 0x24 
+// };
+
+void Rtc8563::handleTwiIrq () {
+   TWCR |= (1 << TWINT); // clear Interrupt Request
+}
+
+#ifdef __AVR_ATmega328P__
+void Rtc8563::init () {}
+void Rtc8563::cleanup () {}
+int8_t Rtc8563::run (uint8_t subtest) { return -1; }
+PGM_P Rtc8563::getName () { return PSTR("?"); }
+#endif
+
+#if defined(__AVR_ATmega644P__) || defined(__AVR_ATmega1284P__)
+
+void Rtc8563::init () {
+   PORTC &= ~(1 << PC7); // nInt and nPowerOn (Q7 -> BATTERY)
+   DDRC |= (1 << PC7);
+   TWBR = 13; // 100kHz (TWPS1:0 = 00), TWBR = (F_CPU - 16 * 100000) / (2 * 100000 * 4);
+   TWBR = 28; // 50kHz (TWPS1:0 = 00), TWBR = (F_CPU - 16 * 50000) / (2 * 50000 * 4);
+   TWBR = 100; // 50kHz (TWPS1:0 = 00), TWBR = (F_CPU - 16 * 50000) / (2 * 50000 * 4);
+   TWCR = (1 << TWEN);
+   enabled = true;
+}
+
+void Rtc8563::cleanup () {
+   enabled = false;
+   TWCR = (1 << TWEN);
+   TWBR = 0;
+   // PORTC &= ~(1 << PC7);
+   // DDRC &= ~(1 << PC7);
+}
+
+PGM_P Rtc8563::getName () {
+   return PSTR("RTC-8563");
+}
+
+uint8_t Rtc8563::bcd2bin (uint8_t value) {
+   return (value >> 4) * 10 + (value & 0x0f);
+}
+
+int8_t Rtc8563::run (uint8_t subtest) {
+   int key = EOF;
+   if (subtest == 0) {
+      // printf_P(PSTR(" BM280 ... "));
+      rtc8563.begin(0x51);
+      Clock_t clock;
+      // uint8_t bufferConfig[] = { 0x00, 0x00, 0x01, 0x01 };
+      // uint8_t bufferSetClock[] = { 0x02, 0x00, 0x00, 0x08, 0x16, 0x02, 0x08, 0x24 };
+
+      Rtc8563Reg_t reg;
+      uint8_t *pReg = (uint8_t *)(void *)&reg;
+      memset(&reg, 0, sizeof(reg));
+
+      printf_P(PSTR("\n => config 8563 ... "));
+      reg.reg1.field.tie = 1;
+      if (!rtc8563.write(pReg, 2)) {
+         printf_P(PSTR_ERROR);
+      } else {
+         printf_P(PSTR_Done);
+      }
+      printf_P(PSTR("\n press:"));
+      printf_P(PSTR("\n   t .... timer on/off"));
+      printf_P(PSTR("\n   p .... power on/off (PC7->Q1)"));
+      printf_P(PSTR("\n   c .... init clock"));
+      printf_P(PSTR("\n   w/W .. weekday (+/-)\n"));
+      printf_P(PSTR("\n   y/Y .. year (+/-)"));
+      printf_P(PSTR("\n   m/M .. month (+/-)"));
+      printf_P(PSTR("\n   d/D .. day (+/-)"));
+      printf_P(PSTR("\n   h/H .. hour (+/-)"));
+      printf_P(PSTR("\n   n/N .. minute (+/-)"));
+      printf_P(PSTR("\n   s/S .. second (+/-)\n"));
+
+      do {
+         uint8_t addr = 0x00;
+         printf_P(PSTR("\n => read register 0-15 (hex):"));
+         if (!rtc8563.write_then_read(&addr, 1, pReg, sizeof(reg))) {
+            printf_P(PSTR_ERROR);
+            key = waitAndReadKey(1000);
+            continue;
+         }
+         memccpy(&clock, &reg.clock, sizeof(clock), sizeof(clock));
+         for (uint8_t i = 0; i < 16; i++) {
+            if (i % 4 == 0) {
+               printf_P(PSTR(" ")); 
+            }
+            printf_P(PSTR(" %02X"), pReg[i]);
+         }
+         uint16_t year = (clock.month.field.century ? 2100 : 2000) + bcd2bin(clock.year.byte);
+         int8_t month = bcd2bin(clock.month.byte);
+         int8_t day = bcd2bin(clock.day.byte);
+         int8_t hrs = bcd2bin(clock.hour.byte);
+         int8_t min = bcd2bin(clock.min.byte);
+         int8_t sec = bcd2bin(clock.sec.byte);
+         int8_t weekday = clock.weekday.byte;
+
+         PGM_P d = weekday >= 0 && weekday < 7 ? &PSTR_WEEKDAYS[weekday * 3] : PSTR("??");
+         printf_P(PSTR("  --> "));
+         printf_P(d);
+         printf_P(PSTR(", %d %04d-%02d-%02d %02d:%02d:%02d"), weekday, year, month, day, hrs, min, sec);
+         printf_P(PSTR(" - Timer=0x%02x"), reg.timer);
+
+         key = waitAndReadKey(1000);
+         bool ok = true;
+         bool change = false;
+         switch (key) {
+            case 't': ok &= setTimer(reg.timerControl.field.enable ? 0 : 10, reg); break;
+            case 'c': ok &= setClock(5, 2024,8,16, 17,12,10 ); break; // ok &= rtc8563.write(bufferSetClock, sizeof(bufferSetClock)); break;
+            case 'p': powerOnOff(10, reg); break;
+            case 'y': year++; change = true; break;
+            case 'Y': year--; change = true; break;
+            case 'm': month++; change = true; break;
+            case 'M': month--; change = true; break;
+            case 'd': day++; change = true; break;
+            case 'D': day--; change = true; break;
+            case 'h': hrs++; change = true; break;
+            case 'H': hrs--; change = true; break;
+            case 'n': min++; change = true; break;
+            case 'N': min--; change = true; break;
+            case 's': sec++; change = true; break;
+            case 'S': sec--; change = true; break;
+            case 'w': weekday++; change = true; break;
+            case 'W': weekday--; change = true; break;
+         }
+         if (change) {
+            printf_P(PSTR("\n set: %04d-%02d-%02d %02d:%02d:%02d"), year, month, day, hrs, min, sec);
+            setClock(weekday, year, month, day, hrs, min, sec);
+         }
+         
+      } while (key != ESCAPE);
+
+      return 0;
+   }
+
+   return -1;
+}
+
+bool Rtc8563::setTimer (uint8_t seconds, Rtc8563Reg_t &reg) {
+
+   reg.timerControl.field.fd = FDTIMER_1HZ;
+   reg.timerControl.field.enable = seconds > 0;
+   reg.timer = seconds;
+   reg.reg1.field.tie = seconds > 0;
+   // clear and alarm flag behavior on I2C write different to datasheet
+   //    datasheet: tf cleared to 0, af remains unchanged
+   //    realchip:  tf remains 1 and af is set to 1 (no negative result because tie=0 and aie=0)
+   reg.reg1.field.tf = 0; // clear timer flag
+   reg.reg1.field.af = 1; // alarm flag remains unchanged
+   if (seconds > 0) {
+      printf_P(PSTR("\n Timer set to %ds (1:%02X) ... "), seconds, reg.reg1.byte);
+   } else {
+      printf_P(PSTR("\n Timer off ... "));
+   }
+   if (rtc8563.writeByteAndBuffer(0x01, &reg.reg1.byte, 1) && rtc8563.writeByteAndBuffer(14, &reg.timerControl.byte, 2)) {
+      printf_P(PSTR("OK"));
+      return true;
+   } else {
+      printf_P(PSTR("fails"));
+      return false;
+   }
+}
+
+bool Rtc8563::powerOnOff (uint8_t delayOffSeconds, Rtc8563Reg_t &reg) {
+   int key = EOF;
+   if (PORTC & (1 << PC7)) {
+      printf_P(PSTR("\n power on ..."));
+      DDRC |= ( 1<< PC7);
+      PORTC &= ~(1 << PC7);
+      setTimer(0, reg);
+   } else {
+      printf_P(PSTR("\n"));
+      key = EOF;
+      for (int8_t i = 9; i > 0 && key == EOF; i--) {
+         printf_P(PSTR("\r press ESC to abort, power off in %ds (press key to skip timer) "), i);
+         key = waitAndReadKey(1000);
+      }
+      if (key == ESCAPE) {
+         return true;
+      }
+      setTimer(10, reg);
+      reg.reg1.field.af = 1;  // alarm flag remains unchanged
+      reg.reg1.field.tf = 0;  // timer flag clear
+      reg.reg1.field.tie = 1; // enable timer interrupt 
+      rtc8563.writeByteAndBuffer(0x01, &reg.reg1.byte, 1);
+      printf_P(PSTR("\n power off now ..."));
+      DDRC |= ( 1<< PC7);
+      PORTC |= (1 << PC7);
+      _delay_ms(5);
+      DDRC &= ~( 1<< PC7);
+      PORTC &= ~(1 << PC7);
+      waitAndReadKey(5000);
+      printf_P(PSTR("power off fails, I am still alive :-) ... proceed"));
+   }
+   return true;
+}
+
+
+bool Rtc8563::setClock (int8_t weekday, uint16_t year, int8_t month, int8_t day, int8_t hour, int8_t min, int8_t sec) {
+   Clock_t clock;
+   clock.month.field.century = (year < 2000 || year > 2100) ? 1: 0;
+   uint8_t y = year % 100; clock.year.field.bcdL = y % 10; clock.year.field.bcdH = y / 10;
+
+   while (weekday < 0) { weekday += 7; }
+   while (weekday > 6) { weekday -= 7; }
+   clock.weekday.field.bcdL =  weekday;
+
+   while (month < 1) { month += 12; }
+   while (month > 12) { month -= 12; }
+   clock.month.field.bcdL = month % 10; clock.month.field.bcdH = month / 10;
+   
+   while (day < 1) { day += 31; }
+   while (day > 31) { day -= 31; }
+   clock.day.field.bcdL = day % 10; clock.day.field.bcdH = day / 10;
+   
+   while (hour < 0) { hour += 24; }
+   while (hour > 23) { hour -= 24; }
+   clock.hour.field.bcdL = hour % 10; clock.hour.field.bcdH = hour / 10;
+   
+   while (min < 0) { min += 60; }
+   while (min > 59) { min -= 60; }
+   clock.min.field.bcdL = min % 10; clock.min.field.bcdH = min / 10;
+
+   while (sec < 0) { sec += 60; }
+   while (sec > 59) { sec -= 60; }
+   clock.sec.field.bcdL = sec % 10; clock.sec.field.bcdH = sec / 10;
+
+   printf_P(PSTR("\n %p %p %p %p %p %p %p -> write: "), &clock.sec.byte, &clock.min.byte, &clock.hour.byte, &clock.day.byte, &clock.weekday.byte, &clock.month.byte, &clock.year.byte );
+   for (uint8_t i = 0; i < sizeof(clock); i++) {
+      printf_P(PSTR(" %02x"), ((uint8_t *)(void *)&clock)[i]);
+   }
+   return rtc8563.writeByteAndBuffer(0x02, (uint8_t *)(void *)&clock, sizeof(clock));
+}
+
+#endif
+
diff --git a/software/test-software/src/units/rtc8563.hpp b/software/test-software/src/units/rtc8563.hpp
new file mode 100644 (file)
index 0000000..ca79713
--- /dev/null
@@ -0,0 +1,70 @@
+#ifndef RTC8563_HPP
+#define RTC8563_HPP
+
+#include <stdint.h>
+#include "../main.hpp"
+#include "../adafruit/bme280.h"
+#include "../adafruit/ens160.h"
+#include "../i2cmaster.hpp"
+#include "../i2cslave.hpp"
+
+
+class Rtc8563 : public TestUnit {
+   public:
+      typedef enum { NORMAL } Rtc8563Mode_t;
+   
+   private:
+      I2cMaster rtc8563;
+      typedef enum { FDCLOCKOUT_32768HZ = 0, FDCLOCKOUT_1024HZ = 1, FDCLOCKOUT_32HZ = 2, FDCLOCKOUT_1HZ = 3 } FDCLOCKOUT_t;
+      typedef enum { FDTIMER_4096HZ = 0, FDTIMER_64HZ = 1, FDTIMER_1HZ = 2, FDTIMER_1D60HZ = 3 } FDTIMER_t;
+      typedef struct {
+         union { uint8_t byte; struct { uint8_t bcdL:4; uint8_t bcdH:3; uint8_t voltageLow:1; } field; } sec;
+         union { uint8_t byte; struct { uint8_t bcdL:4; uint8_t bcdH:3; uint8_t notUsed:1; } field; } min;
+         union { uint8_t byte; struct { uint8_t bcdL:4; uint8_t bcdH:2; uint8_t notUsed:2; } field; } hour;
+         union { uint8_t byte; struct { uint8_t bcdL:4; uint8_t bcdH:2; uint8_t notUsed:2; } field; } day;
+         union { uint8_t byte; struct { uint8_t bcdL:3; } field; uint8_t notUsed:5; } weekday;
+         union { uint8_t byte; struct { uint8_t bcdL:4; uint8_t bcdH:1; uint8_t notUsed:2; uint8_t century:1; } field; } month;
+         union { uint8_t byte; struct { uint8_t bcdL:4; uint8_t bcdH:4; } field; } year;
+      } Clock_t; // identical to 8563 register 2..8
+
+      typedef struct {
+         union { uint8_t byte; struct { uint8_t bcdL:4; uint8_t bcdH:3; uint8_t enable:1; } field; } min;
+         union { uint8_t byte; struct { uint8_t bcdL:4; uint8_t bcdH:2; uint8_t notUsed:1; uint8_t enable:1; } field; } hour;
+         union { uint8_t byte; struct { uint8_t bcdL:4; uint8_t bcdH:2; uint8_t notUsed:1; uint8_t enable:1; } field; } day;
+         union { uint8_t byte; struct { uint8_t bcdL:3; } field; uint8_t notUsed:4; uint8_t enable_W:1; } weekday;
+      } Alarm_t;
+
+      typedef struct {
+         union { uint8_t byte; struct { uint8_t notUsed210:3; uint8_t testC:1; uint8_t notUsed4:1; uint8_t stop:1; uint8_t notUsed6:1; uint8_t test1:1; } field; } reg0;
+         union { uint8_t byte; struct { uint8_t tie:1; uint8_t aie:1; uint8_t tf:1; uint8_t af:1; uint8_t ti_tp:1; uint8_t notUsed:3; } field; } reg1;
+         Clock_t clock;
+         Alarm_t alarm;
+         union { uint8_t byte; struct { FDCLOCKOUT_t fd:2; uint8_t notUsed65432:5; uint8_t enable:1; } field; } clockoutControl;
+         union { uint8_t byte; struct { FDTIMER_t fd:2; uint8_t notUsed65432:5; uint8_t enable:1; } field; } timerControl;
+         uint8_t timer;
+      } Rtc8563Reg_t;
+
+
+   public:
+      bool enabled;
+
+   public:
+      Rtc8563 (Rtc8563Mode_t mode) { enabled = false; this->mode = mode; }
+      void tick1ms () { rtc8563.tick1ms(); }
+      virtual void init ();
+      virtual void cleanup ();
+      virtual int8_t run (uint8_t subtest);
+      virtual PGM_P getName ();
+      void handleTwiIrq ();
+
+   private:
+      Rtc8563Mode_t mode;
+      uint8_t bcd2bin (uint8_t value);
+      int8_t runModeNormal (uint8_t subtest);
+      int8_t runModeBattery (uint8_t subtest);
+      bool setTimer (uint8_t seconds, Rtc8563Reg_t &reg);
+      bool powerOnOff (uint8_t delayOffSeconds, Rtc8563Reg_t &reg);
+      bool setClock (int8_t weekday, uint16_t year, int8_t month, int8_t day, int8_t hour, int8_t min, int8_t sec);
+};
+
+#endif
\ No newline at end of file
diff --git a/software/test-software/src/units/seg7.cpp b/software/test-software/src/units/seg7.cpp
new file mode 100644 (file)
index 0000000..f522456
--- /dev/null
@@ -0,0 +1,283 @@
+#include <stdio.h>
+#include <avr/io.h>
+
+#include "seg7.hpp"
+#include "../main.hpp"
+
+#if defined(__AVR_ATmega644P__) || defined(__AVR_ATmega1284P__)
+
+   // Nano-644 / Nano 1284
+   // ---------------------------------------------------------------
+
+   // Nano-X-Base             V1a  V2a
+   // ---------------------------------
+   // Anode Segment A ....... PB0  PB0
+   // Anode Segment B ....... PB1  PB1
+   // Anode Segment C ....... PB2  PB2
+   // Anode Segment D ....... PB3  PB3
+   // Anode Segment E ....... PB4  PB4
+   // Anode Segment F ....... PB5  PB5
+   // Anode Segment G ....... PB6  PB6
+   // Anode DP .............. PB7  PB7
+   // ---------------------------------
+   // Cathode Char 1 ........ PA0  PD4
+   // Cathode Char 2 ........ PA1  PD5
+   // Cathode Char 3 ........ PA2  PD6
+   // Cathode Char 4 ........ PA3  PD7
+   // ---------------------------------
+   // nOE (Output Enable) ... PD5  PA1
+   // Anode L1:2 ............ PD6  PA2
+   // Anode L3 .............. PD7  PA3
+
+void Seg7::init () {
+      setAnodes(0);
+      setCathodes(0);
+      DDRB = 0xff;
+      switch (hardwareVersion) {
+         case 1: {
+            DDRA |= (1 << PA3) | (1 << PA2) | (1 << PA1) | (1 << PA0);
+            DDRD |= (1 << PD7) | (1 << PD6) | (1 << PD5);
+            break;
+         }
+         case 2: {
+            DDRA |= (1 << PA3) | (1 << PA2) | (1 << PA1);
+            DDRD |= (1 << PD7) | (1 << PD6) | (1 << PD5) | (1 << PD4);
+         }
+         break;
+      }
+   }
+
+   void Seg7::cleanup () {
+      setAnodes(0);
+      setCathodes(0);
+      DDRB = 0x00;
+      switch (hardwareVersion) {
+         case 1: {
+            DDRA &= ~((1 << PA3) | (1 << PA2) | (1 << PA1) | (1 << PA0));
+            DDRD &= ~((1 << PD7) | (1 << PD6) | (1 << PD5));
+            break;
+         }
+         case 2: {
+            DDRA &= ~((1 << PA3) | (1 << PA2) | (1 << PA1));
+            DDRD &= ~((1 << PD7) | (1 << PD6) | (1 << PD5) | (1 << PD4));
+         }
+         break;
+      }
+   }
+
+   void Seg7::setAnodes (uint16_t a) {
+      if (a & 0x0001) PORTB |= (1 << PB0); else PORTB &= ~(1 << PB0); // Anode Char A
+      if (a & 0x0002) PORTB |= (1 << PB1); else PORTB &= ~(1 << PB1); // Anode Char B
+      if (a & 0x0004) PORTB |= (1 << PB2); else PORTB &= ~(1 << PB2); // Anode Char C
+      if (a & 0x0008) PORTB |= (1 << PB3); else PORTB &= ~(1 << PB3); // Anode Char D
+      if (a & 0x0010) PORTB |= (1 << PB4); else PORTB &= ~(1 << PB4); // Anode Char E
+      if (a & 0x0020) PORTB |= (1 << PB5); else PORTB &= ~(1 << PB5); // Anode Char F
+      if (a & 0x0040) PORTB |= (1 << PB6); else PORTB &= ~(1 << PB6); // Anode Char G
+      if (a & 0x0080) PORTB |= (1 << PB7); else PORTB &= ~(1 << PB7); // Anode Char DP
+      switch (hardwareVersion) {
+         case 1: {
+            if (a & 0x0100) PORTD |= (1 << PD6); else PORTD &= ~(1 << PD6); // Anode L1/L2
+            if (a & 0x0200) PORTD |= (1 << PD7); else PORTD &= ~(1 << PD7); // Anode L3
+            break;
+         }
+         case 2: {
+            if (a & 0x0100) PORTA |= (1 << PA2); else PORTA &= ~(1 << PA2); // Anode L1/L2
+            if (a & 0x0200) PORTA |= (1 << PA3); else PORTA &= ~(1 << PA3); // Anode L3
+            break;
+         }
+      }
+   }
+
+   void Seg7::setCathodes (uint8_t c) {
+      switch (hardwareVersion) {
+         case 1: {
+            if (c & 0x01) PORTA |= (1 << PA0); else PORTA &= ~(1 << PA0); // Chathode Char 1 (most left)
+            if (c & 0x02) PORTA |= (1 << PA1); else PORTA &= ~(1 << PA1); // Chathode Char 2
+            if (c & 0x04) PORTA |= (1 << PA2); else PORTA &= ~(1 << PA2); // Chathode Char 3
+            if (c & 0x08) PORTA |= (1 << PA3); else PORTA &= ~(1 << PA3); // Chathode Char 4 (most right)
+            break;
+         }
+         case 2: {
+            if (c & 0x01) PORTD |= (1 << PD4); else PORTD &= ~(1 << PD4); // Chathode Char 1 (most left)
+            if (c & 0x02) PORTD |= (1 << PD5); else PORTD &= ~(1 << PD5); // Chathode Char 2
+            if (c & 0x04) PORTD |= (1 << PD6); else PORTD &= ~(1 << PD6); // Chathode Char 3
+            if (c & 0x08) PORTD |= (1 << PD7); else PORTD &= ~(1 << PD7); // Chathode Char 4 (most right)
+            break;
+         }
+      }
+   }
+
+   void Seg7::setOE (bool enabled) {
+      switch (hardwareVersion) {
+         case 1: if (enabled) PORTD &= ~(1 << PD5); else PORTD |= (1 << PD5); break;
+         case 2: if (enabled) PORTA &= ~(1 << PA1); else PORTA |= (1 << PA1); break;
+      }
+   }
+
+#endif
+
+#ifdef __AVR_ATmega328P__
+
+   // Arduino-Nano-5V
+   // ---------------------------------------------------------------
+
+   // Nano-X-Base             V1a  V2a
+   // ---------------------------------
+   // Anode Segment A ....... PD4  PD4
+   // Anode Segment B ....... PB0  PB0
+   // Anode Segment C ....... PD7  PD7
+   // Anode Segment D ....... PD6  PD6
+   // Anode Segment E ....... PB2  PB2
+   // Anode Segment F ....... PB3  PB3
+   // Anode Segment G ....... PB4  PB4
+   // Anode DP .............. PB5  PB5
+   // ---------------------------------
+   // Cathode Char 1 ........ PC0  PD5
+   // Cathode Char 2 ........ PC1  PB1
+   // Cathode Char 3 ........ PC2  PD3
+   // Cathode Char 4 ........ PC3  PD2
+   // ---------------------------------
+   // nOE (Output Enable) ... PB1  PC1
+   // Anode L1:2 ............ PD3  PC2
+   // Anode L3 .............. PD2  PC3
+
+
+   void Seg7::init () {
+      enabled = 1;
+      setAnodes(0);
+      setCathodes(0);
+      switch (hardwareVersion) {
+         case 1: {
+            DDRB |= (1 << PB5) | (1 << PB4) | (1 << PB3) | (1 << PB2) | (1 << PB1) | (1 << PB0);
+            DDRC |= (1 << PC3) | (1 << PC2) | (1 << PC1) | (1 << PC0);
+            DDRD |= (1 << PD7) | (1 << PD6) | (1 << PD4) | (1 << PD3) | (1 << PD2);
+            break;
+         }
+         case 2: {
+            DDRB |= (1 << PB5) | (1 << PB4) | (1 << PB3) | (1 << PB2) | (1 << PB1) | (1 << PB0);
+            DDRC |= (1 << PC3) | (1 << PC2) | (1 << PC1);
+            DDRD |= (1 << PD7) | (1 << PD6) | (1 << PD5) | (1 << PD4) | (1 << PD3) | (1 << PD2);
+            break;
+         }
+      }
+
+   }
+
+   void Seg7::cleanup () {
+      enabled = 0;
+      setAnodes(0);
+      setCathodes(0);
+      switch (hardwareVersion) {
+         case 1: {
+            DDRB &= ~((1 << PB5) | (1 << PB4) | (1 << PB3) | (1 << PB2) | (1 << PB1) | (1 << PB0));
+            DDRC &= ~((1 << PC3) | (1 << PC2) | (1 << PC1) | (1 << PC0));
+            DDRD &= ~((1 << PD7) | (1 << PD6) | (1 << PD4) | (1 << PD3) | (1 << PD2));
+            break;
+         }
+         case 2: {
+            DDRB &= ~((1 << PB5) | (1 << PB4) | (1 << PB3) | (1 << PB2) | (1 << PB1) | (1 << PB0) );
+            DDRC &= ~((1 << PC3) | (1 << PC2) | (1 << PC1));
+            DDRD &= ~((1 << PD7) | (1 << PD6) | (1 << PD5) | (1 << PD4) | (1 << PD3) | (1 << PD2));
+            break;
+         }
+      }
+
+   }
+
+   void Seg7::setAnodes (uint16_t a) {
+      if (a & 0x0001) PORTD |= (1 << PD4); else PORTD &= ~(1 << PD4); // Anode Char A
+      if (a & 0x0002) PORTB |= (1 << PB0); else PORTB &= ~(1 << PB0); // Anode Char B
+      if (a & 0x0004) PORTD |= (1 << PD7); else PORTD &= ~(1 << PD7); // Anode Char C
+      if (a & 0x0008) PORTD |= (1 << PD6); else PORTD &= ~(1 << PD6); // Anode Char D
+      if (a & 0x0010) PORTB |= (1 << PB2); else PORTB &= ~(1 << PB2); // Anode Char E
+      if (a & 0x0020) PORTB |= (1 << PB3); else PORTB &= ~(1 << PB3); // Anode Char F
+      if (a & 0x0040) PORTB |= (1 << PB4); else PORTB &= ~(1 << PB4); // Anode Char G
+      if (a & 0x0080) PORTB |= (1 << PB5); else PORTB &= ~(1 << PB5); // Anode Char DP
+      switch (hardwareVersion) {
+         case 1: {
+            if (a & 0x0100) PORTD |= (1 << PD3); else PORTD &= ~(1 << PD3); // Anode L1/L2
+            if (a & 0x0200) PORTD |= (1 << PD2); else PORTD &= ~(1 << PD2); // Anode L3
+            break;
+         }
+         case 2: {
+            if (a & 0x0100) PORTC |= (1 << PC2); else PORTC &= ~(1 << PC2); // Anode L1/L2
+            if (a & 0x0200) PORTC |= (1 << PC3); else PORTC &= ~(1 << PC3); // Anode L3
+            break;
+         }
+      }
+   }
+
+   void Seg7::setCathodes (uint8_t c) {
+      switch (hardwareVersion) {
+         case 1: {
+            if (c & 0x01) PORTC |= (1 << PC0); else PORTC &= ~(1 << PC0); // Chathode Char 1 (most left)
+            if (c & 0x02) PORTC |= (1 << PC1); else PORTC &= ~(1 << PC1); // Chathode Char 2
+            if (c & 0x04) PORTC |= (1 << PC2); else PORTC &= ~(1 << PC2); // Chathode Char 3
+            if (c & 0x08) PORTC |= (1 << PC3); else PORTC &= ~(1 << PC3); // Chathode Char 4 (most right)
+            break;
+         }
+         case 2: {
+            if (c & 0x01) PORTD |= (1 << PD5); else PORTD &= ~(1 << PD5); // Chathode Char 1 (most left)
+            if (c & 0x02) PORTB |= (1 << PB1); else PORTB &= ~(1 << PB1); // Chathode Char 2
+            if (c & 0x04) PORTD |= (1 << PD3); else PORTD &= ~(1 << PD3); // Chathode Char 3
+            if (c & 0x08) PORTD |= (1 << PD2); else PORTD &= ~(1 << PD2); // Chathode Char 4 (most right)
+            break;
+         }
+      }
+   }
+
+   void Seg7::setOE (bool enabled) {
+      switch (hardwareVersion) {
+         case 1: if (enabled)  PORTB &= ~(1 << PB1); else PORTB |= (1 << PB1); break;
+         case 2: if (enabled)  PORTC &= ~(1 << PC1); else PORTC |= (1 << PC1); break;
+      }
+   }
+
+#endif
+
+const char *segName[] = { "A", "B", "C", "D", "E", "F", "G", "DP" };
+
+
+int8_t Seg7::run (uint8_t subtest) {
+   if (subtest == 0) {
+      setCathodes(0x0f); // all segment cathodes conected to GND
+      setAnodes(0x3ff);  // all segments ON
+      setOE(true);
+      printf_P(PSTR("ON"));
+      wait(2000);
+      setAnodes(0);
+      return 0;
+
+   } else if (subtest == 1) {
+      printf_P(PSTR("OFF"));
+      wait(1000);
+      return 0;
+
+   } else if (subtest == 2) {
+      setAnodes(0x100); // L1/L2 ON
+      printf_P(PSTR("L1/L2 ON"));
+      wait(1000);
+      setAnodes(0);
+      return 0;
+
+   } else if (subtest == 3) {
+      setAnodes(0x200); // L3 ON
+      printf_P(PSTR("L1/L2 ON"));
+      wait(1000);
+      setAnodes(0);
+      return 0;
+
+   } else if (subtest < (4 + 4 * 8)) {
+      uint8_t chIndex = (subtest - 4) / 8;
+      uint8_t segIndex = (subtest - 4) % 8;
+      setCathodes(1 << chIndex);
+      setAnodes(1 << segIndex);
+      printf_P(PSTR("Char %d - %s -> %02x"), chIndex, segName[segIndex], (1 << segIndex));
+      wait(400);
+      return 0;
+   }
+
+   return -1;
+}
+
+
diff --git a/software/test-software/src/units/seg7.hpp b/software/test-software/src/units/seg7.hpp
new file mode 100644 (file)
index 0000000..0e71fde
--- /dev/null
@@ -0,0 +1,25 @@
+#ifndef SEG7_HPP
+#define SEG7_HPP
+
+#include <stdint.h>
+#include "../main.hpp"
+#include <avr/pgmspace.h>
+
+class Seg7 : public TestUnit {
+   public:
+      bool enabled;
+   
+   public:
+      Seg7 () { enabled = false; }
+      virtual void init ();
+      virtual void cleanup ();
+      virtual int8_t run (uint8_t subtest);
+      virtual PGM_P getName () { return PSTR("Seg7"); }
+
+   private:
+      void setAnodes (uint16_t);
+      void setCathodes (uint8_t mask);
+      void setOE (bool enabled);
+};
+
+#endif
\ No newline at end of file
diff --git a/software/test-software/src/units/switch.cpp b/software/test-software/src/units/switch.cpp
new file mode 100644 (file)
index 0000000..4ce9456
--- /dev/null
@@ -0,0 +1,100 @@
+#include <stdio.h>
+#include <avr/io.h>
+#include <util/delay.h>
+
+#include "switch.hpp"
+#include "../main.hpp"
+
+#if defined(__AVR_ATmega644P__) || defined(__AVR_ATmega1284P__)
+
+   // Nano-644
+   // ---------------------------------------------------------------
+   // PA0 ..... SW1
+   // PA1 ..... SW2
+   // PA2 ..... SW3
+   // PA3 ..... SW4
+
+   void Switch::init () {
+      DDRA &= ~((1 << PORTA3) | (1 << PORTA2) | (1 << PORTA1) | (1 << PORTA0));
+      PORTA |= (1 << PORTA3) | (1 << PORTA2) | (1 << PORTA1) | (1 << PORTA0);
+   }
+
+   void Switch::cleanup () {
+      PORTA &= ~((1 << PORTA3) | (1 << PORTA2) | (1 << PORTA1) | (1 << PORTA0));
+      DDRA &= ~((1 << PORTA3) | (1 << PORTA2) | (1 << PORTA1) | (1 << PORTA0));
+   }
+
+   bool Switch::isPressed (SWITCH sw) {
+      switch (sw) {
+         case SW1: return (PINA & ( 1 << PA0)) == 0;
+         case SW2: return (PINA & ( 1 << PA1)) == 0;
+         case SW3: return (PINA & ( 1 << PA2)) == 0;
+         case SW4: return (PINA & ( 1 << PA3)) == 0;
+         default: return false;
+      }
+   }
+
+#endif
+
+#ifdef __AVR_ATmega328P__
+
+   // Arduino-Nano-5V
+   // ---------------------------------------------------------------
+   // PC0 ..... SW1
+   // PC1 ..... SW2
+   // PC2 ..... SW3
+   // PC3 ..... SW4
+
+   void Switch::init () {
+      DDRC &= ~((1 << PORTC3) | (1 << PORTC2) | (1 << PORTC1) | (1 << PORTC0));
+      PORTC |= (1 << PORTC3) | (1 << PORTC2) | (1 << PORTC1) | (1 << PORTC0);
+   }
+
+   void Switch::cleanup () {
+      PORTC &= ~((1 << PORTC3) | (1 << PORTC2) | (1 << PORTC1) | (1 << PORTC0));
+      DDRC &= ~((1 << PORTC3) | (1 << PORTC2) | (1 << PORTC1) | (1 << PORTC0));
+   }
+
+   bool Switch::isPressed (SWITCH sw) {
+      switch (sw) {
+         case SW1: return (PINC & ( 1 << PC0)) == 0;
+         case SW2: return (PINC & ( 1 << PC1)) == 0;
+         case SW3: return (PINC & ( 1 << PC2)) == 0;
+         case SW4: return (PINC & ( 1 << PC3)) == 0;
+         default: return false;
+      }
+   }
+
+#endif
+
+int8_t Switch::run (uint8_t subtest) {
+   if (subtest < 16) {
+      SWITCH sw = (SWITCH)(subtest / 4);
+      switch (subtest % 4) {
+         case 1: {
+            if (!isPressed(sw)) {
+               printf_P(PSTR("Press SW%d"), sw + 1);
+               while (!isPressed(sw) && wait(0) == EOF) {}
+               wait(10);
+            }
+            return 0;
+         }
+
+         case 0: case 2: {
+            if (isPressed(sw)) {
+               printf_P(PSTR("Release SW%d "), sw + 1);
+               while (isPressed(sw) && wait(0) == EOF) {}
+               wait(10);
+            }
+            return 0;
+         }
+
+         case 3: {
+            return 0;
+         }
+      }
+
+   }
+   
+   return -1;
+}
diff --git a/software/test-software/src/units/switch.hpp b/software/test-software/src/units/switch.hpp
new file mode 100644 (file)
index 0000000..03bf0b2
--- /dev/null
@@ -0,0 +1,20 @@
+#ifndef SWITCH_HPP
+#define SWITCH_HPP
+
+#include <stdint.h>
+#include "../main.hpp"
+#include <avr/pgmspace.h>
+
+class Switch : public TestUnit {
+   typedef enum { SW1 = 0, SW2 = 1, SW3 = 2, SW4 = 3 } SWITCH;
+
+   public:
+      Switch () {};
+      virtual void init ();
+      virtual void cleanup ();
+      virtual int8_t run (uint8_t subtest);
+      virtual PGM_P getName () { return PSTR("Switch"); }
+      bool isPressed (SWITCH sw);
+};
+
+#endif
\ No newline at end of file
diff --git a/software/test-software/src/units/uart1.cpp b/software/test-software/src/units/uart1.cpp
new file mode 100644 (file)
index 0000000..57e0bfb
--- /dev/null
@@ -0,0 +1,65 @@
+#include <stdio.h>
+#include <avr/io.h>
+#include <util/delay.h>
+
+#include "uart1.hpp"
+#include "../main.hpp"
+
+#ifdef __AVR_ATmega328P__
+   void Uart1::init () {}
+   void Uart1::cleanup () {}
+   int8_t Uart1::run (uint8_t subtest) { return -1; }
+   void Uart1::handleRxByte (uint8_t b) {}
+#endif
+
+#if defined(__AVR_ATmega644P__) || defined(__AVR_ATmega1284P__)
+
+int uart1_putchar(char c, FILE *stream) {
+   if (c == '\n') {
+      uart1_putchar('\r', stream);
+   }
+   loop_until_bit_is_set(UCSR1A, UDRE1);
+   UDR1 = c;
+   return 0;
+}
+
+static FILE mystderr = { 0, 0, _FDEV_SETUP_WRITE , 0, 0, uart1_putchar, NULL, 0 };
+
+void Uart1::init () {
+   PORTD |= (1 << PD2); // enable RxD1 pullup
+   UCSR1A = (1 << U2X1);
+   UCSR1B = (1 << RXCIE1) | (1 << RXEN1) | (1 <<TXEN1);
+   UCSR1C = (1 << UCSZ11) | ( 1<< UCSZ10);
+   UBRR1H = 0;
+   UBRR1L = F_CPU / 8 / 115200 - 1;
+   stderr = &mystderr;
+   enabled = 1;
+}
+
+void Uart1::cleanup () {
+   enabled = 0;
+   UCSR1A = 0;
+   UCSR1B = 0;
+   UCSR1C = 0;
+   UBRR1H = 0;
+   UBRR1L = 0;
+   stderr = NULL;
+}
+
+int8_t Uart1::run (uint8_t subtest) {
+   if (subtest == 0) {
+      do {
+         printf_P(PSTR("\n => send text via UART1 now..."));
+         fprintf_P(stderr, PSTR("Hello UART1, ECHO-Modus active\n"));
+      } while (wait(5000) == EOF);
+      return 0;
+   }
+
+   return -1;
+}
+
+void Uart1::handleRxByte (uint8_t b) {
+   uart1_putchar(b, stderr);
+}
+
+#endif
\ No newline at end of file
diff --git a/software/test-software/src/units/uart1.hpp b/software/test-software/src/units/uart1.hpp
new file mode 100644 (file)
index 0000000..40437e1
--- /dev/null
@@ -0,0 +1,21 @@
+#ifndef UART1_HPP
+#define UART1_HPP
+
+#include <stdint.h>
+#include "../main.hpp"
+#include <avr/pgmspace.h>
+
+class Uart1 : public TestUnit {
+   public:
+      uint8_t  enabled;
+
+   public:
+      Uart1 () { enabled = 0; }
+      virtual void init ();
+      virtual void cleanup ();
+      virtual int8_t run (uint8_t subtest);
+      virtual PGM_P getName () { return PSTR("Uart1"); }
+      void handleRxByte (uint8_t);
+};
+
+#endif
\ No newline at end of file