diff --git a/CMakeLists.txt b/CMakeLists.txt index 2dc4019..eae5888 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,11 +5,14 @@ add_library(picoled include/picoled/buffer.h include/picoled/gfx/2D.h include/picoled/SSD1306.h + include/picoled/ST7789.h src/picoled.cpp + src/buffer.cpp src/2D.cpp src/SSD1306.cpp + src/ST7789.cpp ) -target_link_libraries(picoled PUBLIC hardware_i2c pico_stdlib) +target_link_libraries(picoled PUBLIC pico_stdlib hardware_i2c hardware_spi) target_include_directories(picoled PUBLIC include) diff --git a/include/picoled.h b/include/picoled.h index 13fbcb1..ed520d8 100644 --- a/include/picoled.h +++ b/include/picoled.h @@ -8,7 +8,6 @@ namespace picoled { -// rgb565 class pixel { public: @@ -59,7 +58,7 @@ public: size_t get_width() const { return buffer_a.get_width(); } size_t get_height() const { return buffer_a.get_height(); } - void write(int x, int y, const pixel& p) { active_buffer().write(x, y, p); } + virtual void write(int x, int y, const pixel& p) { active_buffer().write(x, y, p); } buffer& active_buffer() { return active ? buffer_b : buffer_a; } @@ -67,9 +66,10 @@ public: void swap_buffers(); protected: - virtual void update_impl() = 0; + virtual void update_impl() const = 0; buffer& inactive_buffer() { return active ? buffer_a : buffer_b; } + const buffer& inactive_buffer() const { return active ? buffer_a : buffer_b; } private: bool active; diff --git a/include/picoled/SSD1306.h b/include/picoled/SSD1306.h index fa25d08..08133bf 100644 --- a/include/picoled/SSD1306.h +++ b/include/picoled/SSD1306.h @@ -1,8 +1,6 @@ #ifndef PICOLED_SSD1306_H #define PICOLED_SSD1306_H -#include - #include #include "picoled.h" @@ -17,7 +15,7 @@ public: virtual ~ssd1306(); protected: - void update_impl() override; + void update_impl() const override; private: void command(uint8_t cmd) const; diff --git a/include/picoled/ST7789.h b/include/picoled/ST7789.h new file mode 100644 index 0000000..ddf7b46 --- /dev/null +++ b/include/picoled/ST7789.h @@ -0,0 +1,39 @@ +#ifndef PICOLED_ST7889_H +#define PICOLED_ST7889_H + +#include + +#include "picoled.h" + +namespace picoled +{ + +class st7789 : public oled +{ +public: + st7789(size_t width, size_t height, spi_inst_t* spi, uint8_t cs_pin, uint8_t dc_pin); + virtual ~st7789() = default; + + void write(int x, int y, const pixel& p) override + { + oled::write(x, y, format(p)); + } + +protected: + void update_impl() const override; + +private: + void command(uint8_t cmd) const; + void command(uint8_t cmd, uint8_t param) const; + void command(uint8_t cmd, const uint8_t* params, size_t param_count) const; + + spi_inst_t* spi; + uint8_t cs_pin; + uint8_t dc_pin; + + static pixel format(const pixel& p); +}; + +} + +#endif//PICOLED_ST7889_H diff --git a/include/picoled/buffer.h b/include/picoled/buffer.h index a5b74ab..3e8bdd9 100644 --- a/include/picoled/buffer.h +++ b/include/picoled/buffer.h @@ -2,10 +2,45 @@ #define PICOLED_BUFFER_H #include +#include namespace picoled { +class fixed +{ +public: + fixed(); + + fixed(float value); + operator int16_t() const; + + fixed operator-() const; + fixed operator~() const; + + fixed operator+(const fixed& other) const; + fixed operator-(const fixed& other) const; + fixed operator*(const fixed& other) const; + fixed operator/(const fixed& other) const; + + fixed& operator+=(const fixed& other); + fixed& operator-=(const fixed& other); + fixed& operator*=(const fixed& other); + fixed& operator/=(const fixed& other); + + bool operator<(const fixed& other) const; + bool operator<=(const fixed& other) const; + bool operator>(const fixed& other) const; + bool operator>=(const fixed& other) const; + bool operator==(const fixed& other) const; + + static fixed min(fixed a, fixed b); + static fixed max(fixed a, fixed b); + +private: + int32_t value; +}; + // behavior when out of bounds enum class buffer_read_mode { @@ -45,9 +80,9 @@ public: data[x + y * width] = pixel; } - T read_uv(float x, float y, buffer_read_mode mode = buffer_read_mode::clamp) const + T read_uv(fixed x, fixed y, buffer_read_mode mode = buffer_read_mode::clamp) const { - return read(x * width, y * height, mode); + return read(x * fixed(width), y * fixed(height), mode); } T read(int x, int y, buffer_read_mode mode = buffer_read_mode::clamp) const @@ -67,6 +102,11 @@ public: return T(); } + const uint8_t* data_pointer() const + { + return reinterpret_cast(data); + } + void clear() { for (int i = 0; i < width * height; i++) diff --git a/include/picoled/gfx/2D.h b/include/picoled/gfx/2D.h index 2e445fa..4403578 100644 --- a/include/picoled/gfx/2D.h +++ b/include/picoled/gfx/2D.h @@ -9,31 +9,31 @@ namespace picoled::gfx class vec2 { public: - float x; - float y; + fixed x; + fixed y; vec2 operator+(const vec2& other) const { return { x + other.x, y + other.y }; } vec2 operator-(const vec2& other) const { return { x - other.x, y - other.y }; } vec2 operator*(const vec2& other) const { return { x * other.x, y * other.y }; } vec2 operator/(const vec2& other) const { return { x / other.x, y / other.y }; } - float operator%(const vec2& other) const { return x * other.x + y * other.y; } - float operator^(const vec2& other) const { return x * other.y - y * other.x; } + fixed operator%(const vec2& other) const { return x * other.x + y * other.y; } + fixed operator^(const vec2& other) const { return x * other.y - y * other.x; } }; class vec3 { public: - float x; - float y; - float z; + fixed x; + fixed y; + fixed z; vec3 operator+(const vec3& other) const { return { x + other.x, y + other.y, z + other.z }; } vec3 operator-(const vec3& other) const { return { x - other.x, y - other.y, z - other.z }; } vec3 operator*(const vec3& other) const { return { x * other.x, y * other.y, z * other.z }; } vec3 operator/(const vec3& other) const { return { x / other.x, y / other.y, z / other.z }; } - float operator%(const vec3& other) const { return x * other.x + y * other.y + z * other.z; } + fixed operator%(const vec3& other) const { return x * other.x + y * other.y + z * other.z; } vec3 operator^(const vec3& other) const { return @@ -67,7 +67,7 @@ public: vec2 max() const; private: - float area_double() const; + fixed area_double() const; }; class gfx2d diff --git a/src/2D.cpp b/src/2D.cpp index 64f57a9..4a5fd89 100644 --- a/src/2D.cpp +++ b/src/2D.cpp @@ -10,7 +10,7 @@ namespace picoled::gfx triangle triangle::fix_winding() const { - if (((b.position - a.position) ^ (c.position - a.position)) < 0) + if (((b.position - a.position) ^ (c.position - a.position)) < fixed(0)) { return { a, c, b }; } @@ -22,27 +22,27 @@ triangle triangle::fix_winding() const vec3 triangle::barycentric(const vec2& p) const { - const float total = area_double(); - const float u = triangle{ b, a, p }.area_double() / total; - const float v = triangle{ a, c, p }.area_double() / total; - return vec3{ u, v, 1 - u - v }; + const fixed total = area_double(); + const fixed u = triangle{ b, a, p }.area_double() / total; + const fixed v = triangle{ a, c, p }.area_double() / total; + return vec3{ u, v, fixed(1) - u - v }; } bool triangle::contains(const vec2& p) const { - const float abp = (b.position - a.position) ^ (p - a.position); - const float bcp = (c.position - b.position) ^ (p - b.position); - const float cap = (a.position - c.position) ^ (p - c.position); + const fixed abp = (b.position - a.position) ^ (p - a.position); + const fixed bcp = (c.position - b.position) ^ (p - b.position); + const fixed cap = (a.position - c.position) ^ (p - c.position); - return abp >= 0 && bcp >= 0 && cap >= 0; + return abp >= fixed(0) && bcp >= fixed(0) && cap >= fixed(0); } vec2 triangle::min() const { return { - fminf(fminf(a.position.x, b.position.x), c.position.x), - fminf(fminf(a.position.y, b.position.y), c.position.y) + fixed::min(fixed::min(a.position.x, b.position.x), c.position.x), + fixed::min(fixed::min(a.position.y, b.position.y), c.position.y) }; } @@ -50,12 +50,12 @@ vec2 triangle::max() const { return { - fmaxf(fmaxf(a.position.x, b.position.x), c.position.x), - fmaxf(fmaxf(a.position.y, b.position.y), c.position.y) + fixed::max(fixed::max(a.position.x, b.position.x), c.position.x), + fixed::max(fixed::max(a.position.y, b.position.y), c.position.y) }; } -float triangle::area_double() const +fixed triangle::area_double() const { // don't divide by two since it would cancel out // in the barycentric coordinate calculation anyway @@ -69,16 +69,16 @@ void gfx2d::draw_triangle(triangle tri, const buffer& texture, bool ignor const vec2 min = tri.min(); const vec2 max = tri.max(); - for (float i = min.x; i <= max.x; i += 0.75f) + for (fixed i = min.x; i <= max.x; i += 1) { - for (float j = min.y; j <= max.y; j += 0.75f) + for (fixed j = min.y; j <= max.y; j += 1) { const vec2 position = { i, j }; if (tri.contains(position)) { const vec3 barycentric = tri.barycentric(position); - const float u = barycentric % vec3{ tri.a.texture_coordinates.x, tri.b.texture_coordinates.x, tri.c.texture_coordinates.x }; - const float v = barycentric % vec3{ tri.a.texture_coordinates.y, tri.b.texture_coordinates.y, tri.c.texture_coordinates.y }; + const fixed u = barycentric % vec3{ tri.a.texture_coordinates.x, tri.b.texture_coordinates.x, tri.c.texture_coordinates.x }; + const fixed v = barycentric % vec3{ tri.a.texture_coordinates.y, tri.b.texture_coordinates.y, tri.c.texture_coordinates.y }; const pixel read = texture.read_uv(u, v, buffer_read_mode::clamp); screen->write(i, j, read); } diff --git a/src/SSD1306.cpp b/src/SSD1306.cpp index 04d10f4..f03fc2b 100644 --- a/src/SSD1306.cpp +++ b/src/SSD1306.cpp @@ -78,7 +78,7 @@ ssd1306::~ssd1306() delete[] buffer; } -void ssd1306::update_impl() +void ssd1306::update_impl() const { // clear display to zero memset(&buffer[1], 0, get_width() * get_height() * sizeof(*buffer) / 8); diff --git a/src/ST7789.cpp b/src/ST7789.cpp new file mode 100644 index 0000000..e07db4b --- /dev/null +++ b/src/ST7789.cpp @@ -0,0 +1,92 @@ +#include "picoled/ST7789.h" + +#include + +namespace picoled +{ + +st7789::st7789(size_t width, size_t height, spi_inst_t* spi, uint8_t cs_pin, uint8_t dc_pin) : + oled(width, height), + spi(spi), + cs_pin(cs_pin), + dc_pin(dc_pin) +{ + command(0x01); sleep_ms(150); // reset device + command(0x11); sleep_ms(10); // stop sleeping + command(0x3A, 0x66); sleep_ms(10); // set display format + { + constexpr size_t PARAM_COUNT = 4; + // no clue where these numbers come from + // see https://github.com/adafruit/Adafruit-ST7735-Library/blob/62112b90eddcb2ecc51f474e9fe98b68eb26cb2a/Adafruit_ST7789.cpp#L111-L117 + const uint8_t params[PARAM_COUNT] = + { + 0x00, + 0x35, + 0x00, + 0xBB + }; + + command(0x2A, params, PARAM_COUNT); // set column address range + } + { + constexpr size_t PARAM_COUNT = 4; + // no clue where these numbers come from + // see https://github.com/adafruit/Adafruit-ST7735-Library/blob/62112b90eddcb2ecc51f474e9fe98b68eb26cb2a/Adafruit_ST7789.cpp#L111-L117 + const uint8_t params[PARAM_COUNT] = + { + 0x00, + 0x28, + 0x01, + 0x17 + }; + + command(0x2B, params, PARAM_COUNT); // set row address range + } + command(0x36, 0b11000000); // set memory addressing mode + command(0x21); // turn on display inversion + command(0x13); sleep_ms(10); // set display to normal mode + command(0x29); sleep_ms(10); // turn on display +} + +void st7789::update_impl() const +{ + command(0x2C, inactive_buffer().data_pointer(), get_width() * get_height() * 3); +} + +void st7789::command(uint8_t cmd) const +{ + command(cmd, nullptr, 0); +} + +void st7789::command(uint8_t cmd, uint8_t param) const +{ + command(cmd, ¶m, 1); +} + +void st7789::command(uint8_t cmd, const uint8_t* params, size_t param_count) const +{ + gpio_put(cs_pin, false); // select chip + + gpio_put(dc_pin, false); // command mode + spi_write_blocking(spi, &cmd, 1); + + gpio_put(dc_pin, true); // parameters are sent via data mode + spi_write_blocking(spi, params, param_count); + + gpio_put(cs_pin, true); // deselect chip +} + +pixel st7789::format(const pixel& p) +{ + const uint16_t red_scaled = p.red() * 0x3F / 0xFF; // scale to 6-bit color + const uint16_t green_scaled = p.green() * 0x3F / 0xFF; // scale to 6-bit color + const uint16_t blue_scaled = p.blue() * 0x3F / 0xFF; // scale to 6-bit color + + const uint8_t red = (red_scaled & 0xFF) << 2; + const uint8_t green = (green_scaled & 0xFF) << 2; + const uint8_t blue = (blue_scaled & 0xFF) << 2; + + return { red, green, blue }; +} + +} diff --git a/src/buffer.cpp b/src/buffer.cpp new file mode 100644 index 0000000..297c31e --- /dev/null +++ b/src/buffer.cpp @@ -0,0 +1,110 @@ +#include "picoled/buffer.h" + +#include + +namespace +{ + +constexpr int FRACTIONAL_BITS = 16; + +} + +namespace picoled +{ + +fixed::fixed() : + value(0) +{} + +fixed::fixed(float value) : + value(value * (1 << FRACTIONAL_BITS)) +{} + +fixed::operator int16_t() const +{ + return value >> FRACTIONAL_BITS; +} + +fixed fixed::operator-() const +{ + fixed f; + f.value = -value; + return f; +} + +fixed fixed::operator~() const +{ + fixed f; + f.value = ~value; + return f; +} + +fixed fixed::operator+(const fixed& other) const +{ + fixed copy = *this; + copy += other; + return copy; +} + +fixed fixed::operator-(const fixed& other) const +{ + fixed copy = *this; + copy -= other; + return copy; +} + +fixed fixed::operator*(const fixed& other) const +{ + fixed copy = *this; + copy *= other; + return copy; +} + +fixed fixed::operator/(const fixed& other) const +{ + fixed copy = *this; + copy /= other; + return copy; +} + +fixed& fixed::operator+=(const fixed& other) +{ + value += other.value; + return *this; +} + +fixed& fixed::operator-=(const fixed& other) +{ + value -= other.value; + return *this; +} + +fixed& fixed::operator*=(const fixed& other) +{ + value = (int64_t(value) * int64_t(other.value)) >> FRACTIONAL_BITS; + return *this; +} + +fixed& fixed::operator/=(const fixed& other) +{ + value = (int64_t(value) << FRACTIONAL_BITS) / other.value; + return *this; +} + +bool fixed::operator>(const fixed& other) const { return value > other.value; } +bool fixed::operator>=(const fixed& other) const { return value >= other.value; } +bool fixed::operator<(const fixed& other) const { return value < other.value; } +bool fixed::operator<=(const fixed& other) const { return value <= other.value; } +bool fixed::operator==(const fixed& other) const { return value == other.value; } + +fixed fixed::min(fixed a, fixed b) +{ + return a < b ? a : b; +} + +fixed fixed::max(fixed a, fixed b) +{ + return a > b ? a : b; +} + +}