diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..91e8793 --- /dev/null +++ b/.clang-format @@ -0,0 +1,258 @@ +--- +Language: Cpp +# BasedOnStyle: Google +AccessModifierOffset: -1 +AlignAfterOpenBracket: Align +AlignArrayOfStructures: None +AlignConsecutiveAssignments: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + PadOperators: true +AlignConsecutiveBitFields: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + PadOperators: false +AlignConsecutiveDeclarations: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + PadOperators: false +AlignConsecutiveMacros: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + PadOperators: false +AlignEscapedNewlines: Left +AlignOperands: Align +AlignTrailingComments: + Kind: Always + OverEmptyLines: 0 +AllowAllArgumentsOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortEnumsOnASingleLine: true +AllowShortFunctionsOnASingleLine: All +AllowShortIfStatementsOnASingleLine: WithoutElse +AllowShortLambdasOnASingleLine: All +AllowShortLoopsOnASingleLine: true +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: true +AlwaysBreakTemplateDeclarations: Yes +AttributeMacros: + - __capability +BinPackArguments: true +BinPackParameters: true +BitFieldColonSpacing: Both +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: Never + AfterEnum: false + AfterExternBlock: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + BeforeCatch: false + BeforeElse: false + BeforeLambdaBody: false + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakAfterAttributes: Never +BreakAfterJavaFieldAnnotations: false +BreakArrays: true +BreakBeforeBinaryOperators: None +BreakBeforeConceptDeclarations: Always +BreakBeforeBraces: Attach +BreakBeforeInlineASMColon: OnlyMultiline +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeColon +BreakInheritanceList: BeforeColon +BreakStringLiterals: true +ColumnLimit: 80 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: true +DisableFormat: false +EmptyLineAfterAccessModifier: Never +EmptyLineBeforeAccessModifier: LogicalBlock +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IfMacros: + - KJ_IF_MAYBE +IncludeBlocks: Regroup +IncludeCategories: + - Regex: '^' + Priority: 2 + SortPriority: 0 + CaseSensitive: false + - Regex: '^<.*\.h>' + Priority: 1 + SortPriority: 0 + CaseSensitive: false + - Regex: '^<.*' + Priority: 2 + SortPriority: 0 + CaseSensitive: false + - Regex: '.*' + Priority: 3 + SortPriority: 0 + CaseSensitive: false +IncludeIsMainRegex: '([-_](test|unittest))?$' +IncludeIsMainSourceRegex: '' +IndentAccessModifiers: false +IndentCaseBlocks: false +IndentCaseLabels: true +IndentExternBlock: AfterExternBlock +IndentGotoLabels: true +IndentPPDirectives: None +IndentRequiresClause: true +IndentWidth: 2 +IndentWrappedFunctionNames: false +InsertBraces: false +InsertNewlineAtEOF: false +InsertTrailingCommas: None +IntegerLiteralSeparator: + Binary: 0 + BinaryMinDigits: 0 + Decimal: 0 + DecimalMinDigits: 0 + Hex: 0 + HexMinDigits: 0 +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +LambdaBodyIndentation: Signature +LineEnding: DeriveLF +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Never +ObjCBlockIndentWidth: 2 +ObjCBreakBeforeNestedBlockParam: true +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PackConstructorInitializers: NextLine +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 1 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakOpenParenthesis: 0 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyIndentedWhitespace: 0 +PenaltyReturnTypeOnItsOwnLine: 200 +PointerAlignment: Left +PPIndentWidth: -1 +QualifierAlignment: Leave +RawStringFormats: + - Language: Cpp + Delimiters: + - cc + - CC + - cpp + - Cpp + - CPP + - 'c++' + - 'C++' + CanonicalDelimiter: '' + BasedOnStyle: google + - Language: TextProto + Delimiters: + - pb + - PB + - proto + - PROTO + EnclosingFunctions: + - EqualsProto + - EquivToProto + - PARSE_PARTIAL_TEXT_PROTO + - PARSE_TEST_PROTO + - PARSE_TEXT_PROTO + - ParseTextOrDie + - ParseTextProtoOrDie + - ParseTestProto + - ParsePartialTestProto + CanonicalDelimiter: pb + BasedOnStyle: google +ReferenceAlignment: Pointer +ReflowComments: true +RemoveBracesLLVM: false +RemoveSemicolon: false +RequiresClausePosition: OwnLine +RequiresExpressionIndentation: OuterScope +SeparateDefinitionBlocks: Leave +ShortNamespaceLines: 1 +SortIncludes: CaseSensitive +SortJavaStaticImport: Before +SortUsingDeclarations: LexicographicNumeric +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceAroundPointerQualifiers: Default +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeParensOptions: + AfterControlStatements: true + AfterForeachMacros: true + AfterFunctionDefinitionName: false + AfterFunctionDeclarationName: false + AfterIfMacros: true + AfterOverloadedOperator: false + AfterRequiresInClause: false + AfterRequiresInExpression: false + BeforeNonEmptyParentheses: false +SpaceBeforeRangeBasedForLoopColon: true +SpaceBeforeSquareBrackets: false +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: Never +SpacesInConditionalStatement: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInLineCommentPrefix: + Minimum: 1 + Maximum: -1 +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Auto +StatementAttributeLikeMacros: + - Q_EMIT +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 8 +UseTab: Never +WhitespaceSensitiveMacros: + - BOOST_PP_STRINGIZE + - CF_SWIFT_NAME + - NS_SWIFT_NAME + - PP_STRINGIZE + - STRINGIZE +... diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 6684ce1..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,49 +0,0 @@ -name: PPUC CI - -on: - push: - - pull_request: - - schedule: - - cron: '0 8 * * *' # run at 08:00 UTC - -jobs: - pio-run: - runs-on: ubuntu-latest - - strategy: - matrix: - controller: ['DistributionController', 'EffectController', 'InputController', 'IOBoardController'] - - name: PPUC ${{ matrix.controller }} - - steps: - - uses: actions/checkout@v3 - - - name: Cache pip - uses: actions/cache@v3 - with: - path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} - restore-keys: | - ${{ runner.os }}-pip- - - - name: Cache PlatformIO - uses: actions/cache@v3 - with: - path: ~/.platformio - key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }} - - - name: Set up Python - uses: actions/setup-python@v3 - - - name: Install PlatformIO - run: | - python -m pip install --upgrade pip - pip install --upgrade platformio - - - name: Run PlatformIO - run: | - cd test/${{ matrix.controller }} - pio run diff --git a/.github/workflows/io-boards.yml b/.github/workflows/io-boards.yml new file mode 100644 index 0000000..d1a42d3 --- /dev/null +++ b/.github/workflows/io-boards.yml @@ -0,0 +1,111 @@ +name: PPUC IO Boards + +on: + push: + pull_request: + schedule: + - cron: '0 9 * * *' # run at 08:00 UTC + +jobs: + version: + name: Detect version + runs-on: ubuntu-latest + outputs: + tag: ${{ steps.version.outputs.tag }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - id: version + run: | + VERSION_MAJOR=$(grep -Eo "FIRMWARE_VERSION_MAJOR\s+[0-9]+" src/PPUC.h | grep -Eo "[0-9]+") + VERSION_MINOR=$(grep -Eo "FIRMWARE_VERSION_MINOR\s+[0-9]+" src/PPUC.h | grep -Eo "[0-9]+") + VERSION_PATCH=$(grep -Eo "FIRMWARE_VERSION_PATCH\s+[0-9]+" src/PPUC.h | grep -Eo "[0-9]+") + TAG="${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}" + echo "${TAG}" + echo "tag=${TAG}" >> $GITHUB_OUTPUT + - name: Check git tag + if: startsWith(github.ref, 'refs/tags/v') + run: | + GIT_TAG="${GITHUB_REF#refs/tags/}" + EXPECTED_TAG="v${{ steps.version.outputs.tag }}" + if [[ "${GIT_TAG}" != "${EXPECTED_TAG}" ]]; then + echo "Error: Git tag (${GIT_TAG}) does not match version from PPUC.h (v${{ steps.version.outputs.tag }})" + exit 1 + fi + + pio-run: + name: Build and upload firmware + runs-on: ubuntu-latest + needs: [ version ] + + strategy: + fail-fast: false + matrix: + controller: ['IO_16_8_1'] + + steps: + - name: PPUC ${{ matrix.controller }} + uses: actions/checkout@v4 + + - name: Cache pip + uses: actions/cache@v4 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} + restore-keys: | + ${{ runner.os }}-pip- + + - name: Cache PlatformIO + uses: actions/cache@v4 + with: + path: ~/.platformio + key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }} + + - name: Set up Python + uses: actions/setup-python@v5 + + - name: Install PlatformIO + run: | + python -m pip install --upgrade pip + pip install --upgrade platformio + + - name: Build elf2uf2 from source + run: | + sudo apt update && sudo apt install -y cmake gcc-arm-none-eabi libnewlib-arm-none-eabi + git clone https://github.com/ckormanyos/elf2uf2.git + cd elf2uf2 + make all + sudo cp bin/elf2uf2 /usr/local/bin/ + cd .. + + - name: Run PlatformIO + run: | + pio run + + - name: Create UF2 file (using elf2uf2) + run: | + ELF_FILE=$(find .pio/build/ -name "*.elf" | head -n 1) + echo "Found ELF file: $ELF_FILE" + elf2uf2 "$ELF_FILE" "${ELF_FILE%.elf}.uf2" + cp ${ELF_FILE%.elf}.uf2 ${{ matrix.controller }}-${{ needs.version.outputs.tag }}.uf2 + - name: Upload UF2 artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.controller }}-firmware + path: | + ${{ matrix.controller }}-${{ needs.version.outputs.tag }}.uf2 + + post-build: + runs-on: ubuntu-latest + needs: [ version, pio-run ] + name: Release + steps: + - uses: actions/download-artifact@v4 + - name: Release + uses: softprops/action-gh-release@v1 + if: startsWith(github.ref, 'refs/tags/v') + with: + draft: true + files: | + */*.uf2 diff --git a/.gitignore b/.gitignore index c30f8ea..155326f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .idea -.pio .vscode -.DS_Store \ No newline at end of file +.pio +.DS_Store +*.pio.h diff --git a/README.md b/README.md index e7c1e7e..c8f70c7 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ The development happens as part of the [PinMAME project](https://github.com/vpin We want to enable people to be creative and to modernize old pinball machines using today's technology. Our goal is to establish an open and affordable platform for that. Ideally people will publish their game-specific PPUs so others could -leverage and potentially improve them. We want to see a growing library of PPUs and a vital homebrew pinball community. +leverage and potentially improve them. We want to see a growing library of PPUs and a vital homebrew pinball community. ## Concept @@ -48,7 +48,7 @@ WIP ### Replacing a CPU (and drivers) -WIP, see [PinMAME project](https://github.com/vpinball/pinmame/tree/master/src/ppuc). +WIP, see [PPUC.org](https://ppuc.org). ## Licence diff --git a/library.json b/library.json index eba1bca..64eab09 100644 --- a/library.json +++ b/library.json @@ -1,18 +1,18 @@ { - "name": "PPUC", - "description": "Pinball Power-Up Controller", + "name": "PPUC/io-boards", + "description": "Pinball Power-Up Controller IO Boards Library", "keywords": "pinball", "authors": { "name": "Markus Kalkbrenner", - "url": "https://github.com/mkalkbrenner/PPUC" + "url": "https://github.com/PPUC" }, - "version": "0.2.2", + "version": "0.3.0", "downloadUrl": "https://github.com/mkalkbrenner/PPUC/archive/main.zip", "frameworks": "arduino", "platforms": ["atmelavr", "teensy"], "repository": { "type": "git", - "url": "https://github.com/mkalkbrenner/PPUC.git" + "url": "https://github.com/PPUC/io-boards.git" }, "license": "GPL-3.0-or-later", "export": { diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..618d469 --- /dev/null +++ b/platformio.ini @@ -0,0 +1,17 @@ +[platformio] +default_envs = IO_16_8_1 + +[env:IO_16_8_1] +platform = https://github.com/mkalkbrenner/platform-raspberrypi#issue-112 +framework = arduino +board = pico +board_build.core = earlephilhower +board_build.filesystem_size = 0.5m +monitor_speed = 115200 +build_flags = + -D PICO_STDIO_USB ; enable stdio over USB +lib_deps = + mkalkbrenner/WavePWM + kitesurfer1404/WS2812FX + Bounce2 + RPI_PICO_TimerInterrupt diff --git a/src/DistributionController.cpp b/src/DistributionController.cpp deleted file mode 100644 index c0c9a38..0000000 --- a/src/DistributionController.cpp +++ /dev/null @@ -1,13 +0,0 @@ -#include "DistributionController.h" - -VPXComLink* DistributionController::vpxComLink() { - return _vpxComLink; -} - -DistributionControllerTestButtons* DistributionController::testButtons() { - return _testButtons; -} - -EventDispatcher* DistributionController::eventDispatcher() { - return _eventDispatcher; -} diff --git a/src/DistributionController.h b/src/DistributionController.h deleted file mode 100644 index 7a728e6..0000000 --- a/src/DistributionController.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - DistributionController.h - Created by Markus Kalkbrenner. -*/ - -#ifndef DISTRIBUTIONCONTROLLER_h -#define DISTRIBUTIONCONTROLLER_h - -#include "PPUC.h" - -#include "EventDispatcher/EventDispatcher.h" -#include "EventDispatcher/EventListener.h" -#include "InputDevices/DistributionControllerTestButtons.h" -#include "VisualPinball/VPXComLink.h" - -class DistributionController { -public: - DistributionController(int controllerType, byte pf) { - platform = pf; - _eventDispatcher = new EventDispatcher(); - - if (controllerType == CONTROLLER_MEGA_ALL_INPUT) { - _vpxComLink = new VPXComLink(_eventDispatcher, platform); - _testButtons = new DistributionControllerTestButtons(_eventDispatcher); - - _eventDispatcher->addListener(_vpxComLink, EVENT_SOURCE_SWITCH); - } else { - Serial.print("Unsupported Distribution Controller: "); - Serial.println(controllerType); - } - } - - VPXComLink* vpxComLink(); - - DistributionControllerTestButtons* testButtons(); - - EventDispatcher* eventDispatcher(); - - byte platform; - -private: - VPXComLink* _vpxComLink; - DistributionControllerTestButtons* _testButtons; - EventDispatcher* _eventDispatcher; -}; - -#endif diff --git a/src/EffectDevices/CombinedGiAndLightMatrixWS2812FXDevice.cpp b/src/EffectDevices/CombinedGiAndLightMatrixWS2812FXDevice.cpp index 3d44266..45a9375 100644 --- a/src/EffectDevices/CombinedGiAndLightMatrixWS2812FXDevice.cpp +++ b/src/EffectDevices/CombinedGiAndLightMatrixWS2812FXDevice.cpp @@ -1,333 +1,226 @@ #include "CombinedGiAndLightMatrixWS2812FXDevice.h" void CombinedGiAndLightMatrixWS2812FXDevice::on() { - WS2812FXDevice::on(); - effectRunning = true; + WS2812FXDevice::on(); + effectRunning = true; } void CombinedGiAndLightMatrixWS2812FXDevice::off() { - effectRunning = false; - // No stop. Just reset to quit effects and return to standard GI and Light Matrix operation. - reset(); + effectRunning = false; + // No stop. Just reset to quit effects and return to standard GI and Light + // Matrix operation. + reset(); } -void CombinedGiAndLightMatrixWS2812FXDevice::assignLedToGiString(uint8_t giString, int16_t led) { - assignLedToGiString(giString, led, ULTRAWHITE); -} - -void CombinedGiAndLightMatrixWS2812FXDevice::assignLedToGiString(uint8_t giString, int16_t led, uint32_t color) { - if (numLEDsGI[--giString] < _MAX_LEDS_GI_STRING) { - ledGIPositions[giString][numLEDsGI[giString]] = led; - ledGIColors[giString][numLEDsGI[giString]++] = color; - } -} +void CombinedGiAndLightMatrixWS2812FXDevice::handleEvent(Event* event) { + if (!effectRunning) { + if (event->sourceId == EVENT_SOURCE_GI) { + uint8_t giString = event->eventId - 1; + if (giString >= NUM_GI_STRINGS) { + return; + } + // Brightness is a value between 0 and 8. Convert it into a value from 0 + // to 255. + uint8_t giBrightness = 0; + switch (event->value) { + case 1: + giBrightness = 35; + break; + case 2: + giBrightness = 60; + break; + case 3: + giBrightness = 85; + break; + case 4: + giBrightness = 115; + break; + case 5: + giBrightness = 155; + break; + case 6: + giBrightness = 205; + break; + case 7: + case 8: + giBrightness = 255; + break; + } + + if (targetGIBrightness[giString] != giBrightness) { + if ((targetGIBrightness[giString] < giBrightness && msHeatUp == 0) || + (targetGIBrightness[giString] > giBrightness && msAfterGlow == 0)) { + for (auto& led : getGILEDsByNumber(giString)) { + ws2812FX->setPixelColor( + led->position, + getDimmedPixelColor(led->color, giBrightness)); + } + } else { + for (auto& led : getGILEDsByNumber(giString)) { + intializeNewLEDState(led, targetGIBrightness[giString] > giBrightness); + } + } -void CombinedGiAndLightMatrixWS2812FXDevice::assignLedRangeToGiString(uint8_t giString, int16_t first, int16_t last) { - for (int16_t i = first; i <= last; i++) { - assignLedToGiString(giString, i); + sourceGIBrightness[giString] = targetGIBrightness[giString]; + targetGIBrightness[giString] = giBrightness; + } + } else if (event->sourceId == EVENT_SOURCE_LIGHT) { + uint8_t number = event->eventId; + bool on = (bool)event->value; + for (auto& led : getLightMatrixLEDsByNumber(number)) { + intializeNewLEDState(led, on); + } + } else if (event->sourceId == EVENT_SOURCE_SOLENOID) { + uint8_t number = event->eventId; + bool on = (bool)event->value; + for (auto& led : getFlasherLEDsByNumber(number)) { + intializeNewLEDState(led, on); + } } -} - -void CombinedGiAndLightMatrixWS2812FXDevice::assignLedToLightMatrix(uint8_t column, uint8_t row, int16_t led) { - assignLedToLightMatrix(column, row, led, ULTRAWHITE); -} - -void CombinedGiAndLightMatrixWS2812FXDevice::assignLedToLightMatrix(uint8_t column, uint8_t row, int16_t led, uint32_t color) { - assignLedToLightMatrixDE(((column - 1) * 8) + row , led, color); -} - -void CombinedGiAndLightMatrixWS2812FXDevice::assignLedToLightMatrixWPC(uint8_t number, int16_t led) { - assignLedToLightMatrixWPC(number, led, ULTRAWHITE); - wpc = true; -} - -void CombinedGiAndLightMatrixWS2812FXDevice::assignLedToLightMatrixWPC(uint8_t number, int16_t led, uint32_t color) { - assignLedToLightMatrix(number / 10 % 10, number % 10, led, color); - wpc = true; -} - -void CombinedGiAndLightMatrixWS2812FXDevice::assignLedToLightMatrixSYS11(uint8_t number, int16_t led) { - assignLedToLightMatrixDE(number, led, ULTRAWHITE); -} - -void CombinedGiAndLightMatrixWS2812FXDevice::assignLedToLightMatrixSYS11(uint8_t number, int16_t led, uint32_t color) { - assignLedToLightMatrixDE(number, led, color); -} - -void CombinedGiAndLightMatrixWS2812FXDevice::assignLedToLightMatrixDE(uint8_t number, int16_t led) { - assignLedToLightMatrixDE(number, led, ULTRAWHITE); -} - -void CombinedGiAndLightMatrixWS2812FXDevice::assignLedToLightMatrixDE(uint8_t number, int16_t led, uint32_t color) { - --number; - for (int i = 0; i < _MAX_LEDS_PER_LIGHT; i++) { - if (ledLightMatrixPositions[number][i] == -1) { - ledLightMatrixPositions[number][i] = led; - ledLightMatrixColors[number][i] = color; - break; + } +} + +void CombinedGiAndLightMatrixWS2812FXDevice::intializeNewLEDState(LED* led, + bool on) { + if (on && msHeatUp == 0) { + ws2812FX->setPixelColor(led->position, led->color); + } else if (!on && msAfterGlow == 0) { + ws2812FX->setPixelColor(led->position, RGBW_BLACK); + } else if (on) { + if (led->heatUp == 0 && led->afterGlow == 0) { + led->heatUp = millis(); + } else if (led->afterGlow > 0) { + // There's still an after glow effect running. Start heat up + // from current value. + uint8_t value = wavePWMAfterGlow->getExponentialValue( + millis() - led->afterGlow + msAfterGlow); + led->afterGlow = 0; + for (int ms = 1; ms <= msHeatUp; ms++) { + if (wavePWMHeatUp->getExponentialValue(ms) >= value) { + led->heatUp = millis() - ms; + break; } + } + // safety net + if (led->heatUp == 0) { + led->heatUp = millis() - msHeatUp + 1; + } } -} - -void CombinedGiAndLightMatrixWS2812FXDevice::handleEvent(Event* event) { - if (!effectRunning) { - if (event->sourceId == EVENT_SOURCE_GI) { - uint8_t giString = event->eventId - 1; - // Brightness is a value between 0 and 8. Convert it into a value from 0 to 255. - uint8_t giBrightness = 0; - switch (event->value) { - case 1: - giBrightness = 35; - break; - case 2: - giBrightness = 60; - break; - case 3: - giBrightness = 85; - break; - case 4: - giBrightness = 115; - break; - case 5: - giBrightness = 155; - break; - case 6: - giBrightness = 205; - break; - case 7: - case 8: - giBrightness = 255; - break; - } - - if (targetGIBrightness[giString] != giBrightness) { - if ( - (targetGIBrightness[giString] < giBrightness && msHeatUp == 0) || - (targetGIBrightness[giString] > giBrightness && msAfterGlow == 0) - ) { - for (int i = 0; i <= numLEDsGI[giString]; i++) { - setDimmedPixelColor(ledGIPositions[giString][i], ledGIColors[giString][i], giBrightness); - } - } - else { - if (targetGIBrightness[giString] < giBrightness) { - if (heatUpGI[giString] == 0 && afterGlowGI[giString] == 0) { - heatUpGI[giString] = millis(); - } else if (afterGlowGI[giString] > 0) { - // There's still an after glow effect running. Start heat up from current value. - byte value = wavePWMAfterGlow->getExponentialValue(millis() - afterGlowGI[giString] + msAfterGlow); - afterGlowGI[giString] = 0; - for (int ms = 1; ms <= msHeatUp; ms++) { - if (wavePWMHeatUp->getExponentialValue(ms) >= value) { - heatUpGI[giString] = millis() - ms; - break; - } - } - // safety net - if (heatUpGI[giString] == 0) { - heatUpGI[giString] = millis() - msHeatUp + 1; - } - } - } else { - if (afterGlowGI[giString] == 0 && heatUpGI[giString] == 0) { - afterGlowGI[giString] = millis(); - } else if (heatUpGI[giString] > 0) { - // There's still a heat up effect running. Start after glow from current value. - byte value = wavePWMHeatUp->getExponentialValue(millis() - heatUp[giString]); - heatUpGI[giString] = 0; - for (int ms = msAfterGlow; ms <= (msAfterGlow * 2); ms++) { - if (wavePWMAfterGlow->getExponentialValue(ms) <= value) { - afterGlowGI[giString] = millis() - ms; - break; - } - } - // safety net - if (afterGlowGI[giString] == 0) { - afterGlowGI[giString] = millis() - (2 * msAfterGlow) + 1; - } - } - } - } - - sourceGIBrightness[giString] = targetGIBrightness[giString]; - targetGIBrightness[giString] = giBrightness; - } - } - else if (event->sourceId == EVENT_SOURCE_LIGHT) { - //char system = (event->eventId & 0xFF00) >> 8; - //uint8_t number = event->eventId & 0x00FF; - - uint8_t number = event->eventId; - - if (wpc) { - // WPC - uint8_t column = number / 10 % 10; - uint8_t row = number % 10; - number = ((column - 1) * 8) + row; - } - - --number; - bool on = (bool) event->value; - - for (int i = 0; i < _MAX_LEDS_PER_LIGHT; i++) { - if (ledLightMatrixPositions[number][i] >= 0) { - if (on && msHeatUp == 0) { - ws2812FX->setPixelColor(ledLightMatrixPositions[number][i], ledLightMatrixColors[number][i]); - } - else if (!on && msAfterGlow == 0) { - ws2812FX->setPixelColor(ledLightMatrixPositions[number][i], RGBW_BLACK); - } - else if (i == 0) { - if (on) { - if (heatUp[number] == 0 && afterGlow[number] == 0) { - heatUp[number] = millis(); - } else if (afterGlow[number] > 0) { - // There's still an after glow effect running. Start heat up from current value. - byte value = wavePWMAfterGlow->getExponentialValue(millis() - afterGlow[number] + msAfterGlow); - afterGlow[number] = 0; - for (int ms = 1; ms <= msHeatUp; ms++) { - if (wavePWMHeatUp->getExponentialValue(ms) >= value) { - heatUp[number] = millis() - ms; - break; - } - } - // safety net - if (heatUp[number] == 0) { - heatUp[number] = millis() - msHeatUp + 1; - } - } - } else { - if (afterGlow[number] == 0 && heatUp[number] == 0) { - afterGlow[number] = millis(); - } else if (heatUp[number] > 0) { - // There's still a heat up effect running. Start after glow from current value. - byte value = wavePWMHeatUp->getExponentialValue(millis() - heatUp[number]); - heatUp[number] = 0; - for (int ms = msAfterGlow; ms <= (msAfterGlow * 2); ms++) { - if (wavePWMAfterGlow->getExponentialValue(ms) <= value) { - afterGlow[number] = millis() - ms; - break; - } - } - // safety net - if (afterGlow[number] == 0) { - afterGlow[number] = millis() - (2 * msAfterGlow) + 1; - } - } - } - - // terminate the loop - return; - } - } - } + } else { + if (led->afterGlow == 0 && led->heatUp == 0) { + led->afterGlow = millis(); + } else if (led->heatUp > 0) { + // There's still a heat up effect running. Start after glow from + // current value. + uint8_t value = wavePWMHeatUp->getExponentialValue(millis() - led->heatUp); + led->heatUp = 0; + for (int ms = msAfterGlow; ms <= (msAfterGlow * 2); ms++) { + if (wavePWMAfterGlow->getExponentialValue(ms) <= value) { + led->afterGlow = millis() - ms; + break; } + } + // safety net + if (led->afterGlow == 0) { + led->afterGlow = millis() - (2 * msAfterGlow) + 1; + } } + } } void CombinedGiAndLightMatrixWS2812FXDevice::updateAfterGlow() { - for (uint8_t giString = 0; giString < NUM_GI_STRINGS; giString++) { - uint8_t glowBrightness = targetGIBrightness[giString]; - if (heatUpGI[giString] > 0) { - if ((millis() - heatUpGI[giString]) >= msHeatUp) { - heatUpGI[giString] = 0; - } - else { - float diff = targetGIBrightness[giString] - sourceGIBrightness[giString]; - float mult = diff / 255; - glowBrightness = sourceGIBrightness[giString] + (wavePWMHeatUp->getExponentialValue(millis() - heatUpGI[giString]) * mult); - } - } - else if (afterGlowGI[giString] > 0) { - if ((millis() - afterGlowGI[giString]) >= msAfterGlow) { - afterGlowGI[giString] = 0; - } - else { - float diff = sourceGIBrightness[giString] - targetGIBrightness[giString]; - float mult = diff / 255; - glowBrightness = targetGIBrightness[giString] + (wavePWMAfterGlow->getExponentialValue(millis() - afterGlowGI[giString] + msAfterGlow) * mult); - } + for (uint8_t giString = 0; giString < NUM_GI_STRINGS; giString++) { + uint8_t glowBrightness = targetGIBrightness[giString]; + + for (auto& led : getChangingGILEDsByNumber(giString)) { + if (led->heatUp > 0) { + if ((millis() - led->heatUp) >= msHeatUp) { + led->heatUp = 0; + } else { + float diff = + targetGIBrightness[giString] - sourceGIBrightness[giString]; + float mult = diff / 255; + glowBrightness = + sourceGIBrightness[giString] + + (wavePWMHeatUp->getExponentialValue(millis() - led->heatUp) * + mult); } - else { - continue; + } else if (led->afterGlow > 0) { + if ((millis() - led->afterGlow) >= msAfterGlow) { + led->afterGlow = 0; + } else { + float diff = + sourceGIBrightness[giString] - targetGIBrightness[giString]; + float mult = diff / 255; + glowBrightness = + targetGIBrightness[giString] + + (wavePWMAfterGlow->getExponentialValue( + millis() - led->afterGlow + msAfterGlow) * + mult); } + } - for (int i = 0; i < numLEDsGI[giString]; i++) { - if (ledGIPositions[giString][i] != -1) { - setDimmedPixelColor(ledGIPositions[giString][i], ledGIColors[giString][i], glowBrightness); - } - } + ws2812FX->setPixelColor( + led->position, getDimmedPixelColor(led->color, glowBrightness)); + } + } + + for (auto& led : getChangingLightMatrixAndFlasherLEDs()) { + uint8_t glowBrightness; + if (led->heatUp > 0) { + if ((millis() - led->heatUp) >= msHeatUp) { + led->heatUp = 0; + ws2812FX->setPixelColor(led->position, led->color); + } else { + glowBrightness = + wavePWMHeatUp->getExponentialValue(millis() - led->heatUp); + } + } else if (led->afterGlow > 0) { + if ((millis() - led->afterGlow) >= msAfterGlow) { + led->afterGlow = 0; + ws2812FX->setPixelColor(led->position, RGBW_BLACK); + } else { + glowBrightness = wavePWMAfterGlow->getExponentialValue( + millis() - led->afterGlow + msAfterGlow); + } } - for (uint8_t number = 0; number < _LIGHT_MATRIX_SIZE; number++) { - uint8_t glowBrightness; - if (heatUp[number] > 0) { - if ((millis() - heatUp[number]) >= msHeatUp) { - heatUp[number] = 0; - for (int i = 0; i < _MAX_LEDS_PER_LIGHT; i++) { - if (ledLightMatrixPositions[number][i] != -1) { - ws2812FX->setPixelColor(ledLightMatrixPositions[number][i], ledLightMatrixColors[number][i]); - } - } - } - else { - glowBrightness = wavePWMHeatUp->getExponentialValue(millis() - heatUp[number]); - } - } - else if (afterGlow[number] > 0) { - if ((millis() - afterGlow[number]) >= msAfterGlow) { - afterGlow[number] = 0; - for (int i = 0; i < _MAX_LEDS_PER_LIGHT; i++) { - if (ledLightMatrixPositions[number][i] != -1) { - ws2812FX->setPixelColor(ledLightMatrixPositions[number][i], RGBW_BLACK); - } - } - } - else { - glowBrightness = wavePWMAfterGlow->getExponentialValue(millis() - afterGlow[number] + msAfterGlow); - } - } - - if (heatUp[number] > 0 || afterGlow[number] > 0) { - for (int i = 0; i < _MAX_LEDS_PER_LIGHT; i++) { - if (ledLightMatrixPositions[number][i] != -1) { - setDimmedPixelColor(ledLightMatrixPositions[number][i], ledLightMatrixColors[number][i], glowBrightness); - } - } - } + if (led->heatUp > 0 || led->afterGlow > 0) { + ws2812FX->setPixelColor( + led->position, getDimmedPixelColor(led->color, glowBrightness)); } + } } -void CombinedGiAndLightMatrixWS2812FXDevice::setDimmedPixelColor(int16_t led, uint32_t color, uint8_t brightness) { - uint8_t w = (color >> 24) & 0xFF; - uint8_t r = (color >> 16) & 0xFF; - uint8_t g = (color >> 8) & 0xFF; - uint8_t b = color & 0xFF; +uint32_t CombinedGiAndLightMatrixWS2812FXDevice::getDimmedPixelColor(uint32_t color, uint8_t brightness) { + uint8_t w = (color >> 24) & 0xFF; + uint8_t r = (color >> 16) & 0xFF; + uint8_t g = (color >> 8) & 0xFF; + uint8_t b = color & 0xFF; - // uint32_t for more space during the operation - uint32_t mult = brightness + 1; - b = (b * mult) >> 8; - g = (g * mult) >> 8; - r = (r * mult) >> 8; - w = (w * mult) >> 8; + // uint32_t for more space during the operation + uint32_t mult = brightness + 1; + b = (b * mult) >> 8; + g = (g * mult) >> 8; + r = (r * mult) >> 8; + w = (w * mult) >> 8; - ws2812FX->setPixelColor(led, r, g, b, w); + return (uint32_t(w) << 24) | (uint32_t(r) << 16) | (uint32_t(g) << 8) | + uint32_t(b); } -void CombinedGiAndLightMatrixWS2812FXDevice::setHeatUp() { - setHeatUp(30); -} +void CombinedGiAndLightMatrixWS2812FXDevice::setHeatUp() { setHeatUp(30); } void CombinedGiAndLightMatrixWS2812FXDevice::setAfterGlow() { - setAfterGlow(400); + setAfterGlow(400); } void CombinedGiAndLightMatrixWS2812FXDevice::setHeatUp(int ms) { - wavePWMHeatUp->setup(ms * 2); - msHeatUp = ms; + wavePWMHeatUp->setup(ms * 2); + msHeatUp = ms; } void CombinedGiAndLightMatrixWS2812FXDevice::setAfterGlow(int ms) { - wavePWMAfterGlow->setup(ms * 2); - msAfterGlow = ms; + wavePWMAfterGlow->setup(ms * 2); + msAfterGlow = ms; } diff --git a/src/EffectDevices/CombinedGiAndLightMatrixWS2812FXDevice.h b/src/EffectDevices/CombinedGiAndLightMatrixWS2812FXDevice.h index c439a3a..b64be7c 100644 --- a/src/EffectDevices/CombinedGiAndLightMatrixWS2812FXDevice.h +++ b/src/EffectDevices/CombinedGiAndLightMatrixWS2812FXDevice.h @@ -1,49 +1,88 @@ /* CombinedGiAndLightMatrixWS2812FXDevice.h - Created by Markus Kalkbrenner, 2021. + Created by Markus Kalkbrenner, 2021 - 2025. - Play more pinball! -*/ + WPC matrix numbering: + + | C1 | C2 | C3 | C4 | C5 | C6 | C7 | C8 + ---+----+----+----+----+----+----+----+---- + R1 | 11 | 21 | 31 | 41 | 51 | 61 | 71 | 81 + ---+----+----+----+----+----+----+----+---- + R2 | 12 | 22 | 32 | 42 | 52 | 62 | 72 | 82 + ---+----+----+----+----+----+----+----+---- + R3 | 13 | 23 | 33 | 43 | 53 | 63 | 73 | 83 + ---+----+----+----+----+----+----+----+---- + R4 | 14 | 24 | 34 | 44 | 54 | 64 | 74 | 84 + ---+----+----+----+----+----+----+----+---- + R5 | 15 | 25 | 35 | 45 | 55 | 65 | 75 | 85 + ---+----+----+----+----+----+----+----+---- + R6 | 16 | 26 | 36 | 46 | 56 | 66 | 76 | 86 + ---+----+----+----+----+----+----+----+---- + R7 | 17 | 27 | 37 | 47 | 57 | 67 | 77 | 87 + ---+----+----+----+----+----+----+----+---- + R8 | 18 | 28 | 38 | 48 | 58 | 68 | 78 | 88 + + DE and SYS4-SYS11 matrix numbering: + + | C1 | C2 | C3 | C4 | C5 | C6 | C7 | C8 + ---+----+----+----+----+----+----+----+---- + R1 | 1 | 9 | 17 | 25 | 33 | 41 | 49 | 57 + ---+----+----+----+----+----+----+----+---- + R2 | 2 | 10 | 18 | 26 | 34 | 42 | 50 | 58 + ---+----+----+----+----+----+----+----+---- + R3 | 3 | 11 | 19 | 27 | 35 | 43 | 51 | 59 + ---+----+----+----+----+----+----+----+---- + R4 | 4 | 12 | 20 | 28 | 36 | 44 | 52 | 60 + ---+----+----+----+----+----+----+----+---- + R5 | 5 | 13 | 21 | 29 | 37 | 45 | 53 | 61 + ---+----+----+----+----+----+----+----+---- + R6 | 6 | 14 | 22 | 30 | 38 | 46 | 54 | 62 + ---+----+----+----+----+----+----+----+---- + R7 | 7 | 15 | 23 | 31 | 39 | 47 | 55 | 63 + ---+----+----+----+----+----+----+----+---- + R8 | 8 | 16 | 24 | 32 | 40 | 48 | 56 | 64 -// WPC matrix numbering: -// -// | C1 | C2 | C3 | C4 | C5 | C6 | C7 | C8 -// ---+----+----+----+----+----+----+----+---- -// R1 | 11 | 21 | 31 | 41 | 51 | 61 | 71 | 81 -// ---+----+----+----+----+----+----+----+---- -// R2 | 12 | 22 | 32 | 42 | 52 | 62 | 72 | 82 -// ---+----+----+----+----+----+----+----+---- -// R3 | 13 | 23 | 33 | 43 | 53 | 63 | 73 | 83 -// ---+----+----+----+----+----+----+----+---- -// R4 | 14 | 24 | 34 | 44 | 54 | 64 | 74 | 84 -// ---+----+----+----+----+----+----+----+---- -// R5 | 15 | 25 | 35 | 45 | 55 | 65 | 75 | 85 -// ---+----+----+----+----+----+----+----+---- -// R6 | 16 | 26 | 36 | 46 | 56 | 66 | 76 | 86 -// ---+----+----+----+----+----+----+----+---- -// R7 | 17 | 27 | 37 | 47 | 57 | 67 | 77 | 87 -// ---+----+----+----+----+----+----+----+---- -// R8 | 18 | 28 | 38 | 48 | 58 | 68 | 78 | 88 - -// DE and SYS11 matrix numbering: -// -// | C1 | C2 | C3 | C4 | C5 | C6 | C7 | C8 -// ---+----+----+----+----+----+----+----+---- -// R1 | 1 | 9 | 17 | 25 | 33 | 41 | 49 | 57 -// ---+----+----+----+----+----+----+----+---- -// R2 | 2 | 10 | 18 | 26 | 34 | 42 | 50 | 58 -// ---+----+----+----+----+----+----+----+---- -// R3 | 3 | 11 | 19 | 27 | 35 | 43 | 51 | 59 -// ---+----+----+----+----+----+----+----+---- -// R4 | 4 | 12 | 20 | 28 | 36 | 44 | 52 | 60 -// ---+----+----+----+----+----+----+----+---- -// R5 | 5 | 13 | 21 | 29 | 37 | 45 | 53 | 61 -// ---+----+----+----+----+----+----+----+---- -// R6 | 6 | 14 | 22 | 30 | 38 | 46 | 54 | 62 -// ---+----+----+----+----+----+----+----+---- -// R7 | 7 | 15 | 23 | 31 | 39 | 47 | 55 | 63 -// ---+----+----+----+----+----+----+----+---- -// R8 | 8 | 16 | 24 | 32 | 40 | 48 | 56 | 64 + Whitestar and early SAM matrix numbering: + + | C1 | C2 | C3 | C4 | C5 | C6 | C7 | C8 + ---+----+----+----+----+----+----+----+---- + R1 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 + ---+----+----+----+----+----+----+----+---- + R2 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 + ---+----+----+----+----+----+----+----+---- + R3 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 + ---+----+----+----+----+----+----+----+---- + R4 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 + ---+----+----+----+----+----+----+----+---- + R5 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 + ---+----+----+----+----+----+----+----+---- + R6 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 + ---+----+----+----+----+----+----+----+---- + R7 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 + ---+----+----+----+----+----+----+----+---- + R8 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 + ---+----+----+----+----+----+----+----+---- + R9 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 + ---+----+----+----+----+----+----+----+---- + R10| 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 + + Bally 35 is speecial. It can drive 60 lamps. But using relays, 60 different + lamps could be driven, for example used in Elektra to drive the lamps of two + different playfields. In libpiname that is handled using a standard 8x8 matrix + and adding a second (virtual) 8x8 matrix. We don't see the state of the relay, + but get dedicated lamp numbers from 1-60 and 65-124. So lamps that get + triggered in combination with the relay get offset of 64 to their number. So + we have 120 lamps out of 128. + + Capcom uses two real 8x8 matrix and has no GI. So we have 128 CPU-controlled + lamps. + + In order to ease the AfterGlow handling and to avoid long iterations across + arrays and to reduce the number of addressable LED strings, we extend the + original Lamp Matrix. + Starting at position 129, we add custom LEDs which are added to the playfield + and which are not part of the original matrix. +*/ #ifndef CombinedGiAndLightMatrixWS2812FXDevice_h #define CombinedGiAndLightMatrixWS2812FXDevice_h @@ -51,93 +90,173 @@ #include #include -#include "WS2812FXDevice.h" +#include +#include +#include + #include "../EventDispatcher/Event.h" +#include "../EventDispatcher/EventDispatcher.h" #include "../EventDispatcher/EventListener.h" -#include "../InputDevices/GeneralIlluminationWPC.h" - -#define _MAX_LEDS_GI_STRING 50 -#define _LIGHT_MATRIX_SIZE 64 -#define _MAX_LEDS_PER_LIGHT 3 - -class CombinedGiAndLightMatrixWS2812FXDevice : public WS2812FXDevice, public EventListener { -public: - - CombinedGiAndLightMatrixWS2812FXDevice(WS2812FX* ws2812FX, int firstLED, int lastLED, int firstSegment, int lastSegment) : WS2812FXDevice(ws2812FX, firstLED, lastLED, firstSegment, lastSegment) { - wavePWMHeatUp = new WavePWM(); - wavePWMAfterGlow = new WavePWM(); - afterGlowSupport = true; - for (int number = 0; number < NUM_GI_STRINGS; number++) { - for (int i = 0; i < _MAX_LEDS_GI_STRING; i++) { - ledGIPositions[number][i] = -1; - } - } - for (int number = 0; number < _LIGHT_MATRIX_SIZE; number++) { - for (int i = 0; i < _MAX_LEDS_PER_LIGHT; i++) { - ledLightMatrixPositions[number][i] = -1; - } - } - } +#include "../PPUC.h" +#include "WS2812FXDevice.h" - void on(); - void off(); +// Number of WPC GI strings +#define NUM_GI_STRINGS 5 - void assignLedToGiString(uint8_t giString, int16_t led); - void assignLedToGiString(uint8_t giString, int16_t led, uint32_t color); +struct LED { + uint8_t number; // Original system number (GI string, light matrix pos, + // flasher port) + uint16_t position; // Position in WS2812 stripe + uint32_t color; // LED color + uint32_t heatUp; // Heat up effect value + uint32_t afterGlow; // After glow effect value - void assignLedRangeToGiString(uint8_t giString, int16_t first, int16_t last); + LED(uint8_t num, uint16_t pos, uint32_t col = 0) + : number(num), position(pos), color(col), heatUp(0), afterGlow(0) {} +}; - void assignLedToLightMatrix(uint8_t column, uint8_t row, int16_t led); - void assignLedToLightMatrix(uint8_t column, uint8_t row, int16_t led, uint32_t color); +class CombinedGiAndLightMatrixWS2812FXDevice : public WS2812FXDevice, + public EventListener { + public: + CombinedGiAndLightMatrixWS2812FXDevice(WS2812FX* ws2812FX, int firstLED, + int lastLED, int firstSegment, + int lastSegment, + EventDispatcher* eventDispatcher) + : WS2812FXDevice(ws2812FX, firstLED, lastLED, firstSegment, lastSegment) { + wavePWMHeatUp = new WavePWM(); + wavePWMAfterGlow = new WavePWM(); + afterGlowSupport = true; - void assignLedToLightMatrixWPC(uint8_t number, int16_t led); - void assignLedToLightMatrixWPC(uint8_t number, int16_t led, uint32_t color); + eventDispatcher->addListener(this, EVENT_SOURCE_GI); + eventDispatcher->addListener(this, EVENT_SOURCE_SOLENOID); + eventDispatcher->addListener(this, EVENT_SOURCE_LIGHT); + } - void assignLedToLightMatrixDE(uint8_t number, int16_t led); - void assignLedToLightMatrixDE(uint8_t number, int16_t led, uint32_t color); + ~CombinedGiAndLightMatrixWS2812FXDevice() { + delete wavePWMHeatUp; + delete wavePWMAfterGlow; + } - void assignLedToLightMatrixSYS11(uint8_t number, int16_t led); - void assignLedToLightMatrixSYS11(uint8_t number, int16_t led, uint32_t color); + void on(); + void off(); - void setDimmedPixelColor(int16_t led, uint32_t color, uint8_t brightness); + void assignLedToGiString(uint8_t number, int16_t led, uint32_t color = 0) { + giLEDs.emplace_back(number, led, color); + rebuildGIIndex(); + } - void setHeatUp(); - void setAfterGlow(); + void assignLedToLightMatrix(uint8_t number, int16_t led, uint32_t color = 0) { + lightMatrixLEDs.emplace_back(number, led, color); + rebuildLightMatrixIndex(); + } - void setHeatUp(int ms); - void setAfterGlow(int ms); + void assignLedToFlasher(uint8_t number, int16_t led, uint32_t color = 0) { + flasherLEDs.emplace_back(number, led, color); + rebuildFlasherIndex(); + } - void handleEvent(Event* event); - void handleEvent(ConfigEvent* event) {} - void updateAfterGlow(); + // Fast access methods + std::vector getGILEDsByNumber(uint8_t number) { + auto it = giIndex.find(number); + if (it != giIndex.end()) return it->second; + return {}; + } -protected: - int16_t numLEDsGI[NUM_GI_STRINGS] = {0}; + std::vector getLightMatrixLEDsByNumber(uint8_t number) { + auto it = lightMatrixIndex.find(number); + if (it != lightMatrixIndex.end()) return it->second; + return {}; + } - int16_t ledGIPositions[NUM_GI_STRINGS][_MAX_LEDS_GI_STRING] = {{0}}; - uint32_t ledGIColors[NUM_GI_STRINGS][_MAX_LEDS_GI_STRING] = {{0}}; - uint8_t sourceGIBrightness[NUM_GI_STRINGS] = {0}; - uint8_t targetGIBrightness[NUM_GI_STRINGS] = {0}; + std::vector getFlasherLEDsByNumber(uint8_t number) { + auto it = flasherIndex.find(number); + if (it != flasherIndex.end()) return it->second; + return {}; + } - // Internally we store the positions in Data East numbering from 1 to 64. - // The WPC-specific functions convert the WPC-specific numbering. - int16_t ledLightMatrixPositions[_LIGHT_MATRIX_SIZE][_MAX_LEDS_PER_LIGHT] = {{0}}; - uint32_t ledLightMatrixColors[_LIGHT_MATRIX_SIZE][_MAX_LEDS_PER_LIGHT] = {{0}}; + std::vector getChangingGILEDsByNumber(uint8_t number) { + std::vector result; + for (auto led : getGILEDsByNumber(number)) { + if (led->heatUp > 0 || led->afterGlow > 0) { + result.push_back(led); + } + } + return result; + } - WavePWM* wavePWMHeatUp; - WavePWM* wavePWMAfterGlow; + std::vector getChangingLightMatrixAndFlasherLEDs() { + std::vector result; + for (auto& led : getLightMatrixLEDs()) { + if (led.heatUp > 0 || led.afterGlow > 0) { + result.push_back(&led); // Use &led to get the pointer + } + } + for (auto& led : getFlasherLEDs()) { + if (led.heatUp > 0 || led.afterGlow > 0) { + result.push_back(&led); // Use &led to get the pointer + } + } + return result; + } + + // Direct access to vectors for iteration + std::vector& getGILEDs() { return giLEDs; } + std::vector& getLightMatrixLEDs() { return lightMatrixLEDs; } + std::vector& getFlasherLEDs() { return flasherLEDs; } + + uint32_t getDimmedPixelColor(uint32_t color, uint8_t brightness); + void setHeatUp(); + void setAfterGlow(); + void setHeatUp(int ms); + void setAfterGlow(int ms); + void handleEvent(Event* event); + void handleEvent(ConfigEvent* event) {} + void updateAfterGlow(); + + protected: + std::vector giLEDs; + std::vector lightMatrixLEDs; + std::vector flasherLEDs; + + // Indexes for fast lookup by number + std::unordered_map> giIndex; + std::unordered_map> lightMatrixIndex; + std::unordered_map> flasherIndex; + + uint8_t sourceGIBrightness[NUM_GI_STRINGS] = {0}; + uint8_t targetGIBrightness[NUM_GI_STRINGS] = {0}; + + void intializeNewLEDState(LED* led, bool on); + + void rebuildGIIndex() { + giIndex.clear(); + for (auto& led : giLEDs) { + giIndex[led.number].push_back(&led); + } + } + + void rebuildLightMatrixIndex() { + lightMatrixIndex.clear(); + for (auto& led : lightMatrixLEDs) { + lightMatrixIndex[led.number].push_back(&led); + } + } + + void rebuildFlasherIndex() { + flasherIndex.clear(); + for (auto& led : flasherLEDs) { + flasherIndex[led.number].push_back(&led); + } + } - // When no effects are running, we're in normal GI and Light Matrix mode. - bool stopped = false; // Never stop the updates. - bool effectRunning = false; - bool wpc = false; + WavePWM* wavePWMHeatUp; + WavePWM* wavePWMAfterGlow; - int16_t msHeatUp = 0; - int16_t msAfterGlow = 0; - uint32_t heatUpGI[NUM_GI_STRINGS] = {0}; - uint32_t afterGlowGI[NUM_GI_STRINGS] = {0}; - uint32_t heatUp[_LIGHT_MATRIX_SIZE] = {0}; - uint32_t afterGlow[_LIGHT_MATRIX_SIZE] = {0}; + // When no effects are running, we're in normal GI and Light Matrix mode. + bool stopped = false; // Never stop the updates. + bool effectRunning = false; + int16_t msHeatUp = 0; + int16_t msAfterGlow = 0; }; #endif diff --git a/src/EffectDevices/Definitions.h b/src/EffectDevices/Definitions.h index 6e5f9f0..f8f44c2 100644 --- a/src/EffectDevices/Definitions.h +++ b/src/EffectDevices/Definitions.h @@ -6,42 +6,42 @@ #ifndef Definitions_h #define Definitions_h -#if defined(__IMXRT1062__) // Teensy 4.1 - #include +#if defined(__IMXRT1062__) // Teensy 4.1 +#include #else - #include +#include - #define WS2812_RGB NEO_RGB - #define WS2812_RBG NEO_RBG - #define WS2812_GBR NEO_GBR - #define WS2812_GRB NEO_GRB - #define WS2812_BRG NEO_BRG - #define WS2812_BGR NEO_BGR +#define WS2812_RGB NEO_RGB +#define WS2812_RBG NEO_RBG +#define WS2812_GBR NEO_GBR +#define WS2812_GRB NEO_GRB +#define WS2812_BRG NEO_BRG +#define WS2812_BGR NEO_BGR - #define WS2812_RGBW NEO_RGBW - #define WS2812_RBGW NEO_RBGW - #define WS2812_GBRW NEO_GBRW - #define WS2812_GRBW NEO_GRBW - #define WS2812_BRGW NEO_BRGW - #define WS2812_BGRW NEO_BGRW - #define WS2812_WRGB NEO_WRGB - #define WS2812_WRBG NEO_WRBG - #define WS2812_WGRB NEO_WGRB - #define WS2812_WGBR NEO_WGBR - #define WS2812_WBRG NEO_WBRG - #define WS2812_WBGR NEO_WBGR - #define WS2812_RWGB NEO_RWGB - #define WS2812_RWBG NEO_RWBG - #define WS2812_GWRB NEO_GWRB - #define WS2812_GWBR NEO_GWBR - #define WS2812_BWRG NEO_BWRG - #define WS2812_BWGR NEO_BWGR - #define WS2812_RGWB NEO_RGWB - #define WS2812_RBWG NEO_RBWG - #define WS2812_GRWB NEO_GRWB - #define WS2812_GBWR NEO_GBWR - #define WS2812_BRWG NEO_BRWG - #define WS2812_BGWR NEO_BGWR +#define WS2812_RGBW NEO_RGBW +#define WS2812_RBGW NEO_RBGW +#define WS2812_GBRW NEO_GBRW +#define WS2812_GRBW NEO_GRBW +#define WS2812_BRGW NEO_BRGW +#define WS2812_BGRW NEO_BGRW +#define WS2812_WRGB NEO_WRGB +#define WS2812_WRBG NEO_WRBG +#define WS2812_WGRB NEO_WGRB +#define WS2812_WGBR NEO_WGBR +#define WS2812_WBRG NEO_WBRG +#define WS2812_WBGR NEO_WBGR +#define WS2812_RWGB NEO_RWGB +#define WS2812_RWBG NEO_RWBG +#define WS2812_GWRB NEO_GWRB +#define WS2812_GWBR NEO_GWBR +#define WS2812_BWRG NEO_BWRG +#define WS2812_BWGR NEO_BWGR +#define WS2812_RGWB NEO_RGWB +#define WS2812_RBWG NEO_RBWG +#define WS2812_GRWB NEO_GRWB +#define WS2812_GBWR NEO_GBWR +#define WS2812_BRWG NEO_BRWG +#define WS2812_BGWR NEO_BGWR #endif diff --git a/src/EffectDevices/EffectDevice.cpp b/src/EffectDevices/EffectDevice.cpp index 1057f6f..f922a25 100644 --- a/src/EffectDevices/EffectDevice.cpp +++ b/src/EffectDevices/EffectDevice.cpp @@ -1,5 +1,3 @@ #include "EffectDevice.h" -void EffectDevice::off() { - reset(); -} +void EffectDevice::off() { reset(); } diff --git a/src/EffectDevices/EffectDevice.h b/src/EffectDevices/EffectDevice.h index 6fd0676..4dea4e7 100644 --- a/src/EffectDevices/EffectDevice.h +++ b/src/EffectDevices/EffectDevice.h @@ -9,14 +9,14 @@ #define EFFECTDEVICE_h class EffectDevice { -public: - virtual void reset() = 0; + public: + virtual void reset() = 0; - virtual void on() = 0; + virtual void on() = 0; - virtual void off(); + virtual void off(); - virtual ~EffectDevice() {} + virtual ~EffectDevice() {} }; #endif diff --git a/src/EffectDevices/LedBuiltInDevice.cpp b/src/EffectDevices/LedBuiltInDevice.cpp index 574a41a..a5202c4 100644 --- a/src/EffectDevices/LedBuiltInDevice.cpp +++ b/src/EffectDevices/LedBuiltInDevice.cpp @@ -1,9 +1,5 @@ #include "LedBuiltInDevice.h" -void LedBuiltInDevice::on() { - digitalWrite(LED_BUILTIN, HIGH); -} +void LedBuiltInDevice::on() { digitalWrite(LED_BUILTIN, HIGH); } -void LedBuiltInDevice::reset() { - digitalWrite(LED_BUILTIN, LOW); -} +void LedBuiltInDevice::reset() { digitalWrite(LED_BUILTIN, LOW); } diff --git a/src/EffectDevices/LedBuiltInDevice.h b/src/EffectDevices/LedBuiltInDevice.h index f54f368..ad95d57 100644 --- a/src/EffectDevices/LedBuiltInDevice.h +++ b/src/EffectDevices/LedBuiltInDevice.h @@ -13,15 +13,12 @@ #include "EffectDevice.h" class LedBuiltInDevice : public EffectDevice { -public: - LedBuiltInDevice() { - pinMode(LED_BUILTIN, OUTPUT); - } + public: + LedBuiltInDevice() { pinMode(LED_BUILTIN, OUTPUT); } - void on(); - - void reset(); + void on(); + void reset(); }; #endif diff --git a/src/EffectDevices/NullDevice.cpp b/src/EffectDevices/NullDevice.cpp index 7a51026..4ea05e5 100644 --- a/src/EffectDevices/NullDevice.cpp +++ b/src/EffectDevices/NullDevice.cpp @@ -1,7 +1,5 @@ #include "NullDevice.h" -void NullDevice::on() { -} +void NullDevice::on() {} -void NullDevice::reset() { -} +void NullDevice::reset() {} diff --git a/src/EffectDevices/NullDevice.h b/src/EffectDevices/NullDevice.h index 1e25976..db9a3c6 100644 --- a/src/EffectDevices/NullDevice.h +++ b/src/EffectDevices/NullDevice.h @@ -13,12 +13,10 @@ #include "EffectDevice.h" class NullDevice : public EffectDevice { -public: - - void on(); - - void reset(); + public: + void on(); + void reset(); }; #endif diff --git a/src/EffectDevices/RgbPWMDevice.cpp b/src/EffectDevices/RgbPWMDevice.cpp index 7f7406c..dbaefae 100644 --- a/src/EffectDevices/RgbPWMDevice.cpp +++ b/src/EffectDevices/RgbPWMDevice.cpp @@ -1,33 +1,21 @@ #include "RgbStripDevice.h" void RgbStripDevice::setPWM(uint8_t pwm) { - setPWMRed(pwm); - setPWMGreen(pwm); - setPWMBlue(pwm); + setPWMRed(pwm); + setPWMGreen(pwm); + setPWMBlue(pwm); - currentPWM = pwm; + currentPWM = pwm; } -void RgbStripDevice::setPWMRed(uint8_t pwm) { - analogWrite(pin, pwm); -} +void RgbStripDevice::setPWMRed(uint8_t pwm) { analogWrite(pin, pwm); } -void RgbStripDevice::setPWMGreen(uint8_t pwm) { - analogWrite(pinGreen, pwm); -} +void RgbStripDevice::setPWMGreen(uint8_t pwm) { analogWrite(pinGreen, pwm); } -void RgbStripDevice::setPWMBlue(uint8_t pwm) { - analogWrite(pinBlue, pwm); -} +void RgbStripDevice::setPWMBlue(uint8_t pwm) { analogWrite(pinBlue, pwm); } -WavePWM* RgbStripDevice::getWavePWMRed() { - return wavePWM; -} +WavePWM* RgbStripDevice::getWavePWMRed() { return wavePWM; } -WavePWM* RgbStripDevice::getWavePWMGreen() { - return wavePWMGreen; -} +WavePWM* RgbStripDevice::getWavePWMGreen() { return wavePWMGreen; } -WavePWM* RgbStripDevice::getWavePWMBlue() { - return wavePWMBlue; -} +WavePWM* RgbStripDevice::getWavePWMBlue() { return wavePWMBlue; } diff --git a/src/EffectDevices/RgbStripDevice.h b/src/EffectDevices/RgbStripDevice.h index 027b66f..b2f13d7 100644 --- a/src/EffectDevices/RgbStripDevice.h +++ b/src/EffectDevices/RgbStripDevice.h @@ -14,32 +14,33 @@ #include "WavePWMDevice.h" class RgbStripDevice : public WavePWMDevice { -public: - RgbStripDevice(int pinRed, int pinGreen, int pinBlue) : WavePWMDevice(pinRed){ - this->wavePWMGreen = new WavePWM(); - this->wavePWMBlue = new WavePWM(); - this->pinGreen = pinGreen; - this->pinBlue = pinBlue; - pinMode(pinGreen, OUTPUT); - pinMode(pinBlue, OUTPUT); - } - - void setPWM(uint8_t pwm); - - void setPWMRed(uint8_t pwm); - void setPWMGreen(uint8_t pwm); - void setPWMBlue(uint8_t pwm); - - WavePWM* getWavePWMRed(); - WavePWM* getWavePWMGreen(); - WavePWM* getWavePWMBlue(); - -protected: - WavePWM* wavePWMGreen; - WavePWM* wavePWMBlue; - - int pinGreen; - int pinBlue; + public: + RgbStripDevice(int pinRed, int pinGreen, int pinBlue) + : WavePWMDevice(pinRed) { + this->wavePWMGreen = new WavePWM(); + this->wavePWMBlue = new WavePWM(); + this->pinGreen = pinGreen; + this->pinBlue = pinBlue; + pinMode(pinGreen, OUTPUT); + pinMode(pinBlue, OUTPUT); + } + + void setPWM(uint8_t pwm); + + void setPWMRed(uint8_t pwm); + void setPWMGreen(uint8_t pwm); + void setPWMBlue(uint8_t pwm); + + WavePWM* getWavePWMRed(); + WavePWM* getWavePWMGreen(); + WavePWM* getWavePWMBlue(); + + protected: + WavePWM* wavePWMGreen; + WavePWM* wavePWMBlue; + + int pinGreen; + int pinBlue; }; #endif diff --git a/src/EffectDevices/WS2812FXDevice.cpp b/src/EffectDevices/WS2812FXDevice.cpp index f4eb0f0..09ee37c 100644 --- a/src/EffectDevices/WS2812FXDevice.cpp +++ b/src/EffectDevices/WS2812FXDevice.cpp @@ -1,57 +1,39 @@ #include "WS2812FXDevice.h" void WS2812FXDevice::on() { - reset(); - stopped = false; + reset(); + stopped = false; } void WS2812FXDevice::off() { - reset(); - stopped = true; + reset(); + stopped = true; } void WS2812FXDevice::reset() { - ws2812FX->setSegment(firstSegment, firstLED, lastLED, FX_MODE_STATIC, RGBW_BLACK, 1, NO_OPTIONS); + ws2812FX->setSegment(firstSegment, firstLED, lastLED, FX_MODE_STATIC, + RGBW_BLACK, 1, NO_OPTIONS); } -WS2812FX* WS2812FXDevice::getWS2812FX() { - return ws2812FX; -} +WS2812FX* WS2812FXDevice::getWS2812FX() { return ws2812FX; } -int WS2812FXDevice::getFirstLED() { - return firstLED; -} +int WS2812FXDevice::getFirstLED() { return firstLED; } -int WS2812FXDevice::getlastLED() { - return lastLED; -} +int WS2812FXDevice::getlastLED() { return lastLED; } -int WS2812FXDevice::getNumLEDs() { - return lastLED - firstLED + 1; -} +int WS2812FXDevice::getNumLEDs() { return lastLED - firstLED + 1; } -int WS2812FXDevice::getFirstSegment() { - return firstSegment; -} +int WS2812FXDevice::getFirstSegment() { return firstSegment; } -int WS2812FXDevice::getLastSegment() { - return lastSegment; -} +int WS2812FXDevice::getLastSegment() { return lastSegment; } -int WS2812FXDevice::getNumSegments() { - return lastSegment - firstSegment + 1; -} +int WS2812FXDevice::getNumSegments() { return lastSegment - firstSegment + 1; } -bool WS2812FXDevice::isStopped() { - return stopped; -} +bool WS2812FXDevice::isStopped() { return stopped; } void WS2812FXDevice::setBrightness(byte b) { - brightness = b; - ws2812FX->setBrightness(brightness); -} - -byte WS2812FXDevice::getBrightness() { - return brightness; + brightness = b; + ws2812FX->setBrightness(brightness); } +byte WS2812FXDevice::getBrightness() { return brightness; } diff --git a/src/EffectDevices/WS2812FXDevice.h b/src/EffectDevices/WS2812FXDevice.h index 196399b..ca4698d 100644 --- a/src/EffectDevices/WS2812FXDevice.h +++ b/src/EffectDevices/WS2812FXDevice.h @@ -13,62 +13,69 @@ #include "EffectDevice.h" -#define RGBW_BLACK (uint32_t)0x00000000 -#define RGBW_PUREWHITE (uint32_t)0xFF000000 +#define RGBW_BLACK (uint32_t)0x00000000 +#define RGBW_PUREWHITE (uint32_t)0xFF000000 #define RGBW_ULTRAWHITE (uint32_t)0xFFFFFFFF class WS2812FXDevice : public EffectDevice { -public: - WS2812FXDevice(WS2812FX* ws2812FX, int firstLED, int lastLED, int firstSegment, int lastSegment) { - this->ws2812FX = ws2812FX; - this->firstLED = firstLED; - this->lastLED = lastLED; - this->firstSegment = firstSegment; - this->lastSegment = lastSegment; + public: + WS2812FXDevice(WS2812FX* ws2812FX, int firstLED, int lastLED, + int firstSegment, int lastSegment) { + this->ws2812FX = ws2812FX; + this->firstLED = firstLED; + this->lastLED = lastLED; + this->firstSegment = firstSegment; + this->lastSegment = lastSegment; + } + + ~WS2812FXDevice() { + reset(); + + if (ws2812FX) { + delete ws2812FX; } + } - virtual void on(); + virtual void on(); - virtual void off(); + virtual void off(); - void reset(); + void reset(); - WS2812FX* getWS2812FX(); + WS2812FX* getWS2812FX(); - int getFirstLED(); - int getlastLED(); - int getNumLEDs(); + int getFirstLED(); + int getlastLED(); + int getNumLEDs(); - int getFirstSegment(); - int getLastSegment(); - int getNumSegments(); + int getFirstSegment(); + int getLastSegment(); + int getNumSegments(); - bool isStopped(); + bool isStopped(); - void setBrightness(byte b); - byte getBrightness(); + void setBrightness(byte b); + byte getBrightness(); - void _reduceLEDs(int lastLED, int lastSegment) { - this->lastLED = lastLED; - this->lastSegment = lastSegment; - } + void _reduceLEDs(int lastLED, int lastSegment) { + this->lastLED = lastLED; + this->lastSegment = lastSegment; + } - bool hasAfterGlowSupport() { - return afterGlowSupport; - }; + bool hasAfterGlowSupport() { return afterGlowSupport; }; -protected: - WS2812FX* ws2812FX; + protected: + WS2812FX* ws2812FX; - int firstLED; - int lastLED; - int firstSegment; - int lastSegment; + int firstLED; + int lastLED; + int firstSegment; + int lastSegment; - byte brightness = 64; + byte brightness = 50; - bool stopped = true; - bool afterGlowSupport = false; + bool stopped = true; + bool afterGlowSupport = false; }; #endif diff --git a/src/EffectDevices/WavePWMDevice.cpp b/src/EffectDevices/WavePWMDevice.cpp index 4b5c883..baead2d 100644 --- a/src/EffectDevices/WavePWMDevice.cpp +++ b/src/EffectDevices/WavePWMDevice.cpp @@ -1,22 +1,29 @@ #include "WavePWMDevice.h" -void WavePWMDevice::on() { - reset(); -} +#include "../EventDispatcher/CrossLinkDebugger.h" -void WavePWMDevice::reset() { - setPWM(0); -} +void WavePWMDevice::on() { reset(); } + +void WavePWMDevice::reset() { setPWM(0); } void WavePWMDevice::setPWM(uint8_t pwm) { - analogWrite(pin, pwm); - currentPWM = pwm; + if (powerOn && coinDoorClosed) { + analogWrite(pin, (int)(pwm * pwmLimitFactor)); + } + currentPWM = pwm; } -uint8_t WavePWMDevice::getPWM() { - return currentPWM; -} +uint8_t WavePWMDevice::getPWM() { return currentPWM; } -WavePWM* WavePWMDevice::getWavePWM() { - return wavePWM; -} +WavePWM* WavePWMDevice::getWavePWM() { return wavePWM; } + +void WavePWMDevice::handleEvent(Event* event) { + HighPowerOffAware::handleEvent(event); + + if (!(powerOn && coinDoorClosed) && powerToggled) { + // Deactivate the output. + digitalWrite(pin, 0); + powerOn = false; + CrossLinkDebugger::debug("Deactivated PWM device on port %d", pin); + } +} \ No newline at end of file diff --git a/src/EffectDevices/WavePWMDevice.h b/src/EffectDevices/WavePWMDevice.h index 2c36949..dcf6a35 100644 --- a/src/EffectDevices/WavePWMDevice.h +++ b/src/EffectDevices/WavePWMDevice.h @@ -11,31 +11,51 @@ #include #include +#include "../HighPowerOffAware.h" #include "EffectDevice.h" -class WavePWMDevice : public EffectDevice { -public: - WavePWMDevice(int pin) { - this->wavePWM = new WavePWM(); - this->pin = pin; - pinMode(pin, OUTPUT); - } +class WavePWMDevice : public EffectDevice, public HighPowerOffAware { + public: + WavePWMDevice(int pin) { + this->wavePWM = new WavePWM(); + this->pin = pin; + pinMode(pin, OUTPUT); + } - void on(); + WavePWMDevice(int pin, EventDispatcher* eventDispatcher) + : HighPowerOffAware(eventDispatcher) { + this->wavePWM = new WavePWM(); + this->pin = pin; + pinMode(pin, OUTPUT); + } - void reset(); + WavePWMDevice(int pin, uint8_t maxPWM, EventDispatcher* eventDispatcher) + : HighPowerOffAware(eventDispatcher) { + this->wavePWM = new WavePWM(); + this->pin = pin; + // A hard limit, for example to run a 12V motor at 48V, maxPWM has to be 63. + this->pwmLimitFactor = maxPWM / 255; + pinMode(pin, OUTPUT); + } - virtual void setPWM(uint8_t pwm); + void on(); - uint8_t getPWM(); + void reset(); - WavePWM* getWavePWM(); + virtual void setPWM(uint8_t pwm); -protected: - WavePWM* wavePWM; + uint8_t getPWM(); - int pin; - uint8_t currentPWM = 0; + WavePWM* getWavePWM(); + + void handleEvent(Event* event); + + protected: + WavePWM* wavePWM; + + int pin; + uint8_t currentPWM = 0; + float pwmLimitFactor = 1.0; }; #endif diff --git a/src/Effects/Effect.cpp b/src/Effects/Effect.cpp index cdbaa2d..081471e 100644 --- a/src/Effects/Effect.cpp +++ b/src/Effects/Effect.cpp @@ -1,58 +1,45 @@ #include "Effect.h" -Effect::Effect() { -} +Effect::Effect() {} -void Effect::setEventDispatcher(EventDispatcher* eD) { - eventDispatcher = eD; -} +void Effect::setEventDispatcher(EventDispatcher* eD) { eventDispatcher = eD; } -void Effect::setDevice(EffectDevice* effectDevice) { - device = effectDevice; -} +void Effect::setDevice(EffectDevice* effectDevice) { device = effectDevice; } -void Effect::dispatch(Event* event) { - eventDispatcher->dispatch(event); -} +void Effect::dispatch(Event* event) { eventDispatcher->dispatch(event); } -bool Effect::isRunning() { - return running; -} +bool Effect::isRunning() { return running; } void Effect::start(int r) { - //Serial.print("effect started, repeat "); - //Serial.println(r); - - running = true; - repeat = r; - stage = 0; - ms = 0; - _ms = millis(); -} + // Serial.print("effect started, repeat "); + // Serial.println(r); -void Effect::updateMillis() { - ms = millis() - _ms; + running = true; + repeat = r; + stage = 0; + ms = 0; + _ms = millis(); } +void Effect::updateMillis() { ms = millis() - _ms; } + void Effect::resetMillis() { - ms = 0; - _ms = millis(); + ms = 0; + _ms = millis(); } void Effect::stop() { - if (repeat > 0) { - start(--repeat); - } - else if (repeat == -1) { - start(-1); - } - else { - terminate(); - } + if (repeat > 0) { + start(--repeat); + } else if (repeat == -1) { + start(-1); + } else { + terminate(); + } } void Effect::terminate() { - running = false; - stage = 0; - repeat = 0; + running = false; + stage = 0; + repeat = 0; } diff --git a/src/Effects/Effect.h b/src/Effects/Effect.h index 8186259..8c817ba 100644 --- a/src/Effects/Effect.h +++ b/src/Effects/Effect.h @@ -11,42 +11,43 @@ #include #include "../EffectDevices/EffectDevice.h" -#include "../EventDispatcher/EventDispatcher.h" #include "../EventDispatcher/Event.h" +#include "../EventDispatcher/EventDispatcher.h" class Effect { -public: - Effect(); + public: + Effect(); - bool isRunning(); + bool isRunning(); - virtual void start(int repeat = 0); + virtual void start(int repeat = 0); - virtual void stop(); + virtual void stop(); - virtual void terminate(); + virtual void terminate(); - virtual void update() = 0; + virtual void update() = 0; - virtual void updateMillis(); + virtual void updateMillis(); - virtual void resetMillis(); + virtual void resetMillis(); - void setEventDispatcher(EventDispatcher* eD); + void setEventDispatcher(EventDispatcher* eD); - virtual void setDevice(EffectDevice* effectDevice); + virtual void setDevice(EffectDevice* effectDevice); -protected: - void dispatch(Event* event); + protected: + void dispatch(Event* event); - EventDispatcher* eventDispatcher; - EffectDevice* device; + EventDispatcher* eventDispatcher; + EffectDevice* device; - bool running = false; - int repeat = 0; // -1 is endless, 0 means play once, 3 means repeat three times, ... - unsigned int ms; - unsigned long _ms; - int stage = 0; + bool running = false; + int repeat = + 0; // -1 is endless, 0 means play once, 3 means repeat three times, ... + unsigned int ms; + unsigned long _ms; + int stage = 0; }; #endif diff --git a/src/Effects/EffectContainer.h b/src/Effects/EffectContainer.h index 045ef32..2a8c925 100644 --- a/src/Effects/EffectContainer.h +++ b/src/Effects/EffectContainer.h @@ -8,45 +8,50 @@ #ifndef EFFECTCONTAINER_h #define EFFECTCONTAINER_h -#include "Effect.h" #include "../EffectDevices/EffectDevice.h" #include "../EventDispatcher/Event.h" +#include "Effect.h" struct EffectContainer { - - Effect* effect; - EffectDevice* device; - Event* event; - int priority; // 0 = default, higher value means higher priority - int repeat; // 0 = no repeat, >=1 times repeat - int mode; // -1 = always, 0 = normal play, >=1 multiball, mode, scene, ball save ... - - EffectContainer(Effect* ef, EffectDevice* d, Event* ev, int p) { - effect = ef; - device = d; - event = ev; - priority = p; - repeat = 1; - mode = 0; - } - - EffectContainer(Effect* ef, EffectDevice* d, Event* ev, int p, int r) { - effect = ef; - device = d; - event = ev; - priority = p; - repeat = r; - mode = 0; - } - - EffectContainer(Effect* ef, EffectDevice* d, Event* ev, int p, int r, int m) { - effect = ef; - device = d; - event = ev; - priority = p; - repeat = r; - mode = m; - } + Effect* effect; + EffectDevice* device; + Event* event; + int priority; // 0 = default, higher value means higher priority + int repeat; // 0 = no repeat, >=1 times repeat + int mode; // -1 = always, 0 = normal play, >=1 multiball, mode, scene, ball + // save ... + + EffectContainer(Effect* ef, EffectDevice* d, Event* ev, int p) { + effect = ef; + device = d; + event = ev; + priority = p; + repeat = 1; + mode = 0; + } + + EffectContainer(Effect* ef, EffectDevice* d, Event* ev, int p, int r) { + effect = ef; + device = d; + event = ev; + priority = p; + repeat = r; + mode = 0; + } + + EffectContainer(Effect* ef, EffectDevice* d, Event* ev, int p, int r, int m) { + effect = ef; + device = d; + event = ev; + priority = p; + repeat = r; + mode = m; + } + + ~EffectContainer() { + if (effect) delete effect; + if (event) delete event; + } }; #endif diff --git a/src/Effects/ImpulsePWMEffect.cpp b/src/Effects/ImpulsePWMEffect.cpp index e1cfd8c..c27520a 100644 --- a/src/Effects/ImpulsePWMEffect.cpp +++ b/src/Effects/ImpulsePWMEffect.cpp @@ -1,17 +1,17 @@ #include "ImpulsePWMEffect.h" void ImpulsePWMEffect::update() { - if (ms > waveDuration) { - stop(); - return; - } + if (ms > waveDuration) { + stop(); + return; + } - uint8_t pwm = compression * wavePWM->getQuadraticValue(ms); + uint8_t pwm = compression * wavePWM->getQuadraticValue(ms); - // Safety net. - if (pwm > 255) { - pwm = 255; - } + // Safety net. + if (pwm > 255) { + pwm = 255; + } - ((WavePWMDevice *) device)->setPWM(pwm); + ((WavePWMDevice *)device)->setPWM(pwm); } diff --git a/src/Effects/ImpulsePWMEffect.h b/src/Effects/ImpulsePWMEffect.h index 95f7948..ddf7225 100644 --- a/src/Effects/ImpulsePWMEffect.h +++ b/src/Effects/ImpulsePWMEffect.h @@ -11,18 +11,18 @@ #include #include -#include "WavePWMEffect.h" #include "../EffectDevices/EffectDevice.h" #include "../EffectDevices/WavePWMDevice.h" +#include "WavePWMEffect.h" class ImpulsePWMEffect : public WavePWMEffect { -public: - - ImpulsePWMEffect(unsigned int frequency, uint8_t maxIntensity = 255) : WavePWMEffect(frequency, maxIntensity) { - // nop - } + public: + ImpulsePWMEffect(unsigned int frequency, uint8_t maxIntensity = 255) + : WavePWMEffect(frequency, maxIntensity) { + // nop + } - virtual void update(); + virtual void update(); }; #endif diff --git a/src/Effects/LedBlinkEffect.cpp b/src/Effects/LedBlinkEffect.cpp index 91b2c98..e41b9c9 100644 --- a/src/Effects/LedBlinkEffect.cpp +++ b/src/Effects/LedBlinkEffect.cpp @@ -1,28 +1,23 @@ #include "LedBlinkEffect.h" void LedBlinkEffect::update() { - if (stage == 0) { - device->off(); - ++stage; - } - else if (stage == 1 && ms >= 200) { - device->on(); - ++stage; - } - else if (stage == 2 && ms >= 400) { - device->off(); - ++stage; - } - else if (stage == 3 && ms >= 600) { - device->on(); - ++stage; - } - else if (stage == 4 && ms >= 800) { - device->off(); - ++stage; - } - else if (ms >= 1000) { - device->on(); - stop(); - } + if (stage == 0) { + device->off(); + ++stage; + } else if (stage == 1 && ms >= 200) { + device->on(); + ++stage; + } else if (stage == 2 && ms >= 400) { + device->off(); + ++stage; + } else if (stage == 3 && ms >= 600) { + device->on(); + ++stage; + } else if (stage == 4 && ms >= 800) { + device->off(); + ++stage; + } else if (ms >= 1000) { + device->on(); + stop(); + } } diff --git a/src/Effects/LedBlinkEffect.h b/src/Effects/LedBlinkEffect.h index bdc8c7f..1db31e2 100644 --- a/src/Effects/LedBlinkEffect.h +++ b/src/Effects/LedBlinkEffect.h @@ -13,9 +13,8 @@ #include "Effect.h" class LedBlinkEffect : public Effect { -public: - void update(); - + public: + void update(); }; #endif diff --git a/src/Effects/LedOnEffect.cpp b/src/Effects/LedOnEffect.cpp new file mode 100644 index 0000000..9b3eb37 --- /dev/null +++ b/src/Effects/LedOnEffect.cpp @@ -0,0 +1,6 @@ +#include "LedOnEffect.h" + +void LedOnEffect::update() { + device->on(); + stop(); +} diff --git a/src/Effects/LedOnEffect.h b/src/Effects/LedOnEffect.h new file mode 100644 index 0000000..29dd892 --- /dev/null +++ b/src/Effects/LedOnEffect.h @@ -0,0 +1,20 @@ +/* + LedOnEffect.h + Created by Markus Kalkbrenner, 2023. + + Play more pinball! +*/ + +#ifndef LedOnEffect_h +#define LedOnEffect_h + +#include + +#include "Effect.h" + +class LedOnEffect : public Effect { + public: + void update(); +}; + +#endif diff --git a/src/Effects/NullEffect.cpp b/src/Effects/NullEffect.cpp index 9e5cd79..2ac623e 100644 --- a/src/Effects/NullEffect.cpp +++ b/src/Effects/NullEffect.cpp @@ -1,6 +1,6 @@ #include "NullEffect.h" void NullEffect::update() { - device->reset(); - stop(); + device->reset(); + stop(); } diff --git a/src/Effects/NullEffect.h b/src/Effects/NullEffect.h index a990721..54a00da 100644 --- a/src/Effects/NullEffect.h +++ b/src/Effects/NullEffect.h @@ -13,9 +13,8 @@ #include "Effect.h" class NullEffect : public Effect { -public: - void update(); - + public: + void update(); }; #endif diff --git a/src/Effects/RGBColorCycleEffect.cpp b/src/Effects/RGBColorCycleEffect.cpp index fcb13e5..3a97ae0 100644 --- a/src/Effects/RGBColorCycleEffect.cpp +++ b/src/Effects/RGBColorCycleEffect.cpp @@ -1,61 +1,56 @@ #include "RGBColorCycleEffect.h" void RGBColorCycleEffect::update() { - WS2812FX* ws2812FX = ((WS2812FXDevice*) device)->getWS2812FX(); - if (stage == 0) { - color = 0xFF0000; - device->on(); - ++stage; - } + WS2812FX* ws2812FX = ((WS2812FXDevice*)device)->getWS2812FX(); + if (stage == 0) { + color = 0xFF0000; + device->on(); + ++stage; + } - int blue = color & 255; - int green = (color >> 8) & 255; - int red = (color >> 16) & 255; + int blue = color & 255; + int green = (color >> 8) & 255; + int red = (color >> 16) & 255; - if (ms >= delay) { - resetMillis(); - if (stage == 1) { - color = ws2812FX->Color(red, ++green, blue); - ws2812FX->setColor(color); - if (color >= 0xFFFF00) { - ++stage; - } - } - else if (stage == 2) { - color = ws2812FX->Color(red, green, ++blue); - ws2812FX->setColor(color); - if (color >= 0xFFFFFF) { - ++stage; - } - } - else if (stage == 3) { - color = ws2812FX->Color(--red, green, blue); - ws2812FX->setColor(color); - if (color <= 0x00FFFF) { - ++stage; - } - } - else if (stage == 4) { - color = ws2812FX->Color(red, --green, blue); - ws2812FX->setColor(color); - if (color <= 0x0000FF) { - ++stage; - } - } - else if (stage == 5) { - color = ws2812FX->Color(++red, green, blue); - ws2812FX->setColor(color); - if (color >= 0xFF00FF) { - ++stage; - } - } - else if (stage == 6) { - color = ws2812FX->Color(red, green, --blue); - ws2812FX->setColor(color); - if (color <= 0xFF0000) { - device->off(); - stop(); - } - } + if (ms >= delay) { + resetMillis(); + if (stage == 1) { + color = ws2812FX->Color(red, ++green, blue); + ws2812FX->setColor(color); + if (color >= 0xFFFF00) { + ++stage; + } + } else if (stage == 2) { + color = ws2812FX->Color(red, green, ++blue); + ws2812FX->setColor(color); + if (color >= 0xFFFFFF) { + ++stage; + } + } else if (stage == 3) { + color = ws2812FX->Color(--red, green, blue); + ws2812FX->setColor(color); + if (color <= 0x00FFFF) { + ++stage; + } + } else if (stage == 4) { + color = ws2812FX->Color(red, --green, blue); + ws2812FX->setColor(color); + if (color <= 0x0000FF) { + ++stage; + } + } else if (stage == 5) { + color = ws2812FX->Color(++red, green, blue); + ws2812FX->setColor(color); + if (color >= 0xFF00FF) { + ++stage; + } + } else if (stage == 6) { + color = ws2812FX->Color(red, green, --blue); + ws2812FX->setColor(color); + if (color <= 0xFF0000) { + device->off(); + stop(); + } } + } } diff --git a/src/Effects/RGBColorCycleEffect.h b/src/Effects/RGBColorCycleEffect.h index 45006f2..a5cadec 100644 --- a/src/Effects/RGBColorCycleEffect.h +++ b/src/Effects/RGBColorCycleEffect.h @@ -10,20 +10,18 @@ #include -#include "Effect.h" #include "../EffectDevices/WS2812FXDevice.h" +#include "Effect.h" class RGBColorCycleEffect : public Effect { -public: - RGBColorCycleEffect(unsigned int delayMs) { - delay = delayMs; - } + public: + RGBColorCycleEffect(unsigned int delayMs) { delay = delayMs; } - void update(); + void update(); -protected: - unsigned int delay; - uint32_t color; + protected: + unsigned int delay; + uint32_t color; }; #endif diff --git a/src/Effects/RampDownStopPWMEffect.cpp b/src/Effects/RampDownStopPWMEffect.cpp index 4ce5134..90656f7 100644 --- a/src/Effects/RampDownStopPWMEffect.cpp +++ b/src/Effects/RampDownStopPWMEffect.cpp @@ -1,28 +1,29 @@ #include "RampDownStopPWMEffect.h" void RampDownStopPWMEffect::updateMillis() { - // Immediately shift to the falling curve of the first period by adding (waveDuration / 2) - ms = millis() - _ms + (waveDuration / 2); + // Immediately shift to the falling curve of the first period by adding + // (waveDuration / 2) + ms = millis() - _ms + (waveDuration / 2); } void RampDownStopPWMEffect::update() { - if (ms > waveDuration) { - stop(); - return; - } + if (ms > waveDuration) { + stop(); + return; + } - if (stage < 1) { - maxIntensity = ((WavePWMDevice *) device)->getPWM(); - compression = 255 / maxIntensity; - stage = 1; - } + if (stage < 1) { + maxIntensity = ((WavePWMDevice *)device)->getPWM(); + compression = 255 / maxIntensity; + stage = 1; + } - uint8_t pwm = compression * wavePWM->getQuadraticValue(ms); + uint8_t pwm = compression * wavePWM->getQuadraticValue(ms); - // Safety net. - if (pwm > 255) { - pwm = 255; - } + // Safety net. + if (pwm > 255) { + pwm = 255; + } - ((WavePWMDevice *) device)->setPWM(pwm); + ((WavePWMDevice *)device)->setPWM(pwm); } diff --git a/src/Effects/RampDownStopPWMEffect.h b/src/Effects/RampDownStopPWMEffect.h index e0205a7..6f03ace 100644 --- a/src/Effects/RampDownStopPWMEffect.h +++ b/src/Effects/RampDownStopPWMEffect.h @@ -11,20 +11,19 @@ #include #include -#include "WavePWMEffect.h" #include "../EffectDevices/EffectDevice.h" #include "../EffectDevices/WavePWMDevice.h" +#include "WavePWMEffect.h" class RampDownStopPWMEffect : public WavePWMEffect { -public: - - RampDownStopPWMEffect(unsigned int frequency) : WavePWMEffect(frequency) { - // nop - } + public: + RampDownStopPWMEffect(unsigned int frequency) : WavePWMEffect(frequency) { + // nop + } - virtual void updateMillis(); + virtual void updateMillis(); - virtual void update(); + virtual void update(); }; #endif diff --git a/src/Effects/SinePWMEffect.cpp b/src/Effects/SinePWMEffect.cpp index 5255086..d1aacb5 100644 --- a/src/Effects/SinePWMEffect.cpp +++ b/src/Effects/SinePWMEffect.cpp @@ -1,38 +1,36 @@ #include "SinePWMEffect.h" void SinePWMEffect::update() { - uint8_t pwm = 0; + uint8_t pwm = 0; - if (stage == 0) { - if (ms < (waveDuration / 4)) { - pwm = rampCompression * wavePWM->getQuadraticValue(ms); - } - else { - stage = 1; - } + if (stage == 0) { + if (ms < (waveDuration / 4)) { + pwm = rampCompression * wavePWM->getQuadraticValue(ms); + } else { + stage = 1; } + } - if (stage == 1) { - if (ms < duration) { - pwm = compression * wavePWM->getQuadraticValue(ms) + minIntensity; - } - else { - stage = 2; - } + if (stage == 1) { + if (ms < duration) { + pwm = compression * wavePWM->getQuadraticValue(ms) + minIntensity; + } else { + stage = 2; } + } - if (stage == 2) { - pwm = rampCompression * wavePWM->getQuadraticValue(ms); - if (pwm == 0) { - stop(); - return; - } + if (stage == 2) { + pwm = rampCompression * wavePWM->getQuadraticValue(ms); + if (pwm == 0) { + stop(); + return; } + } - // Safety net. - if (pwm > 255) { - pwm = 255; - } + // Safety net. + if (pwm > 255) { + pwm = 255; + } - ((WavePWMDevice *) device)->setPWM(pwm); + ((WavePWMDevice *)device)->setPWM(pwm); } diff --git a/src/Effects/SinePWMEffect.h b/src/Effects/SinePWMEffect.h index 962643a..14f627a 100644 --- a/src/Effects/SinePWMEffect.h +++ b/src/Effects/SinePWMEffect.h @@ -11,23 +11,24 @@ #include #include -#include "WavePWMEffect.h" #include "../EffectDevices/EffectDevice.h" #include "../EffectDevices/WavePWMDevice.h" +#include "WavePWMEffect.h" class SinePWMEffect : public WavePWMEffect { -public: - - SinePWMEffect(unsigned int frequency, unsigned int duration, uint8_t maxIntensity = 255, uint8_t minIntensity = 0) : WavePWMEffect(frequency, maxIntensity, minIntensity) { - this->duration = duration; - this->rampCompression = 255 / maxIntensity; - } - - virtual void update(); - -protected: - unsigned int duration; - uint8_t rampCompression; + public: + SinePWMEffect(unsigned int frequency, unsigned int duration, + uint8_t maxIntensity = 255, uint8_t minIntensity = 0) + : WavePWMEffect(frequency, maxIntensity, minIntensity) { + this->duration = duration; + this->rampCompression = 255 / maxIntensity; + } + + virtual void update(); + + protected: + unsigned int duration; + uint8_t rampCompression; }; #endif diff --git a/src/Effects/WS2812FXEffect.cpp b/src/Effects/WS2812FXEffect.cpp index 94df349..4b49096 100644 --- a/src/Effects/WS2812FXEffect.cpp +++ b/src/Effects/WS2812FXEffect.cpp @@ -1,50 +1,61 @@ #include "WS2812FXEffect.h" -void WS2812FXEffect::setDevice(EffectDevice* effectDevice) { - Effect::setDevice(effectDevice); - ws2812FX = (WS2812FX*) ((WS2812FXDevice *) device)->getWS2812FX(); +void WS2812FXEffect::setDevice(EffectDevice *effectDevice) { + Effect::setDevice(effectDevice); + ws2812FX = (WS2812FX *)((WS2812FXDevice *)device)->getWS2812FX(); } void WS2812FXEffect::start(int r) { - Effect::start(); - device->on(); - ws2812FX->setSegment(getFirstSegment(), getFirstLED(), getlastLED(), mode, colors, speed, options); + Effect::start(); + device->on(); + if (segment == 255) { + ws2812FX->setSegment(getFirstSegment(), getFirstLED(), getlastLED(), mode, + colors, speed, options); ws2812FX->resetSegmentRuntime(getFirstSegment()); + } else { + ws2812FX->getSegment(segment)->mode = mode; + ws2812FX->getSegment(segment)->colors[0] = colors[0]; + ws2812FX->getSegment(segment)->colors[1] = colors[1]; + ws2812FX->getSegment(segment)->colors[2] = colors[2]; + ws2812FX->getSegment(segment)->speed = speed; + ws2812FX->getSegment(segment)->options = options; + ws2812FX->resetSegmentRuntime(segment); + } } void WS2812FXEffect::stop() { - device->off(); - Effect::stop(); + device->off(); + Effect::stop(); } void WS2812FXEffect::update() { - // Don't call service() here! + // Don't call service() here! - if (duration && duration < ms) { - stop(); - } + if (duration && duration < ms) { + stop(); + } } int WS2812FXEffect::getFirstLED() { - return ((WS2812FXDevice *) device)->getFirstLED(); + return ((WS2812FXDevice *)device)->getFirstLED(); } int WS2812FXEffect::getlastLED() { - return ((WS2812FXDevice *) device)->getlastLED(); + return ((WS2812FXDevice *)device)->getlastLED(); } int WS2812FXEffect::getNumLEDs() { - return ((WS2812FXDevice *) device)->getNumLEDs(); + return ((WS2812FXDevice *)device)->getNumLEDs(); } int WS2812FXEffect::getFirstSegment() { - return ((WS2812FXDevice *) device)->getFirstSegment(); + return ((WS2812FXDevice *)device)->getFirstSegment(); } int WS2812FXEffect::getLastSegment() { - return ((WS2812FXDevice *) device)->getLastSegment(); + return ((WS2812FXDevice *)device)->getLastSegment(); } int WS2812FXEffect::getNumSegments() { - return ((WS2812FXDevice *) device)->getNumSegments(); + return ((WS2812FXDevice *)device)->getNumSegments(); } diff --git a/src/Effects/WS2812FXEffect.h b/src/Effects/WS2812FXEffect.h index 210c5f7..4cd7f18 100644 --- a/src/Effects/WS2812FXEffect.h +++ b/src/Effects/WS2812FXEffect.h @@ -11,54 +11,78 @@ #include #include -#include "Effect.h" #include "../EffectDevices/EffectDevice.h" #include "../EffectDevices/WS2812FXDevice.h" +#include "Effect.h" class WS2812FXEffect : public Effect { -public: - - WS2812FXEffect(uint8_t mode, uint32_t color, uint16_t speed, uint8_t options, int duration = 0) { - this->mode = mode; - this->colors[0] = color; - this->speed = speed; - this->options = options; - this->duration = duration; - } - - WS2812FXEffect(uint8_t mode, const uint32_t colors[], uint16_t speed, uint8_t options, int duration = 0) { - this->mode = mode; - this->colors[0] = colors[0]; - this->colors[1] = colors[1]; - this->colors[2] = colors[2]; - this->speed = speed; - this->options = options; - this->duration = duration; - } - - virtual void setDevice(EffectDevice* effectDevice); - - virtual void start(int repeat = 0); - - virtual void stop(); - - virtual void update(); - - int getFirstLED(); - int getlastLED(); - int getNumLEDs(); - - int getFirstSegment(); - int getLastSegment(); - int getNumSegments(); - -protected: - WS2812FX* ws2812FX; - uint8_t mode; - uint32_t colors[3] = {0, 0, 0}; - uint16_t speed; - uint8_t options; - int duration; // 0 means unlimited + public: + WS2812FXEffect(uint8_t mode, uint32_t color, uint16_t speed, uint8_t options, + int duration = 0) { + this->mode = mode; + this->colors[0] = color; + this->speed = speed; + this->options = options; + this->duration = duration; + } + + WS2812FXEffect(uint8_t mode, const uint32_t colors[], uint16_t speed, + uint8_t options, int duration = 0) { + this->mode = mode; + this->colors[0] = colors[0]; + this->colors[1] = colors[1]; + this->colors[2] = colors[2]; + this->speed = speed; + this->options = options; + this->duration = duration; + } + + WS2812FXEffect(uint8_t segment, uint8_t mode, uint32_t color, uint16_t speed, + uint8_t options, int duration = 0) { + this->segment = segment; + this->mode = mode; + this->colors[0] = color; + this->speed = speed; + this->options = options; + this->duration = duration; + } + + WS2812FXEffect(uint8_t segment, uint8_t mode, const uint32_t colors[], + uint16_t speed, uint8_t options, int duration = 0) { + this->segment = segment; + this->mode = mode; + this->colors[0] = colors[0]; + this->colors[1] = colors[1]; + this->colors[2] = colors[2]; + this->speed = speed; + this->options = options; + this->duration = duration; + } + + virtual void setDevice(EffectDevice* effectDevice); + + virtual void start(int repeat = 0); + + virtual void stop(); + + virtual void update(); + + int getFirstLED(); + int getlastLED(); + int getNumLEDs(); + + int getFirstSegment(); + int getLastSegment(); + int getNumSegments(); + + protected: + WS2812FX* ws2812FX; + uint8_t segment = 255; + uint8_t mode; + uint32_t colors[3] = {0, 0, 0}; + uint16_t speed; + uint8_t options; + int duration; // 0 means unlimited }; #endif diff --git a/src/Effects/WavePWMEffect.cpp b/src/Effects/WavePWMEffect.cpp index 19d9feb..9094d27 100644 --- a/src/Effects/WavePWMEffect.cpp +++ b/src/Effects/WavePWMEffect.cpp @@ -1,17 +1,17 @@ #include "WavePWMEffect.h" void WavePWMEffect::setDevice(EffectDevice* effectDevice) { - Effect::setDevice(effectDevice); - wavePWM = (WavePWM*) ((WavePWMDevice *) device)->getWavePWM(); + Effect::setDevice(effectDevice); + wavePWM = (WavePWM*)((WavePWMDevice*)device)->getWavePWM(); } void WavePWMEffect::start(int r) { - Effect::start(r); - device->on(); - wavePWM->setup(waveDuration); + Effect::start(r); + device->on(); + wavePWM->setup(waveDuration); } void WavePWMEffect::stop() { - device->off(); - Effect::stop(); + device->off(); + Effect::stop(); } diff --git a/src/Effects/WavePWMEffect.h b/src/Effects/WavePWMEffect.h index ea93062..6d0f41a 100644 --- a/src/Effects/WavePWMEffect.h +++ b/src/Effects/WavePWMEffect.h @@ -11,34 +11,34 @@ #include #include -#include "Effect.h" #include "../EffectDevices/EffectDevice.h" #include "../EffectDevices/WavePWMDevice.h" +#include "Effect.h" class WavePWMEffect : public Effect { -public: - - WavePWMEffect(unsigned int frequency, uint8_t maxIntensity = 255, uint8_t minIntensity = 0) { - this->waveDuration = 1000 / frequency; - this->maxIntensity = maxIntensity; - this->minIntensity = minIntensity; - this->compression = 255 / (maxIntensity - minIntensity); - } + public: + WavePWMEffect(unsigned int frequency, uint8_t maxIntensity = 255, + uint8_t minIntensity = 0) { + this->waveDuration = 1000 / frequency; + this->maxIntensity = maxIntensity; + this->minIntensity = minIntensity; + this->compression = 255 / (maxIntensity - minIntensity); + } - virtual void setDevice(EffectDevice* effectDevice); + virtual void setDevice(EffectDevice* effectDevice); - virtual void start(int repeat = 0); + virtual void start(int repeat = 0); - virtual void stop(); + virtual void stop(); - virtual void update() = 0; + virtual void update() = 0; -protected: - WavePWM* wavePWM; - unsigned int waveDuration; - uint8_t maxIntensity; - uint8_t minIntensity; - uint8_t compression; + protected: + WavePWM* wavePWM; + unsigned int waveDuration; + uint8_t maxIntensity; + uint8_t minIntensity; + uint8_t compression; }; #endif diff --git a/src/EffectsController.cpp b/src/EffectsController.cpp index 4c5db4d..218e029 100644 --- a/src/EffectsController.cpp +++ b/src/EffectsController.cpp @@ -1,273 +1,532 @@ #include "EffectsController.h" // see https://forum.arduino.cc/index.php?topic=398610.0 -EffectsController* EffectsController::effectsControllerInstance = NULL; +EffectsController *EffectsController::effectsControllerInstance = NULL; -EventDispatcher* EffectsController::eventDispatcher() { - return _eventDispatcher; +EventDispatcher *EffectsController::eventDispatcher() { + return _eventDispatcher; } -LedBuiltInDevice* EffectsController::ledBuiltInDevice() { - return _ledBuiltInDevice; +LedBuiltInDevice *EffectsController::ledBuiltInDevice() { + return _ledBuiltInDevice; } -NullDevice* EffectsController::nullDevice() { - return _nullDevice; -} +NullDevice *EffectsController::nullDevice() { return _nullDevice; } -WavePWMDevice* EffectsController::shakerPWMDevice() { - return _shakerPWMDevice; -} +WavePWMDevice *EffectsController::shakerPWMDevice() { return _shakerPWMDevice; } -WavePWMDevice* EffectsController::ledPWMDevice() { - return _ledPWMDevice; -} +WavePWMDevice *EffectsController::ledPWMDevice() { return _ledPWMDevice; } -RgbStripDevice* EffectsController::rgbStripDevice() { - return _rgbStripeDevice; -} +RgbStripDevice *EffectsController::rgbStripDevice() { return _rgbStripeDevice; } -WS2812FXDevice* EffectsController::ws2812FXDevice(int port) { - return ws2812FXDevices[--port][0]; +WS2812FXDevice *EffectsController::ws2812FXDevice(int port) { + return ws2812FXDevices[--port][0]; } -CombinedGiAndLightMatrixWS2812FXDevice* EffectsController::giAndLightMatrix(int port) { - return (CombinedGiAndLightMatrixWS2812FXDevice*) ws2812FXDevice(port); +CombinedGiAndLightMatrixWS2812FXDevice *EffectsController::giAndLightMatrix( + int port) { + return (CombinedGiAndLightMatrixWS2812FXDevice *)ws2812FXDevice(port); } -CombinedGiAndLightMatrixWS2812FXDevice* EffectsController::createCombinedGiAndLightMatrixWs2812FXDevice(int port) { - WS2812FXDevice* ws2812FXDevice = ws2812FXDevices[--port][0]; - - CombinedGiAndLightMatrixWS2812FXDevice* giAndLightMatrix = new CombinedGiAndLightMatrixWS2812FXDevice( - ws2812FXDevice->getWS2812FX(), - ws2812FXDevice->getFirstLED(), - ws2812FXDevice->getlastLED(), - ws2812FXDevice->getFirstSegment(), - ws2812FXDevice->getLastSegment() - ); +CombinedGiAndLightMatrixWS2812FXDevice * +EffectsController::createCombinedGiAndLightMatrixWs2812FXDevice(int port) { + WS2812FXDevice *ws2812FXDevice = ws2812FXDevices[--port][0]; - giAndLightMatrix->off(); + CombinedGiAndLightMatrixWS2812FXDevice *giAndLightMatrix = + new CombinedGiAndLightMatrixWS2812FXDevice( + ws2812FXDevice->getWS2812FX(), ws2812FXDevice->getFirstLED(), + ws2812FXDevice->getlastLED(), ws2812FXDevice->getFirstSegment(), + ws2812FXDevice->getLastSegment(), _eventDispatcher); - ws2812FXDevices[port][0] = giAndLightMatrix; - delete ws2812FXDevice; + giAndLightMatrix->off(); - _eventDispatcher->addListener(giAndLightMatrix, EVENT_SOURCE_GI); - _eventDispatcher->addListener(giAndLightMatrix, EVENT_SOURCE_LIGHT); + ws2812FXDevices[port][0] = giAndLightMatrix; + delete ws2812FXDevice; - return giAndLightMatrix; + return giAndLightMatrix; } -WS2812FXDevice* EffectsController::createWS2812FXDevice(int port, int number, int segments, int firstLED, int lastLED) { - --port; +WS2812FXDevice *EffectsController::createWS2812FXDevice(int port, int number, + int segments, + int firstLED, + int lastLED) { + --port; - if (number == 0) { - ws2812FXDevices[port][0]->_reduceLEDs(lastLED, segments - 1); - } - else { - int firstSegment = ws2812FXDevices[port][number - 1]->getLastSegment() + 1; - - ws2812FXDevices[port][number] = new WS2812FXDevice( - ws2812FXDevices[port][0]->getWS2812FX(), - firstLED, - lastLED, - firstSegment, - firstSegment + segments - 1 - ); - - ++ws2812FXDeviceCounters[port]; - } + if (number == 0) { + ws2812FXDevices[port][0]->_reduceLEDs(lastLED, segments - 1); + } else { + int firstSegment = ws2812FXDevices[port][number - 1]->getLastSegment() + 1; - return ws2812FXDevices[port][number]; -} + ws2812FXDevices[port][number] = + new WS2812FXDevice(ws2812FXDevices[port][0]->getWS2812FX(), firstLED, + lastLED, firstSegment, firstSegment + segments - 1); -WS2812FXDevice* EffectsController::ws2812FXDevice(int port, int number) { - return ws2812FXDevices[--port][number]; -} + ++ws2812FXDeviceCounters[port]; + } -GeneralIlluminationWPC* EffectsController::generalIllumintationWPC() { - return _generalIllumintationWPC; + return ws2812FXDevices[port][number]; } -void EffectsController::addEffect(Effect* effect, EffectDevice* device, Event* event, int priority, int repeat, int mode) { - addEffect(new EffectContainer(effect, device, event, priority, repeat, mode)); +WS2812FXDevice *EffectsController::ws2812FXDevice(int port, int number) { + return ws2812FXDevices[--port][number]; } -void EffectsController::addEffect(EffectContainer* container) { - container->effect->setEventDispatcher(this->eventDispatcher()); - container->effect->setDevice(container->device); - stackEffectContainers[++stackCounter] = container; +void EffectsController::addEffect(Effect *effect, EffectDevice *device, + Event *event, int priority, int repeat, + int mode) { + addEffect(new EffectContainer(effect, device, event, priority, repeat, mode)); } -void EffectsController::attachBrightnessControl(byte port, byte poti) { - brightnessControl[--port] = poti; +void EffectsController::addEffect(EffectContainer *container) { + container->effect->setEventDispatcher(this->eventDispatcher()); + container->effect->setDevice(container->device); + stackEffectContainers[++stackCounter] = container; } void EffectsController::setBrightness(byte port, byte brightness) { - ws2812FXDevices[--port][0]->setBrightness(brightness); - ws2812FXbrightness[port] = brightness; + ws2812FXDevices[--port][0]->setBrightness(brightness); + ws2812FXbrightness[port] = brightness; } -void EffectsController::handleEvent(Event* event) { - for (int i = 0; i <= stackCounter; i++) { - if ( - event->sourceId == stackEffectContainers[i]->event->sourceId && +void EffectsController::handleEvent(Event *event) { + switch (event->sourceId) { + case EVENT_RESET: + if (_shakerPWMDevice) _shakerPWMDevice->reset(); + if (_ledPWMDevice) _ledPWMDevice->reset(); + + for (int i = 0; i < PPUC_MAX_WS2812FX_DEVICES; i++) { + if (ws2812FXstates[i]) { + for (int k = ws2812FXDeviceCounters[i] - 1; k >= 0; k--) { + if (ws2812FXDevices[i][k]) { + delete ws2812FXDevices[i][k]; + } + } + } + } + + break; + + default: + for (int i = 0; i <= stackCounter; i++) { + if (event->sourceId == stackEffectContainers[i]->event->sourceId && event->eventId == stackEffectContainers[i]->event->eventId && event->value == stackEffectContainers[i]->event->value && - ( - mode == stackEffectContainers[i]->mode || - -1 == stackEffectContainers[i]->mode // -1 means any mode - ) - ) { - for (int k = 0; k <= stackCounter; k++) { - if ( - stackEffectContainers[i]->device == stackEffectContainers[k]->device && - stackEffectContainers[k]->effect->isRunning() - ) { - if (stackEffectContainers[i]->priority > stackEffectContainers[k]->priority) { - stackEffectContainers[k]->effect->terminate(); - stackEffectContainers[i]->effect->start(stackEffectContainers[i]->repeat); - } - break; - } - if (k == stackCounter) { - stackEffectContainers[i]->effect->start(stackEffectContainers[i]->repeat); - } + (mode == stackEffectContainers[i]->mode || + -1 == stackEffectContainers[i]->mode // -1 means any mode + )) { + for (int k = 0; k <= stackCounter; k++) { + if (stackEffectContainers[i]->device == + stackEffectContainers[k]->device && + stackEffectContainers[k]->effect->isRunning()) { + if (stackEffectContainers[i]->priority > + stackEffectContainers[k]->priority) { + stackEffectContainers[k]->effect->terminate(); + stackEffectContainers[i]->effect->start( + stackEffectContainers[i]->repeat); + } + break; } + if (k == stackCounter) { + stackEffectContainers[i]->effect->start( + stackEffectContainers[i]->repeat); + } + } } - } + } + } } -void EffectsController::handleEvent(ConfigEvent* event) { - if (event->boardId == boardId) { - switch (event->topic) { - case CONFIG_TOPIC_LED_STRING: - switch (event->key) { - case CONFIG_TOPIC_PORT: - config_port = event->value; - config_type = 0; - config_amount = 0; - config_afterGlow = 0; - break; - case CONFIG_TOPIC_TYPE: - config_type = event->value; - break; - case CONFIG_TOPIC_AMOUNT_LEDS: - config_amount = event->value; - break; - case CONFIG_TOPIC_AFTER_GLOW: - config_afterGlow = event->value; - break; - case CONFIG_TOPIC_LIGHT_UP: - config_heatUp = event->value; - ws2812FXDevices[0][0] = new WS2812FXDevice( - new WS2812FX(config_amount, config_port, config_type), - 0, - config_amount - 1, - 0, - 0 - ); - ws2812FXDevices[0][0]->getWS2812FX()->init(); - ws2812FXDeviceCounters[0] = 1; - - // Brightness might be overwritten later. - //ws2812FXDevices[0][0]->setBrightness(WS2812FX_BRIGHTNESS); - ws2812FXDevices[0][0]->off(); - ws2812FXstates[0] = true; - break; +void EffectsController::handleEvent(ConfigEvent *event) { + if (event->boardId == boardId) { + if (flickerState) + _ledBuiltInDevice->on(); + else + _ledBuiltInDevice->off(); + flickerState = !flickerState; + + switch (event->topic) { + case CONFIG_TOPIC_PLATFORM: + platform = event->value; + break; + + case CONFIG_TOPIC_LED_STRING: + switch (event->key) { + case CONFIG_TOPIC_PORT: + config_values[0] = event->value; // port + config_neoPixelType = 0; + config_values[1] = 0; // amount of leds + config_values[2] = 0; // after glow + config_values[3] = 50; // brightness + config_values[4] = 0; // heat up + break; + case CONFIG_TOPIC_TYPE: + config_neoPixelType = (neoPixelType)event->value; + break; + case CONFIG_TOPIC_BRIGHTNESS: + config_values[3] = event->value; + break; + case CONFIG_TOPIC_AMOUNT_LEDS: + config_values[1] = event->value; + break; + case CONFIG_TOPIC_AFTER_GLOW: + config_values[2] = event->value; + break; + case CONFIG_TOPIC_LIGHT_UP: + config_values[4] = event->value; + if (!ws2812FXDevices[0][0]) { + ws2812FXDevices[0][0] = + new CombinedGiAndLightMatrixWS2812FXDevice( + new WS2812FX(config_values[1], config_values[0], + config_neoPixelType), + 0, config_values[1] - 1, 0, 0, _eventDispatcher); + ws2812FXDevices[0][0]->getWS2812FX()->init(); + ws2812FXDeviceCounters[0] = 1; + + // Brightness might be overwritten later. + ws2812FXDevices[0][0]->setBrightness(config_values[3]); + // "off" means no effects, standard operation mode. + ws2812FXDevices[0][0]->off(); + if (config_values[4] > 0) { + ((CombinedGiAndLightMatrixWS2812FXDevice *) + ws2812FXDevices[0][0]) + ->setHeatUp(config_values[4]); + } + if (config_values[2] > 0) { + ((CombinedGiAndLightMatrixWS2812FXDevice *) + ws2812FXDevices[0][0]) + ->setAfterGlow(config_values[2]); + } + ws2812FXstates[0] = true; + } + break; + } + break; + + case CONFIG_TOPIC_LED_SEGMENT: + if (ws2812FXDevices[0][0]) { + switch (event->key) { + case CONFIG_TOPIC_PORT: + config_values[0] = event->value; // port + config_values[1] = 0; // number + config_values[2] = 0; // from + config_values[3] = 0; // to + break; + case CONFIG_TOPIC_NUMBER: + config_values[1] = event->value; + break; + case CONFIG_TOPIC_FROM: + config_values[2] = event->value; + break; + case CONFIG_TOPIC_TO: + config_values[3] = event->value; + + ws2812FXDevices[0][0]->getWS2812FX()->setSegment( + config_values[1], config_values[2], config_values[3]); + break; + } + } + break; + + case CONFIG_TOPIC_LED_EFFECT: + if (ws2812FXDevices[0][0]) { + switch (event->key) { + case CONFIG_TOPIC_PORT: + config_values[0] = event->value; // port + config_values[1] = 0; // segment + config_values[2] = 0; // duration + config_values[3] = 0; // effect + config_values[4] = 0; // reverse + config_values[5] = 0; // speed + config_values[6] = 0; // mode + config_values[7] = 0; // priority + config_values[8] = 0; // repeat + config_payload = 0; // color + ws1812Effect = nullptr; + break; + case CONFIG_TOPIC_LED_SEGMENT: + config_values[1] = event->value; + break; + case CONFIG_TOPIC_COLOR: + config_payload = event->value; + break; + case CONFIG_TOPIC_DURATION: + config_values[2] = event->value; + break; + case CONFIG_TOPIC_EFFECT: + config_values[3] = event->value; + break; + case CONFIG_TOPIC_REVERSE: + config_values[4] = event->value; + break; + case CONFIG_TOPIC_SPEED: + config_values[5] = event->value; + break; + case CONFIG_TOPIC_MODE: + config_values[6] = event->value; + break; + case CONFIG_TOPIC_PRIORITY: + config_values[7] = event->value; + break; + case CONFIG_TOPIC_REPEAT: + config_values[8] = event->value; + ws1812Effect = new WS2812FXEffect( + config_values[1], config_values[3], config_payload, + config_values[5], + config_values[4] == 1 ? REVERSE : NO_OPTIONS, + config_values[2]); + break; + } + } + break; + + case CONFIG_TOPIC_TRIGGER: + switch (event->key) { + case CONFIG_TOPIC_PORT: + config_values[0] = event->value; // port + config_values[1] = 0; // type + config_values[2] = 0; // source + config_values[3] = 0; // number + config_values[4] = 0; // value + break; + case CONFIG_TOPIC_TYPE: + config_values[1] = event->value; + break; + case CONFIG_TOPIC_SOURCE: + config_values[2] = event->value; // source + break; + case CONFIG_TOPIC_NUMBER: + config_values[3] = event->value; + break; + case CONFIG_TOPIC_VALUE: + config_values[4] = event->value; + switch (config_values[1]) { + case CONFIG_TOPIC_LED_EFFECT: + if (ws1812Effect) { + addEffect( + ws1812Effect, ws2812FXDevices[0][0], + new Event(config_values[2], config_values[3], + config_values[4]), + config_values[7], // priority + config_values[8] == 255 ? -1 + : config_values[8], // repeat + config_values[6] // mode + ); + } + break; + + case CONFIG_TOPIC_PWM_EFFECT: + if (pwmEffect) { + addEffect( + pwmEffect, _shakerPWMDevice, + new Event(config_values[2], config_values[3], + config_values[4]), + config_values[7], // priority + config_values[8] == 255 ? -1 + : config_values[8], // repeat + config_values[6] // mode + ); } break; + } + break; + } + break; + + case CONFIG_TOPIC_LAMPS: + if (ws2812FXDevices[0][0]) { + switch (event->key) { + case CONFIG_TOPIC_PORT: + config_values[0] = event->value; // port + config_values[1] = 0; // type + config_values[2] = 0; // number + config_values[3] = 0; // led number + config_payload = 0; // color + break; + case CONFIG_TOPIC_TYPE: + config_values[1] = event->value; + break; + case CONFIG_TOPIC_NUMBER: + config_values[2] = event->value; + break; + case CONFIG_TOPIC_LED_NUMBER: + config_values[3] = event->value; + break; + case CONFIG_TOPIC_COLOR: + config_payload = event->value; + switch (config_values[1]) { + case LED_TYPE_GI: + ((CombinedGiAndLightMatrixWS2812FXDevice *) + ws2812FXDevices[0][0]) + ->assignLedToGiString(config_values[2], config_values[3], + config_payload); + break; + case LED_TYPE_LAMP: + ((CombinedGiAndLightMatrixWS2812FXDevice *) + ws2812FXDevices[0][0]) + ->assignLedToLightMatrix( + config_values[2], config_values[3], config_payload); + break; + case LED_TYPE_FLASHER: + ((CombinedGiAndLightMatrixWS2812FXDevice *) + ws2812FXDevices[0][0]) + ->assignLedToFlasher(config_values[2], config_values[3], + config_payload); + break; + } + break; + } + } + break; + + case CONFIG_TOPIC_PWM: + switch (event->key) { + case CONFIG_TOPIC_PORT: + config_values[0] = event->value; // port + config_values[1] = 0; // power + case CONFIG_TOPIC_POWER: + config_values[1] = event->value; + break; + case CONFIG_TOPIC_TYPE: + switch (event->value) { + case PWM_TYPE_SHAKER: // Shaker + _shakerPWMDevice = new WavePWMDevice( + config_values[0], config_values[1], _eventDispatcher); + _shakerPWMDevice->off(); + break; + } + break; + } + break; + + case CONFIG_TOPIC_PWM_EFFECT: + if (_shakerPWMDevice) { + switch (event->key) { + case CONFIG_TOPIC_PORT: + config_values[0] = event->value; // port + config_values[1] = 0; // duration + config_values[2] = 0; // effect + config_values[3] = 0; // frequency + config_values[4] = 0; // max intensity + config_values[5] = 0; // min intensity + config_values[6] = 0; // mode + config_values[7] = 0; // priority + config_values[8] = 0; // repeat + config_payload = 0; // color + pwmEffect = nullptr; + break; + case CONFIG_TOPIC_DURATION: + config_values[1] = event->value; + break; + case CONFIG_TOPIC_EFFECT: + config_values[2] = event->value; + break; + case CONFIG_TOPIC_FREQUENCY: + config_values[3] = event->value; + break; + case CONFIG_TOPIC_MAX_INTENSITY: + config_values[4] = event->value; + break; + case CONFIG_TOPIC_MIN_INTENSITY: + config_values[5] = event->value; + break; + case CONFIG_TOPIC_MODE: + config_values[6] = event->value; + break; + case CONFIG_TOPIC_PRIORITY: + config_values[7] = event->value; + break; + case CONFIG_TOPIC_REPEAT: + config_values[8] = event->value; + switch (config_values[2]) { + case PWM_EFFECT_SINE: + pwmEffect = + new SinePWMEffect(config_values[3], config_values[1], + config_values[4], config_values[5]); + break; + case PWM_EFFECT_IMPULSE: + pwmEffect = + new ImpulsePWMEffect(config_values[3], config_values[4]); + break; + case PWM_EFFECT_RAMP_DOWN_STOP: + pwmEffect = new RampDownStopPWMEffect(config_values[3]); + break; + } + break; + } } + break; } + } } void EffectsController::update() { - if (controllerType == CONTROLLER_MEGA_ALL_INPUT) { - _testButtons->update(); - } - - if (platform == PLATFORM_WPC) { - _generalIllumintationWPC->update(); - } + _eventDispatcher->update(); - _eventDispatcher->update(); - - for (int i = 0; i <= stackCounter; i++) { - if (stackEffectContainers[i]->effect->isRunning()) { - stackEffectContainers[i]->effect->updateMillis(); - stackEffectContainers[i]->effect->update(); - } + for (int i = 0; i <= stackCounter; i++) { + if (stackEffectContainers[i]->effect->isRunning()) { + stackEffectContainers[i]->effect->updateMillis(); + stackEffectContainers[i]->effect->update(); } + } - if (millis() - ws2812UpdateInterval > UPDATE_INTERVAL_WS2812FX_EFFECTS) { - // Updating the LEDs too fast leads to undefined behavior. Just update effects every 3ms. - ws2812UpdateInterval = millis(); - - for (int i = 0; i < PPUC_MAX_WS2812FX_DEVICES; i++) { - if (ws2812FXstates[i]) { - if (ws2812FXrunning[i]) { - ws2812FXDevices[i][0]->getWS2812FX()->service(); - - bool stop = true; - for (int k = 0; k < ws2812FXDeviceCounters[i]; k++) { - stop &= ws2812FXDevices[i][k]->isStopped(); - } - - if (stop) { - ws2812FXDevices[i][0]->getWS2812FX()->stop(); - ws2812FXrunning[i] = false; - } - } else { - bool start = false; - for (int k = 0; k < ws2812FXDeviceCounters[i]; k++) { - start |= !ws2812FXDevices[i][k]->isStopped(); - } - - if (start) { - ws2812FXDevices[i][0]->getWS2812FX()->start(); - ws2812FXrunning[i] = true; - ws2812FXDevices[i][0]->getWS2812FX()->service(); - } - } - } - } - } + if (millis() - ws2812UpdateInterval > UPDATE_INTERVAL_WS2812FX_EFFECTS) { + // Updating the LEDs too fast leads to undefined behavior. Just update + // effects every 3ms. + ws2812UpdateInterval = millis(); - if (millis() - ws2812AfterGlowUpdateInterval > UPDATE_INTERVAL_WS2812FX_AFTERGLOW) { - // Updating the LEDs too fast leads to undefined behavior. Just update every 3ms. - ws2812AfterGlowUpdateInterval = millis(); - for (int i = 0; i < PPUC_MAX_WS2812FX_DEVICES; i++) { - if (ws2812FXstates[i] && ws2812FXDevices[i][0]->hasAfterGlowSupport() && !ws2812FXrunning[i]) { - // No other effect is running, handle after glow effect. - ((CombinedGiAndLightMatrixWS2812FXDevice *) ws2812FXDevices[i][0])->updateAfterGlow(); - ws2812FXDevices[i][0]->getWS2812FX()->show(); - } + for (int i = 0; i < PPUC_MAX_WS2812FX_DEVICES; i++) { + if (ws2812FXstates[i]) { + if (ws2812FXrunning[i]) { + ws2812FXDevices[i][0]->getWS2812FX()->service(); + + bool stop = true; + for (int k = 0; k < ws2812FXDeviceCounters[i]; k++) { + stop &= ws2812FXDevices[i][k]->isStopped(); + } + + if (stop) { + ws2812FXDevices[i][0]->getWS2812FX()->stop(); + ws2812FXrunning[i] = false; + } + } else { + bool start = false; + for (int k = 0; k < ws2812FXDeviceCounters[i]; k++) { + start |= !ws2812FXDevices[i][k]->isStopped(); + } + + if (start) { + ws2812FXDevices[i][0]->getWS2812FX()->start(); + ws2812FXrunning[i] = true; + ws2812FXDevices[i][0]->getWS2812FX()->service(); + } } + } } + } - if (brightnessControlBasePin > 0) { - if (millis() - brightnessUpdateInterval > UPDATE_INTERVAL_WS2812FX_BRIGHTNESS) { - // Don't update the brightness too often. - brightnessUpdateInterval = millis(); - for (byte i = 0; i < PPUC_MAX_BRIGHTNESS_CONTROLS; i++) { - brightnessControlReads[i] = analogRead(brightnessControlBasePin + i) / 4; - } - for (byte i = 0; i < PPUC_MAX_WS2812FX_DEVICES; i++) { - if (brightnessControl[i] > 0) { - setBrightness(i + 1, brightnessControlReads[brightnessControl[i - 1]]); - } - } - } + if (millis() - ws2812AfterGlowUpdateInterval > + UPDATE_INTERVAL_WS2812FX_AFTERGLOW) { + // Updating the LEDs too fast leads to undefined behavior. Just update + // every 3ms. + ws2812AfterGlowUpdateInterval = millis(); + for (int i = 0; i < PPUC_MAX_WS2812FX_DEVICES; i++) { + if (ws2812FXstates[i] && ws2812FXDevices[i][0]->hasAfterGlowSupport() && + !ws2812FXrunning[i]) { + // No other effect is running, handle after glow effect. + ((CombinedGiAndLightMatrixWS2812FXDevice *)ws2812FXDevices[i][0]) + ->updateAfterGlow(); + ws2812FXDevices[i][0]->getWS2812FX()->show(); + } } + } } void EffectsController::start() { - for (int i = 0; i < PPUC_MAX_WS2812FX_DEVICES; i++) { - if (ws2812FXbrightness[i] == 0) { - setBrightness(i + 1, WS2812FX_BRIGHTNESS); - } + for (int i = 0; i < PPUC_MAX_WS2812FX_DEVICES; i++) { + if (ws2812FXbrightness[i] == 0) { + // setBrightness(i + 1, WS2812FX_BRIGHTNESS); } + } - _eventDispatcher->dispatch( - new Event(EVENT_SOURCE_EFFECT, 1, 255) - ); + _eventDispatcher->dispatch(new Event(EVENT_SOURCE_EFFECT, 1, 255)); } diff --git a/src/EffectsController.h b/src/EffectsController.h index aac4396..f27b579 100644 --- a/src/EffectsController.h +++ b/src/EffectsController.h @@ -8,38 +8,33 @@ #ifndef EFFECTSCONTROLLER_h #define EFFECTSCONTROLLER_h -#include "PPUC.h" -#if defined(__IMXRT1062__) // Teensy 4.1 - #include -#else - #include -#endif +#include #include -#include "Effects/Effect.h" -#include "Effects/EffectContainer.h" -#include "EventDispatcher/CrossLinkDebugger.h" -#include "EventDispatcher/Event.h" -#include "EventDispatcher/EventDispatcher.h" -#include "EventDispatcher/EventListener.h" -#include "InputDevices/EffectControllerTestButtons.h" -#include "InputDevices/GeneralIlluminationWPC.h" +#include "EffectDevices/CombinedGiAndLightMatrixWS2812FXDevice.h" #include "EffectDevices/LedBuiltInDevice.h" #include "EffectDevices/NullDevice.h" -#include "EffectDevices/WavePWMDevice.h" -#include "EffectDevices/WS2812FXDevice.h" -#include "EffectDevices/CombinedGiAndLightMatrixWS2812FXDevice.h" #include "EffectDevices/RgbStripDevice.h" +#include "EffectDevices/WS2812FXDevice.h" +#include "EffectDevices/WavePWMDevice.h" +#include "Effects/Effect.h" +#include "Effects/EffectContainer.h" +#include "Effects/ImpulsePWMEffect.h" #include "Effects/LedBlinkEffect.h" +#include "Effects/LedOnEffect.h" #include "Effects/NullEffect.h" #include "Effects/RGBColorCycleEffect.h" -#include "Effects/WS2812FXEffect.h" -#include "Effects/ImpulsePWMEffect.h" #include "Effects/RampDownStopPWMEffect.h" #include "Effects/SinePWMEffect.h" +#include "Effects/WS2812FXEffect.h" +#include "EventDispatcher/CrossLinkDebugger.h" +#include "EventDispatcher/Event.h" +#include "EventDispatcher/EventDispatcher.h" +#include "EventDispatcher/EventListener.h" +#include "PPUC.h" #ifndef EFFECT_STACK_SIZE - #define EFFECT_STACK_SIZE 128 +#define EFFECT_STACK_SIZE 128 #endif #define WS2812FX_BRIGHTNESS 64 @@ -48,362 +43,114 @@ #define UPDATE_INTERVAL_WS2812FX_AFTERGLOW 3 #define UPDATE_INTERVAL_WS2812FX_BRIGHTNESS 10 -#if PPUC_CONTROLLER == CONTROLLER_TEENSY_OUTPUT - #define PPUC_MAX_WS2812FX_DEVICES 7 - #define PPUC_MAX_BRIGHTNESS_CONTROLS 4 -#else - #define PPUC_MAX_WS2812FX_DEVICES 1 - #define PPUC_MAX_BRIGHTNESS_CONTROLS 1 -#endif - -#if defined(PPUC_NUM_LEDS_1) && defined(PPUC_LED_TYPE_1) && defined(__IMXRT1062__) - DMAMEM byte frameBuffer1[PPUC_NUM_LEDS_1 * ((PPUC_LED_TYPE_1 < 6) ? 3 : 4) * 4]; // 12 bytes per LED for RGB, 16 bytes for RGBW -#endif - -#if defined(PPUC_NUM_LEDS_2) && defined(PPUC_LED_TYPE_2) && defined(__IMXRT1062__) - DMAMEM byte frameBuffer2[PPUC_NUM_LEDS_2 * ((PPUC_LED_TYPE_2 < 6) ? 3 : 4) * 4]; // 12 bytes per LED for RGB, 16 bytes for RGBW -#endif - -#if defined(PPUC_NUM_LEDS_3) && defined(PPUC_LED_TYPE_3) && defined(__IMXRT1062__) - DMAMEM byte frameBuffer3[PPUC_NUM_LEDS_3 * ((PPUC_LED_TYPE_3 < 6) ? 3 : 4) * 4]; // 12 bytes per LED for RGB, 16 bytes for RGBW -#endif - -#if defined(PPUC_NUM_LEDS_4) && defined(PPUC_LED_TYPE_4) && defined(__IMXRT1062__) - DMAMEM byte frameBuffer4[PPUC_NUM_LEDS_4 * ((PPUC_LED_TYPE_4 < 6) ? 3 : 4) * 4]; // 12 bytes per LED for RGB, 16 bytes for RGBW -#endif - -#if defined(PPUC_NUM_LEDS_5) && defined(PPUC_LED_TYPE_5) && defined(__IMXRT1062__) - DMAMEM byte frameBuffer5[PPUC_NUM_LEDS_5 * ((PPUC_LED_TYPE_5 < 6) ? 3 : 4) * 4]; // 12 bytes per LED for RGB, 16 bytes for RGBW -#endif - -#if defined(PPUC_NUM_LEDS_6) && defined(PPUC_LED_TYPE_6) && defined(__IMXRT1062__) - DMAMEM byte frameBuffer6[PPUC_NUM_LEDS_6 * ((PPUC_LED_TYPE_6 < 6) ? 3 : 4) * 4]; // 12 bytes per LED for RGB, 16 bytes for RGBW -#endif - -#if defined(PPUC_NUM_LEDS_7) && defined(PPUC_LED_TYPE_7) && defined(__IMXRT1062__) - DMAMEM byte frameBuffer7[PPUC_NUM_LEDS_7 * ((PPUC_LED_TYPE_7 < 6) ? 3 : 4) * 4]; // 12 bytes per LED for RGB, 16 bytes for RGBW -#endif +#define PPUC_MAX_WS2812FX_DEVICES 1 +#define PPUC_MAX_BRIGHTNESS_CONTROLS 1 class EffectsController : public EventListener { - -public: - EffectsController(int ct, int pf) : EventListener(){ - controllerType = ct; - platform = pf; - - effectsControllerInstance = this; - _eventDispatcher = new EventDispatcher(); - _eventDispatcher->addListener(this); - - if (controllerType == CONTROLLER_TEENSY_OUTPUT) { - _ledBuiltInDevice = new LedBuiltInDevice(); - _ledBuiltInDevice->on(); - _nullDevice = new NullDevice(); - _testButtons = new EffectControllerTestButtons(_eventDispatcher); - _shakerPWMDevice = new WavePWMDevice(36); - _shakerPWMDevice->off(); - _ledPWMDevice = new WavePWMDevice(37); - _ledPWMDevice->off(); - if (controllerType != CONTROLLER_TEENSY_OUTPUT) { - _rgbStripeDevice = new RgbStripDevice(9, 10, 11); - _rgbStripeDevice->off(); - } - else { - // In revision 0.1.0 these pins are D5-D7, but we don't need them for the WPC GI. - pinMode(9, INPUT); - pinMode(10, INPUT); - pinMode(11, INPUT); - } - - brightnessControlBasePin = 38; - - #if defined(PPUC_NUM_LEDS_1) && defined(PPUC_LED_TYPE_1) - ws2812FXDevices[0][0] = new WS2812FXDevice( - new WS2812FX(PPUC_NUM_LEDS_1, 1, PPUC_LED_TYPE_1), - 0, - PPUC_NUM_LEDS_1 - 1, - 0, - 0 - ); - ws2812FXDevices[0][0]->getWS2812FX()->init(); - ws2812FXDeviceCounters[0] = 1; - - #if defined(__IMXRT1062__) // Teensy 4.1 - ws2812Serial[0] = new WS2812Serial(PPUC_NUM_LEDS_1, frameBuffer1, ws2812FXDevices[0][0]->getWS2812FX()->getPixels(), 1, PPUC_LED_TYPE_1); - ws2812Serial[0]->begin(); - ws2812FXDevices[0][0]->getWS2812FX()->setCustomShow(EffectsController::ws2812SerialShow1); - #endif - - ws2812FXDevices[0][0]->off(); - ws2812FXstates[0] = true; - #endif - #if defined(PPUC_NUM_LEDS_2) && defined(PPUC_LED_TYPE_2) - ws2812FXDevices[1][0] = new WS2812FXDevice( - new WS2812FX(PPUC_NUM_LEDS_2, 8, PPUC_LED_TYPE_2), - 0, - PPUC_NUM_LEDS_2 - 1, - 0, - 0 - ); - ws2812FXDevices[1][0]->getWS2812FX()->init(); - ws2812FXDeviceCounters[1] = 1; - - #if defined(__IMXRT1062__) // Teensy 4.1 - ws2812Serial[1] = new WS2812Serial(PPUC_NUM_LEDS_2, frameBuffer2, ws2812FXDevices[1][0]->getWS2812FX()->getPixels(), 8, PPUC_LED_TYPE_2); - ws2812Serial[1]->begin(); - ws2812FXDevices[1][0]->getWS2812FX()->setCustomShow(EffectsController::ws2812SerialShow2); - #endif - - ws2812FXDevices[1][0]->off(); - ws2812FXstates[1] = true; - #endif - #if defined(PPUC_NUM_LEDS_3) && defined(PPUC_LED_TYPE_3) - ws2812FXDevices[2][0] = new WS2812FXDevice( - new WS2812FX(PPUC_NUM_LEDS_3, 14, PPUC_LED_TYPE_3), - 0, - PPUC_NUM_LEDS_3 - 1, - 0, - 0 - ); - ws2812FXDevices[2][0]->getWS2812FX()->init(); - ws2812FXDeviceCounters[2] = 1; - - #if defined(__IMXRT1062__) // Teensy 4.1 - ws2812Serial[2] = new WS2812Serial(PPUC_NUM_LEDS_3, frameBuffer3, ws2812FXDevices[2][0]->getWS2812FX()->getPixels(), 14, PPUC_LED_TYPE_3); - ws2812Serial[2]->begin(); - ws2812FXDevices[2][0]->getWS2812FX()->setCustomShow(EffectsController::ws2812SerialShow3); - #endif - - ws2812FXDevices[2][0]->off(); - ws2812FXstates[2] = true; - #endif - #if defined(PPUC_NUM_LEDS_4) && defined(PPUC_LED_TYPE_4) - ws2812FXDevices[3][0] = new WS2812FXDevice( - new WS2812FX(PPUC_NUM_LEDS_4, 17, PPUC_LED_TYPE_4), - 0, - PPUC_NUM_LEDS_4 - 1, - 0, - 0 - ); - ws2812FXDevices[3][0]->getWS2812FX()->init(); - ws2812FXDeviceCounters[3] = 1; - - #if defined(__IMXRT1062__) // Teensy 4.1 - ws2812Serial[3] = new WS2812Serial(PPUC_NUM_LEDS_4, frameBuffer4, ws2812FXDevices[3][0]->getWS2812FX()->getPixels(), 17, PPUC_LED_TYPE_4); - ws2812Serial[3]->begin(); - ws2812FXDevices[3][0]->getWS2812FX()->setCustomShow(EffectsController::ws2812SerialShow4); - #endif - - ws2812FXDevices[3][0]->off(); - ws2812FXstates[3] = true; - #endif - #if defined(PPUC_NUM_LEDS_5) && defined(PPUC_LED_TYPE_5) - ws2812FXDevices[4][0] = new WS2812FXDevice( - new WS2812FX(PPUC_NUM_LEDS_5, 20, PPUC_LED_TYPE_5), - 0, - PPUC_NUM_LEDS_5 - 1, - 0, - 0 - ); - ws2812FXDevices[4][0]->getWS2812FX()->init(); - ws2812FXDeviceCounters[4] = 1; - - #if defined(__IMXRT1062__) // Teensy 4.1 - ws2812Serial[4] = new WS2812Serial(PPUC_NUM_LEDS_5, frameBuffer5, ws2812FXDevices[4][0]->getWS2812FX()->getPixels(), 20, PPUC_LED_TYPE_5); - ws2812Serial[4]->begin(); - ws2812FXDevices[4][0]->getWS2812FX()->setCustomShow(EffectsController::ws2812SerialShow5); - #endif - - ws2812FXDevices[4][0]->off(); - ws2812FXstates[4] = true; - #endif - #if defined(PPUC_NUM_LEDS_6) && defined(PPUC_LED_TYPE_6) - ws2812FXDevices[5][0] = new WS2812FXDevice( - new WS2812FX(PPUC_NUM_LEDS_6, 24, PPUC_LED_TYPE_6), - 0, - PPUC_NUM_LEDS_6 - 1, - 0, - 0 - ); - ws2812FXDevices[5][0]->getWS2812FX()->init(); - ws2812FXDeviceCounters[5] = 1; - - #if defined(__IMXRT1062__) // Teensy 4.1 - ws2812Serial[5] = new WS2812Serial(PPUC_NUM_LEDS_6, frameBuffer6, ws2812FXDevices[5][0]->getWS2812FX()->getPixels(), 24, PPUC_LED_TYPE_6); - ws2812Serial[5]->begin(); - ws2812FXDevices[5][0]->getWS2812FX()->setCustomShow(EffectsController::ws2812SerialShow6); - #endif - - ws2812FXDevices[5][0]->off(); - ws2812FXstates[5] = true; - #endif - #if defined(PPUC_NUM_LEDS_7) && defined(PPUC_LED_TYPE_7) - ws2812FXDevices[6][0] = new WS2812FXDevice( - new WS2812FX(PPUC_NUM_LEDS_7, 29, PPUC_LED_TYPE_7), - 0, - PPUC_NUM_LEDS_7 - 1, - 0, - 0 - ); - ws2812FXDevices[6][0]->getWS2812FX()->init(); - ws2812FXDeviceCounters[6] = 1; - - #if defined(__IMXRT1062__) // Teensy 4.1 - ws2812Serial[6] = new WS2812Serial(PPUC_NUM_LEDS_7, frameBuffer7, ws2812FXDevices[6][0]->getWS2812FX()->getPixels(), 29, PPUC_LED_TYPE_7); - ws2812Serial[6]->begin(); - ws2812FXDevices[6][0]->getWS2812FX()->setCustomShow(EffectsController::ws2812SerialShow7); - #endif - - ws2812FXDevices[6][0]->off(); - ws2812FXstates[6] = true; - #endif - _testButtons = new EffectControllerTestButtons(_eventDispatcher); - - if (platform == PLATFORM_WPC) { - _generalIllumintationWPC = new GeneralIlluminationWPC(_eventDispatcher); - _generalIllumintationWPC->start(); - } - } - else if (controllerType == CONTROLLER_NANO_PIN2DMD_OUTPUT) { - _ledBuiltInDevice = new LedBuiltInDevice(); - _ledBuiltInDevice->on(); - _nullDevice = new NullDevice(); - _shakerPWMDevice = new WavePWMDevice(9); - _shakerPWMDevice->off(); - #if defined(PIN_A0) - brightnessControlBasePin = PIN_A0; - #endif - #if defined(PPUC_NUM_LEDS_1) && defined(PPUC_LED_TYPE_1) - ws2812FXDevices[0][0] = new WS2812FXDevice( - new WS2812FX(PPUC_NUM_LEDS_1, 6, PPUC_LED_TYPE_1), - 0, - PPUC_NUM_LEDS_1 - 1, - 0, - 0 - ); - ws2812FXDevices[0][0]->getWS2812FX()->init(); - ws2812FXDeviceCounters[0] = 1; - - ws2812FXDevices[0][0]->off(); - ws2812FXstates[0] = true; - #endif - } - else if (controllerType == CONTROLLER_16_8_1) { - // Read bordID. - // @todo draft! - boardId = (analogRead(28) - 100) / 16 ; - } else { - Serial.print("Unsupported Effects Controller: "); - Serial.println(controllerType); - } + public: + EffectsController(int ct, int pf) : EventListener() { + controllerType = ct; + platform = pf; + + effectsControllerInstance = this; + _eventDispatcher = new EventDispatcher(); + _eventDispatcher->addListener(this); + + if (controllerType == CONTROLLER_16_8_1) { + // Read bordID. Ideal value at 10bit resolution: (DIP+1)*1023*2/35 + // -> 58.46 to 935.3 + boardId = (16 - ((int)((analogRead(28) + 29.23) / 58.46))) & 0b0111; + + _ledBuiltInDevice = new LedBuiltInDevice(); + _ledBuiltInDevice->on(); + + addEffect(new LedBlinkEffect(), _ledBuiltInDevice, new Event(EVENT_RUN), + 1, // priority + -1, // repeat + 0 // mode + ); + } else { + Serial.print("Unsupported Effects Controller: "); + Serial.println(controllerType); } + } - EventDispatcher* eventDispatcher(); - - LedBuiltInDevice* ledBuiltInDevice(); - - NullDevice* nullDevice(); - - WavePWMDevice* shakerPWMDevice(); - - WavePWMDevice* ledPWMDevice(); + EventDispatcher* eventDispatcher(); - RgbStripDevice* rgbStripDevice(); + LedBuiltInDevice* ledBuiltInDevice(); - WS2812FXDevice* ws2812FXDevice(int port); + NullDevice* nullDevice(); - CombinedGiAndLightMatrixWS2812FXDevice* createCombinedGiAndLightMatrixWs2812FXDevice(int port); + WavePWMDevice* shakerPWMDevice(); - CombinedGiAndLightMatrixWS2812FXDevice* giAndLightMatrix(int port); + WavePWMDevice* ledPWMDevice(); - WS2812FXDevice* createWS2812FXDevice(int port, int number, int segments, int firstLED, int lastLED); + RgbStripDevice* rgbStripDevice(); - WS2812FXDevice* ws2812FXDevice(int port, int number); + WS2812FXDevice* ws2812FXDevice(int port); - GeneralIlluminationWPC* generalIllumintationWPC(); + CombinedGiAndLightMatrixWS2812FXDevice* + createCombinedGiAndLightMatrixWs2812FXDevice(int port); - void addEffect(Effect* effect, EffectDevice* device, Event* event, int priority, int repeat, int mode); + CombinedGiAndLightMatrixWS2812FXDevice* giAndLightMatrix(int port); - //void addEffect(Effect* effect, EffectDevice* device, EventSequence* sequence, int priority, int repeat); + WS2812FXDevice* createWS2812FXDevice(int port, int number, int segments, + int firstLED, int lastLED); - void addEffect(EffectContainer* container); + WS2812FXDevice* ws2812FXDevice(int port, int number); - void attachBrightnessControl(byte port, byte poti); + void addEffect(Effect* effect, EffectDevice* device, Event* event, + int priority, int repeat, int mode); - void setBrightness(byte port, byte brightness); + // void addEffect(Effect* effect, EffectDevice* device, EventSequence* + // sequence, int priority, int repeat); - void start(); + void addEffect(EffectContainer* container); - void update(); + void attachBrightnessControl(byte port, byte poti); - void handleEvent(Event* event); + void setBrightness(byte port, byte brightness); - void handleEvent(ConfigEvent* event); + void start(); - #if defined(__IMXRT1062__) // Teensy 4.1 - static void ws2812SerialShow1() { - effectsControllerInstance->ws2812Serial[0]->show(); - } - static void ws2812SerialShow2() { - effectsControllerInstance->ws2812Serial[1]->show(); - } - static void ws2812SerialShow3() { - effectsControllerInstance->ws2812Serial[2]->show(); - } - static void ws2812SerialShow4() { - effectsControllerInstance->ws2812Serial[3]->show(); - } - static void ws2812SerialShow5() { - effectsControllerInstance->ws2812Serial[4]->show(); - } - static void ws2812SerialShow6() { - effectsControllerInstance->ws2812Serial[5]->show(); - } - static void ws2812SerialShow7() { - effectsControllerInstance->ws2812Serial[6]->show(); - } - #endif + void update(); -private: - EventDispatcher* _eventDispatcher; - LedBuiltInDevice* _ledBuiltInDevice; - NullDevice* _nullDevice; - WavePWMDevice* _shakerPWMDevice; - WavePWMDevice* _ledPWMDevice; - RgbStripDevice* _rgbStripeDevice; - WS2812FXDevice* ws2812FXDevices[PPUC_MAX_WS2812FX_DEVICES][10]; - int ws2812FXDeviceCounters[PPUC_MAX_WS2812FX_DEVICES] = {0}; - bool ws2812FXstates[PPUC_MAX_WS2812FX_DEVICES] = {0}; - bool ws2812FXrunning[PPUC_MAX_WS2812FX_DEVICES] = {0}; - bool ws2812FXbrightness[PPUC_MAX_WS2812FX_DEVICES] = {0}; - #if defined(__IMXRT1062__) // Teensy 4.1 - WS2812Serial* ws2812Serial[PPUC_MAX_WS2812FX_DEVICES]; - #endif - EffectContainer* stackEffectContainers[EFFECT_STACK_SIZE]; - int stackCounter = -1; - byte brightnessControl[PPUC_MAX_WS2812FX_DEVICES] = {0}; - byte brightnessControlReads[PPUC_MAX_BRIGHTNESS_CONTROLS] = {0}; - byte brightnessControlBasePin = 0; + void handleEvent(Event* event); - int mode = 0; + void handleEvent(ConfigEvent* event); - byte platform; - byte controllerType; - byte boardId = 255; - byte config_port = 0; - byte config_type = 0; - byte config_amount = 0; - byte config_afterGlow = 0; - byte config_heatUp = 0; + private: + EventDispatcher* _eventDispatcher; + LedBuiltInDevice* _ledBuiltInDevice; + NullDevice* _nullDevice; + WavePWMDevice* _shakerPWMDevice; + WavePWMDevice* _ledPWMDevice; + RgbStripDevice* _rgbStripeDevice; + WS2812FXDevice* ws2812FXDevices[PPUC_MAX_WS2812FX_DEVICES][10]; + int ws2812FXDeviceCounters[PPUC_MAX_WS2812FX_DEVICES] = {0}; + bool ws2812FXstates[PPUC_MAX_WS2812FX_DEVICES] = {0}; + bool ws2812FXrunning[PPUC_MAX_WS2812FX_DEVICES] = {0}; + byte ws2812FXbrightness[PPUC_MAX_WS2812FX_DEVICES] = {0}; + EffectContainer* stackEffectContainers[EFFECT_STACK_SIZE]; + int stackCounter = -1; + bool flickerState = false; + int mode = 0; - unsigned long ws2812UpdateInterval = 0; - unsigned long ws2812AfterGlowUpdateInterval = 0; - unsigned long brightnessUpdateInterval = 0; + byte platform; + byte controllerType; + byte boardId = 255; + byte config_port = 0; + byte config_values[9] = {0}; + neoPixelType config_neoPixelType = 0; + uint32_t config_payload = 0; + WS2812FXEffect* ws1812Effect; + WavePWMEffect* pwmEffect; - EffectControllerTestButtons* _testButtons; - GeneralIlluminationWPC* _generalIllumintationWPC; + unsigned long ws2812UpdateInterval = 0; + unsigned long ws2812AfterGlowUpdateInterval = 0; + unsigned long brightnessUpdateInterval = 0; - static EffectsController* effectsControllerInstance; + static EffectsController* effectsControllerInstance; }; #endif diff --git a/src/EventDispatcher/CrossLinkDebugger.cpp b/src/EventDispatcher/CrossLinkDebugger.cpp index 812d054..6a7c287 100644 --- a/src/EventDispatcher/CrossLinkDebugger.cpp +++ b/src/EventDispatcher/CrossLinkDebugger.cpp @@ -1,25 +1,77 @@ #include "CrossLinkDebugger.h" -void CrossLinkDebugger::handleEvent(Event* event) { - // On Teensy Serial is the USB Serial. - Serial.print("handleEvent: sourceId "); - Serial.print(event->sourceId); - Serial.print(", eventId "); - Serial.print(event->eventId, DEC); - Serial.print(", value "); - Serial.println(event->value, DEC); +bool CrossLinkDebugger::active = false; + +CrossLinkDebugger::CrossLinkDebugger() { + if (get_core_num() == 0) { + rp2040.idleOtherCore(); + Serial.println("PPUC IO_16_8_1"); + Serial.print("PPUC board #"); + // Read bordID. Ideal value at 10bit resolution: (DIP+1)*1023*2/35 -> 58.46 + // to 935.3 + Serial.println((16 - ((int)((analogRead(28) + 29.23) / 58.46))) & 0b0111); + Serial.println("PPUC core #0 started"); + Serial.println("PPUC CrossLinkDebugger"); + Serial.println("----------------------"); + rp2040.resumeOtherCore(); + } else { + rp2040.idleOtherCore(); + Serial.println("PPUC core #1 started"); + rp2040.resumeOtherCore(); + } + + CrossLinkDebugger::active = true; +} + +void CrossLinkDebugger::handleEvent(Event *event) { + rp2040.idleOtherCore(); + Serial.print("Core "); + Serial.print(get_core_num(), DEC); + Serial.print(" "); + Serial.print("handleEvent: sourceId "); + Serial.print(event->sourceId); + Serial.print(", eventId "); + Serial.print(event->eventId, DEC); + Serial.print(", value "); + Serial.println(event->value, DEC); + rp2040.resumeOtherCore(); } -void CrossLinkDebugger::handleEvent(ConfigEvent* event) { - // On Teensy Serial is the USB Serial. - Serial.print("handleEvent: ConfigEvent, boardId "); - Serial.print(event->boardId, DEC); - Serial.print(", topic "); - Serial.print(event->topic, DEC); - Serial.print(", index "); - Serial.print(event->index, DEC); - Serial.print(", key "); - Serial.print(event->key, DEC); - Serial.print(", value "); - Serial.println(event->value, HEX); +void CrossLinkDebugger::handleEvent(ConfigEvent *event) { + rp2040.idleOtherCore(); + Serial.print("Core "); + Serial.print(get_core_num(), DEC); + Serial.print(" "); + Serial.print("handleConfigEvent: boardId "); + Serial.print(event->boardId, DEC); + Serial.print(", topic "); + Serial.print(event->topic, DEC); + Serial.print(", index "); + Serial.print(event->index, DEC); + Serial.print(", key "); + Serial.print(event->key, DEC); + Serial.print(", value(DEC) "); + Serial.print(event->value, DEC); + Serial.print(", value(HEX) "); + Serial.println(event->value, HEX); + rp2040.resumeOtherCore(); +} + +void CrossLinkDebugger::debug(const char *format, ...) { + if (CrossLinkDebugger::active) { + char buffer[1024]; + + va_list args; + va_start(args, format); + vsnprintf(buffer, sizeof(buffer), format, args); + va_end(args); + + rp2040.idleOtherCore(); + Serial.print("Core "); + Serial.print(get_core_num(), DEC); + Serial.print(" "); + Serial.print("debug: "); + Serial.println(buffer); + rp2040.resumeOtherCore(); + } } diff --git a/src/EventDispatcher/CrossLinkDebugger.h b/src/EventDispatcher/CrossLinkDebugger.h index 512cd64..ee21079 100644 --- a/src/EventDispatcher/CrossLinkDebugger.h +++ b/src/EventDispatcher/CrossLinkDebugger.h @@ -9,17 +9,21 @@ #define CROSSLINKDEBUGGER_h #include +#include #include "Event.h" #include "EventListener.h" class CrossLinkDebugger : public EventListener { -public: - CrossLinkDebugger() {} + public: + CrossLinkDebugger(); - void handleEvent(Event* event); - void handleEvent(ConfigEvent* event); + void handleEvent(Event *event); + void handleEvent(ConfigEvent *event); + static void debug(const char *format, ...); + + static bool active; }; #endif diff --git a/src/EventDispatcher/Event.h b/src/EventDispatcher/Event.h index 4760520..b024f1d 100644 --- a/src/EventDispatcher/Event.h +++ b/src/EventDispatcher/Event.h @@ -1,121 +1,187 @@ /* Event.h Created by Markus Kalkbrenner, 2021-2023. - - Play more pinball! */ #ifndef EVENT_h #define EVENT_h -#define EVENT_SOURCE_ANY 42 // "*" -#define EVENT_SOURCE_DEBUG 66 // "B" Debug -#define EVENT_CONFIGURATION 67 // "C" Configure I/O -#define EVENT_SOURCE_DMD 68 // "D" VPX/DOF/PUP -#define EVENT_SOURCE_EVENT 69 // "E" VPX/DOF/PUP common event from different system, like -#define EVENT_SOURCE_EFFECT 70 // "F" custom event from running Effect -#define EVENT_SOURCE_GI 71 // "G" WPC GI -#define EVENT_SOURCE_LIGHT 76 // "L" VPX/DOF/PUP lights, mainly playfield inserts -#define EVENT_NULL 78 // "N" NULL event -#define EVENT_SOURCE_SOUND 79 // "O" sound command -#define EVENT_POLL_EVENTS 80 // "P" Poll events command, mainly read switches -#define EVENT_READ_SWITCHES 82 // "R" Read current state of all switches on i/o boards -#define EVENT_SOURCE_SOLENOID 83 // "S" VPX/DOF/PUP includes flashers -#define EVENT_SOURCE_SWITCH 87 // "W" VPX/DOF/PUP - -#define CONFIG_TOPIC_LED_STRING 103 // "g" -#define CONFIG_TOPIC_LAMPS 108 // "l" -#define CONFIG_TOPIC_MECHS 109 // "m" -#define CONFIG_TOPIC_PWM 112 // "p" -#define CONFIG_TOPIC_SWITCHES 115 // "s" - -#define CONFIG_TOPIC_HOLD_POWER_ACTIVATION_TIME 65 // "A" -#define CONFIG_TOPIC_FAST_SWITCH 70 // "F" -#define CONFIG_TOPIC_AFTER_GLOW 71 // "G" -#define CONFIG_TOPIC_HOLD_POWER 72 // "H" -#define CONFIG_TOPIC_MAX_PULSE_TIME 77 // "M" -#define CONFIG_TOPIC_NUMBER 78 // "N" -#define CONFIG_TOPIC_AMOUNT_LEDS 79 // "O" -#define CONFIG_TOPIC_PORT 80 // "P" -#define CONFIG_TOPIC_MIN_PULSE_TIME 84 // "T" -#define CONFIG_TOPIC_LIGHT_UP 85 // "U" -#define CONFIG_TOPIC_POWER 87 // "W" -#define CONFIG_TOPIC_TYPE 89 // "Y" - -typedef unsigned char UINT8; -typedef unsigned short UINT16; -typedef unsigned int UINT32; +#include + +// microseconds +#define RS485_MODE_SWITCH_DELAY 50 + +#define EVENT_SOURCE_ANY 42 // "*" +#define EVENT_SOURCE_DEBUG 66 // "B" Debug +#define EVENT_CONFIGURATION 67 // "C" Configure I/O +#define EVENT_SOURCE_DMD 68 // "D" VPX/DOF/PUP +#define EVENT_SOURCE_EVENT \ + 69 // "E" VPX/DOF/PUP common event from different system, like +#define EVENT_SOURCE_EFFECT 70 // "F" custom event from running Effect +#define EVENT_SOURCE_GI 71 // "G" GI +#define EVENT_SOURCE_LIGHT \ + 76 // "L" VPX/DOF/PUP lights, mainly playfield inserts +#define EVENT_NULL 78 // "N" NULL event +#define EVENT_SOURCE_SOUND 79 // "O" sound command +#define EVENT_POLL_EVENTS 80 // "P" Poll events command, mainly read switches +#define EVENT_READ_SWITCHES \ + 82 // "R" Read current state of all switches on i/o boards +#define EVENT_SOURCE_SOLENOID 83 // "S" VPX/DOF/PUP includes flashers +#define EVENT_SOURCE_SWITCH 87 // "W" VPX/DOF/PUP +#define EVENT_PING 88 // "X" +#define EVENT_PONG 89 // "Y" +#define EVENT_RESET 90 // "Z" +#define EVENT_RUN 91 // RUN +#define EVENT_NO_ERROR 98 // NO ERROR +#define EVENT_ERROR 99 // ERROR + +#define CONFIG_TOPIC_PLATFORM 102 // "f" +#define CONFIG_TOPIC_LED_STRING 103 // "g" +#define CONFIG_TOPIC_LED_SEGMENT 104 // "h" +#define CONFIG_TOPIC_LED_EFFECT 105 // "i" +#define CONFIG_TOPIC_PWM_EFFECT 106 // "j" +#define CONFIG_TOPIC_LAMPS 108 // "l" +#define CONFIG_TOPIC_MECHS 109 // "m" +#define CONFIG_TOPIC_PWM 112 // "p" +#define CONFIG_TOPIC_COIN_DOOR_CLOSED_SWITCH 113 // "q" +#define CONFIG_TOPIC_GAME_ON_SOLENOID 114 // "r" +#define CONFIG_TOPIC_SWITCHES 115 // "s" +#define CONFIG_TOPIC_TRIGGER 116 // "t" +#define CONFIG_TOPIC_SWITCH_MATRIX 120 // "x" +#define CONFIG_TOPIC_SWITCH_CHAIN 121 // "y" + +#define CONFIG_TOPIC_HOLD_POWER_ACTIVATION_TIME 65 // "A" +#define CONFIG_TOPIC_DURATION 65 // "A" +#define CONFIG_TOPIC_VALUE 65 // "A" +#define CONFIG_TOPIC_BRIGHTNESS 66 // "B" +#define CONFIG_TOPIC_REVERSE 66 // "B" +#define CONFIG_TOPIC_COLOR 67 // "C" +#define CONFIG_TOPIC_FAST_SWITCH 70 // "F" +#define CONFIG_TOPIC_FREQUENCY 70 // "F" +#define CONFIG_TOPIC_AFTER_GLOW 71 // "G" +#define CONFIG_TOPIC_HOLD_POWER 72 // "H" +#define CONFIG_TOPIC_LED_NUMBER 76 // "L" +#define CONFIG_TOPIC_MIN_PULSE_TIME 77 // "M" +#define CONFIG_TOPIC_FROM 77 // "M" +#define CONFIG_TOPIC_MIN_INTENSITY 77 // "M" +#define CONFIG_TOPIC_DEBOUNCE_TIME 77 // "M" +#define CONFIG_TOPIC_NUMBER 78 // "N" +#define CONFIG_TOPIC_AMOUNT_LEDS 79 // "O" +#define CONFIG_TOPIC_NUM_ROWS 79 // "O" +#define CONFIG_TOPIC_PORT 80 // "P" +#define CONFIG_TOPIC_SPEED 83 // "S" +#define CONFIG_TOPIC_SOURCE 83 // "S" +#define CONFIG_TOPIC_MAX_PULSE_TIME 84 // "T" +#define CONFIG_TOPIC_TO 84 // "T" +#define CONFIG_TOPIC_MAX_INTENSITY 84 // "T" +#define CONFIG_TOPIC_LIGHT_UP 85 // "U" +#define CONFIG_TOPIC_ACTIVE_LOW 86 // "V" +#define CONFIG_TOPIC_POWER 87 // "W" +#define CONFIG_TOPIC_NEXT_BOARD 88 // "X" +#define CONFIG_TOPIC_TYPE 89 // "Y" +#define CONFIG_TOPIC_EFFECT 89 // "Y" +#define CONFIG_TOPIC_MODE 90 // "Z" +#define CONFIG_TOPIC_PRIORITY 91 // +#define CONFIG_TOPIC_REPEAT 92 // +#define CONFIG_TOPIC_NULL 99 // NULL + +#define PWM_TYPE_SOLENOID 1 // Coil +#define PWM_TYPE_FLASHER 2 // Flasher +#define PWM_TYPE_LAMP 3 // Lamp +#define PWM_TYPE_MOTOR 4 // Motor +#define PWM_TYPE_SHAKER 5 // Shaker + +#define LED_TYPE_GI 1 // GI +#define LED_TYPE_FLASHER 2 // Flasher +#define LED_TYPE_LAMP 3 // Lamp + +#define PWM_EFFECT_SINE 1 +#define PWM_EFFECT_RAMP_DOWN_STOP 2 +#define PWM_EFFECT_IMPULSE 3 struct Event { - byte sourceId; - word eventId; - byte value; - bool localFast; - - Event(byte sId, word eId) { - sourceId = sId; - eventId = eId; - value = 1; - localFast = false; - } - - Event(char sId, word eId, byte v) { - sourceId = sId; - eventId = eId; - value = v; - localFast = false; - } - - Event(char sId, word eId, byte v, bool lf) { - sourceId = sId; - eventId = eId; - value = v; - localFast = lf; - } - - Event(const Event* other) { - sourceId = other->sourceId; - eventId = other->eventId; - value = other->value; - localFast = other->localFast; - } - - bool operator==(const Event &other) const { - return this->sourceId == other.sourceId - && this->eventId == other.eventId - && this->value == other.value; - } - - bool operator!=(const Event &other) const { - return !(*this == other); - } + uint8_t sourceId; + uint16_t eventId; + uint8_t value; + bool localFast; + Event(uint8_t sId) { + sourceId = sId; + eventId = 1; + value = 1; + localFast = false; + } + + Event(uint8_t sId, uint16_t eId) { + sourceId = sId; + eventId = eId; + value = 1; + localFast = false; + } + + Event(uint8_t sId, uint16_t eId, uint8_t v) { + sourceId = sId; + eventId = eId; + value = v; + localFast = false; + } + + Event(uint8_t sId, uint16_t eId, uint8_t v, bool lf) { + sourceId = sId; + eventId = eId; + value = v; + localFast = lf; + } + + // Clone the event. + Event(const Event* other) { + sourceId = other->sourceId; + eventId = other->eventId; + value = other->value; + localFast = other->localFast; + } + + bool operator==(const Event& other) const { + return this->sourceId == other.sourceId && this->eventId == other.eventId && + this->value == other.value; + } + + bool operator!=(const Event& other) const { return !(*this == other); } }; struct ConfigEvent { - byte sourceId; // EVENT_CONFIGURATION - byte boardId; // - byte topic; // lamps - byte index; // 0, index of assignment - byte key; // ledType, assignment/brightness - int value; // FFFF00FF - - ConfigEvent(char b, char t, char i, char k, int v) { - sourceId = EVENT_CONFIGURATION; - boardId = b; - topic = t; - index = i; - key = k; - value = v; - } - - ConfigEvent(const ConfigEvent* other) { - sourceId = other->sourceId; - boardId = other->boardId; - topic = other->topic; - index = other->index; - key = other->key; - value = other->value; - } + uint8_t sourceId; // EVENT_CONFIGURATION + uint8_t boardId; // + uint8_t topic; // lamps + uint8_t index; // 0, index of assignment + uint8_t key; // ledType, assignment/brightness + uint32_t value; // FFFF00FF + + ConfigEvent(uint8_t b) { + sourceId = EVENT_CONFIGURATION; + boardId = b; + topic = CONFIG_TOPIC_NULL; + index = 1; + key = 1; + value = 1; + } + + ConfigEvent(uint8_t b, uint8_t t, uint8_t i, uint8_t k, uint32_t v) { + sourceId = EVENT_CONFIGURATION; + boardId = b; + topic = t; + index = i; + key = k; + value = v; + } + + // Clone the event. + ConfigEvent(const ConfigEvent* other) { + boardId = other->boardId; + topic = other->topic; + index = other->index; + key = other->key; + value = other->value; + } }; #endif diff --git a/src/EventDispatcher/EventDispatcher.cpp b/src/EventDispatcher/EventDispatcher.cpp index 239d8db..50bfe26 100644 --- a/src/EventDispatcher/EventDispatcher.cpp +++ b/src/EventDispatcher/EventDispatcher.cpp @@ -1,217 +1,657 @@ #include "EventDispatcher.h" -EventDispatcher::EventDispatcher() { - msg[0] = (byte) 255; - msg[5] = (byte) 255; +#include "hardware/uart.h" +#include + +namespace { +constexpr uint32_t kV2RxTimeoutUs = 8000; +} - cmsg[0] = (byte) 255; - cmsg[1] = (byte) EVENT_CONFIGURATION; - cmsg[10] = (byte) 255; +EventDispatcher::EventDispatcher() { + for (uint16_t i = 0; i < ppuc::v2::kMaxCoilBits; ++i) { + coilIndexToNumber[i] = i; + } + for (uint16_t i = 0; i < ppuc::v2::kMaxLampBits; ++i) { + lampIndexToNumber[i] = i; + } + for (uint16_t i = 0; i < ppuc::v2::kMaxSwitchBits; ++i) { + switchIndexToNumber[i] = i; + } } void EventDispatcher::setRS485ModePin(int pin) { - rs485 = true; - rs485Pin = pin; - pinMode(rs485Pin, OUTPUT); - digitalWrite(rs485Pin, LOW); // Read. + rs485 = true; + rs485Pin = pin; +} + +void EventDispatcher::setBoard(byte b) { board = b; } + +void EventDispatcher::setMultiCoreCrossLink(MultiCoreCrossLink *mccl) { + multiCoreCrossLink = mccl; + multiCore = true; } -void EventDispatcher::setMultiCoreCrossLink(MultiCoreCrossLink* mccl) { -#if defined(ARDUINO_ARCH_MBED_RP2040) || defined(ARDUINO_ARCH_RP2040) - multiCoreCrossLink = mccl; - multiCore = true; -#endif +MultiCoreCrossLink *EventDispatcher::getMultiCoreCrossLink() { + return multiCoreCrossLink; } -// Backward Compatibility, use addCrossLinkSerial(). void EventDispatcher::setCrossLinkSerial(HardwareSerial &reference) { - crossLink = -1; - addCrossLinkSerial(reference); + hwSerial = (HardwareSerial *)&reference; } -void EventDispatcher::addCrossLinkSerial(HardwareSerial &reference) { - hwSerial[++crossLink] = (HardwareSerial*) &reference; - hwSerial[crossLink]->begin(115200); +void EventDispatcher::setDebug(bool enabled) { debugEnabled = enabled; } + +void EventDispatcher::setNextSwitchBoard(byte boardId) { + nextSwitchBoard = boardId; +} + +void EventDispatcher::addListener(EventListener *eventListener) { + addListener(eventListener, EVENT_SOURCE_ANY); } -void EventDispatcher::addListener(EventListener* eventListener) { - addListener(eventListener, EVENT_SOURCE_ANY); +void EventDispatcher::addListener(EventListener *eventListener, char sourceId) { + if (numListeners < (MAX_EVENT_LISTENERS - 1)) { + eventListeners[++numListeners] = eventListener; + eventListenerFilters[numListeners] = sourceId; + } } -void EventDispatcher::addListener(EventListener* eventListener, char sourceId) { - if (numListeners < (MAX_EVENT_LISTENERS - 1)) { - eventListeners[++numListeners] = eventListener; - eventListenerFilters[numListeners] = sourceId; +void EventDispatcher::dispatch(Event *event) { + if (EVENT_RESET == event->sourceId) { + // Force immediate handling of the reset event. Forget about the others. + while (!eventQueue.empty()) { + Event *e = eventQueue.front(); + eventQueue.pop(); + delete e; + } + } + + eventQueue.push(event); + + if (event->localFast) { + for (byte i = 0; i <= numListeners; i++) { + if (event->sourceId == eventListenerFilters[i] || + EVENT_SOURCE_ANY == eventListenerFilters[i]) { + eventListeners[i]->handleEvent(event); + } } + } } -void EventDispatcher::dispatch(Event* event) { - if (stackCounter < (EVENT_STACK_SIZE - 1)) { - stackEvents[++stackCounter] = event; +void EventDispatcher::callListeners(Event *event, bool sendToOtherCore, + bool sendToRS485) { + if (!event->localFast) { + for (byte i = 0; i <= numListeners; i++) { + if (event->sourceId == eventListenerFilters[i] || + EVENT_SOURCE_ANY == eventListenerFilters[i]) { + eventListeners[i]->handleEvent(event); + } + } + } - if (event->localFast) { - for (byte i = 0; i <= numListeners; i++) { - if (event->sourceId == eventListenerFilters[i] || EVENT_SOURCE_ANY == eventListenerFilters[i]) { - eventListeners[i]->handleEvent(event); - } - } - } + if (rs485 && sendToRS485) { + msg[0] = 0b11111111; + msg[1] = event->sourceId; + msg[2] = highByte(event->eventId); + msg[3] = lowByte(event->eventId); + msg[4] = event->value; + msg[5] = 0b10101010; + msg[6] = 0b01010101; + + hwSerial->write(msg, 7); + } + + if (multiCore && sendToOtherCore && event->sourceId != EVENT_NULL) { + multiCoreCrossLink->pushEvent(event); + } + + if (event->sourceId == EVENT_SOURCE_SWITCH) { + updateSwitchBitmap(event); + } + + // delete the event and free the memory + delete event; +} + +void EventDispatcher::callListeners(ConfigEvent *event, bool sendToOtherCore) { + for (byte i = 0; i <= numListeners; i++) { + if (EVENT_CONFIGURATION == eventListenerFilters[i] || + EVENT_SOURCE_ANY == eventListenerFilters[i]) { + eventListeners[i]->handleEvent(event); } - else { - // Too many events stacked, delete the event and free the memory. - delete event; + } + + if (multiCoreCrossLink && sendToOtherCore && event->boardId == board) { + multiCoreCrossLink->pushConfigEvent(event); + } + + // delete the event and free the memory + delete event; +} + +bool EventDispatcher::readBytes(byte *buffer, size_t len) { + size_t offset = 0; + uint32_t start = micros(); + while (offset < len) { + if (hwSerial->available() > 0) { + buffer[offset++] = hwSerial->read(); + continue; + } + + if ((micros() - start) > 8000) { + return false; } + } + + return true; } -void EventDispatcher::callListeners(Event* event, int sender, bool flush) { - if (!event->localFast) { - for (byte i = 0; i <= numListeners; i++) { - if (event->sourceId == eventListenerFilters[i] || EVENT_SOURCE_ANY == eventListenerFilters[i]) { - eventListeners[i]->handleEvent(event); - } +size_t EventDispatcher::getV2PayloadBytes(ppuc::v2::FrameType frameType) { + switch (frameType) { + case ppuc::v2::kFrameSetup: + return ppuc::v2::kSetupPayloadBytes; + case ppuc::v2::kFrameMapping: + return ppuc::v2::kMappingPayloadBytes; + case ppuc::v2::kFrameConfig: + return ppuc::v2::kConfigPayloadBytes; + case ppuc::v2::kFrameOutputState: + return ppuc::v2::BitsToBytes(runtimeConfig.coilBits) + + ppuc::v2::BitsToBytes(runtimeConfig.lampBits); + case ppuc::v2::kFrameSwitchState: + return ppuc::v2::BitsToBytes(runtimeConfig.switchBits); + case ppuc::v2::kFrameSwitchNoChange: + return 0; + case ppuc::v2::kFrameHeartbeat: + case ppuc::v2::kFrameError: + case ppuc::v2::kFrameReset: + return 0; + default: + return 0; + } +} + +bool EventDispatcher::processV2Frame(const byte* frame, size_t payloadBytes) { + const ppuc::v2::FrameType frameType = ppuc::v2::ExtractType(frame[1]); + const size_t crcOffset = ppuc::v2::kHeaderBytes + payloadBytes; + uint16_t receivedCrc = word(frame[crcOffset], frame[crcOffset + 1]); + uint16_t expectedCrc = + ppuc::v2::Crc16Ccitt(frame, ppuc::v2::kHeaderBytes + payloadBytes); + if (receivedCrc != expectedCrc) { + v2RxCrcFail++; + return false; + } + v2RxFrames++; + + if (frameType == ppuc::v2::kFrameSetup) { + ppuc::v2::RuntimeConfig newConfig; + newConfig.coilBits = word(frame[4], frame[5]); + newConfig.lampBits = word(frame[6], frame[7]); + newConfig.switchBits = word(frame[8], frame[9]); + if (ppuc::v2::IsValidRuntimeConfig(newConfig)) { + runtimeConfig = newConfig; + memset(outputCoils, 0, sizeof(outputCoils)); + memset(outputLamps, 0, sizeof(outputLamps)); + memset(switchStates, 0, sizeof(switchStates)); + for (uint16_t i = 0; i < runtimeConfig.coilBits; ++i) { + coilIndexToNumber[i] = i; + } + for (uint16_t i = 0; i < runtimeConfig.lampBits; ++i) { + lampIndexToNumber[i] = i; + } + for (uint16_t i = 0; i < runtimeConfig.switchBits; ++i) { + switchIndexToNumber[i] = i; + } + if (!v2UartDmaActive) { + if (startV2UartDmaTransport()) { + v2CutoverOk++; + } else { + v2CutoverFail++; } + } } + return true; + } - if (!rs485 || flush) { - // Send to other micro controller. But only if there's room left in write buffer. Otherwise the program will be - // blocked. The buffer gets full if the data is not fetched by the other controller for any reason. - // @todo Possible optimization to check hwSerial->availableForWrite() >= 6 failed on Arduino for unknown reason. - - if (crossLink != -1 /* && hwSerial->availableForWrite() >= 6 */) { - // = (byte) 255; - msg[1] = (byte) event->sourceId; - msg[2] = highByte(event->eventId); - msg[3] = lowByte(event->eventId); - msg[4] = event->value; - // = (byte) 255; - - for (int i = 0; i <= crossLink; i++) { - if (i != sender) { - hwSerial[i]->write(msg, 6); - } - } - } + if (frameType == ppuc::v2::kFrameMapping) { + const uint8_t domain = frame[4]; + const uint16_t index = word(frame[6], frame[7]); + const uint16_t number = word(frame[8], frame[9]); -#if defined(ARDUINO_ARCH_MBED_RP2040) || defined(ARDUINO_ARCH_RP2040) - if (multiCore && sender != -1) { - multiCoreCrossLink->pushEventNonBlocking(event); - } -#endif + if (domain == ppuc::v2::kDomainCoil && index < runtimeConfig.coilBits) { + coilIndexToNumber[index] = number; + } else if (domain == ppuc::v2::kDomainLamp && + index < runtimeConfig.lampBits) { + lampIndexToNumber[index] = number; + } else if (domain == ppuc::v2::kDomainSwitch && + index < runtimeConfig.switchBits) { + switchIndexToNumber[index] = number; + } + return true; + } + + if (frameType == ppuc::v2::kFrameOutputState) { + const size_t coilBytes = ppuc::v2::BitsToBytes(runtimeConfig.coilBits); + const size_t lampBytes = ppuc::v2::BitsToBytes(runtimeConfig.lampBits); + applyOutputStates(&frame[4], coilBytes, &frame[4 + coilBytes], lampBytes); + if (frame[2] == board) { + if (switchDirty) { + sendSwitchStateFrame(nextSwitchBoard); + switchDirty = false; + } else { + sendSwitchNoChangeFrame(nextSwitchBoard); + } } + return true; + } + + if (frameType == ppuc::v2::kFrameSwitchState) { + const size_t switchBytes = ppuc::v2::BitsToBytes(runtimeConfig.switchBits); + applySwitchStates(&frame[4], switchBytes); + return true; + } + + if (frameType == ppuc::v2::kFrameSwitchNoChange) { + return true; + } - // delete the event and free the memory - delete event; + if (frameType == ppuc::v2::kFrameReset) { + dispatch(new Event(EVENT_RESET)); + return true; + } + + if (frameType == ppuc::v2::kFrameConfig) { + callListeners( + new ConfigEvent(frame[4], frame[5], frame[6], frame[7], + (((uint32_t)frame[8]) << 24) | + (((uint32_t)frame[9]) << 16) | + (((uint32_t)frame[10]) << 8) | + ((uint32_t)frame[11])), + true); + return true; + } + + return true; } -void EventDispatcher::callListeners(ConfigEvent* event, int sender) { - for (byte i = 0; i <= numListeners; i++) { - if (event->sourceId == eventListenerFilters[i]) { - eventListeners[i]->handleEvent(event); - } +bool EventDispatcher::startV2UartDmaTransport() { + if (v2UartDmaActive) { + return true; + } + + int rxDma = dma_claim_unused_channel(false); + if (rxDma < 0) { + return false; + } + + int txDma = dma_claim_unused_channel(false); + if (txDma < 0) { + dma_channel_unclaim(rxDma); + return false; + } + v2RxDmaChannel = rxDma; + v2TxDmaChannel = txDma; + v2UartDmaActive = true; + v2RxState = V2_RX_IDLE; + v2RxPayloadBytes = 0; + v2RxStateStartUs = micros(); + + return true; +} + +void EventDispatcher::stopV2UartDmaTransport() { + if (!v2UartDmaActive) { + return; + } + + dma_channel_abort(v2RxDmaChannel); + dma_channel_abort(v2TxDmaChannel); + dma_channel_unclaim(v2RxDmaChannel); + dma_channel_unclaim(v2TxDmaChannel); + + v2UartDmaActive = false; + v2RxState = V2_RX_IDLE; + v2RxDmaChannel = -1; + v2TxDmaChannel = -1; +} + +bool EventDispatcher::sendV2FrameUartDma(const byte* frame, size_t frameBytes) { + if (!v2UartDmaActive || !frame || frameBytes == 0) { + return false; + } + + memcpy(v2DmaTxBuffer, frame, frameBytes); + dma_channel_config txConfig = dma_channel_get_default_config(v2TxDmaChannel); + channel_config_set_transfer_data_size(&txConfig, DMA_SIZE_8); + channel_config_set_dreq(&txConfig, uart_get_dreq(uart1, true)); + channel_config_set_read_increment(&txConfig, true); + channel_config_set_write_increment(&txConfig, false); + + digitalWrite(rs485Pin, HIGH); // Write. + delayMicroseconds(RS485_MODE_SWITCH_DELAY); + dma_channel_configure(v2TxDmaChannel, &txConfig, &uart1_hw->dr, + v2DmaTxBuffer, frameBytes, true); + dma_channel_wait_for_finish_blocking(v2TxDmaChannel); + uart_tx_wait_blocking(uart1); + digitalWrite(rs485Pin, LOW); // Read. + delayMicroseconds(RS485_MODE_SWITCH_DELAY); + v2TxFrames++; + return true; +} + +void EventDispatcher::serviceV2UartDmaRx() { + if (!v2UartDmaActive) { + return; + } + + if (v2RxState == V2_RX_IDLE) { + dma_channel_config rxConfig = dma_channel_get_default_config(v2RxDmaChannel); + channel_config_set_transfer_data_size(&rxConfig, DMA_SIZE_8); + channel_config_set_dreq(&rxConfig, uart_get_dreq(uart1, false)); + channel_config_set_read_increment(&rxConfig, false); + channel_config_set_write_increment(&rxConfig, true); + dma_channel_configure(v2RxDmaChannel, &rxConfig, v2DmaRxBuffer, + &uart1_hw->dr, ppuc::v2::kHeaderBytes, true); + v2RxState = V2_RX_HEADER; + v2RxStateStartUs = micros(); + v2RxDmaRestarts++; + return; + } + + if (v2RxState == V2_RX_HEADER && dma_channel_is_busy(v2RxDmaChannel)) { + if ((micros() - v2RxStateStartUs) > kV2RxTimeoutUs) { + dma_channel_abort(v2RxDmaChannel); + v2RxState = V2_RX_IDLE; + v2RxDmaTimeouts++; } + return; + } - if (sender != -1) { - if (crossLink != -1 /* && hwSerial->availableForWrite() >= 6 */) { - // = (byte) 255; - cmsg[2] = (byte) event->boardId; - cmsg[3] = event->topic; - cmsg[4] = event->index; - cmsg[5] = event->key; - cmsg[6] = event->value >> 24; - cmsg[7] = (event->value >> 16) & 0xff; - cmsg[8] = (event->value >> 8) & 0xff; - cmsg[9] = event->value & 0xff; - // = (byte) 255; - - for (int i = 0; i <= crossLink; i++) { - if (i != sender) { - hwSerial[i]->write(cmsg, 11); - } - } - } + if (v2RxState == V2_RX_HEADER) { + if (v2DmaRxBuffer[0] != ppuc::v2::kSyncByte) { + v2RxSyncFail++; + v2RxState = V2_RX_IDLE; + return; + } + ppuc::v2::FrameType frameType = ppuc::v2::ExtractType(v2DmaRxBuffer[1]); + v2RxPayloadBytes = getV2PayloadBytes(frameType); -#if defined(ARDUINO_ARCH_MBED_RP2040) || defined(ARDUINO_ARCH_RP2040) - if (multiCoreCrossLink) { - multiCoreCrossLink->pushConfigEvent(event); - } -#endif + dma_channel_config rxConfig = dma_channel_get_default_config(v2RxDmaChannel); + channel_config_set_transfer_data_size(&rxConfig, DMA_SIZE_8); + channel_config_set_dreq(&rxConfig, uart_get_dreq(uart1, false)); + channel_config_set_read_increment(&rxConfig, false); + channel_config_set_write_increment(&rxConfig, true); + dma_channel_configure(v2RxDmaChannel, &rxConfig, + &v2DmaRxBuffer[ppuc::v2::kHeaderBytes], + &uart1_hw->dr, + v2RxPayloadBytes + ppuc::v2::kCrcBytes, true); + v2RxState = V2_RX_BODY; + v2RxStateStartUs = micros(); + return; + } + + if (v2RxState == V2_RX_BODY && dma_channel_is_busy(v2RxDmaChannel)) { + if ((micros() - v2RxStateStartUs) > kV2RxTimeoutUs) { + dma_channel_abort(v2RxDmaChannel); + v2RxState = V2_RX_IDLE; + v2RxDmaTimeouts++; + } + return; + } + + if (v2RxState == V2_RX_BODY) { + processV2Frame(v2DmaRxBuffer, v2RxPayloadBytes); + v2RxState = V2_RX_IDLE; + } +} + +int16_t EventDispatcher::findMappedIndex(const uint16_t* table, uint16_t count, + uint16_t number) { + for (uint16_t i = 0; i < count; ++i) { + if (table[i] == number) { + return (int16_t)i; + } + } + return -1; +} + +void EventDispatcher::updateSwitchBitmap(Event *event) { + // V2 switch reporting is bitmap-based. Legacy switch events still originate + // from the existing switch devices/listeners; this method mirrors those + // events into the dense V2 switch-state RAM bitmap. On token/poll, the board + // sends this bitmap back to the CPU in one V2 switch frame. + int16_t mappedIndex = + findMappedIndex(switchIndexToNumber, runtimeConfig.switchBits, + event->eventId); + if (mappedIndex < 0) { + return; + } + + ppuc::v2::SetBitmapBit(switchStates, (uint16_t)mappedIndex, + event->value != 0); + if (!applyingRemoteSwitchState) { + switchDirty = true; + } +} + +void EventDispatcher::applyOutputStates(const byte *coils, size_t coilBytes, + const byte *lamps, size_t lampBytes) { + // V2 output frames carry full RAM snapshots. To preserve existing + // EventListener behavior, we synthesize legacy events only for changed bits + // (edge detection old snapshot -> new snapshot). This keeps the rest of the + // firmware event-driven without requiring listener rewrites. + for (uint16_t n = 0; n < runtimeConfig.coilBits; ++n) { + bool oldState = ppuc::v2::GetBitmapBit(outputCoils, n); + bool newState = ppuc::v2::GetBitmapBit(coils, n); + if (oldState != newState) { + callListeners( + new Event(EVENT_SOURCE_SOLENOID, coilIndexToNumber[n], + newState ? 1 : 0), + true, false); + } + } + memcpy(outputCoils, coils, coilBytes); + + for (uint16_t n = 0; n < runtimeConfig.lampBits; ++n) { + bool oldState = ppuc::v2::GetBitmapBit(outputLamps, n); + bool newState = ppuc::v2::GetBitmapBit(lamps, n); + if (oldState != newState) { + callListeners( + new Event(EVENT_SOURCE_LIGHT, lampIndexToNumber[n], newState ? 1 : 0), + true, false); + } + } + memcpy(outputLamps, lamps, lampBytes); +} + +void EventDispatcher::applySwitchStates(const byte* switches, + size_t switchBytes) { + // Global switch state is board-to-board on the RS485 bus. CPU/libppuc never + // broadcasts switch states. Every board consumes incoming switch frames and + // emits local switch events for fast-flip/effect listeners. + applyingRemoteSwitchState = true; + for (uint16_t n = 0; n < runtimeConfig.switchBits; ++n) { + bool oldState = ppuc::v2::GetBitmapBit(switchStates, n); + bool newState = ppuc::v2::GetBitmapBit(switches, n); + if (oldState != newState) { + dispatch(new Event(EVENT_SOURCE_SWITCH, switchIndexToNumber[n], + newState ? 1 : 0, true)); } + } + applyingRemoteSwitchState = false; + memcpy(switchStates, switches, switchBytes); +} + +void EventDispatcher::sendSwitchStateFrame(byte nextBoard) { + // Switch updates are transmitted as a compact V2 frame containing the full + // dense switch bitmap. The CPU selects the responding board via token + // (header.nextBoard in output frame). This board answers once and then + // returns RS485 direction to RX mode. + const size_t switchBytes = ppuc::v2::BitsToBytes(runtimeConfig.switchBits); + const size_t frameBytes = + ppuc::v2::kHeaderBytes + switchBytes + ppuc::v2::kCrcBytes; + + byte* frame = v2DmaTxBuffer; + frame[0] = ppuc::v2::kSyncByte; + frame[1] = ppuc::v2::ComposeTypeAndFlags(ppuc::v2::kFrameSwitchState, + ppuc::v2::kFlagKeyframe); + frame[2] = nextBoard; + frame[3] = txSequence++; + memcpy(&frame[4], switchStates, switchBytes); - // delete the event and free the memory - delete event; + uint16_t crc = + ppuc::v2::Crc16Ccitt(frame, ppuc::v2::kHeaderBytes + switchBytes); + frame[4 + switchBytes] = highByte(crc); + frame[5 + switchBytes] = lowByte(crc); + + if (!v2UartDmaActive || !sendV2FrameUartDma(frame, frameBytes)) { + v2TxFallback++; + digitalWrite(rs485Pin, HIGH); // Write. + delayMicroseconds(RS485_MODE_SWITCH_DELAY); + hwSerial->write(frame, frameBytes); + hwSerial->flush(); + digitalWrite(rs485Pin, LOW); // Read. + delayMicroseconds(RS485_MODE_SWITCH_DELAY); + } + + lastPoll = millis(); +} + +void EventDispatcher::sendSwitchNoChangeFrame(byte nextBoard) { + byte* frame = v2DmaTxBuffer; + frame[0] = ppuc::v2::kSyncByte; + frame[1] = ppuc::v2::ComposeTypeAndFlags(ppuc::v2::kFrameSwitchNoChange, + ppuc::v2::kFlagNone); + frame[2] = nextBoard; + frame[3] = txSequence++; + uint16_t crc = ppuc::v2::Crc16Ccitt(frame, ppuc::v2::kHeaderBytes); + frame[4] = highByte(crc); + frame[5] = lowByte(crc); + + if (!v2UartDmaActive || !sendV2FrameUartDma(frame, ppuc::v2::kResetFrameBytes)) { + v2TxFallback++; + digitalWrite(rs485Pin, HIGH); // Write. + delayMicroseconds(RS485_MODE_SWITCH_DELAY); + hwSerial->write(frame, ppuc::v2::kResetFrameBytes); + hwSerial->flush(); + digitalWrite(rs485Pin, LOW); // Read. + delayMicroseconds(RS485_MODE_SWITCH_DELAY); + } + + v2SwitchNoChangeTx++; + lastPoll = millis(); +} + +bool EventDispatcher::handleV2Frame() { + if (hwSerial->available() < (int)ppuc::v2::kHeaderBytes) { + return false; + } + + if (hwSerial->peek() != ppuc::v2::kSyncByte) { + return false; + } + + if (!readBytes(v2Buffer, ppuc::v2::kHeaderBytes)) { + return false; + } + + ppuc::v2::FrameType frameType = ppuc::v2::ExtractType(v2Buffer[1]); + size_t payloadBytes = getV2PayloadBytes(frameType); + if (frameType != ppuc::v2::kFrameHeartbeat && + frameType != ppuc::v2::kFrameError && + frameType != ppuc::v2::kFrameReset && payloadBytes == 0 && + frameType != ppuc::v2::kFrameOutputState && + frameType != ppuc::v2::kFrameSwitchNoChange) { + return false; + } + + if (!readBytes(&v2Buffer[ppuc::v2::kHeaderBytes], + payloadBytes + ppuc::v2::kCrcBytes)) { + return false; + } + + return processV2Frame(v2Buffer, payloadBytes); } void EventDispatcher::update() { - if (!rs485) { - while (stackCounter >= 0) { - Event *event = stackEvents[stackCounter--]; - // Integer MAX_CROSS_LINKS is always higher than crossLinks, so this parameters means "no sender, send to all". - callListeners(event, MAX_CROSS_LINKS, false); - } + if (!rs485) { // We're on Core1, the EffectController. Transmit stacked + // events to Core0. + while (!eventQueue.empty()) { + Event *e = eventQueue.front(); + eventQueue.pop(); + callListeners(e, true, false); + } + } else { + while (!eventQueue.empty()) { + Event *e = eventQueue.front(); + eventQueue.pop(); + callListeners(e, true, false); } - for (int i = 0; i <= crossLink; i++) { - if (hwSerial[i]->available() >= 6) { - byte startByte = hwSerial[i]->read(); - if (startByte == 255) { - byte sourceId = hwSerial[i]->read(); - if (sourceId != 0) { - if (sourceId == EVENT_CONFIGURATION) { - while (hwSerial[i]->available() < 9) {} - - // We have a ConfigEvent. - byte boardId = hwSerial[i]->read(); - byte topic = hwSerial[i]->read(); - byte index = hwSerial[i]->read(); - byte key = hwSerial[i]->read(); - int value = - (hwSerial[i]->read() << 24) + - (hwSerial[i]->read() << 16) + - (hwSerial[i]->read() << 8) + - hwSerial[i]->read(); - callListeners(new ConfigEvent(boardId, topic, index, key, value), i); - } - else { - if (sourceId == EVENT_POLL_EVENTS && rs485) { - digitalWrite(rs485Pin, HIGH); // Write. - while (stackCounter >= 0) { - Event *event = stackEvents[stackCounter--]; - // Integer MAX_CROSS_LINKS is always higher than crossLinks, so this parameters means "no sender, send to all". - callListeners(event, MAX_CROSS_LINKS, true); - } - digitalWrite(rs485Pin, LOW); // Read. - } - - word eventId = word(hwSerial[i]->read(), hwSerial[i]->read()); - if (eventId != 0) { - byte value = hwSerial[i]->read(); - byte stopByte = hwSerial[i]->read(); - if (stopByte == 255) { - callListeners(new Event((char) sourceId, eventId, value), i, false); - } - } - } - } - } + if (v2UartDmaActive) { + serviceV2UartDmaRx(); + } else { + // Fallback parser is still needed for V2 bootstrap and fault handling: + // - bootstrap: receive initial V2 setup frame before DMA cutover + // - fault path: continue operating if UART DMA transport cannot start + while (hwSerial->available() > 0) { + int firstByte = hwSerial->peek(); + if (firstByte == ppuc::v2::kSyncByte) { + if (!handleV2Frame()) { + break; + } + } else { + // Desync/noise, consume one byte and continue. + hwSerial->read(); } + } } + } -#if defined(ARDUINO_ARCH_MBED_RP2040) || defined(ARDUINO_ARCH_RP2040) - if (multiCoreCrossLink) { - if (multiCoreCrossLink->eventAvailable()) { - Event *event; - if (multiCoreCrossLink->popEventNonBlocking(event)) { - callListeners(event, -1, false); - } - } + if (multiCoreCrossLink) { + if (multiCoreCrossLink->eventAvailable()) { + Event *event = multiCoreCrossLink->popEvent(); + callListeners(event, false, false); + } - if (multiCoreCrossLink->configEventAvailable()) { - ConfigEvent *configEvent; - if (multiCoreCrossLink->popConfigEventNonBlocking(configEvent)) { - callListeners(configEvent, -1); - } - } + if (multiCoreCrossLink->configEventAvailable()) { + ConfigEvent *configEvent = multiCoreCrossLink->popConfigEvent(); + callListeners(configEvent, false); } -#endif + } + + if (debugEnabled && Serial && (millis() - debugLastPrintMs) >= 1000) { + debugLastPrintMs = millis(); + rp2040.idleOtherCore(); + Serial.print("V2DBG board="); + Serial.print(board); + Serial.print(" active="); + Serial.print(v2UartDmaActive ? 1 : 0); + Serial.print(" cutover_ok="); + Serial.print(v2CutoverOk); + Serial.print(" cutover_fail="); + Serial.print(v2CutoverFail); + Serial.print(" rx="); + Serial.print(v2RxFrames); + Serial.print(" rx_crc_fail="); + Serial.print(v2RxCrcFail); + Serial.print(" rx_sync_fail="); + Serial.print(v2RxSyncFail); + Serial.print(" rx_dma_restart="); + Serial.print(v2RxDmaRestarts); + Serial.print(" rx_dma_timeout="); + Serial.print(v2RxDmaTimeouts); + Serial.print(" tx="); + Serial.print(v2TxFrames); + Serial.print(" tx_nochange="); + Serial.print(v2SwitchNoChangeTx); + Serial.print(" tx_fallback="); + Serial.println(v2TxFallback); + rp2040.resumeOtherCore(); + } +} + +uint32_t EventDispatcher::getLastPoll() { + if (running) return lastPoll; + return millis(); } diff --git a/src/EventDispatcher/EventDispatcher.h b/src/EventDispatcher/EventDispatcher.h index 4b629f2..c3a0050 100644 --- a/src/EventDispatcher/EventDispatcher.h +++ b/src/EventDispatcher/EventDispatcher.h @@ -10,17 +10,16 @@ #include -#include "MultiCoreCrossLink.h" +#include +#include "hardware/dma.h" #include "Event.h" #include "EventListener.h" +#include "MultiCoreCrossLink.h" +#include "../PPUCProtocolV2.h" #ifndef MAX_EVENT_LISTENERS -#define MAX_EVENT_LISTENERS 5 -#endif - -#ifndef MAX_CROSS_LINKS -#define MAX_CROSS_LINKS 8 +#define MAX_EVENT_LISTENERS 32 #endif #ifndef EVENT_STACK_SIZE @@ -28,46 +27,106 @@ #endif class EventDispatcher { -public: - EventDispatcher(); - - void setRS485ModePin(int pin); - - void setMultiCoreCrossLink(MultiCoreCrossLink* mccl); - - void setCrossLinkSerial(HardwareSerial &reference); - - void addCrossLinkSerial(HardwareSerial &reference); - - void addListener(EventListener* eventListener, char sourceId); - - void addListener(EventListener* eventListener); - - void dispatch(Event* event); - - void update(); - -private: - void callListeners(Event* event, int sender, bool flush); - - void callListeners(ConfigEvent* event, int sender); - - Event* stackEvents[EVENT_STACK_SIZE]; - int stackCounter = -1; - - EventListener* eventListeners[MAX_EVENT_LISTENERS]; - char eventListenerFilters[MAX_EVENT_LISTENERS]; - int numListeners = -1; - - byte msg[6] = {0}; - byte cmsg[11] = {0}; - - bool rs485 = false; - int rs485Pin = 0; - bool multiCore = false; - int crossLink = -1; - HardwareSerial* hwSerial[MAX_CROSS_LINKS]; - MultiCoreCrossLink* multiCoreCrossLink; + public: + EventDispatcher(); + + void setRS485ModePin(int pin); + + void setBoard(byte b); + + void setMultiCoreCrossLink(MultiCoreCrossLink* mccl); + + MultiCoreCrossLink* getMultiCoreCrossLink(); + + void setCrossLinkSerial(HardwareSerial& reference); + void setDebug(bool enabled); + void setNextSwitchBoard(byte boardId); + + void addListener(EventListener* eventListener, char sourceId); + + void addListener(EventListener* eventListener); + + void dispatch(Event* event); + + void update(); + + uint32_t getLastPoll(); + + private: + bool readBytes(byte* buffer, size_t len); + bool handleV2Frame(); + bool startV2UartDmaTransport(); + void stopV2UartDmaTransport(); + void serviceV2UartDmaRx(); + bool sendV2FrameUartDma(const byte* frame, size_t frameBytes); + size_t getV2PayloadBytes(ppuc::v2::FrameType frameType); + bool processV2Frame(const byte* frame, size_t payloadBytes); + void sendSwitchStateFrame(byte nextBoard); + void sendSwitchNoChangeFrame(byte nextBoard); + void applyOutputStates(const byte* coils, size_t coilBytes, const byte* lamps, + size_t lampBytes); + void applySwitchStates(const byte* switches, size_t switchBytes); + void updateSwitchBitmap(Event* event); + int16_t findMappedIndex(const uint16_t* table, uint16_t count, + uint16_t number); + + void callListeners(Event* event, bool sendToOtherCore, bool sendToRS485); + + void callListeners(ConfigEvent* event, bool sendToOtherCore); + + std::queue eventQueue; + + EventListener* eventListeners[MAX_EVENT_LISTENERS]; + char eventListenerFilters[MAX_EVENT_LISTENERS]; + int numListeners = -1; + + byte msg[12]; + byte v2Buffer[ppuc::v2::kHeaderBytes + ppuc::v2::kMaxCoilBytes + + ppuc::v2::kMaxLampBytes + ppuc::v2::kCrcBytes]; + byte v2DmaRxBuffer[ppuc::v2::kHeaderBytes + ppuc::v2::kMaxCoilBytes + + ppuc::v2::kMaxLampBytes + ppuc::v2::kCrcBytes]; + byte v2DmaTxBuffer[ppuc::v2::kHeaderBytes + ppuc::v2::kMaxSwitchBytes + + ppuc::v2::kCrcBytes]; + byte outputCoils[ppuc::v2::kMaxCoilBytes] = {0}; + byte outputLamps[ppuc::v2::kMaxLampBytes] = {0}; + byte switchStates[ppuc::v2::kMaxSwitchBytes] = {0}; + uint16_t coilIndexToNumber[ppuc::v2::kMaxCoilBits]; + uint16_t lampIndexToNumber[ppuc::v2::kMaxLampBits]; + uint16_t switchIndexToNumber[ppuc::v2::kMaxSwitchBits]; + byte txSequence = 0; + ppuc::v2::RuntimeConfig runtimeConfig; + bool v2UartDmaActive = false; + enum V2RxState { V2_RX_IDLE = 0, V2_RX_HEADER, V2_RX_BODY }; + V2RxState v2RxState = V2_RX_IDLE; + size_t v2RxPayloadBytes = 0; + uint32_t v2RxStateStartUs = 0; + int v2RxDmaChannel = -1; + int v2TxDmaChannel = -1; + bool debugEnabled = false; + uint32_t debugLastPrintMs = 0; + uint32_t v2CutoverOk = 0; + uint32_t v2CutoverFail = 0; + uint32_t v2RxFrames = 0; + uint32_t v2RxCrcFail = 0; + uint32_t v2RxSyncFail = 0; + uint32_t v2RxDmaRestarts = 0; + uint32_t v2RxDmaTimeouts = 0; + uint32_t v2TxFrames = 0; + uint32_t v2SwitchNoChangeTx = 0; + uint32_t v2TxFallback = 0; + bool switchDirty = false; + bool applyingRemoteSwitchState = false; + + bool rs485 = false; + uint8_t rs485Pin = 0; + byte board = 255; + byte nextSwitchBoard = ppuc::v2::kNoBoard; + uint32_t lastPoll; + bool running = false; + + bool multiCore = false; + HardwareSerial* hwSerial; + MultiCoreCrossLink* multiCoreCrossLink; }; #endif diff --git a/src/EventDispatcher/EventListener.h b/src/EventDispatcher/EventListener.h index 948b55f..2b0c67f 100644 --- a/src/EventDispatcher/EventListener.h +++ b/src/EventDispatcher/EventListener.h @@ -11,9 +11,9 @@ #include "Event.h" class EventListener { -public: - virtual void handleEvent(Event* event) = 0; - virtual void handleEvent(ConfigEvent* event) = 0; + public: + virtual void handleEvent(Event* event) = 0; + virtual void handleEvent(ConfigEvent* event) = 0; }; #endif diff --git a/src/EventDispatcher/MultiCoreCrossLink.h b/src/EventDispatcher/MultiCoreCrossLink.h index fa2f1c7..259874b 100644 --- a/src/EventDispatcher/MultiCoreCrossLink.h +++ b/src/EventDispatcher/MultiCoreCrossLink.h @@ -1,81 +1,92 @@ #ifndef MULTI_CORE_CROSS_LINK_h #define MULTI_CORE_CROSS_LINK_h -#if defined(ARDUINO_ARCH_MBED_RP2040) || defined(ARDUINO_ARCH_RP2040) #include -#endif + +#include #include "Event.h" #ifndef EVENT_STACK_SIZE -#define EVENT_STACK_SIZE 100 +#define EVENT_STACK_SIZE 128 #endif +typedef struct QueuedEvent { + byte sourceId; + word eventId; + byte value; + bool localFast; +} EventItem; + +typedef struct QueuedConfigEvent { + byte boardId; + byte topic; + byte index; + byte key; + int value; +} ConfigEventItem; + class MultiCoreCrossLink { -#if defined(ARDUINO_ARCH_MBED_RP2040) || defined(ARDUINO_ARCH_RP2040) -public: - MultiCoreCrossLink() { - queue_init(&_eventQueue[0], sizeof(Event), EVENT_STACK_SIZE); - queue_init(&_eventQueue[1], sizeof(Event), EVENT_STACK_SIZE); - queue_init(&_configEventQueue, sizeof(ConfigEvent), EVENT_STACK_SIZE); + public: + MultiCoreCrossLink() { + if (get_core_num() == 0) { + queue_init(&_eventQueue[0], sizeof(EventItem), EVENT_STACK_SIZE); + queue_init(&_eventQueue[1], sizeof(EventItem), EVENT_STACK_SIZE); + queue_init(&_configEventQueue, sizeof(ConfigEventItem), EVENT_STACK_SIZE); } + } - ~MultiCoreCrossLink() { /* noop */ }; + ~MultiCoreCrossLink(){}; - void pushEvent(Event* event) { - while (!pushEventNonBlocking(event)) { /* noop */ } - } + void pushEvent(Event *event) { + QueuedEvent queuedEvent; + queuedEvent.sourceId = event->sourceId; + queuedEvent.eventId = event->eventId; + queuedEvent.value = event->value; + queuedEvent.localFast = event->localFast; - bool pushEventNonBlocking(Event* event) { - return queue_try_add(&_eventQueue[get_core_num() ^ 1], new Event(event)); - } - - Event* popEvent() { - Event* event; - while (!popEventNonBlocking(event)) { /* noop */ } - return event; - } + queue_add_blocking(&_eventQueue[get_core_num() ^ 1], &queuedEvent); + } - bool popEventNonBlocking(Event* event) { - return queue_try_remove(&_eventQueue[get_core_num()], event); - } + Event *popEvent() { + QueuedEvent queuedEvent; + queue_remove_blocking(&_eventQueue[get_core_num()], &queuedEvent); - int eventAvailable() { - return queue_get_level(&_eventQueue[get_core_num()]); - } + return new Event(queuedEvent.sourceId, queuedEvent.eventId, + queuedEvent.value, queuedEvent.localFast); + } - void pushConfigEvent(ConfigEvent* event) { - if (get_core_num() == 0) { - while (!pushConfigEventNonBlocking(event)) { /* noop */ } - } - } + int eventAvailable() { return queue_get_level(&_eventQueue[get_core_num()]); } - bool pushConfigEventNonBlocking(ConfigEvent* event) { - if (get_core_num() == 0) { - return queue_try_add(&_configEventQueue, new ConfigEvent(event)); - } + void pushConfigEvent(ConfigEvent *event) { + if (get_core_num() == 0) { + QueuedConfigEvent queuedConfigEvent; + queuedConfigEvent.boardId = event->boardId; + queuedConfigEvent.topic = event->topic; + queuedConfigEvent.index = event->index; + queuedConfigEvent.key = event->key; + queuedConfigEvent.value = event->value; - return false; + queue_add_blocking(&_configEventQueue, &queuedConfigEvent); } + } - ConfigEvent* popConfigEvent() { - ConfigEvent* event; - while (!popConfigEventNonBlocking(event)) { /* noop */ } - return event; - } + ConfigEvent *popConfigEvent() { + QueuedConfigEvent queuedConfigEvent; + queue_remove_blocking(&_configEventQueue, &queuedConfigEvent); - bool popConfigEventNonBlocking(ConfigEvent* event) { - return get_core_num() == 1 && queue_try_remove(&_configEventQueue, event); - } + return new ConfigEvent(queuedConfigEvent.boardId, queuedConfigEvent.topic, + queuedConfigEvent.index, queuedConfigEvent.key, + queuedConfigEvent.value); + } - int configEventAvailable() { - return get_core_num() == 1 && queue_get_level(&_configEventQueue); - } + int configEventAvailable() { + return get_core_num() == 1 && queue_get_level(&_configEventQueue); + } -private: - queue_t _eventQueue[2]; - queue_t _configEventQueue; -#endif + private: + queue_t _eventQueue[2]; + queue_t _configEventQueue; }; #endif \ No newline at end of file diff --git a/src/HighPowerOffAware.h b/src/HighPowerOffAware.h new file mode 100644 index 0000000..96f681a --- /dev/null +++ b/src/HighPowerOffAware.h @@ -0,0 +1,76 @@ +#ifndef HIGHPOWEROFFAWARE_h +#define HIGHPOWEROFFAWARE_h + +#include + +#include "EventDispatcher/Event.h" +#include "EventDispatcher/EventDispatcher.h" + +class HighPowerOffAware : public EventListener { + public: + HighPowerOffAware() {} + + HighPowerOffAware(EventDispatcher *eventDispatcher) { + eventDispatcher->addListener(this, EVENT_CONFIGURATION); + eventDispatcher->addListener(this, EVENT_RUN); + eventDispatcher->addListener(this, EVENT_SOURCE_SOLENOID); + eventDispatcher->addListener(this, EVENT_SOURCE_SWITCH); + } + + void handleEvent(Event *event) { + powerToggled = false; + + if (coinDoorSwitch > 0 && event->sourceId == EVENT_SOURCE_SWITCH && + (byte)event->eventId == coinDoorSwitch) { + coinDoorClosed = (bool)event->value; + powerToggled = true; + } + + if (gameOnSolenoid > 0 && event->sourceId == EVENT_SOURCE_SOLENOID && + (byte)event->eventId == gameOnSolenoid) { + powerOn = (bool)event->value; + powerToggled = true; + } + + if (event->sourceId == EVENT_RUN) { + // Fake coin door switch and gome on solenoid if not present in the + // current game. + if ((bool)event->value) { + if (0 == coinDoorSwitch) coinDoorClosed = true; + if (0 == gameOnSolenoid) powerOn = true; + } else { + powerOn = false; + powerToggled = true; + } + } + } + + void handleEvent(ConfigEvent *event) { + switch (event->topic) { + case CONFIG_TOPIC_COIN_DOOR_CLOSED_SWITCH: + switch (event->key) { + case CONFIG_TOPIC_NUMBER: + coinDoorClosed = event->value; + break; + } + break; + + case CONFIG_TOPIC_GAME_ON_SOLENOID: + switch (event->key) { + case CONFIG_TOPIC_NUMBER: + gameOnSolenoid = event->value; + break; + } + break; + } + } + + protected: + byte coinDoorSwitch = 0; + byte gameOnSolenoid = 0; + bool coinDoorClosed = false; + bool powerOn = false; + bool powerToggled = false; +}; + +#endif diff --git a/src/IOBoardController.cpp b/src/IOBoardController.cpp index 37e61c6..7eeb9de 100644 --- a/src/IOBoardController.cpp +++ b/src/IOBoardController.cpp @@ -1,111 +1,212 @@ #include "IOBoardController.h" -IOBoardController::IOBoardController(int controllerType) { - _eventDispatcher = new EventDispatcher(); - _eventDispatcher->addListener(this, EVENT_CONFIGURATION); - - if (controllerType == CONTROLLER_16_8_1) { - // Read bordID. - // @todo draft! - boardId = (analogRead(28) - 100) / 16 ; - - #if defined(ARDUINO_ARCH_MBED_RP2040) || defined(ARDUINO_ARCH_RP2040) - Serial1.setTX(16); - Serial1.setRX(17); - _eventDispatcher->setRS485ModePin(18); - Serial1.setFIFOSize(128); // @todo find the right size. - _eventDispatcher->addCrossLinkSerial(Serial1); - #endif - - _pwmDevices = new PwmDevices(_eventDispatcher); - _switches = new Switches(boardId, _eventDispatcher); - } else { - Serial.print("Unsupported Input Controller: "); - Serial.println(controllerType); +#include "EventDispatcher/CrossLinkDebugger.h" + +#define SWITCH_DEBOUNCE 10 + +IOBoardController::IOBoardController(int cT) { + _eventDispatcher = new EventDispatcher(); + _eventDispatcher->addListener(this, EVENT_CONFIGURATION); + _eventDispatcher->addListener(this, EVENT_PING); + _eventDispatcher->addListener(this, EVENT_RUN); + _eventDispatcher->addListener(this, EVENT_RESET); + + controllerType = cT; + + if (controllerType == CONTROLLER_16_8_1) { + // Read bordID. Ideal value at 10bit resolution: (DIP+1)*1023*2/35 -> 58.46 + // to 935.3 + boardId = 16 - ((int)((analogRead(28) + 29.23) / 58.46)); + m_debug = (boardId & 0b1000) != 0; + if (m_debug) { + boardId -= 8; } + + _eventDispatcher->setBoard(boardId); + _eventDispatcher->setDebug(m_debug); + _eventDispatcher->setRS485ModePin(RS485_MODE_PIN); + _eventDispatcher->setCrossLinkSerial(Serial1); + _multiCoreCrossLink = new MultiCoreCrossLink(); + _eventDispatcher->setMultiCoreCrossLink(_multiCoreCrossLink); + _pwmDevices = new PwmDevices(_eventDispatcher); + _switches = new Switches(boardId, _eventDispatcher); + _switchMatrix = new SwitchMatrix(boardId, _eventDispatcher); + // Adjust PWM properties if needed. + analogWriteFreq(500); + analogWriteResolution(8); + } } void IOBoardController::update() { - switches()->update(); - pwmDevices()->update(); - eventDispatcher()->update(); + if (running) { + if (activeSwitches) { + // nop + } + if (activeSwitchMatrix) { + //switchMatrix()->update(); + } + if (activePwmDevices) { + pwmDevices()->update(); + } + } else { + if (activePwmDevices) { + pwmDevices()->off(); + } + } + + if (resetTimer > 0 && resetTimer < millis()) { + if (!m_debug) { + rp2040.reboot(); + } else { + resetTimer = 0; + CrossLinkDebugger::debug( + "Skipped reset to keep USB debugging connection alive."); + } + } + + eventDispatcher()->update(); } -void IOBoardController::handleEvent(ConfigEvent* event) { - if (event->boardId == boardId) { - switch (event->topic) { - case CONFIG_TOPIC_SWITCHES: - switch (event->key) { - case CONFIG_TOPIC_PORT: - port = event->value; - break; - case CONFIG_TOPIC_NUMBER: - _switches->registerSwitch((byte) port, event->value); - break; - } - break; +void IOBoardController::handleEvent(Event *event) { + switch (event->sourceId) { + case EVENT_PING: + // In case that serial debugging is active, send 99 as PING response, + // otherwise 1. + _eventDispatcher->dispatch( + new Event(EVENT_PONG, m_debug ? 99 : 1, boardId)); + break; + + case EVENT_RUN: + running = (bool)event->value; + break; + + case EVENT_RESET: + // Clear all configurations or reboot the device. + _pwmDevices->reset(); - case CONFIG_TOPIC_PWM: - switch (event->key) { - case CONFIG_TOPIC_PORT: - port = event->value; - number = 0; - power = 0; - minPulseTime = 0; - maxPulseTime = 0; - holdPower = 0; - holdPowerActivationTime = 0; - fastSwitch = 0; - break; - case CONFIG_TOPIC_NUMBER: - number = event->value; - break; - case CONFIG_TOPIC_POWER: - power = event->value; - break; - case CONFIG_TOPIC_MIN_PULSE_TIME: - minPulseTime = event->value; - break; - case CONFIG_TOPIC_MAX_PULSE_TIME: - maxPulseTime = event->value; - break; - case CONFIG_TOPIC_HOLD_POWER: - holdPower = event->value; - break; - case CONFIG_TOPIC_HOLD_POWER_ACTIVATION_TIME: - holdPowerActivationTime = event->value; - break; - case CONFIG_TOPIC_FAST_SWITCH: - fastSwitch = event->value; - break; - case CONFIG_TOPIC_TYPE: - switch (event->value) { - case 1: // Coil - _pwmDevices->registerSolenoid((byte) port, number, power, minPulseTime, maxPulseTime, holdPower, holdPowerActivationTime, fastSwitch); - break; - case 2: // Flasher - _pwmDevices->registerFlasher((byte) port, number, power); - break; - case 3: // Lamp - _pwmDevices->registerLamp((byte) port, number, power); - break; - } - break; - } + // Issue a delayed reset of the board. + // Core 1 should have enough time to turn off it's devices. + resetTimer = millis() + WAIT_FOR_EFFECT_CONTROLLER_RESET; + + break; + } +} + +void IOBoardController::handleEvent(ConfigEvent *event) { + if (event->boardId == boardId) { + switch (event->topic) { + case CONFIG_TOPIC_SWITCH_MATRIX: + switch (event->key) { + case CONFIG_TOPIC_ACTIVE_LOW: + if (event->value) { + _switchMatrix->setActiveLow(); + } + break; + case CONFIG_TOPIC_NUM_ROWS: + rows = (uint8_t)event->value; + _switchMatrix->setNumRows(rows); + _switches->setNumSwitches(MAX_SWITCHES - NUM_COLUMNS - rows); + break; + case CONFIG_TOPIC_PORT: + port = event->value; + break; + case CONFIG_TOPIC_NUMBER: + _switchMatrix->registerSwitch((byte)port, event->value); + activeSwitchMatrix = true; + break; + } + break; + + case CONFIG_TOPIC_SWITCH_CHAIN: + if (event->key == CONFIG_TOPIC_NEXT_BOARD) { + _eventDispatcher->setNextSwitchBoard((byte)event->value); + } + break; + + case CONFIG_TOPIC_SWITCHES: + switch (event->key) { + case CONFIG_TOPIC_PORT: + port = event->value; + break; + case CONFIG_TOPIC_NUMBER: + number = event->value; + break; + case CONFIG_TOPIC_DEBOUNCE_TIME: + _switches->registerSwitch((byte)port, number, event->value); + activeSwitches = true; + break; + } + break; + + case CONFIG_TOPIC_PWM: + switch (event->key) { + case CONFIG_TOPIC_PORT: + port = event->value; + number = 0; + power = 0; + minPulseTime = 0; + maxPulseTime = 0; + holdPower = 0; + holdPowerActivationTime = 0; + fastSwitch = 0; + break; + case CONFIG_TOPIC_NUMBER: + number = event->value; + break; + case CONFIG_TOPIC_POWER: + power = event->value; + break; + case CONFIG_TOPIC_MIN_PULSE_TIME: + minPulseTime = event->value; + break; + case CONFIG_TOPIC_MAX_PULSE_TIME: + maxPulseTime = event->value; + break; + case CONFIG_TOPIC_HOLD_POWER: + holdPower = event->value; + break; + case CONFIG_TOPIC_HOLD_POWER_ACTIVATION_TIME: + holdPowerActivationTime = event->value; + break; + case CONFIG_TOPIC_FAST_SWITCH: + fastSwitch = event->value; + break; + case CONFIG_TOPIC_TYPE: + switch (event->value) { + case PWM_TYPE_SOLENOID: // Coil + _pwmDevices->registerSolenoid( + (byte)port, number, power, minPulseTime, maxPulseTime, + holdPower, holdPowerActivationTime, fastSwitch); + activePwmDevices = true; break; + case PWM_TYPE_FLASHER: // Flasher + _pwmDevices->registerFlasher((byte)port, number, power); + activePwmDevices = true; + break; + case PWM_TYPE_LAMP: // Lamp + _pwmDevices->registerLamp((byte)port, number, power); + activePwmDevices = true; + break; + case PWM_TYPE_MOTOR: // Motor + // @todo + break; + case PWM_TYPE_SHAKER: // Shaker + // Shaker is handled by the EffectController. + break; + } + break; } + break; } + } } -PwmDevices* IOBoardController::pwmDevices() { - return _pwmDevices; -} +PwmDevices *IOBoardController::pwmDevices() { return _pwmDevices; } -Switches* IOBoardController::switches() { - return _switches; -} +Switches *IOBoardController::switches() { return _switches; } -EventDispatcher* IOBoardController::eventDispatcher() { - return _eventDispatcher; -} +SwitchMatrix *IOBoardController::switchMatrix() { return _switchMatrix; } +EventDispatcher *IOBoardController::eventDispatcher() { + return _eventDispatcher; +} diff --git a/src/IOBoardController.h b/src/IOBoardController.h index 85bb745..9e7240b 100644 --- a/src/IOBoardController.h +++ b/src/IOBoardController.h @@ -1,57 +1,66 @@ /* IOBoardController.h Created by Markus Kalkbrenner. - - GPIO0-7: Input (Switches) or low power output - GPIO8-15: Input (Switches) - GPIO16,17,18: UART TX, UART RX, RS485 Direction - GPIO19-24, 26, 27: Power Out (PWM) - GPIO25: Status-LED - GPIO28: ADC für Adressierung - GPIO29: Reserve (z.B. für einen LED-Strip oder zweite Status-LED) */ #ifndef IOBOARDCONTROLLER_h #define IOBOARDCONTROLLER_h -#include "PPUC.h" - #include "EventDispatcher/Event.h" #include "EventDispatcher/EventDispatcher.h" +#include "EventDispatcher/MultiCoreCrossLink.h" #include "IODevices/PwmDevices.h" +#include "IODevices/SwitchMatrix.h" #include "IODevices/Switches.h" +#include "PPUC.h" class IOBoardController : public EventListener { -public: - IOBoardController(int controllerType); + public: + IOBoardController(int controllerType); + + PwmDevices *pwmDevices(); + + Switches *switches(); + + SwitchMatrix *switchMatrix(); - PwmDevices* pwmDevices(); + EventDispatcher *eventDispatcher(); - Switches* switches(); + void handleEvent(Event *event); - EventDispatcher* eventDispatcher(); + void handleEvent(ConfigEvent *event); - void handleEvent(Event* event) {} + void update(); - void handleEvent(ConfigEvent* event); + bool isDebug() { return m_debug; } - void update(); + private: + PwmDevices *_pwmDevices; + Switches *_switches; + SwitchMatrix *_switchMatrix; -private: - PwmDevices* _pwmDevices; - Switches* _switches; + bool running = false; + bool activePwmDevices = false; + bool activeSwitches = false; + bool activeSwitchMatrix = false; + bool m_debug = false; - byte boardId; - byte port = 0; - byte number = 0; - byte power = 0; - byte minPulseTime = 0; - byte maxPulseTime = 0; - byte holdPower = 0; - byte holdPowerActivationTime = 0; - byte fastSwitch = 0; + int controllerType; + byte boardId; + byte port = 0; + byte number = 0; + byte power = 0; + byte rows = 0; + uint16_t minPulseTime = 0; + uint16_t maxPulseTime = 0; + byte holdPower = 0; + uint16_t holdPowerActivationTime = 0; + byte fastSwitch = 0; + byte type = 0; + uint32_t resetTimer = 0; - EventDispatcher* _eventDispatcher; + EventDispatcher *_eventDispatcher; + MultiCoreCrossLink *_multiCoreCrossLink; }; #endif diff --git a/src/IODevices/PwmDevices.cpp b/src/IODevices/PwmDevices.cpp index f47f6a1..f13f4b1 100644 --- a/src/IODevices/PwmDevices.cpp +++ b/src/IODevices/PwmDevices.cpp @@ -1,117 +1,199 @@ #include "PwmDevices.h" -void PwmDevices::registerSolenoid(byte p, byte n, byte pow, byte minPT, byte maxPT, byte hP, byte hPAT, byte fS) { - if (last < MAX_PWM_OUTPUTS) { - port[last] = p; - number[last] = n; - power[last] = pow; - minPulseTime[last] = minPT; - maxPulseTime[last] = maxPT; - holdPower[last] = hP; - holdPowerActivationTime[last] = hPAT; - fastSwitch[last] = fS; - - pinMode(p, OUTPUT); - analogWrite(p, 0); - type[last++] = 1; - } +#include "EventDispatcher/CrossLinkDebugger.h" + +void PwmDevices::registerSolenoid(byte p, byte n, byte pow, uint16_t minPT, + uint16_t maxPT, byte hP, uint16_t hPAT, + byte fS) { + if (last < MAX_PWM_OUTPUTS) { + port[last] = p; + number[last] = n; + power[last] = pow; + minPulseTime[last] = minPT; + maxPulseTime[last] = maxPT; + holdPower[last] = hP; + holdPowerActivationTime[last] = hPAT; + fastSwitch[last] = fS; + + pinMode(p, OUTPUT); + analogWrite(p, 0); + type[last++] = PWM_TYPE_SOLENOID; + } } void PwmDevices::registerFlasher(byte p, byte n, byte pow) { - if (last < MAX_PWM_OUTPUTS) { - port[last] = p; - number[last] = n; - power[last] = pow; - - pinMode(p, OUTPUT); - analogWrite(p, 0); - type[last++] = 2; - } + if (last < MAX_PWM_OUTPUTS) { + port[last] = p; + number[last] = n; + power[last] = pow; + + pinMode(p, OUTPUT); + analogWrite(p, 0); + type[last++] = PWM_TYPE_FLASHER; + } } void PwmDevices::registerLamp(byte p, byte n, byte pow) { - if (last < MAX_PWM_OUTPUTS) { - port[last] = p; - number[last] = n; - power[last] = pow; - - pinMode(p, OUTPUT); - analogWrite(p, 0); - type[last++] = 3; - } + if (last < MAX_PWM_OUTPUTS) { + port[last] = p; + number[last] = n; + power[last] = pow; + + pinMode(p, OUTPUT); + analogWrite(p, 0); + type[last++] = PWM_TYPE_LAMP; + } +} + +void PwmDevices::off() { + for (uint8_t i = 0; i < last; i++) { + // Turn off PWM output. + analogWrite(port[i], 0); + activated[i] = 0; + currentPower[i] = 0; + scheduled[i] = 0; + } +} + +void PwmDevices::reset() { + off(); + + for (uint8_t i = 0; i < MAX_PWM_OUTPUTS; i++) { + port[i] = 0; + number[i] = 0; + power[i] = 0; + minPulseTime[i] = 0; + maxPulseTime[i] = 0; + holdPower[i] = 0; + holdPowerActivationTime[i] = 0; + fastSwitch[i] = 0; + type[i] = 0; + activated[i] = 0; + currentPower[i] = 0; + scheduled[i] = 0; + } + + last = 0; } void PwmDevices::update() { - _ms = millis(); + _ms = millis(); - for (byte i = 0; i < last; i++) { - if (activated[i] > 0) { - if ( - (scheduled[i] && ((_ms - activated[i]) > minPulseTime[i])) || - ((maxPulseTime[i] > 0) && ((_ms - activated[i]) > maxPulseTime[i])) - ) { - analogWrite(port[i], 0); - activated[i] = 0; - scheduled[i] = false; - } - else if ((holdPowerActivationTime[i] > 0) && ((_ms - activated[i]) > holdPowerActivationTime[i])) { - analogWrite(port[i], holdPower[i]); - } - } + // Iterate over all outputs. + for (byte i = 0; i < last; i++) { + if (activated[i] > 0) { + // The output is active. + uint32_t timePassed = _ms - activated[i]; + if ((scheduled[i] && (minPulseTime[i] > 0) && + (timePassed > minPulseTime[i])) || + ((maxPulseTime[i] > 0) && (timePassed > maxPulseTime[i]))) { + // Deactivate the output if it is scheduled for delayed deactivation and + // the minimum pulse time is reached. Deactivate the output if the + // maximum pulse time is reached. + analogWrite(port[i], 0); + activated[i] = 0; + holdPower[i] = 0; + scheduled[i] = false; + CrossLinkDebugger::debug( + "Performed scheduled deactivation of PWM device on port %d after " + "%dms", + port[i], timePassed); + } else if ((holdPowerActivationTime[i] > 0) && + (currentPower[i] > holdPower[i]) && + (timePassed > holdPowerActivationTime[i])) { + // Reduce the power of the activated output if the hold power activation + // time pased since the activation. + analogWrite(port[i], holdPower[i]); + currentPower[i] = holdPower[i]; + CrossLinkDebugger::debug( + "Reduced power of PWM device on port %d to power %d after %dms", + port[i], holdPower[i], timePassed); + } } + } } -void PwmDevices::handleEvent(Event* event) { - _ms = millis(); - - switch (event->eventId) { - case EVENT_SOURCE_SOLENOID: - for (byte i = 0; i < last; i++) { - if ((type[i] == 1 || type[i] == 2) && number[i] == (byte) event->eventId) { - if (event->value && !activated[i]) { - analogWrite(port[i], power[i]); - activated[i] = _ms; - } - else if (!event->value && activated[i]) { - if (minPulseTime[i] > 0 && (_ms - activated[i]) < minPulseTime[i]) { - scheduled[i] = true; - } - else { - analogWrite(port[i], 0); - activated[i] = 0; - } - } - } - } - break; - - case EVENT_SOURCE_SWITCH: - for (byte i = 0; i < last; i++) { - if (type[i] == 1 && fastSwitch[i] == (byte) event->eventId) { - if (event->value) { - analogWrite(port[i], power[i]); - activated[i] = _ms; - } - else if (activated[i]) { - analogWrite(port[i], 0); - activated[i] = 0; - scheduled[i] = false; - } - } - } - break; - - case EVENT_SOURCE_LIGHT: - for (byte i = 0; i < last; i++) { - if (type[i] == 3 && number[i] == (byte) event->eventId) { - if (event->value) { - analogWrite(port[i], power[i]); - } - else if (activated[i]) { - analogWrite(port[i], 0); - } - } +void PwmDevices::updateSolenoidOrFlasher(bool targetState, byte i) { + _ms = millis(); + + if (targetState && activated[i] == 0) { + // Event received to activate the output and output isn't activated already. + // Activate it! + analogWrite(port[i], power[i]); + // Rememebr when it got activated. + activated[i] = _ms; + currentPower[i] = power[i]; + CrossLinkDebugger::debug("Activated PWM device on port %d with power %d", + port[i], power[i]); + } else if (!targetState && activated[i] > 0) { + // Event received to deactivate the output. + // Check if a minimum pulse time is configured for this output. + if ((_ms >= activated[i]) && (minPulseTime[i] > 0) && + (_ms - activated[i]) < minPulseTime[i]) { + // A minimum pulse time is configured for this output. + // Don't deactivate it immediately but schedule its later deactivation. + scheduled[i] = true; + CrossLinkDebugger::debug("Scheduled PWM device state change port %d", + port[i]); + } else { + // Deactivate the output. + analogWrite(port[i], 0); + // Mark the output as deactivated. + activated[i] = 0; + currentPower[i] = 0; + CrossLinkDebugger::debug("Deactivated PWM device on port %d", port[i]); + } + } +} + +void PwmDevices::handleEvent(Event *event) { + HighPowerOffAware::handleEvent(event); + + _ms = millis(); + + if (powerOn && coinDoorClosed) { + switch (event->sourceId) { + case EVENT_SOURCE_SOLENOID: + for (byte i = 0; i < last; i++) { + if ((type[i] == PWM_TYPE_SOLENOID || type[i] == PWM_TYPE_FLASHER) && + number[i] == (byte)event->eventId) { + updateSolenoidOrFlasher((bool)event->value, i); + } + } + break; + + case EVENT_SOURCE_SWITCH: + // A switch event was triggered or received. Activate or deactivate any + // output that is configured as "fastSwitch" for that switch. + for (byte i = 0; i < last; i++) { + if (type[i] == PWM_TYPE_SOLENOID && + fastSwitch[i] == (byte)event->eventId) { + updateSolenoidOrFlasher((bool)event->value, i); + } + } + break; + + case EVENT_SOURCE_LIGHT: + for (byte i = 0; i < last; i++) { + if (type[i] == PWM_TYPE_LAMP && number[i] == (byte)event->eventId) { + if (event->value) { + analogWrite(port[i], power[i]); + } else if (activated[i]) { + analogWrite(port[i], 0); } - break; + } + } + break; + } + } else if (powerToggled) { + for (byte i = 0; i < last; i++) { + // Deactivate the output. + analogWrite(port[i], 0); + // Mark the output as deactivated. + activated[i] = 0; + currentPower[i] = 0; + powerOn = false; + CrossLinkDebugger::debug("Deactivated PWM device on port %d", port[i]); } + } } \ No newline at end of file diff --git a/src/IODevices/PwmDevices.h b/src/IODevices/PwmDevices.h index 345bc72..bf8c437 100644 --- a/src/IODevices/PwmDevices.h +++ b/src/IODevices/PwmDevices.h @@ -9,50 +9,50 @@ #include -#include "../EventDispatcher/Event.h" -#include "../EventDispatcher/EventDispatcher.h" +#include "../HighPowerOffAware.h" #ifndef MAX_PWM_OUTPUTS #define MAX_PWM_OUTPUTS 16 #endif -class PwmDevices : public EventListener { -public: - //Constructor - PwmDevices(EventDispatcher* eD) { - _eventDispatcher = eD; - _eventDispatcher->addListener(this, EVENT_SOURCE_LIGHT); - _eventDispatcher->addListener(this, EVENT_SOURCE_SOLENOID); - _eventDispatcher->addListener(this, EVENT_SOURCE_SWITCH); - } - - void registerSolenoid(byte p, byte n, byte pow, byte minPT, byte maxPT, byte hP, byte hPAT, byte fS); - void registerFlasher(byte p, byte n, byte pow); - void registerLamp(byte p, byte n, byte pow); - - void update(); - - void handleEvent(Event* event); - - void handleEvent(ConfigEvent* event) {} - -private: - unsigned long _ms; - - byte port[MAX_PWM_OUTPUTS] = {0}; - byte number[MAX_PWM_OUTPUTS] = {0}; - byte power[MAX_PWM_OUTPUTS] = {0}; - byte minPulseTime[MAX_PWM_OUTPUTS] = {0}; - byte maxPulseTime[MAX_PWM_OUTPUTS] = {0}; - byte holdPower[MAX_PWM_OUTPUTS] = {0}; - byte holdPowerActivationTime[MAX_PWM_OUTPUTS] = {0}; - byte fastSwitch[MAX_PWM_OUTPUTS] = {0}; - byte type[MAX_PWM_OUTPUTS] = {0}; - unsigned long activated[MAX_PWM_OUTPUTS] = {0}; - bool scheduled[MAX_PWM_OUTPUTS] = {0}; - byte last = 0; - - EventDispatcher* _eventDispatcher; +class PwmDevices : public HighPowerOffAware { + public: + // Constructor + PwmDevices(EventDispatcher *eventDispatcher) + : HighPowerOffAware(eventDispatcher) { + eventDispatcher->addListener(this, EVENT_SOURCE_LIGHT); + // Listening to solenoids and switches is added in HighPowerOffAware(). + } + + void registerSolenoid(byte p, byte n, byte pow, uint16_t minPT, + uint16_t maxPT, byte hP, uint16_t hPAT, byte fS); + void registerFlasher(byte p, byte n, byte pow); + void registerLamp(byte p, byte n, byte pow); + + void update(); + void off(); + void reset(); + + void handleEvent(Event *event); + + private: + uint32_t _ms; + + byte port[MAX_PWM_OUTPUTS] = {0}; + byte number[MAX_PWM_OUTPUTS] = {0}; + byte power[MAX_PWM_OUTPUTS] = {0}; + uint16_t minPulseTime[MAX_PWM_OUTPUTS] = {0}; + uint16_t maxPulseTime[MAX_PWM_OUTPUTS] = {0}; + byte holdPower[MAX_PWM_OUTPUTS] = {0}; + uint16_t holdPowerActivationTime[MAX_PWM_OUTPUTS] = {0}; + byte fastSwitch[MAX_PWM_OUTPUTS] = {0}; + byte type[MAX_PWM_OUTPUTS] = {0}; + uint32_t activated[MAX_PWM_OUTPUTS] = {0}; + byte currentPower[MAX_PWM_OUTPUTS] = {0}; + bool scheduled[MAX_PWM_OUTPUTS] = {0}; + byte last = 0; + + void updateSolenoidOrFlasher(bool targetState, byte i); }; #endif diff --git a/src/IODevices/SwitchMatrix.cpp b/src/IODevices/SwitchMatrix.cpp new file mode 100644 index 0000000..7a8eec2 --- /dev/null +++ b/src/IODevices/SwitchMatrix.cpp @@ -0,0 +1,160 @@ +#include "SwitchMatrix.h" + +#include "SwitchMatrixPIO/ActiveHigh4Columns.pio.h" +#include "SwitchMatrixPIO/ActiveHigh4Rows.pio.h" +#include "SwitchMatrixPIO/ActiveHigh8Rows.pio.h" +#include "SwitchMatrixPIO/ActiveLow4Columns.pio.h" +#include "SwitchMatrixPIO/ActiveLow4Rows.pio.h" +#include "SwitchMatrixPIO/ActiveLow8Rows.pio.h" + +SwitchMatrix* SwitchMatrix::instance = nullptr; + +void SwitchMatrix::registerSwitch(byte p, byte n) { + if (p < (NUM_COLUMNS * numRows)) { + mapping[p] = n; + active = true; + } +} + +void SwitchMatrix::handleRowChanges(uint32_t raw) { + absolute_time_t now = get_absolute_time(); + uint32_t changed = raw ^ lastStable; // raw to raw comparison + + for (int column = 0; column < NUM_COLUMNS; column++) { + for (int row = 0; row < numRows; row++) { + uint8_t pos = column * numRows + row; + if (mapping[pos] == 0) continue; // Not registered + + uint32_t mask = 1u << ((NUM_COLUMNS - 1 - column) * numRows + row); + + if (changed & mask) { + // Convert RAW to logical pressed/released + // ----------------------------------------- + // activeLow : pressed = raw_bit == 0 + // activeHigh: pressed = raw_bit == 1 + bool rawBit = (raw & mask) != 0; + bool switchState = activeLow ? (!rawBit) // active-low: 0 = pressed + : rawBit; // active-high: 1 = pressed + // Debounce + if (absolute_time_diff_us(debounceTime[pos][switchState], now) >= + MATRIX_SWITCH_DEBOUNCE * 1000) { + debounceTime[pos][switchState] = now; + // Store the *raw* stable state + if (rawBit) + lastStable |= mask; // raw=1 + else + lastStable &= ~mask; // raw=0 + + // Dispatch all switch events as "local fast". + // If a PWM output registered to it, we have "fast flip". Useful for + // flippers, kick backs, jets and sling shots. + _eventDispatcher->dispatch(new Event( + EVENT_SOURCE_SWITCH, word(0, mapping[pos]), switchState, true)); + } + } + } + } +} + +void SwitchMatrix::handleEvent(Event* event) { + switch (event->sourceId) { + case EVENT_READ_SWITCHES: + // The CPU requested all current states. Usually this event is sent when + // the game gets started. + if (active) { + // First, send OFF for all switches then ON for the active ones using + // the IRQ handler. + for (int i = 0; i < (NUM_COLUMNS * numRows); i++) { + if (mapping[i] != 0) { + _eventDispatcher->dispatch( + new Event(EVENT_SOURCE_SWITCH, word(0, mapping[i]), 0)); + } + } + + if (!running) { + instance = this; + running = true; + + uint columns_offset; + pio_sm_config c_columns; + uint rows_offset; + pio_sm_config c_rows; + + if (activeLow) { + extern const pio_program_t active_low_4_columns_pio_program; + columns_offset = + pio_add_program(pio, &active_low_4_columns_pio_program); + c_columns = active_low_4_columns_pio_program_get_default_config( + columns_offset); + + if (4 == numRows) { + extern const pio_program_t active_low_4_rows_pio_program; + uint rows_offset = + pio_add_program(pio, &active_low_4_rows_pio_program); + pio_sm_config c_rows = + active_low_4_rows_pio_program_get_default_config(rows_offset); + } else { // 8 rows + extern const pio_program_t active_low_8_rows_pio_program; + uint rows_offset = + pio_add_program(pio, &active_low_8_rows_pio_program); + pio_sm_config c_rows = + active_low_8_rows_pio_program_get_default_config(rows_offset); + } + } else { // active high + extern const pio_program_t active_high_4_columns_pio_program; + columns_offset = + pio_add_program(pio, &active_high_4_columns_pio_program); + c_columns = active_high_4_columns_pio_program_get_default_config( + columns_offset); + + if (4 == numRows) { + extern const pio_program_t active_high_4_rows_pio_program; + uint rows_offset = + pio_add_program(pio, &active_high_4_rows_pio_program); + pio_sm_config c_rows = + active_high_4_rows_pio_program_get_default_config( + rows_offset); + } else { // 8 rows + extern const pio_program_t active_high_8_rows_pio_program; + uint rows_offset = + pio_add_program(pio, &active_high_8_rows_pio_program); + pio_sm_config c_rows = + active_high_8_rows_pio_program_get_default_config( + rows_offset); + } + } + + // Columns + sm_config_set_in_pins(&c_columns, COLUMNS_BASE_PIN); + // Connect GPIOs to this PIO block + for (uint i = 0; i < NUM_COLUMNS; i++) { + pio_gpio_init(pio, COLUMNS_BASE_PIN + i); + } + // Set the pin direction at the PIO + pio_sm_set_consecutive_pindirs(pio, sm_rows, COLUMNS_BASE_PIN, + NUM_COLUMNS, true); + sm_config_set_out_shift(&c_columns, false, false, 0); + pio_sm_init(pio, sm_columns, columns_offset, &c_columns); + pio_sm_set_enabled(pio, sm_columns, true); + + // Rows + sm_config_set_in_pins(&c_rows, COLUMNS_BASE_PIN - numRows); + // Connect GPIOs to this PIO block + for (uint i = 0; i < (numRows + NUM_COLUMNS); i++) { + pio_gpio_init(pio, COLUMNS_BASE_PIN - numRows + i); + } + // Set the pin direction at the PIO, we also read the 4 column pins + pio_sm_set_consecutive_pindirs(pio, sm_rows, + COLUMNS_BASE_PIN - numRows, + numRows + NUM_COLUMNS, false); + sm_config_set_in_shift(&c_rows, false, false, 0); + pio_sm_init(pio, sm_rows, rows_offset, &c_rows); + irq_set_exclusive_handler(PIO0_IRQ_0, onRowChanges); + irq_set_enabled(PIO0_IRQ_0, true); + pio_set_irq0_source_enabled(pio, pis_interrupt0, true); + pio_sm_set_enabled(pio, sm_rows, true); + } + } + break; + } +} diff --git a/src/IODevices/SwitchMatrix.h b/src/IODevices/SwitchMatrix.h new file mode 100644 index 0000000..8465def --- /dev/null +++ b/src/IODevices/SwitchMatrix.h @@ -0,0 +1,65 @@ +/* + SwitchMatrix_h. + Created by Markus Kalkbrenner, 2023-2025. +*/ + +#ifndef SwitchMatrix_h +#define SwitchMatrix_h + +#include "../EventDispatcher/Event.h" +#include "../EventDispatcher/EventDispatcher.h" +#include "hardware/gpio.h" +#include "hardware/pio.h" + +#define COLUMNS_BASE_PIN 15 // GPIO 15-18 for columns, the only pins with required hardware on IO_16_8_1 board +#define NUM_COLUMNS 4 +#define MAX_ROWS 8 +#define MATRIX_SWITCH_DEBOUNCE 2 + +class SwitchMatrix : public EventListener { + public: + SwitchMatrix(byte bId, EventDispatcher* eD) { + boardId = bId; + _eventDispatcher = eD; + _eventDispatcher->addListener(this, EVENT_POLL_EVENTS); + _eventDispatcher->addListener(this, EVENT_READ_SWITCHES); + } + + void setActiveLow() { activeLow = true; } + void setNumRows(uint8_t n) { numRows = n; } + void registerSwitch(byte p, byte n); + + void handleEvent(Event* event); + + void handleEvent(ConfigEvent* event) {} + + void handleRowChanges(uint32_t raw); + + PIO pio = pio0; + int sm_columns = 0; + int sm_rows = 1; + + static SwitchMatrix* instance; + + static void __not_in_flash_func(onRowChanges)() { + // IRQ0 clear + pio0_hw->irq = 1u << 0; + + uint32_t raw = pio_sm_get_blocking(instance->pio, instance->sm_rows); + instance->handleRowChanges(raw); + } + + private: + byte boardId; + bool activeLow = false; + uint8_t numRows = 4; + bool running = false; + bool active = false; + + byte mapping[NUM_COLUMNS * MAX_ROWS] = {0}; + uint32_t lastStable = 0; + absolute_time_t debounceTime[NUM_COLUMNS * MAX_ROWS][2] = {0}; + EventDispatcher* _eventDispatcher; +}; + +#endif diff --git a/src/IODevices/SwitchMatrix8x16.pio b/src/IODevices/SwitchMatrix8x16.pio new file mode 100644 index 0000000..9bd416c --- /dev/null +++ b/src/IODevices/SwitchMatrix8x16.pio @@ -0,0 +1,114 @@ +.program columns8x16_pio +.wrap_target + set y, 7 ; 8 columns (0-7) + +loop: + mov osr, y ; copy Y to OSR for output + out pins, 8 ; set 8 pins for columns, one is active LOW, others HIGH + nop [16] ; short delay to let other state machines read rows + jmp y-- loop ; decrement Y, loop until Y < 0 +.wrap + + +.program even_rows_high_pio + ; Initialize X to all pins HIGH. + set x, 0 ; X = 0x0 + mov y, ~x ; Y = 0xFFFFFFFF + mov x, y ; X = 0xFFFFFFFF + mov isr, y ; ISR = 0xFFFFFFFF + +loop: + wait 0 GPIO 10 ; wait for even column 8 + in pins, 16 ; read 16 rows to ISR (shifting into lower 16 bits of the 32bit ISR) + wait 0 GPIO 8 ; wait for even column 6 + in pins, 16 ; read 16 rows to ISR (shifting into lower 16 bits of the 32bit ISR) + + mov y, isr ; copy ISR to Y for comparison + jmp x!=y changed ; jump to "changed" if X (old state) != Y (new state) + jmp loop + +changed: + mov osr, y + push block ; push OSR to FIFO, "block" waits until the CPU handled enough data in the FIFO, so we don't overflow it + irq 1 ; trigger interrupt 1 to notify the main CPU that a even columns state has changed + mov x, y ; update X to the new switch states + jmp loop + + +.program odd_rows_high_pio + ; Initialize X to all pins HIGH. + set x, 0 ; X = 0x0 + mov y, ~x ; Y = 0xFFFFFFFF + mov x, y ; X = 0xFFFFFFFF + mov isr, y ; ISR = 0xFFFFFFFF + +loop: + wait 0 GPIO 9 ; wait for odd column 7 + in pins, 16 ; read 16 rows to ISR (shifting into lower 16 bits of the 32bit ISR) + wait 0 GPIO 7 ; wait for odd column 5 + in pins, 16 ; read 16 rows to ISR (shifting into lower 16 bits of the 32bit ISR) + + mov y, isr ; copy ISR to Y for comparison + jmp x!=y changed ; jump to "changed" if X (old state) != Y (new state) + jmp loop + +changed: + mov osr, y + push block ; push OSR to FIFO, "block" waits until the CPU handled enough data in the FIFO, so we don't overflow it + irq 0 ; trigger interrupt 0 to notify the main CPU that a odd columns state has changed + mov x, y ; update X to the new odd columns state + jmp loop + + +; IMPORTANT: +; The "low" PIO programs must run on a separate PIO instance from the "high" PIO programs, +; because of the limitation of interrupts per PIO instance (only 2). + +.program even_rows_low_pio + ; Initialize X to all pins HIGH. + set x, 0 ; X = 0x0 + mov y, ~x ; Y = 0xFFFFFFFF + mov x, y ; X = 0xFFFFFFFF + mov isr, y ; ISR = 0xFFFFFFFF + +loop: + wait 0 GPIO 6 ; wait for even column 4 + in pins, 16 ; read 16 rows to ISR (shifting into lower 16 bits of the 32bit ISR) + wait 0 GPIO 4 ; wait for even column 2 + in pins, 16 ; read 16 rows to ISR (shifting into lower 16 bits of the 32bit ISR) + + mov y, isr ; copy ISR to Y for comparison + jmp x!=y changed ; jump to "changed" if X (old state) != Y (new state) + jmp loop + +changed: + mov osr, y + push block ; push OSR to FIFO, "block" waits until the CPU handled enough data in the FIFO, so we don't overflow it + irq 1 ; trigger interrupt 1 to notify the main CPU that a even columns state has changed + mov x, y ; update X to the new switch states + jmp loop + + +.program odd_rows_low_pio + ; Initialize X to all pins HIGH. + set x, 0 ; X = 0x0 + mov y, ~x ; Y = 0xFFFFFFFF + mov x, y ; X = 0xFFFFFFFF + mov isr, y ; ISR = 0xFFFFFFFF + +loop: + wait 0 GPIO 5 ; wait for odd column 3 + in pins, 16 ; read 16 rows to ISR (shifting into lower 16 bits of the 32bit ISR) + wait 0 GPIO 3 ; wait for odd column 1 + in pins, 16 ; read 16 rows to ISR (shifting into lower 16 bits of the 32bit ISR) + + mov y, isr ; copy ISR to Y for comparison + jmp x!=y changed ; jump to "changed" if X (old state) != Y (new state) + jmp loop + +changed: + mov osr, y + push block ; push OSR to FIFO, "block" waits until the CPU handled enough data in the FIFO, so we don't overflow it + irq 0 ; trigger interrupt 0 to notify the main CPU that a odd columns state has changed + mov x, y ; update X to the new odd columns state + jmp loop diff --git a/src/IODevices/SwitchMatrixPIO/ActiveHigh4Columns.pio b/src/IODevices/SwitchMatrixPIO/ActiveHigh4Columns.pio new file mode 100644 index 0000000..c054f12 --- /dev/null +++ b/src/IODevices/SwitchMatrixPIO/ActiveHigh4Columns.pio @@ -0,0 +1,15 @@ +.program active_high_4_columns_pio +.wrap_target + set x, 1 + mov isr, x ; initialize ISR with 0x00000001 + set x, 0 ; X = 0 + set y, 3 ; 4 columns (0-3) + nop [16] ; short delay to let other state machines perform debouncing + +loop: + mov osr, isr ; copy ISR to OSR for output + out pins, 4 ; set 4 pins for columns, one is HIGH, others LOW + in x, 1 ; shift left ISR by 1 bit + nop [16] ; short delay to let other state machines read rows + jmp y-- loop ; decrement Y, loop until Y < 0 +.wrap diff --git a/src/IODevices/SwitchMatrixPIO/ActiveHigh4Rows.pio b/src/IODevices/SwitchMatrixPIO/ActiveHigh4Rows.pio new file mode 100644 index 0000000..2adad9d --- /dev/null +++ b/src/IODevices/SwitchMatrixPIO/ActiveHigh4Rows.pio @@ -0,0 +1,35 @@ +.program active_high_4_rows_pio + ; Initialize X to all pins LOW + set x, 0 ; X = 0x0 + mov y, x ; Y = 0x0 + mov isr, y ; ISR = 0x0 + mov osr, y ; ISR = 0x0 + +loop: + wait 0 PIN 4 ; wait for column 1 + in pins, 4 ; read 4 rows to ISR (shifting into lower 4 bits of the 32bit ISR) + wait 0 PIN 5 ; wait for column 2 + in pins, 4 ; read 4 rows to ISR (shifting into lower 4 bits of the 32bit ISR) + wait 0 PIN 6 ; wait for odd column 3 + in pins, 4 ; read 4 rows to ISR (shifting into lower 4 bits of the 32bit ISR) + wait 0 PIN 7 ; wait for odd column 4 + in pins, 4 ; read 4 rows to ISR (shifting into lower 4 bits of the 32bit ISR) + + mov y, isr ; copy ISR to X for comparison + jmp x!=y changed + + ; two consecutive identical reads required for debouncing + mov y, osr ; copy OSR to Y for comparison + jmp x!=y new_stable_state + + jmp loop + +changed: + mov x, isr ; update X to the new state + jmp loop + +new_stable_state: + mov osr, isr ; update OSR to the new state, used for debouncing + push block ; push ISR to FIFO, "block" waits until the CPU handled enough data in the FIFO, so we don't overflow it, ISR is empty after that! + irq 0 ; trigger interrupt 0 to notify the main CPU that matrix changed + jmp loop diff --git a/src/IODevices/SwitchMatrixPIO/ActiveHigh8Rows.pio b/src/IODevices/SwitchMatrixPIO/ActiveHigh8Rows.pio new file mode 100644 index 0000000..160ac0e --- /dev/null +++ b/src/IODevices/SwitchMatrixPIO/ActiveHigh8Rows.pio @@ -0,0 +1,35 @@ +.program active_high_8_rows_pio + ; Initialize X to all pins LOW + set x, 0 ; X = 0x0 + mov y, x ; Y = 0x0 + mov isr, y ; ISR = 0x0 + mov osr, y ; ISR = 0x0 + +loop: + wait 0 PIN 8 ; wait for column 1 + in pins, 8 ; read 8 rows to ISR (shifting into lower 8 bits of the 32bit ISR) + wait 0 PIN 9 ; wait for column 2 + in pins, 8 ; read 8 rows to ISR (shifting into lower 8 bits of the 32bit ISR) + wait 0 PIN 10 ; wait for odd column 3 + in pins, 8 ; read 8 rows to ISR (shifting into lower 8 bits of the 32bit ISR) + wait 0 PIN 11 ; wait for odd column 4 + in pins, 8 ; read 8 rows to ISR (shifting into lower 8 bits of the 32bit ISR) + + mov y, isr ; copy ISR to X for comparison + jmp x!=y changed + + ; two consecutive identical reads required for debouncing + mov y, osr ; copy OSR to Y for comparison + jmp x!=y new_stable_state + + jmp loop + +changed: + mov x, isr ; update X to the new state + jmp loop + +new_stable_state: + mov osr, isr ; update OSR to the new state, used for debouncing + push block ; push ISR to FIFO, "block" waits until the CPU handled enough data in the FIFO, so we don't overflow it, ISR is empty after that! + irq 0 ; trigger interrupt 0 to notify the main CPU that matrix changed + jmp loop diff --git a/src/IODevices/SwitchMatrixPIO/ActiveLow4Columns.pio b/src/IODevices/SwitchMatrixPIO/ActiveLow4Columns.pio new file mode 100644 index 0000000..ae3fa59 --- /dev/null +++ b/src/IODevices/SwitchMatrixPIO/ActiveLow4Columns.pio @@ -0,0 +1,16 @@ +.program active_low_4_columns_pio +.wrap_target + set y, 1 + mov isr, ~y ; initialize ISR with 0xFFFFFFFE + set y, 0 ; Y = 0 + mov x, ~y ; X = 0xFFFFFFFF + set y, 3 ; 4 columns (0-3) + nop [16] ; short delay to let other state machines perform debouncing + +loop: + mov osr, isr ; copy ISR to OSR for output + out pins, 4 ; set 4 pins for columns, one is LOW, others HIGH + in x, 1 ; shift left ISR by 1 bit + nop [16] ; short delay to let other state machines read rows + jmp y-- loop ; decrement Y, loop until Y < 0 +.wrap diff --git a/src/IODevices/SwitchMatrixPIO/ActiveLow4Rows.pio b/src/IODevices/SwitchMatrixPIO/ActiveLow4Rows.pio new file mode 100644 index 0000000..cdcc6b8 --- /dev/null +++ b/src/IODevices/SwitchMatrixPIO/ActiveLow4Rows.pio @@ -0,0 +1,37 @@ +.program active_low_4_rows_pio + ; Initialize X to all pins HIGH + set x, 0 ; X = 0x0 + mov y, ~x ; Y = 0xFFFFFFFF + mov x, y ; X = 0xFFFFFFFF + mov osr, y ; OSR = 0xFFFFFFFF + +loop: + set y, 0 ; X = 0x0 + mov isr, ~y ; ISR = 0xFFFFFFFF + wait 0 PIN 4 ; wait for column 1 + in pins, 4 ; read 4 rows to ISR (shifting into lower 4 bits of the 32bit ISR) + wait 0 PIN 5 ; wait for column 2 + in pins, 4 ; read 4 rows to ISR (shifting into lower 4 bits of the 32bit ISR) + wait 0 PIN 6 ; wait for odd column 3 + in pins, 4 ; read 4 rows to ISR (shifting into lower 4 bits of the 32bit ISR) + wait 0 PIN 7 ; wait for odd column 4 + in pins, 4 ; read 4 rows to ISR (shifting into lower 4 bits of the 32bit ISR) + + mov y, isr ; copy ISR to X for comparison + jmp x!=y changed + + ; two consecutive identical reads required for debouncing + mov y, osr ; copy OSR to Y for comparison + jmp x!=y new_stable_state + + jmp loop + +changed: + mov x, isr ; update X to the new state + jmp loop + +new_stable_state: + mov osr, isr ; update OSR to the new state, used for debouncing + push block ; push ISR to FIFO, "block" waits until the CPU handled enough data in the FIFO, so we don't overflow it, ISR is empty after that! + irq 0 ; trigger interrupt 0 to notify the main CPU that matrix changed + jmp loop diff --git a/src/IODevices/SwitchMatrixPIO/ActiveLow8Rows.pio b/src/IODevices/SwitchMatrixPIO/ActiveLow8Rows.pio new file mode 100644 index 0000000..607d270 --- /dev/null +++ b/src/IODevices/SwitchMatrixPIO/ActiveLow8Rows.pio @@ -0,0 +1,37 @@ +.program active_low_8_rows_pio + ; Initialize X to all pins HIGH + set x, 0 ; X = 0x0 + mov y, ~x ; Y = 0xFFFFFFFF + mov x, y ; X = 0xFFFFFFFF + mov osr, y ; OSR = 0xFFFFFFFF + +loop: + set y, 0 ; X = 0x0 + mov isr, ~y ; ISR = 0xFFFFFFFF + wait 0 PIN 8 ; wait for column 1 + in pins, 8 ; read 8 rows to ISR (shifting into lower 8 bits of the 32bit ISR) + wait 0 PIN 9 ; wait for column 2 + in pins, 8 ; read 8 rows to ISR (shifting into lower 8 bits of the 32bit ISR) + wait 0 PIN 10 ; wait for odd column 3 + in pins, 8 ; read 8 rows to ISR (shifting into lower 8 bits of the 32bit ISR) + wait 0 PIN 11 ; wait for odd column 4 + in pins, 8 ; read 8 rows to ISR (shifting into lower 8 bits of the 32bit ISR) + + mov y, isr ; copy ISR to X for comparison + jmp x!=y changed + + ; two consecutive identical reads required for debouncing + mov y, osr ; copy OSR to Y for comparison + jmp x!=y new_stable_state + + jmp loop + +changed: + mov x, isr ; update X to the new state + jmp loop + +new_stable_state: + mov osr, isr ; update OSR to the new state, used for debouncing + push block ; push ISR to FIFO, "block" waits until the CPU handled enough data in the FIFO, so we don't overflow it, ISR is empty after that! + irq 0 ; trigger interrupt 0 to notify the main CPU that matrix changed + jmp loop diff --git a/src/IODevices/Switches.cpp b/src/IODevices/Switches.cpp index af88984..4e30711 100644 --- a/src/IODevices/Switches.cpp +++ b/src/IODevices/Switches.cpp @@ -1,54 +1,129 @@ #include "Switches.h" -void Switches::registerSwitch(byte p, byte n) { - if (last < MAX_SWITCHES) { - port[last] = p; - number[last] = n; +#include "SwitchesPIO/ActiveLow16Switches.pio.h" +#include "SwitchesPIO/ActiveLow4Switches.pio.h" +#include "SwitchesPIO/ActiveLow8Switches.pio.h" - pinMode(p, INPUT); - state[last++] = digitalRead(p); - } +Switches* Switches::instance = nullptr; + +void Switches::registerSwitch(byte p, byte n, uint8_t debounceTimeMs) { + if (last < (numSwitches - 1) && p < numSwitches) { + port[++last] = p; + number[last] = n; + debounceSetting[last] = debounceTimeMs; + active = true; + } } -void Switches::update() { - // Wait for SWITCH_DEBOUNCE milliseconds to debounce the switches. That covers the edge case that a switch was hit - // right before the last polling of events. After SWITCH_DEBOUNCE milliseconds every switch is allowed to toggle - // once until the events get polled again. - if (millis() - _ms >= SWITCH_DEBOUNCE) { - for (int i = 0; i <= last; i++) { - if (!toggled[i]) { - bool new_state = digitalRead(port[i]); - if (new_state != state[i]) { - state[i] = new_state; - toggled[i] = true; - // Dispatch all switch events as "local fast". - // If a PWM output registered to it, we have "fast flip". Useful for flippers, kick backs, jets and - // sling shots. - _eventDispatcher->dispatch(new Event(EVENT_SOURCE_SWITCH, word(0, number[i]), state[i], true)); - } - } +void Switches::handleSwitchChanges(uint32_t raw) { + uint32_t now = millis(); + uint32_t changed = raw ^ currentStable; + if (changed > 0) { + uint32_t allSwitchesMask = 0; + + for (int i = 0; i <= last; i++) { + uint32_t mask = 1u << (port[i] - SWITCHES_BASE_PIN); + allSwitchesMask |= mask; + + if (changed & mask) { + // Debounce + if ((debounceTime[i] + debounceSetting[i]) < now) { + debounceTime[i] = now; + bool switchState = ((raw & mask) != 0); + if (switchState) + currentStable |= mask; // set bit in lastStable to 1 + else + currentStable &= ~mask; // set bit in lastStable to 0 + // Dispatch all switch events as "local fast". + // If a PWM output registered to it, we have "fast flip". Useful for + // flippers, kick backs, jets and sling shots. + _eventDispatcher->dispatch(new Event(EVENT_SOURCE_SWITCH, + word(0, number[i]), + switchState ? 1 : 0, true)); + // digitalWrite(LED_BUILTIN, switchState); } + } } + + // Set unregistered switches to raw value to for next comparison + currentStable = + (currentStable & allSwitchesMask) + (raw & ~allSwitchesMask); + } + + // Push debounced state to PIO for next detection + pio_sm_put(pio, sm, ~currentStable); } void Switches::handleEvent(Event* event) { - switch (event->eventId) { - case EVENT_POLL_EVENTS: - if (boardId == (byte) event->value) { - // This I/O board has been polled for events, so all current switch states are transmitted. Reset switch - // debounce timer and toggles. - _ms = millis(); - for (int i = 0; i <= last; i++) { - toggled[i] = false; - } - } - break; - - case EVENT_READ_SWITCHES: - // The CPU requested all current states. - for (int i = 0; i <= last; i++) { - _eventDispatcher->dispatch(new Event(EVENT_SOURCE_SWITCH, word(0, number[i]), state[i])); - } - break; - } + switch (event->sourceId) { + case EVENT_READ_SWITCHES: + // The CPU requested all current states. Usually this event is sent when + // the game gets started. + if (active) { + // First, send OFF for all switches then ON for the active ones using + // the IRQ handler. + for (int i = 0; i <= last; i++) { + if (number[i] != 0) { + uint16_t mask = 1u << (port[i] - SWITCHES_BASE_PIN); + _eventDispatcher->dispatch( + new Event(EVENT_SOURCE_SWITCH, word(0, number[i]), + ((currentStable & mask) > 0) ? 1 : 0)); + } + } + + if (!running) { + instance = this; + running = true; + + uint offset; + pio_sm_config c; + + switch (numSwitches) { + case 4: + extern const pio_program_t active_low_4_switches_pio_program; + offset = pio_add_program(pio, &active_low_4_switches_pio_program); + c = active_low_4_switches_pio_program_get_default_config(offset); + break; + + case 8: + extern const pio_program_t active_low_8_switches_pio_program; + offset = pio_add_program(pio, &active_low_8_switches_pio_program); + c = active_low_8_switches_pio_program_get_default_config(offset); + break; + + case MAX_SWITCHES: + default: + extern const pio_program_t active_low_16_switches_pio_program; + offset = + pio_add_program(pio, &active_low_16_switches_pio_program); + c = active_low_16_switches_pio_program_get_default_config(offset); + break; + } + + sm_config_set_in_pins(&c, SWITCHES_BASE_PIN); + if (MAX_SWITCHES == numSwitches) { + // Using GPIO 15-18 as switch inputs on IO_16_8_1 board requires + // resetting the sateful input after reading. + // Set begins at GPIO 15 for 4 pins. + sm_config_set_set_pins(&c, 15, 4); + // Side-set begins at GPIO 15. + sm_config_set_sideset_pins(&c, 15); + } + // Connect GPIOs to this PIO block + for (uint i = 0; i < numSwitches; i++) { + pio_gpio_init(pio, SWITCHES_BASE_PIN + i); + } + // Set the pin direction at the PIO + pio_sm_set_consecutive_pindirs(pio, sm, SWITCHES_BASE_PIN, + numSwitches, false); + sm_config_set_in_shift(&c, false, false, 0); + pio_sm_init(pio, sm, offset, &c); + irq_set_exclusive_handler(PIO0_IRQ_1, onSwitchChanges); + irq_set_enabled(PIO0_IRQ_1, true); + pio_set_irq1_source_enabled(pio, pis_interrupt1, true); + pio_sm_set_enabled(pio, sm, true); + } + } + break; + } } diff --git a/src/IODevices/Switches.h b/src/IODevices/Switches.h index 37a2035..e79e98d 100644 --- a/src/IODevices/Switches.h +++ b/src/IODevices/Switches.h @@ -12,44 +12,63 @@ #include "../EventDispatcher/Event.h" #include "../EventDispatcher/EventDispatcher.h" +#include "hardware/gpio.h" +#include "hardware/pio.h" -#ifndef MAX_SWITCHES +#define SWITCHES_BASE_PIN 3 #define MAX_SWITCHES 16 -#endif - -#ifndef SWITCH_DEBOUNCE -#define SWITCH_DEBOUNCE 5 -#endif class Switches : public EventListener { -public: - Switches(byte bId, EventDispatcher* eD) { - boardId = bId; + public: + Switches(byte bId, EventDispatcher* eD) { + boardId = bId; + _eventDispatcher = eD; + _eventDispatcher->addListener(this, EVENT_POLL_EVENTS); + _eventDispatcher->addListener(this, EVENT_READ_SWITCHES); + } + + void setNumSwitches(uint8_t n) { + numSwitches = n; + validSwitchMask = (1u << numSwitches) - 1; + } + void registerSwitch(byte p, byte n, uint8_t debounceTimeMs); + + void handleEvent(Event* event); + + void handleEvent(ConfigEvent* event) {} + + void handleSwitchChanges(uint32_t raw); - _eventDispatcher = eD; - _eventDispatcher->addListener(this, EVENT_POLL_EVENTS); - _eventDispatcher->addListener(this, EVENT_READ_SWITCHES); - } + PIO pio = pio0; + int sm = 2; // State machine 0 and 1 are used by SwitchMatrix + uint16_t validSwitchMask = (1u << MAX_SWITCHES) - 1; + static Switches* instance; + uint8_t numSwitches = MAX_SWITCHES; - void registerSwitch(byte p, byte n); + static void __not_in_flash_func(onSwitchChanges)() { + // re-enable IRQ1 for next switch change (clear IRQ 1) + pio0_hw->irq = 1u << 1; - void update(); + // Get 32 bit from FIFO + uint32_t raw = pio_sm_get(instance->pio, instance->sm); + instance->handleSwitchChanges((~raw) & instance->validSwitchMask); + } - void handleEvent(Event* event); + private: + byte boardId; - void handleEvent(ConfigEvent* event) {} + bool running = false; + bool active = false; -private: - byte boardId; - unsigned long _ms; + byte port[MAX_SWITCHES] = {0}; + byte number[MAX_SWITCHES] = {0}; + uint8_t debounceSetting[MAX_SWITCHES] = {0}; + uint32_t debounceTime[MAX_SWITCHES] = {0}; + int last = -1; - byte port[MAX_SWITCHES] = {0}; - byte number[MAX_SWITCHES] = {0}; - bool state[MAX_SWITCHES] = {0}; - bool toggled[MAX_SWITCHES] = {0}; - byte last = 0; + uint16_t currentStable = 0; - EventDispatcher* _eventDispatcher; + EventDispatcher* _eventDispatcher; }; #endif diff --git a/src/IODevices/SwitchesPIO/ActiveLow16Switches.pio b/src/IODevices/SwitchesPIO/ActiveLow16Switches.pio new file mode 100644 index 0000000..3f4bdbe --- /dev/null +++ b/src/IODevices/SwitchesPIO/ActiveLow16Switches.pio @@ -0,0 +1,46 @@ +.program active_low_16_switches_pio +.side_set 4 opt + set x, 0 ; X = 0x0 + mov osr, ~x + jmp reset_stateful_pins + +loop: + mov isr, null ; ISR is 0x0 + in pins, 16 ; read 16 switches to ISR + mov y, isr ; copy ISR to Y for comparison + jmp x!=y changed ; jump to "changed" if X (old state) != Y (new state) + + ; two consecutive identical reads required for first level of debouncing + mov y, osr ; copy OSR to Y for comparison + jmp x!=y new_stable_state + + jmp loop + +changed: + mov x, y ; update X to the new state + nop ; short delay for debouncing + nop ; short delay for debouncing + nop ; short delay for debouncing + nop ; short delay for debouncing + nop ; short delay for debouncing + nop ; short delay for debouncing + + jmp loop + +new_stable_state: + push ; push ISR to RX FIFO + irq 1 ; trigger interrupt 1 to notify the main CPU that switches changed + pull ; pull debounced state from TX FIFO to OSR + mov x, osr ; update X to the new state from OSR + +reset_stateful_pins: + ; Reset stateful pins (GPIO 15-18) + set pindirs, 0b1111 ; change direction of 4 pins (15-18) to output + nop side 0 ; set LOW + nop side 0b1111 ; set 4 pins to HIGH + nop side 0b1111 ; short delay + nop side 0b1111 ; short delay + nop side 0b1111 ; short delay + nop side 0 ; set LOW + set pindirs, 0 ; change direction all pins back to input + jmp loop diff --git a/src/IODevices/SwitchesPIO/ActiveLow4Switches.pio b/src/IODevices/SwitchesPIO/ActiveLow4Switches.pio new file mode 100644 index 0000000..de3aa17 --- /dev/null +++ b/src/IODevices/SwitchesPIO/ActiveLow4Switches.pio @@ -0,0 +1,28 @@ +.program active_low_4_switches_pio + set x, 0 ; X = 0x0 + mov osr, ~x + +loop: + mov isr, null ; ISR is 0x0 + in pins, 8 ; read 4 switches to ISR + mov y, isr ; copy ISR to Y for comparison + jmp x!=y changed ; jump to "changed" if X (old state) != Y (new state) + + ; two consecutive identical reads required for first level of debouncing + mov y, osr ; copy OSR to Y for comparison + jmp x!=y new_stable_state + + jmp loop + +changed: + mov x, y ; update X to the new state + + jmp loop + +new_stable_state: + push ; push ISR to RX FIFO + irq 1 ; trigger interrupt 1 to notify the main CPU that switches changed + pull ; pull debounced state from TX FIFO to OSR + mov x, osr ; update X to the new state from OSR + + jmp loop diff --git a/src/IODevices/SwitchesPIO/ActiveLow8Switches.pio b/src/IODevices/SwitchesPIO/ActiveLow8Switches.pio new file mode 100644 index 0000000..19c140a --- /dev/null +++ b/src/IODevices/SwitchesPIO/ActiveLow8Switches.pio @@ -0,0 +1,28 @@ +.program active_low_8_switches_pio + set x, 0 ; X = 0x0 + mov osr, ~x + +loop: + mov isr, null ; ISR is 0x0 + in pins, 8 ; read 8 switches to ISR + mov y, isr ; copy ISR to Y for comparison + jmp x!=y changed ; jump to "changed" if X (old state) != Y (new state) + + ; two consecutive identical reads required for first level of debouncing + mov y, osr ; copy OSR to Y for comparison + jmp x!=y new_stable_state + + jmp loop + +changed: + mov x, y ; update X to the new state + + jmp loop + +new_stable_state: + push ; push ISR to RX FIFO + irq 1 ; trigger interrupt 1 to notify the main CPU that switches changed + pull ; pull debounced state from TX FIFO to OSR + mov x, osr ; update X to the new state from OSR + + jmp loop diff --git a/src/InputController.cpp b/src/InputController.cpp deleted file mode 100644 index cef5510..0000000 --- a/src/InputController.cpp +++ /dev/null @@ -1,56 +0,0 @@ -#include "InputController.h" - -InputController::InputController(int controllerType, byte pf) { - _eventDispatcher = new EventDispatcher(); - InputController(controllerType, pf, _eventDispatcher); -} - -InputController::InputController(int controllerType, byte pf, EventDispatcher* eventDispatcher) { - pinMode(LED_BUILTIN, OUTPUT); - - platform = pf; - _eventDispatcher = eventDispatcher; - - if (controllerType == CONTROLLER_MEGA_ALL_INPUT) { - _solenoids = new Solenoids(controllerType, _eventDispatcher); - _switchMatrix = new SwitchMatrix(_eventDispatcher, platform); - _lightMatrix = new LightMatrix(_eventDispatcher, platform); - _pin2Dmd = new PIN2DMD(_eventDispatcher); - _pupComLink = new PUPComLink(); - _testButtons = new InputControllerTestButtons(_eventDispatcher); - } - else if (controllerType == CONTROLLER_NANO_PIN2DMD_OUTPUT) { - _pin2Dmd = new PIN2DMD(_eventDispatcher); - } else { - Serial.print("Unsupported Input Controller: "); - Serial.println(controllerType); - } -} - -Solenoids* InputController::solenoids() { - return _solenoids; -} - -SwitchMatrix* InputController::switchMatrix() { - return _switchMatrix; -} - -LightMatrix* InputController::lightMatrix() { - return _lightMatrix; -} - -PIN2DMD* InputController::pin2Dmd() { - return _pin2Dmd; -} - -PUPComLink* InputController::pupComLink() { - return _pupComLink; -} - -InputControllerTestButtons* InputController::testButtons() { - return _testButtons; -} - -EventDispatcher* InputController::eventDispatcher() { - return _eventDispatcher; -} diff --git a/src/InputController.h b/src/InputController.h deleted file mode 100644 index 3c1971b..0000000 --- a/src/InputController.h +++ /dev/null @@ -1,52 +0,0 @@ -/* - InputController.h - Created by Markus Kalkbrenner. -*/ - -#ifndef INPUTCONTROLLER_h -#define INPUTCONTROLLER_h - -#include "PPUC.h" - -#include "EventDispatcher/Event.h" -#include "EventDispatcher/EventDispatcher.h" -#include "InputDevices/Solenoids.h" -#include "InputDevices/SwitchMatrix.h" -#include "InputDevices/LightMatrix.h" -#include "InputDevices/PIN2DMD.h" -#include "InputDevices/InputControllerTestButtons.h" -#include "VisualPinball/PUPComLink.h" - -class InputController { -public: - InputController(int controllerType, byte platform); - - InputController(int controllerType, byte platform, EventDispatcher* eventDispatcher); - - Solenoids* solenoids(); - - SwitchMatrix* switchMatrix(); - - LightMatrix* lightMatrix(); - - PIN2DMD* pin2Dmd(); - - PUPComLink* pupComLink(); - - InputControllerTestButtons* testButtons(); - - EventDispatcher* eventDispatcher(); - - byte platform; - -private: - Solenoids* _solenoids; - SwitchMatrix* _switchMatrix; - LightMatrix* _lightMatrix; - PIN2DMD* _pin2Dmd; - PUPComLink* _pupComLink; - InputControllerTestButtons* _testButtons; - EventDispatcher* _eventDispatcher; -}; - -#endif diff --git a/src/InputDevices/DistributionControllerTestButtons.cpp b/src/InputDevices/DistributionControllerTestButtons.cpp deleted file mode 100644 index e0d6f4a..0000000 --- a/src/InputDevices/DistributionControllerTestButtons.cpp +++ /dev/null @@ -1,24 +0,0 @@ -#include "DistributionControllerTestButtons.h" - -DistributionControllerTestButtons::DistributionControllerTestButtons(EventDispatcher* eD) { - eventDispatcher = eD; - - for (int i = 0; i <= 1; i++) { - button[i] = new Bounce2::Button(); - button[i]->attach(22 + i, INPUT_PULLUP); - button[i]->interval(10); - button[i]->setPressedState(LOW); - } -} - -void DistributionControllerTestButtons::update() { - for (int i = 0; i <= 1; i++) { - button[i]->update(); - if (button[i]->pressed()) { - eventDispatcher->dispatch(new Event(EVENT_SOURCE_SWITCH, word(0, 63 + i), 1)); - } - else if (button[i]->released()) { - eventDispatcher->dispatch(new Event(EVENT_SOURCE_SWITCH, word(0, 63 + i), 0)); - } - } -} diff --git a/src/InputDevices/DistributionControllerTestButtons.h b/src/InputDevices/DistributionControllerTestButtons.h deleted file mode 100644 index c3c263b..0000000 --- a/src/InputDevices/DistributionControllerTestButtons.h +++ /dev/null @@ -1,28 +0,0 @@ -/* - DistributionControllerTestButtons.h - Created by Markus Kalkbrenner, 2022. - - Play more pinball! -*/ - -#ifndef EffectControllerTestButtons_h -#define EffectControllerTestButtons_h - -#include -#include - -#include "../EventDispatcher/Event.h" -#include "../EventDispatcher/EventDispatcher.h" - -class DistributionControllerTestButtons { -public: - DistributionControllerTestButtons(EventDispatcher* eD); - - void update(); - -protected: - EventDispatcher* eventDispatcher; - Bounce2::Button* button[2]; -}; - -#endif diff --git a/src/InputDevices/EffectControllerTestButtons.cpp b/src/InputDevices/EffectControllerTestButtons.cpp deleted file mode 100644 index 9a1b3f5..0000000 --- a/src/InputDevices/EffectControllerTestButtons.cpp +++ /dev/null @@ -1,21 +0,0 @@ -#include "EffectControllerTestButtons.h" - -EffectControllerTestButtons::EffectControllerTestButtons(EventDispatcher* eD) { - eventDispatcher = eD; - - for (int i = 0; i <= 1; i++) { - button[i] = new Bounce2::Button(); - button[i]->attach(22 + i, INPUT_PULLUP); - button[i]->interval(10); - button[i]->setPressedState(LOW); - } -} - -void EffectControllerTestButtons::update() { - for (int i = 0; i <= 1; i++) { - button[i]->update(); - if (button[i]->pressed()) { - eventDispatcher->dispatch(new Event(EVENT_SOURCE_SWITCH, word(0, 201 + i))); - } - } -} diff --git a/src/InputDevices/EffectControllerTestButtons.h b/src/InputDevices/EffectControllerTestButtons.h deleted file mode 100644 index c3b2fdc..0000000 --- a/src/InputDevices/EffectControllerTestButtons.h +++ /dev/null @@ -1,28 +0,0 @@ -/* - EffectControllerTestButtons.h - Created by Markus Kalkbrenner, 2021. - - Play more pinball! -*/ - -#ifndef EffectControllerTestButtons_h -#define EffectControllerTestButtons_h - -#include -#include - -#include "../EventDispatcher/Event.h" -#include "../EventDispatcher/EventDispatcher.h" - -class EffectControllerTestButtons { -public: - EffectControllerTestButtons(EventDispatcher* eD); - - void update(); - -protected: - EventDispatcher* eventDispatcher; - Bounce2::Button* button[2]; -}; - -#endif diff --git a/src/InputDevices/GeneralIlluminationWPC.cpp b/src/InputDevices/GeneralIlluminationWPC.cpp deleted file mode 100644 index aa1ed60..0000000 --- a/src/InputDevices/GeneralIlluminationWPC.cpp +++ /dev/null @@ -1,156 +0,0 @@ -// -// Created by Markus Kalkbrenner on 14.03.21. -// - -#include "GeneralIlluminationWPC.h" - -// see https://forum.arduino.cc/index.php?topic=398610.0 -GeneralIlluminationWPC* GeneralIlluminationWPC::giInstance = NULL; - -void GeneralIlluminationWPC::start() { - // initialize data - for (int i = 0; i < NUM_GI_STRINGS; i++) { - for (int k = 0; k <= NUM_BRIGHTNESS; k++) { - sBrightnessHist[i][k] = 0; - } - } - - attachInterrupt(digitalPinToInterrupt(2), GeneralIlluminationWPC::_changeD0, RISING); - attachInterrupt(digitalPinToInterrupt(3), GeneralIlluminationWPC::_changeD1, RISING); - attachInterrupt(digitalPinToInterrupt(4), GeneralIlluminationWPC::_changeD2, RISING); - attachInterrupt(digitalPinToInterrupt(5), GeneralIlluminationWPC::_changeD3, RISING); - attachInterrupt(digitalPinToInterrupt(6), GeneralIlluminationWPC::_changeD4, RISING); - attachInterrupt(digitalPinToInterrupt(12), GeneralIlluminationWPC::_changeZC, FALLING); -} - -void GeneralIlluminationWPC::stop() { - detachInterrupt(digitalPinToInterrupt(2)); - detachInterrupt(digitalPinToInterrupt(3)); - detachInterrupt(digitalPinToInterrupt(4)); - detachInterrupt(digitalPinToInterrupt(5)); - detachInterrupt(digitalPinToInterrupt(6)); - detachInterrupt(digitalPinToInterrupt(12)); -} - -void GeneralIlluminationWPC::update() { - for (int string = 0; string < NUM_GI_STRINGS; string++) { - if (sBrightness[string] != sBrightnessTarget[string]) { - sBrightness[string] = sBrightnessTarget[string]; - eventDispatcher->dispatch(new Event(EVENT_SOURCE_GI, word(0, string + 1), sBrightness[string])); - } - } -} - -void GeneralIlluminationWPC::newBrightness(uint8_t string, uint8_t b) { - // Check whether the brightness has changed - if (b <= NUM_BRIGHTNESS && b != sBrightnessTarget[string]) { - // Add the current brightness value to the histogram - if (sBrightnessHist[string][b] < 255) { - sBrightnessHist[string][b]++; - } - - // Only switch when some measurements in the center of the - // brightness interval have been seen - if (sBrightnessHist[string][b] > BRIGHTNESS_SWITCH_THRESH) { - // switch to the new brightness target value - sBrightnessTarget[string] = b; - - // clear the histogram - memset((void*) &(sBrightnessHist[string][0]), 0, NUM_BRIGHTNESS + 1); - } - } -} - -void GeneralIlluminationWPC::_changeD0() { - giInstance->handlePinChange(0); -} - -void GeneralIlluminationWPC::_changeD1() { - giInstance->handlePinChange(1); -} - -void GeneralIlluminationWPC::_changeD2() { - giInstance->handlePinChange(2); -} - -void GeneralIlluminationWPC::_changeD3() { - giInstance->handlePinChange(3); -} - -void GeneralIlluminationWPC::_changeD4() { - giInstance->handlePinChange(4); -} - -void GeneralIlluminationWPC::handlePinChange(uint8_t giString) { - // Handle the new brightness value - newBrightness(giString, dtToBrightness(micros() - sZCIntTime)); - - sInterruptsSeen |= (1 << giString); -} - -void GeneralIlluminationWPC::_changeZC() { - //Serial.println("ZC"); - - // What time is it? - giInstance->sZCIntTime = micros(); - - // All strings without interrupt in the previous interval are either fully - // on or off. So their brightness is not set by handlePinChange(). Set - // 0 (off) or NUM_BRIGHTNESS (on) here. - for (int i = 0; i < NUM_GI_STRINGS; i++) { - if ((giInstance->sInterruptsSeen & (1 << i)) == 0) { - giInstance->newBrightness(i, digitalRead(2 + i) ? 0 : NUM_BRIGHTNESS); - } - } - // Clear the interrupts mask - giInstance->sInterruptsSeen = 0; -} - -uint8_t GeneralIlluminationWPC::dtToBrightness(uint32_t dt) -{ - // This function leaves some margin at the interval borders to account for - // the fact the intervals are slightly overlapping and therefore an - // unambiguous brightness determination is not always possible. In this case - // the function returns 255. - if (dt < (1200 - BRIGHTNESS_INTERVAL_MARGIN)) { - // It seems that brightness level 7 is never used in any WPC pinball machine. - // It us selectable in the GI test, but the time is not measurable. - // The signal remains HIGH and therefore a brightness level of 8 will be - // detected. So we return 8 here as well, even if this part of the code - // should never be reached. - return NUM_BRIGHTNESS; - } - else if ((dt > (1200 + BRIGHTNESS_INTERVAL_MARGIN)) && - (dt < (2200 - BRIGHTNESS_INTERVAL_MARGIN))) - { - return 6; - } - else if ((dt > (2200 + BRIGHTNESS_INTERVAL_MARGIN)) && - (dt < (3200 - BRIGHTNESS_INTERVAL_MARGIN))) - { - return 5; - } - else if ((dt > (3200 + BRIGHTNESS_INTERVAL_MARGIN)) && - (dt < (4200 - BRIGHTNESS_INTERVAL_MARGIN))) - { - return 4; - } - else if ((dt > (4200 + BRIGHTNESS_INTERVAL_MARGIN)) && - (dt < (5200 - BRIGHTNESS_INTERVAL_MARGIN))) - { - return 3; - } - else if ((dt > (5200 + BRIGHTNESS_INTERVAL_MARGIN)) && - (dt < (6200 - BRIGHTNESS_INTERVAL_MARGIN))) - { - return 2; - } - else if ((dt > (6200 + BRIGHTNESS_INTERVAL_MARGIN)) && - (dt < (7200 - BRIGHTNESS_INTERVAL_MARGIN))) - { - return 1; - } - - // invalid interval - return BRIGHTNESS_UNCERTAIN; -} diff --git a/src/InputDevices/GeneralIlluminationWPC.h b/src/InputDevices/GeneralIlluminationWPC.h deleted file mode 100644 index a0a564c..0000000 --- a/src/InputDevices/GeneralIlluminationWPC.h +++ /dev/null @@ -1,113 +0,0 @@ -/* - GeneralIlluminationWPC.h. - Created by Markus Kalkbrenner, 2021. - - Based on Afterglow GI by Christoph Schmid, 2019 - - Play more pinball! -*/ - -#ifndef _GENERALILLUMINATIONWPC_H -#define _GENERALILLUMINATIONWPC_H - -/* This code assumes following pin layout: - * - * +----------+---------------+------+ - * | Name | Function | Pin# | - * +----------+---------------+------+ - * | STR1_IN | D0 Data Bus | D2 | - * | STR2_IN | D1 Data Bus | D3 | - * | STR3_IN | D2 Data Bus | D4 | - * | STR4_IN | D3 Data Bus | D5 | - * | STR5_IN | D4 Data Bus | D6 | - * | ZC | Zero Crossing | D12 | - * +----------+---------------+------+ -*/ - -// The WPC CPU issues a short signal to turn on the triac controlling the -// AC voltage. The Triac will stay on until the next zero crossing of the -// AC signal. -// The closer the next signal is to the zero crossing, the brighter the lamps -// will be. If no signal is issued at all (stays high), the lamps will light -// at full power (brightness level 8). If the signal remains low, the lamps -// are turned off. -// The zero crossing signal is issued with twice the AC frequency, i.e. with -// 100Hz or every 10ms in Europe. -// The scope says the ZC signal is 1ms long while the Triac signal is only -// ~8us long. - -// ZC Sig TR Sig ZC Sig ZC Zero Crossing Signal -// | | | TR Triac enable signal -// |--+ | | | | v| | | | |--+ B0-B6 Brightness 1-7 levels (WPC GI) -// |ZC| |ZC| -// | |B7 B6 B5 B4 B3 B2 B1 | | -// +-----------------------------+------ -// 0ms 10ms - - -#include "../EventDispatcher/EventDispatcher.h" - -//------------------------------------------------------------------------------ -// Setup - -// Number of GI strings -#define NUM_GI_STRINGS 5 - -// Number of brightness steps -#define NUM_BRIGHTNESS 8 - -// Number of center brightness values required to switch value -#define BRIGHTNESS_SWITCH_THRESH 5 - -// Brightness interval margin to account for overlapping intervals [us] -#define BRIGHTNESS_INTERVAL_MARGIN 150 - -// Uncertain brightness value -#define BRIGHTNESS_UNCERTAIN 255 - -class GeneralIlluminationWPC { -public: - GeneralIlluminationWPC(EventDispatcher *eD) { - giInstance = this; - eventDispatcher = eD; - - pinMode(2, INPUT); - pinMode(3, INPUT); - pinMode(4, INPUT); - pinMode(5, INPUT); - pinMode(6, INPUT); - pinMode(12, INPUT_PULLUP); // Inverted on Adapter. - } - - void start(); - void stop(); - void update(); - - void handlePinChange(uint8_t giString); - void newBrightness(uint8_t string, uint8_t b); - - static void _changeD0(); - static void _changeD1(); - static void _changeD2(); - static void _changeD3(); - static void _changeD4(); - static void _changeZC(); - - volatile uint32_t sZCIntTime = 0; - volatile uint8_t sInterruptsSeen = 0; - volatile uint32_t sDataIntLast[NUM_GI_STRINGS] = {0}; - -protected: - uint8_t dtToBrightness(uint32_t dt); - - EventDispatcher* eventDispatcher; - - uint8_t sBrightness[NUM_GI_STRINGS] = {0}; - uint8_t sBrightnessTarget[NUM_GI_STRINGS] = {0}; - uint8_t sBrightnessHist[NUM_GI_STRINGS][NUM_BRIGHTNESS + 1]; // including 0 for 'off' - -private: - static GeneralIlluminationWPC *giInstance; -}; - -#endif //_GENERALILLUMINATIONWPC_H diff --git a/src/InputDevices/InputControllerTestButtons.cpp b/src/InputDevices/InputControllerTestButtons.cpp deleted file mode 100644 index 7d8519e..0000000 --- a/src/InputDevices/InputControllerTestButtons.cpp +++ /dev/null @@ -1,21 +0,0 @@ -#include "InputControllerTestButtons.h" - -InputControllerTestButtons::InputControllerTestButtons(EventDispatcher* eD) { - eventDispatcher = eD; - - for (int i = 0; i <= 1; i++) { - button[i] = new Bounce2::Button(); - button[i]->attach(52 + i, INPUT_PULLUP); - button[i]->interval(10); - button[i]->setPressedState(LOW); - } -} - -void InputControllerTestButtons::update() { - for (int i = 0; i <= 1; i++) { - button[i]->update(); - if (button[i]->pressed()) { - eventDispatcher->dispatch(new Event(EVENT_SOURCE_SWITCH, word(0, 203 + i))); - } - } -} diff --git a/src/InputDevices/InputControllerTestButtons.h b/src/InputDevices/InputControllerTestButtons.h deleted file mode 100644 index b30e3f4..0000000 --- a/src/InputDevices/InputControllerTestButtons.h +++ /dev/null @@ -1,28 +0,0 @@ -/* - InputControllerTestButtons.h - Created by Markus Kalkbrenner, 2021. - - Play more pinball! -*/ - -#ifndef InputControllerTestButtons_h -#define InputControllerTestButtons_h - -#include -#include - -#include "../EventDispatcher/Event.h" -#include "../EventDispatcher/EventDispatcher.h" - -class InputControllerTestButtons { -public: - InputControllerTestButtons(EventDispatcher* eD); - - void update(); - -protected: - EventDispatcher* eventDispatcher; - Bounce2::Button* button[2]; -}; - -#endif diff --git a/src/InputDevices/LightMatrix.cpp b/src/InputDevices/LightMatrix.cpp deleted file mode 100644 index 02e6c03..0000000 --- a/src/InputDevices/LightMatrix.cpp +++ /dev/null @@ -1,105 +0,0 @@ -#if defined (__AVR__) || (__avr__) || (__IMXRT1062__) -#include -#endif - -#include "LightMatrix.h" - -// see https://forum.arduino.cc/index.php?topic=398610.0 -LightMatrix* LightMatrix::lightMatrixInstance = NULL; - -void LightMatrix::start() { -#if defined (__AVR__) || (__avr__) || (__IMXRT1062__) - Timer1.attachInterrupt(LightMatrix::_readRow); -#endif -} - -void LightMatrix::stop() { -#if defined (__AVR__) || (__avr__) || (__IMXRT1062__) - Timer1.detachInterrupt(); -#endif -} - -void LightMatrix::_readRow() { - // 74HC165 16bit sampling - uint16_t inData = lightMatrixInstance->sampleInput(); - byte inColMask = (inData >> 8); // LSB is col 0, MSB is col 7 - byte inRowMask = ~(byte)inData; // high means OFF, LSB is row 0, MSB is row 7 - - // evaluate the column reading - // only one bit should be set as only one column can be active at a time - uint32_t inCol = NUM_COLS; - switch (inColMask) { - case 0x01: inCol = 0; break; - case 0x02: inCol = 1; break; - case 0x04: inCol = 2; break; - case 0x08: inCol = 3; break; - case 0x10: inCol = 4; break; - case 0x20: inCol = 5; break; - case 0x40: inCol = 6; break; - case 0x80: inCol = 7; break; - default: - // This may happen if the sample is taken in between column transition. - // Depending on the pinball ROM version the duration of this transition varies. - // On a Whitewater with Home ROM LH6 (contains anti ghosting updates) this - // gap was measured to be around 30us long. - // Machines with anti-ghosting firmware will show a gap with no column enabled - // for a while during the transition while older firmwares might have two - // columns enabled at the same time due to slow transistor deactivation. Both - // cases are caught here. - // See also https://emmytech.com/arcade/led_ghost_busting/index.html for details. - return; - } - - // Update only with a valid input. If the input is invalid the current - // matrix state is left unchanged. - // The matrix is updated only once per original column cycle. The code - // waits for a number of consecutive consistent information before updating the matrix. - if (lightMatrixInstance->updateValid(inCol, inRowMask)) { - //Serial.print(inCol + 1); Serial.print(": "); Serial.println(inRowMask, BIN); - // update the current column - lightMatrixInstance->rows[inCol] = inRowMask; - } -} - -uint16_t LightMatrix::sampleInput() { - // drive CLK and LOAD low - digitalWrite(6, LOW); - digitalWrite(7, LOW); - - // wait some time - uint16_t data = 0; - data += 17; - data -= 3; - - // drive LOAD high to save pin states - digitalWrite(7, HIGH); - - // clock in all data - for (byte i = 0; i < 16; i++) { - data <<= 1; // make way for the new bit - digitalWrite(6, LOW); // CLK low - data |= digitalRead(5); // read data bit - digitalWrite(6, HIGH); // CLK high - } - - return data; -} - -bool LightMatrix::updateValid(byte inCol, byte inRowMask) { - // check if the current column has not been handled already - if (inRowMask != lastRowMask[inCol]) { - // reset the counter when the data changes - consistentSamples[inCol] = 1; - lastRowMask[inCol] = inRowMask; - } - // count number of consecutive samples with consistent data - else if (consistentSamples[inCol] < 255) { - consistentSamples[inCol]++; - } - - if (consistentSamples[inCol] >= SINGLE_UPDATE_CONS) { - return true; - } - - return false; -} diff --git a/src/InputDevices/LightMatrix.h b/src/InputDevices/LightMatrix.h deleted file mode 100644 index 51f929e..0000000 --- a/src/InputDevices/LightMatrix.h +++ /dev/null @@ -1,63 +0,0 @@ -/* - LightMatrix.h - Created by Markus Kalkbrenner, 2020. - Derived from afterglow: 2018-2019 Christoph Schmid - - Play more pinball! -*/ - -#ifndef LightMatrix_h -#define LightMatrix_h - -#include -#if defined (__AVR__) || (__avr__) || (__IMXRT1062__) -#include -#endif - -#include "Matrix.h" -#include "../EventDispatcher/Event.h" - -// Number of consistent data samples required for matrix update -#define SINGLE_UPDATE_CONS 2 - -// local time interval, config A [us] -#define TTAG_INT_A (250) - -class LightMatrix : public Matrix { -public: - LightMatrix(EventDispatcher* eD, byte pf) : Matrix(eD, pf) { - lightMatrixInstance = this; - - eventSource = EVENT_SOURCE_LIGHT; - maxChangesPerRead = MAX_FIELDS_REGISTERED; - - pinMode(5, INPUT); - pinMode(6, OUTPUT); - pinMode(7, OUTPUT); - -#if defined (__AVR__) || (__avr__) || (__IMXRT1062__) - Timer1.initialize(TTAG_INT_A); -#endif - } - - void start(); - - void stop(); - - static void _readRow(); - - uint16_t sampleInput(); - - bool updateValid(byte inCol, byte inRowMask); - - void updateCol(uint32_t col, byte rowMask); - - // remember the last row samples - volatile byte lastRowMask[8] = {0}; - volatile byte consistentSamples[8] = {0}; - -private: - static LightMatrix* lightMatrixInstance; -}; - -#endif diff --git a/src/InputDevices/Matrix.cpp b/src/InputDevices/Matrix.cpp deleted file mode 100644 index e4aaeb7..0000000 --- a/src/InputDevices/Matrix.cpp +++ /dev/null @@ -1,102 +0,0 @@ -#include "Matrix.h" - -Matrix::Matrix(EventDispatcher* eD, byte pf) { - eventDispatcher = eD; - platform = pf; - - setLastColToRead(8); - - for (int i = 0; i < lastColToRead; i++) { - rows[i] = B00000000; - previousRows[i] = B00000000; - } -} - -void Matrix::setLastColToRead(byte last) { - lastColToRead = last; -} - -void Matrix::registerFieldAsEvent(byte row, byte column, byte number) { - if (registeredFieldsCounter < (MAX_FIELDS_REGISTERED - 1)) { - registeredFieldRowCol[++registeredFieldsCounter] = word(row - 1, column - 1); - registeredFieldNum[registeredFieldsCounter] = number; - } -} - -void Matrix::registerAllFieldsAsEvent() { - registeredFieldsCounter = -1; - - if (platform == PLATFORM_WPC) { - for (byte col = 1; col <= lastColToRead; col++) { - for (byte row = 1; row <= 8; row++) { - registerFieldAsEvent(row, col, col * 10 + row); - } - } - } - else if (platform == PLATFORM_DATA_EAST || platform == PLATFORM_SYS11) { - byte number = 0; - for (byte col = 1; col <= lastColToRead; col++) { - for (byte row = 1; row <= 8; row++) { - registerFieldAsEvent(row, col, ++number); - } - } - } -} - -void Matrix::update() { - if (updateDelay > 0) { - uint32_t ms = millis(); - - if (nextUpdate > ms) { - return; - } - - nextUpdate = ms + updateDelay; - } - - byte fieldNum[maxChangesPerRead] = {0}; - byte value[maxChangesPerRead] = {0}; - byte counter = 0; - - for (int col = 0; col < lastColToRead; col++) { - for (int row = 0; row < 8; row++) { - word row_col = word(row, col); - for (byte i = 0; i <= registeredFieldsCounter; i++) { - if (row_col == registeredFieldRowCol[i]) { - byte bit = 1 << row; - if ((rows[col] & bit) != (previousRows[col] & bit)) { - if (counter < maxChangesPerRead) { - fieldNum[counter] = registeredFieldNum[i]; - value[counter++] = (rows[col] & bit) ? 1 : 0; - } - else { - // Too many changes, assume erroneous read. - return; - } - } - } - } - } - previousRows[col] = rows[col]; - } - - if (counter <= maxChangesPerRead) { - for (int i = 0; i < counter; i++) { - eventDispatcher->dispatch( - new Event(eventSource, word(0, fieldNum[i]), value[i]) - ); - } - } -} - -void Matrix::print() { - Serial.print("Matrix "); Serial.println(eventSource); - Serial.println(" 8 7 6 5 4 3 2 1"); - for (int i = 0; i < lastColToRead; i++) { - Serial.print(i + 1); - for (byte mask = 0x80; mask; mask >>= 1) { - Serial.print(mask & rows[i] ? " *" : " -"); - } - Serial.println(); - } -} diff --git a/src/InputDevices/Matrix.h b/src/InputDevices/Matrix.h deleted file mode 100644 index 9bbd7a9..0000000 --- a/src/InputDevices/Matrix.h +++ /dev/null @@ -1,126 +0,0 @@ -/* - Matrix.h - Created by Markus Kalkbrenner, 2020-2021. - - Play more pinball! -*/ - -// WPC matrix numbering: -// -// | C1 | C2 | C3 | C4 | C5 | C6 | C7 | C8 -// ---+----+----+----+----+----+----+----+---- -// R1 | 11 | 21 | 31 | 41 | 51 | 61 | 71 | 81 -// ---+----+----+----+----+----+----+----+---- -// R2 | 12 | 22 | 32 | 42 | 52 | 62 | 72 | 82 -// ---+----+----+----+----+----+----+----+---- -// R3 | 13 | 23 | 33 | 43 | 53 | 63 | 73 | 83 -// ---+----+----+----+----+----+----+----+---- -// R4 | 14 | 24 | 34 | 44 | 54 | 64 | 74 | 84 -// ---+----+----+----+----+----+----+----+---- -// R5 | 15 | 25 | 35 | 45 | 55 | 65 | 75 | 85 -// ---+----+----+----+----+----+----+----+---- -// R6 | 16 | 26 | 36 | 46 | 56 | 66 | 76 | 86 -// ---+----+----+----+----+----+----+----+---- -// R7 | 17 | 27 | 37 | 47 | 57 | 67 | 77 | 87 -// ---+----+----+----+----+----+----+----+---- -// R8 | 18 | 28 | 38 | 48 | 58 | 68 | 78 | 88 - -// WPC matrix numbering (IJ, STTNG, TZ): -// -// | C1 | C2 | C3 | C4 | C5 | C6 | C7 | C8 | C9 -// ---+----+----+----+----+----+----+----+----+---- -// R1 | 11 | 21 | 31 | 41 | 51 | 61 | 71 | 81 | 91 -// ---+----+----+----+----+----+----+----+----+---- -// R2 | 12 | 22 | 32 | 42 | 52 | 62 | 72 | 82 | 92 -// ---+----+----+----+----+----+----+----+----+---- -// R3 | 13 | 23 | 33 | 43 | 53 | 63 | 73 | 83 | 93 -// ---+----+----+----+----+----+----+----+----+---- -// R4 | 14 | 24 | 34 | 44 | 54 | 64 | 74 | 84 | 94 -// ---+----+----+----+----+----+----+----+----+---- -// R5 | 15 | 25 | 35 | 45 | 55 | 65 | 75 | 85 | 95 -// ---+----+----+----+----+----+----+----+----+---- -// R6 | 16 | 26 | 36 | 46 | 56 | 66 | 76 | 86 | 96 -// ---+----+----+----+----+----+----+----+----+---- -// R7 | 17 | 27 | 37 | 47 | 57 | 67 | 77 | 87 | 97 -// ---+----+----+----+----+----+----+----+----+---- -// R8 | 18 | 28 | 38 | 48 | 58 | 68 | 78 | 88 | 98 - -// DE matrix numbering: -// -// | C1 | C2 | C3 | C4 | C5 | C6 | C7 | C8 -// ---+----+----+----+----+----+----+----+---- -// R1 | 1 | 9 | 17 | 25 | 33 | 41 | 49 | 57 -// ---+----+----+----+----+----+----+----+---- -// R2 | 2 | 10 | 18 | 26 | 34 | 42 | 50 | 58 -// ---+----+----+----+----+----+----+----+---- -// R3 | 3 | 11 | 19 | 27 | 35 | 43 | 51 | 59 -// ---+----+----+----+----+----+----+----+---- -// R4 | 4 | 12 | 20 | 28 | 36 | 44 | 52 | 60 -// ---+----+----+----+----+----+----+----+---- -// R5 | 5 | 13 | 21 | 29 | 37 | 45 | 53 | 61 -// ---+----+----+----+----+----+----+----+---- -// R6 | 6 | 14 | 22 | 30 | 38 | 46 | 54 | 62 -// ---+----+----+----+----+----+----+----+---- -// R7 | 7 | 15 | 23 | 31 | 39 | 47 | 55 | 63 -// ---+----+----+----+----+----+----+----+---- -// R8 | 8 | 16 | 24 | 32 | 40 | 48 | 56 | 64 - -// In case of 9 Columns on WPC the 9th column is only read every second run. - -#ifndef Matrix_h -#define Matrix_h - -#include "../PPUC.h" - -#include "../EventDispatcher/Event.h" -#include "../EventDispatcher/EventDispatcher.h" - -#ifndef NUM_COLS -#define NUM_COLS 9 -#endif - -#ifndef MAX_FIELDS_REGISTERED -#define MAX_FIELDS_REGISTERED (NUM_COLS * 8) -#endif - -class Matrix { -public: - Matrix(EventDispatcher* eD, byte pf); - - virtual void start() = 0; - - virtual void stop() = 0; - - void update(); - - void print(); - - void setLastColToRead(byte lastColToRead); - - void registerFieldAsEvent(byte row, byte column, byte number); - - void registerAllFieldsAsEvent(); - - static void _readRow() {} - - volatile byte lastColToRead; - volatile byte rows[NUM_COLS]; - -protected: - EventDispatcher* eventDispatcher; - - char eventSource; - byte maxChangesPerRead = 6; - byte previousRows[NUM_COLS]; - - int registeredFieldsCounter = -1; - word registeredFieldRowCol[MAX_FIELDS_REGISTERED]; - byte registeredFieldNum[MAX_FIELDS_REGISTERED]; - - byte platform; - - byte updateDelay = 0; - uint32_t nextUpdate = 0; -}; - -#endif diff --git a/src/InputDevices/PIN2DMD.cpp b/src/InputDevices/PIN2DMD.cpp deleted file mode 100644 index 216f541..0000000 --- a/src/InputDevices/PIN2DMD.cpp +++ /dev/null @@ -1,56 +0,0 @@ -#include "PIN2DMD.h" - -void PIN2DMD::setSerial(HardwareSerial &reference) { - hwSerial = (HardwareSerial*) &reference; - hwSerial->begin(57600); -} - -void PIN2DMD::update() { - if (hwSerial->available() > 2) { - byte deviceByte = hwSerial->read(); - if (debug) { - Serial.println(deviceByte, DEC); - } - if (deviceByte != 0) { - byte eventByte = hwSerial->read(); - if (debug) { - Serial.println(eventByte, DEC); - } - if (eventByte != 0) { - byte nullByte = hwSerial->read(); - if (debug) { - Serial.println(nullByte, DEC); - } - if (nullByte == 0) { - // Quick explanation: - // The PUP Player understands event IDs that are 8 bits long. But we assume that nobody needs that - // much events. But more than 255, which is the maximum value of 4 bits, are exceeded. The pack for - // STTNG is already very close to this limit. On the other hand we don’t really need a device ID as - // the will be the only Device attached to PIN2DMD. But as the device must must not be "0", we - // agreed on this pattern: - // The „hundreds“ of the decimal Device ID in PIN2DMD are the actual ID. So "100" means Device 1. - // ("200" aka Device 2 is not used at the moment and therefore ignored.) The other two digits are - // added as high byte to the event ID which itself is treated as low byte. - // - // Examples of decimal values to be entered in the PIN2DMD Editor: - // 100 187 => Device 1, Event 187 - // 143 187 => Device 1, Event 11195 (43 decimal is 101011 binary, 187 is 10111011, binary 101011 10111011 is 11195 decimal) - // 200 123 => Device 2, Event 123 (Device2 is currently not used) - // 243 187 => Device 2, Event 11195 (Device2 is currently not used) - // - // 100 255 => Device 1, Event 255 - // 101 001 => Device 1, Event 257 - // - // This looks complicated, but I consider this to be the smartest solution to close the gap between - // PIN2DMD 4bit event IDs and PUPPack 8bit event IDs that remains somehow „readable“ in the PIN2DMD - // Editor. - eventDispatcher->dispatch(new Event(EVENT_SOURCE_DMD, word((deviceByte & 0b1111111) - 100, eventByte))); - } - } - } - } -} - -void PIN2DMD::setDebug(bool value) { - debug = value; -} diff --git a/src/InputDevices/PIN2DMD.h b/src/InputDevices/PIN2DMD.h deleted file mode 100644 index 817af4e..0000000 --- a/src/InputDevices/PIN2DMD.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - PIN2DMD.h - Created by Markus Kalkbrenner, 2020-2021. - - Play more pinball! -*/ - -#ifndef PIN2DMD_h -#define PIN2DMD_h - -#include - -#include "../EventDispatcher/Event.h" -#include "../EventDispatcher/EventDispatcher.h" - -class PIN2DMD { -public: - PIN2DMD(EventDispatcher* eD) { - eventDispatcher = eD; - } - - void setSerial(HardwareSerial &reference); - - void update(); - - void setDebug(bool value); - -private: - EventDispatcher* eventDispatcher; - - HardwareSerial* hwSerial; - - bool debug = false; - -}; - -#endif diff --git a/src/InputDevices/Solenoids.cpp b/src/InputDevices/Solenoids.cpp deleted file mode 100644 index de9bada..0000000 --- a/src/InputDevices/Solenoids.cpp +++ /dev/null @@ -1,152 +0,0 @@ -#include "Solenoids.h" - -Solenoids::Solenoids(int controllerType, EventDispatcher* ed) { - eventDispatcher = ed; - - if (controllerType == CONTROLLER_MEGA_ALL_INPUT) { - pins[0] = 8; - pins[1] = 9; - pins[2] = 10; - pins[3] = 11; - pins[4] = 12; - pins[5] = 13; - pins[6] = 30; - pins[7] = 31; - pins[8] = 32; - pins[9] = 33; - pins[10] = 34; - pins[11] = 35; - pins[12] = 36; - pins[13] = 37; - pins[14] = 38; - pins[15] = 39; - pins[16] = 40; - pins[17] = 41; - pins[18] = 42; - pins[19] = 43; - pins[20] = 44; - pins[21] = 45; - pins[22] = 46; - pins[23] = 47; - pins[24] = 48; - pins[25] = 49; - } - - for (int i = 0; i < NUM_PINS; i++) { - pinMode(pins[i], INPUT); - pinStates[i] = false; - previousPinStates[i] = false; - registeredNum[i] = 0; - } -} - -void Solenoids::update() { - for (int i = 0; i < NUM_PINS; i++) { - previousPinStates[i] = pinStates[i]; - pinStates[i] = digitalRead(pins[i]) == HIGH; - if ((registeredNum[i] != 0) && (previousPinStates[i] != pinStates[i])) { - eventDispatcher->dispatch(new Event(EVENT_SOURCE_SOLENOID, word(0, registeredNum[i]), pinStates[i])); - } - } -} - -void Solenoids::registerJ3(byte pin, byte number) { - if (pin == 1) { registeredNum[0] = number; } - else if (pin == 2) { registeredNum[1] = number; } - else if (pin == 3) { registeredNum[2] = number; } - else if (pin == 4) { registeredNum[3] = number; } - else if (pin == 5) { registeredNum[4] = number; } - // 6 is key - else if (pin == 7) { registeredNum[5] = number; } -} - -void Solenoids::registerJ4(byte pin, byte number) { - if (pin == 1) { registeredNum[0] = number; } - else if (pin == 2) { registeredNum[1] = number; } - // 3 is key - else if (pin == 4) { registeredNum[3] = number; } - else if (pin == 5) { registeredNum[4] = number; } - else if (pin == 6) { registeredNum[2] = number; } - else if (pin == 7) { registeredNum[5] = number; } -} - -void Solenoids::registerJ122(byte pin, byte number) { - if (pin == 1) { registeredNum[6] = number; } - else if (pin == 2) { registeredNum[7] = number; } - else if (pin == 3) { registeredNum[8] = number; } - else if (pin == 4) { registeredNum[9] = number; } - else if (pin == 5) { registeredNum[10] = number; } - else if (pin == 6) { registeredNum[11] = number; } - // 7 is key - else if (pin == 8) { registeredNum[12] = number; } - else if (pin == 9) { registeredNum[13] = number; } -} - -void Solenoids::registerJ123(byte pin, byte number) { - if (pin == 1) { registeredNum[6] = number; } - // 2 is key - else if (pin == 3) { registeredNum[7] = number; } - else if (pin == 4) { registeredNum[8] = number; } - else if (pin == 5) { registeredNum[9] = number; } -} - -void Solenoids::registerJ124(byte pin, byte number) { - if (pin == 1) { registeredNum[6] = number; } - else if (pin == 2) { registeredNum[7] = number; } - else if (pin == 3) { registeredNum[8] = number; } - // 4 is key - else if (pin == 5) { registeredNum[9] = number; } -} - - -void Solenoids::registerJ125(byte pin, byte number) { - if (pin == 1) { registeredNum[14] = number; } - else if (pin == 2) { registeredNum[15] = number; } - else if (pin == 3) { registeredNum[16] = number; } - // 4 is key - else if (pin == 5) { registeredNum[17] = number; } - else if (pin == 6) { registeredNum[18] = number; } - else if (pin == 7) { registeredNum[19] = number; } - else if (pin == 8) { registeredNum[20] = number; } - else if (pin == 9) { registeredNum[21] = number; } -} - -void Solenoids::registerJ126(byte pin, byte number) { - if (pin == 1) { registeredNum[14] = number; } - else if (pin == 2) { registeredNum[15] = number; } - else if (pin == 3) { registeredNum[16] = number; } - else if (pin == 4) { registeredNum[17] = number; } - else if (pin == 5) { registeredNum[18] = number; } - else if (pin == 6) { registeredNum[19] = number; } - else if (pin == 7) { registeredNum[20] = number; } - else if (pin == 8) { registeredNum[21] = number; } - // 9 is key - else if (pin == 10) { registeredNum[22] = number; } - else if (pin == 11) { registeredNum[23] = number; } - else if (pin == 12) { registeredNum[24] = number; } - else if (pin == 13) { registeredNum[25] = number; } -} - -void Solenoids::registerJ110(byte pin, byte number) { -} - -void Solenoids::registerJ111(byte pin, byte number) { -} - -void Solenoids::registerJ9(byte pin, byte number) { -} - -void Solenoids::registerP11(byte pin, byte number) { -} - -void Solenoids::registerP12(byte pin, byte number) { -} - -//helps with debugging -void Solenoids::print() { - for (int i = 0; i < NUM_PINS; i++) { - Serial.print(pinStates[i]); - Serial.print(" "); - } - Serial.println(); -} diff --git a/src/InputDevices/Solenoids.h b/src/InputDevices/Solenoids.h deleted file mode 100644 index a419b8b..0000000 --- a/src/InputDevices/Solenoids.h +++ /dev/null @@ -1,55 +0,0 @@ -/* - Solenoids.h - Created by Markus Kalkbrenner, 2020-2021. - - Play more pinball! -*/ -#ifndef SOLENOIDS_h -#define SOLENOIDS_h - -#define NUM_PINS 26 - -#include "../PPUC.h" - -#include "../EventDispatcher/Event.h" -#include "../EventDispatcher/EventDispatcher.h" - -class Solenoids { -public: - //Constructor - Solenoids(int controllerType, EventDispatcher* eD); - - // WPC - void registerJ3(byte pin, byte number); // A16100 8-Driver PCB Assembly - void registerJ4(byte pin, byte number); // A16100 8-Driver PCB Assembly - void registerJ122(byte pin, byte number); - void registerJ123(byte pin, byte number); - void registerJ124(byte pin, byte number); - void registerJ125(byte pin, byte number); - void registerJ126(byte pin, byte number); - - // WPC95 - void registerJ110(byte pin, byte number); - void registerJ111(byte pin, byte number); - - // Sega - void registerJ9(byte pin, byte number); - void registerP11(byte pin, byte number); - void registerP12(byte pin, byte number); - - void update(); - - void print(); - -private: - EventDispatcher* eventDispatcher; - - int pins[NUM_PINS]; - bool pinStates[NUM_PINS]; - byte previousPinStates[NUM_PINS]; - - byte registeredNum[NUM_PINS]; -}; - - -#endif diff --git a/src/InputDevices/SwitchMatrix.cpp b/src/InputDevices/SwitchMatrix.cpp deleted file mode 100644 index 0501d20..0000000 --- a/src/InputDevices/SwitchMatrix.cpp +++ /dev/null @@ -1,75 +0,0 @@ -#include "SwitchMatrix.h" - -// see https://forum.arduino.cc/index.php?topic=398610.0 -SwitchMatrix* SwitchMatrix::switchMatrixInstance = NULL; - -void SwitchMatrix::start() { - attachInterrupt(digitalPinToInterrupt(CS_ODD), SwitchMatrix::_readRowForOddColumn, RISING); -} - -void SwitchMatrix::stop() { - detachInterrupt(digitalPinToInterrupt(CS_ODD)); - detachInterrupt(digitalPinToInterrupt(CS_EVEN)); -} - -void SwitchMatrix::_readRowForOddColumn() { - //Serial.println("CS_ODD"); - switchMatrixInstance->readRow(CS_ODD); -} - -void SwitchMatrix::_readRowForEvenColumn() { - //Serial.println("CS_EVEN"); - switchMatrixInstance->readRow(CS_EVEN); -} - -void SwitchMatrix::readRow(int pin) { - // Immediately turn off further interrupts. - detachInterrupt(digitalPinToInterrupt(pin)); - - delayMicroseconds(rowReadDelay); - - if (pin == CS_ODD && digitalRead(CS_X) == LOW) { - //Serial.println("CS_X"); - columnCounter = 0; - } - - if (columnCounter >= 0 && columnCounter < lastColToRead) { - //Serial.println(switchMatrixInstance->columnCounter, DEC); - -#if defined (__AVR_ATmega2560__) - // Read row return at PIN 22 - 29 three times and use the majority of bits. - byte a = PINA; - delayMicroseconds(4); - byte b = PINA; - delayMicroseconds(4); - byte c = PINA; -#else - // The Switch Matrix adapter currently requires an Arduino. - byte a = 0; - byte b = 0; - byte c = 0; -#endif - - if (columnCounter > 0 || - // The first column is only valid, if CS_X is still LOW. Otherwise it's noise. - (columnCounter == 0 && digitalRead(CS_X) == LOW) - ) { - //Serial.println("READ"); - rows[columnCounter++] |= ((a & b) | (b & c) | (c & a)) ^ 0b11111111; - } - else { - columnCounter = 255; - } - } - - if (columnCounter >= lastColToRead) { - columnCounter = 255; - } - - if (pin == CS_ODD && columnCounter >= 0 && columnCounter < lastColToRead) { - attachInterrupt(digitalPinToInterrupt(CS_EVEN), SwitchMatrix::_readRowForEvenColumn, RISING); - } - else { - attachInterrupt(digitalPinToInterrupt(CS_ODD), SwitchMatrix::_readRowForOddColumn, RISING); - } -} diff --git a/src/InputDevices/SwitchMatrix.h b/src/InputDevices/SwitchMatrix.h deleted file mode 100644 index 76ffa3e..0000000 --- a/src/InputDevices/SwitchMatrix.h +++ /dev/null @@ -1,74 +0,0 @@ -/* - SwitchMatrix.h. - Created by Markus Kalkbrenner, 2020-2021. - - Play more pinball! -*/ - -#ifndef SwitchMatrix_h -#define SwitchMatrix_h - -#include - -#include "Matrix.h" -#include "../EventDispatcher/Event.h" -#include "../EventDispatcher/EventDispatcher.h" - -#define CS_ODD 3 -#define CS_EVEN 2 -#define CS_X 4 - -class SwitchMatrix : public Matrix { -public: - SwitchMatrix(EventDispatcher* eD, byte pf) : Matrix(eD, pf) { - switchMatrixInstance = this; - - eventSource = EVENT_SOURCE_SWITCH; - - if (platform == PLATFORM_WPC) { - // Read rows some micro seconds after column strobe signal. - rowReadDelay = 4; - - // On WPC the switches are read every 2ms. Ensure that we have a complete read before sending next events. - updateDelay = 3; - } - else if (platform == PLATFORM_DATA_EAST) { - // @todo - } - else if (platform == PLATFORM_SYS11) { - // @todo - } - - maxChangesPerRead = 3; - - pinMode(CS_ODD, INPUT); - pinMode(CS_EVEN, INPUT); - pinMode(CS_X, INPUT); - - pinMode(22, INPUT); - pinMode(23, INPUT); - pinMode(24, INPUT); - pinMode(25, INPUT); - pinMode(26, INPUT); - pinMode(27, INPUT); - pinMode(28, INPUT); - pinMode(29, INPUT); - } - - void start(); - - void stop(); - - void readRow(int pin); - static void _readRowForOddColumn(); - static void _readRowForEvenColumn(); - -protected: - byte columnCounter = 255; - int rowReadDelay = 0; - -private: - static SwitchMatrix* switchMatrixInstance; -}; - -#endif diff --git a/src/PPUC.h b/src/PPUC.h index 8848472..27b065d 100644 --- a/src/PPUC.h +++ b/src/PPUC.h @@ -1,4 +1,4 @@ -/* +/** PPUC.h Created by Markus Kalkbrenner. */ @@ -6,22 +6,18 @@ #ifndef PPUC_h #define PPUC_h +#define FIRMWARE_VERSION_MAJOR 0 // X Digits +#define FIRMWARE_VERSION_MINOR 2 // Max 2 Digits +#define FIRMWARE_VERSION_PATCH 0 // Max 2 Digits + #include -#define CONTROLLER_MEGA_ALL_INPUT 1 -#define CONTROLLER_TEENSY_OUTPUT 10 -#define CONTROLLER_TEENSY_OUTPUT_2 11 -#define CONTROLLER_NANO_PIN2DMD_OUTPUT 20 -#define CONTROLLER_16_8_1 30 +#include "PPUCPlatforms.h" +#include "PPUCTimings.h" -#define PLATFORM_WPC 1 -#define PLATFORM_DATA_EAST 2 -#define PLATFORM_SYS11 3 -#define PLATFORM_LIBPINMAME 100 +#define CONTROLLER_16_8_1 1 -typedef unsigned char UINT8; -typedef unsigned short UINT16; -typedef unsigned int UINT32; +#define RS485_MODE_PIN 2 #include diff --git a/src/PPUCPlatforms.h b/src/PPUCPlatforms.h new file mode 100644 index 0000000..4ef72c3 --- /dev/null +++ b/src/PPUCPlatforms.h @@ -0,0 +1,29 @@ +/** + PPUCPlatforms.h + Created by Markus Kalkbrenner. +*/ + +#ifndef PPUC_PLATFORMS_h +#define PPUC_PLATFORMS_h + +#define PLATFORM_DATA_EAST 2 + +// Williams +#define PLATFORM_WPC 1 +#define PLATFORM_SYS3 3 +#define PLATFORM_SYS4 4 +#define PLATFORM_SYS6 6 +#define PLATFORM_SYS7 7 +#define PLATFORM_SYS11 11 + +#define PLATFORM_BALLY35 35 + +// Stern +#define PLATFORM_WHITESTAR 50 +#define PLATFORM_SAM 51 + +#define PLATFORM_CAPCOM 99 + +#define PLATFORM_LIBPINMAME 100 + +#endif diff --git a/src/PPUCProtocolV2.h b/src/PPUCProtocolV2.h new file mode 100644 index 0000000..ef0e1f3 --- /dev/null +++ b/src/PPUCProtocolV2.h @@ -0,0 +1,218 @@ +#pragma once + +#include +#include + +namespace ppuc { +namespace v2 { + +constexpr uint32_t kBaudRate = 250000; + +constexpr uint8_t kSyncByte = 0xA5; +constexpr uint8_t kNoBoard = 0xFF; +constexpr uint8_t kMaxBoards = 8; + +// Bitmaps are indexed by global device number: bit N => device number N. +// Runtime counts are configured per game and announced with SetupFrame. +constexpr uint16_t kDefaultCoilBits = 24; +constexpr uint16_t kDefaultLampBits = 64; +constexpr uint16_t kDefaultSwitchBits = 64; +constexpr uint16_t kMaxCoilBits = 256; +constexpr uint16_t kMaxLampBits = 256; +constexpr uint16_t kMaxSwitchBits = 256; + +constexpr size_t BitsToBytes(uint16_t bits) { return (bits + 7u) / 8u; } + +constexpr size_t kDefaultCoilBytes = BitsToBytes(kDefaultCoilBits); +constexpr size_t kDefaultLampBytes = BitsToBytes(kDefaultLampBits); +constexpr size_t kDefaultSwitchBytes = BitsToBytes(kDefaultSwitchBits); +constexpr size_t kMaxCoilBytes = BitsToBytes(kMaxCoilBits); +constexpr size_t kMaxLampBytes = BitsToBytes(kMaxLampBits); +constexpr size_t kMaxSwitchBytes = BitsToBytes(kMaxSwitchBits); + +constexpr size_t kHeaderBytes = 4; +constexpr size_t kCrcBytes = 2; + +enum FrameType : uint8_t { + kFrameOutputState = 0x01, + kFrameSwitchState = 0x02, + kFrameHeartbeat = 0x03, + kFrameError = 0x04, + kFrameSetup = 0x05, + kFrameMapping = 0x06, + kFrameReset = 0x07, + kFrameConfig = 0x08, + kFrameSwitchNoChange = 0x09, +}; + +enum MappingDomain : uint8_t { + kDomainCoil = 0x01, + kDomainLamp = 0x02, + kDomainSwitch = 0x03, +}; + +enum FrameFlag : uint8_t { + kFlagNone = 0x00, + kFlagKeyframe = 0x10, + kFlagDelta = 0x20, + kFlagError = 0x80, +}; + +struct FrameHeader { + uint8_t sync; + uint8_t typeAndFlags; + uint8_t nextBoard; + uint8_t sequence; +}; + +struct SetupPayload { + uint16_t coilBits; + uint16_t lampBits; + uint16_t switchBits; +}; + +struct MappingPayload { + uint8_t domain; + uint8_t reserved; + uint16_t index; + uint16_t number; +}; + +struct ConfigPayload { + uint8_t boardId; + uint8_t topic; + uint8_t index; + uint8_t key; + uint32_t value; +}; + +struct OutputPayload { + // Only first BitsToBytes(coilBits/lampBits) bytes are used at runtime. + uint8_t coils[kMaxCoilBytes]; + uint8_t lamps[kMaxLampBytes]; +}; + +struct SwitchPayload { + // Only first BitsToBytes(switchBits) bytes are used at runtime. + uint8_t switches[kMaxSwitchBytes]; +}; + +struct SetupFrame { + FrameHeader header; + SetupPayload payload; + uint16_t crc; +}; + +struct MappingFrame { + FrameHeader header; + MappingPayload payload; + uint16_t crc; +}; + +struct ConfigFrame { + FrameHeader header; + ConfigPayload payload; + uint16_t crc; +}; + +struct OutputStateFrame { + FrameHeader header; + OutputPayload payload; + uint16_t crc; +}; + +struct SwitchStateFrame { + FrameHeader header; + SwitchPayload payload; + uint16_t crc; +}; + +constexpr size_t kSetupPayloadBytes = sizeof(SetupPayload); +constexpr size_t kMappingPayloadBytes = sizeof(MappingPayload); +constexpr size_t kConfigPayloadBytes = sizeof(ConfigPayload); +constexpr size_t kOutputPayloadBytes = sizeof(OutputPayload); +constexpr size_t kSwitchPayloadBytes = sizeof(SwitchPayload); +constexpr size_t kResetFrameBytes = kHeaderBytes + kCrcBytes; +constexpr size_t kSetupFrameBytes = sizeof(SetupFrame); +constexpr size_t kMappingFrameBytes = sizeof(MappingFrame); +constexpr size_t kConfigFrameBytes = sizeof(ConfigFrame); +constexpr size_t kOutputFrameBytes = sizeof(OutputStateFrame); +constexpr size_t kSwitchFrameBytes = sizeof(SwitchStateFrame); + +struct RuntimeConfig { + uint16_t coilBits = kDefaultCoilBits; + uint16_t lampBits = kDefaultLampBits; + uint16_t switchBits = kDefaultSwitchBits; +}; + +inline bool IsValidRuntimeConfig(const RuntimeConfig& cfg) { + return cfg.coilBits > 0 && cfg.coilBits <= kMaxCoilBits && cfg.lampBits > 0 && + cfg.lampBits <= kMaxLampBits && cfg.switchBits > 0 && + cfg.switchBits <= kMaxSwitchBits; +} + +inline size_t OutputPayloadBytes(const RuntimeConfig& cfg) { + return BitsToBytes(cfg.coilBits) + BitsToBytes(cfg.lampBits); +} + +inline size_t SwitchPayloadBytes(const RuntimeConfig& cfg) { + return BitsToBytes(cfg.switchBits); +} + +inline size_t OutputFrameBytes(const RuntimeConfig& cfg) { + return kHeaderBytes + OutputPayloadBytes(cfg) + kCrcBytes; +} + +inline size_t SwitchFrameBytes(const RuntimeConfig& cfg) { + return kHeaderBytes + SwitchPayloadBytes(cfg) + kCrcBytes; +} + +inline uint16_t Crc16Ccitt(const uint8_t* data, size_t len) { + uint16_t crc = 0xFFFF; + for (size_t i = 0; i < len; ++i) { + crc ^= static_cast(data[i]) << 8; + for (uint8_t bit = 0; bit < 8; ++bit) { + if (crc & 0x8000) { + crc = static_cast((crc << 1) ^ 0x1021); + } else { + crc <<= 1; + } + } + } + return crc; +} + +inline uint8_t ComposeTypeAndFlags(FrameType type, uint8_t flags) { + return static_cast(static_cast(type) | flags); +} + +inline FrameType ExtractType(uint8_t typeAndFlags) { + return static_cast(typeAndFlags & 0x0F); +} + +inline uint8_t ExtractFlags(uint8_t typeAndFlags) { + return static_cast(typeAndFlags & 0xF0); +} + +inline bool IsValidBoard(uint8_t board) { + return board == kNoBoard || board < kMaxBoards; +} + +inline void SetBitmapBit(uint8_t* bitmap, uint16_t number, bool on) { + const uint16_t byteIndex = number / 8u; + const uint8_t bitMask = static_cast(1u << (number % 8u)); + if (on) { + bitmap[byteIndex] |= bitMask; + } else { + bitmap[byteIndex] &= static_cast(~bitMask); + } +} + +inline bool GetBitmapBit(const uint8_t* bitmap, uint16_t number) { + const uint16_t byteIndex = number / 8u; + const uint8_t bitMask = static_cast(1u << (number % 8u)); + return (bitmap[byteIndex] & bitMask) != 0; +} + +} // namespace v2 +} // namespace ppuc diff --git a/src/PPUCTimings.h b/src/PPUCTimings.h new file mode 100644 index 0000000..821bc01 --- /dev/null +++ b/src/PPUCTimings.h @@ -0,0 +1,17 @@ +/** + PPUCTimings.h + Created by Markus Kalkbrenner. +*/ + +#ifndef PPUC_TIMINGS_h +#define PPUC_TIMINGS_h + +#define WAIT_FOR_EFFECT_CONTROLLER_RESET 3000 // 3 seconds +#define WAIT_FOR_SERIAL_DEBUGGER_TIMEOUT 1000 // 1 second +#define WAIT_FOR_IO_BOARD_BOOT 1000 // 1 second + +#define WAIT_FOR_IO_BOARD_RESET \ + (WAIT_FOR_SERIAL_DEBUGGER_TIMEOUT + WAIT_FOR_EFFECT_CONTROLLER_RESET + \ + WAIT_FOR_IO_BOARD_BOOT) // 5 seconds + +#endif diff --git a/src/VisualPinball/PUPComLink.cpp b/src/VisualPinball/PUPComLink.cpp deleted file mode 100644 index 319b6df..0000000 --- a/src/VisualPinball/PUPComLink.cpp +++ /dev/null @@ -1,62 +0,0 @@ -#include "PUPComLink.h" - -void PUPComLink::setSerial(HardwareSerial &reference) { - hwSerial = (HardwareSerial*) &reference; - hwSerial->begin(115200, SERIAL_8N1); -} - -void PUPComLink::handleEvent(Event* event) { - write(PUP_POST_EVENT_COMMAND, event->sourceId, event->eventId, event->value); -} - -void PUPComLink::postEvent(char msgtype, int msgindex, int msgvalue) { - write(PUP_POST_EVENT_COMMAND, msgtype, word(msgindex), word(msgvalue)); -} - -void PUPComLink::customCommand(char msgtype, int msgindex, int msgvalue) { - write(PUP_CUSTOM_COMMAND, msgtype, word(msgindex), word(msgvalue)); -} - -void PUPComLink::setVolume(int volume) { - write(PUP_CUSTOM_COMMAND, PUP_CUSTOM_VOLUME, word(0), word(volume)); -} - -void PUPComLink::startBatch(int id) { - write(PUP_CUSTOM_COMMAND, PUP_CUSTOM_BATCH, word(0), word(id)); -} - -void PUPComLink::restart() { - write(PUP_CUSTOM_COMMAND, PUP_CUSTOM_RESTART, word(0), word(1)); -} - -void PUPComLink::shutdown() { - write(PUP_CUSTOM_COMMAND, PUP_CUSTOM_SHUTDOWN, word(0), word(1)); -} - -int PUPComLink::available() { - return hwSerial->available(); -} - -byte PUPComLink::read() { - return hwSerial->read(); -} - -void PUPComLink::write(byte command, char msgtype, word msgindex, word msgvalue) { - // Send to PUP Com Link. But only if there's room left in write buffer. Otherwise the program will be blocked. The - // buffer gets full if the data is not fetched by PUP Com Link for any reason. - // @todo Possible optimization to check hwSerial->availableForWrite() >= 8 failed on Arduino for unknown reason. - //if (hwSerial->availableForWrite() >= 8) { - byte msg[8]; - - msg[0] = command; - msg[1] = msgtype; - msg[2] = highByte(msgindex); - msg[3] = lowByte(msgindex); - msg[4] = highByte(msgvalue); - msg[5] = lowByte(msgvalue); - msg[6] = msg[0] ^ msg[1] ^ msg[2] ^ msg[3] ^ msg[4] ^ msg[5]; - msg[7] = PUP_EOF; - - hwSerial->write(msg, 8); - //} -} diff --git a/src/VisualPinball/PUPComLink.h b/src/VisualPinball/PUPComLink.h deleted file mode 100644 index 727e8e2..0000000 --- a/src/VisualPinball/PUPComLink.h +++ /dev/null @@ -1,60 +0,0 @@ -/* - PUPComLink.h - Created by Markus Kalkbrenner, 2020-2021. - - Play more pinball! -*/ - -#ifndef PUPCOMLINK_h -#define PUPCOMLINK_h - -#include - -#include "../EventDispatcher/Event.h" -#include "../EventDispatcher/EventDispatcher.h" - -#define PUP_POST_EVENT_COMMAND 80 // "P" -#define PUP_CUSTOM_COMMAND 67 // "C" -#define PUP_EOF 13 -#define PUP_VALUE_ON 1 -#define PUP_CUSTOM_VOLUME 86 // "V" -#define PUP_CUSTOM_BATCH 66 // "B" -#define PUP_CUSTOM_RESTART 82 // "R" -#define PUP_CUSTOM_SHUTDOWN 83 // "S" - -class PUPComLink : public EventListener { -public: - PUPComLink() {} - - void setSerial(HardwareSerial &reference); - - void handleEvent(Event* event); - - void handleEvent(ConfigEvent* event) {} - - void postEvent(char msgtype, int msgindex, int msgvalue); - - void customCommand(char msgtype, int msgindex, int msgvalue); - - void setVolume(int volume); - - /** - * Starts "id".bat in the "pinupsystem\launch" folder - */ - void startBatch(int id); - - void restart(); - - void shutdown(); - - int available(); - - byte read(); - -protected: - void write(byte command, char msgtype, word msgindex, word msgvalue); - - HardwareSerial* hwSerial; -}; - -#endif diff --git a/src/VisualPinball/VPXComLink.cpp b/src/VisualPinball/VPXComLink.cpp deleted file mode 100644 index 7d70e96..0000000 --- a/src/VisualPinball/VPXComLink.cpp +++ /dev/null @@ -1,51 +0,0 @@ -#if defined(__IMXRT1062__) // Teensy 4.1 -#include -#endif - -#include "VPXComLink.h" - -void VPXComLink::update() { - if (Serial.available() >= 6) { - byte startByte = Serial.read(); - if (startByte == 255) { - byte sourceId = Serial.read(); - if (sourceId != 0) { - word eventId = word(Serial.read(), Serial.read()); - if (eventId != 0) { - byte value = Serial.read(); - byte stopByte = Serial.read(); - if (stopByte == 255) { - eventDispatcher->dispatch(new Event((char) sourceId, eventId, value)); - } - } - } - } - } -} - -void VPXComLink::handleEvent(Event* event) { - // Add this to platformio.ini to get a Keyboard: - // build_flags = -D USB_SERIAL_HID -#ifdef Keyboard - if (event->sourceId == EVENT_SOURCE_SWITCH) { - if (platform == PLATFORM_SYS11) { - switch (event->eventId) { - case 63: - if (event->value) { - Keyboard.press(KEY_END); - } else { - Keyboard.release(KEY_END); - } - break; - case 64: - if (event->value) { - Keyboard.press(KEY_PAGE_UP); - } else { - Keyboard.release(KEY_PAGE_UP); - } - break; - } - } - } -#endif -} diff --git a/src/VisualPinball/VPXComLink.h b/src/VisualPinball/VPXComLink.h deleted file mode 100644 index 6fff299..0000000 --- a/src/VisualPinball/VPXComLink.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - VPXComLink.h - Created by Markus Kalkbrenner, 2021. - - Play more pinball! -*/ - -#ifndef VPXCOMLINK_h -#define VPXCOMLINK_h - -#include - -#include "../EventDispatcher/Event.h" -#include "../EventDispatcher/EventDispatcher.h" -#include "../EventDispatcher/EventListener.h" - -class VPXComLink : public EventListener { -public: - VPXComLink(EventDispatcher* eD, byte pf) { - eventDispatcher = eD; - platform = pf; - } - - void update(); - - void handleEvent(Event* event); - - void handleEvent(ConfigEvent* event) {} - -private: - EventDispatcher* eventDispatcher; - - byte platform; - -}; - -#endif diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..ae525ed --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,117 @@ +// Markus Kalkbrenner 2023-2025 + +// set to officially supported 200MHz clock +// @see SYS_CLK_MHZ https://github.com/raspberrypi/pico-sdk/releases/tag/2.1.1 +#define SYS_CLK_MHZ 200 + +#include "EffectsController.h" +#include "EventDispatcher/CrossLinkDebugger.h" +#include "IOBoardController.h" +#include "PPUC.h" +#include "PPUCProtocolV2.h" +#include "RPi_Pico_TimerInterrupt.h" + +IOBoardController ioBoardController(CONTROLLER_16_8_1); + +// Platform will be adjusted by ConfigEvent. +EffectsController effectsController(CONTROLLER_16_8_1, PLATFORM_LIBPINMAME); + +RPI_PICO_Timer ITimer(1); + +volatile uint32_t watchdog_ms = millis(); +volatile uint32_t lastPoll_ms = millis(); + +// Turn off all High Power Outputs in case the main loop has not finished in 1 +// second (or 2 seconds in edge cases). +bool watchdog(struct repeating_timer *t) { + uint32_t ms = millis(); + if ((ms - watchdog_ms) > 1000 || (ms - lastPoll_ms) > 3000) { + for (int i = 19; i <= 26; i++) digitalWrite(i, LOW); + } + + return true; +} + +bool usb_debugging = false; +bool core_0_initialized = false; + +// Each controller will be bound to its own core and has it's own +// EventDispatcher. Only the EventDispatcher of IOBoardController +// is attached to RS485. But both EventDispatchers must share the +// same MultiCoreCrosslink to send and receive events between +// both cores. + +void setup() { + // Overclock according to Raspberry Pi Pico SDK recommendations. + set_sys_clock_khz(SYS_CLK_KHZ, true); + + // RS485 connection. + Serial1.end(); // Deactivete UART to empty TX FIFO after reboot + delay(5); + pinMode(RS485_MODE_PIN, OUTPUT); + digitalWrite(RS485_MODE_PIN, LOW); // Read mode + delay(5); + Serial1.begin(ppuc::v2::kBaudRate); + // Empty RX FIFO after reboot + while (Serial1.available()) { + Serial1.read(); + } + + usb_debugging = ioBoardController.isDebug(); + + if (usb_debugging) { + pinMode(LED_BUILTIN, OUTPUT); + Serial.begin(115200); + delay(100); + // Wait for a serial connection of a debugger via USB CDC. + // The Pico implements USB itself so special care must be taken. Use + // while(!Serial){} in the setup() code before printing anything so that + // it waits for the USB connection to be established. + // https://community.platformio.org/t/serial-monitor-not-working/1512/25 + while (!Serial) { + digitalWrite(LED_BUILTIN, HIGH); + delay(100); + digitalWrite(LED_BUILTIN, LOW); + delay(100); + digitalWrite(LED_BUILTIN, HIGH); + delay(100); + digitalWrite(LED_BUILTIN, LOW); + delay(1000); + } + + Serial.println("USB Serial debugging active."); + // ioBoardController.eventDispatcher()->addListener(new CrossLinkDebugger()); + } else { + // The watchdog interferes with the USB debuging, so only start it + // if USB debugging is not active. + if (!ITimer.attachInterruptInterval(1000000, watchdog)) { + // @todo + } + } + + core_0_initialized = true; + rp2040.restartCore1(); +} + +void setup1() { + while (!core_0_initialized) { + } + + if (usb_debugging) { + delay(10); + effectsController.eventDispatcher()->addListener(new CrossLinkDebugger()); + } + + effectsController.eventDispatcher()->setMultiCoreCrossLink( + ioBoardController.eventDispatcher()->getMultiCoreCrossLink()); + + effectsController.start(); +} + +void loop() { + watchdog_ms = millis(); + ioBoardController.update(); + lastPoll_ms = ioBoardController.eventDispatcher()->getLastPoll(); +} + +void loop1() { effectsController.update(); } diff --git a/test/DistributionController/platformio.ini b/test/DistributionController/platformio.ini deleted file mode 100644 index 2034bc3..0000000 --- a/test/DistributionController/platformio.ini +++ /dev/null @@ -1,23 +0,0 @@ -; PlatformIO Project Configuration File -; -; Build options: build flags, source filter -; Upload options: custom upload port, speed and extra flags -; Library options: dependencies, extra library storages -; Advanced options: extra scripting -; -; Please visit documentation for the other options and examples -; https://docs.platformio.org/page/projectconf.html - -[env:teensy41] -platform = teensy -board = teensy41 -framework = arduino -lib_extra_dirs = - ../.. -lib_deps = - mkalkbrenner/WavePWM - mkalkbrenner/WS2812Serial - kitesurfer1404/WS2812FX - thomasfredericks/Bounce2 - https://github.com/PaulStoffregen/TimerOne.git#master -build_flags = -D USB_SERIAL_HID diff --git a/test/DistributionController/src/main.cpp b/test/DistributionController/src/main.cpp deleted file mode 100644 index 4440379..0000000 --- a/test/DistributionController/src/main.cpp +++ /dev/null @@ -1,33 +0,0 @@ -// Markus Kalkbrenner 2022 -// Note to self: Play more pinball! - -#include - -#include "DistributionController.h" -#include - -DistributionController distributionController("0.1.0", PLATFORM_SYS11); - -void setup() { - Serial.begin(9600); // USB is always 12 Mbit/sec - - distributionController.eventDispatcher()->addCrossLinkSerial(Serial1); - distributionController.eventDispatcher()->addCrossLinkSerial(Serial2); - distributionController.eventDispatcher()->addCrossLinkSerial(Serial3); - distributionController.eventDispatcher()->addCrossLinkSerial(Serial4); - distributionController.eventDispatcher()->addCrossLinkSerial(Serial5); - distributionController.eventDispatcher()->addCrossLinkSerial(Serial6); - distributionController.eventDispatcher()->addCrossLinkSerial(Serial7); - distributionController.eventDispatcher()->addCrossLinkSerial(Serial8); - - //distributionController.eventDispatcher()->addListener(new PPUCCrossLinkDebugger()); -} - -void loop() { - // read data - distributionController.vpxComLink()->update(); - distributionController.testButtons()->update(); - - // handle data - distributionController.eventDispatcher()->update(); -} diff --git a/test/DistributionController/test/README b/test/DistributionController/test/README deleted file mode 100644 index b94d089..0000000 --- a/test/DistributionController/test/README +++ /dev/null @@ -1,11 +0,0 @@ - -This directory is intended for PlatformIO Unit Testing and project tests. - -Unit Testing is a software testing method by which individual units of -source code, sets of one or more MCU program modules together with associated -control data, usage procedures, and operating procedures, are tested to -determine whether they are fit for use. Unit testing finds problems early -in the development cycle. - -More information about PlatformIO Unit Testing: -- https://docs.platformio.org/page/plus/unit-testing.html diff --git a/test/EffectController/platformio.ini b/test/EffectController/platformio.ini deleted file mode 100644 index 7019959..0000000 --- a/test/EffectController/platformio.ini +++ /dev/null @@ -1,22 +0,0 @@ -; PlatformIO Project Configuration File -; -; Build options: build flags, source filter -; Upload options: custom upload port, speed and extra flags -; Library options: dependencies, extra library storages -; Advanced options: extra scripting -; -; Please visit documentation for the other options and examples -; https://docs.platformio.org/page/projectconf.html - -[env:teensy41] -platform = teensy -board = teensy41 -framework = arduino -lib_extra_dirs = - ../.. -lib_deps = - mkalkbrenner/WavePWM - mkalkbrenner/WS2812Serial - kitesurfer1404/WS2812FX - thomasfredericks/Bounce2 - https://github.com/PaulStoffregen/TimerOne.git#master diff --git a/test/EffectController/src/main.cpp b/test/EffectController/src/main.cpp deleted file mode 100644 index 49644b1..0000000 --- a/test/EffectController/src/main.cpp +++ /dev/null @@ -1,128 +0,0 @@ -// Markus Kalkbrenner 2022 -// Note to self: Play more pinball! - -#include - -#define PPUC_NUM_LEDS_1 60 -#define PPUC_LED_TYPE_1 SK6812_GBRW -#define PPUC_NUM_LEDS_2 1 -#define PPUC_LED_TYPE_2 WS2812_GRB -#define PPUC_NUM_LEDS_3 1000 -#define PPUC_LED_TYPE_3 WS2812_GRB -#define PPUC_NUM_LEDS_4 23 -#define PPUC_LED_TYPE_4 WS2812_GRB -#define PPUC_NUM_LEDS_5 18 -#define PPUC_LED_TYPE_5 WS2812_GRB -#define PPUC_NUM_LEDS_6 60 -#define PPUC_LED_TYPE_6 WS2812_GRB -#define PPUC_NUM_LEDS_7 60 -#define PPUC_LED_TYPE_7 WS2812_GRB - -#include - -EffectsController effectsController("0.1.0", PLATFORM_SYS11); - -void setup() { - // Debug - Serial.begin(9600); // USB is always 12 Mbit/sec - - effectsController.eventDispatcher()->addListener(new CrossLinkDebugger()); - - // Setup - effectsController.eventDispatcher()->setCrossLinkSerial(Serial8); - - effectsController.createCombinedGiAndLightMatrixWs2812FXDevice(7); - - effectsController.setBrightness(1, 64); - effectsController.setBrightness(7, 128); - //effectsController.attachBrightnessControl(7, 1); - - effectsController.giAndLightMatrix(7)->setHeatUp(40); - effectsController.giAndLightMatrix(7)->setAfterGlow(280); - - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(1, 1, ORANGE); // HOLD - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(2, 2, ORANGE); // BONUS - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(3, 3, ORANGE); // DOUBLE - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(4, 4, ORANGE); // SCORES - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(5, 5, ORANGE); // CYCLONE 50K - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(6, 6, ORANGE); // CYCLONE 100K - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(7, 7, ORANGE); // CYCLONE Gate Bonis - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(8, 8, ORANGE); // RIDE THE COMET (on ramp) - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(9, 9, RED); // W/L SCORE FERRIS WHEEL BONUS - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(10, 10, ORANGE); // ADV. "X" - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(11, 11, ORANGE); // Balloon 25K (Ducks) - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(12, 12, ORANGE); // Balloon 50K (Ducks) - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(13, 13, ORANGE); // Balloon LITES EX. BALL (Ducks) - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(14, 14, ORANGE); // 1 (Gate Lane) - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(15, 15, ORANGE); // 2 (Gate Lane) - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(16, 16, RED); // 3 (Gate Lane) - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(17, 17, RED); // RIDE AGAIN - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(18, 18, RED); // SPINS MYSTERY WHEEL - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(19, 19, WHITE); // Ducks (top) - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(20, 20, WHITE); // Ducks (mid) - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(21, 21, WHITE); // Ducks (bottom) - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(22, 22, RED); // Ball Toss (top) - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(23, 23, YELLOW); // Ball Toss (mid) - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(24, 24, WHITE); // Ball Toss (bottom) - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(25, 25, YELLOW); // EXTRA BALL Left Outlane - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(26, 26, YELLOW); // EXTRA BALL Right Outlane - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(27, 27, YELLOW); // COMET 20K - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(28, 28, YELLOW); // COMET 40K - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(29, 29, YELLOW); // COMET 60K - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(30, 30, RED); // COMET 80K - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(31, 31, RED); // COMET 100K - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(32, 32, WHITE); // COMET 1 Million - - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(49, 33, YELLOW); // 2X - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(50, 34, YELLOW); // 7X - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(51, 35, YELLOW); // 6X - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(52, 36, WHITE); // 5X - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(53, 37, YELLOW); // 4X - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(54, 38, YELLOW); // 3X - - // Test Buttons - effectsController.addEffect( - new LedBlinkEffect(), - effectsController.ledBuiltInDevice(), - new Event(EVENT_SOURCE_SWITCH, 201), - 1, // priority - 5, // repeat - 0 // mode - ); - - effectsController.addEffect( - new NullEffect(), - effectsController.ledBuiltInDevice(), - new Event(EVENT_SOURCE_SWITCH, 202), - 2, // priority - 0, // repeat - 0 // mode - ); - - effectsController.addEffect( - new LedBlinkEffect(), - effectsController.ledBuiltInDevice(), - new Event(EVENT_SOURCE_SWITCH, 203), - 1, // priority - 5, // repeat - 0 // mode - ); - - // Controller start - effectsController.addEffect( - new NullEffect(), - effectsController.ledBuiltInDevice(), - new Event(EVENT_SOURCE_SWITCH, 204), - 2, // priority - 0, // repeat - 0 // mode - ); - - // Start - effectsController.start(); - //effectsController.generalIllumintationWPC()->start(); -} - -void loop() { - effectsController.update(); -} diff --git a/test/EffectController/test/README b/test/EffectController/test/README deleted file mode 100644 index b94d089..0000000 --- a/test/EffectController/test/README +++ /dev/null @@ -1,11 +0,0 @@ - -This directory is intended for PlatformIO Unit Testing and project tests. - -Unit Testing is a software testing method by which individual units of -source code, sets of one or more MCU program modules together with associated -control data, usage procedures, and operating procedures, are tested to -determine whether they are fit for use. Unit testing finds problems early -in the development cycle. - -More information about PlatformIO Unit Testing: -- https://docs.platformio.org/page/plus/unit-testing.html diff --git a/test/EffectControllerPico/platformio.ini b/test/EffectControllerPico/platformio.ini deleted file mode 100644 index 0f457a4..0000000 --- a/test/EffectControllerPico/platformio.ini +++ /dev/null @@ -1,22 +0,0 @@ -; PlatformIO Project Configuration File -; -; Build options: build flags, source filter -; Upload options: custom upload port, speed and extra flags -; Library options: dependencies, extra library storages -; Advanced options: extra scripting -; -; Please visit documentation for the other options and examples -; https://docs.platformio.org/page/projectconf.html - -[env:pico] -platform = raspberrypi -board = pico -framework = arduino -lib_extra_dirs = - ../.. -lib_deps = - mkalkbrenner/WavePWM - kitesurfer1404/WS2812FX - Bounce2 - Adafruit_NeoPixel - diff --git a/test/EffectControllerPico/src/main.cpp b/test/EffectControllerPico/src/main.cpp deleted file mode 100644 index 2b0ea36..0000000 --- a/test/EffectControllerPico/src/main.cpp +++ /dev/null @@ -1,123 +0,0 @@ -// Markus Kalkbrenner 2022 -// Note to self: Play more pinball! - -#include - -#define PPUC_NUM_LEDS_1 60 -#define PPUC_LED_TYPE_1 SK6812_GBRW -#define PPUC_NUM_LEDS_2 1 -#define PPUC_LED_TYPE_2 WS2812_GRB -#define PPUC_NUM_LEDS_3 1000 -#define PPUC_LED_TYPE_3 WS2812_GRB -#define PPUC_NUM_LEDS_4 23 -#define PPUC_LED_TYPE_4 WS2812_GRB -#define PPUC_NUM_LEDS_5 18 -#define PPUC_LED_TYPE_5 WS2812_GRB -#define PPUC_NUM_LEDS_6 60 -#define PPUC_LED_TYPE_6 WS2812_GRB -#define PPUC_NUM_LEDS_7 60 -#define PPUC_LED_TYPE_7 WS2812_GRB - -#include - -EffectsController effectsController("0.1.0", PLATFORM_LIBPINMAME); - -void setup() { - // Setup - effectsController.eventDispatcher()->setCrossLinkSerial(Serial); - - effectsController.createCombinedGiAndLightMatrixWs2812FXDevice(7); - - effectsController.setBrightness(1, 64); - effectsController.setBrightness(7, 128); - //effectsController.attachBrightnessControl(7, 1); - - effectsController.giAndLightMatrix(7)->setHeatUp(40); - effectsController.giAndLightMatrix(7)->setAfterGlow(280); - - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(1, 1, ORANGE); // HOLD - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(2, 2, ORANGE); // BONUS - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(3, 3, ORANGE); // DOUBLE - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(4, 4, ORANGE); // SCORES - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(5, 5, ORANGE); // CYCLONE 50K - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(6, 6, ORANGE); // CYCLONE 100K - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(7, 7, ORANGE); // CYCLONE Gate Bonis - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(8, 8, ORANGE); // RIDE THE COMET (on ramp) - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(9, 9, RED); // W/L SCORE FERRIS WHEEL BONUS - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(10, 10, ORANGE); // ADV. "X" - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(11, 11, ORANGE); // Balloon 25K (Ducks) - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(12, 12, ORANGE); // Balloon 50K (Ducks) - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(13, 13, ORANGE); // Balloon LITES EX. BALL (Ducks) - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(14, 14, ORANGE); // 1 (Gate Lane) - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(15, 15, ORANGE); // 2 (Gate Lane) - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(16, 16, RED); // 3 (Gate Lane) - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(17, 17, RED); // RIDE AGAIN - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(18, 18, RED); // SPINS MYSTERY WHEEL - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(19, 19, WHITE); // Ducks (top) - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(20, 20, WHITE); // Ducks (mid) - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(21, 21, WHITE); // Ducks (bottom) - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(22, 22, RED); // Ball Toss (top) - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(23, 23, YELLOW); // Ball Toss (mid) - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(24, 24, WHITE); // Ball Toss (bottom) - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(25, 25, YELLOW); // EXTRA BALL Left Outlane - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(26, 26, YELLOW); // EXTRA BALL Right Outlane - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(27, 27, YELLOW); // COMET 20K - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(28, 28, YELLOW); // COMET 40K - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(29, 29, YELLOW); // COMET 60K - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(30, 30, RED); // COMET 80K - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(31, 31, RED); // COMET 100K - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(32, 32, WHITE); // COMET 1 Million - - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(49, 33, YELLOW); // 2X - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(50, 34, YELLOW); // 7X - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(51, 35, YELLOW); // 6X - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(52, 36, WHITE); // 5X - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(53, 37, YELLOW); // 4X - effectsController.giAndLightMatrix(7)->assignLedToLightMatrixSYS11(54, 38, YELLOW); // 3X - - // Test Buttons - effectsController.addEffect( - new LedBlinkEffect(), - effectsController.ledBuiltInDevice(), - new Event(EVENT_SOURCE_SWITCH, 201), - 1, // priority - 5, // repeat - 0 // mode - ); - - effectsController.addEffect( - new NullEffect(), - effectsController.ledBuiltInDevice(), - new Event(EVENT_SOURCE_SWITCH, 202), - 2, // priority - 0, // repeat - 0 // mode - ); - - effectsController.addEffect( - new LedBlinkEffect(), - effectsController.ledBuiltInDevice(), - new Event(EVENT_SOURCE_SWITCH, 203), - 1, // priority - 5, // repeat - 0 // mode - ); - - // Controller start - effectsController.addEffect( - new NullEffect(), - effectsController.ledBuiltInDevice(), - new Event(EVENT_SOURCE_SWITCH, 204), - 2, // priority - 0, // repeat - 0 // mode - ); - - // Start - effectsController.start(); - //effectsController.generalIllumintationWPC()->start(); -} - -void loop() { - effectsController.update(); -} diff --git a/test/EffectControllerPico/test/README b/test/EffectControllerPico/test/README deleted file mode 100644 index b94d089..0000000 --- a/test/EffectControllerPico/test/README +++ /dev/null @@ -1,11 +0,0 @@ - -This directory is intended for PlatformIO Unit Testing and project tests. - -Unit Testing is a software testing method by which individual units of -source code, sets of one or more MCU program modules together with associated -control data, usage procedures, and operating procedures, are tested to -determine whether they are fit for use. Unit testing finds problems early -in the development cycle. - -More information about PlatformIO Unit Testing: -- https://docs.platformio.org/page/plus/unit-testing.html diff --git a/test/IOBoardController/platformio.ini b/test/IOBoardController/platformio.ini deleted file mode 100644 index 788c5f3..0000000 --- a/test/IOBoardController/platformio.ini +++ /dev/null @@ -1,22 +0,0 @@ -; PlatformIO Project Configuration File -; -; Build options: build flags, source filter -; Upload options: custom upload port, speed and extra flags -; Library options: dependencies, extra library storages -; Advanced options: extra scripting -; -; Please visit documentation for the other options and examples -; https://docs.platformio.org/page/projectconf.html - -[env:pico] -platform = https://github.com/maxgerhardt/platform-raspberrypi.git -board = pico -framework = arduino -board_build.core = earlephilhower -board_build.filesystem_size = 0.5m -lib_extra_dirs = - ../.. -lib_deps = - mkalkbrenner/WavePWM - kitesurfer1404/WS2812FX - thomasfredericks/Bounce2 diff --git a/test/IOBoardController/src/main.cpp b/test/IOBoardController/src/main.cpp deleted file mode 100644 index a8407b6..0000000 --- a/test/IOBoardController/src/main.cpp +++ /dev/null @@ -1,27 +0,0 @@ -// Markus Kalkbrenner 2022-2023 -// Note to self: Play more pinball! - -#include - -#include "EffectsController.h" -#include "IOBoardController.h" - -IOBoardController ioBoardController(CONTROLLER_16_8_1); -EffectsController effectsController(CONTROLLER_16_8_1, PLATFORM_WPC); -MultiCoreCrossLink* multiCoreCrossLink = new MultiCoreCrossLink(); - -void setup() { - ioBoardController.eventDispatcher()->setMultiCoreCrossLink(multiCoreCrossLink); -} - -void setup1() { - effectsController.eventDispatcher()->setMultiCoreCrossLink(multiCoreCrossLink); -} - -void loop() { - ioBoardController.update(); -} - -void loop1() { - effectsController.update(); -} diff --git a/test/IOBoardController/test/README b/test/IOBoardController/test/README deleted file mode 100644 index b94d089..0000000 --- a/test/IOBoardController/test/README +++ /dev/null @@ -1,11 +0,0 @@ - -This directory is intended for PlatformIO Unit Testing and project tests. - -Unit Testing is a software testing method by which individual units of -source code, sets of one or more MCU program modules together with associated -control data, usage procedures, and operating procedures, are tested to -determine whether they are fit for use. Unit testing finds problems early -in the development cycle. - -More information about PlatformIO Unit Testing: -- https://docs.platformio.org/page/plus/unit-testing.html diff --git a/test/InputController/platformio.ini b/test/InputController/platformio.ini deleted file mode 100644 index 30888cd..0000000 --- a/test/InputController/platformio.ini +++ /dev/null @@ -1,21 +0,0 @@ -; PlatformIO Project Configuration File -; -; Build options: build flags, source filter -; Upload options: custom upload port, speed and extra flags -; Library options: dependencies, extra library storages -; Advanced options: extra scripting -; -; Please visit documentation for the other options and examples -; https://docs.platformio.org/page/projectconf.html - -[env:megaatmega2560] -platform = atmelavr -framework = arduino -board = megaatmega2560 -lib_extra_dirs = - ../.. -lib_deps = - mkalkbrenner/WavePWM - kitesurfer1404/WS2812FX - thomasfredericks/Bounce2 - https://github.com/PaulStoffregen/TimerOne.git#master diff --git a/test/InputController/src/main.cpp b/test/InputController/src/main.cpp deleted file mode 100644 index d50eeb2..0000000 --- a/test/InputController/src/main.cpp +++ /dev/null @@ -1,59 +0,0 @@ -// Markus Kalkbrenner 2022 -// Note to self: Play more pinball! - -#include -#include -#include - -InputController inputController("0.1.0", PLATFORM_WPC); - -void setup() { - inputController.pupComLink()->setSerial(Serial); - inputController.pin2Dmd()->setSerial(Serial1); - inputController.eventDispatcher()->setCrossLinkSerial(Serial3); - - //inputController.switchMatrix()->setLastColToRead(9); - inputController.switchMatrix()->registerFieldAsEvent(3, 1, 13); // Start Button - inputController.switchMatrix()->registerFieldAsEvent(7, 2, 27); // Ball Shooter - inputController.switchMatrix()->registerFieldAsEvent(8, 2, 28); // Rocket Kicker - inputController.switchMatrix()->registerFieldAsEvent(1, 3, 31); // Bumper - inputController.switchMatrix()->registerFieldAsEvent(2, 3, 32); // Bumper - inputController.switchMatrix()->registerFieldAsEvent(3, 3, 33); // Bumper - inputController.switchMatrix()->registerFieldAsEvent(1, 6, 61); // Lower Skill - inputController.switchMatrix()->registerFieldAsEvent(2, 6, 62); // Center Skill - inputController.switchMatrix()->registerFieldAsEvent(3, 6, 63); // Upper Skill - - inputController.lightMatrix()->registerAllFieldsAsEvent(); - - inputController.solenoids()->registerJ125(1, 17); // Flasher Bumpers - inputController.solenoids()->registerJ125(2, 18); // Flasher Power Payoff - inputController.solenoids()->registerJ125(3, 19); // Flasher Mini-Playfield - inputController.solenoids()->registerJ125(5, 20); // Flasher Upper Left Ramp - inputController.solenoids()->registerJ125(6, 21); // Flasher Left Magnet - inputController.solenoids()->registerJ125(8, 23); // Flasher Lower Right Magnet - inputController.solenoids()->registerJ125(9, 24); // Flasher Gumball Motor - - inputController.solenoids()->registerJ124(1, 25); // Left Mini Magnet - inputController.solenoids()->registerJ124(2, 26); // Right Mini Magnet - inputController.solenoids()->registerJ124(3, 27); // Left Ramp Diverter - inputController.solenoids()->registerJ124(5, 28); // Inside Ramp - - - inputController.eventDispatcher()->addListener(new CrossLinkDebugger()); - inputController.eventDispatcher()->addListener(inputController.pupComLink(), EVENT_SOURCE_ANY); - - inputController.switchMatrix()->start(); - inputController.lightMatrix()->start(); -} - -void loop() { - // read data - inputController.pin2Dmd()->update(); - inputController.switchMatrix()->update(); - inputController.lightMatrix()->update(); - inputController.solenoids()->update(); - inputController.testButtons()->update(); - - // handle data - inputController.eventDispatcher()->update(); -} diff --git a/test/InputController/test/README b/test/InputController/test/README deleted file mode 100644 index e69de29..0000000 diff --git a/test/NanoGIController/platformio.ini b/test/NanoGIController/platformio.ini deleted file mode 100644 index 8478e66..0000000 --- a/test/NanoGIController/platformio.ini +++ /dev/null @@ -1,21 +0,0 @@ -; PlatformIO Project Configuration File -; -; Build options: build flags, source filter -; Upload options: custom upload port, speed and extra flags -; Library options: dependencies, extra library storages -; Advanced options: extra scripting -; -; Please visit documentation for the other options and examples -; https://docs.platformio.org/page/projectconf.html - -[env:nanoatmega328] -platform = atmelavr -board = nanoatmega328 -framework = arduino -lib_extra_dirs = - ../.. -lib_deps = - mkalkbrenner/WavePWM - kitesurfer1404/WS2812FX - thomasfredericks/Bounce2 - https://github.com/PaulStoffregen/TimerOne.git#master diff --git a/test/NanoGIController/src/main.cpp b/test/NanoGIController/src/main.cpp deleted file mode 100644 index da87fb6..0000000 --- a/test/NanoGIController/src/main.cpp +++ /dev/null @@ -1,19 +0,0 @@ -// Markus Kalkbrenner 2022 -// Note to self: Play more pinball! - -#include -#include "InputController.h" -#include "EffectsController.h" - -EffectsController effectsController(CONTROLLER_NANO_PIN2DMD_GI, PLATFORM_DATA_EAST); -InputController inputController(CONTROLLER_NANO_PIN2DMD_GI, PLATFORM_DATA_EAST, effectsController.eventDispatcher()); - -void setup() { - inputController.pin2Dmd()->setSerial(Serial); -} - -void loop() { - inputController.pin2Dmd()->update(); - inputController.eventDispatcher()->update(); - effectsController.update(); -} diff --git a/test/NanoGIController/test/README b/test/NanoGIController/test/README deleted file mode 100644 index e69de29..0000000