diff --git a/.github/workflows/core_build.yml b/.github/workflows/core_build.yml
deleted file mode 100644
index 2b1404b02b..0000000000
--- a/.github/workflows/core_build.yml
+++ /dev/null
@@ -1,121 +0,0 @@
-name: Core Build
-
-on:
- push:
- branches: [ "master" ]
- pull_request:
- branches: [ "master" ]
- workflow_dispatch: # manual trigger
-
-jobs:
- setup:
- runs-on: ubuntu-latest
- outputs:
- vortex_version_major: ${{ steps.set_version.outputs.vortex_version_major }}
- vortex_version_minor: ${{ steps.set_version.outputs.vortex_version_minor }}
- vortex_build_number: ${{ steps.set_version.outputs.vortex_build_number }}
- vortex_version_number: ${{ steps.set_version.outputs.vortex_version_number }}
- steps:
- - uses: actions/checkout@v4
- with:
- fetch-depth: 0 # Fetches all history for all branches and tags
- - name: Determine Version and Build Number
- id: set_version
- run: |
- # Fetch all tags
- git fetch --depth=1 origin +refs/tags/*:refs/tags/*
- # Get the latest tag that matches the branch suffix
- LATEST_TAG=$(git tag --list | grep -E "^[[:digit:]]+\.[[:digit:]]+\$" | sort -V | tail -n1)
- if [ -z "$LATEST_TAG" ]; then
- echo "No matching tags found. Setting default version."
- VERSION_MAJOR="0"
- VERSION_MINOR="1"
- BUILD_NUMBER="0"
- else
- echo "Found latest tag: $LATEST_TAG"
- VERSION_MAJOR=$(echo $LATEST_TAG | cut -d. -f1)
- VERSION_MINOR=$(echo $LATEST_TAG | cut -d. -f2)
- BUILD_NUMBER=$(git rev-list --count $LATEST_TAG..HEAD)
- fi
- FULL_VERSION="$VERSION_MAJOR.$VERSION_MINOR.$BUILD_NUMBER"
- echo "vortex_version_major=$VERSION_MAJOR" >> $GITHUB_OUTPUT
- echo "vortex_version_minor=$VERSION_MINOR" >> $GITHUB_OUTPUT
- echo "vortex_build_number=$BUILD_NUMBER" >> $GITHUB_OUTPUT
- echo "vortex_version_number=$FULL_VERSION" >> $GITHUB_OUTPUT
- echo "Version Number: $FULL_VERSION"
-
- test:
- needs: setup
- runs-on: ubuntu-latest
- steps:
- - name: Checkout current repository
- uses: actions/checkout@v3
- - name: Update Package Lists
- run: sudo apt-get update
- - name: Install Dependencies
- run: sudo apt-get install valgrind g++ make --fix-missing
- - name: Build
- run: |
- export VORTEX_VERSION_MAJOR=${{ needs.setup.outputs.vortex_version_major }}
- export VORTEX_VERSION_MINOR=${{ needs.setup.outputs.vortex_version_minor }}
- export VORTEX_BUILD_NUMBER=${{ needs.setup.outputs.vortex_build_number }}
- export VORTEX_VERSION_NUMBER=${{ needs.setup.outputs.vortex_version_number }}
- make -j
- working-directory: VortexEngine
- - name: Set execute permissions for test script
- run: chmod +x ./runtests.sh
- working-directory: VortexEngine/tests
- - name: Run general tests
- run: ./runtests.sh --general
- working-directory: VortexEngine/tests
-
- wasm:
- needs: [setup, test]
- runs-on: ubuntu-latest
- steps:
- - name: Checkout current repository
- uses: actions/checkout@v3
- - name: Update Package Lists
- run: sudo apt-get update
- - name: Install Emscripten
- run: |
- sudo apt install -y cmake python3
- git clone https://github.com/emscripten-core/emsdk.git
- cd emsdk
- ./emsdk install latest
- ./emsdk activate latest
- working-directory: VortexEngine/VortexLib
- - name: Build Webassembly
- run: |
- source ./emsdk/emsdk_env.sh
- export VORTEX_VERSION_MAJOR=${{ needs.setup.outputs.vortex_version_major }}
- export VORTEX_VERSION_MINOR=${{ needs.setup.outputs.vortex_version_minor }}
- export VORTEX_BUILD_NUMBER=${{ needs.setup.outputs.vortex_build_number }}
- export VORTEX_VERSION_NUMBER=${{ needs.setup.outputs.vortex_version_number }}
- make -j wasm
- working-directory: VortexEngine/VortexLib
-
- docs:
- needs: [setup, test, wasm]
- runs-on: ubuntu-latest
- if: github.ref == 'refs/heads/master'
- steps:
- - name: Checkout current repository
- uses: actions/checkout@v3
- - name: Update Package Lists
- run: sudo apt-get update
- - name: Install Dependencies
- run: sudo apt-get install doxygen graphviz texlive --fix-missing
- - name: Checkout doxygen-awesome
- run: git clone https://github.com/jothepro/doxygen-awesome-css.git doxygen-awesome-css
- - name: Generate Documentation
- run: |
- mkdir -p docs/core
- doxygen Doxyfile
- echo "Listing contents of docs/core:"
- ls -R docs/core || echo "No files found in docs/core"
- - name: Upload Doxygen Documentation as Artifact
- uses: actions/upload-artifact@v3
- with:
- name: doxygen-docs-core
- path: docs/core
diff --git a/.github/workflows/duo_build.yml b/.github/workflows/duo_build.yml
new file mode 100644
index 0000000000..4cf28a6303
--- /dev/null
+++ b/.github/workflows/duo_build.yml
@@ -0,0 +1,184 @@
+name: Duo Build
+
+on:
+ push:
+ branches: [ "duo" ]
+ pull_request:
+ branches: [ "duo" ]
+ workflow_dispatch: # manual trigger
+
+jobs:
+ setup:
+ runs-on: ubuntu-latest
+ outputs:
+ vortex_version_major: ${{ steps.set_version.outputs.vortex_version_major }}
+ vortex_version_minor: ${{ steps.set_version.outputs.vortex_version_minor }}
+ vortex_build_number: ${{ steps.set_version.outputs.vortex_build_number }}
+ vortex_version_number: ${{ steps.set_version.outputs.vortex_version_number }}
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0 # Fetches all history for all branches and tags
+ - name: Determine Version and Build Number
+ id: set_version
+ run: |
+ BRANCH_SUFFIX="d"
+ # Fetch all tags
+ git fetch --depth=1 origin +refs/tags/*:refs/tags/*
+ # Get the latest tag that matches the branch suffix
+ LATEST_TAG=$(git tag --list "*${BRANCH_SUFFIX}" | sort -V | tail -n1)
+ if [ -z "$LATEST_TAG" ]; then
+ echo "No matching tags found. Setting default version."
+ VERSION_MAJOR="0"
+ VERSION_MINOR="1"
+ BUILD_NUMBER="0"
+ else
+ echo "Found latest tag: $LATEST_TAG"
+ VERSION_NUMBER=$(echo $LATEST_TAG | sed "s/${BRANCH_SUFFIX}//g")
+ VERSION_MAJOR=$(echo $VERSION_NUMBER | cut -d. -f1)
+ VERSION_MINOR=$(echo $VERSION_NUMBER | cut -d. -f2)
+ echo "Revisions:"
+ git rev-list $LATEST_TAG..HEAD
+ BUILD_NUMBER=$(git rev-list --count $LATEST_TAG..HEAD)
+ fi
+ FULL_VERSION="$VERSION_MAJOR.$VERSION_MINOR.$BUILD_NUMBER"
+ echo "vortex_version_major=$VERSION_MAJOR" >> $GITHUB_OUTPUT
+ echo "vortex_version_minor=$VERSION_MINOR" >> $GITHUB_OUTPUT
+ echo "vortex_build_number=$BUILD_NUMBER" >> $GITHUB_OUTPUT
+ echo "vortex_version_number=$FULL_VERSION" >> $GITHUB_OUTPUT
+ echo "Version Number: $FULL_VERSION"
+
+ test:
+ needs: setup
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout current repository
+ uses: actions/checkout@v4
+ - name: Update Package Lists
+ run: sudo apt-get update
+ - name: Install Dependencies
+ run: sudo apt-get install valgrind g++ make --fix-missing
+ - name: Build
+ run: |
+ export VORTEX_VERSION_MAJOR=${{ needs.setup.outputs.vortex_version_major }}
+ export VORTEX_VERSION_MINOR=${{ needs.setup.outputs.vortex_version_minor }}
+ export VORTEX_BUILD_NUMBER=${{ needs.setup.outputs.vortex_build_number }}
+ export VORTEX_VERSION_NUMBER=${{ needs.setup.outputs.vortex_version_number }}
+ make -j
+ working-directory: VortexEngine
+ - name: Set execute permissions for test script
+ run: chmod +x ./runtests.sh
+ working-directory: VortexEngine/tests
+ - name: Run general tests
+ run: ./runtests.sh --general
+ working-directory: VortexEngine/tests
+
+ embedded:
+ needs: [setup, test]
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - name: Set up Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: '3.x'
+ - name: Install Dependencies
+ run: make install
+ - name: Build Binary
+ run: |
+ export VORTEX_VERSION_MAJOR=${{ needs.setup.outputs.vortex_version_major }}
+ export VORTEX_VERSION_MINOR=${{ needs.setup.outputs.vortex_version_minor }}
+ export VORTEX_BUILD_NUMBER=${{ needs.setup.outputs.vortex_build_number }}
+ export VORTEX_VERSION_NUMBER=${{ needs.setup.outputs.vortex_version_number }}
+ make build
+ - name: Archive production artifacts
+ uses: actions/upload-artifact@v4
+ with:
+ name: embedded firmware
+ path: |
+ vortex.bin
+ vortex.elf
+ vortex.map
+ vortex.hex
+ - name: Archive production artifacts for deployment
+ uses: actions/upload-artifact@v4
+ with:
+ name: firmware-artifact
+ path: vortex.bin
+
+ wasm:
+ needs: [setup, test, embedded]
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout current repository
+ uses: actions/checkout@v4
+ - name: Update Package Lists
+ run: sudo apt-get update
+ - name: Install Emscripten
+ run: |
+ sudo apt install -y cmake python3
+ git clone https://github.com/emscripten-core/emsdk.git
+ cd emsdk
+ ./emsdk install latest
+ ./emsdk activate latest
+ working-directory: VortexEngine/VortexLib
+ - name: Build Webassembly
+ run: |
+ source ./emsdk/emsdk_env.sh
+ export VORTEX_VERSION_MAJOR=${{ needs.setup.outputs.vortex_version_major }}
+ export VORTEX_VERSION_MINOR=${{ needs.setup.outputs.vortex_version_minor }}
+ export VORTEX_BUILD_NUMBER=${{ needs.setup.outputs.vortex_build_number }}
+ export VORTEX_VERSION_NUMBER=${{ needs.setup.outputs.vortex_version_number }}
+ make -j wasm
+ working-directory: VortexEngine/VortexLib
+
+ docs:
+ needs: [setup, test, embedded, wasm]
+ runs-on: ubuntu-latest
+ if: github.ref == 'refs/heads/duo'
+ steps:
+ - name: Checkout current repository
+ uses: actions/checkout@v4
+ - name: Update Package Lists
+ run: sudo apt-get update
+ - name: Install Dependencies
+ run: sudo apt-get install doxygen graphviz texlive --fix-missing
+ - name: Checkout doxygen-awesome
+ run: git clone https://github.com/jothepro/doxygen-awesome-css.git doxygen-awesome-css
+ - name: Generate Documentation
+ run: |
+ mkdir -p docs/duo
+ doxygen Doxyfile
+ echo "Listing contents of docs/duo:"
+ ls -R docs/duo || echo "No files found in docs/duo"
+ - name: Upload Doxygen Documentation as Artifact
+ uses: actions/upload-artifact@v3
+ with:
+ name: doxygen-docs-duo
+ path: docs/duo
+
+ deploy:
+ needs: [setup, test, embedded, wasm, docs]
+ runs-on: ubuntu-latest
+ if: github.ref == 'refs/heads/duo'
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/download-artifact@v4
+ with:
+ name: firmware-artifact
+ path: build
+ - name: Rename and Deploy Firmware
+ run: |
+ DEVICE_TYPE="duo"
+ VERSIONED_FILENAME="VortexEngine-${DEVICE_TYPE}-${{ needs.setup.outputs.vortex_version_number }}.bin"
+ mv build/vortex.bin build/$VERSIONED_FILENAME
+ echo "Version is ${{ needs.setup.outputs.vortex_version_number }}"
+ echo "Filename is is $VERSIONED_FILENAME"
+ curl -X POST \
+ -F "file=@build/$VERSIONED_FILENAME" \
+ -F "device=$DEVICE_TYPE" \
+ -F "version=${{ needs.setup.outputs.vortex_version_number }}" \
+ -F "category=firmware" \
+ -F "clientApiKey=${{ secrets.VORTEX_COMMUNITY_API_KEY }}" \
+ https://vortex.community/firmware/upload
+
diff --git a/Doxyfile b/Doxyfile
index 9db6ecc800..322132c462 100644
--- a/Doxyfile
+++ b/Doxyfile
@@ -32,7 +32,7 @@ DOXYFILE_ENCODING = UTF-8
# title of most generated pages and in a few other places.
# The default value is: My Project.
-PROJECT_NAME = "Vortex Engine"
+PROJECT_NAME = "Vortex Duo"
# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
# could be handy for archiving the generated documentation or if some version
@@ -58,7 +58,7 @@ PROJECT_LOGO =
# entered, it will be relative to the location where doxygen was started. If
# left blank the current directory will be used.
-OUTPUT_DIRECTORY = docs/core
+OUTPUT_DIRECTORY = docs/duo
# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub-
# directories (in 2 levels) under the output directory of each output format and
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000000..1c279a53ef
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,224 @@
+.PHONY: all clean install serial upload compute_version
+
+ifeq ($(OS),Windows_NT) # Windows
+ BINDIR="C:/Program Files (x86)/Atmel/Studio/7.0/toolchain/avr8/avr8-gnu-toolchain/bin/"
+ AVRDUDEDIR="$(shell echo "$$LOCALAPPDATA")/Arduino15/packages/DxCore/tools/avrdude/6.3.0-arduino17or18/bin"
+ PYTHON="$(shell echo "$$LOCALAPPDATA")/Arduino15/packages/megaTinyCore/tools/python3/3.7.2-post1/python3"
+ PYPROG="$(shell echo "$$LOCALAPPDATA")/Arduino15/packages/megaTinyCore/hardware/megaavr/2.6.5/tools/prog.py"
+ DEVICE_DIR="C:/Program Files (x86)/Atmel/Studio/7.0/Packs/atmel/ATtiny_DFP/1.10.348/gcc/dev/attiny3217"
+ INCLUDE_DIR="C:/Program Files (x86)/Atmel/Studio/7.0/Packs/atmel/ATtiny_DFP/1.10.348/include/"
+else # linux
+ BINDIR=~/atmel_setup/avr8-gnu-toolchain-linux_x86_64/bin/
+ DEVICE_DIR=~/atmel_setup/gcc/dev/attiny3217
+ INCLUDE_DIR=~/atmel_setup/include/
+endif
+
+CC = ${BINDIR}/avr-g++
+LD = ${BINDIR}/avr-g++
+OBJCOPY = ${BINDIR}/avr-objcopy -v
+AR = ${BINDIR}/avr-gcc-ar
+SIZE = ${BINDIR}/avr-size
+OBJDUMP = ${BINDIR}/avr-objdump
+NM = ${BINDIR}/avr-nm
+AVRDUDE = ${AVRDUDEDIR}/avrdude
+
+AVRDUDE_CONF = avrdude.conf
+AVRDUDE_PORT = usb
+AVRDUDE_PROGRAMMER = atmelice_updi
+AVRDUDE_BAUDRATE = 115200
+AVRDUDE_CHIP = attiny3217
+
+AVRDUDE_FLAGS = -C$(AVRDUDE_CONF) \
+ -p$(AVRDUDE_CHIP) \
+ -c$(AVRDUDE_PROGRAMMER) \
+ -P$(AVRDUDE_PORT) \
+ -b$(AVRDUDE_BAUDRATE) \
+ -v
+
+CPU_SPEED = 10000000L
+
+# the port for serial upload
+SERIAL_PORT = COM11
+
+# whether eeprom is erased on flash (must write this fuse first to take effect)
+SAVE_EEPROM = 1
+
+# WDTCFG { PERIOD=OFF, WINDOW=OFF }
+WDTCFG = 0b00000000
+# BODCFG { SLEEP=DIS, ACTIVE=DIS, SAMPFREQ=1KHZ, LVL=BODLEVEL0 }
+BODCFG = 0x00
+# OSCCFG { FREQSEL=20mhz, OSCLOCK=CLEAR }
+OSCCFG = 0x02
+# RESERVED
+#FUSE3 = 0x00
+# TCD0CFG { CMPA=CLEAR, CMPB=CLEAR, CMPC=CLEAR, CMPD=CLEAR, CMPAEN=CLEAR, CPMCEN=CLEAR, CMPDEN=CLEAR }
+TCD0CFG = 0x00
+# SYSCFG0
+SYSCFG0 = 0b1100010$(SAVE_EEPROM)
+# SYSCFG1 { SUT=64ms }
+SYSCFG1 = 0x07
+# fuse7 = APPEND
+APPEND = 0x00
+# fuse8 = BOOTEND
+# This controls the amount of storage for modes at the end of the flash memory,
+# it is the boundary for the segment that can be rewritten by the program, 0x7e
+# means 0x7e00/0x8000 bytes are program and 0x200 bytes are reserved for flash
+# storage of modes, this does not include the eeprom.
+BOOTEND = 0x7e
+
+# The branch/tag suffix for this device
+BRANCH_SUFFIX=d
+
+# compiler defines
+DEFINES=\
+ -DVORTEX_VERSION_MAJOR=$(VORTEX_VERSION_MAJOR) \
+ -DVORTEX_VERSION_MINOR=$(VORTEX_VERSION_MINOR) \
+ -DVORTEX_BUILD_NUMBER=$(VORTEX_BUILD_NUMBER) \
+ -DVORTEX_VERSION_NUMBER=$(VORTEX_VERSION_NUMBER) \
+ -D__AVR_ATtiny3217__ \
+ -DF_CPU=$(CPU_SPEED) \
+
+CFLAGS = -g \
+ -Os \
+ -MMD \
+ -Wall \
+ -flto \
+ -mrelax \
+ -std=gnu++17 \
+ -fshort-enums \
+ -fpack-struct \
+ -fno-exceptions \
+ -fdata-sections \
+ -funsigned-char \
+ -ffunction-sections\
+ -funsigned-bitfields \
+ -fno-threadsafe-statics \
+ -mmcu=$(AVRDUDE_CHIP) \
+ -B $(DEVICE_DIR)
+
+LDFLAGS = -g \
+ -Wall \
+ -Os \
+ -flto \
+ -fuse-linker-plugin \
+ -Wl,--gc-sections \
+ -mrelax \
+ -lm \
+ -mmcu=$(AVRDUDE_CHIP) \
+ -B $(DEVICE_DIR)
+
+INCLUDES=\
+ -I $(INCLUDE_DIR) \
+ -I ./VortexEngine/src/
+
+ifneq ($(DEFINES),)
+ CFLAGS+=$(DEFINES)
+endif
+ifneq ($(INCLUDES),)
+ CFLAGS+=$(INCLUDES)
+endif
+
+# Source files
+ifeq ($(OS),Windows_NT) # Windows
+SRCS = \
+ $(shell find ./VortexEngine/src/ -type f -name '\*.cpp') \
+ ./VortexEngine/appmain.cpp
+else # linux
+SRCS = \
+ $(shell find ./VortexEngine/src/ -type f -name \*.cpp) \
+ ./VortexEngine/appmain.cpp
+endif
+
+OBJS = $(SRCS:.cpp=.o)
+
+DFILES = $(SRCS:.cpp=.d)
+
+# Target name
+TARGET = vortex
+
+all: compute_version $(TARGET).hex
+ $(OBJDUMP) --disassemble --source --line-numbers --demangle --section=.text $(TARGET).elf > $(TARGET).lst
+ $(NM) --numeric-sort --line-numbers --demangle --print-size --format=s $(TARGET).elf > $(TARGET).map
+ chmod +x avrsize.sh
+ ./avrsize.sh $(TARGET).elf $(BOOTEND)00
+ @echo "== Success building Duo v$(VORTEX_VERSION_NUMBER) =="
+
+$(TARGET).hex: $(TARGET).elf
+ $(OBJCOPY) -O binary -R .eeprom $(TARGET).elf $(TARGET).bin
+ $(OBJCOPY) -O ihex -j .eeprom --set-section-flags=.eeprom=alloc,load --no-change-warnings --change-section-lma .eeprom=0 $(TARGET).elf $(TARGET).eep
+ $(OBJCOPY) -O ihex -R .eeprom $< $@
+
+$(TARGET).elf: $(OBJS)
+ $(LD) $(LDFLAGS) $^ -o $@
+
+%.o: %.S
+ $(CC) $(ASMFLAGS) -c $< -o $@
+
+%.o: %.cpp
+ $(CC) $(CFLAGS) -c $< -o $@
+
+upload: all
+ $(AVRDUDE) $(AVRDUDE_FLAGS) \
+ -Ufuse0:w:$(WDTCFG):m \
+ -Ufuse1:w:$(BODCFG):m \
+ -Ufuse2:w:$(OSCCFG):m \
+ -Ufuse4:w:$(TCD0CFG):m \
+ -Ufuse5:w:$(SYSCFG0):m \
+ -Ufuse6:w:$(SYSCFG1):m \
+ -Ufuse7:w:$(APPEND):m \
+ -Ufuse8:w:$(BOOTEND):m \
+ -Uflash:w:$(TARGET).hex:i
+
+# upload via SerialUPDI
+serial: all
+ $(PYTHON) -u $(PYPROG) -t uart -u $(SERIAL_PORT) -b 921600 -d $(AVRDUDE_CHIP) \
+ --fuses 0:$(WDTCFG) 1:$(BODCFG) 2:$(OSCCFG) 4:$(TCD0CFG) 5:$(SYSCFG0) 6:$(SYSCFG1) 7:$(APPEND) 8:$(BOOTEND) -f $(TARGET).hex -a write -v
+
+production:
+ @FILE_URL=$$(curl -s https://vortex.community/downloads/json/duo | sed -n 's/.*"fileUrl":"\([^"]*\)".*/\1/p'); \
+ FILENAME=$$(basename $$FILE_URL); \
+ if [ ! -f "$$FILENAME" ]; then \
+ echo "Downloading new firmware: $$FILENAME"; \
+ curl -L -O "$$FILE_URL"; \
+ fi; \
+ $(OBJCOPY) -I binary -O ihex $$FILENAME firmware.hex > /dev/null; \
+ echo "Uploading Duo Firmware: $$FILENAME"; \
+ $(PYTHON) -u $(PYPROG) -t uart -u $(SERIAL_PORT) -b 921600 -d $(AVRDUDE_CHIP) \
+ --fuses 0:$(WDTCFG) 1:$(BODCFG) 2:$(OSCCFG) 4:$(TCD0CFG) 5:$(SYSCFG0) 6:$(SYSCFG1) 7:$(APPEND) 8:$(BOOTEND) -f firmware.hex -a write -v
+ rm -f firmware.hex > /dev/null
+
+ifneq ($(OS),Windows_NT) # Linux
+build: all
+INSTALL_DIR=~/atmel_setup
+# Name of the toolchain tarball
+TOOLCHAIN_TAR=avr8-gnu-toolchain-3.7.0.1796-linux.any.x86_64.tar.gz
+# Name of the ATtiny DFP zip
+ATTINY_ZIP=Atmel.ATtiny_DFP.2.0.368.atpack
+install:
+ @echo "Setting up in directory $(INSTALL_DIR)"
+ @mkdir -p $(INSTALL_DIR)
+ @cd $(INSTALL_DIR) && \
+ echo "Downloading and installing AVR 8-bit Toolchain..." && \
+ wget -q https://ww1.microchip.com/downloads/aemDocuments/documents/DEV/ProductDocuments/SoftwareTools/$(TOOLCHAIN_TAR) && \
+ tar -xf $(TOOLCHAIN_TAR) && \
+ echo "Downloading and installing ATtiny DFP..." && \
+ wget -q http://packs.download.atmel.com/$(ATTINY_ZIP) && \
+ unzip $(ATTINY_ZIP)
+ @echo "Download and extraction complete. You'll find the toolchain and pack files in $(INSTALL_DIR)"
+endif
+
+clean:
+ rm -f $(OBJS) $(TARGET).elf $(TARGET).hex $(TARGET).bin $(DFILES)
+
+compute_version:
+ $(eval LATEST_TAG ?= $(shell git fetch --depth=1 origin +refs/tags/*:refs/tags/* &> /dev/null && git tag --list "*$(BRANCH_SUFFIX)" | sort -V | tail -n1))
+ $(eval VORTEX_VERSION_MAJOR ?= $(shell echo $(LATEST_TAG) | cut -d. -f1))
+ $(eval VORTEX_VERSION_MINOR ?= $(shell echo $(LATEST_TAG) | sed 's/$(BRANCH_SUFFIX)$$//' | cut -d. -f2))
+ $(eval VORTEX_BUILD_NUMBER ?= $(shell git rev-list --count $(LATEST_TAG)..HEAD))
+ $(eval VORTEX_VERSION_MAJOR := $(if $(VORTEX_VERSION_MAJOR),$(VORTEX_VERSION_MAJOR),0))
+ $(eval VORTEX_VERSION_MINOR := $(if $(VORTEX_VERSION_MINOR),$(VORTEX_VERSION_MINOR),1))
+ $(eval VORTEX_BUILD_NUMBER := $(if $(VORTEX_BUILD_NUMBER),$(VORTEX_BUILD_NUMBER),0))
+ $(eval VORTEX_VERSION_NUMBER := $(VORTEX_VERSION_MAJOR).$(VORTEX_VERSION_MINOR).$(VORTEX_BUILD_NUMBER))
+
+# include dependency files to ensure partial rebuilds work correctly
+-include $(DFILES)
diff --git a/VortexEngine.atsln b/VortexEngine.atsln
new file mode 100644
index 0000000000..f488b4f490
--- /dev/null
+++ b/VortexEngine.atsln
@@ -0,0 +1,22 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Atmel Studio Solution File, Format Version 11.00
+VisualStudioVersion = 14.0.23107.0
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{E66E83B9-2572-4076-B26E-6BE79FF3018A}") = "VortexEngine", "VortexEngine\VortexEngine.cppproj", "{DCE6C7E3-EE26-4D79-826B-08594B9AD897}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|AVR = Debug|AVR
+ Release|AVR = Release|AVR
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {DCE6C7E3-EE26-4D79-826B-08594B9AD897}.Debug|AVR.ActiveCfg = Debug|AVR
+ {DCE6C7E3-EE26-4D79-826B-08594B9AD897}.Debug|AVR.Build.0 = Debug|AVR
+ {DCE6C7E3-EE26-4D79-826B-08594B9AD897}.Release|AVR.ActiveCfg = Release|AVR
+ {DCE6C7E3-EE26-4D79-826B-08594B9AD897}.Release|AVR.Build.0 = Release|AVR
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/VortexEngine/VortexCLI/Makefile b/VortexEngine/VortexCLI/Makefile
index b4b2f2b541..fe45f6a0bd 100644
--- a/VortexEngine/VortexCLI/Makefile
+++ b/VortexEngine/VortexCLI/Makefile
@@ -19,6 +19,9 @@ RANLIB=ranlib
CFLAGS=-O2 -g -Wall
+# The branch/tag suffix for this device
+BRANCH_SUFFIX=d
+
# compiler defines
DEFINES=\
-D VORTEX_LIB \
@@ -135,9 +138,9 @@ clean:
# calculate the version number of the build
compute_version:
- $(eval LATEST_TAG ?= $(shell git fetch --depth=1 origin +refs/tags/*:refs/tags/* &> /dev/null && git tag --list | grep --invert-match '[a-zA-Z]' | sort -V | tail -n1))
+ $(eval LATEST_TAG ?= $(shell git fetch --depth=1 origin +refs/tags/*:refs/tags/* &> /dev/null && git tag --list "*$(BRANCH_SUFFIX)" | sort -V | tail -n1))
$(eval VORTEX_VERSION_MAJOR ?= $(shell echo $(LATEST_TAG) | cut -d. -f1))
- $(eval VORTEX_VERSION_MINOR ?= $(shell echo $(LATEST_TAG) | cut -d. -f2))
+ $(eval VORTEX_VERSION_MINOR ?= $(shell echo $(LATEST_TAG) | sed 's/$(BRANCH_SUFFIX)$$//' | cut -d. -f2))
$(eval VORTEX_BUILD_NUMBER ?= $(shell git rev-list --count $(LATEST_TAG)..HEAD))
$(eval VORTEX_VERSION_MAJOR := $(if $(VORTEX_VERSION_MAJOR),$(VORTEX_VERSION_MAJOR),0))
$(eval VORTEX_VERSION_MINOR := $(if $(VORTEX_VERSION_MINOR),$(VORTEX_VERSION_MINOR),1))
diff --git a/VortexEngine/VortexEngine.componentinfo.xml b/VortexEngine/VortexEngine.componentinfo.xml
new file mode 100644
index 0000000000..376bf8af26
--- /dev/null
+++ b/VortexEngine/VortexEngine.componentinfo.xml
@@ -0,0 +1,86 @@
+
+
+
+
+
+
+ Device
+ Startup
+
+
+ Atmel
+ 1.10.0
+ C:/Program Files (x86)\Atmel\Studio\7.0\Packs
+
+
+
+
+ C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATtiny_DFP\1.10.348\include\
+
+ include
+ C
+
+
+ include/
+
+
+
+
+ C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATtiny_DFP\1.10.348\include\avr\iotn3217.h
+
+ header
+ C
+ vKmqiC5Iu75NT1p4uv8rXg==
+
+ include/avr/iotn3217.h
+
+
+
+
+ C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATtiny_DFP\1.10.348\templates\main.c
+ template
+ source
+ C Exe
+ KjvOcFWd++tbnsEMfVPd/w==
+
+ templates/main.c
+ Main file (.c)
+
+
+
+ C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATtiny_DFP\1.10.348\templates\main.cpp
+ template
+ source
+ C Exe
+ b1blCCNoZpiRE6nioQfFjA==
+
+ templates/main.cpp
+ Main file (.cpp)
+
+
+
+ C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATtiny_DFP\1.10.348\gcc\dev\attiny3217
+
+ libraryPrefix
+ GCC
+
+
+ gcc/dev/attiny3217
+
+
+
+
+ ATtiny_DFP
+ C:/Program Files (x86)/Atmel/Studio/7.0/Packs/atmel/ATtiny_DFP/1.10.348/Atmel.ATtiny_DFP.pdsc
+ 1.10.348
+ true
+ ATtiny3217
+
+
+
+ Resolved
+ Fixed
+ true
+
+
+
\ No newline at end of file
diff --git a/VortexEngine/VortexEngine.cppproj b/VortexEngine/VortexEngine.cppproj
new file mode 100644
index 0000000000..2c7e5d6e4f
--- /dev/null
+++ b/VortexEngine/VortexEngine.cppproj
@@ -0,0 +1,603 @@
+
+
+
+ 2.0
+ 7.0
+ com.Atmel.AVRGCC8.CPP
+ dce6c7e3-ee26-4d79-826b-08594b9ad897
+ ATtiny3217
+ none
+ Executable
+ CPP
+ $(MSBuildProjectName)
+ .elf
+ $(MSBuildProjectDirectory)\$(Configuration)
+ VortexEngine
+ VortexEngine
+ VortexEngine
+ Native
+ true
+ false
+ true
+ true
+ 0x20000000
+
+ false
+ exception_table
+ 2
+ 0
+ 0
+
+ com.atmel.avrdbg.tool.atmelice
+ J42700066736
+ 0x1E9522
+ UPDI
+
+
+
+ 1500000
+
+ UPDI
+
+ com.atmel.avrdbg.tool.atmelice
+ J42700066736
+ Atmel-ICE
+
+ 1500000
+
+
+
+
+ -mmcu=attiny3217 -B "%24(PackRepoDir)\atmel\ATtiny_DFP\1.10.348\gcc\dev\attiny3217"
+ True
+ True
+ True
+ True
+ False
+ True
+ True
+
+
+ NDEBUG
+
+
+
+
+ %24(PackRepoDir)\atmel\ATtiny_DFP\1.10.348\include\
+
+
+ Optimize for size (-Os)
+ True
+ True
+ True
+ True
+ True
+
+
+ NDEBUG
+
+
+
+
+ %24(PackRepoDir)\atmel\ATtiny_DFP\1.10.348\include\
+
+
+ Optimize for size (-Os)
+ True
+ True
+ True
+
+
+ libm
+
+
+
+
+ %24(PackRepoDir)\atmel\ATtiny_DFP\1.10.348\include\
+
+
+
+
+
+
+
+
+ -mmcu=attiny3217 -B "%24(PackRepoDir)\atmel\ATtiny_DFP\1.10.348\gcc\dev\attiny3217"
+ True
+ True
+ True
+ True
+ False
+ True
+ True
+
+
+ DEBUG
+ F_CPU=10000000L
+
+
+
+
+ %24(PackRepoDir)\atmel\ATtiny_DFP\1.10.348\include\
+
+
+ Optimize for size (-Os)
+ True
+ True
+ Maximum (-g3)
+ True
+ -flto -mrelax -std=gnu++17 -fno-threadsafe-statics -fno-exceptions
+ True
+ True
+
+
+ DEBUG
+ F_CPU=10000000L
+
+
+
+
+ %24(PackRepoDir)\atmel\ATtiny_DFP\1.10.348\include\
+
+
+ Optimize for size (-Os)
+ True
+ True
+ Maximum (-g3)
+ True
+ -flto -mrelax -std=gnu++17 -fno-threadsafe-statics -fno-exceptions
+
+
+ libm
+
+
+
+
+ %24(PackRepoDir)\atmel\ATtiny_DFP\1.10.348\include\
+
+
+
+
+ False
+ $(MSBuildProjectDirectory)\$(Configuration)
+ all
+ clean
+ C:\Users\danie\source\repos\VortexTestingFramework\VortexTestingFramework\VortexEngine\Makefile
+
+
+
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+ compile
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/VortexEngine/VortexLib/Makefile b/VortexEngine/VortexLib/Makefile
index 98900f08b8..5d02c7f95e 100644
--- a/VortexEngine/VortexLib/Makefile
+++ b/VortexEngine/VortexLib/Makefile
@@ -24,6 +24,9 @@ ifndef WASM
CFLAGS += -g
endif
+# The branch/tag suffix for this device
+BRANCH_SUFFIX=d
+
# compiler defines
DEFINES=\
-D VORTEX_LIB \
@@ -146,9 +149,9 @@ clean:
# calculate the version number of the build
compute_version:
- $(eval LATEST_TAG ?= $(shell git fetch --depth=1 origin +refs/tags/*:refs/tags/* &> /dev/null && git tag --list | grep --invert-match '[a-zA-Z]' | sort -V | tail -n1))
+ $(eval LATEST_TAG ?= $(shell git fetch --depth=1 origin +refs/tags/*:refs/tags/* &> /dev/null && git tag --list "*$(BRANCH_SUFFIX)" | sort -V | tail -n1))
$(eval VORTEX_VERSION_MAJOR ?= $(shell echo $(LATEST_TAG) | cut -d. -f1))
- $(eval VORTEX_VERSION_MINOR ?= $(shell echo $(LATEST_TAG) | cut -d. -f2))
+ $(eval VORTEX_VERSION_MINOR ?= $(shell echo $(LATEST_TAG) | sed 's/$(BRANCH_SUFFIX)$$//' | cut -d. -f2))
$(eval VORTEX_BUILD_NUMBER ?= $(shell git rev-list --count $(LATEST_TAG)..HEAD))
$(eval VORTEX_VERSION_MAJOR := $(if $(VORTEX_VERSION_MAJOR),$(VORTEX_VERSION_MAJOR),0))
$(eval VORTEX_VERSION_MINOR := $(if $(VORTEX_VERSION_MINOR),$(VORTEX_VERSION_MINOR),1))
diff --git a/VortexEngine/VortexLib/VortexLib.cpp b/VortexEngine/VortexLib/VortexLib.cpp
index 09e5e61c77..2dadb22a50 100644
--- a/VortexEngine/VortexLib/VortexLib.cpp
+++ b/VortexEngine/VortexLib/VortexLib.cpp
@@ -211,14 +211,6 @@ EMSCRIPTEN_BINDINGS(Vortex) {
.value("LED_FIRST", LedPos::LED_FIRST)
.value("LED_0", LedPos::LED_0)
.value("LED_1", LedPos::LED_1)
- .value("LED_2", LedPos::LED_2)
- .value("LED_3", LedPos::LED_3)
- .value("LED_4", LedPos::LED_4)
- .value("LED_5", LedPos::LED_5)
- .value("LED_6", LedPos::LED_6)
- .value("LED_7", LedPos::LED_7)
- .value("LED_8", LedPos::LED_8)
- .value("LED_9", LedPos::LED_9)
.value("LED_COUNT", LedPos::LED_COUNT)
.value("LED_LAST", LedPos::LED_LAST)
.value("LED_ALL", LedPos::LED_ALL)
diff --git a/VortexEngine/appmain.cpp b/VortexEngine/appmain.cpp
index a80363ed5b..342961b72f 100644
--- a/VortexEngine/appmain.cpp
+++ b/VortexEngine/appmain.cpp
@@ -3,8 +3,5 @@
int main()
{
VortexEngine::init();
- for (;;) {
- VortexEngine::tick();
- }
return 0;
}
diff --git a/VortexEngine/src/Buttons/Button.cpp b/VortexEngine/src/Buttons/Button.cpp
index e565dbbb5c..2290229db6 100644
--- a/VortexEngine/src/Buttons/Button.cpp
+++ b/VortexEngine/src/Buttons/Button.cpp
@@ -8,8 +8,47 @@
#include "VortexLib.h"
#endif
+#ifdef VORTEX_EMBEDDED
+#include "../VortexEngine.h"
+#include
+#include
+
+// Update here to change button pin/port
+#define PIN_NUM 2
+#define PORT_LETTER C
+
+// expands out details to make the macros work
+#define CONCATENATE_DETAIL(x, y) x##y
+#define CONCATENATE(x, y) CONCATENATE_DETAIL(x, y)
+#define CONCATENATE_DETAIL_3(x, y, z) x##y##z
+#define CONCATENATE_3(x, y, z) CONCATENATE_DETAIL_3(x, y, z)
+
+// macros for the button pin/port
+#define BUTTON_PORT CONCATENATE(PORT, PORT_LETTER)
+#define BUTTON_VPORT CONCATENATE(VPORT, PORT_LETTER)
+#define BUTTON_PIN CONCATENATE_3(PIN, PIN_NUM, _bm)
+#define PIN_CTRL CONCATENATE_3(PIN, PIN_NUM, CTRL)
+#define PORT_VECT CONCATENATE_3(PORT, PORT_LETTER, _PORT_vect)
+
+// interrupt handler to wakeup device on button press
+ISR(PORT_VECT)
+{
+ // mark the interrupt as handled
+ BUTTON_PORT.INTFLAGS = BUTTON_PIN;
+ // turn off the interrupt
+ BUTTON_PORT.PIN_CTRL &= ~PORT_ISC_gm;
+ // call the wakeup routine in the engine
+ VortexEngine::wakeup();
+}
+
+void Button::enableWake()
+{
+ // turn on the above interrupt for FALLING edge, maintain the pullup enabled
+ BUTTON_PORT.PIN_CTRL = PORT_PULLUPEN_bm | PORT_ISC_FALLING_gc;
+}
+#endif
+
Button::Button() :
- m_pinNum(0),
m_pressTime(0),
m_releaseTime(0),
m_holdDuration(0),
@@ -31,27 +70,33 @@ Button::~Button()
bool Button::init(uint8_t pin)
{
- m_pinNum = 0;
m_pressTime = 0;
m_releaseTime = 0;
m_holdDuration = 0;
m_releaseDuration = 0;
m_consecutivePresses = 0;
- m_releaseCount = 0;
- m_buttonState = false;
m_newPress = false;
m_newRelease = false;
- m_isPressed = m_buttonState;
m_shortClick = false;
m_longClick = false;
-
- m_pinNum = pin;
+ // this is weird, when I did m_releaseCount = !m_buttonState the
+ // compiler generated a huge amount of assembly, but not !check()
+ m_buttonState = check();
+ m_releaseCount = !check();
+ m_isPressed = m_buttonState;
+#ifdef VORTEX_EMBEDDED
+ BUTTON_PORT.PIN_CTRL = PORT_PULLUPEN_bm;
+#endif
return true;
}
bool Button::check()
{
- return (Vortex::vcallbacks()->checkPinHook(m_pinNum) == 0);
+#ifdef VORTEX_LIB
+ return (Vortex::vcallbacks()->checkPinHook(0) == 0);
+#else
+ return ((BUTTON_VPORT.IN & BUTTON_PIN) == 0);
+#endif
}
void Button::update()
@@ -76,9 +121,16 @@ void Button::update()
m_pressTime = Time::getCurtime();
m_newPress = true;
} else {
- // the button was just released
- m_releaseTime = Time::getCurtime();
- m_newRelease = true;
+ // simply ignore the first release, always. Because they just turned the device
+ // on and we don't want them to cycle to the next mode or something
+ if (m_releaseCount > 0) {
+ // the button was just released
+ m_releaseTime = Time::getCurtime();
+ m_newRelease = true;
+ }
+ // count releases even inside the ignore window so that
+ // we can tell if the button was released to stop ignoring
+ m_releaseCount++;
}
}
diff --git a/VortexEngine/src/Buttons/Button.h b/VortexEngine/src/Buttons/Button.h
index 16b6248d78..8d0d98ab19 100644
--- a/VortexEngine/src/Buttons/Button.h
+++ b/VortexEngine/src/Buttons/Button.h
@@ -3,6 +3,8 @@
#include
+#include "../VortexConfig.h"
+
// although there is only one button on the VortexFramework
// I am still opting for a non-static button class
class Button
@@ -23,6 +25,11 @@ class Button
// poll the button pin and update the state of the button object
void update();
+#ifdef VORTEX_EMBEDDED
+ // enable the button-wake trigger to wake the device on press
+ void enableWake();
+#endif
+
// whether the button was pressed this tick
bool onPress() const { return m_newPress; }
// whether the button was released this tick
@@ -54,9 +61,6 @@ class Button
uint8_t releaseCount() const { return m_releaseCount; }
private:
- // the pin number that is read
- uint8_t m_pinNum;
-
// ========================================
// state data that is populated each check
diff --git a/VortexEngine/src/Buttons/Buttons.cpp b/VortexEngine/src/Buttons/Buttons.cpp
index 466b22920c..36c4736b43 100644
--- a/VortexEngine/src/Buttons/Buttons.cpp
+++ b/VortexEngine/src/Buttons/Buttons.cpp
@@ -15,32 +15,26 @@
// This will simply point at Buttons::m_button.
Button *g_pButton = nullptr;
-// static members
-Button Buttons::m_buttons[NUM_BUTTONS];
-
bool Buttons::init()
{
- // initialize the button on pin 1
- if (!m_buttons[0].init(1)) {
+ g_pButton = new Button();
+ if (!g_pButton) {
return false;
}
- g_pButton = &m_buttons[0];
- return true;
+ // init the button on pin 1
+ return g_pButton->init(1);
}
void Buttons::cleanup()
{
+ delete g_pButton;
}
void Buttons::update()
{
- // would iterate all buttons and check them here
- // but there's only one button so
- for (uint8_t i = 0; i < NUM_BUTTONS; ++i) {
- m_buttons[i].update();
- }
+ g_pButton->update();
#ifdef VORTEX_LIB
// read input from the vortex lib interface, for example Vortex::shortClick()
- Vortex::handleInputQueue(m_buttons, NUM_BUTTONS);
+ Vortex::handleInputQueue(g_pButton, NUM_BUTTONS);
#endif
}
diff --git a/VortexEngine/src/Buttons/Buttons.h b/VortexEngine/src/Buttons/Buttons.h
index 7f02e34c73..ad9ed95146 100644
--- a/VortexEngine/src/Buttons/Buttons.h
+++ b/VortexEngine/src/Buttons/Buttons.h
@@ -25,8 +25,6 @@ class Buttons
static uint8_t numButtons() { return NUM_BUTTONS; }
private:
- // feel free to add more I guess
- static Button m_buttons[NUM_BUTTONS];
};
// best way I think
diff --git a/VortexEngine/src/Colors/Colorset.cpp b/VortexEngine/src/Colors/Colorset.cpp
index ea469c2384..8d19353bd8 100644
--- a/VortexEngine/src/Colors/Colorset.cpp
+++ b/VortexEngine/src/Colors/Colorset.cpp
@@ -14,7 +14,7 @@
#define INDEX_NONE UINT8_MAX
Colorset::Colorset() :
- m_palette(nullptr),
+ m_palette(),
m_curIndex(INDEX_NONE),
m_numColors(0)
{
@@ -52,18 +52,18 @@ Colorset::~Colorset()
}
Colorset::Colorset(Colorset &&other) noexcept :
- m_palette(other.m_palette),
+ m_palette(),
m_curIndex(INDEX_NONE),
m_numColors(other.m_numColors)
{
- other.m_palette = nullptr;
+ memcpy((void *)m_palette, (void *)other.m_palette, sizeof(m_palette));
+ memset((void *)other.m_palette, 0, sizeof(m_palette));
other.m_numColors = 0;
other.m_curIndex = INDEX_NONE;
}
void Colorset::operator=(const Colorset &other)
{
- clear();
initPalette(other.m_numColors);
for (uint8_t i = 0; i < other.m_numColors; ++i) {
m_palette[i] = other.m_palette[i];
@@ -101,10 +101,7 @@ void Colorset::init(RGBColor c1, RGBColor c2, RGBColor c3, RGBColor c4,
void Colorset::clear()
{
- if (m_palette) {
- delete[] m_palette;
- m_palette = nullptr;
- }
+ memset((void *)m_palette, 0, sizeof(m_palette));
m_numColors = 0;
resetIndex();
}
@@ -133,22 +130,6 @@ bool Colorset::addColor(RGBColor col)
if (m_numColors >= MAX_COLOR_SLOTS) {
return false;
}
- // allocate a new palette one larger than before
- RGBColor *temp = new RGBColor[m_numColors + 1];
- if (!temp) {
- return false;
- }
- // if there is already some colors in the palette
- if (m_numColors && m_palette) {
- // copy over existing colors
- for (uint8_t i = 0; i < m_numColors; ++i) {
- temp[i] = m_palette[i];
- }
- // and delete the existing palette
- delete[] m_palette;
- }
- // reassign new palette
- m_palette = temp;
// insert new color and increment number of colors
m_palette[m_numColors] = col;
m_numColors++;
@@ -212,10 +193,6 @@ void Colorset::removeColor(uint8_t index)
m_palette[i] = m_palette[i + 1];
}
m_palette[--m_numColors].clear();
- if (!m_numColors) {
- delete[] m_palette;
- m_palette = nullptr;
- }
}
// create a set of truely random colors
@@ -310,7 +287,7 @@ void Colorset::adjustBrightness(uint8_t fadeby)
// get a color from the colorset
RGBColor Colorset::get(uint8_t index) const
{
- if (index >= m_numColors || !m_palette) {
+ if (index >= m_numColors) {
return RGBColor(0, 0, 0);
}
return m_palette[index];
@@ -328,20 +305,13 @@ void Colorset::set(uint8_t index, RGBColor col)
}
return;
}
- if (!m_palette) {
- // should be impossible because if the index is less than
- // the number of colors then there must be non-zero number
- // of colors which means the palette should be initialized
- ERROR_LOGF("Programmer error setting color index %u with no palette", index);
- return;
- }
m_palette[index] = col;
}
// skip some amount of colors
void Colorset::skip(int32_t amount)
{
- if (!m_numColors || !m_palette) {
+ if (!m_numColors) {
return;
}
// if the colorset hasn't started yet
@@ -364,7 +334,7 @@ void Colorset::skip(int32_t amount)
RGBColor Colorset::cur()
{
- if (m_curIndex >= m_numColors || !m_palette) {
+ if (m_curIndex >= m_numColors) {
return RGBColor(0, 0, 0);
}
if (m_curIndex == INDEX_NONE) {
@@ -391,7 +361,7 @@ void Colorset::resetIndex()
RGBColor Colorset::getPrev()
{
- if (!m_numColors || !m_palette) {
+ if (!m_numColors) {
return RGB_OFF;
}
// handle wrapping at 0
@@ -406,7 +376,7 @@ RGBColor Colorset::getPrev()
RGBColor Colorset::getNext()
{
- if (!m_numColors || !m_palette) {
+ if (!m_numColors) {
return RGB_OFF;
}
// iterate current index, let it wrap at max uint8
@@ -420,7 +390,7 @@ RGBColor Colorset::getNext()
// peek at the next color but don't iterate
RGBColor Colorset::peek(int32_t offset) const
{
- if (!m_numColors || !m_palette) {
+ if (!m_numColors) {
return RGB_OFF;
}
uint8_t nextIndex = 0;
@@ -506,19 +476,7 @@ bool Colorset::unserialize(ByteStream &buffer)
bool Colorset::initPalette(uint8_t numColors)
{
- if (m_palette) {
- delete[] m_palette;
- m_palette = nullptr;
- }
- if (!numColors) {
- return true;
- }
- //m_palette = (RGBColor *)vcalloc(numColors, sizeof(RGBColor));
- m_palette = new RGBColor[numColors];
- if (!m_palette) {
- ERROR_OUT_OF_MEMORY();
- return false;
- }
+ clear();
m_numColors = numColors;
return true;
}
diff --git a/VortexEngine/src/Colors/Colorset.h b/VortexEngine/src/Colors/Colorset.h
index 9eb1d635f5..2c34f32c0d 100644
--- a/VortexEngine/src/Colors/Colorset.h
+++ b/VortexEngine/src/Colors/Colorset.h
@@ -151,7 +151,7 @@ class Colorset
bool initPalette(uint8_t numColors);
// palette of colors
- RGBColor *m_palette;
+ RGBColor m_palette[MAX_COLOR_SLOTS];
// the current index, starts at UINT8_MAX so that
// the very first call to getNext will iterate to 0
uint8_t m_curIndex;
diff --git a/VortexEngine/src/Leds/LedTypes.h b/VortexEngine/src/Leds/LedTypes.h
index 73bcce80f3..9ebea169de 100644
--- a/VortexEngine/src/Leds/LedTypes.h
+++ b/VortexEngine/src/Leds/LedTypes.h
@@ -15,14 +15,6 @@ enum LedPos : uint8_t
// LED constants for each led
LED_0 = LED_FIRST,
LED_1,
- LED_2,
- LED_3,
- LED_4,
- LED_5,
- LED_6,
- LED_7,
- LED_8,
- LED_9,
// the number of entries above
LED_COUNT,
@@ -63,16 +55,16 @@ enum LedPos : uint8_t
// LED_ODDS = (LED_COUNT + 3),
};
+// some helpers for microlight code
+#define LED_TIP LED_0
+#define LED_TOP LED_1
+
enum Pair : uint8_t
{
PAIR_FIRST = 0,
// one pair for each pair of leds, adjust this to be 2x the LED_COUNT
PAIR_0 = PAIR_FIRST,
- PAIR_1,
- PAIR_2,
- PAIR_3,
- PAIR_4,
PAIR_COUNT,
PAIR_LAST = (PAIR_COUNT - 1),
@@ -81,6 +73,21 @@ enum Pair : uint8_t
// Compile-time check on the number of pairs and leds
static_assert(LED_COUNT == (PAIR_COUNT * 2), "Incorrect number of Pairs for Leds! Adjust the Led enum or Pair enum to match");
+// backwards compat with bigger patterns
+#define LED_2 LED_0
+#define LED_3 LED_1
+#define LED_4 LED_0
+#define LED_5 LED_1
+#define LED_6 LED_0
+#define LED_7 LED_1
+#define LED_8 LED_0
+#define LED_9 LED_1
+
+#define PAIR_1 PAIR_0
+#define PAIR_2 PAIR_0
+#define PAIR_3 PAIR_0
+#define PAIR_4 PAIR_0
+
// check if an led is even or odd
#define isEven(pos) ((pos % 2) == 0)
#define isOdd(pos) ((pos % 2) != 0)
diff --git a/VortexEngine/src/Leds/Leds.cpp b/VortexEngine/src/Leds/Leds.cpp
index 0d1dd4c400..2149378462 100644
--- a/VortexEngine/src/Leds/Leds.cpp
+++ b/VortexEngine/src/Leds/Leds.cpp
@@ -12,13 +12,37 @@
#include "../../VortexLib/VortexLib.h"
#endif
+#ifdef VORTEX_EMBEDDED
+#include
+#include
+#endif
+
+// swap two variables in place
+#define SWAP(x, y) x ^= y; y ^= x; x ^= y;
+
+#define LED_DATA_PIN 7
+
// global brightness
uint8_t Leds::m_brightness = DEFAULT_BRIGHTNESS;
// array of led color values
RGBColor Leds::m_ledColors[LED_COUNT] = { RGB_OFF };
+// Output PORT register
+volatile uint8_t *Leds::m_port = nullptr;
+// Output PORT bitmask
+uint8_t Leds::m_pinMask = 0;
+
bool Leds::init()
{
+#ifdef VORTEX_EMBEDDED
+ // clear the onboard led so it displays nothing
+ // tiny neo pixels
+ PORTB.DIRSET |= PIN4_bm;
+ // register ouput port
+ m_port = &VPORTB.OUT;
+ // create a pin mask to use later
+ m_pinMask = PIN4_bm;
+#endif
#ifdef VORTEX_LIB
Vortex::vcallbacks()->ledsInit(m_ledColors, LED_COUNT);
#endif
@@ -260,7 +284,180 @@ void Leds::holdAll(RGBColor col)
void Leds::update()
{
+#ifdef VORTEX_EMBEDDED
+ RGBColor ledbackups[LED_COUNT];
+ memcpy(ledbackups, m_ledColors, sizeof(m_ledColors));
+ for (int c = 0; c < LED_COUNT; ++c) {
+#define SCALE8(i, scale) (((uint16_t)i * (uint16_t)(scale)) >> 8)
+ m_ledColors[c].red = SCALE8(m_ledColors[c].red, m_brightness);
+ m_ledColors[c].green = SCALE8(m_ledColors[c].green, m_brightness);
+ m_ledColors[c].blue = SCALE8(m_ledColors[c].blue, m_brightness);
+ }
+ // swap the red and green channels for the 2nd led on the microlight,
+ // they will be swapped back at the end of this function
+ SWAP(m_ledColors[LED_1].red, m_ledColors[LED_1].green);
+#endif
+
#ifdef VORTEX_LIB
Vortex::vcallbacks()->ledsShow();
#endif
+
+ // Thanks to TinyNeoPixel for this code
+#ifdef VORTEX_EMBEDDED
+ volatile uint16_t
+ i = LED_COUNT * sizeof(RGBColor); // Loop counter
+ volatile uint8_t
+ *ptr = (volatile uint8_t *)m_ledColors, // Pointer to next byte
+ b = *ptr++, // Current byte value
+ hi, // PORT w/output bit set high
+ lo; // PORT w/output bit set low
+
+ // AVRxt MCUs -- tinyAVR 0/1/2, megaAVR 0, AVR Dx ----------------------
+ // with extended maximum speeds to supm_port vigorously overclocked
+ // Dx-series parts. This is by no means intended to imply that they will
+ // run at those speeds, only that - if they do - you can control WS2812s
+ // with them.
+
+ // Hand-tuned assembly code issues data to the LED drivers at a specific
+ // rate. There's separate code for different CPU speeds (8, 12, 16 MHz)
+ // for both the WS2811 (400 KHz) and WS2812 (800 KHz) drivers. The
+ // datastream timing for the LED drivers allows a little wiggle room each
+ // way (listed in the datasheets), so the conditions for compiling each
+ // case are set up for a range of frequencies rather than just the exact
+ // 8, 12 or 16 MHz values, permitting use with some close-but-not-spot-on
+ // devices (e.g. 16.5 MHz DigiSpark). The ranges were arrived at based
+ // on the datasheet figures and have not been extensively tested outside
+ // the canonical 8/12/16 MHz speeds; there's no guarantee these will work
+ // close to the extremes (or possibly they could be pushed further).
+ // Keep in mind only one CPU speed case actually gets compiled; the
+ // resulting program isn't as massive as it might look from source here.
+
+ #if (F_CPU >= 9500000UL) && (F_CPU <= 11100000UL)
+ /*
+ volatile uint8_t n1, n2 = 0; // First, next bits out
+
+ */
+ // 14 instruction clocks per bit: HHHHxxxxLLLLL
+ // ST instructions: ^ ^ ^ (T=0,4,7)
+ volatile uint8_t next;
+
+ hi = *m_port | m_pinMask;
+ lo = *m_port & ~m_pinMask;
+ next = lo;
+ if (b & 0x80) {
+ next = hi;
+ }
+
+ // Don't "optimize" the OUT calls into the bitTime subroutine;
+ // we're exploiting the RCALL and RET as 3- and 4-cycle NOPs!
+ asm volatile(
+ "_head10:" "\n\t" // (T = 0)
+ "st %a[port], %[hi]" "\n\t" // (T = 1)
+ "rcall _bitTime10" "\n\t" // Bit 7 (T = 14)
+ "st %a[port], %[hi]" "\n\t"
+ "rcall _bitTime10" "\n\t" // Bit 6
+ "st %a[port], %[hi]" "\n\t"
+ "rcall _bitTime10" "\n\t" // Bit 5
+ "st %a[port], %[hi]" "\n\t"
+ "rcall _bitTime10" "\n\t" // Bit 4
+ "st %a[port], %[hi]" "\n\t"
+ "rcall _bitTime10" "\n\t" // Bit 3
+ "st %a[port], %[hi]" "\n\t"
+ "rcall _bitTime10" "\n\t" // Bit 2
+ "st %a[port], %[hi]" "\n\t"
+ "rcall _bitTime10" "\n\t" // Bit 1
+ // Bit 0:
+ "st %a[port], %[hi]" "\n\t" // 1 PORT = hi (T = 1)
+ "ld %[byte] , %a[ptr]+" "\n\t" // 2 b = *ptr++ (T = 4)
+ "st %a[port], %[next]" "\n\t" // 1 PORT = next (T = 5)
+ "nop" "\n\t" // 1 nop (T = 6)
+ "mov %[next] , %[lo]" "\n\t" // 1 next = lo (T = 7)
+ "sbrc %[byte] , 7" "\n\t" // 1-2 if (b & 0x80) (T = 8)
+ "mov %[next] , %[hi]" "\n\t" // 0-1 next = hi (T = 9)
+ "st %a[port], %[lo]" "\n\t" // 1 PORT = lo (T = 10)
+ "sbiw %[count], 1" "\n\t" // 2 i-- (T = 12)
+ "brne _head10" "\n\t" // 2 if (i != 0) -> (next byte) (T = 14)
+ "rjmp _done10" "\n\t"
+ "_bitTime10:" "\n\t" // nop nop nop (T = 4)
+ "st %a[port], %[next]" "\n\t" // 1 PORT = next (T = 5)
+ "mov %[next], %[lo]" "\n\t" // 1 next = lo (T = 6)
+ "lsl %[byte]" "\n\t" // 1 b <<= 1 (T = 7)
+ "sbrc %[byte], 7" "\n\t" // 1-2 if (b & 0x80) (T = 8)
+ "mov %[next], %[hi]" "\n\t" // 0-1 next = hi (T = 9)
+ "st %a[port], %[lo]" "\n\t" // 1 PORT = lo (T = 10)
+ "ret" "\n\t" // 4 return to above where we called from
+ "_done10:" "\n"
+ : [ptr] "+e" (ptr),
+ [byte] "+r" (b),
+ [next] "+r" (next),
+ [count] "+w" (i)
+ : [port] "e" (m_port),
+ [hi] "r" (hi),
+ [lo] "r" (lo));
+
+// 20 MHz(ish) AVRxt ------------------------------------------------------
+#elif (F_CPU >= 19000000UL) && (F_CPU <= 22000000L)
+
+
+ // 25 inst. clocks per bit: HHHHHHHxxxxxxxxLLLLLLLLLL
+ // ST instructions: ^ ^ ^ (T=0,7,15)
+
+ volatile uint8_t next, bit;
+
+ hi = *m_port | m_pinMask;
+ lo = *m_port & ~m_pinMask;
+ next = lo;
+ bit = 8;
+
+ asm volatile(
+ "head20:" "\n\t" // Clk Pseudocode (T = 0)
+ "st %a[port], %[hi]" "\n\t" // 1 PORT = hi (T = 1)
+ "sbrc %[byte], 7" "\n\t" // 1-2 if (b & 128)
+ "mov %[next], %[hi]" "\n\t" // 0-1 next = hi (T = 3)
+ "dec %[bit]" "\n\t" // 1 bit-- (T = 4)
+ "nop" "\n\t" // 1 nop (T = 5)
+ "rjmp .+0" "\n\t" // 2 nop nop (T = 7)
+ "st %a[port], %[next]" "\n\t" // 1 PORT = next (T = 8)
+ "mov %[next] , %[lo]" "\n\t" // 1 next = lo (T = 9)
+ "breq nextbyte20" "\n\t" // 1-2 if (bit == 0) (from dec above)
+ "rol %[byte]" "\n\t" // 1 b <<= 1 (T = 11)
+ "rjmp .+0" "\n\t" // 2 nop nop (T = 13)
+ "rjmp .+0" "\n\t" // 2 nop nop (T = 15)
+ "st %a[port], %[lo]" "\n\t" // 1 PORT = lo (T = 16)
+ "nop" "\n\t" // 1 nop (T = 17)
+ "rjmp .+0" "\n\t" // 2 nop nop (T = 19)
+ "rjmp .+0" "\n\t" // 2 nop nop (T = 21)
+ "rjmp .+0" "\n\t" // 2 nop nop (T = 23)
+ "rjmp head20" "\n\t" // 2 -> head20 (next bit out)
+ "nextbyte20:" "\n\t" // (T = 11)
+ "ldi %[bit] , 8" "\n\t" // 1 bit = 8 (T = 12)
+ "ld %[byte] , %a[ptr]+" "\n\t" // 2 b = *ptr++ (T = 14)
+ "nop" "\n\t" // 1 nop (T = 15)
+ "st %a[port], %[lo]" "\n\t" // 1 PORT = lo (T = 16)
+ "nop" "\n\t" // 1 nop (T = 17)
+ "rjmp .+0" "\n\t" // 2 nop nop (T = 19)
+ "rjmp .+0" "\n\t" // 2 nop nop (T = 21)
+ "sbiw %[count], 1" "\n\t" // 2 i-- (T = 23)
+ "brne head20" "\n" // 2 if (i != 0) -> (next byte) ()
+ : [ptr] "+e" (ptr),
+ [byte] "+r" (b),
+ [bit] "+d" (bit),
+ [next] "+r" (next),
+ [count] "+w" (i)
+ : [port] "e" (m_port),
+ [hi] "r" (hi),
+ [lo] "r" (lo));
+
+ #else
+ #error "CPU SPEED NOT SUPPORTED"
+ #endif
+ // END AVR ----------------------------------------------------------------
+#endif
+
+#ifdef VORTEX_EMBEDDED
+ // swap red and green channels back so all algorithms continue working
+ SWAP(m_ledColors[LED_1].red, m_ledColors[LED_1].green);
+ // restore the led colors from the backup (brightness mod)
+ memcpy(m_ledColors, ledbackups, sizeof(m_ledColors));
+#endif
}
diff --git a/VortexEngine/src/Leds/Leds.h b/VortexEngine/src/Leds/Leds.h
index d7e7cc7c41..bdcd94af9b 100644
--- a/VortexEngine/src/Leds/Leds.h
+++ b/VortexEngine/src/Leds/Leds.h
@@ -125,6 +125,11 @@ class Leds
// array of led color values
static RGBColor m_ledColors[LED_COUNT];
+
+ // Output PORT register
+ static volatile uint8_t *m_port;
+ // Output PORT bitmask
+ static uint8_t m_pinMask;
};
#endif
diff --git a/VortexEngine/src/Menus/Menu.cpp b/VortexEngine/src/Menus/Menu.cpp
index abfa81f77b..fc4cd52b8b 100644
--- a/VortexEngine/src/Menus/Menu.cpp
+++ b/VortexEngine/src/Menus/Menu.cpp
@@ -71,9 +71,7 @@ Menu::MenuAction Menu::run()
// every time the button is clicked, change the target led
if (g_pButton->onShortClick()) {
- do {
- nextBulbSelection();
- } while (!isValidLedSelection(m_targetLeds));
+ nextBulbSelection();
}
// on a long press of the button, lock in the target led
if (g_pButton->onLongClick()) {
@@ -99,12 +97,7 @@ Menu::MenuAction Menu::run()
void Menu::showBulbSelection()
{
Leds::clearAll();
- if (m_targetLeds == MAP_LED(LED_MULTI)) {
- LedPos pos = (LedPos)((Time::getCurtime() / 30) % LED_COUNT);
- Leds::blinkIndexOffset(pos, pos * 10, 50, 500, RGB_MAGENTA1);
- } else {
- Leds::blinkMap(m_targetLeds, BULB_SELECT_OFF_MS, BULB_SELECT_ON_MS, RGB_MAGENTA1);
- }
+ Leds::blinkMap(m_targetLeds, BULB_SELECT_OFF_MS, BULB_SELECT_ON_MS, RGB_MAGENTA1);
// blink when selecting
Menus::showSelection(RGB_MAGENTA1);
}
@@ -112,45 +105,26 @@ void Menu::showBulbSelection()
void Menu::showExit()
{
if (g_pButton->isPressed() && g_pButton->holdDuration() > SHORT_CLICK_THRESHOLD_TICKS) {
- Leds::setAll(RGB_RED);
- return;
+ Leds::setIndex(LED_1, RGB_RED);
+ } else {
+ Leds::clearIndex(LED_1);
+ Leds::blinkIndex(LED_0, EXIT_MENU_OFF_MS, EXIT_MENU_ON_MS, RGB_WHITE0);
+ Leds::blinkIndex(LED_1, EXIT_MENU_OFF_MS, EXIT_MENU_ON_MS, RGB_RED0);
}
- Leds::clearAll();
- Leds::setAll(RGB_WHITE0);
- Leds::blinkAll(EXIT_MENU_OFF_MS, EXIT_MENU_ON_MS, RGB_RED0);
}
void Menu::nextBulbSelection()
{
- Mode *cur = Modes::curMode();
// The target led can be 0 through LED_COUNT to represent any led or all leds
// modulo by LED_COUNT + 1 to include LED_COUNT (all) as a target
switch (m_targetLeds) {
case MAP_LED_ALL:
- if (cur->isMultiLed()) {
- // do not allow multi led to select anything else
- //break;
- }
m_targetLeds = MAP_LED(LED_FIRST);
break;
case MAP_LED(LED_LAST):
- m_targetLeds = MAP_PAIR_EVENS;
- break;
- case MAP_PAIR_EVENS:
- m_targetLeds = MAP_PAIR_ODDS;
- break;
- case MAP_PAIR_ODDS:
- m_targetLeds = MAP_LED(LED_MULTI);
- break;
- case MAP_LED(LED_MULTI):
m_targetLeds = MAP_LED_ALL;
break;
default: // LED_FIRST through LED_LAST
- // do not allow multi led to select anything else
- if (cur->isMultiLed()) {
- //m_targetLeds = MAP_LED_ALL;
- //break;
- }
// iterate as normal
m_targetLeds = MAP_LED(((ledmapGetFirstLed(m_targetLeds) + 1) % (LED_COUNT + 1)));
break;
diff --git a/VortexEngine/src/Menus/Menu.h b/VortexEngine/src/Menus/Menu.h
index 67d49e929b..a21d190763 100644
--- a/VortexEngine/src/Menus/Menu.h
+++ b/VortexEngine/src/Menus/Menu.h
@@ -16,7 +16,7 @@ class Menu
virtual bool init();
// the action for the menu to execute
- enum MenuAction :uint8_t {
+ enum MenuAction : uint8_t {
// quit the menus
MENU_QUIT,
// continue running the menu
@@ -44,10 +44,6 @@ class Menu
// iterate to next bulb selection
void nextBulbSelection();
- // an overridable api that allows derived menus to decide which led selections
- // should be available before they have actually opened
- virtual bool isValidLedSelection(LedMap selection) const { return true; }
-
// the mode copied from the current mode used to preview changes
Mode m_previewMode;
// the color of this menu
diff --git a/VortexEngine/src/Menus/MenuList/ColorSelect.cpp b/VortexEngine/src/Menus/MenuList/ColorSelect.cpp
index 50a069f254..a5858c8840 100644
--- a/VortexEngine/src/Menus/MenuList/ColorSelect.cpp
+++ b/VortexEngine/src/Menus/MenuList/ColorSelect.cpp
@@ -1,9 +1,13 @@
#include "ColorSelect.h"
+#include "../../VortexEngine.h"
+
#include "../../Time/TimeControl.h"
+#include "../../Patterns/PatternBuilder.h"
#include "../../Patterns/Pattern.h"
#include "../../Colors/Colorset.h"
#include "../../Buttons/Button.h"
+#include "../../Random/Random.h"
#include "../../Time/Timings.h"
#include "../../Menus/Menus.h"
#include "../../Modes/Modes.h"
@@ -32,6 +36,8 @@ ColorSelect::~ColorSelect()
{
// revert the hsv to rgb algorithm to normal
g_hsv_rgb_alg = HSV_TO_RGB_GENERIC;
+ // make sure force sleep is re-enabled when we leave
+ VortexEngine::toggleForceSleep(true);
}
bool ColorSelect::init()
@@ -188,12 +194,12 @@ void ColorSelect::showSlotSelection()
const RGBColor &col = m_colorset[m_curSelection];
if (withinNumColors && holdDurationCheck && holdDurationModCheck) {
// breath red for delete slot
- Leds::breatheIndex(LED_ALL, 0, holdDur);
+ Leds::blinkIndex(LED_0, 50, 100, col);
+ Leds::breatheIndex(LED_1, 0, holdDur);
} else if (withinNumColors) {
if (col.empty()) {
- Leds::setAll(RGB_WHITE0);
+ Leds::setIndex(LED_0, RGB_WHITE0);
}
- // blink the selected slot color
Leds::blinkAll(150, 650, col);
} else if (exitIndex < MAX_COLOR_SLOTS) {
if (m_curSelection == exitIndex) {
@@ -203,7 +209,13 @@ void ColorSelect::showSlotSelection()
exitIndex++;
}
if (m_curSelection == exitIndex) {
+ // display the full set
showFullSet(50, 100);
+ // set LED_1 to green to indicate save and exit
+ Leds::setIndex(LED_1, RGB_GREEN2);
+ // if not on exitIndex or add new color set LED_1 based on button state
+ } else if (m_curSelection != m_colorset.numColors() && !holdDurationCheck) {
+ Leds::setIndex(LED_1, g_pButton->isPressed() ? RGB_OFF : RGB_WHITE2);
}
}
@@ -222,12 +234,8 @@ void ColorSelect::showSelection(ColorSelectState mode)
return;
case STATE_PICK_HUE1:
hue = m_curSelection * (255 / 4);
- MAP_FOREACH_LED(MAP_PAIR_EVENS) {
- Leds::breatheIndex(pos, hue, (now / 2), 22, 255, 180);
- }
- MAP_FOREACH_LED(MAP_PAIR_ODDS) {
- Leds::breatheIndex(pos, hue, (now / 2) + 125, 22, 255, 180);
- }
+ Leds::breatheIndex(LED_0, hue, (now / 2), 22, 255, 180);
+ Leds::breatheIndex(LED_1, hue, (now / 2) + 125, 22, 255, 180);
// force sat at hue level1
sat = 255;
// NOTE: return here
@@ -240,9 +248,11 @@ void ColorSelect::showSelection(ColorSelectState mode)
break;
case STATE_PICK_SAT:
sat = sats[m_curSelection];
+ Leds::breatheIndexSat(LED_1, hue, (now / 3), 100, 150, 150);
break;
case STATE_PICK_VAL:
val = vals[m_curSelection];
+ Leds::breatheIndexVal(LED_1, hue, (now / 3), 100, sat, 150);
break;
}
Leds::setMap(MAP_PAIR_EVENS, HSVColor(hue, sat, val));
@@ -259,12 +269,5 @@ void ColorSelect::showFullSet(uint8_t offMs, uint8_t onMs)
if ((now % offOnMs) < MS_TO_TICKS(onMs)) {
Leds::setAll(m_colorset.get((now / offOnMs) % numCols));
}
-}
-
-bool ColorSelect::isValidLedSelection(LedMap selection) const
-{
- // if we have a multi-led pattern then we can only select LED_MULTI otherwise
- // if we don't have a multi-led pattern then we can't select multi
- bool selectedMulti = (selection == MAP_LED(LED_MULTI));
- return selectedMulti == m_previewMode.isMultiLed();
+ Leds::setIndex(LED_1, RGB_GREEN0);
}
diff --git a/VortexEngine/src/Menus/MenuList/ColorSelect.h b/VortexEngine/src/Menus/MenuList/ColorSelect.h
index 6bda0fd1c3..e0f0ed496a 100644
--- a/VortexEngine/src/Menus/MenuList/ColorSelect.h
+++ b/VortexEngine/src/Menus/MenuList/ColorSelect.h
@@ -22,11 +22,8 @@ class ColorSelect : public Menu
void onLongClick() override;
private:
- // override the led selection api to choose which led maps can be selected
- bool isValidLedSelection(LedMap selection) const override;
-
// private enumeration for internal state of color selection
- enum ColorSelectState : uint32_t
+ enum ColorSelectState : uint8_t
{
STATE_INIT,
STATE_PICK_SLOT,
diff --git a/VortexEngine/src/Menus/MenuList/FactoryReset.cpp b/VortexEngine/src/Menus/MenuList/FactoryReset.cpp
index 287af81739..5489144ed0 100644
--- a/VortexEngine/src/Menus/MenuList/FactoryReset.cpp
+++ b/VortexEngine/src/Menus/MenuList/FactoryReset.cpp
@@ -76,13 +76,14 @@ void FactoryReset::onLongClick()
// the button was held down long enough so actually perform the factory reset
// restore defaults and then leave menu and save
if (m_advanced) {
- uint8_t curModeIndex = Modes::curModeIndex();
// reset the target mode slot on the target led
- const default_mode_entry &def = default_modes[curModeIndex];
- Colorset set(def.numColors, def.cols);
+ const DefaultModeEntry &defMode = defaultModes[Modes::curModeIndex()];
Mode *cur = Modes::curMode();
- cur->setPatternMap(m_targetLeds, def.patternID, nullptr, &set);
- // re-initialize the current mode
+ MAP_FOREACH_LED(m_targetLeds) {
+ const DefaultLedEntry &led = defMode.leds[pos];
+ Colorset set(led.numColors, led.cols);
+ cur->setPattern(led.patternID, pos, nullptr, &set);
+ }
cur->init();
} else {
Leds::setBrightness(DEFAULT_BRIGHTNESS);
@@ -122,6 +123,7 @@ void FactoryReset::showReset()
uint8_t onMs = (progress > 60) ? 30 : 100;
uint8_t sat = (uint8_t)((progress * 5) >> 1); // Using bit shift for division by 2
Leds::clearAll();
- Leds::blinkAll(offMs, onMs, HSVColor(0, 255 - sat, 180));
+ Leds::blinkIndex(LED_0, offMs, onMs, HSVColor(0, 255 - sat, 180));
+ Leds::blinkIndex(LED_1, offMs, onMs, RGB_WHITE0);
}
diff --git a/VortexEngine/src/Menus/MenuList/GlobalBrightness.cpp b/VortexEngine/src/Menus/MenuList/GlobalBrightness.cpp
index 8772f88b8c..68853295b0 100644
--- a/VortexEngine/src/Menus/MenuList/GlobalBrightness.cpp
+++ b/VortexEngine/src/Menus/MenuList/GlobalBrightness.cpp
@@ -1,7 +1,16 @@
#include "GlobalBrightness.h"
-#include "../../Modes/Modes.h"
+#include "../../VortexEngine.h"
+
+#include "../../Patterns/PatternArgs.h"
+#include "../../Patterns/Pattern.h"
+#include "../../Time/TimeControl.h"
+#include "../../Colors/Colorset.h"
+#include "../../Buttons/Button.h"
+#include "../../Time/Timings.h"
#include "../../Menus/Menus.h"
+#include "../../Modes/Modes.h"
+#include "../../Time/Timer.h"
#include "../../Leds/Leds.h"
#include "../../Log/Log.h"
@@ -9,12 +18,17 @@
#define NUM_BRIGHTNESS_OPTIONS (sizeof(m_brightnessOptions) / sizeof(m_brightnessOptions[0]))
GlobalBrightness::GlobalBrightness(const RGBColor &col, bool advanced) :
- Menu(col, advanced)
+ Menu(col, advanced),
+ m_keychain_modeState(KEYCHAIN_MODE_STATE_OFF),
+ m_lastStateChange(0),
+ m_colorIndex(0)
{
}
GlobalBrightness::~GlobalBrightness()
{
+ // ensure force sleep is turned back on when we leave
+ VortexEngine::toggleForceSleep(true);
}
bool GlobalBrightness::init()
@@ -31,6 +45,17 @@ bool GlobalBrightness::init()
m_curSelection = i;
}
}
+ if (m_advanced) {
+ // turn off force sleep while running keychain mode
+ VortexEngine::toggleForceSleep(false);
+ // enable keychain mode so if the device sleeps it will wake into keychain mode
+ Modes::setKeychainMode(true);
+ // start in the off position, this call is necessary to update the
+ // lastStateChange time to the current time of init
+ setKeychainModeState(KEYCHAIN_MODE_STATE_SOLID);
+ // reset the color index to ensure it starts on first color
+ m_colorIndex = 0;
+ }
DEBUG_LOG("Entered global brightness");
return true;
}
@@ -41,6 +66,9 @@ Menu::MenuAction GlobalBrightness::run()
if (result != MENU_CONTINUE) {
return result;
}
+ if (m_advanced) {
+ return runKeychainMode();
+ }
// show the current brightness
showBrightnessSelection();
// show selections
@@ -51,12 +79,18 @@ Menu::MenuAction GlobalBrightness::run()
void GlobalBrightness::onShortClick()
{
+ if (m_advanced) {
+ return;
+ }
// include one extra option for the exit slot
m_curSelection = (m_curSelection + 1) % (NUM_BRIGHTNESS_OPTIONS + 1);
}
void GlobalBrightness::onLongClick()
{
+ if (m_advanced) {
+ return;
+ }
if (m_curSelection >= NUM_BRIGHTNESS_OPTIONS) {
// no save exit
leaveMenu();
@@ -76,3 +110,101 @@ void GlobalBrightness::showBrightnessSelection()
}
Leds::setAll(HSVColor(38, 255, m_brightnessOptions[m_curSelection]));
}
+
+// ==================== KEYCHAIN_MODE STUFF ====================
+
+// don't worry about this stuff
+#define KEYCHAIN_MODE_TIMER_MS 2100
+#define KEYCHAIN_MODE_TIMER_TICKS MS_TO_TICKS(KEYCHAIN_MODE_TIMER_MS)
+#define KEYCHAIN_MODE_EXIT_CLICKS 8
+#define KEYCHAIN_MODE_SLEEP_S 10
+#define KEYCHAIN_MODE_SLEEP_TICKS SEC_TO_TICKS(KEYCHAIN_MODE_SLEEP_S)
+
+// bonus simulate keychain_mode in this menu
+Menu::MenuAction GlobalBrightness::runKeychainMode()
+{
+ // check for exit
+ if (g_pButton->onConsecutivePresses(KEYCHAIN_MODE_EXIT_CLICKS)) {
+ // turn off keychain mode now
+ Modes::setKeychainMode(false);
+ return MENU_QUIT;
+ }
+ uint32_t now = Time::getCurtime();
+ // whether the button was pressed before the timer expired
+ // when the button is first pressed after the window has expired switch off
+ if (g_pButton->onPress() && m_keychain_modeState != KEYCHAIN_MODE_STATE_OFF && now > (m_lastStateChange + KEYCHAIN_MODE_TIMER_TICKS)) {
+ setKeychainModeState(KEYCHAIN_MODE_STATE_OFF);
+ return MENU_CONTINUE;
+ }
+ // when the button is released, but only if they pressed it within this state
+ if (g_pButton->onRelease() && g_pButton->pressTime() > m_lastStateChange) {
+ // advance the state or turn off based on press time
+ setKeychainModeState((keychain_mode_state)(m_keychain_modeState + 1));
+ return MENU_CONTINUE;
+ }
+ // as long as the button is held down the leds clear
+ if (g_pButton->isPressed()) {
+ Leds::clearAll();
+ return MENU_CONTINUE;
+ }
+ // check for sleep after some seconds
+ if (m_keychain_modeState == KEYCHAIN_MODE_STATE_OFF && now > (m_lastStateChange + KEYCHAIN_MODE_SLEEP_TICKS)) {
+ // Optional: indicate when the keychain is turning off (useful mainly for debugging)
+ //Leds::holdAll(RGB_PURPLE);
+ VortexEngine::enterSleep(false);
+ return MENU_QUIT;
+ }
+ // render the signal mode in a different way
+ if (m_keychain_modeState == KEYCHAIN_MODE_STATE_SIGNAL) {
+ // use a custom blink because the SIGNAL_OFF duration is too large for uint8 params
+ Leds::clearAll();
+ Leds::blinkIndexOffset(LED_ALL,
+ Time::getCurtime() - m_lastStateChange,
+ SIGNAL_OFF_DURATION,
+ SIGNAL_ON_DURATION,
+ m_previewMode.getColorset().get(m_colorIndex));
+ } else {
+ // play the keychain_mode mode
+ m_previewMode.play();
+ }
+ return MENU_CONTINUE;
+}
+
+void GlobalBrightness::setKeychainModeState(keychain_mode_state newState)
+{
+ // update the keychain_mode state
+ m_keychain_modeState = (keychain_mode_state)(newState % KEYCHAIN_MODE_STATE_COUNT);
+ // record the time of this statechange
+ m_lastStateChange = Time::getCurtime();
+ // this could
+ uint8_t numCols = Modes::curMode()->getColorset().numColors();
+ // the args that will define the timing of the blink on the solid pattern
+ PatternArgs args;
+ switch (m_keychain_modeState) {
+ case KEYCHAIN_MODE_STATE_OFF:
+ default:
+ // iterate to next color and wrap to 0, don't use modulus because numCols could be 0
+ m_colorIndex++;
+ if (m_colorIndex >= numCols) {
+ m_colorIndex = 0;
+ }
+ break;
+ case KEYCHAIN_MODE_STATE_SOLID:
+ args.init(200);
+ break;
+ case KEYCHAIN_MODE_STATE_DOPS:
+ args.init(1, 10);
+ break;
+ case KEYCHAIN_MODE_STATE_SIGNAL:
+ // unfortunately the signal blink timing is too large to fit into the uint8 params
+ // so we just use a custom blink to render that one, but use the previewMode for
+ // the other two states
+ break;
+ }
+ // update the mode and ensure current colorset is always used, use PATTERN_SOLID
+ // because that will never iterate to the next color and allows us to force
+ // the color index via argument 6
+ args.arg6 = m_colorIndex;
+ m_previewMode.setPattern(PATTERN_SOLID, LED_ALL, &args);
+ m_previewMode.init();
+}
diff --git a/VortexEngine/src/Menus/MenuList/GlobalBrightness.h b/VortexEngine/src/Menus/MenuList/GlobalBrightness.h
index 34f2b65cfc..5efabf408a 100644
--- a/VortexEngine/src/Menus/MenuList/GlobalBrightness.h
+++ b/VortexEngine/src/Menus/MenuList/GlobalBrightness.h
@@ -26,6 +26,33 @@ class GlobalBrightness : public Menu
BRIGHTNESS_OPTION_3,
BRIGHTNESS_OPTION_4
};
+
+private:
+ // don't worry about this stuff
+ enum keychain_mode_state : uint8_t
+ {
+ // sleeping / fake off
+ KEYCHAIN_MODE_STATE_OFF = 0,
+
+ // solid/tracer
+ KEYCHAIN_MODE_STATE_SOLID,
+
+ // dops blink 4 / 16
+ KEYCHAIN_MODE_STATE_DOPS,
+
+ // signal blink 16 / 120
+ KEYCHAIN_MODE_STATE_SIGNAL,
+
+ // total states
+ KEYCHAIN_MODE_STATE_COUNT
+ };
+
+ keychain_mode_state m_keychain_modeState;
+ uint32_t m_lastStateChange;
+ uint8_t m_colorIndex;
+
+ void setKeychainModeState(keychain_mode_state newState);
+ Menu::MenuAction runKeychainMode();
};
#endif
diff --git a/VortexEngine/src/Menus/MenuList/ModeSharing.cpp b/VortexEngine/src/Menus/MenuList/ModeSharing.cpp
index e3391c1df2..d537380eb4 100644
--- a/VortexEngine/src/Menus/MenuList/ModeSharing.cpp
+++ b/VortexEngine/src/Menus/MenuList/ModeSharing.cpp
@@ -4,9 +4,9 @@
#include "../../Serial/Serial.h"
#include "../../Time/TimeControl.h"
#include "../../Time/Timings.h"
-#include "../../Wireless/IRReceiver.h"
+#include "../../Wireless/VLReceiver.h"
#include "../../Wireless/VLSender.h"
-#include "../../Wireless/IRSender.h"
+#include "../../Patterns/Pattern.h"
#include "../../Buttons/Button.h"
#include "../../Modes/Modes.h"
#include "../../Modes/Mode.h"
@@ -29,12 +29,14 @@ bool ModeSharing::init()
if (!Menu::init()) {
return false;
}
- // skip led selection
- m_ledSelected = true;
+ if (!m_advanced) {
+ // skip led selection
+ m_ledSelected = true;
+ }
// start on receive because it's the more responsive of the two
// the odds of opening receive and then accidentally receiving
// a mode that is being broadcast nearby is completely unlikely
- beginReceivingIR();
+ beginReceiving();
DEBUG_LOG("Entering Mode Sharing");
return true;
}
@@ -46,36 +48,41 @@ Menu::MenuAction ModeSharing::run()
return result;
}
switch (m_sharingMode) {
- case ModeShareState::SHARE_SEND_IR:
- // render the 'send mode' lights
- showSendModeIR();
- // continue sending any data as long as there is more to send
- continueSendingIR();
- break;
- case ModeShareState::SHARE_SEND_VL:
+ case ModeShareState::SHARE_SEND:
// render the 'send mode' lights
- showSendModeVL();
+ showSendMode();
// continue sending any data as long as there is more to send
- continueSendingVL();
+ continueSending();
break;
case ModeShareState::SHARE_RECEIVE:
// render the 'receive mode' lights
showReceiveMode();
// load any modes that are received
- receiveModeIR();
+ receiveMode();
break;
}
return MENU_CONTINUE;
}
+void ModeSharing::onLedSelected()
+{
+ // if we selected leds that implies advanced mode
+ if (m_targetLeds == MAP_LED(LED_1)) {
+ // if we selected the top led then simply swap the two patterns
+ // so that the top led is sent first -- if the receiver is receiving
+ // into one slot then it will only use the first pattern to do so
+ m_previewMode.swapPatterns(LED_0, LED_1);
+ }
+}
+
// handlers for clicks
void ModeSharing::onShortClick()
{
switch (m_sharingMode) {
case ModeShareState::SHARE_RECEIVE:
// click while on receive -> end receive, start sending
- IRReceiver::endReceiving();
- beginSendingIR();
+ VLReceiver::endReceiving();
+ beginSending();
DEBUG_LOG("Switched to send mode");
break;
default:
@@ -86,45 +93,27 @@ void ModeSharing::onShortClick()
void ModeSharing::onLongClick()
{
- Modes::updateCurMode(&m_previewMode);
- leaveMenu(true);
+ leaveMenu();
}
-void ModeSharing::beginSendingVL()
+void ModeSharing::beginSending()
{
// if the sender is sending then cannot start again
if (VLSender::isSending()) {
ERROR_LOG("Cannot begin sending, sender is busy");
return;
}
- m_sharingMode = ModeShareState::SHARE_SEND_VL;
+ m_sharingMode = ModeShareState::SHARE_SEND;
// initialize it with the current mode data
- VLSender::loadMode(Modes::curMode());
+ VLSender::loadMode(&m_previewMode);
// send the first chunk of data, leave if we're done
if (!VLSender::send()) {
// when send has completed, stores time that last action was completed to calculate interval between sends
- beginReceivingIR();
- }
-}
-
-void ModeSharing::beginSendingIR()
-{
- // if the sender is sending then cannot start again
- if (IRSender::isSending()) {
- ERROR_LOG("Cannot begin sending, sender is busy");
- return;
- }
- m_sharingMode = ModeShareState::SHARE_SEND_IR;
- // initialize it with the current mode data
- IRSender::loadMode(Modes::curMode());
- // send the first chunk of data, leave if we're done
- if (!IRSender::send()) {
- // when send has completed, stores time that last action was completed to calculate interval between sends
- beginReceivingIR();
+ beginReceiving();
}
}
-void ModeSharing::continueSendingVL()
+void ModeSharing::continueSending()
{
// if the sender isn't sending then nothing to do
if (!VLSender::isSending()) {
@@ -132,80 +121,70 @@ void ModeSharing::continueSendingVL()
}
if (!VLSender::send()) {
// when send has completed, stores time that last action was completed to calculate interval between sends
- beginReceivingIR();
+ beginReceiving();
}
}
-void ModeSharing::continueSendingIR()
-{
- // if the sender isn't sending then nothing to do
- if (!IRSender::isSending()) {
- return;
- }
- if (!IRSender::send()) {
- // when send has completed, stores time that last action was completed to calculate interval between sends
- beginReceivingIR();
- }
-}
-
-void ModeSharing::beginReceivingIR()
+void ModeSharing::beginReceiving()
{
m_sharingMode = ModeShareState::SHARE_RECEIVE;
- IRReceiver::beginReceiving();
+ VLReceiver::beginReceiving();
}
-void ModeSharing::receiveModeIR()
+void ModeSharing::receiveMode()
{
// if reveiving new data set our last data time
- if (IRReceiver::onNewData()) {
+ if (VLReceiver::onNewData()) {
m_timeOutStartTime = Time::getCurtime();
// if our last data was more than time out duration reset the recveiver
} else if (m_timeOutStartTime > 0 && (m_timeOutStartTime + MAX_TIMEOUT_DURATION) < Time::getCurtime()) {
- IRReceiver::resetIRState();
+ VLReceiver::resetVLState();
m_timeOutStartTime = 0;
return;
}
- // check if the IRReceiver has a full packet available
- if (!IRReceiver::dataReady()) {
+ // check if the VLReceiver has a full packet available
+ if (!VLReceiver::dataReady()) {
// nothing available yet
return;
}
DEBUG_LOG("Mode ready to receive! Receiving...");
- // receive the IR mode into the current mode
- if (!IRReceiver::receiveMode(&m_previewMode)) {
+ // receive the VL mode into the current mode
+ if (!VLReceiver::receiveMode(&m_previewMode)) {
ERROR_LOG("Failed to receive mode");
return;
}
DEBUG_LOGF("Success receiving mode: %u", m_previewMode.getPatternID());
- if (!m_advanced) {
- Modes::updateCurMode(&m_previewMode);
- // leave menu and save settings, even if the mode was the same whatever
- leaveMenu(true);
+ if (m_advanced && m_targetLeds != MAP_LED_ALL) {
+ LedPos target = ledmapGetFirstLed(m_targetLeds);
+ LedPos other = LED_1;
+ // if the user picked the top led to copy into then swap the patterns
+ // in the incoming mode so the 0th pattern is on the top led
+ if (target == LED_1) {
+ other = LED_0;
+ m_previewMode.swapPatterns(LED_0, LED_1);
+ }
+ m_previewMode.copyPatternFrom(Modes::curMode(), other, other);
}
+ Modes::updateCurMode(&m_previewMode);
+ // leave menu and save settings, even if the mode was the same whatever
+ leaveMenu(true);
}
-void ModeSharing::showSendModeVL()
-{
- // show a dim color when not sending
- Leds::clearAll();
-}
-
-void ModeSharing::showSendModeIR()
+void ModeSharing::showSendMode()
{
// show a dim color when not sending
- Leds::clearAll();
+ if (!VLSender::isSending()) {
+ Leds::setAll(RGBColor(0, 20, 20));
+ }
}
void ModeSharing::showReceiveMode()
{
- if (IRReceiver::isReceiving()) {
+ if (VLReceiver::isReceiving()) {
// using uint32_t to avoid overflow, the result should be within 10 to 255
- Leds::setAll(RGBColor(0, IRReceiver::percentReceived(), 0));
+ Leds::setIndex(LED_0, RGBColor(0, VLReceiver::percentReceived(), 0));
+ Leds::clearIndex(LED_1);
} else {
- if (m_advanced) {
- m_previewMode.play();
- } else {
- Leds::setAll(RGB_WHITE0);
- }
+ Leds::setAll(RGB_WHITE0);
}
}
diff --git a/VortexEngine/src/Menus/MenuList/ModeSharing.h b/VortexEngine/src/Menus/MenuList/ModeSharing.h
index dc5adf2357..96512e0de3 100644
--- a/VortexEngine/src/Menus/MenuList/ModeSharing.h
+++ b/VortexEngine/src/Menus/MenuList/ModeSharing.h
@@ -12,25 +12,24 @@ class ModeSharing : public Menu
bool init() override;
MenuAction run() override;
+ // callback after the user selects the target led
+ void onLedSelected() override;
+
// handlers for clicks
void onShortClick() override;
void onLongClick() override;
private:
- void beginSendingVL();
- void beginSendingIR();
- void continueSendingVL();
- void continueSendingIR();
- void beginReceivingIR();
- void receiveModeIR();
-
- void showSendModeVL();
- void showSendModeIR();
+ void beginSending();
+ void continueSending();
+ void beginReceiving();
+ void receiveMode();
+
+ void showSendMode();
void showReceiveMode();
enum class ModeShareState {
- SHARE_SEND_IR, // send mode over ir
- SHARE_SEND_VL, // send mode over vl
+ SHARE_SEND, // send mode
SHARE_RECEIVE, // receive mode
};
diff --git a/VortexEngine/src/Menus/MenuList/PatternSelect.cpp b/VortexEngine/src/Menus/MenuList/PatternSelect.cpp
index 9f4e1582af..227a69f835 100644
--- a/VortexEngine/src/Menus/MenuList/PatternSelect.cpp
+++ b/VortexEngine/src/Menus/MenuList/PatternSelect.cpp
@@ -15,7 +15,6 @@
PatternSelect::PatternSelect(const RGBColor &col, bool advanced) :
Menu(col, advanced),
- m_newPatternID(PATTERN_FIRST),
m_srcLed(LED_FIRST),
m_started(false)
{
@@ -54,63 +53,28 @@ void PatternSelect::onLedSelected()
void PatternSelect::onShortClick()
{
- nextPattern();
-}
-
-void PatternSelect::nextPatternID()
-{
- // increment to next pattern
- PatternID endList = PATTERN_SINGLE_LAST;
- PatternID beginList = PATTERN_SINGLE_FIRST;
-#if VORTEX_SLIM == 0
- // if targeted multi led or all singles, iterate through multis
- if ((m_targetLeds == MAP_LED_ALL) || (m_targetLeds == MAP_LED(LED_MULTI))) {
- endList = PATTERN_MULTI_LAST;
- }
- // if targeted multi then start at multis and only iterate multis
- if ((m_targetLeds == MAP_LED(LED_MULTI))) {
- beginList = PATTERN_MULTI_FIRST;
- }
-#endif
- m_newPatternID = (PatternID)((m_newPatternID + 1) % endList);
- if (m_newPatternID > endList || m_newPatternID < beginList) {
- m_newPatternID = beginList;
+ PatternID newID = (PatternID)(m_previewMode.getPatternID(m_srcLed) + 1);
+ if (newID > PATTERN_SINGLE_LAST) {
+ newID = PATTERN_SINGLE_FIRST;
+ Leds::holdAll(RGB_WHITE);
}
-}
-
-void PatternSelect::nextPattern()
-{
- if (m_started) {
- nextPatternID();
- } else {
+ if (!m_started) {
m_started = true;
- // Do not modify m_newPatternID Here! It has been set in the long click handler
- // to be the start of the list we want to iterate
+ newID = PATTERN_FIRST;
}
// set the new pattern id
- if (isMultiLedPatternID(m_newPatternID)) {
- m_previewMode.setPattern(m_newPatternID);
+ if (isMultiLedPatternID(newID)) {
+ m_previewMode.setPattern(newID);
} else {
- // if the user selected multi then just put singles on all leds
- LedMap setLeds = (m_targetLeds == MAP_LED(LED_MULTI)) ? LED_ALL : m_targetLeds;
- m_previewMode.setPatternMap(setLeds, m_newPatternID);
- // TODO: clear multi a better way
- m_previewMode.clearPattern(LED_MULTI);
+ m_previewMode.setPatternMap(m_targetLeds, newID);
}
m_previewMode.init();
- DEBUG_LOGF("Iterated to pattern id %d", m_newPatternID);
+ DEBUG_LOGF("Iterated to pattern id %d", newID);
}
void PatternSelect::onLongClick()
{
- bool needsSave = false;
- Mode *cur = Modes::curMode();
- needsSave = !cur || !cur->equals(&m_previewMode);
- if (needsSave) {
- // update the current mode with the new pattern
- Modes::updateCurMode(&m_previewMode);
- }
- DEBUG_LOGF("Saving pattern %u", m_newPatternID);
- // done in the pattern select menu
- leaveMenu(needsSave);
+ // store the mode as current mode
+ Modes::updateCurMode(&m_previewMode);
+ leaveMenu(true);
}
diff --git a/VortexEngine/src/Menus/MenuList/PatternSelect.h b/VortexEngine/src/Menus/MenuList/PatternSelect.h
index 5355b65e61..e825f56b54 100644
--- a/VortexEngine/src/Menus/MenuList/PatternSelect.h
+++ b/VortexEngine/src/Menus/MenuList/PatternSelect.h
@@ -23,12 +23,6 @@ class PatternSelect : public Menu
void onLongClick() override;
private:
- void nextPatternID();
- void nextPattern();
-
- // the patternid of the current demo
- PatternID m_newPatternID;
-
// helpful member
LedPos m_srcLed;
diff --git a/VortexEngine/src/Menus/MenuList/Randomizer.cpp b/VortexEngine/src/Menus/MenuList/Randomizer.cpp
index 608cb3980e..90d76daa0c 100644
--- a/VortexEngine/src/Menus/MenuList/Randomizer.cpp
+++ b/VortexEngine/src/Menus/MenuList/Randomizer.cpp
@@ -166,6 +166,8 @@ void Randomizer::showRandomizationSelect()
// this is blinking the light to off so the params are switched but still effectively correct
Leds::blinkAll(DOPS_ON_DURATION, DOPS_OFF_DURATION);
}
+ // indicate on the 2nd led whether the button is pressed
+ Leds::setIndex(LED_1, g_pButton->isPressed() ? RGB_OFF : RGB_WHITE1);
// render the click selection blink
Menus::showSelection();
}
diff --git a/VortexEngine/src/Modes/DefaultModes.cpp b/VortexEngine/src/Modes/DefaultModes.cpp
index a0f4be8b7c..a2f0e7b7e4 100644
--- a/VortexEngine/src/Modes/DefaultModes.cpp
+++ b/VortexEngine/src/Modes/DefaultModes.cpp
@@ -2,128 +2,67 @@
#include "../Colors/ColorTypes.h"
+// all the different colorsets used in defaults defined separately from the default list
+// so we can save space instead of using fixed sized arrays in the DefaultModeEntry for colors
+const uint32_t rgbCols[] = { RGB_RED, RGB_GREEN, RGB_BLUE, };
+const uint32_t pat2TipCols = 0x97709F;
+const uint32_t pat2TopCols = 0x4D00B2;
+const uint32_t pat4TipCols[] = { 0xC4003B, 0x700000, 0x000000, 0x0017E9, 0x22004E, 0x000000, };
+const uint32_t pat4TopCols[] = { 0xC4003B, 0x4D00B2, 0x0017E9 };
+const uint32_t pat5TipCols[] = { 0xFFC600, 0x235500, 0x00FF66, 0x004355, 0x0600FF, 0x480055, 0xFF0057 };
+const uint32_t pat5TopCols[] = { 0xFFC600, 0x235500, 0x00FF66, 0x004355, 0x0600FF, 0x480055, 0xFF0057 };
+const uint32_t pat6Cols[] = { 0x9FFF00, 0x00FF66, 0x009FFF, 0x5A00FF, 0xFF009F };
+const uint32_t pat7Cols[] = { 0x00AA90, 0x00B1FF, 0x300055, 0xFF002D, 0x541B00 };
+const uint32_t pat8TipCols[] = { 0x0000FF, 0x400055, 0x54000B, 0x525400, 0x26AA00 };
+const uint32_t pat8TopCols[] = { 0xFFC600, 0x235500, 0x00FF66, 0x004355, 0x0600FF, 0x480055, 0xFF0057 };
+const uint32_t pat9Cols[] = { 0xFFF600, 0x000880 };
+
// Here is the array of 'default modes' that are assigned to
// the gloveset upon factory reset
-const default_mode_entry default_modes[MAX_MODES] = {
- {
- PATTERN_DOPS, 5, {
- RGB_RED,
- RGB_GREEN,
- RGB_BLUE,
- 0xABAA00,
- 0x5500AB
- }
- },
- {
- PATTERN_GHOSTCRUSH, 5, {
- RGB_WHITE,
- RGB_WHITE,
- RGB_OFF,
- 0x700000,
- RGB_OFF,
- }
- },
- {
- PATTERN_WARPWORM, 2, {
- RGB_GREEN,
- 0x26004B,
- }
- },
- {
- PATTERN_PULSISH, 3, {
- 0x00AB55,
- 0x8D1C55,
- 0x00001C
- }
- },
-
- {
- PATTERN_ZIGZAG, 6, {
- RGB_OFF,
- 0x56D400,
- 0x5500AB,
- RGB_OFF,
- RGB_RED,
- 0x700000
- }
- },
- {
- PATTERN_STROBE, 8, {
- 0xD4002B,
- RGB_OFF,
- 0x0056AA,
- RGB_OFF,
- 0x8E711C,
- RGB_OFF,
- 0x0056AA,
- RGB_OFF
- }
- },
- {
- PATTERN_SNOWBALL, 3, {
- 0x170600,
- 0x00840A,
- 0x12002A
- }
- },
- {
- PATTERN_ULTRADOPS, 8, {
- 0x1C0000,
- 0x4B2600,
- 0xABAA00,
- 0x001C00,
- 0x00130A,
- 0x00001C,
- 0x26004B,
- 0x13000A
- }
- },
- {
- PATTERN_VORTEX, 4, {
- 0xAA0055,
- 0x7070C5,
- 0x0A0013,
- 0x1C8E55,
- }
- },
- {
- PATTERN_VORTEXWIPE, 8, {
- RGB_RED,
- 0x00001C,
- 0x00001C,
- 0x00001C,
- 0x00001C,
- 0x00001C,
- 0x00001C,
- 0x00001C,
- }
- },
- {
- PATTERN_GHOSTCRUSH, 7, {
- 0x26004B,
- RGB_OFF,
- RGB_GREEN,
- RGB_WHITE,
- RGB_GREEN,
- RGB_OFF,
- 0x26004B,
- }
- },
- {
- PATTERN_VORTEXWIPE, 3, {
- 0x00AB55,
- 0x7F0081,
- 0xAA381C,
- }
- },
- {
- PATTERN_COMPLEMENTARY_BLEND, 3, {
- RGB_RED,
- RGB_GREEN,
- RGB_BLUE
- }
- }
+const DefaultModeEntry defaultModes[MAX_MODES] = {
+ // mode 1
+ {{
+ { PATTERN_STROBEGAP, 3, rgbCols }, // tip
+ { PATTERN_STROBEGAP, 3, rgbCols } // top
+ }},
+ // mode 2
+ {{
+ { PATTERN_COMPLEMENTARY_BLEND, 3, rgbCols },
+ { PATTERN_BLEND, 3, rgbCols }
+ }},
+ // mode 3
+ {{
+ { PATTERN_STROBE, 1, &pat2TipCols },
+ { PATTERN_HYPERGAP, 1, &pat2TopCols }
+ }},
+ // mode 4
+ {{
+ { PATTERN_STROBE, 6, pat4TipCols },
+ { PATTERN_STROBIE, 3, pat4TopCols }
+ }},
+ // mode 5
+ {{
+ { PATTERN_DOUBLEDOPS, 7, pat5TipCols },
+ { PATTERN_DOUBLEDOPS, 7, pat5TopCols }
+ }},
+ // mode 6
+ {{
+ { PATTERN_GHOSTCRUSH, 5, pat6Cols },
+ { PATTERN_GHOSTCRUSH, 5, pat6Cols }
+ }},
+ // mode 7
+ {{
+ { PATTERN_ULTRADOPS, 5, pat7Cols },
+ { PATTERN_ULTRADOPS, 5, pat7Cols }
+ }},
+ // mode 8
+ {{
+ { PATTERN_PICOGAP, 5, pat8TipCols },
+ { PATTERN_PICOGAP, 7, pat8TopCols }
+ }},
+ // mode 9
+ {{
+ { PATTERN_DOPS, 2, pat9Cols },
+ { PATTERN_HYPERSTROBE, 2, pat9Cols }
+ }},
};
-
-// exposed size of the default modes array
-const uint8_t num_default_modes = (sizeof(default_modes) / sizeof(default_modes[0]));
diff --git a/VortexEngine/src/Modes/DefaultModes.h b/VortexEngine/src/Modes/DefaultModes.h
index 888e4fb7bf..6c685a0d81 100644
--- a/VortexEngine/src/Modes/DefaultModes.h
+++ b/VortexEngine/src/Modes/DefaultModes.h
@@ -2,20 +2,23 @@
#define DEFAULT_MODES_H
#include "../Patterns/Patterns.h"
+#include "../Leds/LedTypes.h"
#include "../VortexConfig.h"
// structure of the entries in the default modes array
-struct default_mode_entry
+struct DefaultLedEntry
{
PatternID patternID;
uint8_t numColors;
- uint32_t cols[MAX_COLOR_SLOTS];
+ const uint32_t *cols;
};
-// exposed global array of default modes
-extern const default_mode_entry default_modes[MAX_MODES];
+struct DefaultModeEntry
+{
+ DefaultLedEntry leds[LED_COUNT];
+};
-// exposed size of the default modes array
-extern const uint8_t num_default_modes;
+// exposed global array of default modes
+extern const DefaultModeEntry defaultModes[MAX_MODES];
#endif
diff --git a/VortexEngine/src/Modes/Mode.cpp b/VortexEngine/src/Modes/Mode.cpp
index 3d129d18b8..9a0447e228 100644
--- a/VortexEngine/src/Modes/Mode.cpp
+++ b/VortexEngine/src/Modes/Mode.cpp
@@ -13,6 +13,16 @@
#include "../Leds/Leds.h"
#include "../Log/Log.h"
+
+Mode::Mode(const DefaultModeEntry &entry) :
+ Mode()
+{
+ for (LedPos pos = LED_FIRST; pos < LED_COUNT; ++pos) {
+ Colorset set(entry.leds[pos].numColors, entry.leds[pos].cols);
+ setPattern(entry.leds[pos].patternID, pos, nullptr, &set);
+ }
+}
+
#if FIXED_LED_COUNT == 0
// for internal reference to the led count
#define MODE_LEDCOUNT m_numLeds
@@ -296,17 +306,7 @@ bool Mode::unserialize(ByteStream &buffer)
// if there is a multi led pattern then unserialize it
if (flags & MODE_FLAG_MULTI_LED) {
#if VORTEX_SLIM == 1
- // unserialize the multi pattern
- Pattern *multiPat = PatternBuilder::unserialize(buffer);
- // if there are no single leds then discard the firstpat
- if ((flags & MODE_FLAG_SINGLE_LED) != 0 && multiPat) {
- // discard the multi pattern
- delete multiPat;
- } else {
- // otherwise turn on the all same single flag to use the multi as a single
- flags = MODE_FLAG_SINGLE_LED | MODE_FLAG_ALL_SAME_SINGLE;
- firstPat = multiPat;
- }
+ return false;
#else
// otherwise in normal build actually unserialize it
m_multiPat = PatternBuilder::unserialize(buffer);
@@ -323,25 +323,25 @@ bool Mode::unserialize(ByteStream &buffer)
// is there an led map to unserialize? if not default to all
LedMap map = (1 << ledCount) - 1;
if (flags & MODE_FLAG_SPARSE_SINGLES) {
- if (!buffer.unserialize32((uint32_t *)&map)) {
- return false;
- }
+ return false;
}
// unserialize all singleled patterns into their positions
MAP_FOREACH_LED(map) {
if (pos >= LED_COUNT) {
// in case the map encodes led positions this device doesn't support
- break;
+ continue;
}
- if (!firstPat) {
- // save the first pattern so that it can be duped if this is 'all same'
+ //if (!firstPat) {
+ // save the first pattern so that it can be duped if this is 'all same'
+ if (pos == LED_FIRST || (flags & MODE_FLAG_ALL_SAME_SINGLE) == 0) {
m_singlePats[pos] = firstPat = PatternBuilder::unserialize(buffer);
- } else if (flags & MODE_FLAG_ALL_SAME_SINGLE) {
- // if all same then just dupe first
- m_singlePats[pos] = PatternBuilder::dupe(firstPat);
} else {
- // otherwise unserialize the pattern like normal
- m_singlePats[pos] = PatternBuilder::unserialize(buffer);
+ // if all same then just dupe first
+ m_singlePats[pos] = PatternBuilder::dupe(m_singlePats[LED_FIRST]);
+ }
+ if (!m_singlePats[pos]) {
+ clearPattern(LED_ALL);
+ return false;
}
if (!m_singlePats[pos]) {
clearPattern(LED_ALL);
@@ -349,6 +349,7 @@ bool Mode::unserialize(ByteStream &buffer)
}
m_singlePats[pos]->bind(pos);
}
+
// there is a few different possibilities here:
// 1. The provided ledCount is less than our current LED_COUNT
// -> if this happens we need to repeat the first ledCount leds
@@ -369,7 +370,11 @@ bool Mode::unserialize(ByteStream &buffer)
// around at ledCount so that we repeat the first ledCount over again
for (LedPos pos = (LedPos)ledCount; pos < LED_COUNT; ++pos) {
m_singlePats[pos] = PatternBuilder::dupe(m_singlePats[src]);
+ if (!m_singlePats[pos]) {
+ return false;
+ }
m_singlePats[pos]->bind(pos);
+ // have to modulate the source by the source mode's led count
src = (LedPos)((src + 1) % ledCount);
}
return true;
@@ -593,6 +598,26 @@ bool Mode::setPatternMap(LedMap map, PatternID pat, const PatternArgs *args, con
return true;
}
+void Mode::copyPatternFrom(const Mode *other, LedPos to, LedPos from)
+{
+ if (to >= LED_COUNT || from >= LED_COUNT) {
+ return;
+ }
+ delete m_singlePats[to];
+ m_singlePats[to] = PatternBuilder::dupe(other->m_singlePats[from]);
+}
+
+
+void Mode::swapPatterns(LedPos a, LedPos b)
+{
+ if (a >= LED_COUNT || b >= LED_COUNT) {
+ return;
+ }
+ Pattern *temp = m_singlePats[a];
+ m_singlePats[a] = m_singlePats[b];
+ m_singlePats[b] = temp;
+}
+
// set colorset at a specific position
bool Mode::setColorset(const Colorset &set, LedPos pos)
{
@@ -880,6 +905,48 @@ void Mode::test()
}
INFO_LOG("Test setPatternAt passed.\n");
+ // Test setPattern with multi-led pattern
+ Mode modeMultiLedTest;
+ PatternID multiPatternID = PATTERN_MULTI_FIRST;
+ PatternArgs multiPatternArgs = { 200, 200, 200, 5 };
+ Colorset multiColorset(255, 0, 0);
+
+ modeMultiLedTest.setPattern(multiPatternID, &multiPatternArgs, &multiColorset);
+
+ // Check if the multi-led pattern and colorset were set correctly
+ assert(modeMultiLedTest.getPatternID() == multiPatternID);
+ assert(modeMultiLedTest.getColorset()->equals(&multiColorset));
+ INFO_LOG("Test setPattern with multi-led pattern passed.\n");
+
+ // Test setPattern with single-led pattern and multi-led pattern simultaneously
+ Mode modeBothTest;
+ PatternID singlePatternID = PATTERN_SOLID;
+ PatternArgs singlePatternArgs = { 100, 255, 0, 10 };
+ Colorset singleColorset(0, 0, 255);
+
+ modeBothTest.setPattern(singlePatternID, &singlePatternArgs, &singleColorset);
+ modeBothTest.setPattern(multiPatternID, &multiPatternArgs, &multiColorset);
+
+ // Check if the single-led and multi-led patterns coexist
+ assert(modeBothTest.hasMultiLed() && modeBothTest.hasSingleLed());
+ INFO_LOG("Test setPattern with single-led and multi-led patterns simultaneously passed.\n");
+
+ // Test setPatternAt
+ Mode modeSetPatternAtTest;
+ LedPos setPatternAtPosition = LED_5;
+ modeSetPatternAtTest.setPatternAt(setPatternAtPosition, testPatternID, &testPatternArgs, &testColorset);
+
+ // Check if the pattern and colorset were set correctly at the specified position
+ for (LedPos pos = LED_FIRST; pos < LED_COUNT; ++pos) {
+ if (pos == setPatternAtPosition) {
+ assert(modeSetPatternAtTest.getPatternIDAt(pos) == testPatternID);
+ assert(modeSetPatternAtTest.getColorsetAt(pos)->equals(&testColorset));
+ } else {
+ assert(modeSetPatternAtTest.getPatternIDAt(pos) == PATTERN_NONE);
+ }
+ }
+ INFO_LOG("Test setPatternAt passed.\n");
+
// Test setColorsetAt
Colorset newColorset(RGB_YELLOW, RGB_ORANGE, RGB_GREEN);
LedPos setColorsetPosition = LED_3;
@@ -927,6 +994,18 @@ void Mode::test()
modeClearColorsetTest.setPattern(testPatternID, &testPatternArgs, &testColorset);
modeClearColorsetTest.clearColorsets();
+ // Test clearPattern with multi-led pattern
+ Mode modeClearMultiLedTest;
+ modeClearMultiLedTest.setPattern(multiPatternID, &multiPatternArgs, &multiColorset);
+ modeClearMultiLedTest.clearPatterns();
+
+ assert(modeClearMultiLedTest.getPatternID() == PATTERN_NONE);
+ INFO_LOG("Test clearPattern with multi-led pattern passed.\n");
+
+ // Test clearPattern with both single-led and multi-led patterns
+ modeBothTest.clearPatterns();
+
+ assert(modeBothTest.getPatternID() == PATTERN_NONE);
for (LedPos pos = LED_FIRST; pos < LED_COUNT; ++pos) {
assert(modeClearColorsetTest.getColorsetAt(pos)->numColors() == 0);
}
diff --git a/VortexEngine/src/Modes/Mode.h b/VortexEngine/src/Modes/Mode.h
index e364483863..3c0c574b6d 100644
--- a/VortexEngine/src/Modes/Mode.h
+++ b/VortexEngine/src/Modes/Mode.h
@@ -4,6 +4,7 @@
#include "../Leds/LedTypes.h"
#include "../Patterns/Patterns.h"
#include "../VortexConfig.h"
+#include "DefaultModes.h"
class SingleLedPattern;
class MultiLedPattern;
@@ -42,6 +43,7 @@ class Mode
#endif
Mode();
+ Mode(const DefaultModeEntry &entry);
Mode(PatternID id, const Colorset &set);
Mode(PatternID id, const PatternArgs &args, const Colorset &set);
Mode(PatternID id, const PatternArgs *args, const Colorset *set);
@@ -95,6 +97,12 @@ class Mode
bool setPattern(PatternID pat, LedPos pos = LED_ANY, const PatternArgs *args = nullptr, const Colorset *set = nullptr);
bool setPatternMap(LedMap pos, PatternID pat, const PatternArgs *args = nullptr, const Colorset *set = nullptr);
+ // copy a pattern from another mode ledpos into this mode
+ void copyPatternFrom(const Mode *other, LedPos to, LedPos from);
+
+ // swap two patterns
+ void swapPatterns(LedPos a, LedPos b);
+
// set colorset at a specific position
bool setColorset(const Colorset &set, LedPos pos = LED_ANY);
// set colorset at each position in a map
diff --git a/VortexEngine/src/Modes/Modes.cpp b/VortexEngine/src/Modes/Modes.cpp
index a81a6d72db..b411fcfde5 100644
--- a/VortexEngine/src/Modes/Modes.cpp
+++ b/VortexEngine/src/Modes/Modes.cpp
@@ -81,8 +81,16 @@ void Modes::play()
DEBUG_LOG("Error failed to load any modes!");
return;
}
- // shortclick cycles to the next mode
+ // shortclick either turns off the lights, cycles to the next mode
+ // or possibly locks the lights based on the situation
if (g_pButton->onShortClick()) {
+ if (Modes::oneClickModeEnabled()) {
+ // enter sleep doesn't return on arduino, but it does on vortexlib
+ // so we need to return right after -- we can't just use an else
+ // don't need to save when switching on/off in one click mode
+ VortexEngine::enterSleep(false);
+ return;
+ }
nextMode();
}
// play the current mode
@@ -92,7 +100,6 @@ void Modes::play()
// full save/load to/from buffer
bool Modes::saveToBuffer(ByteStream &modesBuffer)
{
- // first write out the header
if (!serializeSaveHeader(modesBuffer)) {
return false;
}
@@ -114,7 +121,6 @@ bool Modes::loadFromBuffer(ByteStream &modesBuffer)
// failed to decompress?
return false;
}
- // read out the header first
if (!unserializeSaveHeader(modesBuffer)) {
return false;
}
@@ -135,10 +141,20 @@ bool Modes::saveHeader()
if (!serializeSaveHeader(headerBuffer)) {
return false;
}
- // serialize the number of modes
+ // the number of modes
if (!headerBuffer.serialize8(m_numModes)) {
return false;
}
+ // only on the duo just save some extra stuff to the header slot
+#ifdef VORTEX_EMBEDDED
+ // Duo also saves the build number to the save header so the chromalink can
+ // read it out, other devices just have the version hardcoded into their
+ // editor connection hello message. Don't do this in VortexLib because
+ // it will alter the savefile format and break compatibility
+ if (!headerBuffer.serialize8((uint8_t)VORTEX_BUILD_NUMBER)) {
+ return false;
+ }
+#endif
if (!Storage::write(0, headerBuffer)) {
return false;
}
@@ -260,8 +276,6 @@ bool Modes::serializeSaveHeader(ByteStream &saveBuffer)
if (!VortexEngine::serializeVersion(saveBuffer)) {
return false;
}
- // NOTE: instead of global brightness the duo uses this to store the
- // startup mode ID. The duo doesn't offer a global brightness option
if (!saveBuffer.serialize8(m_globalFlags)) {
return false;
}
@@ -296,9 +310,6 @@ bool Modes::unserializeSaveHeader(ByteStream &saveHeader)
ERROR_LOGF("Incompatible savefile version: %u.%u", major, minor);
return false;
}
- // NOTE: instead of global brightness the duo uses this to store the
- // startup mode ID. The duo doesn't offer a global brightness option
- // unserialize the global brightness
if (!saveHeader.unserialize8(&m_globalFlags)) {
return false;
}
@@ -388,10 +399,12 @@ bool Modes::setDefaults()
{
clearModes();
// add each default mode with each of the given colors
- for (uint8_t i = 0; i < num_default_modes; ++i) {
- const default_mode_entry &def = default_modes[i];
- Colorset set(def.numColors, def.cols);
- addMode(def.patternID, nullptr, &set);
+ for (uint8_t i = 0; i < MAX_MODES; ++i) {
+ Mode defMode(defaultModes[i]);
+ if (!addMode(&defMode)) {
+ ERROR_LOGF("Failed to add default mode %u", i);
+ return false;
+ }
}
return true;
}
diff --git a/VortexEngine/src/Serial/ByteStream.h b/VortexEngine/src/Serial/ByteStream.h
index 937901f570..ec5fa17486 100644
--- a/VortexEngine/src/Serial/ByteStream.h
+++ b/VortexEngine/src/Serial/ByteStream.h
@@ -175,9 +175,9 @@ class ByteStream
// The raw buffer of data along with size and flags
RawBuffer *m_pData;
// the index in the raw buffer for unserialization
- uint32_t m_position;
+ uint16_t m_position;
// the actual size of the buffer raw buffer
- uint32_t m_capacity;
+ uint16_t m_capacity;
};
#endif
diff --git a/VortexEngine/src/Storage/Storage.cpp b/VortexEngine/src/Storage/Storage.cpp
index 357cc6bd3c..2ca73294cc 100644
--- a/VortexEngine/src/Storage/Storage.cpp
+++ b/VortexEngine/src/Storage/Storage.cpp
@@ -8,6 +8,13 @@
#include "../Serial/ByteStream.h"
#include "../Log/Log.h"
+#ifdef VORTEX_EMBEDDED
+#include
+#include
+#include
+#include
+#endif
+
#ifdef VORTEX_LIB
#include "../VortexLib/VortexLib.h"
#endif
@@ -17,6 +24,7 @@
#include
#else
#include
+#include
#endif
#endif
@@ -29,6 +37,21 @@ std::string Storage::m_storageFilename;
#define STORAGE_FILENAME DEFAULT_STORAGE_FILENAME
#endif
+// The first half of the data goes into the eeprom and then the rest goes into
+// flash, the EEPROM is 256 and storage size is 512 so the flash storage is 256
+#define FLASH_STORAGE_SIZE (STORAGE_SIZE)
+// The position of the flash storage is right before the end of the flash, as long
+// as the program leaves 256 bytes of space at the end of flash then this will fit
+#define FLASH_STORAGE_SPACE ((volatile uint8_t *)(0x10000 - FLASH_STORAGE_SIZE))
+// The header is max 15 bytes big, this is decided by the MAX_MODE_SIZE being
+// 76, since there's only 256 bytes in the eeprom that leaves exactly 27 bytes
+// leftover after packing 3x modes. Out of the 27 bytes 12 is the ByteStream
+// header leaving 15 for the max header size. Of the 15 only 7 bytes are really
+// being used and the rest are extra bytes reserved for future use
+#define HEADER_SIZE 15
+// The full header size includes the 12 bytes for the serialbuffer header
+#define STORAGE_HEADER_SIZE (HEADER_SIZE + 12)
+
uint32_t Storage::m_lastSaveSize = 0;
Storage::Storage()
@@ -49,8 +72,11 @@ void Storage::cleanup()
{
}
+#define FLASH_PAGE_SIZE 128
+
+
// store a serial buffer to storage
-bool Storage::write(uint16_t slot, ByteStream &buffer)
+bool Storage::write(uint8_t slot, ByteStream &buffer)
{
#ifdef VORTEX_LIB
if (!Vortex::storageEnabled()) {
@@ -59,6 +85,7 @@ bool Storage::write(uint16_t slot, ByteStream &buffer)
}
#endif
// check size
+ uint16_t size = buffer.rawSize();
if (buffer.rawSize() > MAX_MODE_SIZE) {
ERROR_LOG("Buffer too big for storage space");
return false;
@@ -69,7 +96,61 @@ bool Storage::write(uint16_t slot, ByteStream &buffer)
// just in case
buffer.recalcCRC();
#ifdef VORTEX_EMBEDDED
- // implement device storage here
+ const uint8_t *buf = (const uint8_t *)buffer.rawData();
+ // the header is slot 0 and it gets 17 bytes in the start of the eeprom then
+ // the next 3 modes are 76 bytes each taking up 228 bytes for a total of 245
+ if (slot < 4) {
+ uint8_t eepSlot = 0;
+ if (slot > 0) {
+ eepSlot = STORAGE_HEADER_SIZE + (MAX_MODE_SIZE * (slot - 1));
+ }
+ // header eeprom (12 bytes of 256)
+ // 3 modes eeprom (76 x 3 bytes of 244)
+ uint8_t slotSize = (slot == 0) ? STORAGE_HEADER_SIZE : MAX_MODE_SIZE;
+ for (uint8_t i = 0; i < slotSize; ++i) {
+ uint8_t b = (i < size) ? buf[i] : 0x00;
+ eepromWriteByte(eepSlot + i, b);
+ }
+ } else {
+ // Then flash storage is 0x200 or 512 bytes which allows for 6 x 76 byte modes
+ // the base address of the slot we are writing
+ uint16_t slotAddr = (uint16_t)FLASH_STORAGE_SPACE + (MAX_MODE_SIZE * (slot - 4));
+ // The storage slot may lay across a page boundary which means potentially writing
+ // two pages instead of just one. In order to update only part of a page, the page
+ // buffer must be filled with both the previous content along with the new data.
+ // For example, imagine 2 pages of data: |xxxxxxSSSS|SSSxxxxxxx| the x's are other
+ // data that must be preserved, and the S's denote the storage slot being written.
+ // This would take place over two iterations of the loop, each writing out one page
+ // by read-then-writing-back the x's and writing out the new S's. This is necessary
+ // because the page buffer must be filled to perform a page write, at least I think
+ while (size > 0) {
+ uint16_t pageStart = slotAddr & ~(FLASH_PAGE_SIZE - 1);
+ uint16_t offset = slotAddr % FLASH_PAGE_SIZE;
+ uint16_t space = FLASH_PAGE_SIZE - offset;
+ uint16_t writeSize = (size < space) ? size : space;
+
+ for (uint8_t i = 0; i < FLASH_PAGE_SIZE; ++i) {
+ uint8_t value;
+ if (i >= offset && i < offset + writeSize) {
+ // if this is within the slot then write out the new data
+ value = buf[i - offset];
+ } else {
+ // otherwise just write-back the same value to fill the pagebuffer
+ value = *(volatile uint8_t *)(pageStart + i);
+ }
+ *(volatile uint8_t *)(pageStart + i) = value;
+ }
+
+ // Erase and write the flash page
+ _PROTECTED_WRITE_SPM(NVMCTRL.CTRLA, NVMCTRL_CMD_PAGEERASEWRITE_gc);
+ while (NVMCTRL.STATUS & (NVMCTRL_FBUSY_bm | NVMCTRL_EEBUSY_bm));
+
+ // continue to the next page
+ slotAddr += writeSize;
+ buf += writeSize;
+ size -= writeSize;
+ }
+ }
#elif defined(_WIN32)
HANDLE hFile = CreateFile(STORAGE_FILENAME, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
@@ -79,7 +160,11 @@ bool Storage::write(uint16_t slot, ByteStream &buffer)
DWORD written = 0;
DWORD offset = slot * MAX_MODE_SIZE;
SetFilePointer(hFile, offset, NULL, FILE_BEGIN);
- if (!WriteFile(hFile, buffer.rawData(), MAX_MODE_SIZE, &written, NULL)) {
+ uint8_t modeBuffer[MAX_MODE_SIZE] = {0};
+ // copy the mode data into a temp buffer
+ memcpy(modeBuffer, buffer.rawData(), size);
+ // then copy the full size of the temp buffer in
+ if (!WriteFile(hFile, modeBuffer, MAX_MODE_SIZE, &written, NULL)) {
// error
return false;
}
@@ -91,7 +176,10 @@ bool Storage::write(uint16_t slot, ByteStream &buffer)
}
long offset = slot * MAX_MODE_SIZE;
fseek(f, offset, SEEK_SET);
- if (!fwrite(buffer.rawData(), sizeof(char), MAX_MODE_SIZE, f)) {
+ uint8_t modeBuffer[MAX_MODE_SIZE] = {0};
+ // copy the mode data into a temp buffer
+ memcpy(modeBuffer, buffer.rawData(), size);
+ if (!fwrite(modeBuffer, sizeof(char), MAX_MODE_SIZE, f)) {
return false;
}
fclose(f);
@@ -101,7 +189,7 @@ bool Storage::write(uint16_t slot, ByteStream &buffer)
}
// read a serial buffer from storage
-bool Storage::read(uint16_t slot, ByteStream &buffer)
+bool Storage::read(uint8_t slot, ByteStream &buffer)
{
#ifdef VORTEX_LIB
if (!Vortex::storageEnabled()) {
@@ -118,7 +206,19 @@ bool Storage::read(uint16_t slot, ByteStream &buffer)
return false;
}
#ifdef VORTEX_EMBEDDED
- // implement device storage here
+ uint8_t *buf = (uint8_t *)buffer.rawData();
+ volatile uint8_t *src;
+ if (slot == 0) { // slot 0 is header eeprom
+ src = (volatile uint8_t *)MAPPED_EEPROM_START;
+ size = STORAGE_HEADER_SIZE;
+ } else if (slot < 4) { // slots 1-3 are eeprom
+ src = (volatile uint8_t *)MAPPED_EEPROM_START + STORAGE_HEADER_SIZE + (MAX_MODE_SIZE * (slot - 1));
+ } else { // slots 4-9 are flash
+ src = (volatile uint8_t *)FLASH_STORAGE_SPACE + (MAX_MODE_SIZE * (slot - 4));
+ }
+ for (uint8_t i = 0; i < size; ++i) {
+ buf[i] = src[i];
+ }
#elif defined(_WIN32)
HANDLE hFile = CreateFile(STORAGE_FILENAME, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
@@ -164,3 +264,37 @@ uint32_t Storage::lastSaveSize()
{
return m_lastSaveSize;
}
+
+#ifdef VORTEX_EMBEDDED
+// write out the eeprom byte
+void Storage::eepromWriteByte(uint16_t index, uint8_t in)
+{
+ if (eepromReadByte(index) == in) {
+ return;
+ }
+ // The first two pages of the data goes into the eeprom and then the last page goes
+ // into the USERROW which is located at 0x1300
+ uint16_t adr = MAPPED_EEPROM_START + (index);
+ __asm__ __volatile__(
+ "ldi r30, 0x00" "\n\t"
+ "ldi r31, 0x10" "\n\t"
+ "ldd r18, Z+2" "\n\t"
+ "andi r18, 3" "\n\t"
+ "brne .-6" "\n\t"
+ "st X, %0" "\n\t"
+ "ldi %0, 0x9D" "\n\t"
+ "out 0x34, %0" "\n\t"
+ "ldi %0, 0x03" "\n\t"
+ "st Z, %0" "\n\t"
+ :"+d"(in)
+ : "x"(adr)
+ : "r30", "r31", "r18");
+
+ while (!(NVMCTRL.STATUS & NVMCTRL_EEBUSY_bm));
+}
+
+uint8_t Storage::eepromReadByte(uint16_t index)
+{
+ return *(volatile uint8_t *)(MAPPED_EEPROM_START + index);
+}
+#endif
diff --git a/VortexEngine/src/Storage/Storage.h b/VortexEngine/src/Storage/Storage.h
index 19f0999f54..80da3edcb9 100644
--- a/VortexEngine/src/Storage/Storage.h
+++ b/VortexEngine/src/Storage/Storage.h
@@ -19,9 +19,9 @@ class Storage
static void cleanup();
// store a serial buffer to storage
- static bool write(uint16_t slot, ByteStream &buffer);
+ static bool write(uint8_t slot, ByteStream &buffer);
// read a serial buffer from storage
- static bool read(uint16_t slot, ByteStream &buffer);
+ static bool read(uint8_t slot, ByteStream &buffer);
// the last save size (use STORAGE_SIZE For total space)
static uint32_t lastSaveSize();
@@ -32,6 +32,10 @@ class Storage
#endif
private:
+ // write out a single byte to the eeprom
+ static void eepromWriteByte(uint16_t index, uint8_t in);
+ static uint8_t eepromReadByte(uint16_t index);
+
#ifdef VORTEX_LIB
static std::string m_storageFilename;
#endif
diff --git a/VortexEngine/src/Time/TimeControl.cpp b/VortexEngine/src/Time/TimeControl.cpp
index 8493deb667..76afc8c816 100644
--- a/VortexEngine/src/Time/TimeControl.cpp
+++ b/VortexEngine/src/Time/TimeControl.cpp
@@ -9,6 +9,11 @@
#include "../Leds/Leds.h"
+#ifdef VORTEX_EMBEDDED
+#include
+#include
+#endif
+
#if !defined(_WIN32) || defined(WASM)
#include
#include
@@ -27,9 +32,9 @@ static LARGE_INTEGER start;
uint32_t Time::m_tickrate = DEFAULT_TICKRATE;
#endif
uint32_t Time::m_curTick = 0;
+#ifdef VORTEX_LIB
uint32_t Time::m_prevTime = 0;
uint32_t Time::m_firstTime = 0;
-#ifdef VORTEX_LIB
uint32_t Time::m_simulationTick = 0;
bool Time::m_isSimulation = false;
bool Time::m_instantTimestep = false;
@@ -45,12 +50,27 @@ bool Time::m_instantTimestep = false;
bool Time::init()
{
- m_firstTime = m_prevTime = microseconds();
+#ifdef VORTEX_EMBEDDED
+ // initialize main clock
+#if (F_CPU == 20000000)
+ // No division on clock
+ _PROTECTED_WRITE(CLKCTRL_MCLKCTRLB, 0x00);
+#elif (F_CPU == 10000000)
+ // 20MHz prescaled by 2, Clock DIV2
+ _PROTECTED_WRITE(CLKCTRL_MCLKCTRLB, (CLKCTRL_PEN_bm | CLKCTRL_PDIV_2X_gc));
+#else
+ #error "F_CPU not supported"
+#endif
+ // IVSEL = 1 means Interrupt vectors are placed at the start of the boot section of the Flash
+ // as opposed to the application section of Flash. See 13.5.1
+ _PROTECTED_WRITE(CPUINT_CTRLA, CPUINT_IVSEL_bm);
+#endif
m_curTick = 0;
#if VARIABLE_TICKRATE == 1
m_tickrate = DEFAULT_TICKRATE;
#endif
#ifdef VORTEX_LIB
+ m_firstTime = m_prevTime = microseconds();
m_simulationTick = 0;
m_isSimulation = false;
m_instantTimestep = false;
@@ -79,11 +99,12 @@ void Time::tickClock()
}
#endif
+ // the rest of this only runs inside vortexlib because on the duo the tick runs in the
+ // tcb timer callback instead of in a busy loop constantly checking microseconds()
#ifdef VORTEX_LIB
if (m_instantTimestep) {
return;
}
-#endif
// perform timestep
uint32_t elapsed_us;
@@ -119,6 +140,7 @@ void Time::tickClock()
// store current time
m_prevTime = microseconds();
+#endif
}
// the real current time, bypass simulations, used by timers
@@ -170,8 +192,10 @@ uint32_t Time::millisecondsToTicks(uint32_t ms)
uint32_t Time::microseconds()
{
#ifndef VORTEX_LIB // Embedded avr devices
- // arduino micros, or whatever micro implementation you have chosen for the embedded device
- return micros();
+ uint32_t ticks;
+ // divide by 10
+ ticks = (m_curTick * DEFAULT_TICKRATE) + (TCB0.CNT / 1000);
+ return ticks;
#elif defined(_WIN32) // windows
LARGE_INTEGER now;
QueryPerformanceCounter(&now);
@@ -188,9 +212,42 @@ uint32_t Time::microseconds()
#endif
}
+#ifdef VORTEX_EMBEDDED
+__attribute__ ((noinline))
+#endif
void Time::delayMicroseconds(uint32_t us)
{
-#ifdef _WIN32
+#ifdef VORTEX_EMBEDDED
+#if F_CPU >= 20000000L
+ // for a one-microsecond delay, burn 4 clocks and then return
+ __asm__ __volatile__ (
+ "rjmp .+0" "\n\t" // 2 cycles
+ "nop" ); // 1 cycle
+ // wait 3 cycles with 2 words
+ if (us <= 1) return; // = 3 cycles, (4 when true)
+ // the loop takes a 1/2 of a microsecond (10 cycles) per iteration
+ // so execute it twice for each microsecond of delay requested.
+ us = us << 1; // x2 us, = 2 cycles
+ // we just burned 21 (23) cycles above, remove 2
+ // us is at least 4 so we can subtract 2.
+ us -= 2; // 2 cycles
+#elif F_CPU >= 10000000L
+ // for a 1 microsecond delay, simply return. the overhead
+ // of the function call takes 14 (16) cycles, which is 1.5us
+ if (us <= 2) return; // = 3 cycles, (4 when true)
+ // we just burned 20 (22) cycles above, remove 4, (5*4=20)
+ // us is at least 6 so we can subtract 4
+ us -= 4; // 2 cycles
+#endif
+ __asm__ __volatile__(
+ "1: sbiw %0, 1" "\n\t" // 2 cycles
+ "rjmp .+0" "\n\t" // 2 cycles
+ "rjmp .+0" "\n\t" // 2 cycles
+ "rjmp .+0" "\n\t" // 2 cycles
+ "brne 1b" : "=w" (us) : "0" (us) // 2 cycles
+ );
+ // return = 4 cycles
+#elif defined(_WIN32)
uint32_t newtime = microseconds() + us;
while (microseconds() < newtime) {
// busy loop
diff --git a/VortexEngine/src/Time/TimeControl.h b/VortexEngine/src/Time/TimeControl.h
index 3a23ddd068..7c7a061984 100644
--- a/VortexEngine/src/Time/TimeControl.h
+++ b/VortexEngine/src/Time/TimeControl.h
@@ -105,6 +105,9 @@ class Time
#endif
private:
+#ifdef VORTEX_EMBEDDED
+ static void initMCUTime();
+#endif
#if VARIABLE_TICKRATE == 1
// the number of ticks per second
@@ -114,13 +117,13 @@ class Time
// global tick counter
static uint32_t m_curTick;
+#ifdef VORTEX_LIB
// the last frame timestamp
static uint32_t m_prevTime;
// the first timestamp
static uint32_t m_firstTime;
-#ifdef VORTEX_LIB
// the current simulation offset, simulations are
// used to fastforward patterns and colorsets by
// simulating tick changes and running pattern logic
diff --git a/VortexEngine/src/Time/Timings.h b/VortexEngine/src/Time/Timings.h
index 647ef2ca78..c3391b41f1 100644
--- a/VortexEngine/src/Time/Timings.h
+++ b/VortexEngine/src/Time/Timings.h
@@ -13,6 +13,11 @@
#define SHORT_CLICK_THRESHOLD_TICKS MS_TO_TICKS(CLICK_THRESHOLD)
#define CONSECUTIVE_WINDOW_TICKS MS_TO_TICKS(CONSECUTIVE_WINDOW)
#define AUTO_RANDOM_DELAY_TICKS MS_TO_TICKS(AUTO_RANDOM_DELAY)
+#define UNLOCK_WAKE_WINDOW_TICKS MS_TO_TICKS(UNLOCK_WAKE_WINDOW)
+#define SLEEP_ENTER_THRESHOLD_TICKS MS_TO_TICKS(SLEEP_TRIGGER_TIME)
+#define SLEEP_WINDOW_THRESHOLD_TICKS MS_TO_TICKS(SLEEP_WINDOW_TIME)
+#define FORCE_SLEEP_THRESHOLD_TICKS MS_TO_TICKS(FORCE_SLEEP_TIME)
+#define ONE_CLICK_THRESHOLD_TICKS MS_TO_TICKS(ONE_CLICK_MODE_TRHESHOLD)
#define DELETE_THRESHOLD_TICKS MS_TO_TICKS(COL_DELETE_THRESHOLD)
#define DELETE_CYCLE_TICKS MS_TO_TICKS(COL_DELETE_CYCLE)
#define FACTORY_RESET_THRESHOLD_TICKS MS_TO_TICKS(RESET_HOLD_TIME)
diff --git a/VortexEngine/src/VortexConfig.h b/VortexEngine/src/VortexConfig.h
index dc47583a5c..7b12d5a843 100644
--- a/VortexEngine/src/VortexConfig.h
+++ b/VortexEngine/src/VortexConfig.h
@@ -38,7 +38,7 @@
// the engine flavour, this should change for each device/flavour
// of the engine that branches off from the main indefinitely
-#define VORTEX_NAME "Core"
+#define VORTEX_NAME "Duo"
// the full name of this build for ex:
// Vortex Engine v1.0 'Igneous' (built Tue Jan 31 19:03:55 2023)
@@ -50,7 +50,7 @@
// This disabled multi-led patterns and compression because the
// multi-led patterns take up too much space and the compression
// uses too much stack space to run on smaller devices
-#define VORTEX_SLIM 0
+#define VORTEX_SLIM 1
// ===================================================================
// Numeric Configurations
@@ -59,7 +59,7 @@
//
// How long the button must be held to trigger menu selection and
// begin blinking the first menu color
-#define MENU_TRIGGER_TIME 1000
+#define MENU_TRIGGER_TIME 500
// Short Click Threshold (in milliseconds)
//
@@ -68,6 +68,27 @@
// it will be registered as a 'long click'
#define CLICK_THRESHOLD 250
+// Sleep Enter Threshold (in milliseconds)
+//
+// How long the button has to be held at the main mode area to enter
+// sleep mode
+#define SLEEP_TRIGGER_TIME 500
+
+// Sleep Window Time (in milliseconds)
+//
+// How long the user has to release the button to enter sleep
+#define SLEEP_WINDOW_TIME 750
+
+// Force Sleep Time (in milliseconds)
+//
+// How long the user has to hold the button anywhere to force sleep
+#define FORCE_SLEEP_TIME 6000
+
+// One-click mode threshold
+//
+// How long the user has to hold the button to trigger one-click mode
+#define ONE_CLICK_MODE_TRHESHOLD 500
+
// Device Lock Clicks
//
// How many rapid clicks the user must perform to lock/unlock the device.
@@ -119,7 +140,7 @@
// Leave Advanced Color Select Clicks
//
// The number of consecutive clicks required to exit the advanced color select menu
-#define LEAVE_ADV_COL_SELECT_CLICKS 5
+#define LEAVE_ADV_COL_SELECT_CLICKS 8
// Color delete threshold (in milliseconds)
//
@@ -158,7 +179,7 @@
//
// The starting default global brightness if there is no savefile
// present The maximum value is 255
-#define DEFAULT_BRIGHTNESS 185
+#define DEFAULT_BRIGHTNESS 255
// Max Modes
//
@@ -176,7 +197,7 @@
// This should not be set to 0, it should be a specific maximum for
// each separate device
//
-#define MAX_MODES 13
+#define MAX_MODES 9
// Default Tickrate in Ticks Per Second (TPS)
//
@@ -361,7 +382,7 @@
// Turn on the editor connection, some devices are capable of connecting to
// the pc-based editor, this controls whether the engine exposes the purple
// editor connection menu or not
-#define ENABLE_EDITOR_CONNECTION 1
+#define ENABLE_EDITOR_CONNECTION 0
// Compression Test
//
@@ -536,14 +557,14 @@
// These are the various storage space constants of the vortex device
// maximum size of a mode here
-#define MAX_MODE_SIZE 512
+#define MAX_MODE_SIZE 76
// the number of storage slots for modes, add 1 for the header
#define NUM_MODE_SLOTS (MAX_MODES + 1)
-// the space available for storing modes, we can't make this too big
-// otherwise we will have trouble loading it into memory
-#define STORAGE_SIZE (MAX_MODE_SIZE * NUM_MODE_SLOTS)
+// the space available for storing modes, this is a bit inaccurate on the duo
+// because the eeprom is also used but that isn't reflected here
+#define STORAGE_SIZE 0x200
// ===================================================================
// Test Framework configurations
diff --git a/VortexEngine/src/VortexEngine.cpp b/VortexEngine/src/VortexEngine.cpp
index 087931c7d9..28e7af62cf 100644
--- a/VortexEngine/src/VortexEngine.cpp
+++ b/VortexEngine/src/VortexEngine.cpp
@@ -15,7 +15,11 @@
#include "Menus/Menus.h"
#include "Modes/Mode.h"
#include "Leds/Leds.h"
-#include "Log/Log.h"
+
+#ifdef VORTEX_EMBEDDED
+#include
+#include
+#endif
#ifdef VORTEX_LIB
#include "VortexLib.h"
@@ -23,12 +27,19 @@
// bool in vortexlib to simulate sleeping
volatile bool VortexEngine::m_sleeping = false;
+// whether the 'force sleep' option is available (hold down for long time)
+bool VortexEngine::m_forceSleepEnabled = true;
// auto cycling
bool VortexEngine::m_autoCycle = false;
bool VortexEngine::init()
{
+#ifdef VORTEX_EMBEDDED
+ // clear the output pins to initialize everything
+ clearOutputPins();
+#endif
+
// all of the global controllers
if (!SerialComs::init()) {
DEBUG_LOG("Serial failed to initialize");
@@ -95,6 +106,24 @@ bool VortexEngine::init()
timerTest();
#endif
+#ifdef VORTEX_EMBEDDED
+ // setup TCB0 to track micros() and run ticks
+ TCB0.CCMP = 10000;
+ TCB0.INTCTRL = TCB_CAPT_bm;
+ // Set clock source to CPU divided by 1, Keep running in sleep mode, Enable TCB
+ TCB0.CTRLA = TCB_CLKSEL_CLKDIV1_gc | TCB_RUNSTDBY_bm | TCB_ENABLE_bm;
+ // set the state of the mosfet based on whether the chip is locked or not
+ enableMOSFET(!Modes::locked());
+ // setup sleep mode for standby
+ set_sleep_mode(SLEEP_MODE_STANDBY);
+ // enable interrupts
+ sei();
+ // standby indefinitely while the ISR runs VortexEngine::tick
+ while (!m_sleeping) {
+ sleep_mode();
+ }
+#endif
+
return true;
}
@@ -167,26 +196,147 @@ void VortexEngine::tick()
Leds::update();
}
+// warning this function is quite heavy at this point, it could probably be split
void VortexEngine::runMainLogic()
{
// the current tick
uint32_t now = Time::getCurtime();
- // load modes if necessary
+ // if the device is locked then that takes priority over all, while locked the
+ // device will only listen for clicks to wakeup momentarily then go back to sleep
+ if (Modes::locked()) {
+ // several fast clicks will unlock the device
+ if (g_pButton->onConsecutivePresses(DEVICE_LOCK_CLICKS - 1)) {
+ // turn off the lock flag and save it to disk
+ Modes::setLocked(false);
+#ifdef VORTEX_EMBEDDED
+ // then enable the mosfet
+ enableMOSFET(true);
+#endif
+ } else if (now > (CONSECUTIVE_WINDOW_TICKS * DEVICE_LOCK_CLICKS)) {
+ // go back to sleep if they don't unlock in time, don't save
+ enterSleep(false);
+ }
+ // OPTIONAL: render a dim led during unlock window waiting for clicks?
+ //Leds::setIndex(LED_1, RGB_RED4);
+ Leds::clearAll();
+ // don't do anything else while locked, just return
+ return;
+ }
+
+ // the device is not locked, proceed with regular logic
+
+ // if the button hasn't been released since turning on then there is custom logic
+ if (g_pButton->releaseCount() == 0) {
+ if (!Modes::load()) {
+ return;
+ }
+ // if the button is held for 2 seconds from off, switch to on click mode on
+ // the last mode shown before sleep
+ if (!Modes::keychainModeEnabled() && now == ONE_CLICK_THRESHOLD_TICKS && g_pButton->isPressed()) {
+ // whether oneclick mode is now enabled
+ bool isEnabledNow = !Modes::oneClickModeEnabled();
+ // toggle one click mode
+ Modes::setOneClickMode(isEnabledNow);
+ // if we turned it on then switch to that mode
+ if (isEnabledNow) {
+ Modes::switchToStartupMode();
+ } else {
+ Modes::setCurMode(0);
+ }
+ // flash either low white or dim white2 to indicate
+ // whether one-click mode has been turned on or off
+ Leds::holdAll(isEnabledNow ? RGB_WHITE0 : RGB_WHITE5);
+ }
+ return;
+ }
+
+#ifdef VORTEX_EMBEDDED
+ // ESD PROTECTION!
+ // Sometimes the chip can be turned on via ESD triggering the wakeup pin
+ // if the engine makes it here in less than 2 ticks that means the device turned on
+ // via ESD and not via a normal click which cannot possibly be done in less than 1 tick
+ if (now < 2) {
+ // if that happens then just gracefully go back to sleep to prevent the chip
+ // from turning on randomly in a plastic bag
+ // do not save on ESD re-sleep
+ enterSleep(false);
+ return;
+ }
+#endif
+
if (!Modes::load()) {
- // don't do anything if modes couldn't load
return;
}
- // if the menus are open and running then just return
+ // finally the user has released the button after initially turning it on,
+ // just run the regular main logic of the system
+
+ // re-enter keychain mode if it was never disabled
+ if (Modes::keychainModeEnabled() && !Menus::checkInMenu()) {
+ // switch to the last mode we were on
+ Modes::switchToStartupMode();
+ // enter keychain mode menu
+ Menus::openMenu(MENU_GLOBAL_BRIGHTNESS, true);
+ }
+
+ // first look for the force-sleep and instant on/off toggle
+ const uint32_t holdTime = g_pButton->holdDuration();
+ // force-sleep check takes precedence above all, but it does not run when keychain mode is enabled
+ if (m_forceSleepEnabled && holdTime >= FORCE_SLEEP_THRESHOLD_TICKS) {
+ // as long as they hold down past this threshold just turn off
+ if (g_pButton->isPressed()) {
+ Leds::clearAll();
+ return;
+ }
+ // but as soon as they actually release put the device to sleep
+ if (g_pButton->onRelease()) {
+ // do not save on force sleep
+ enterSleep(false);
+ }
+ return;
+ }
+
+ // run the menus to see if they are open and need to do anything
if (Menus::run()) {
+ // if they return true that means the menus are open and rendering so just return
return;
}
- // check if we should enter the menu
- if (g_pButton->isPressed() && g_pButton->holdDuration() > MENU_TRIGGER_THRESHOLD_TICKS) {
- DEBUG_LOG("Entering Menu Selection...");
- Menus::openMenuSelection();
+ // if the user releases the button after the sleep threshold and
+ // we're still in menu state not open, then we can go to sleep
+ if (g_pButton->onRelease() && holdTime >= SLEEP_ENTER_THRESHOLD_TICKS) {
+ // enter sleep mode
+ enterSleep();
+ return;
+ }
+
+ // if the button is just held beyond the sleep threshold then
+ if (g_pButton->isPressed() && holdTime >= SLEEP_ENTER_THRESHOLD_TICKS) {
+ // clear all the leds for a short moment
+ Leds::clearAll();
+ // then oncethe user holds past the sleep window threshold open up the menus
+ if (holdTime >= (SLEEP_ENTER_THRESHOLD_TICKS + SLEEP_WINDOW_THRESHOLD_TICKS)) {
+ // open the menu selection area
+ DEBUG_LOG("Entering ring fill...");
+ Menus::openMenuSelection();
+ }
+ // don't play the modes because the user is going into menus
+ return;
+ }
+
+ // lastly check if we are locking the device, which can only happen if they click the
+ // button 5 times quickly when the device was off, so 4 times in the first x ticks because
+ // the first click was used to wake the device and isn't counted in the consecutive clicks
+ if (now < (CONSECUTIVE_WINDOW_TICKS * DEVICE_LOCK_CLICKS) && g_pButton->onConsecutivePresses(DEVICE_LOCK_CLICKS - 1)) {
+#ifdef VORTEX_LIB
+ if (!Vortex::lockEnabled()) {
+ return;
+ }
+#endif
+ // lock and just go to sleep, don't need to reset consecutive press counter here
+ Modes::setLocked(true);
+ enterSleep();
return;
}
@@ -261,20 +411,103 @@ void VortexEngine::enterSleep(bool save)
// clear all the leds
Leds::clearAll();
Leds::update();
+#ifdef VORTEX_EMBEDDED
+ // init the output pins to prevent any floating pins
+ clearOutputPins();
+ // close the mosfet so that power cannot flow to the leds
+ enableMOSFET(false);
+ // delay for a bit to let the mosfet close and leds turn off
+ Time::delayMicroseconds(250);
+ // this is an ISR that runs in the timecontrol system to handle
+ // micros, it will wake the device up periodically
+ TCB0.INTCTRL = 0;
+ TCB0.CTRLA = 0;
+ // Enable wake on interrupt for the button
+ g_pButton->enableWake();
+ // Set sleep mode to POWER DOWN mode
+ set_sleep_mode(SLEEP_MODE_PWR_DOWN);
+ // enable the sleep boo lright before we enter sleep, this will allow
+ // the main loop to break and return
+ m_sleeping = true;
+ // enter sleep
+ sleep_mode();
+#else
// enable the sleep bool
m_sleeping = true;
+#endif
}
void VortexEngine::wakeup(bool reset)
{
DEBUG_LOG("Waking up");
- m_sleeping = false;
+#ifdef VORTEX_EMBEDDED
+ // turn the LED mosfet back on
+ enableMOSFET(true);
+ if (reset) {
+ // just reset
+ _PROTECTED_WRITE(RSTCTRL.SWRR, 1);
+ }
+#else
// need to fake the reset in vortexlib, lol this works I guess
+ m_sleeping = false;
if (reset) {
cleanup();
init();
}
+#endif
+}
+
+#ifdef VORTEX_EMBEDDED
+// main tick function
+ISR(TCB0_INT_vect)
+{
+ // Clear interrupt flag
+ TCB0.INTFLAGS = TCB_CAPT_bm;
+ VortexEngine::tick();
+}
+
+void VortexEngine::clearOutputPins()
+{
+ // Set all pins to input with pull-ups
+ PORTA.DIRCLR = 0xFF;
+ PORTA.PIN0CTRL = PORT_ISC_INPUT_DISABLE_gc;
+ PORTA.PIN1CTRL = PORT_ISC_INPUT_DISABLE_gc;
+ PORTA.PIN2CTRL = PORT_ISC_INPUT_DISABLE_gc;
+ PORTA.PIN3CTRL = PORT_ISC_INPUT_DISABLE_gc;
+ PORTA.PIN4CTRL = PORT_ISC_INPUT_DISABLE_gc;
+ PORTA.PIN5CTRL = PORT_ISC_INPUT_DISABLE_gc;
+ PORTA.PIN6CTRL = PORT_ISC_INPUT_DISABLE_gc;
+ PORTA.PIN7CTRL = PORT_ISC_INPUT_DISABLE_gc;
+ PORTB.DIRCLR = 0xFF;
+ PORTB.PIN0CTRL = PORT_ISC_INPUT_DISABLE_gc;
+ PORTB.PIN1CTRL = PORT_ISC_INPUT_DISABLE_gc;
+ PORTB.PIN2CTRL = PORT_ISC_INPUT_DISABLE_gc;
+ PORTB.PIN3CTRL = PORT_ISC_INPUT_DISABLE_gc;
+ PORTB.PIN4CTRL = PORT_ISC_INPUT_DISABLE_gc;
+ PORTB.PIN5CTRL = PORT_ISC_INPUT_DISABLE_gc;
+ PORTB.PIN6CTRL = PORT_ISC_INPUT_DISABLE_gc;
+ PORTB.PIN7CTRL = PORT_ISC_INPUT_DISABLE_gc;
+ PORTC.DIRCLR = 0xFF;
+ PORTC.PIN0CTRL = PORT_ISC_INPUT_DISABLE_gc;
+ PORTC.PIN1CTRL = PORT_ISC_INPUT_DISABLE_gc;
+ PORTC.PIN2CTRL = PORT_ISC_INPUT_DISABLE_gc;
+ PORTC.PIN3CTRL = PORT_ISC_INPUT_DISABLE_gc;
+ PORTC.PIN4CTRL = PORT_ISC_INPUT_DISABLE_gc;
+ PORTC.PIN5CTRL = PORT_ISC_INPUT_DISABLE_gc;
+}
+
+void VortexEngine::enableMOSFET(bool enabled)
+{
+ PORTC.DIRSET |= PIN4_bm;
+ if (enabled) {
+ // Set Mosfet pin (PC4) to output and set it HIGH
+ PORTC.OUTSET |= PIN4_bm;
+ } else {
+ // Set Mosfet pin (PC4) to output and set it LOW
+ PORTC.OUTCLR |= PIN4_bm;
+ }
}
+#endif
#if COMPRESSION_TEST == 1
#include
diff --git a/VortexEngine/src/VortexEngine.h b/VortexEngine/src/VortexEngine.h
index 23ead641d6..ae1b054840 100644
--- a/VortexEngine/src/VortexEngine.h
+++ b/VortexEngine/src/VortexEngine.h
@@ -44,12 +44,22 @@ class VortexEngine
static void enterSleep(bool save = true);
static void wakeup(bool reset = true);
+ // toggle force sleep from working
+ static void toggleForceSleep(bool enabled) { m_forceSleepEnabled = enabled; }
// toggle auto cycle enabled
static void setAutoCycle(bool enabled) { m_autoCycle = enabled; }
+#ifdef VORTEX_EMBEDDED
+ // clear output pins
+ static void clearOutputPins();
+ // enable/disable the mosfet
+ static void enableMOSFET(bool enabled);
+#endif
+
private:
// bool in vortexlib to simulate sleeping
static volatile bool m_sleeping;
+ static bool m_forceSleepEnabled;
// whether auto cycle modes is turned on
static bool m_autoCycle;
diff --git a/VortexEngine/src/Wireless/IRConfig.h b/VortexEngine/src/Wireless/IRConfig.h
index 2a8e68ddf2..a80afee022 100644
--- a/VortexEngine/src/Wireless/IRConfig.h
+++ b/VortexEngine/src/Wireless/IRConfig.h
@@ -5,8 +5,8 @@
//
// Whether to enable the Infrared system as a whole
//
-#define IR_ENABLE_SENDER 1
-#define IR_ENABLE_RECEIVER 1
+#define IR_ENABLE_SENDER 0
+#define IR_ENABLE_RECEIVER 0
// the size of IR blocks in bits
#define IR_DEFAULT_BLOCK_SIZE 32
diff --git a/VortexEngine/src/Wireless/IRSender.cpp b/VortexEngine/src/Wireless/IRSender.cpp
index 1d127a1978..a4119926dc 100644
--- a/VortexEngine/src/Wireless/IRSender.cpp
+++ b/VortexEngine/src/Wireless/IRSender.cpp
@@ -30,8 +30,15 @@ uint32_t IRSender::m_blockSize = 0;
// write total
uint32_t IRSender::m_writeCounter = 0;
+#if defined(VORTEX_EMBEDDED)
+// Timer used for PWM, is initialized in initpwm()
+Tcc *IR_TCCx;
+#endif
+
bool IRSender::init()
{
+ // initialize the IR device
+ initPWM();
return true;
}
@@ -111,6 +118,8 @@ void IRSender::beginSend()
m_isSending = true;
DEBUG_LOGF("[%zu] Beginning send size %u (blocks: %u remainder: %u blocksize: %u)",
Time::microseconds(), m_size, m_numBlocks, m_remainder, m_blockSize);
+ // init sender before writing, is this necessary here? I think so
+ initPWM();
// wakeup the other receiver with a very quick mark/space
sendMark(50);
sendSpace(100);
@@ -144,6 +153,9 @@ void IRSender::sendMark(uint16_t time)
#ifdef VORTEX_LIB
// send mark timing over socket
Vortex::vcallbacks()->infraredWrite(true, time);
+#else
+ startPWM();
+ Time::delayMicroseconds(time);
#endif
}
@@ -152,6 +164,79 @@ void IRSender::sendSpace(uint16_t time)
#ifdef VORTEX_LIB
// send space timing over socket
Vortex::vcallbacks()->infraredWrite(false, time);
+#else
+ stopPWM();
+ Time::delayMicroseconds(time);
+#endif
+}
+
+// shamelessly stolen from IRLib2, thanks
+void IRSender::initPWM()
+{
+#if defined(VORTEX_EMBEDDED)
+ // just in case
+ pinMode(IR_SEND_PWM_PIN, OUTPUT);
+ digitalWrite(IR_SEND_PWM_PIN, LOW); // When not sending PWM, we want it low
+ uint8_t port = g_APinDescription[IR_SEND_PWM_PIN].ulPort; // 0
+ uint8_t pin = g_APinDescription[IR_SEND_PWM_PIN].ulPin; // 8
+ ETCChannel IR_TCC_Channel = TCC0_CH0;
+ int8_t IR_PER_EorF = PORT_PMUX_PMUXE_E;
+ //println();Serial.print("Port:"); Serial.print(port,DEC); Serial.print(" Pin:"); Serial.println(pin,DEC);
+ // Enable the port multiplexer for the PWM channel on pin
+ PORT->Group[port].PINCFG[pin].bit.PMUXEN = 1;
+
+ // Connect the TCC timer to the port outputs - port pins are paired odd PMUXO and even PMUXEII
+ // F & E peripherals specify the timers: TCC0, TCC1 and TCC2
+ PORT->Group[port].PMUX[pin >> 1].reg |= IR_PER_EorF;
+
+// pinPeripheral (IR_SEND_PWM_PIN,PIO_TIMER_ALT);
+ // Feed GCLK0 to TCC0 and TCC1
+ REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN | // Enable GCLK0 to TCC0 and TCC1
+ GCLK_CLKCTRL_GEN_GCLK0 | // Select GCLK0
+ GCLK_CLKCTRL_ID_TCC0_TCC1; // Feed GCLK0 to TCC0 and TCC1
+ while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
+
+ // Normal (single slope) PWM operation: timers countinuously count up to PER
+ // register value and then is reset to 0
+ IR_TCCx = (Tcc*) GetTC(IR_TCC_Channel);
+ IR_TCCx->WAVE.reg |= TCC_WAVE_WAVEGEN_NPWM; // Setup single slope PWM on TCCx
+ while (IR_TCCx->SYNCBUSY.bit.WAVE); // Wait for synchronization
+
+ // Each timer counts up to a maximum or TOP value set by the PER register,
+ // this determines the frequency of the PWM operation.
+ uint32_t cc = F_CPU/(38*1000) - 1;
+ IR_TCCx->PER.reg = cc; // Set the frequency of the PWM on IR_TCCx
+ while(IR_TCCx->SYNCBUSY.bit.PER);
+
+ // The CCx register value corresponds to the pulsewidth in microseconds (us)
+ // Set the duty cycle of the PWM on TCC0 to 33%
+ IR_TCCx->CC[GetTCChannelNumber(IR_TCC_Channel)].reg = cc/3;
+ while (IR_TCCx->SYNCBUSY.reg & TCC_SYNCBUSY_MASK);
+ //while(IR_TCCx->SYNCBUSY.bit.CC3);
+
+ // Enable IR_TCCx timer but do not turn on PWM yet. Will turn it on later.
+ IR_TCCx->CTRLA.reg |= TCC_CTRLA_PRESCALER_DIV1; // Divide GCLK0 by 1
+ while (IR_TCCx->SYNCBUSY.bit.ENABLE);
+ IR_TCCx->CTRLA.reg &= ~TCC_CTRLA_ENABLE; //initially off will turn on later
+ while (IR_TCCx->SYNCBUSY.bit.ENABLE);
+#endif
+}
+
+void IRSender::startPWM()
+{
+#if defined(VORTEX_EMBEDDED)
+ // start the PWM
+ IR_TCCx->CTRLA.reg |= TCC_CTRLA_ENABLE;
+ while (IR_TCCx->SYNCBUSY.bit.ENABLE);
+#endif
+}
+
+void IRSender::stopPWM()
+{
+#if defined(VORTEX_EMBEDDED)
+ // stop the PWM
+ IR_TCCx->CTRLA.reg &= ~TCC_CTRLA_ENABLE;
+ while (IR_TCCx->SYNCBUSY.bit.ENABLE);
#endif
}
diff --git a/VortexEngine/src/Wireless/IRSender.h b/VortexEngine/src/Wireless/IRSender.h
index 8f65fa7048..d8e9df30eb 100644
--- a/VortexEngine/src/Wireless/IRSender.h
+++ b/VortexEngine/src/Wireless/IRSender.h
@@ -34,6 +34,11 @@ class IRSender
// send a mark/space by turning PWM on/off
static void sendMark(uint16_t time);
static void sendSpace(uint16_t time);
+ // Pulse-Width Modulator (IR Transmitter)
+ static void initPWM();
+ // turn the IR transmitter on/off in realtime
+ static void startPWM();
+ static void stopPWM();
// the serial buffer for the data
static ByteStream m_serialBuf;
diff --git a/VortexEngine/src/Wireless/VLReceiver.cpp b/VortexEngine/src/Wireless/VLReceiver.cpp
index 8d2f12a9a1..d7a52c763a 100644
--- a/VortexEngine/src/Wireless/VLReceiver.cpp
+++ b/VortexEngine/src/Wireless/VLReceiver.cpp
@@ -10,14 +10,62 @@
#include "../Leds/Leds.h"
#include "../Log/Log.h"
+#ifdef VORTEX_EMBEDDED
+#include
+#include
+#endif
+
BitStream VLReceiver::m_vlData;
VLReceiver::RecvState VLReceiver::m_recvState = WAITING_HEADER_MARK;
uint32_t VLReceiver::m_prevTime = 0;
uint8_t VLReceiver::m_pinState = 0;
uint32_t VLReceiver::m_previousBytes = 0;
+#ifdef VORTEX_EMBEDDED
+#define MIN_THRESHOLD 200
+#define BASE_OFFSET 100
+#define THRESHOLD_BEGIN (MIN_THRESHOLD + BASE_OFFSET)
+// the sample count exponent, so 5 means 2^5 = 32 samples
+// 0 NONE No accumulation > doesn't work
+// 1 ACC2 2 results accumulated > doesn't work
+// 2 ACC4 4 results accumulated > works okay
+// 3 ACC8 8 results accumulated > works decent
+// 4 ACC16 16 results accumulated > works very well
+// 5 ACC32 32 results accumulated > works best
+// 6 ACC64 64 results accumulated > doesn't work
+#define SAMPLE_COUNT 5
+// the threshold needs to start high then it will be automatically pulled down
+uint16_t threshold = THRESHOLD_BEGIN;
+ISR(ADC0_WCOMP_vect)
+{
+ // this will store the last known state
+ static bool wasAboveThreshold = false;
+ // grab the current analog value but divide it by 8 (the number of samples)
+ uint16_t val = (ADC0.RES >> SAMPLE_COUNT);
+ // calculate a threshold by using the baseline minimum value that is above 0
+ // with a static offset, this ensures whatever the baseline light level and/or
+ // hardware sensitivity is it will always pick a threshold just above the 'off'
+ if (val > MIN_THRESHOLD && val < (threshold + BASE_OFFSET)) {
+ threshold = val + BASE_OFFSET;
+ }
+ // compare the current analog value to the light threshold
+ bool isAboveThreshold = (val > threshold);
+ if (wasAboveThreshold != isAboveThreshold) {
+ VLReceiver::recvPCIHandler();
+ wasAboveThreshold = isAboveThreshold;
+ }
+ // Clear the Window Comparator interrupt flag
+ ADC0.INTFLAGS = ADC_WCMP_bm;
+}
+#endif
+
bool VLReceiver::init()
{
+#ifdef VORTEX_EMBEDDED
+ // Disable digital input buffer on the pin to save power
+ PORTB.PIN1CTRL &= ~PORT_ISC_gm;
+ PORTB.PIN1CTRL |= PORT_ISC_INPUT_DISABLE_gc;
+#endif
return m_vlData.init(VL_RECV_BUF_SIZE);
}
@@ -83,12 +131,63 @@ bool VLReceiver::receiveMode(Mode *pMode)
bool VLReceiver::beginReceiving()
{
+#ifdef VORTEX_EMBEDDED
+ // Set up the ADC
+ // sample campacitance, VDD reference, prescaler division
+ // Options are:
+ // 0x0 DIV2 CLK_PER divided by 2 > works
+ // 0x1 DIV4 CLK_PER divided by 4 > works
+ // 0x2 DIV8 CLK_PER divided by 8 > works
+ // 0x3 DIV16 CLK_PER divided by 16 > works
+ // 0x4 DIV32 CLK_PER divided by 32 > doesn't work
+ // 0x5 DIV64 CLK_PER divided by 64 > doesn't work
+ // 0x6 DIV128 CLK_PER divided by 128 > doesn't work
+ // 0x7 DIV256 CLK_PER divided by 256 > doesn't work
+#if (F_CPU == 20000000)
+ ADC0.CTRLC = ADC_SAMPCAP_bm | ADC_REFSEL_VDDREF_gc | ADC_PRESC_DIV2_gc;
+#else
+ ADC0.CTRLC = ADC_SAMPCAP_bm | ADC_REFSEL_VDDREF_gc | ADC_PRESC_DIV2_gc;
+#endif
+ // no sampling delay and no delay variation
+ ADC0.CTRLD = 0;
+ // sample length
+ // 0 = doesn't work
+ // 1+ = works
+ ADC0.SAMPCTRL = 1;
+ // Select the analog pin input PB1 (AIN10)
+ ADC0.MUXPOS = ADC_MUXPOS_AIN10_gc;
+ // Initialize the Window Comparator Mode in above
+ ADC0.CTRLE = ADC_WINCM_ABOVE_gc;
+ // Set the threshold value very low
+ ADC0.WINHT = 0x1;
+ ADC0.WINLT = 0;
+ // set sampling amount
+ // 0x0 NONE No accumulation > doesn't work
+ // 0x1 ACC2 2 results accumulated > doesn't work
+ // 0x2 ACC4 4 results accumulated > works okay
+ // 0x3 ACC8 8 results accumulated > works decent
+ // 0x4 ACC16 16 results accumulated > works very well
+ // 0x5 ACC32 32 results accumulated > works best
+ // 0x6 ACC64 64 results accumulated > doesn't work
+ ADC0.CTRLB = SAMPLE_COUNT;
+ // Enable Window Comparator interrupt
+ ADC0.INTCTRL = ADC_WCMP_bm;
+ // Enable the ADC and start continuous conversions
+ ADC0.CTRLA = ADC_ENABLE_bm | ADC_FREERUN_bm;
+ // start the first conversion
+ ADC0.COMMAND = ADC_STCONV_bm;
+#endif
resetVLState();
return true;
}
bool VLReceiver::endReceiving()
{
+#ifdef VORTEX_EMBEDDED
+ // Stop conversions and disable the ADC
+ ADC0.CTRLA &= ~(ADC_ENABLE_bm | ADC_FREERUN_bm);
+ ADC0.INTCTRL = 0;
+#endif
resetVLState();
return true;
}
@@ -197,6 +296,10 @@ void VLReceiver::resetVLState()
m_recvState = WAITING_HEADER_MARK;
// zero out the receive buffer and reset bit receiver position
m_vlData.reset();
+#ifdef VORTEX_EMBEDDED
+ // reset the threshold to a high value so that it can be pulled down again
+ threshold = THRESHOLD_BEGIN;
+#endif
DEBUG_LOG("VL State Reset");
}
diff --git a/VortexEngine/tests/tests_general.tar.gz b/VortexEngine/tests/tests_general.tar.gz
index 5c0e6df188..27a4d80bd6 100644
Binary files a/VortexEngine/tests/tests_general.tar.gz and b/VortexEngine/tests/tests_general.tar.gz differ
diff --git a/avrdude.conf b/avrdude.conf
new file mode 100644
index 0000000000..9773592fdc
--- /dev/null
+++ b/avrdude.conf
@@ -0,0 +1,213 @@
+#
+# Overall avrdude defaults; suitable for ~/.avrduderc
+#
+default_parallel = "lpt1";
+default_serial = "com1";
+# JTAG ICE mkII in PDI mode
+programmer
+ id = "jtag2pdi";
+ desc = "Atmel JTAG ICE mkII PDI mode";
+ baudrate = 115200;
+ type = "jtagmkii_pdi";
+ connection_type = usb;
+;
+programmer
+ id = "atmelice";
+ desc = "Atmel-ICE (ARM/AVR) in JTAG mode";
+ type = "jtagice3";
+ connection_type = usb;
+ usbpid = 0x2141;
+;
+programmer
+ id = "atmelice_pdi";
+ desc = "Atmel-ICE (ARM/AVR) in PDI mode";
+ type = "jtagice3_pdi";
+ connection_type = usb;
+ usbpid = 0x2141;
+;
+programmer
+ id = "atmelice_updi";
+ desc = "Atmel-ICE (ARM/AVR) in UPDI mode";
+ type = "jtagice3_updi";
+ connection_type = usb;
+ usbpid = 0x2141;
+;
+programmer
+ id = "atmelice_dw";
+ desc = "Atmel-ICE (ARM/AVR) in debugWIRE mode";
+ type = "jtagice3_dw";
+ connection_type = usb;
+ usbpid = 0x2141;
+;
+programmer
+ id = "atmelice_isp";
+ desc = "Atmel-ICE (ARM/AVR) in ISP mode";
+ type = "jtagice3_isp";
+ connection_type = usb;
+ usbpid = 0x2141;
+;
+programmer
+ id = "jtag2updi";
+ desc = "JTAGv2 to UPDI bridge";
+ type = "jtagmkii_pdi";
+ connection_type = serial;
+ baudrate = 115200;
+;
+programmer
+ id = "ft232r";
+ desc = "FT232R Synchronous BitBang";
+ type = "ftdi_syncbb";
+ connection_type = usb;
+ miso = 1; # RxD
+ sck = 0; # TxD
+ mosi = 2; # RTS
+ reset = 4; # DTR
+;
+# FTDI USB to serial cable TTL-232R-5V with a custom adapter for ICSP
+# http://www.ftdichip.com/Products/Cables/USBTTLSerial.htm
+# http://www.ftdichip.com/Support/Documents/DataSheets/Cables/DS_TTL-232R_CABLES.pdf
+# For ICSP pinout see for example http://www.atmel.com/images/doc2562.pdf
+# (Figure 1. ISP6PIN header pinout and Table 1. Connections required for ISP ...)
+# TTL-232R GND 1 Black -> ICPS GND (pin 6)
+# TTL-232R CTS 2 Brown -> ICPS MOSI (pin 4)
+# TTL-232R VCC 3 Red -> ICPS VCC (pin 2)
+# TTL-232R TXD 4 Orange -> ICPS RESET (pin 5)
+# TTL-232R RXD 5 Yellow -> ICPS SCK (pin 3)
+# TTL-232R RTS 6 Green -> ICPS MISO (pin 1)
+# Except for VCC and GND, you can connect arbitrary pairs as long as
+# the following table is adjusted.
+programmer
+ id = "ttl232r";
+ desc = "FTDI TTL232R-5V with ICSP adapter";
+ type = "ftdi_syncbb";
+ connection_type = usb;
+ miso = 2; # rts
+ sck = 1; # rxd
+ mosi = 3; # cts
+ reset = 0; # txd
+;
+
+
+#
+# PART DEFINITIONS
+#
+
+part
+ id = ".avr8x";
+ desc = "AVR8X family common values";
+ has_updi = yes;
+ nvm_base = 0x1000;
+ ocd_base = 0x0F80;
+
+ memory "signature"
+ size = 3;
+ offset = 0x1100;
+ readsize = 0x3;
+ ;
+
+ memory "prodsig"
+ size = 0x3D;
+ offset = 0x1103;
+ page_size = 0x3D;
+ readsize = 0x3D;
+ ;
+
+ memory "fuses"
+ size = 9;
+ offset = 0x1280;
+ page_size = 0x0A;
+ readsize = 0x0A;
+ ;
+
+ memory "fuse0"
+ size = 1;
+ offset = 0x1280;
+ readsize = 1;
+ ;
+
+ memory "fuse1"
+ size = 1;
+ offset = 0x1281;
+ readsize = 1;
+ ;
+
+ memory "fuse2"
+ size = 1;
+ offset = 0x1282;
+ readsize = 1;
+ ;
+
+ memory "fuse4"
+ size = 1;
+ offset = 0x1284;
+ readsize = 1;
+ ;
+
+ memory "fuse5"
+ size = 1;
+ offset = 0x1285;
+ readsize = 1;
+ ;
+
+ memory "fuse6"
+ size = 1;
+ offset = 0x1286;
+ readsize = 1;
+ ;
+
+ memory "fuse7"
+ size = 1;
+ offset = 0x1287;
+ readsize = 1;
+ ;
+
+ memory "fuse8"
+ size = 1;
+ offset = 0x1288;
+ readsize = 1;
+ ;
+
+ memory "lock"
+ size = 1;
+ offset = 0x128a;
+ readsize = 1;
+ ;
+
+ memory "data"
+ # SRAM, only used to supply the offset
+ offset = 0x1000000;
+ ;
+;
+
+part parent ".avr8x"
+ id = ".avr8x_tiny";
+ desc = "AVR8X tiny family common values";
+# family_id = "tinyAVR";
+
+ memory "usersig"
+ size = 0x20;
+ offset = 0x1300;
+ page_size = 0x20;
+ readsize = 0x100;
+ ;
+;
+
+part parent ".avr8x_tiny"
+ id = "t3217";
+ desc = "ATtiny3217";
+ signature = 0x1E 0x95 0x22;
+
+ memory "flash"
+ size = 0x8000;
+ offset = 0x8000;
+ page_size = 0x80;
+ readsize = 0x100;
+ ;
+
+ memory "eeprom"
+ size = 0x100;
+ offset = 0x1400;
+ page_size = 0x40;
+ readsize = 0x100;
+ ;
+;
diff --git a/avrsize.sh b/avrsize.sh
new file mode 100644
index 0000000000..13020f5bb8
--- /dev/null
+++ b/avrsize.sh
@@ -0,0 +1,56 @@
+#!/bin/bash
+
+# need megatinycore installed for this
+
+if [ "$(uname -o)" == "Msys" ]; then
+ AVR_SIZE="C:/Program Files (x86)/Atmel/Studio/7.0/toolchain/avr8/avr8-gnu-toolchain/bin/avr-size.exe"
+else
+ AVR_SIZE="${HOME}/atmel_setup/avr8-gnu-toolchain-linux_x86_64/bin/avr-size"
+fi
+
+# Replace this with the path to your .elf file
+ELF_FILE=$1
+
+if [ "$ELF_FILE" == "" ]; then
+ echo "Please specify a file: $0 [max flash size]"
+ exit 1
+fi
+
+# Constants for program storage and dynamic memory size
+PROGRAM_STORAGE=32768
+DYNAMIC_MEMORY=2048
+
+# Run avr-size and parse the output
+if [ "$(uname -o)" == "Msys" ]; then
+ OUTPUT=$("$AVR_SIZE" -A $ELF_FILE)
+else
+ OUTPUT=$($AVR_SIZE -A $ELF_FILE)
+fi
+
+# Extract sizes of .text, .data, .rodata, and .bss sections
+TEXT_SIZE=$(echo "$OUTPUT" | grep -E '\.text' | awk '{print $2}')
+DATA_SIZE=$(echo "$OUTPUT" | grep -E '\.data' | awk '{print $2}')
+RODATA_SIZE=$(echo "$OUTPUT" | grep -E '\.rodata' | awk '{print $2}')
+BSS_SIZE=$(echo "$OUTPUT" | grep -E '\.bss' | awk '{print $2}')
+
+# Calculate used program storage and dynamic memory
+PROGRAM_STORAGE_USED=$((TEXT_SIZE + DATA_SIZE + RODATA_SIZE))
+DYNAMIC_MEMORY_USED=$((DATA_SIZE + BSS_SIZE))
+
+# Calculate percentages
+PROGRAM_STORAGE_PERCENT=$(awk -v used="$PROGRAM_STORAGE_USED" -v total="$PROGRAM_STORAGE" 'BEGIN { printf("%.2f", used / total * 100) }')
+DYNAMIC_MEMORY_PERCENT=$(awk -v used="$DYNAMIC_MEMORY_USED" -v total="$DYNAMIC_MEMORY" 'BEGIN { printf("%.2f", used / total * 100) }')
+
+# if a max flash size was passed then compare it against the amount used
+if [ ! -z "$2" ] && [ "$(printf '%d' $2)" -lt "$(printf '%d' $PROGRAM_STORAGE_USED)" ]; then
+ echo "---------"
+ echo "Failure! Program space: 0x$(printf '%x' $PROGRAM_STORAGE_USED) larger than allowed: 0x$(printf '%x' $2)"
+ echo "Reduce program size within 0x$(printf '%x' $2), or reduce flash storage space then adjust BOOTEND in the Makefile"
+ exit 1
+fi
+
+# Display the results
+echo "Success! Program uses $PROGRAM_STORAGE_USED/$PROGRAM_STORAGE or $(printf '0x%x' $PROGRAM_STORAGE_USED)/$(printf '0x%x' $PROGRAM_STORAGE) bytes of flash space ($PROGRAM_STORAGE_PERCENT%)"
+echo "Global variables use $DYNAMIC_MEMORY_USED/$DYNAMIC_MEMORY or $(printf '0x%x' $DYNAMIC_MEMORY_USED)/$(printf '0x%x' $DYNAMIC_MEMORY) bytes of SRAM ($DYNAMIC_MEMORY_PERCENT%)"
+
+