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%)" + +