diff --git a/doc/pinout.md b/doc/pinout.md index fa7df40..578cae0 100644 --- a/doc/pinout.md +++ b/doc/pinout.md @@ -18,8 +18,8 @@ https://www.pololu.com/product/2995 | Identifier | Description | Arduino pin | ATMega328p pin | |------------|----------------------------|-------------|----------------| -| PWM | Motor PWM | 14 | 23 - PC0 | -| DIR | Direction | 15 | 24 - PC1 | +| PWM | Motor PWM | 14 / A0 | 23 - PC0 | +| DIR | Direction | 15 / A1 | 24 - PC1 | | SLP | Sleep (active low) | 16 | 25 - PC2 | | CS | Current sensing | 17 / A3 | 26 - PC3 | diff --git a/src/include/config.h b/src/include/config.h index fbe2365..104add0 100644 --- a/src/include/config.h +++ b/src/include/config.h @@ -151,6 +151,9 @@ class Config static const uint16_t ColorMenuHeaderText = ColorWhite; static const uint16_t ColorMenuHeaderBackground = ColorSoftBlue; + static const uint16_t ColorMenuText = ColorWhite; + static const uint16_t ColorMenuBackground = ColorBlack; + static const uint16_t ColorMenuSelectedText = ColorWhite; static const uint16_t ColorMenuSelectedBackground = ColorDarkBlue; diff --git a/src/include/screenids.h b/src/include/screenids.h index 44942e8..26bea1e 100644 --- a/src/include/screenids.h +++ b/src/include/screenids.h @@ -9,7 +9,8 @@ enum class ScreenId MoveOvercurrent, MoveSensorError, Menu, - Manual + Manual, + Presets }; #endif \ No newline at end of file diff --git a/src/lib/motor.cpp b/src/lib/motor.cpp index 76e255a..63f1be6 100644 --- a/src/lib/motor.cpp +++ b/src/lib/motor.cpp @@ -28,6 +28,7 @@ void motorStop() { digitalWrite(Config::MotorPinPWM, LOW); digitalWrite(Config::MotorPinSleep, LOW); + digitalWrite(Config::MotorPinDirection, LOW); } diff --git a/src/lib/screen/baseheightentry.cpp b/src/lib/screen/baseheightentry.cpp new file mode 100644 index 0000000..676533f --- /dev/null +++ b/src/lib/screen/baseheightentry.cpp @@ -0,0 +1,162 @@ +#include "./calibrate.h" +#include "./home.h" +#include "include/metrics.h" +#include "lib/settings.h" + + +void BaseHeightEntryScreen::onShow() +{ + this->display->setFont(Metrics::LargeFont); + this->display->setTextSize(Metrics::LargeFontTextSize); + + this->display->fillScreen(Config::ColorHomeBackground); + + this->drawTitle(); + this->initHeights(); + + this->drawArrowUp(Metrics::ArrowMargin, Metrics::LargeTextLineHeight + Metrics::LargeTextLineVArrowYOffset, Config::ColorCalibrateIndicators); + this->drawArrowRight(Metrics::ArrowMargin, Metrics::MiddleLargeTextLineY + Metrics::LargeTextLineHArrowYOffset, Config::ColorCalibrateIndicators); + + this->display->setTextColor(Config::ColorCalibrateIndicators); + this->display->setCursor(Metrics::ArrowMargin, Config::DisplayHeight - Metrics::LargeTextLineHeight + Metrics::LargeTextLineYOffset); + this->display->print("OK"); + + this->drawSetHeight(); +} + + +// Credit: https://stackoverflow.com/questions/101439/the-most-efficient-way-to-implement-an-integer-based-power-function-powint-int +uint16_t ipow(uint16_t base, uint8_t exp) +{ + int result = 1; + for (;;) + { + if (exp & 1) + result *= base; + + exp >>= 1; + if (!exp) + break; + + base *= base; + } + + return result; +} + + +void BaseHeightEntryScreen::onButton(Button button) +{ + switch (button) + { + case Button::Top: + { + uint16_t height = this->getHeight(); + uint16_t increment = ipow(10, 3 - this->editingDigit); + uint16_t modulus = increment * 10; + + uint16_t remainder = height % modulus; + uint16_t offset = height - remainder; + + height = offset + ((remainder + increment) % modulus); + if (height > 1999) + height %= 2000; + + this->setHeight(height); + this->drawSetHeight(); + break; + } + + case Button::Middle: + { + this->editingDigit++; + if (this->editingDigit > 3) + this->editingDigit = 0; + + this->drawSetHeight(); + break; + } + + case Button::Bottom: + { + if (!this->isValidHeight()) + return; + + if (this->nextPage()) + { + this->editingDigit = 0; + + this->drawTitle(); + this->drawSetHeight(); + } + + break; + } + } +} + + +void BaseHeightEntryScreen::onTick() +{ +} + + +void BaseHeightEntryScreen::drawTitle() +{ + this->drawLargeTextLineCentered(this->getTitle(), 0, Config::ColorMenuHeaderText, Config::ColorMenuHeaderBackground); +} + + +void BaseHeightEntryScreen::drawSetHeight() +{ + char heightText[7]; + uint16_t height = this->getHeight(); + + if (height > 999) + heightText[0] = '0' + ((height / 1000) % 10); + else + heightText[0] = '0'; + + heightText[1] = '.'; + heightText[2] = '0' + ((height / 100) % 10); + heightText[3] = '0' + ((height / 10) % 10); + heightText[4] = '0' + (height % 10); + heightText[5] = 'm'; + heightText[6] = 0; + + + int16_t textX; + int16_t textY; + uint16_t textW; + uint16_t textH; + this->display->getTextBounds(&heightText[0], 0, 0, &textX, &textY, &textW, &textH); + textX = (Config::DisplayWidth - textW) / 2; + + + + uint8_t editingChar = this->editingDigit; + + // Skip the dot + if (editingChar > 0) + editingChar++; + + + if (this->lastTextWidth > 0) + this->display->fillRect((Config::DisplayWidth - this->lastTextWidth) / 2, Metrics::MiddleLargeTextLineY, this->lastTextWidth, Metrics::LargeTextLineHeight, Config::ColorCalibrateBackground); + + this->lastTextWidth = textW; + + this->display->setTextColor(this->isValidHeight() ? Config::ColorCalibrateValue : Config::ColorCalibrateInvalidValue); + this->display->setCursor(textX, Metrics::MiddleLargeTextLineY + Metrics::LargeTextLineYOffset); + + // Draw each character ourselves so we can keep track of where the line should be drawn + for (uint8_t i = 0; i < sizeof(heightText); i++) + { + int16_t cursorStart = this->display->getCursorX(); + + this->display->print(heightText[i]); + + if (i == editingChar) + this->display->drawFastHLine(cursorStart, Metrics::MiddleLargeTextLineY + Metrics::LargeTextLineHeight - 1, this->display->getCursorX() - cursorStart, Config::ColorCalibrateDigitMarker); + } +} \ No newline at end of file diff --git a/src/lib/screen/baseheightentry.h b/src/lib/screen/baseheightentry.h new file mode 100644 index 0000000..d5901a6 --- /dev/null +++ b/src/lib/screen/baseheightentry.h @@ -0,0 +1,34 @@ +#ifndef __screen_baseheightentry +#define __screen_baseheightentry + +#include "../screen.h" +#include "../control.h" + + +class BaseHeightEntryScreen : public BaseScreen +{ + public: + BaseHeightEntryScreen(ScreenManager* screenManager, Adafruit_GFX* display) : BaseScreen(screenManager, display) { } + + void onShow(); + void onButton(Button button); + void onTick(); + + protected: + virtual void initHeights() = 0; + virtual uint16_t getHeight() = 0; + virtual void setHeight(uint16_t value) = 0; + virtual bool nextPage() = 0; + + virtual bool isValidHeight() = 0; + virtual const char* getTitle() = 0; + + private: + void drawTitle(); + void drawSetHeight(); + + uint16_t lastTextWidth = 0; + uint8_t editingDigit = 0; +}; + +#endif diff --git a/src/lib/screen/calibrate.cpp b/src/lib/screen/calibrate.cpp index 04003f7..f9f9e95 100644 --- a/src/lib/screen/calibrate.cpp +++ b/src/lib/screen/calibrate.cpp @@ -4,110 +4,28 @@ #include "lib/settings.h" -void CalibrateScreen::onShow() + +void CalibrateScreen::initHeights() { - this->display->setFont(Metrics::LargeFont); - this->display->setTextSize(Metrics::LargeFontTextSize); - - this->display->fillScreen(Config::ColorHomeBackground); - - this->drawTitle(); this->height[CalibrateStepCurrent] = Control.getCurrentHeight(); this->height[CalibrateStepMax] = Settings.Height.Maximum; this->height[CalibrateStepMin] = Settings.Height.Minimum; - - this->drawArrowUp(Metrics::ArrowMargin, Metrics::LargeTextLineHeight + Metrics::LargeTextLineVArrowYOffset, Config::ColorCalibrateIndicators); - this->drawArrowRight(Metrics::ArrowMargin, Metrics::MiddleLargeTextLineY + Metrics::LargeTextLineHArrowYOffset, Config::ColorCalibrateIndicators); - - this->display->setTextColor(Config::ColorCalibrateIndicators); - this->display->setCursor(Metrics::ArrowMargin, Config::DisplayHeight - Metrics::LargeTextLineHeight + Metrics::LargeTextLineYOffset); - this->display->print("OK"); - - this->drawSetHeight(); } -// Credit: https://stackoverflow.com/questions/101439/the-most-efficient-way-to-implement-an-integer-based-power-function-powint-int -uint16_t ipow(uint16_t base, uint8_t exp) +bool CalibrateScreen::nextPage() { - int result = 1; - for (;;) - { - if (exp & 1) - result *= base; + this->step = (CalibrateStep)(this->step + 1); + if (this->step < __CalibrateStepCount) + return true; - exp >>= 1; - if (!exp) - break; + Settings.Height.Offset = this->height[CalibrateStepCurrent] - Control.getCurrentMeasurement(); + Settings.Height.Maximum = this->height[CalibrateStepMax]; + Settings.Height.Minimum = this->height[CalibrateStepMin]; + writeSettingsHeights(); - base *= base; - } - - return result; -} - - -void CalibrateScreen::onButton(Button button) -{ - switch (button) - { - case Button::Top: - { - uint16_t height = this->height[this->step]; - uint16_t increment = ipow(10, 3 - this->editingDigit); - uint16_t modulus = increment * 10; - - uint16_t remainder = height % modulus; - uint16_t offset = height - remainder; - - height = offset + ((remainder + increment) % modulus); - if (height > 1999) - height %= 2000; - - this->height[this->step] = height; - this->drawSetHeight(); - break; - } - - case Button::Middle: - { - this->editingDigit++; - if (this->editingDigit > 3) - this->editingDigit = 0; - - this->drawSetHeight(); - break; - } - - case Button::Bottom: - { - if (!this->isValidHeight()) - return; - - this->step = (CalibrateStep)(this->step + 1); - if (this->step == CalibrateStepCount) - { - Settings.Height.Offset = this->height[CalibrateStepCurrent] - Control.getCurrentMeasurement(); - Settings.Height.Maximum = this->height[CalibrateStepMax]; - Settings.Height.Minimum = this->height[CalibrateStepMin]; - writeSettingsHeights(); - - this->screenManager->show(); - } - else - { - this->drawTitle(); - this->drawSetHeight(); - } - - break; - } - } -} - - -void CalibrateScreen::onTick() -{ + this->screenManager->show(); + return false; } @@ -132,78 +50,20 @@ bool CalibrateScreen::isValidHeight() } -void CalibrateScreen::drawTitle() +const char* CalibrateScreen::getTitle() { switch (this->step) { case CalibrateStepCurrent: - this->drawLargeTextLineCentered("Calibrate", 0, Config::ColorMenuHeaderText, Config::ColorMenuHeaderBackground); - break; + return "Calibrate"; case CalibrateStepMin: - this->drawLargeTextLineCentered("Minimum", 0, Config::ColorMenuHeaderText, Config::ColorMenuHeaderBackground); - break; + return "Minimum"; case CalibrateStepMax: - this->drawLargeTextLineCentered("Maximum", 0, Config::ColorMenuHeaderText, Config::ColorMenuHeaderBackground); - break; + return "Maximum"; default: - break; - } -} - - -void CalibrateScreen::drawSetHeight() -{ - char heightText[7]; - uint16_t height = this->height[this->step]; - - if (height > 999) - heightText[0] = '0' + ((height / 1000) % 10); - else - heightText[0] = '0'; - - heightText[1] = '.'; - heightText[2] = '0' + ((height / 100) % 10); - heightText[3] = '0' + ((height / 10) % 10); - heightText[4] = '0' + (height % 10); - heightText[5] = 'm'; - heightText[6] = 0; - - - int16_t textX; - int16_t textY; - uint16_t textW; - uint16_t textH; - this->display->getTextBounds(&heightText[0], 0, 0, &textX, &textY, &textW, &textH); - textX = (Config::DisplayWidth - textW) / 2; - - - - uint8_t editingChar = this->editingDigit; - - // Skip the dot - if (editingChar > 0) - editingChar++; - - - if (this->lastTextWidth > 0) - this->display->fillRect((Config::DisplayWidth - this->lastTextWidth) / 2, Metrics::MiddleLargeTextLineY, this->lastTextWidth, Metrics::LargeTextLineHeight, Config::ColorCalibrateBackground); - - this->lastTextWidth = textW; - - this->display->setTextColor(this->isValidHeight() ? Config::ColorCalibrateValue : Config::ColorCalibrateInvalidValue); - this->display->setCursor(textX, Metrics::MiddleLargeTextLineY + Metrics::LargeTextLineYOffset); - - // Draw each character ourselves so we can keep track of where the line should be drawn - for (uint8_t i = 0; i < sizeof(heightText); i++) - { - int16_t cursorStart = this->display->getCursorX(); - - this->display->print(heightText[i]); - - if (i == editingChar) - this->display->drawFastHLine(cursorStart, Metrics::MiddleLargeTextLineY + Metrics::LargeTextLineHeight - 1, this->display->getCursorX() - cursorStart, Config::ColorCalibrateDigitMarker); + return nullptr; } } \ No newline at end of file diff --git a/src/lib/screen/calibrate.h b/src/lib/screen/calibrate.h index 6c9aa2e..020e0d7 100644 --- a/src/lib/screen/calibrate.h +++ b/src/lib/screen/calibrate.h @@ -2,17 +2,18 @@ #define __screen_calibrate #include "include/screenids.h" +#include "./baseheightentry.h" #include "../screen.h" #include "../control.h" -enum CalibrateStep : uint8_t +enum CalibrateStep { CalibrateStepCurrent = 0, CalibrateStepMax, CalibrateStepMin, - CalibrateStepCount + __CalibrateStepCount }; @@ -21,27 +22,26 @@ enum CalibrateStep : uint8_t * Configures the absolute height (calculates the offset) and * the allowed minimum and maximum absolute height. */ -class CalibrateScreen : public BaseScreen +class CalibrateScreen : public BaseHeightEntryScreen { public: - CalibrateScreen(ScreenManager* screenManager, Adafruit_GFX* display) : BaseScreen(screenManager, display) { } - - void onShow(); - void onButton(Button button); - void onTick(); + CalibrateScreen(ScreenManager* screenManager, Adafruit_GFX* display) : BaseHeightEntryScreen(screenManager, display) { } ScreenId screenId() { return ScreenId::Calibrate; }; - private: - bool isValidHeight(); - void drawTitle(); - void drawSetHeight(); + protected: + void initHeights(); + uint16_t getHeight() { return this->height[this->step]; } + void setHeight(uint16_t value) { this->height[this->step] = value; }; + bool nextPage(); + bool isValidHeight(); + const char* getTitle(); + + private: CalibrateStep step = CalibrateStepCurrent; - uint16_t height[CalibrateStepCount]; - uint16_t lastTextWidth = 0; - uint8_t editingDigit = 0; + uint16_t height[__CalibrateStepCount]; }; #endif diff --git a/src/lib/screen/menu.cpp b/src/lib/screen/menu.cpp index 927503a..e6809bf 100644 --- a/src/lib/screen/menu.cpp +++ b/src/lib/screen/menu.cpp @@ -1,5 +1,9 @@ #include "./menu.h" #include "./home.h" +#include "./calibrate.h" +#include "./manual.h" +#include "./presets.h" +#include "include/config.h" #include "include/metrics.h" @@ -11,16 +15,102 @@ void MenuScreen::onShow() this->display->fillScreen(Config::ColorHomeBackground); this->drawLargeTextLineCentered("Menu", 0, Config::ColorMenuHeaderText, Config::ColorMenuHeaderBackground); -// TODO: implement MenuScreen + for (uint8_t i = 0; i < (uint8_t)MenuItem::__Count; i++) + this->drawMenuItem(i); } void MenuScreen::onButton(Button button) { - this->screenManager->show(); + uint8_t previousIndex = this->itemIndex; + + switch (button) + { + case Button::Middle: + this->activateMenuItem((MenuItem)this->itemIndex); + return; + + case Button::Top: + if (this->itemIndex == 0) + this->itemIndex = (uint8_t)MenuItem::__Count; + + this->itemIndex--; + break; + + case Button::Bottom: + this->itemIndex++; + if (this->itemIndex == (uint8_t)MenuItem::__Count) + this->itemIndex = 0; + + break; + } + + this->drawMenuItem(previousIndex); + this->drawMenuItem(this->itemIndex); } void MenuScreen::onTick() { +} + + +void MenuScreen::drawMenuItem(uint8_t index) +{ + uint16_t y = (index + 2) * Metrics::LargeTextLineHeight; + bool isSelected = index == this->itemIndex; + + this->drawLargeTextLineCentered( + this->getMenuItemTitle((MenuItem)index), y, + isSelected ? Config::ColorMenuSelectedText : Config::ColorMenuText, + isSelected ? Config::ColorMenuSelectedBackground : Config::ColorMenuBackground); +} + + +const char* MenuScreen::getMenuItemTitle(MenuItem item) +{ + switch (item) + { + case MenuItem::Manual: + return "Manual"; + + case MenuItem::Presets: + return "Presets"; + + case MenuItem::Calibrate: + return "Calibrate"; + + case MenuItem::Exit: + return "Exit"; + + default: + return nullptr; + } +} + + +void MenuScreen::activateMenuItem(MenuItem item) +{ + switch (item) + { + case MenuItem::Manual: + // TODO: show manual screen + //this->screenManager->show(); + break; + + case MenuItem::Presets: + this->screenManager->show(); + break; + + case MenuItem::Calibrate: + this->screenManager->show(); + break; + + case MenuItem::Exit: + this->screenManager->show(); + break; + + default: + break; + } } \ No newline at end of file diff --git a/src/lib/screen/menu.h b/src/lib/screen/menu.h index 96950ec..fd84392 100644 --- a/src/lib/screen/menu.h +++ b/src/lib/screen/menu.h @@ -6,6 +6,18 @@ #include "../Control.h" +enum class MenuItem +{ + Manual = 0, + Presets, + Calibrate, + Exit, + + __Count +}; + + + /* * Menu screen * Shows the menu which allows access to the calibration and @@ -23,6 +35,11 @@ class MenuScreen : public BaseScreen ScreenId screenId() { return ScreenId::Menu; }; private: + void drawMenuItem(uint8_t index); + const char* getMenuItemTitle(MenuItem item); + void activateMenuItem(MenuItem item); + + uint8_t itemIndex = 0; }; #endif diff --git a/src/lib/screen/presets.cpp b/src/lib/screen/presets.cpp new file mode 100644 index 0000000..2193a76 --- /dev/null +++ b/src/lib/screen/presets.cpp @@ -0,0 +1,55 @@ +#include "./presets.h" +#include "./home.h" +#include "include/metrics.h" +#include "lib/settings.h" + + + +void PresetsScreen::initHeights() +{ + this->height[0] = Settings.Height.Preset[0]; + this->height[1] = Settings.Height.Preset[1]; +} + + +bool PresetsScreen::nextPage() +{ + this->preset++; + if (this->preset < 2) + return true; + + Settings.Height.Preset[0] = this->height[0]; + Settings.Height.Preset[1] = this->height[1]; + writeSettingsHeights(); + + this->screenManager->show(); + return false; +} + + + +void PresetsScreen::setHeight(uint16_t value) +{ + if (value < Settings.Height.Minimum) + this->height[this->preset] = Settings.Height.Minimum; + else if (value > Settings.Height.Maximum) + this->height[this->preset] = Settings.Height.Maximum; + else + this->height[this->preset] = value; +} + + +const char* PresetsScreen::getTitle() +{ + switch (this->preset) + { + case 0: + return "Preset 1"; + + case 1: + return "Preset 2"; + + default: + return nullptr; + } +} \ No newline at end of file diff --git a/src/lib/screen/presets.h b/src/lib/screen/presets.h new file mode 100644 index 0000000..ebdd668 --- /dev/null +++ b/src/lib/screen/presets.h @@ -0,0 +1,35 @@ +#ifndef __screen_presets +#define __screen_presets + +#include "include/screenids.h" +#include "./baseheightentry.h" +#include "../screen.h" +#include "../control.h" + + +/* + * Presets screen + * Configures the presets. + */ +class PresetsScreen : public BaseHeightEntryScreen +{ + public: + PresetsScreen(ScreenManager* screenManager, Adafruit_GFX* display) : BaseHeightEntryScreen(screenManager, display) { } + + ScreenId screenId() { return ScreenId::Presets; }; + + protected: + void initHeights(); + uint16_t getHeight() { return this->height[this->preset]; } + void setHeight(uint16_t value); + bool nextPage(); + + bool isValidHeight() { return true; } + const char* getTitle(); + + private: + uint8_t preset = 0; + uint16_t height[2]; +}; + +#endif