diff options
| author | Andreea Radoescu <randreea23@gmail.com> | 2026-10-25 18:40:15 +0100 |
|---|---|---|
| committer | Andreea Radoescu <randreea23@gmail.com> | 2026-10-25 18:40:15 +0100 |
| commit | 7c2b1d2d6624f6c401459d27e9216d74b164c09d (patch) | |
| tree | 799d38edfca4ea23667c6ef9a89ef7fc9f21e8bc /firmware/src | |
| parent | 6a9433c1ef495cd3f992fb3fd586677f4cc18b7c (diff) | |
added code to initialize display and render on screen the adc samples
Diffstat (limited to 'firmware/src')
| -rw-r--r-- | firmware/src/debug.hpp | 8 | ||||
| -rw-r--r-- | firmware/src/display.hpp | 242 | ||||
| -rw-r--r-- | firmware/src/firmware.cpp | 10 | ||||
| -rw-r--r-- | firmware/src/font.hpp | 52 |
4 files changed, 306 insertions, 6 deletions
diff --git a/firmware/src/debug.hpp b/firmware/src/debug.hpp index 856da38..49be259 100644 --- a/firmware/src/debug.hpp +++ b/firmware/src/debug.hpp @@ -3,10 +3,18 @@ namespace debug { +static void +toggle_led() { + gpio_xor_mask(1u << PICO_DEFAULT_LED_PIN); +} void init() { stdio_init_all(); + gpio_init(PICO_DEFAULT_LED_PIN); + gpio_set_dir(PICO_DEFAULT_LED_PIN, 1); + gpio_put(PICO_DEFAULT_LED_PIN, !PICO_DEFAULT_LED_PIN_INVERTED); + } } diff --git a/firmware/src/display.hpp b/firmware/src/display.hpp index e2dabf2..b7a6cd0 100644 --- a/firmware/src/display.hpp +++ b/firmware/src/display.hpp @@ -1,22 +1,256 @@ +#pragma once +#include "font.hpp" + +#include <cstring> +#include <format> +#include <string> +#include <cstdint> + #include "hardware/i2c.h" namespace display { -// I2C0 on GPIO8 (SDA) and GPIO9 (SCL) running at 400KHz -constexpr auto I2C_PORT = i2c0; -constexpr unsigned I2C_SDA = 8; -constexpr unsigned I2C_SCL = 9; +// I2C1 on GPIO6 (SDA) and GPIO7 (SCL) running at 400KHz +constexpr auto I2C_PORT = i2c1; +constexpr unsigned I2C_SDA = 6; +constexpr unsigned I2C_SCL = 7; + +// SSD1036 configuration +constexpr auto SSD1306_ADDR = 0x3c; +constexpr auto SSD1036_BYTES_PER_PAGE = 128; + +constexpr unsigned DISPLAY_WIDTH = 128; +constexpr unsigned DISPLAY_HEIGHT = 64; + +constexpr unsigned MAX_W = (DISPLAY_WIDTH - 1); +constexpr unsigned MAX_H = (DISPLAY_HEIGHT - 1); + +// FRAME BUFFER +static uint8_t framebuffer[DISPLAY_HEIGHT * DISPLAY_WIDTH / 8]; + +typedef enum +{ + // Fundamental commands + SET_CONTRAST = 0x81, // Double byte command to select 1 out of + // 256 contrast steps. + SET_ENTIRE_DISP_ON = 0xa4, // Bit0 = 0: Output follows RAM content. + // Bit0 = 1: Output ignores RAM content, + // all pixels are turned on. + SET_NORMAL_INVERTED = 0xa6, // Bit0 = 0: Normal display. + // Bit0 = 1: Inverted display. + SET_DISP_ON_OFF = 0xae, // Bit0 = 0: Display off, sleep mode. + // Bit0 = 1: Display on, normal mode. + + // Addressing setting Commands + SET_ADDRESSING_MODE = 0x20, // Double byte command to set memory + // addressing mode. + SET_COLUMN_ADDRESS = 0x21, // Tripple byte command to setup column start + // and end address. + SET_PAGE_ADDRESS = 0x22, // Tripple byte command to setup page start and + // end address. + + // Hardware configuration (panel resolution and layout related) commands + SET_DISP_START_LINE = 0x40, // Set display RAM display start line + // register. Valid values are 0 to 63. + SET_SEGMENT_REMAP = 0xa0, // Bit 0 = 0: Map col addr 0 to SEG0. + // Bit 0 = 1: Map col addr 127 to SEG0. + SET_MUX_RATIO = 0xa8, // Double byte command to configure display + // height. Valid height values are 15 to 63. + SET_COM_OUTPUT_DIR = 0xc0, // Bit 3 = 0: Scan from 0 to N-1. + // Bit 3 = 1: Scan from N-1 to 0. (N=height) + SET_DISP_OFFSET = 0xd3, // Double byte command to configure vertical + // display shift. Valid values are 0 to 63. + SET_COM_PINS_CONFIG = 0xda, // Double byte command to set COM pins + // hardware configuration. + + // Timing and driving scheme setting commands + SET_DCLK_FOSC = 0xd5, // Double byte command to set display clock + // divide ratio and oscillator frequency. + SET_PRECHARGE_PERIOD = 0xd9, // Double byte command to set pre-charge + // period. + SET_VCOM_DESEL_LEVEL = 0xdb, // Double byte command to set VCOMH deselect + // level. + + // Charge pump command + SET_CHARGE_PUMP = 0x8d, // Double byte command to enable/disable + // charge pump. + // Byte2 = 0x10: Disable charge pump. + // Byte2 = 0x14: Enable charge pump. +} ssd1306_commands; + +static bool +write_commands(uint8_t command) +{ + uint8_t data[] = {0x00, command}; + // send command via I2C + auto write_result = i2c_write_blocking(I2C_PORT, SSD1306_ADDR, data, sizeof(data), false); + return write_result == sizeof(data); +} + +static bool +write_framebuffer(const uint8_t *data, size_t length) { + // SSD1306 data-control byte followed by pixel data. + // Send in chunks to keep the transfer buffer small. + uint8_t packet[17]; + packet[0] = 0x40; + + size_t offset = 0; + + while (offset < length) { + size_t chunk = length - offset; + + if (chunk > 16) { + chunk = 16; + } + + memcpy(&packet[1], &data[offset], chunk); + + int result = i2c_write_blocking( + I2C_PORT, + SSD1306_ADDR, + packet, + chunk + 1, + false + ); + + if (result != (int)(chunk + 1)) { + return false; + } + + offset += chunk; + } + + return true; +} + +static bool +diplay_init() +{ + // Initialize the SDD1306 controller + const uint8_t commands[] = { + SET_DISP_ON_OFF | 0x00, // Display off. + SET_DCLK_FOSC, 0x80, // Set clock divide ratio and oscillator + // frequency. + SET_MUX_RATIO, 0x3f, // Set display height. + SET_DISP_OFFSET, 0x00, // Set vertical display shift to 0. + SET_DISP_START_LINE, // Set display RAM display start line + // register to 0. + SET_CHARGE_PUMP, 0x14, // Enable charge pump. + SET_SEGMENT_REMAP | 0x01, // Map col addr 127 to SEG0. + SET_COM_OUTPUT_DIR | 0x08, // Scan from N-1 to 0. (N=height) + SET_COM_PINS_CONFIG, 0x12, // Set COM pins hardware configuration to + // 0x12. + SET_CONTRAST, 0xcf, // Set contrast to 0xcf + SET_PRECHARGE_PERIOD, 0xf1, // Set pre-charge to 0xf1 + SET_VCOM_DESEL_LEVEL, 0x40, // Set VCOMH deselect to 0x40 + SET_ENTIRE_DISP_ON, // Output follows RAM content. + SET_NORMAL_INVERTED | 0x00, // Normal display. + SET_ADDRESSING_MODE, 0x00, // Set addressing mode to horizontal mode. + SET_COLUMN_ADDRESS, 0x00, 0x7f, // Set column start and end address. + SET_PAGE_ADDRESS, 0x00, 0x07, // Set page start and end address. + SET_DISP_ON_OFF | 0x01, // Display on. + }; + + for (uint8_t command : commands) { + if (!write_commands(command)) { + return false; + } + } + + memset(framebuffer, 0, sizeof(framebuffer)); + + return true; +} + +static bool +display_update() +{ + const uint8_t commands[] = { + SET_COLUMN_ADDRESS, 0x00, 0x7f, // Column range: 0-127 + SET_PAGE_ADDRESS, 0x00, 0x07, // Page range: 0-7 + }; + + for (uint8_t command : commands) { + if (!write_commands(command)) { + return false; + } + } + + return write_framebuffer(framebuffer, sizeof(framebuffer)); + +} void init() { + // Initialize I2C protocol i2c_init(I2C_PORT, 400*1000); gpio_set_function(I2C_SDA, GPIO_FUNC_I2C); gpio_set_function(I2C_SCL, GPIO_FUNC_I2C); gpio_pull_up(I2C_SDA); gpio_pull_up(I2C_SCL); + + // Initialize the display + if (!diplay_init()) { + while (true) { + // Display not responding, stop here. + sleep_ms(1000); + } + } + +} + + + + static void draw_char(int x, int y, char character, int scale = 1) +{ + const Glyph *glyph = find_glyph(character); + + if (glyph == nullptr) { + glyph = find_glyph(' '); + } + + for (int col = 0; col < 5; ++col) { + uint8_t bits = glyph->columns[col]; + + for (int row = 0; row < 7; ++row) { + if (bits & (1u << row)) { + for (int dx = 0; dx < scale; ++dx) { + for (int dy = 0; dy < scale; ++dy) { + int px = x + col * scale + dx; + int py = y + row * scale + dy; + + if (px < 0 || px >= DISPLAY_WIDTH || + py < 0 || py >= DISPLAY_HEIGHT) { + continue; + } + + int index = px + (py / 8) * DISPLAY_WIDTH; + + framebuffer[index] |= + (uint8_t)(1u << (py % 8)); + } + } + } + } + } +} +static void draw_text(int x, int y, const char *text, int scale = 1) +{ + while (*text) { + draw_char(x, y, *text++, scale); + x += 6 * scale; + } +} + +static void +render_framebuffer(uint8_t pot_a, uint8_t pot_b) { + memset(framebuffer, 0, sizeof(framebuffer)); + draw_text(10, 8, "XIAO RP2040", 2); + draw_text(10, 30, std::format("POT A: {}\n", static_cast<std::uint32_t>(pot_a)).c_str(), 1); + draw_text(10, 50, std::format("POT C: {}\n", static_cast<std::uint32_t>(pot_b)).c_str(), 1); } } diff --git a/firmware/src/firmware.cpp b/firmware/src/firmware.cpp index fc5bdd7..8483ada 100644 --- a/firmware/src/firmware.cpp +++ b/firmware/src/firmware.cpp @@ -8,15 +8,21 @@ void update() { - for (auto const [value, changed] : adc::get_samples()) + const auto samples = adc::get_samples(); + for (auto const [value, changed] : samples) { if (!changed) continue; midi::send_message(); - // update framebuffer + } + // update framebuffer + display::render_framebuffer(samples[0].value, samples[1].value); // send frame to display + display::display_update(); + + debug::toggle_led(); } [[noreturn]] int diff --git a/firmware/src/font.hpp b/firmware/src/font.hpp new file mode 100644 index 0000000..5f4f4bc --- /dev/null +++ b/firmware/src/font.hpp @@ -0,0 +1,52 @@ +// ============================================================ +// Tiny 5x7 font: digits, letters and punctuation +// ============================================================ + +struct Glyph { + char character; + uint8_t columns[5]; +}; + +static const Glyph font[] = { + {' ', {0x00,0x00,0x00,0x00,0x00}}, + {'.', {0x00,0x60,0x60,0x00,0x00}}, + {':', {0x00,0x36,0x36,0x00,0x00}}, + {'-', {0x08,0x08,0x08,0x08,0x08}}, + {'+', {0x08,0x08,0x3E,0x08,0x08}}, + {'0', {0x3E,0x51,0x49,0x45,0x3E}}, + {'1', {0x00,0x42,0x7F,0x40,0x00}}, + {'2', {0x42,0x61,0x51,0x49,0x46}}, + {'3', {0x21,0x41,0x45,0x4B,0x31}}, + {'4', {0x18,0x14,0x12,0x7F,0x10}}, + {'5', {0x27,0x45,0x45,0x45,0x39}}, + {'6', {0x3C,0x4A,0x49,0x49,0x30}}, + {'7', {0x01,0x71,0x09,0x05,0x03}}, + {'8', {0x36,0x49,0x49,0x49,0x36}}, + {'9', {0x06,0x49,0x49,0x29,0x1E}}, + {'A', {0x7E,0x11,0x11,0x11,0x7E}}, + {'C', {0x3E,0x41,0x41,0x41,0x22}}, + {'E', {0x7F,0x49,0x49,0x49,0x41}}, + {'H', {0x7F,0x08,0x08,0x08,0x7F}}, + {'I', {0x00,0x41,0x7F,0x41,0x00}}, + {'M', {0x7F,0x02,0x0C,0x02,0x7F}}, + {'N', {0x7F,0x04,0x08,0x10,0x7F}}, + {'O', {0x3E,0x41,0x41,0x41,0x3E}}, + {'P', {0x7F,0x09,0x09,0x09,0x06}}, + {'R', {0x7F,0x09,0x19,0x29,0x46}}, + {'S', {0x46,0x49,0x49,0x49,0x31}}, + {'T', {0x01,0x01,0x7F,0x01,0x01}}, + {'U', {0x3F,0x40,0x40,0x40,0x3F}}, + {'X', {0x63,0x14,0x08,0x14,0x63}}, + {'Y', {0x07,0x08,0x70,0x08,0x07}} +}; + +static const Glyph *find_glyph(char character) +{ + for (const Glyph &glyph : font) { + if (glyph.character == character) { + return &glyph; + } + } + + return nullptr; +} |
