This commit is contained in:
2025-05-06 16:19:13 +02:00
parent 8be0cb4a13
commit a20e4ef745
5 changed files with 744 additions and 1 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

@@ -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 <Arduino.h>
#include <U8g2lib.h>
// 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 <Arduino.h>
#include <U8g2lib.h>
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 <Arduino.h>
#include <Wire.h>
#include <U8g2lib.h>
#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)
* [Files test using joysticks](GUI%20Test.zip)
* [Last files](GUI%20complete.zip)
* [*Library* Only](EasyMenu.zip)