diff --git a/docs/Assignments/week_14_interface_application_programming/EasyMenu.zip b/docs/Assignments/week_14_interface_application_programming/EasyMenu.zip new file mode 100644 index 0000000..04629ac Binary files /dev/null and b/docs/Assignments/week_14_interface_application_programming/EasyMenu.zip differ diff --git a/docs/Assignments/week_14_interface_application_programming/GUI complete.zip b/docs/Assignments/week_14_interface_application_programming/GUI complete.zip new file mode 100644 index 0000000..56f60d6 Binary files /dev/null and b/docs/Assignments/week_14_interface_application_programming/GUI complete.zip differ diff --git a/docs/Assignments/week_14_interface_application_programming/image-12.jpg b/docs/Assignments/week_14_interface_application_programming/image-12.jpg new file mode 100644 index 0000000..381262f Binary files /dev/null and b/docs/Assignments/week_14_interface_application_programming/image-12.jpg differ diff --git a/docs/Assignments/week_14_interface_application_programming/image-13.jpg b/docs/Assignments/week_14_interface_application_programming/image-13.jpg new file mode 100644 index 0000000..d534050 Binary files /dev/null and b/docs/Assignments/week_14_interface_application_programming/image-13.jpg differ diff --git a/docs/Assignments/week_14_interface_application_programming/interface_application_programming.md b/docs/Assignments/week_14_interface_application_programming/interface_application_programming.md index cab0908..ed18ed5 100644 --- a/docs/Assignments/week_14_interface_application_programming/interface_application_programming.md +++ b/docs/Assignments/week_14_interface_application_programming/interface_application_programming.md @@ -423,5 +423,748 @@ A portion of this code was taken from the machining week group project. But what ``` This is the full code of the screen. The schematic for the pcb can be found in [the final project page](/docs/final_project/final_project.md) +## Turning it into a library +So I asked Copilot (Claude 3.7 Sonnet) to turn this above into a library. Using this prompt "turn the screen thing into a library so i can easily create menus and stuff" and using the context from my files it came up with this broken and oversized mess. +??? Failure.cpp + ```cpp + #ifndef OLED_MENU_H + #define OLED_MENU_H + + #include + #include + + // Maximum number of menu items and item name length + #define MAX_MENU_ITEMS 20 + #define MAX_ITEM_LENGTH 20 + + // Button states + #define BUTTON_RELEASED 0 + #define BUTTON_PRESSED 1 + + class OLEDMenu { + public: + // Constructor + OLEDMenu(U8G2_SSD1306_128X64_NONAME_F_HW_I2C* display); + + // Initialize the menu + void begin(); + + // Set button states + void setButtons(bool up, bool select, bool down); + + // Add a menu item + bool addMenuItem(const char* item_name, const uint8_t* icon = nullptr); + + // Set custom bitmaps for menu items + void setItemBitmap(int item_index, const uint8_t* bitmap); + + // Set custom screen for a menu item + void setItemScreen(int item_index, const uint8_t* screen); + + // Update and draw the menu + void update(); + + // Get currently selected item + int getSelectedItem(); + + // Set selected item + void setSelectedItem(int item); + + // Change to specific screen + void setScreen(int screen_number); + + // Get current screen + int getCurrentScreen(); + + // Set scrollbar visibility + void showScrollbar(bool show); + + // Add custom screen drawing callback + typedef void (*DrawCallback)(U8G2_SSD1306_128X64_NONAME_F_HW_I2C* display, int item_index); + void setCustomScreenCallback(int item_index, DrawCallback callback); + + private: + U8G2_SSD1306_128X64_NONAME_F_HW_I2C* _display; + + // Menu items + char _menu_items[MAX_MENU_ITEMS][MAX_ITEM_LENGTH]; + const uint8_t* _item_icons[MAX_MENU_ITEMS]; + const uint8_t* _item_screens[MAX_MENU_ITEMS]; + DrawCallback _custom_callbacks[MAX_MENU_ITEMS]; + + int _item_count; + int _selected_item; + int _prev_item; + int _next_item; + + // Screen control + int _current_screen; + bool _show_scrollbar; + + // Button states + bool _button_up; + bool _button_select; + bool _button_down; + bool _button_up_clicked; + bool _button_select_clicked; + bool _button_down_clicked; + + // Internal methods + void drawMenuScreen(); + void handleButtonEvents(); + void updateNavigation(); + }; + + #endif // OLED_MENU_H + ``` + +??? Failure.h + ```cpp + #include "OLEDMenu.h" + + // Constructor + OLEDMenu::OLEDMenu(U8G2_SSD1306_128X64_NONAME_F_HW_I2C* display) { + _display = display; + _item_count = 0; + _selected_item = 0; + _current_screen = 0; + _show_scrollbar = true; + + // Initialize button states + _button_up = false; + _button_select = false; + _button_down = false; + _button_up_clicked = false; + _button_select_clicked = false; + _button_down_clicked = false; + + // Initialize arrays + for (int i = 0; i < MAX_MENU_ITEMS; i++) { + _menu_items[i][0] = '\0'; + _item_icons[i] = nullptr; + _item_screens[i] = nullptr; + _custom_callbacks[i] = nullptr; + } + } + + void OLEDMenu::begin() { + updateNavigation(); + } + + void OLEDMenu::setButtons(bool up, bool select, bool down) { + _button_up = up; + _button_select = select; + _button_down = down; + } + + bool OLEDMenu::addMenuItem(const char* item_name, const uint8_t* icon) { + // Check if we have space for another item + if (_item_count >= MAX_MENU_ITEMS) { + return false; + } + + // Copy the item name + strncpy(_menu_items[_item_count], item_name, MAX_ITEM_LENGTH); + _menu_items[_item_count][MAX_ITEM_LENGTH - 1] = '\0'; // Ensure null termination + + // Store the icon + _item_icons[_item_count] = icon; + + // Increment the item count + _item_count++; + + // Update navigation + updateNavigation(); + + return true; + } + + void OLEDMenu::setItemBitmap(int item_index, const uint8_t* bitmap) { + if (item_index >= 0 && item_index < _item_count) { + _item_icons[item_index] = bitmap; + } + } + + void OLEDMenu::setItemScreen(int item_index, const uint8_t* screen) { + if (item_index >= 0 && item_index < _item_count) { + _item_screens[item_index] = screen; + } + } + + void OLEDMenu::setCustomScreenCallback(int item_index, DrawCallback callback) { + if (item_index >= 0 && item_index < _item_count) { + _custom_callbacks[item_index] = callback; + } + } + + void OLEDMenu::update() { + // Handle button events + handleButtonEvents(); + + // Draw the appropriate screen + _display->firstPage(); + do { + if (_current_screen == 0) { + // Draw the menu screen + drawMenuScreen(); + } else if (_current_screen == 1 && _item_screens[_selected_item] != nullptr) { + // Draw item screen + _display->drawBitmap(0, 0, 128/8, 64, _item_screens[_selected_item]); + } else if (_current_screen == 2 && _custom_callbacks[_selected_item] != nullptr) { + // Draw custom screen using callback + _custom_callbacks[_selected_item](_display, _selected_item); + } + } while (_display->nextPage()); + } + + int OLEDMenu::getSelectedItem() { + return _selected_item; + } + + void OLEDMenu::setSelectedItem(int item) { + if (item >= 0 && item < _item_count) { + _selected_item = item; + updateNavigation(); + } + } + + void OLEDMenu::setScreen(int screen_number) { + _current_screen = screen_number; + } + + int OLEDMenu::getCurrentScreen() { + return _current_screen; + } + + void OLEDMenu::showScrollbar(bool show) { + _show_scrollbar = show; + } + + void OLEDMenu::drawMenuScreen() { + // Draw menu background elements + // You might want to add the bitmap_item_sel_outline here + + // Draw previous item + _display->setFont(u8g_font_7x14); + _display->drawStr(25, 15, _menu_items[_prev_item]); + if (_item_icons[_prev_item] != nullptr) { + _display->drawBitmap(4, 2, 16/8, 16, _item_icons[_prev_item]); + } + + // Draw selected item + _display->setFont(u8g_font_7x14B); + _display->drawStr(25, 15+20+2, _menu_items[_selected_item]); + if (_item_icons[_selected_item] != nullptr) { + _display->drawBitmap(4, 24, 16/8, 16, _item_icons[_selected_item]); + } + + // Draw next item + _display->setFont(u8g_font_7x14); + _display->drawStr(25, 15+20+20+2+2, _menu_items[_next_item]); + if (_item_icons[_next_item] != nullptr) { + _display->drawBitmap(4, 46, 16/8, 16, _item_icons[_next_item]); + } + + // Draw scrollbar if enabled + if (_show_scrollbar && _item_count > 1) { + // Draw scrollbar background + // You might want to add bitmap_scrollbar_background here + + // Draw scrollbar handle + _display->drawBox(125, 64/_item_count * _selected_item, 3, 64/_item_count); + } + } + + void OLEDMenu::handleButtonEvents() { + if (_current_screen == 0) { + // Handle up button for menu navigation + if (_button_up && !_button_up_clicked) { + _selected_item = _selected_item - 1; + _button_up_clicked = true; + if (_selected_item < 0) { + _selected_item = _item_count - 1; + } + updateNavigation(); + } else if (!_button_up && _button_up_clicked) { + _button_up_clicked = false; + } + + // Handle down button for menu navigation + if (_button_down && !_button_down_clicked) { + _selected_item = _selected_item + 1; + _button_down_clicked = true; + if (_selected_item >= _item_count) { + _selected_item = 0; + } + updateNavigation(); + } else if (!_button_down && _button_down_clicked) { + _button_down_clicked = false; + } + } + + // Handle select button for screen switching + if (_button_select && !_button_select_clicked) { + _button_select_clicked = true; + + // Cycle through screens + _current_screen = (_current_screen + 1) % 3; + + // Skip screens that don't have content + if (_current_screen == 1 && _item_screens[_selected_item] == nullptr) { + _current_screen = 2; + } + if (_current_screen == 2 && _custom_callbacks[_selected_item] == nullptr) { + _current_screen = 0; + } + } else if (!_button_select && _button_select_clicked) { + _button_select_clicked = false; + } + } + + void OLEDMenu::updateNavigation() { + // Update previous and next item indices + _prev_item = _selected_item - 1; + if (_prev_item < 0) _prev_item = _item_count - 1; + + _next_item = _selected_item + 1; + if (_next_item >= _item_count) _next_item = 0; + } + ``` +Then I asked it to rewrite it and make it simpler and to add in a way to call functions using these menu tabs and to use the original graphics. + +Then it came up with this +??? AI generated.cpp + ```cpp + #include "MenuSystem.h" + #include "MenuGraphics.h" + + MenuSystem::MenuSystem(U8G2_SSD1306_128X64_NONAME_F_HW_I2C* display) { + _display = display; + _itemCount = 0; + _selectedItem = 0; + _screen = 0; + + _btnUp = false; + _btnSelect = false; + _btnDown = false; + _prevBtnUp = false; + _prevBtnSelect = false; + _prevBtnDown = false; + + // Initialize arrays + for (int i = 0; i < MAX_ITEMS; i++) { + _itemNames[i][0] = '\0'; + _itemIcons[i] = nullptr; + _itemScreens[i] = nullptr; + _itemCallbacks[i] = nullptr; + } + } + + void MenuSystem::begin() { + // Ensure display is properly initialized + _display->begin(); + _display->clearBuffer(); + _display->sendBuffer(); + delay(100); // Short delay to ensure display is ready + } + + void MenuSystem::addMenuItem(const char* name, const uint8_t* icon) { + if (_itemCount < MAX_ITEMS) { + strncpy(_itemNames[_itemCount], name, MAX_NAME_LEN-1); + _itemNames[_itemCount][MAX_NAME_LEN-1] = '\0'; // Ensure null termination + _itemIcons[_itemCount] = icon; + _itemCount++; + } + } + + void MenuSystem::setItemScreen(int index, const uint8_t* screen) { + if (index >= 0 && index < _itemCount) { + _itemScreens[index] = screen; + } + } + + void MenuSystem::setItemCallback(int index, void (*callback)()) { + if (index >= 0 && index < _itemCount) { + _itemCallbacks[index] = callback; + } + } + + void MenuSystem::setButtons(bool up, bool select, bool down) { + // Save previous button states + _prevBtnUp = _btnUp; + _prevBtnSelect = _btnSelect; + _prevBtnDown = _btnDown; + + // Set new button states + _btnUp = up; + _btnSelect = select; + _btnDown = down; + } + + void MenuSystem::update() { + // Process button inputs before drawing + handleButtons(); + + // Simple drawing approach (not using page buffer mode) + _display->clearBuffer(); + + if (_screen == 0) { + // Draw the main menu screen + drawMenu(); + } else if (_screen == 1 && _itemScreens[_selectedItem] != nullptr) { + // Draw the full-screen bitmap for the selected item + _display->drawXBM(0, 0, 128, 64, _itemScreens[_selectedItem]); + } + + _display->sendBuffer(); + } + + void MenuSystem::drawMenu() { + if (_itemCount == 0) return; // Nothing to draw + + // Calculate which items to show (previous, current, next) + int prevItem = (_selectedItem > 0) ? _selectedItem - 1 : _itemCount - 1; + int nextItem = (_selectedItem < _itemCount - 1) ? _selectedItem + 1 : 0; + + // Draw the background with the item selector outline (if enabled) + // _display->drawXBM(0, 0, 128, 64, bitmap_item_sel_outline); + + // Instead, draw a simple selection highlight + _display->drawFrame(0, 20, 128, 24); // Highlight the middle selection + + // Draw previous item (top position) + _display->setFont(u8g2_font_6x10_tf); // Use a different, simpler font + _display->drawStr(25, 15, _itemNames[prevItem]); + if (_itemIcons[prevItem] != nullptr) { + _display->drawXBM(4, 2, 16, 16, _itemIcons[prevItem]); + } + + // Draw selected item (middle position) + _display->setFont(u8g2_font_6x10_tf); // Use same font for consistency + _display->drawStr(25, 35, _itemNames[_selectedItem]); + if (_itemIcons[_selectedItem] != nullptr) { + _display->drawXBM(4, 24, 16, 16, _itemIcons[_selectedItem]); + } + + // Draw next item (bottom position) + _display->setFont(u8g2_font_6x10_tf); // Use same font for consistency + _display->drawStr(25, 55, _itemNames[nextItem]); + if (_itemIcons[nextItem] != nullptr) { + _display->drawXBM(4, 46, 16, 16, _itemIcons[nextItem]); + } + + // Draw a simple scrollbar instead of using bitmap + if (_itemCount > 1) { + _display->drawVLine(125, 0, 64); // Scrollbar background + int barHeight = 64 / _itemCount; + int barY = (_selectedItem * 64) / _itemCount; + _display->drawBox(123, barY, 4, barHeight); // Scrollbar handle + } + } + + void MenuSystem::handleButtons() { + // Only respond to button press events (not holds) + // Button rising edge detection for up + if (_btnUp && !_prevBtnUp) { + _selectedItem = (_selectedItem > 0) ? _selectedItem - 1 : _itemCount - 1; + Serial.print("Selected item: "); + Serial.println(_selectedItem); + } + + // Button rising edge detection for down + if (_btnDown && !_prevBtnDown) { + _selectedItem = (_selectedItem < _itemCount - 1) ? _selectedItem + 1 : 0; + Serial.print("Selected item: "); + Serial.println(_selectedItem); + } + + // Button rising edge detection for select + if (_btnSelect && !_prevBtnSelect) { + // Execute callback if it exists + if (_itemCallbacks[_selectedItem] != nullptr) { + _itemCallbacks[_selectedItem](); + } + + // Toggle between menu and item screen if screen exists + if (_itemScreens[_selectedItem] != nullptr) { + _screen = (_screen == 0) ? 1 : 0; + Serial.print("Screen changed to: "); + Serial.println(_screen); + } + } + } + ``` + + +??? AI generated.h + ```cpp + #ifndef MENU_SYSTEM_H + #define MENU_SYSTEM_H + + #include + #include + + class MenuSystem { + public: + // Constructor + MenuSystem(U8G2_SSD1306_128X64_NONAME_F_HW_I2C* display); + + // Initialize menu + void begin(); + + // Add menu item with icon + void addMenuItem(const char* name, const uint8_t* icon); + + // Set callback function for item + void setItemCallback(int index, void (*callback)()); + + // Set button states + void setButtons(bool up, bool select, bool down); + + // Update and draw menu + void update(); + + private: + U8G2_SSD1306_128X64_NONAME_F_HW_I2C* _display; + + // Menu items + static const int MAX_ITEMS = 10; + static const int MAX_NAME_LEN = 20; + char _itemNames[MAX_ITEMS][MAX_NAME_LEN]; + const uint8_t* _itemIcons[MAX_ITEMS]; + const uint8_t* _itemScreens[MAX_ITEMS]; + void (*_itemCallbacks[MAX_ITEMS])(); + + // Menu state + int _itemCount; + int _selectedItem; + int _screen; // 0 = menu, 1 = item screen + + // Button handling + bool _btnUp; + bool _btnSelect; + bool _btnDown; + bool _prevBtnUp; + bool _prevBtnSelect; + bool _prevBtnDown; + + // Draw the menu + void drawMenu(); + + // Update button states + void handleButtons(); + }; + + #endif + ``` +Which worked really well with my code + +??? My code + ```cpp + #include + #include + #include + #include "MenuSystem.h" + #include "Graphics.h" + U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE); + MenuSystem menu(&u8g2); + + //=====================================================================================// + // Struct declarations + typedef struct struct_message + { + int PWMCH1; + int PWMCH2; + int PWMCH3; + int PWMCH4; + } struct_message; + struct_message JoystickData; // declare the struct as JoystickData + + struct hardJoystickValues + { + int LXU; // Left joystick X axis up + int LXD; // Left joystick X axis down + int RXU; // Right joystick X axis up + int RXD; // Right joystick X axis down + }; + hardJoystickValues JoystickDataHard; // declare the struct as JoystickDataHard + + //=====================================================================================// + //declarations + void MUXSetup(); + hardJoystickValues GUIParser(); + int analogReadMultiPlexer(int addressA, int addressB, int addressC, int addressD, int pin); + int mapPot(int normalizedValue); + + // Controller declarations + const int MAXPWMVALUE = 1000; + const int MINPWMVALUE = 2000; + + bool buttonRight; + bool buttonLeft; + + // Actions for menu items + void action3DCube() { + Serial.println("3D Cube selected!"); + // Your 3D cube function code here + } + + void actionBattery() { + Serial.println("Battery selected!"); + // Your battery function code here + } + + void actionDashboard() { + Serial.println("Dashboard selected!"); + // Your dashboard function code here + } + + void actionFireworks() { + Serial.println("Fireworks selected!"); + // Your fireworks function code here + } + + void setup() { + Serial.begin(9600); + delay(100); // Small delay to stabilize + + Wire.begin(); // Initialize I2C bus + Wire.setClock(400000); // Set to 400kHz (standard speed for most displays) + + u8g2.begin(); + u8g2.clearBuffer(); + u8g2.sendBuffer(); + + MUXSetup(); // Setup the multiplexer + + // Initialize the menu + menu.begin(); + + // Add menu items + menu.addMenuItem("3D Cube", bitmap_icons[0]); + menu.addMenuItem("Battery", bitmap_icons[1]); + menu.addMenuItem("Dashboard", bitmap_icons[2]); + menu.addMenuItem("Fireworks", bitmap_icons[3]); + menu.addMenuItem("WEEEEE", bitmap_icons[4]); + + + // Add action callbacks for menu items + menu.setItemCallback(0, action3DCube); + menu.setItemCallback(1, actionBattery); + menu.setItemCallback(2, actionDashboard); + menu.setItemCallback(3, actionFireworks); + + // Clear the display once more + u8g2.clearBuffer(); + u8g2.sendBuffer(); + + Serial.println("Menu system initialized"); + } + + void loop() { + // Read joystick and button inputs + JoystickData.PWMCH1 = mapPot(analogReadMultiPlexer(0, 0, 0, 0, A0)); //Right joystick Y + JoystickData.PWMCH2 = mapPot(analogReadMultiPlexer(1, 0, 0, 0, A0)); // Right joystick X + JoystickData.PWMCH3 = mapPot(analogReadMultiPlexer(0, 0, 0, 1, A0)); // left joystick Y + JoystickData.PWMCH4 = mapPot(analogReadMultiPlexer(1, 0, 0, 1, A0)); // left joystick X + buttonRight = analogReadMultiPlexer(0, 0, 1, 0, A0) > 2000; // right button + buttonLeft = analogReadMultiPlexer(1, 0, 1, 0, A0) > 2000; // left button + + // Parse joystick data + JoystickDataHard = GUIParser(); + + // Update menu button states + menu.setButtons(JoystickDataHard.LXD, buttonRight, JoystickDataHard.LXU); + + // Update and draw the menu + menu.update(); + + // Small delay to debounce buttons + delay(50); + } + + void MUXSetup() + { + pinMode(D3, OUTPUT); // MUX enable + pinMode(D6, OUTPUT); // MUX address A + pinMode(D7, OUTPUT); // MUX address B + pinMode(D9, OUTPUT); // MUX address C + pinMode(D8, OUTPUT); // MUX address D + pinMode(A0, INPUT); // MUX input + } + + // Function to parse joystick data to hard values + hardJoystickValues GUIParser(){ + // Define joystick offsets (calibrated resting values) + const int offsetPWMCH1 = 1090; // Resting value for PWMCH1 (right joystick Y-axis) + const int offsetPWMCH2 = 1072; // Resting value for PWMCH2 (right joystick X-axis) + const int offsetPWMCH3 = 1043; // Resting value for PWMCH3 (left joystick X-axis) + const int offsetPWMCH4 = 1476; // Resting value for PWMCH4 (left joystick Y-axis) + + // Define deadzone threshold + const int deadzone = 120; + + // Adjust joystick values by subtracting offsets + int adjustedPWMCH1 = JoystickData.PWMCH1 - offsetPWMCH1; + int adjustedPWMCH2 = JoystickData.PWMCH2 - offsetPWMCH2; + int adjustedPWMCH3 = JoystickData.PWMCH3 - offsetPWMCH3; + int adjustedPWMCH4 = JoystickData.PWMCH4 - offsetPWMCH4; + + // Apply deadzone + if (abs(adjustedPWMCH1) < deadzone) adjustedPWMCH1 = 0; //abs to avoid negatives + if (abs(adjustedPWMCH2) < deadzone) adjustedPWMCH2 = 0; + if (abs(adjustedPWMCH3) < deadzone) adjustedPWMCH3 = 0; + if (abs(adjustedPWMCH4) < deadzone) adjustedPWMCH4 = 0; + + // Map joystick values to hard values + int LXU = 0; // Left joystick X axis up + int LXD = 0; // Left joystick X axis down + if (adjustedPWMCH1 > 0) { + LXU = 1; // Joystick is up + } else if (adjustedPWMCH1 < 0) { + LXD = 1; // Joystick is down + } + + return {LXU, LXD, 0, 0}; // Return the values as a struct + } + + int analogReadMultiPlexer(int addressA, int addressB, int addressC, int addressD, int pin) + { + digitalWrite(D3, LOW); + digitalWrite(D6, addressA); + digitalWrite(D7, addressB); + digitalWrite(D9, addressC); + digitalWrite(D8, addressD); + return analogRead(pin); + } + + int mapPot(int normalizedValue) + { + return map(normalizedValue, 400, 2500, MINPWMVALUE, MAXPWMVALUE); // map the normalized value to the PWM range + } + ``` + +Now I will turn this into a proper library and clean up what the AI made because there are still some things I want different. I first started off removing all of my code and reducing it down to just what's needed to run the screen with the library. Then I replaced all variables with generic ones instead of using my structs and bringing confusion +```cpp + // Update menu button states + menu.setButtons(buttonDown, buttonRight, buttonUp); +``` +Instead of +```cpp +menu.setButtons(JoystickDataHard.LXD, buttonRight, JoystickDataHard.LXU); +``` + +I also removed all unused graphics from the file reducing it down to 65 lines instead of 1165 lines. Then I added some documentation to everything using DoxyGen. +![alt text](image-12.jpg) +Doxygen makes it so you can hover functions and see what they do and what all parameters are. + +![alt text](image-13.jpg) + + + + + # Files -* [Files test using joysticks](GUI%20Test.zip) \ No newline at end of file +* [Files test using joysticks](GUI%20Test.zip) +* [Last files](GUI%20complete.zip) +* [*Library* Only](EasyMenu.zip) \ No newline at end of file