separate display to its own file

This commit is contained in:
Adolfo Reyna
2025-11-19 22:26:52 -05:00
parent a0a707ac0e
commit d07901835e
18 changed files with 176 additions and 88 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -38,6 +38,7 @@ pico_sdk_init()
if (TARGET tinyusb_device) if (TARGET tinyusb_device)
add_executable(hello_usb add_executable(hello_usb
hello_usb.cpp hello_usb.cpp
display.cpp
) )
add_subdirectory(pico-ssd1306) add_subdirectory(pico-ssd1306)

86
display.cpp Normal file
View File

@@ -0,0 +1,86 @@
#include <ctype.h>
#include <cstring>
#include "hardware/i2c.h"
#include "pico-ssd1306/ssd1306.h"
#include "pico-ssd1306/textRenderer/TextRenderer.h"
#include "hardware/gpio.h"
#include "display.h"
#include <new>
using namespace pico_ssd1306;
// Implement the methods declared in `display.h`.
// Use in-place storage for the SSD1306 object to avoid heap allocation on
// constrained embedded systems. The storage is sized using the complete
// `SSD1306` type (this file includes the concrete header), and the object
// is constructed with placement-new during `init()` and explicitly destroyed
// in the destructor.
namespace {
alignas(SSD1306) unsigned char display_storage[sizeof(SSD1306)];
bool display_constructed = false;
}
DisplayManager::DisplayManager()
: display_(nullptr)
{
last_echo_[0] = '\0';
}
DisplayManager::~DisplayManager() {
if (display_constructed && display_) {
// Call destructor explicitly since we used placement-new.
display_->~SSD1306();
display_constructed = false;
}
display_ = nullptr;
}
// (No singleton accessor — the DisplayManager is constructed explicitly by callers.)
void DisplayManager::init(uint8_t i2c_addr) {
if (display_) return; // already initialized
// Initialize I2C for the SSD1306 display (common pins: SDA=GPIO4, SCL=GPIO5)
i2c_init(i2c0, 400 * 1000);
gpio_set_function(4, GPIO_FUNC_I2C);
gpio_set_function(5, GPIO_FUNC_I2C);
gpio_pull_up(4);
gpio_pull_up(5);
// Construct the display object in pre-allocated storage to avoid heap use.
display_ = reinterpret_cast<SSD1306 *>(display_storage);
new (display_) SSD1306(i2c0, i2c_addr, Size::W128xH64);
display_constructed = true;
display_->setOrientation(false);
display_->clear();
display_->sendBuffer();
last_echo_[0] = '\0';
refresh("> ", nullptr);
}
void DisplayManager::refresh(const char *current_input, const char *last_echo) {
if (!display_) return;
display_->clear();
size_t input_len = current_input ? strlen(current_input) : 0;
const unsigned char *input_font = (input_len > LAST_ECHO_VISIBLE_CHARS) ? font_5x8 : font_12x16;
pico_ssd1306::drawText(display_, input_font, current_input ? current_input : "", 0, 0);
const char *echo_to_draw = last_echo ? last_echo : (last_echo_[0] ? last_echo_ : "");
pico_ssd1306::drawText(display_, font_12x16, echo_to_draw, 0, 20);
display_->sendBuffer();
}
void DisplayManager::set_last_echo(const char *text) {
if (!text) return;
strncpy(last_echo_, text, sizeof(last_echo_) - 1);
last_echo_[sizeof(last_echo_) - 1] = '\0';
}
// No C-compatible wrappers: prefer `DisplayManager::instance()` API directly.

73
display.h Normal file
View File

@@ -0,0 +1,73 @@
/* display.h
* Generated from display.cpp — public declarations for the OLED helper
*/
#ifndef DISPLAY_H
#define DISPLAY_H
#include <stddef.h>
#include <stdint.h>
// Display API is provided via the C++ `DisplayManager` singleton below.
#ifdef __cplusplus
// Expose the C++ DisplayManager so callers can use the class API directly.
//
// Note: The implementation constructs the `SSD1306` display object in-place
// using static storage (no heap allocation). Construct a `DisplayManager` in
// `main()` and pass it by reference to functions that need to update the
// display. This avoids global singletons and makes ownership explicit.
//
// Example:
// int main() {
// stdio_init_all();
// DisplayManager display;
// // pass the display I2C address (common values: 0x3C or 0x3D)
// display.init(0x3C);
// while (true) {
// wait_for_usb_connection();
// run_echo_session(display);
// }
// }
//
// void run_echo_session(DisplayManager &display) {
// display.refresh("> ", nullptr);
// // ...
// }
//
// Forward-declare the SSD1306 type to avoid pulling the full display header into
// every translation unit that includes `display.h`.
namespace pico_ssd1306 { class SSD1306; }
class DisplayManager {
public:
// Initialize the hardware and display.
DisplayManager();
~DisplayManager();
// Initialize the hardware and display. Pass the SSD1306 I2C address (default 0x3C).
void init(uint8_t i2c_addr = 0x3C);
// Refresh the display content. Either pointer may be nullptr.
void refresh(const char *current_input, const char *last_echo);
// Update stored last-echo text used when `last_echo` is not provided to `refresh()`.
void set_last_echo(const char *text);
// Non-copyable
DisplayManager(const DisplayManager &) = delete;
DisplayManager &operator=(const DisplayManager &) = delete;
// Implementation details kept small in the header: pointer to the concrete SSD1306
// type (defined in `pico-ssd1306/ssd1306.h`).
pico_ssd1306::SSD1306 *display_;
char last_echo_[64];
static const size_t LAST_ECHO_VISIBLE_CHARS = 10;
};
#endif
#endif // DISPLAY_H

