From 51851e596128901e8cba0acffcfe24a764be3777 Mon Sep 17 00:00:00 2001 From: Kevin Balthaser Date: Tue, 2 May 2023 21:47:25 -0400 Subject: [PATCH 1/3] Add ability to start/stop and configurable preheat when starting a cycle. - Required adding a new `EncoderShim` to intercept all encoder interactions so that we can know a long-press has occurred. - Preheat is indicated by a title bar widget. - Preheat start/stop with long-press is a toggle of current state, it is not impacted in any way by what operation was being done (ie, you start a preheat while stopping a spin cycle with a long-press) - Made encoder inversion work in new shim - Switch StealthChop transition point into a simple on/off for StealthChop as a whole. There isn't any point in having stealthchop enabled at all if we cannot use it across full motor speed range, as the transition is abbrupt, and causes a large jerk. --- KiwiBoard.emf | 94 ++++++++--------------- TMC_calculations.xlsx | Bin 12577 -> 12912 bytes images/Stop.png | Bin 0 -> 861 bytes images/blank-widget-50x50.png | Bin 0 -> 201 bytes images/dry.png | Bin 0 -> 856 bytes images/heat-widget-50x50.png | Bin 0 -> 1123 bytes images/logo-cutout.png | Bin 0 -> 1840 bytes images/readme.md | 3 + images/settings.png | Bin 0 -> 854 bytes images/spash.png | Bin 0 -> 52972 bytes images/spin.png | Bin 0 -> 869 bytes images/wash.png | Bin 0 -> 1035 bytes src/EncoderShim.cpp | 82 ++++++++++++++++++++ src/EncoderShim.h | 46 +++++++++++ src/KiwiBoardFirmware_main.cpp | 136 ++++++++++++++++++++++++--------- src/KiwiBoardFirmware_main.h | 4 +- src/KiwiBoardFirmware_menu.cpp | 19 ++--- src/KiwiBoardFirmware_menu.h | 9 ++- src/heat.h | 46 +++++++++++ src/motorControl.cpp | 41 ++++++---- src/motorControl.h | 19 +++-- src/picoPlatform.cpp | 29 ++++++- src/picoPlatform.h | 11 +++ src/settings.cpp | 2 + src/settings.h | 2 + 25 files changed, 411 insertions(+), 132 deletions(-) create mode 100644 images/Stop.png create mode 100644 images/blank-widget-50x50.png create mode 100644 images/dry.png create mode 100644 images/heat-widget-50x50.png create mode 100644 images/logo-cutout.png create mode 100644 images/readme.md create mode 100644 images/settings.png create mode 100644 images/spash.png create mode 100644 images/spin.png create mode 100644 images/wash.png create mode 100644 src/EncoderShim.cpp create mode 100644 src/EncoderShim.h create mode 100644 src/heat.h diff --git a/KiwiBoard.emf b/KiwiBoard.emf index d4946c9..1ddf44c 100644 --- a/KiwiBoard.emf +++ b/KiwiBoard.emf @@ -217,12 +217,12 @@ { "parentId": 12, "type": "analogItem", - "defaultValue": "0", + "defaultValue": "44", "item": { - "maxValue": 10, + "maxValue": 119, "offset": 1, "divisor": 1, - "unitName": "min", + "unitName": "sec", "step": 1, "name": "Time", "variableName": "spin_duration", @@ -372,6 +372,27 @@ "staticDataInRAM": false } }, + { + "parentId": 15, + "type": "analogItem", + "defaultValue": "4", + "item": { + "maxValue": 9, + "offset": 1, + "divisor": 1, + "unitName": "min", + "step": 1, + "name": "Preheat", + "variableName": "MaxPreheat", + "id": 44, + "eepromAddress": 94, + "functionName": "@settings_changed", + "readOnly": false, + "localOnly": false, + "visible": true, + "staticDataInRAM": false + } + }, { "parentId": 7, "type": "subMenu", @@ -447,19 +468,15 @@ }, { "parentId": 30, - "type": "analogItem", - "defaultValue": "1", + "type": "boolItem", + "defaultValue": "true", "item": { - "maxValue": 2000, - "offset": 0, - "divisor": 1, - "unitName": "", - "step": 5, - "name": "PWM Threshold ", - "variableName": "stealthTransition", - "id": 42, - "eepromAddress": 92, - "functionName": "@stealthTransitionChanged", + "naming": "ON_OFF", + "name": "StealthChop", + "variableName": "StealthChop", + "id": 45, + "eepromAddress": 96, + "functionName": "@stealthChopChange", "readOnly": false, "localOnly": false, "visible": true, @@ -487,7 +504,7 @@ "codeOptions": { "embeddedPlatform": "ARDUINO32", "lastDisplayUuid": "e6342680-6fab-4d1b-ac08-ce8dbfc0974c", - "lastInputUuid": "6dd87e7d-e751-4912-a70d-91793b1d3d87", + "lastInputUuid": "89cd7f70-0457-4884-97c2-0db904ccb0ba", "lastRemoteUuid": "2c101fec-1f7d-4ff3-8d2b-992ad41e7fcb", "lastRemoteUuids": [ "2c101fec-1f7d-4ff3-8d2b-992ad41e7fcb" @@ -576,51 +593,6 @@ "latestValue": "400", "subsystem": "DISPLAY" }, - { - "name": "SW_POLLING_MODE", - "latestValue": "SWITCHES_NO_POLLING", - "subsystem": "INPUT" - }, - { - "name": "SWITCH_IODEVICE", - "latestValue": "devicePins", - "subsystem": "INPUT" - }, - { - "name": "ENCODER_PIN_A", - "latestValue": "ENC1", - "subsystem": "INPUT" - }, - { - "name": "ENCODER_PIN_B", - "latestValue": "ENC2", - "subsystem": "INPUT" - }, - { - "name": "ENCODER_PIN_OK", - "latestValue": "BUTTON", - "subsystem": "INPUT" - }, - { - "name": "ENCODER_BACK_PIN", - "latestValue": "-1", - "subsystem": "INPUT" - }, - { - "name": "ENCODER_NEXT_PIN", - "latestValue": "-1", - "subsystem": "INPUT" - }, - { - "name": "PULLUP_LOGIC", - "latestValue": "true", - "subsystem": "INPUT" - }, - { - "name": "ENCODER_IS_QUARTER_CYCLE", - "latestValue": "false", - "subsystem": "INPUT" - }, { "name": "ITEM_FONT", "latestValue": "num:,4", diff --git a/TMC_calculations.xlsx b/TMC_calculations.xlsx index 15c1c322f5acc4daac63e7e49b45d691e5dc8f78..4369004e89b1712f29796984878cf7c55766a691 100644 GIT binary patch delta 3114 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%0Ax~&)zl={%e*!SG|SA~?F zsJ#N?TK&qRMSkzyPmOO&P(m6VuZ>G4?lp2f=7!lQhn&TlD5A!qvS$LwpKZP?nDbH# zJ%xkZ5Dl|IAN@Wt~( zI!Oox&K785U~{?`zjZ=${G>_y;BC=PZLvvp+N63cBONUwMbgo`Pho}ZXV!Psjkz?{ zBu}nK(yigy{FFjM&$a9WS2K7Jya(ayf^&E9^t$cl^`BYJ*VV+7&#VjFl z6xnj2tbqJ#oPWIh-O<>?yVZ_oL#jj z3S{{z6O|>f?<*qUv|sgE3X_ZYR`4KKpFv2WUl(RbNVD2~Q7}F=Z@aZi2pBzIeaBQI zbI^jIs?}uC%1OGKGQkt24s*7wfR^2mCw32>G3%);L~a{_tkZKh@*2Oqm;ZU}BNT~S zY*Vs|e>d<`qe4wfJ=Q{Z8f}pvX2fhLidL#E+HqyUX3a>amXF+PBVKZ5&T68||`O5*=e8Zf}iK|qp#KVDS$H)9U|n%fW% zi04n=K}L`QD)HyACDI^}{cSsv=WTZ{oQ=EtulGRGhaCv*FAU(I*@L@!$o*RV8=!}z A7ytkO delta 2806 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 diff --git a/images/Stop.png b/images/Stop.png new file mode 100644 index 0000000000000000000000000000000000000000..580db2e9a1c3e4dbd4ef8f7561dd91ccb1879afd GIT binary patch literal 861 zcmV-j1ETziP)b@4KhO73Fw=EWP&6UkT*ftfSx5sic%X6;~(edKFLC&sqgrF{%nqOk}wzy z27|$1+$YjdhWoxR;F3ctG(R~&>(Vsc-UUIDkOj0?XywFz7i0;dK7p{quL(~eC4*K$ z+xPa+LR*QxV<$&E#JP8iO>{(o!=)T$Ul4<^j6#FJ(KTE!&Yq;E{Fm1YI%t=O5SsNY z^{PPhDf)Q>jq0ds%^sm0Ybn-K`_Dy`AR_Z@(!cZ7X~c2yx61MqZbnEdDUmZI#4Crk zs}b7;g=wXWoV^bRr4J3s={BG_d*4g1T<&$#I}1V1TDLC=IAA)G>Ja-JW#kahL>);> zpH8%7LRsx!qtT74B*p3GmyDUqZQ?;g`8>|mj+eKdX*4` zW-)Q2Me<$fhp`AU3(ed?eMJ6lp}6;9GEL#~Zuq=AcvGLMcb|&vi?ERSNdWwgzF0Qq`fY>nV2EV1k&Sc@kktP&8jNHG7_;O@d~L906pmr`Snx?xe;R(mt~2ahkNx__NQr;Z?*R4r^b=TM88&13uP4q6ZPaH34!q zz6-ggIuf${r6N;xGE%fz6DptzS7kO*pXjp$uO9f8RtC?t43Olsz*}nJP!T7MO)R3) zHAEROtsTC6SHl#NFHM%)35Q~=YffrY58(1Q!5u}o9B(?x+|22cf{+A?mdU&m_V|?O zkLFPX#T588hgn>%F20UKl#BC53w|)$!aAj3*UaHp(0BPaFGmumf2nl nID^4pFc=I5gTaUe-vSH(T{oXhZoXm<00000NkvXXu0mjfayf%= literal 0 HcmV?d00001 diff --git a/images/blank-widget-50x50.png b/images/blank-widget-50x50.png new file mode 100644 index 0000000000000000000000000000000000000000..dc649d5393ae45ead9342dd0cbe0f619cc2ad8f0 GIT binary patch literal 201 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 literal 0 HcmV?d00001 diff --git a/images/spin.png b/images/spin.png new file mode 100644 index 0000000000000000000000000000000000000000..9810b2d0b6fe11bac3f21de83233785862700833 GIT binary patch literal 869 zcmV-r1DgDaP)TeBgcnpBlsl7o&+1%yOVMTyP z6@bF8>pi>-^?AT-r;D|Qs3K=X8p6K7=WXuM-xZ>b`pcgj^`DjZdl3L0L%vYd-)p|- z+0**iy#cX6Kx+K*fqd4z<2kOZPF&JC<_cI1e>&)>lMKQ5mE8v(Ol=s-&>S?0oMiW$W(SO}9R&IPEN`mmv)f?*M_SSlc0 znlS_ficGhx5(soy`Y_uXqG{JuT^jPS%F%A8t}Dghi%eO1tAc{a*|;c zK_Lll9qqn8K(9=@h^*#a1jeE8rI?$i}0MZGWvw z6^=7*0V}hedrThAm2-u9l>^Lht9WOwgXE@ZS?XPsKd?Z?JQlv>67H**&QB>>46tX!BXhoh7L#_ vk+DN+h>9LkM{r0a5{X12kx0?#p8x{@{^0O+j=*vB00000NkvXXu0mjfErXNp literal 0 HcmV?d00001 diff --git a/images/wash.png b/images/wash.png new file mode 100644 index 0000000000000000000000000000000000000000..f98ef74a869a70527d1063a8769a8219117c4656 GIT binary patch literal 1035 zcmV+m1oZofP)hC?j)J^>NMUo{sFcuIH z5D*X$5b!2$W%2m)e|logboM}km5c>kgW zf%lWUCx{%p-G%!|R;sXH_CgI|7bvK>jvnvz>u&Q+2=X)d)Fi0O0s=ucy;2^jCv#5Q z@}z@`1++*I#ZMWOyp$uS`{Qq8gP=`BHsMg};Xv)8yR|_uw-H=&Rxjyx3QTkA#~L0i zk%RG2b|Ij|Fyc4&zajf~AP`Bs;tljS1Ap)JE>uhuaSmCPG~j)DPTJmviWwycB}ZX= zN4n~%Fy@K31Yx#MVvJXH3@vBiHxiQiz8dL;U_*9bH=ZapQ76beLWw1552Ly9t|3W7 z6&eYKIKEN$F+0x`j!f=srX*-8)Ms@iM4Xjw*x+_+`a8`lrN+^g_jCTk&4>S&*0uP9X10_9VX5 zUh?e5go@cZ)xbsKVlkd76sjl#l8S_ExqhrlitS^cx(gh&r8PF9`EA90v^hVaJkp87 zBh{Ps>nuT{r#m6%ZY?-?_#8&$_)$}OGRSzvIuZ3M^mn9wV(RzOxX!BBho~N~;HB;x zC&FeGDHI;>zDSTr#Iqskf3~4=Hz$bB(F>EYk|fsK>3)0IwvqlN)aN(ue^D`>^8_s= zQk33EdNRRVFUZFELy#iko?nvztRN?4U%&U6F;`F{<(etNhe&hWh_aE|d19PMG zhZd&1W9JEy1f{JLVM4`*%nvl0r|xIKdv=}?bo%YHuNh3VfdiA literal 0 HcmV?d00001 diff --git a/src/EncoderShim.cpp b/src/EncoderShim.cpp new file mode 100644 index 0000000..e1dd824 --- /dev/null +++ b/src/EncoderShim.cpp @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2023 Kevin Balthaser kevin@nybblebyte.com +*/ +// + +#include "EncoderShim.h" + +EncoderShim::EncoderShim(MenuManager *manager) { + menuManager = manager; + +} + +void EncoderShim::initForEncoder() { + + switches.init(internalDigitalIo(), SWITCHES_NO_POLLING, true); + + switches.addSwitchListener(BUTTON, this, NO_REPEAT, false); + //switches.addSwitch(encoderButton, nullptr); + //switches.onRelease(encoderButton, EncoderShim::*buttonRelease); + setupRotaryEncoderWithInterrupt(ENC1, ENC2, this, HWACCEL_NONE, FULL_CYCLE); + //setupRotaryEncoderWithInterrupt(encoderPinA, encoderPinB, [](int value) {menuMgr.valueChanged(value); }, HWACCEL_REGULAR, type); + +} + +void EncoderShim::encoderHasChanged(int newValue) { + Serial.println("Encoder Turned"); + Serial.println(newValue); + + // Which way did the encoder go... + bool direction; + if (newValue < encoderValue) { + direction = false; + } else { + direction = true; + } + + //Serial.println(direction); + + encoderValue = newValue; + + menuMgr.valueChanged(newValue); + Serial.println("Check for encoderChangeFn"); + if (encoderChangeFn != nullptr) { + Serial.println("call encoderChangefn Callback"); + encoderChangeFn(direction, false); + } + +} + +void EncoderShim::onPressed(pinid_t pin, bool held) { + Serial.println("button down"); + if (encoderClickFn != nullptr) { + encoderClickFn(false, held); + } +} + +void EncoderShim::onReleased(pinid_t pin, bool held) { + Serial.println("button up"); + // always send false, since we don't want the normal tcMenu long press reset behavior, and we are acting + // on Pressed long press, not release. + menuMgr.onMenuSelect(false); +} + +void EncoderShim::registerChangeCallback(EncoderShimFn callback) { + + encoderChangeFn = callback; + +} + +void EncoderShim::registerClickCallback(EncoderShimFn callback) { + + encoderClickFn = callback; + +} + +/** + * Reinitialize the encoder with a and b pins reversed. + */ +void EncoderShim::invertEncoderDirection() { + setupRotaryEncoderWithInterrupt(ENC2, ENC1, this, HWACCEL_NONE, FULL_CYCLE); +} + diff --git a/src/EncoderShim.h b/src/EncoderShim.h new file mode 100644 index 0000000..b851399 --- /dev/null +++ b/src/EncoderShim.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2023 Kevin Balthaser kevin@nybblebyte.com + * + * Create a shim that will sit in between the tcMenu layer, and the firmware. This will allow us to monitor the rotary + * encoder on every cycle to pick up on movement events, and fire any callback events. +*/ +// + +#include "picoPlatform.h" // Need the platform pin definitions +#include + +#ifndef KIWIBOARDFIRMWARE_ENCODERSHIM_H + +#define KIWIBOARDFIRMWARE_ENCODERSHIM_H + + +/** The definition of a callback from EncoderShim object */ +typedef void (*EncoderShimFn)(bool direction, bool longPress); + +class EncoderShim : EncoderListener, SwitchListener { + + public: + EncoderShim(MenuManager* manager); + + void initForEncoder(); + void registerChangeCallback(EncoderShimFn callback); + void registerClickCallback(EncoderShimFn callback); + void invertEncoderDirection(); + + private: + void encoderHasChanged(int newValue) override; + + void onPressed(pinid_t pin, bool held) override; + + void onReleased(pinid_t pin, bool held) override; + + MenuManager* menuManager; + EncoderShimFn encoderChangeFn = nullptr; + EncoderShimFn encoderClickFn = nullptr; + + int encoderValue = 0;// last known encoder value + +}; + + +#endif //KIWIBOARDFIRMWARE_ENCODERSHIM_H diff --git a/src/KiwiBoardFirmware_main.cpp b/src/KiwiBoardFirmware_main.cpp index 4320de5..b41968d 100644 --- a/src/KiwiBoardFirmware_main.cpp +++ b/src/KiwiBoardFirmware_main.cpp @@ -8,6 +8,8 @@ #include #include "splashScreen.h" #include "KiwiBoardFirmware_main.h" +#include "EncoderShim.h" +#include "heat.h" #ifdef SCREENCAP #include "screenServer.h" @@ -17,11 +19,12 @@ #include "MenuChangeObserver.h" // Version Number -const char VERSION_NUM[] PROGMEM = "1.00-Unic"; +const char VERSION_NUM[] PROGMEM = "1.*-PreHeat"; PicoPlatform *platform; -MotorControl *motorControl; +MotorControl *motorControl = nullptr; MenuChangeObserver *observer; +EncoderShim *encoderShim; // Error occurred, in HALT state. bool HALT = false; @@ -63,6 +66,12 @@ void setup() { delay(2000); gfx.fillScreen(TFT_BLACK); + // Setup switches and encoder? + encoderShim = new EncoderShim(&menuMgr); + encoderShim->initForEncoder(); + encoderShim->registerChangeCallback(handleEncoderMove); + encoderShim->registerClickCallback(checkLongPress); + menuRunTime.setReadOnly(true); setupMenu(); menuMgr.load(0xfadf, nullptr); @@ -71,7 +80,7 @@ void setup() { motorControl = new MotorControl(); motorControl->initMotionController(platform, menuGlobalScaler.getIntValueIncludingOffset(), menuIRun.getIntValueIncludingOffset(), - menustealthTransition.getIntValueIncludingOffset()); + menuStealthChop.getBoolean()); motorControl->setStoppedCallback(stoppedCallback); @@ -81,14 +90,15 @@ void setup() { // Check for encoder inversion.. if (menuInvertEncoder.getBoolean()) { // Inversion selected, reinitialize the encoder plugin with the pins reversed. - Serial.println("Encoder inversion requested.. reinit menuMgr with encoder flipped"); - menuMgr.initForEncoder(&renderer, &menuRunTime, ENC2, ENC1, BUTTON); + encoderShim->invertEncoderDirection(); + // reinit menu system so encoder is configured properly. + setupMenu(); } observer = new MenuChangeObserver(&menuMgr, &menuRunTime, &menuWash); menuMgr.addChangeNotification(observer); - menuVersion.setTextValue(VERSION_NUM,true); + menuVersion.setTextValue(VERSION_NUM, true); setMenuOptions(); @@ -150,8 +160,6 @@ void ui_tick() { // Check for motor status, if overheated or shorted, alert user TMC5160::DriverStatus curStatus = motorControl->getDriverStatus(); - Serial.println("Driver Status:"); - Serial.println(curStatus); if (curStatus != TMC5160::DriverStatus::OK) { // launch error dialog motorErrorDialog(curStatus); @@ -167,6 +175,17 @@ void ui_tick() { drawable->endDraw(); } + // Check for heat icon, if the heater is on, show the icon, otherwise swap in the blank widget + if (platform->isHeaterEnabled()) { + if (HeatWidget.getCurrentState() != 1) { + HeatWidget.setCurrentState(1); + } + } else { + if (HeatWidget.getCurrentState() !=0) { + HeatWidget.setCurrentState(0); + } + } + } /** @@ -291,7 +310,7 @@ void CALLBACK_FUNCTION GlobalScalerChanged(int id) { // init motorControl->initMotionController(platform, menuGlobalScaler.getIntValueIncludingOffset(), menuIRun.getIntValueIncludingOffset(), - menustealthTransition.getIntValueIncludingOffset()); + menuStealthChop.getBoolean()); settingsChanged = true; } @@ -311,7 +330,7 @@ void CALLBACK_FUNCTION iRunChanged(int id) { // init motorControl->initMotionController(platform, menuGlobalScaler.getIntValueIncludingOffset(), menuIRun.getIntValueIncludingOffset(), - menustealthTransition.getIntValueIncludingOffset()); + menuStealthChop.getBoolean()); settingsChanged = true; } @@ -324,15 +343,22 @@ void CALLBACK_FUNCTION backlightChange(int id) { PicoPlatform::setBacklight(newVal); } + /** - * Callback for when the user changes the PWM Transition time. The motor controller needs to be stopped, and - * reconfigured. + * Callback for when the user changes the StealthChop setting. * * @param id */ -void CALLBACK_FUNCTION stealthTransitionChanged(int id) { +void CALLBACK_FUNCTION stealthChopChange(int id) { + + // Shut off the TMC + platform->enableMotor(false); + delay(100); // wait for everything to settle... + // init + motorControl->initMotionController(platform, menuGlobalScaler.getIntValueIncludingOffset(), + menuIRun.getIntValueIncludingOffset(), + menuStealthChop.getBoolean()); - motorControl->setPwmTransitionTime(menustealthTransition.getIntValueIncludingOffset()); settingsChanged = true; } @@ -396,6 +422,8 @@ void setMenuOptions() { // first we get the graphics factory auto &factory = renderer.getGraphicsPropertiesFactory(); + // renderer.takeOverDisplay(checkLongPress); + // don't do the periodic reset, which causes some awkward redraw flashes. renderer.turnOffResetLogic(); @@ -464,6 +492,9 @@ void setMenuOptions() { // Blank the title bar so that we can render the logo overtop of it. (Save a draw call to blank the rectangle) appTitleMenuItem.setTitleOverridePgm(""); + // Setup heat icon? + renderer.setFirstWidget(&HeatWidget); + // Black on white cursor // factory.setSelectedColors(RGB(255, 0, 0), RGB(0, 255, 0)); factory.setSelectedColors(RGB(255, 255, 255), RGB(0, 0, 0)); @@ -500,32 +531,24 @@ void setIconStopped(MenuItem *icon) { tcgfx::ConfigurableItemDisplayPropertiesFactory::refreshCache(); } -void renderTimer(unsigned int encoderValue, RenderPressMode clicked) { - - // Test manually rendering the timer to see if it can be done smoother.. - // it didnt really help.. - //Serial.println("Render callback... we own display"); - - int xpos = 100; - // Render the timer box.. maybe ? - TimeStorage ts = menuRunTime.getTime(); - - if (ts.minutes == 0 && ts.seconds == 0) { - // renderer.giveBackDisplay(); - } else { - - gfx.drawRect(0, 50, 320, 100, TFT_WHITE); - gfx.fillRect(0, 52, 320, 100, TFT_WHITE); - gfx.setTextColor(TFT_BLACK); - - xpos += gfx.drawNumber(ts.minutes, xpos, 70, 7); - xpos += gfx.drawChar(':', xpos, 70, 7); - gfx.drawNumber(ts.seconds, xpos, 70, 7); +void checkLongPress(bool direction, bool held) { + + // Check for a long press... no idea what menu ... but whatever? + if (held) { + // what are we long pressing on? + if(menuMgr.findCurrentActive()->getId() == menuSpin.getId() || menuMgr.findCurrentActive()->getId() == menuWash.getId() ) { + if (platform->isHeaterEnabled()) { + // cancel preheat + platform->enableHeater(false); + } else { + platform->startPreheat(); + } + } } } /** - * Reset all of the icons back to their original state, reset the selecton color value. + * Reset all of the icons back to their original state, reset the selection color value. */ void resetIcons() { @@ -570,3 +593,44 @@ void screenCaptureTask() { } #endif } + +/** + * Handle an encoder movement. Only do anything if we are actively running a program, otherwise we don't care + * + * @param direction + * @param held + */ +void handleEncoderMove(bool direction, bool held) { + +// Serial.println("handleEncoderMove..."); +// if (motorControl != nullptr && motorControl->isRunning()) { +// Serial.println("program running.. change speed"); +// // We are running, track left / right as speed changes.. +// +// int curSpeed = motorControl->getMotorSpeed(); +// +// // TODO Add bounds... +// if (!direction) { +// +// curSpeed = curSpeed - 10; +// } else { +// curSpeed = curSpeed + 10; +// } +// +// motorControl->overrideMotorSpeed(curSpeed); +// Serial.println(curSpeed); + + +// BaseDialog *dlg = renderer.getDialog(); +// if (dlg) { +// dlg->setButtons(BTNTYPE_CLOSE, BTNTYPE_NONE); +// dlg->show("Motor Speed Changed", false); +//// char cstr[16]; +//// itoa(curSpeed, cstr, 10); +//// +//// dlg->copyIntoBuffer(cstr); +// } + + //} + +} diff --git a/src/KiwiBoardFirmware_main.h b/src/KiwiBoardFirmware_main.h index 62927a6..2918370 100644 --- a/src/KiwiBoardFirmware_main.h +++ b/src/KiwiBoardFirmware_main.h @@ -5,6 +5,7 @@ */ #include +#include "EncoderShim.h" #ifndef KIWIBOARDFIRMWARE_KIWIBOARDFIRMWARE_MAIN_H #define KIWIBOARDFIRMWARE_KIWIBOARDFIRMWARE_MAIN_H @@ -51,7 +52,8 @@ void setIconStopped(MenuItem* icon); void resetIcons(); -void renderTimer(unsigned int encoderValue, RenderPressMode clicked); +void checkLongPress(bool direction, bool held); +void handleEncoderMove(bool direction, bool held); #endif //KIWIBOARDFIRMWARE_KIWIBOARDFIRMWARE_MAIN_H diff --git a/src/KiwiBoardFirmware_menu.cpp b/src/KiwiBoardFirmware_menu.cpp index 0ce777a..7128c45 100644 --- a/src/KiwiBoardFirmware_menu.cpp +++ b/src/KiwiBoardFirmware_menu.cpp @@ -1,4 +1,4 @@ -/* +/* The code in this file uses open source libraries provided by thecoderscorner DO NOT EDIT THIS FILE, IT WILL BE GENERATED EVERY TIME YOU USE THE UI DESIGNER @@ -22,10 +22,10 @@ GraphicsDeviceRenderer renderer(30, applicationInfo.name, &gfxDrawable); // Global Menu Item declarations RENDERING_CALLBACK_NAME_INVOKE(fnVersionRtCall, textItemRenderFn, "Version", -1, NO_CALLBACK) TextMenuItem menuVersion(fnVersionRtCall, "1.00", 43, 10, NULL); -const AnalogMenuInfo minfostealthTransition = { "PWM Threshold ", 42, 92, 2000, stealthTransitionChanged, 0, 1, "" }; -AnalogMenuItem menustealthTransition(&minfostealthTransition, 0, &menuVersion, INFO_LOCATION_PGM); +const BooleanMenuInfo minfoStealthChop = { "StealthChop", 45, 96, 1, stealthChopChange, NAMING_ON_OFF }; +BooleanMenuItem menuStealthChop(&minfoStealthChop, true, &menuVersion, INFO_LOCATION_PGM); const AnalogMenuInfo minfoIRun = { "IRun", 33, 77, 31, iRunChanged, 0, 1, "" }; -AnalogMenuItem menuIRun(&minfoIRun, 17, &menustealthTransition, INFO_LOCATION_PGM); +AnalogMenuItem menuIRun(&minfoIRun, 17, &menuStealthChop, INFO_LOCATION_PGM); const AnalogMenuInfo minfoGlobalScaler = { "Global Scaler", 32, 75, 255, GlobalScalerChanged, 0, 1, "" }; AnalogMenuItem menuGlobalScaler(&minfoGlobalScaler, 148, &menuIRun, INFO_LOCATION_PGM); const BooleanMenuInfo minfoInvertEncoder = { "Invert Encoder", 34, 79, 1, NO_CALLBACK, NAMING_YES_NO }; @@ -33,8 +33,10 @@ BooleanMenuItem menuInvertEncoder(&minfoInvertEncoder, false, &menuGlobalScaler, const SubMenuInfo minfoAdvanced = { "Advanced", 30, 0xffff, 0, NO_CALLBACK }; BackMenuItem menuBackAdvanced(&minfoAdvanced, &menuInvertEncoder, INFO_LOCATION_PGM); SubMenuItem menuAdvanced(&minfoAdvanced, &menuBackAdvanced, NULL, INFO_LOCATION_PGM); +const AnalogMenuInfo minfoMaxPreheat = { "Preheat", 44, 94, 9, settings_changed, 1, 1, "min" }; +AnalogMenuItem menuMaxPreheat(&minfoMaxPreheat, 4, NULL, INFO_LOCATION_PGM); const AnalogMenuInfo minfocooldownTime = { "Cool Time", 19, 21, 9, settings_changed, 1, 1, "min" }; -AnalogMenuItem menucooldownTime(&minfocooldownTime, 1, NULL, INFO_LOCATION_PGM); +AnalogMenuItem menucooldownTime(&minfocooldownTime, 1, &menuMaxPreheat, INFO_LOCATION_PGM); const BooleanMenuInfo minfofanCooldown = { "Cooldown", 18, 20, 1, settings_changed, NAMING_ON_OFF }; BooleanMenuItem menufanCooldown(&minfofanCooldown, true, &menucooldownTime, INFO_LOCATION_PGM); const AnalogMenuInfo minfodry_speed = { "Speed", 17, 18, 100, settings_changed, 50, 1, "rpm" }; @@ -48,8 +50,8 @@ const AnalogMenuInfo minfospinAMAX = { "Accel", 40, 88, 2000, settings_changed, AnalogMenuItem menuspinAMAX(&minfospinAMAX, 375, NULL, INFO_LOCATION_PGM); const AnalogMenuInfo minfospin_speed = { "Speed", 14, 14, 350, settings_changed, 50, 1, "rpm" }; AnalogMenuItem menuspin_speed(&minfospin_speed, 50, &menuspinAMAX, INFO_LOCATION_PGM); -const AnalogMenuInfo minfospin_duration = { "Time", 13, 12, 10, settings_changed, 1, 1, "min" }; -AnalogMenuItem menuspin_duration(&minfospin_duration, 0, &menuspin_speed, INFO_LOCATION_PGM); +const AnalogMenuInfo minfospin_duration = { "Time", 13, 12, 119, settings_changed, 1, 1, "sec" }; +AnalogMenuItem menuspin_duration(&minfospin_duration, 44, &menuspin_speed, INFO_LOCATION_PGM); const SubMenuInfo minfoSpinSettings = { "Spin", 12, 0xffff, 0, NO_CALLBACK }; BackMenuItem menuBackSpinSettings(&minfoSpinSettings, &menuspin_duration, INFO_LOCATION_PGM); SubMenuItem menuSpinSettings(&minfoSpinSettings, &menuBackSpinSettings, &menuDrySettings, INFO_LOCATION_PGM); @@ -94,8 +96,7 @@ void setupMenu() { gfx.begin(); gfx.setRotation(3); renderer.setUpdatesPerSecond(10); - switches.init(internalDigitalIo(), SWITCHES_NO_POLLING, true); - menuMgr.initForEncoder(&renderer, &menuRunTime, ENC1, ENC2, BUTTON); + menuMgr.initWithoutInput(&renderer, &menuRunTime); renderer.setTitleMode(BaseGraphicalRenderer::TITLE_ALWAYS); renderer.setUseSliderForAnalog(false); installDarkModeModernTheme(renderer, MenuFontDef(nullptr, 4), MenuFontDef(nullptr, 4), false); diff --git a/src/KiwiBoardFirmware_menu.h b/src/KiwiBoardFirmware_menu.h index a061790..27b480b 100644 --- a/src/KiwiBoardFirmware_menu.h +++ b/src/KiwiBoardFirmware_menu.h @@ -1,4 +1,4 @@ -/* +/* The code in this file uses open source libraries provided by thecoderscorner DO NOT EDIT THIS FILE, IT WILL BE GENERATED EVERY TIME YOU USE THE UI DESIGNER @@ -18,7 +18,7 @@ #include #include #include -#include "picoPlatform.h" + // variables we declare that you may need to access extern const PROGMEM ConnectorLocalInfo applicationInfo; extern TFT_eSPI gfx; @@ -30,12 +30,13 @@ extern GraphicsDeviceRenderer renderer; // Global Menu Item exports extern TextMenuItem menuVersion; -extern AnalogMenuItem menustealthTransition; +extern BooleanMenuItem menuStealthChop; extern AnalogMenuItem menuIRun; extern AnalogMenuItem menuGlobalScaler; extern BooleanMenuItem menuInvertEncoder; extern BackMenuItem menuBackAdvanced; extern SubMenuItem menuAdvanced; +extern AnalogMenuItem menuMaxPreheat; extern AnalogMenuItem menucooldownTime; extern BooleanMenuItem menufanCooldown; extern AnalogMenuItem menudry_speed; @@ -75,7 +76,7 @@ void CALLBACK_FUNCTION dry(int id); void CALLBACK_FUNCTION iRunChanged(int id); void CALLBACK_FUNCTION settings_changed(int id); void CALLBACK_FUNCTION spin(int id); -void CALLBACK_FUNCTION stealthTransitionChanged(int id); +void CALLBACK_FUNCTION stealthChopChange(int id); void CALLBACK_FUNCTION wash(int id); #endif // MENU_GENERATED_CODE_H diff --git a/src/heat.h b/src/heat.h new file mode 100644 index 0000000..5c52e1e --- /dev/null +++ b/src/heat.h @@ -0,0 +1,46 @@ +// Heat icon=0, width=50, height=50, size=350 +const uint8_t HeatWidIcon0[] PROGMEM = { +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 +}; +// Heat icon=1, width=50, height=50, size=350 +const uint8_t HeatWidIcon1[] PROGMEM = { +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x00,0x00,0x00,0x00,0x38,0x0c,0x07,0x00,0x00,0x00,0x00,0x78,0x1c,0x07,0x00,0x00,0x00,0x00,0x70,0x38, +0x0e,0x00,0x00,0x00,0x00,0xe0,0x38,0x0c,0x00,0x00,0x00,0x00,0xe0,0x30,0x1c,0x00,0x00,0x00,0x00,0xe0, +0x30,0x1c,0x00,0x00,0x00,0x00,0xe0,0x30,0x1c,0x00,0x00,0x00,0x00,0xe0,0x38,0x0c,0x00,0x00,0x00,0x00, +0x60,0x38,0x0e,0x00,0x00,0x00,0x00,0x70,0x18,0x0e,0x00,0x00,0x00,0x00,0x70,0x1c,0x06,0x00,0x00,0x00, +0x00,0x38,0x0c,0x07,0x00,0x00,0x00,0x00,0x38,0x0e,0x03,0x00,0x00,0x00,0x00,0x1c,0x86,0x03,0x00,0x00, +0x00,0x00,0x1c,0x87,0x01,0x00,0x00,0x00,0x00,0x0c,0xc7,0x01,0x00,0x00,0x00,0x00,0x0e,0xc3,0x01,0x00, +0x00,0x00,0x00,0x8e,0xc3,0x00,0x00,0x00,0x00,0x00,0x8e,0xc3,0x00,0x00,0x00,0x00,0x00,0x0e,0xc3,0x01, +0x00,0x00,0x00,0x00,0x0c,0xc7,0x01,0x00,0x00,0x00,0x00,0x1c,0x87,0x03,0x00,0x00,0x00,0x00,0x10,0x04, +0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0x01,0x00,0x00,0xe0,0xff, +0xff,0xff,0x01,0x00,0x00,0xc0,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 +}; +const uint8_t* const HeatWidIcons[] PROGMEM = { HeatWidIcon0, HeatWidIcon1 }; + +// Widget Generator heat +TitleWidget HeatWidget(HeatWidIcons, 2, 50, 50, nullptr); diff --git a/src/motorControl.cpp b/src/motorControl.cpp index dcbc691..ddcd61b 100644 --- a/src/motorControl.cpp +++ b/src/motorControl.cpp @@ -5,7 +5,7 @@ #include "motorControl.h" #include "picoPlatform.h" -void MotorControl::initMotionController(PicoPlatform *curPlatform, uint16_t globalScaler, uint16_t iRun, uint16_t transition) { +void MotorControl::initMotionController(PicoPlatform *curPlatform, uint16_t globalScaler, uint16_t iRun, bool stealthChop) { this->platform = curPlatform; // Set the current platform. // start motor controller. wants to be enabled to talk to it.. @@ -18,16 +18,22 @@ void MotorControl::initMotionController(PicoPlatform *curPlatform, uint16_t glob motorParams.irun = iRun; // 31 is max running current, adjust accordingly. motorParams.ihold = 0; // holding current, 0 enables freewheel - // Library initializes with StealthChop enabled. motor = new TMC5160_SPI(TMC_SS, TMC5160::DEFAULT_F_CLK, SPISettings(1000000, MSBFIRST, SPI_MODE0), SPI1); motor->begin(powerStageParams, motorParams, TMC5160::NORMAL_MOTOR_DIRECTION); // Disable the transition to SpreadCycle, we don't need accuracy, prefer StealthChop - // motor->writeRegister(TMC5160_Reg::TPWMTHRS, 0); - motor->writeRegister(TMC5160_Reg::TPWMTHRS, transition); - + if (stealthChop) { + // disable transition to SpreadCycle + motor->writeRegister(TMC5160_Reg::TPWMTHRS, 0); + } else { + // turn off stealth chop in the gconf register + TMC5160_Reg::GCONF_Register gconf = { 0 }; + gconf.en_pwm_mode = false; + gconf.shaft = TMC5160::NORMAL_MOTOR_DIRECTION; + motor->writeRegister(TMC5160_Reg::GCONF, gconf.value); + } motor->stop(); // Ensure the controller is initialized with the motor stopped @@ -93,6 +99,7 @@ void MotorControl::startProgram(int programId, SETTINGS currentSettings) { // Set speed in full steps per second. motor->setMaxSpeed(rpmToVmax(currentSettings.wash_speed)); + state.rpm = currentSettings.wash_speed; // Register for VMAX is expressed in uSteps / t // t is 1.398101 at 12mhz @@ -109,7 +116,8 @@ void MotorControl::startProgram(int programId, SETTINGS currentSettings) { state.direction = false; } else if (programId == 1) { // spin-off.. - state.run_end = state.run_start + (currentSettings.spin_duration * 60000); + // Spin is configured in seconds... not minutes... + state.run_end = state.run_start + (currentSettings.spin_duration * 1000); state.isRunning = true; // Enable drive, active low @@ -123,6 +131,7 @@ void MotorControl::startProgram(int programId, SETTINGS currentSettings) { motor->setAcceleration(currentSettings.spin_amax); motor->setMaxSpeed(rpmToVmax(currentSettings.spin_speed)); // Full steps per second + state.rpm = currentSettings.spin_speed; state.direction = true; } else { @@ -142,6 +151,7 @@ void MotorControl::startProgram(int programId, SETTINGS currentSettings) { motor->setRampMode(TMC5160::VELOCITY_MODE); motor->setAcceleration(600); motor->setMaxSpeed(rpmToVmax(currentSettings.dry_speed)); + state.rpm = currentSettings.dry_speed; state.direction = true; } @@ -275,12 +285,17 @@ float MotorControl::rpmToVmax(float rpm) { return vmax; } -void MotorControl::setPwmTransitionTime(int transitionTime) { - // Disable TMC, Enable, Wait to stabalize, and set the value - platform->enableMotor(false); - delay(10); - platform->enableMotor(true); - delay(10); - motor->writeRegister(TMC5160_Reg::TPWMTHRS, transitionTime); +int MotorControl::getMotorSpeed() { + return state.rpm; +} + +void MotorControl::overrideMotorSpeed(int speedRPM) { + + state.rpm = speedRPM; + + // reconfigure VMAX? This might cause problems in wash cycle.. + + motor->setMaxSpeed(rpmToVmax(speedRPM)); + } diff --git a/src/motorControl.h b/src/motorControl.h index 3624069..df31aa3 100644 --- a/src/motorControl.h +++ b/src/motorControl.h @@ -20,7 +20,7 @@ class MotorControl : public Executable /** * Initialize the TMC5160 motion controller running on the provided platform. */ - void initMotionController(PicoPlatform *curPlatform, uint16_t globalScaler, uint16_t iRun, uint16_t transition); + void initMotionController(PicoPlatform *curPlatform, uint16_t globalScaler, uint16_t iRun, bool stealthChop); void stopMotion(); void startProgram(int programId, SETTINGS currentSettings); @@ -50,14 +50,23 @@ class MotorControl : public Executable */ void setStoppedCallback(MotorCallbackFn stopFn); + /** - * Set the value of TPWMTHRS, the transition velocity between StealthChop and SpreadCycle. This - * is set in terms of TSTEP (time per micro-step) + * Set if StealthChop should be enabled, or if we should only use SpreadCycle. * - * @param transitionTime + * @param enabled + */ + void setStealthChop(bool enabled); + + /** + * Return the current configured motor speed. */ - void setPwmTransitionTime(int transitionTime); + int getMotorSpeed(); + /** + * Override the current motor speed. + */ + void overrideMotorSpeed(int speedRPM); /** * Called by task loop. This is what will process any motor control tasks necessary to execute programs. diff --git a/src/picoPlatform.cpp b/src/picoPlatform.cpp index 0b4d8b1..b25b506 100644 --- a/src/picoPlatform.cpp +++ b/src/picoPlatform.cpp @@ -42,7 +42,7 @@ void PicoPlatform::initializePlatform() { pinMode(LCD_BACKLIGHT, OUTPUT); analogWrite(LCD_BACKLIGHT, 125); - + //Setup SPI0 for the TFT SPI.setCS(LCD_CS); SPI.setRX(LCD_MISO); @@ -70,6 +70,9 @@ void PicoPlatform::enableHeater(bool activate) { // If we are turning the heater off, the cooldown logic will turn off the fan if (activate) { enableFan(true); + if (in_preheat) { + in_preheat = false; // IF a preheat already happened, stop that process + } } } @@ -126,9 +129,9 @@ void PicoPlatform::exec() { if (in_cooldown) { Serial.println("in cooldown"); - Serial.print("- cooldown_end = " ) ; + Serial.print("- cooldown_end = "); Serial.println(cooldown_end); - Serial.print("- millis.. " ); + Serial.print("- millis.. "); Serial.println(millis()); if (millis() >= cooldown_end) { Serial.println("cooldown over, turn off fan"); @@ -137,6 +140,13 @@ void PicoPlatform::exec() { } } + // Check for preheat ending.. + if (in_preheat) { + if (millis() >= preheat_end) { + startCooldown(); + } + } + // Heartbeat led = !led; digitalWrite(LED_BUILTIN, led); @@ -148,6 +158,7 @@ void PicoPlatform::startCooldown() { // turn off the heat enableHeater(false); + in_preheat = false; if (settings.fanCooldown) { // cooldown enabled, calculate the fan end time, mark start time. ::tick will handle turning fan off @@ -168,3 +179,15 @@ void PicoPlatform::setBacklight(int value) { analogWrite(LCD_BACKLIGHT, backlight); } + +void PicoPlatform::startPreheat() { + SETTINGS settings = getSettings(); + + // turn on the heat + enableHeater(true); + + // preheat enabled, calculate the preheat end time, mark start time. ::tick will handle finishing preheat + preheat_start = millis(); + preheat_end = preheat_start + (settings.preheatTime * 60000); + in_preheat = true; +} diff --git a/src/picoPlatform.h b/src/picoPlatform.h index afff51f..653c3f3 100644 --- a/src/picoPlatform.h +++ b/src/picoPlatform.h @@ -73,6 +73,14 @@ class PicoPlatform : public Executable { */ void startCooldown(); + /** + * Start an optional preheat cycle. + * + * Look at the current configuration, calculate preheat end, and start time. A call to startCooldown + * will override any running preheat. + */ + void startPreheat(); + /** * To be called by task loop. This is charged with updating any state changes necessary with the * platform outside of direct motor control. @@ -94,6 +102,7 @@ class PicoPlatform : public Executable { bool isFanEnabled(); private: + bool in_preheat = false; bool in_cooldown = false; bool motor_enabled = false; bool heater_enabled = false; @@ -102,5 +111,7 @@ class PicoPlatform : public Executable { unsigned long cooldown_start; // when did a cooldown start. unsigned long cooldown_end; // when should a cooldown stop. + unsigned long preheat_start; + unsigned long preheat_end; }; \ No newline at end of file diff --git a/src/settings.cpp b/src/settings.cpp index 81ab3e5..3c7c929 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -22,6 +22,8 @@ SETTINGS getSettings() { rtn.cooldownTime = menucooldownTime.getIntValueIncludingOffset(); rtn.fanCooldown = menufanCooldown.getBoolean(); + rtn.preheatTime = menuMaxPreheat.getIntValueIncludingOffset(); + return rtn; } diff --git a/src/settings.h b/src/settings.h index 968a21e..39fb343 100644 --- a/src/settings.h +++ b/src/settings.h @@ -23,7 +23,9 @@ struct SETTINGS int dry_duration; int dry_speed; int cooldownTime; + int preheatTime; bool fanCooldown; + }; From d148ca17c47ca47c584fb6e0724b13e7801c945a Mon Sep 17 00:00:00 2001 From: Kevin Balthaser Date: Thu, 4 May 2023 10:40:58 -0400 Subject: [PATCH 2/3] Add ability to start/stop and configurable preheat when starting a cycle. - Required adding a new `EncoderShim` to intercept all encoder interactions so that we can know a long-press has occurred. - Preheat is indicated by a title bar widget. - Preheat start/stop with long-press is a toggle of current state, it is not impacted in any way by what operation was being done (ie, you start a preheat while stopping a spin cycle with a long-press) - Made encoder inversion work in new shim - Switch StealthChop transition point into a simple on/off for StealthChop as a whole. There isn't any point in having stealthchop enabled at all if we cannot use it across full motor speed range, as the transition is abbrupt, and causes a large jerk. --- src/EncoderShim.cpp | 9 +-------- src/EncoderShim.h | 21 +++++++++++++++++--- src/KiwiBoardFirmware_main.cpp | 36 ++-------------------------------- src/KiwiBoardFirmware_main.h | 2 +- src/picoPlatform.cpp | 4 ++-- 5 files changed, 24 insertions(+), 48 deletions(-) diff --git a/src/EncoderShim.cpp b/src/EncoderShim.cpp index e1dd824..e055f7e 100644 --- a/src/EncoderShim.cpp +++ b/src/EncoderShim.cpp @@ -5,8 +5,7 @@ #include "EncoderShim.h" -EncoderShim::EncoderShim(MenuManager *manager) { - menuManager = manager; +EncoderShim::EncoderShim() { } @@ -15,11 +14,7 @@ void EncoderShim::initForEncoder() { switches.init(internalDigitalIo(), SWITCHES_NO_POLLING, true); switches.addSwitchListener(BUTTON, this, NO_REPEAT, false); - //switches.addSwitch(encoderButton, nullptr); - //switches.onRelease(encoderButton, EncoderShim::*buttonRelease); setupRotaryEncoderWithInterrupt(ENC1, ENC2, this, HWACCEL_NONE, FULL_CYCLE); - //setupRotaryEncoderWithInterrupt(encoderPinA, encoderPinB, [](int value) {menuMgr.valueChanged(value); }, HWACCEL_REGULAR, type); - } void EncoderShim::encoderHasChanged(int newValue) { @@ -34,8 +29,6 @@ void EncoderShim::encoderHasChanged(int newValue) { direction = true; } - //Serial.println(direction); - encoderValue = newValue; menuMgr.valueChanged(newValue); diff --git a/src/EncoderShim.h b/src/EncoderShim.h index b851399..badb054 100644 --- a/src/EncoderShim.h +++ b/src/EncoderShim.h @@ -10,7 +10,6 @@ #include #ifndef KIWIBOARDFIRMWARE_ENCODERSHIM_H - #define KIWIBOARDFIRMWARE_ENCODERSHIM_H @@ -20,11 +19,27 @@ typedef void (*EncoderShimFn)(bool direction, bool longPress); class EncoderShim : EncoderListener, SwitchListener { public: - EncoderShim(MenuManager* manager); + EncoderShim(); + /** + * Initialize the encoder and button for pins defined in picoPlatform. This must be called + * before setupMenu. + */ void initForEncoder(); + + /** + * Register the function that should be called when the encoder value changes. + */ void registerChangeCallback(EncoderShimFn callback); + + /** + * Register the function that should be called when the encoder button has been pressed. + */ void registerClickCallback(EncoderShimFn callback); + + /** + * Swap the two encoder pins, this will invert the direction of the encoder in all modes. + */ void invertEncoderDirection(); private: @@ -34,7 +49,7 @@ class EncoderShim : EncoderListener, SwitchListener { void onReleased(pinid_t pin, bool held) override; - MenuManager* menuManager; + // Callback Functions for click and change EncoderShimFn encoderChangeFn = nullptr; EncoderShimFn encoderClickFn = nullptr; diff --git a/src/KiwiBoardFirmware_main.cpp b/src/KiwiBoardFirmware_main.cpp index 04e07c5..ed4e4cc 100644 --- a/src/KiwiBoardFirmware_main.cpp +++ b/src/KiwiBoardFirmware_main.cpp @@ -58,8 +58,6 @@ void setup() { // Init the graphics subsystem and trigger the splash. gfx.begin(); gfx.setRotation(3); - - // turn on the LED if DMA init successfully.. gfx.initDMA(true); showSplash(); @@ -68,7 +66,7 @@ void setup() { gfx.fillScreen(TFT_BLACK); // Setup switches and encoder? - encoderShim = new EncoderShim(&menuMgr); + encoderShim = new EncoderShim(); encoderShim->initForEncoder(); encoderShim->registerChangeCallback(handleEncoderMove); encoderShim->registerClickCallback(checkLongPress); @@ -102,9 +100,6 @@ void setup() { menuVersion.setTextValue(VERSION_NUM, true); setMenuOptions(); - - setTitlePressedCallback(titleBarClick); - scheduleTasks(); } @@ -174,6 +169,7 @@ void ui_tick() { drawable->startDraw(); drawable->drawXBitmap(Coord(135, 0), Coord(50, 50), KiwiLogoWidIcon0); drawable->endDraw(); + } // Check for heat icon, if the heater is on, show the icon, otherwise swap in the blank widget @@ -239,26 +235,6 @@ void motorErrorDialog(TMC5160::DriverStatus status) { } } -/** - * Show version dialog. - * - * @param id - */ -void titleBarClick(int id) { - - const char error[] PROGMEM = "Free Heap Usage"; - - BaseDialog *dlg = renderer.getDialog(); - if (dlg) { - dlg->setButtons(BTNTYPE_NONE, BTNTYPE_CLOSE); - dlg->show(error, false); - int freeHeap = rp2040.getFreeHeap(); - char cstr[16]; - itoa(freeHeap, cstr, 10); - dlg->copyIntoBuffer(cstr); - } -} - void CALLBACK_FUNCTION wash(int id) { run(0, &menuWash); } @@ -423,8 +399,6 @@ void setMenuOptions() { // first we get the graphics factory auto &factory = renderer.getGraphicsPropertiesFactory(); - // renderer.takeOverDisplay(checkLongPress); - // don't do the periodic reset, which causes some awkward redraw flashes. renderer.turnOffResetLogic(); @@ -461,10 +435,6 @@ void setMenuOptions() { GridPosition::JUSTIFY_CENTER_VALUE_ONLY, MenuBorder(0)); // Settings for the Settings menu - // // here is how we completely redefine the drawing of a specific item, you can also define for submenu or default - //color_t specialPalette[] { RGB(0, 255, 0), RGB(255, 0, 0), RGB(70, 70, 70), RGB(0, 0, 255) }; - - // TODO work out these how to style these, because currently, you can't see the cursor when editing multi part large numbers factory.setDrawingPropertiesAllInSub(ItemDisplayProperties::COMPTYPE_ITEM, menuSettings.getId(), settingsMenuPalette, MenuPadding(4), nullptr, 4, 2, 36, @@ -497,7 +467,6 @@ void setMenuOptions() { renderer.setFirstWidget(&HeatWidget); // Black on white cursor -// factory.setSelectedColors(RGB(255, 0, 0), RGB(0, 255, 0)); factory.setSelectedColors(RGB(255, 255, 255), RGB(0, 0, 0)); // Home the user to the wash menu option @@ -563,7 +532,6 @@ void resetIcons() { factory.addImageToCache(DrawableIcon(menuDry.getId(), iconSize, DrawableIcon::ICON_XBITMAP, CycleIconsBitmap2)); factory.addImageToCache( DrawableIcon(menuSettings.getId(), iconSize, DrawableIcon::ICON_XBITMAP, CycleIconsBitmap3)); - MenuPadding perSidePadding(3, 3, 3, 3); if (stoppedButton != nullptr) { MenuPadding buttonPadding(4, 4, 4, 4); diff --git a/src/KiwiBoardFirmware_main.h b/src/KiwiBoardFirmware_main.h index 2918370..8d5744f 100644 --- a/src/KiwiBoardFirmware_main.h +++ b/src/KiwiBoardFirmware_main.h @@ -52,7 +52,7 @@ void setIconStopped(MenuItem* icon); void resetIcons(); -void checkLongPress(bool direction, bool held); +void checkLongPress(bool direction, bool clicked); void handleEncoderMove(bool direction, bool held); diff --git a/src/picoPlatform.cpp b/src/picoPlatform.cpp index b25b506..0590677 100644 --- a/src/picoPlatform.cpp +++ b/src/picoPlatform.cpp @@ -31,7 +31,7 @@ void PicoPlatform::initializePlatform() { pinMode(LED_BUILTIN, OUTPUT); digitalWrite(LED_BUILTIN, HIGH); - // Remap IO to the correct pins for hardware SPI 0 + // Remap IO to the correct pins for hardware SPI 1 SPI1.setRX(TMC_MISO); SPI1.setTX(TMC_MOSI); SPI1.setSCK(TMC_SCLK); @@ -43,7 +43,7 @@ void PicoPlatform::initializePlatform() { pinMode(LCD_BACKLIGHT, OUTPUT); analogWrite(LCD_BACKLIGHT, 125); - //Setup SPI0 for the TFT + //Setup Hardware SPI0 for the TFT SPI.setCS(LCD_CS); SPI.setRX(LCD_MISO); SPI.setTX(LCD_MOSI); From 5a1de6295b24816bfc8a5892b823797df6c7768c Mon Sep 17 00:00:00 2001 From: Kevin Balthaser Date: Sat, 6 May 2023 10:03:04 -0400 Subject: [PATCH 3/3] Version number for release. --- src/KiwiBoardFirmware_main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/KiwiBoardFirmware_main.cpp b/src/KiwiBoardFirmware_main.cpp index ed4e4cc..2b736f2 100644 --- a/src/KiwiBoardFirmware_main.cpp +++ b/src/KiwiBoardFirmware_main.cpp @@ -19,7 +19,7 @@ #include "MenuChangeObserver.h" // Version Number -const char VERSION_NUM[] PROGMEM = "1.*-PreHeat"; +const char VERSION_NUM[] PROGMEM = "1.1.0"; PicoPlatform *platform;