commit 23c1df474c7f8630124ead2fe3d128e201e2b20c Author: shylie Date: Wed Jun 4 14:41:08 2025 -0400 Initial commit diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..ebdeb2a --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required(VERSION 3.13) + +add_library(picoled + include/picoled.h + include/picoled/SSD1306.h + + src/picoled.cpp + src/SSD1306.cpp +) + +target_link_libraries(picoled PUBLIC hardware_i2c pico_stdlib) +target_include_directories(picoled PUBLIC include) diff --git a/include/picoled.h b/include/picoled.h new file mode 100644 index 0000000..12904bc --- /dev/null +++ b/include/picoled.h @@ -0,0 +1,59 @@ +#ifndef PICOLED_H +#define PICOLED_H + +#include + +namespace picoled +{ + +class pixel +{ +public: + pixel() : + pixel(false) + {} + + pixel(bool on) : + on(on) + {} + + bool is_on() const { return on; } + void invert() { on = !on; } + +private: + bool on; +}; + +class oled +{ +public: + oled(size_t width, size_t height); + virtual ~oled(); + + oled(const oled&) = delete; + oled(oled&&) = delete; + + oled& operator=(const oled&) = delete; + oled& operator=(oled&&) = delete; + + pixel& operator()(int x, int y); + const pixel& operator()(int x, int y) const; + + void clear(); + + size_t get_width() const { return width; } + size_t get_height() const { return height; } + + virtual void update() const = 0; + +protected: + size_t width; + size_t height; + +private: + pixel* pixels; +}; + +} + +#endif//PICOLED_H diff --git a/include/picoled/SSD1306.h b/include/picoled/SSD1306.h new file mode 100644 index 0000000..950476f --- /dev/null +++ b/include/picoled/SSD1306.h @@ -0,0 +1,31 @@ +#ifndef PICOLED_SSD1306_H +#define PICOLED_SSD1306_H + +#include + +#include + +#include "picoled.h" + +namespace picoled +{ + +class ssd1306 : public oled +{ +public: + ssd1306(i2c_inst* i2c, uint8_t address); + virtual ~ssd1306(); + + void update() const override; + +private: + void command(uint8_t cmd) const; + + i2c_inst* i2c; + uint8_t address; + uint8_t* buffer; +}; + +} + +#endif//PICOLED_SSD1306_H diff --git a/src/SSD1306.cpp b/src/SSD1306.cpp new file mode 100644 index 0000000..0c2aa83 --- /dev/null +++ b/src/SSD1306.cpp @@ -0,0 +1,110 @@ +#include "picoled/SSD1306.h" + +#include + +// https://cdn-shop.adafruit.com/datasheets/SSD1306.pdf +namespace +{ + +constexpr uint8_t MEMORY_MODE = 0x20; +constexpr uint8_t COLUMN_ADDRESS = 0x21; +constexpr uint8_t PAGE_ADDRESS = 0x22; +constexpr uint8_t CONTRAST = 0x81; +constexpr uint8_t CHARGE_PUMP = 0x8D; +constexpr uint8_t SEGMENT_REMAP = 0xA0; +constexpr uint8_t ALLON_RESUME = 0xA4; +constexpr uint8_t ALLON = 0xA5; +constexpr uint8_t NORMAL_DISPLAY = 0xA6; +constexpr uint8_t INVERT_DISPLAY = 0xA7; +constexpr uint8_t MULTIPLEX = 0xA8; +constexpr uint8_t OFF = 0xAE; +constexpr uint8_t ON = 0xAF; +constexpr uint8_t COMSCAN_INC = 0xC0; +constexpr uint8_t COMSCAN_DEC = 0xC8; +constexpr uint8_t OFFSET = 0xD3; +constexpr uint8_t CLKDIV = 0xD5; +constexpr uint8_t PRECHARGE = 0xD9; +constexpr uint8_t COMPINS = 0xDA; +constexpr uint8_t VCOM_DETECT = 0xDB; +constexpr uint8_t LOW_COLUMN = 0x00; +constexpr uint8_t HIGH_COLUMN = 0x10; +constexpr uint8_t STARTLINE = 0x40; +constexpr uint8_t RIGHT_HSCROLL = 0x26; +constexpr uint8_t LEFT_HSCROLL = 0x27; +constexpr uint8_t VRIGHT_HSCROLL = 0x29; +constexpr uint8_t VLEFT_HSCROLL = 0x2A; +constexpr uint8_t SCROLL_OFF = 0x2E; +constexpr uint8_t SCROLL_ON = 0x2F; +constexpr uint8_t VSCROLL_AREA = 0xA3; + +} + +namespace picoled +{ + +ssd1306::ssd1306(i2c_inst* i2c, uint8_t address) : + oled(128, 64), + i2c(i2c), + address(address), + buffer(new uint8_t[128 * 64 / 8 + 1]) +{ + // the user is expected to set GPIO func and pullup + // as well as initialize the i2c instance + + command(OFF); + command(CLKDIV); command(0x80); + command(MULTIPLEX); command(63); + command(OFFSET); command(0); + command(STARTLINE | 0); + command(MEMORY_MODE); command(0); + command(SEGMENT_REMAP | 0); + command(COMSCAN_DEC); + command(COMPINS); command(0x12); + command(CONTRAST); command(0xCF); + command(PRECHARGE); command(0x22); + command(VCOM_DETECT); command(0x40); + command(NORMAL_DISPLAY); + command(SCROLL_OFF); + command(CHARGE_PUMP); command(0x14); + command(ALLON_RESUME); + command(ON); + + buffer[0] = 0x40; +} + +ssd1306::~ssd1306() +{ + delete[] buffer; +} + +void ssd1306::update() const +{ + // clear display to zero + memset(&buffer[1], 0, width * height * sizeof(*buffer)); + + for (int x = 0; x < width; x++) + { + for (int y = 0; y < height; y++) + { + if (!(*this)(x, y).is_on()) { continue; } + + const int index = (width - x - 1) + (y / 8) * width; + const uint8_t bits = 1 << (y & 7); + + // add one to leave room for 0x40 data indicator at beginning of array + buffer[index + 1] |= bits; + } + } + + command(PAGE_ADDRESS); command(0); command(7); + command(COLUMN_ADDRESS); command(0); command(width - 1); + i2c_write_blocking(i2c, address, buffer, width * height / 8 + 1, false); +} + +void ssd1306::command(uint8_t cmd) const +{ + const uint8_t values[2] = { 0x00, cmd }; + i2c_write_blocking(i2c, address, values, 2, false); +} + +} diff --git a/src/picoled.cpp b/src/picoled.cpp new file mode 100644 index 0000000..dc95ec0 --- /dev/null +++ b/src/picoled.cpp @@ -0,0 +1,35 @@ +#include "picoled.h" + +namespace picoled +{ + +oled::oled(size_t width, size_t height) : + pixels(new pixel[width * height]), + width(width), + height(height) +{} + +oled::~oled() +{ + delete[] pixels; +} + +pixel& oled::operator()(int x, int y) +{ + return pixels[x + y * width]; +} + +const pixel& oled::operator()(int x, int y) const +{ + return pixels[x + y * width]; +} + +void oled::clear() +{ + for (int i = 0; i < width * height; i++) + { + pixels[i] = false; + } +} + +}