View File

@@ -7,55 +7,12 @@
#include "pico/time.h" // Needed for time_us_64() #include "pico/time.h" // Needed for time_us_64()
#include <stdio.h> #include <stdio.h>
#include <ctype.h> #include <ctype.h>
#include <cstring>
#include "hardware/i2c.h" #include "display.h"
#include "hardware/gpio.h"
#include "pico-ssd1306/ssd1306.h"
#include "pico-ssd1306/textRenderer/TextRenderer.h"
using namespace pico_ssd1306;
// Global display pointer so helper functions can update the OLED
static SSD1306 *g_display = nullptr;
// Holds last echoed line for display // Holds last echoed line for display
static char g_last_echo[128] = ""; static char g_last_echo[128] = "";
// Scrolling state for last echo (marquee)
static size_t g_last_echo_scroll_index = 0;
static uint64_t g_last_echo_last_scroll_ms = 0;
static const uint64_t SCROLL_INTERVAL_MS = 300; // milliseconds between scroll steps
static const size_t LAST_ECHO_VISIBLE_CHARS = 10; // visible chars for 12x16 font on 128px width
// Refresh OLED with current input (line 0) and last echoed text (line 2)
static void refresh_display(const char *current_input, const char *last_echo) {
if (!g_display) return;
g_display->clear();
// If current input is longer than 10 chars, use smaller font to fit more text
size_t input_len = current_input ? strlen(current_input) : 0;
const unsigned char *input_font = (input_len > 10) ? font_5x8 : font_12x16;
// Draw current input using chosen font
pico_ssd1306::drawText(g_display, input_font, current_input, 0, 0);
// Draw last echoed text using the larger font for readability
// If the last echo is longer than visible chars, create a scrolling window
size_t last_len = last_echo ? strlen(last_echo) : 0;
if (last_len > LAST_ECHO_VISIBLE_CHARS) {
char window[LAST_ECHO_VISIBLE_CHARS + 1];
// copy up to visible chars starting at scroll index, wrapping as needed
for (size_t i = 0; i < LAST_ECHO_VISIBLE_CHARS; ++i) {
size_t src = (g_last_echo_scroll_index + i) % last_len;
window[i] = last_echo[src];
}
window[LAST_ECHO_VISIBLE_CHARS] = '\0';
pico_ssd1306::drawText(g_display, font_12x16, window, 0, 20);
} else {
pico_ssd1306::drawText(g_display, font_12x16, last_echo, 0, 20);
}
g_display->sendBuffer();
}
// Define the maximum size for the input string buffer // Define the maximum size for the input string buffer
#define MAX_INPUT_LEN 64 #define MAX_INPUT_LEN 64
@@ -65,7 +22,7 @@ static void refresh_display(const char *current_input, const char *last_echo) {
#define ASCII_BACKSPACE 8 #define ASCII_BACKSPACE 8
// Helper function to handle the echoing and resetting of the buffer // Helper function to handle the echoing and resetting of the buffer
void echo_and_reset(char* buffer, int* index_ptr) { void echo_and_reset(char* buffer, int* index_ptr, DisplayManager &display) {
if (*index_ptr > 0) { if (*index_ptr > 0) {
buffer[*index_ptr] = '\0'; buffer[*index_ptr] = '\0';
// Print to USB serial (ALL CAPS) // Print to USB serial (ALL CAPS)
@@ -82,15 +39,13 @@ void echo_and_reset(char* buffer, int* index_ptr) {
g_last_echo[i] = (char)toupper(buffer[i]); g_last_echo[i] = (char)toupper(buffer[i]);
} }
g_last_echo[i] = '\0'; g_last_echo[i] = '\0';
// reset scrolling state whenever last echo changes
g_last_echo_scroll_index = 0;
g_last_echo_last_scroll_ms = time_us_64() / 1000;
} }
*index_ptr = 0; *index_ptr = 0;
printf("> "); printf("> ");
// Update OLED to show cleared input and last echo // Update OLED to show cleared input and last echo
refresh_display("", g_last_echo); display.set_last_echo(g_last_echo);
display.refresh("", g_last_echo);
} }
void wait_for_usb_connection() { void wait_for_usb_connection() {
@@ -101,7 +56,7 @@ void wait_for_usb_connection() {
printf("\nConnection Established! Starting Echo Session...\n"); printf("\nConnection Established! Starting Echo Session...\n");
} }
void run_echo_session() { void run_echo_session(DisplayManager &display) {
char input_buffer[MAX_INPUT_LEN]; char input_buffer[MAX_INPUT_LEN];
int buffer_index = 0; int buffer_index = 0;
uint64_t last_char_time = time_us_64(); uint64_t last_char_time = time_us_64();
@@ -122,10 +77,10 @@ void run_echo_session() {
printf("\b \b"); printf("\b \b");
// update display to reflect removed char // update display to reflect removed char
input_buffer[buffer_index] = '\0'; input_buffer[buffer_index] = '\0';
refresh_display(input_buffer, g_last_echo); display.refresh(input_buffer, g_last_echo);
} }
} else if (input_char == '\r' || input_char == '\n') { } else if (input_char == '\r' || input_char == '\n') {
echo_and_reset(input_buffer, &buffer_index); echo_and_reset(input_buffer, &buffer_index, display);
// after echo_and_reset the display is refreshed inside it // after echo_and_reset the display is refreshed inside it
} else if (buffer_index < (MAX_INPUT_LEN - 1) && isprint(input_char)) { } else if (buffer_index < (MAX_INPUT_LEN - 1) && isprint(input_char)) {
printf("%c", input_char); printf("%c", input_char);
@@ -133,28 +88,19 @@ void run_echo_session() {
buffer_index++; buffer_index++;
// update OLED with current input // update OLED with current input
input_buffer[buffer_index] = '\0'; input_buffer[buffer_index] = '\0';
refresh_display(input_buffer, g_last_echo); display.refresh(input_buffer, g_last_echo);
} }
} }
if (buffer_index > 0 && (time_us_64() - last_char_time) > TIMEOUT_US) { if (buffer_index > 0 && (time_us_64() - last_char_time) > TIMEOUT_US) {
printf("\n--- Timeout Reached (5.0s silence) ---\n"); printf("\n--- Timeout Reached (5.0s silence) ---\n");
echo_and_reset(input_buffer, &buffer_index); echo_and_reset(input_buffer, &buffer_index, display);
last_char_time = time_us_64(); last_char_time = time_us_64();
} }
// Advance marquee for last echoed text if needed // Advance marquee for last echoed text if needed
size_t last_len = strlen(g_last_echo); size_t last_len = strlen(g_last_echo);
uint64_t now_ms = time_us_64() / 1000; uint64_t now_ms = time_us_64() / 1000;
if (last_len > LAST_ECHO_VISIBLE_CHARS) {
if (now_ms - g_last_echo_last_scroll_ms >= SCROLL_INTERVAL_MS) {
g_last_echo_scroll_index = (g_last_echo_scroll_index + 1) % last_len;
g_last_echo_last_scroll_ms = now_ms;
// refresh display to show new window
refresh_display(input_buffer, g_last_echo);
}
}
sleep_us(100); sleep_us(100);
} }
@@ -163,30 +109,12 @@ void run_echo_session() {
int main() { int main() {
stdio_init_all(); stdio_init_all();
DisplayManager display;
// Initialize I2C for the SSD1306 display (common pins: SDA=GPIO4, SCL=GPIO5) display.init();
i2c_init(i2c0, 400 * 1000);
gpio_set_function(4, GPIO_FUNC_I2C);
gpio_set_function(5, GPIO_FUNC_I2C);
gpio_pull_up(4);
gpio_pull_up(5);
// Create display object (address 0x3C is common). Adjust size/address if needed.
SSD1306 display(i2c0, 0x3C, Size::W128xH64);
// rotate display 180 degrees (flip). setOrientation(1) flips screen orientation
display.setOrientation(false);
display.clear();
display.sendBuffer();
// set global pointer and show initial prompt using larger font
g_display = &display;
g_last_echo[0] = '\0';
refresh_display("> ", g_last_echo);
while (true) { while (true) {
wait_for_usb_connection(); wait_for_usb_connection();
run_echo_session(); run_echo_session(display);
} }
return 0; return 0;

0
usb_serial.cpp Normal file
View File