Add partial ST7789 support

This commit is contained in:
shylie 2025-06-14 17:34:57 -04:00
parent dfeccd50b9
commit 846f909cfe
10 changed files with 319 additions and 37 deletions

View File

@ -5,11 +5,14 @@ add_library(picoled
include/picoled/buffer.h include/picoled/buffer.h
include/picoled/gfx/2D.h include/picoled/gfx/2D.h
include/picoled/SSD1306.h include/picoled/SSD1306.h
include/picoled/ST7789.h
src/picoled.cpp src/picoled.cpp
src/buffer.cpp
src/2D.cpp src/2D.cpp
src/SSD1306.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) target_include_directories(picoled PUBLIC include)

View File

@ -8,7 +8,6 @@
namespace picoled namespace picoled
{ {
// rgb565
class pixel class pixel
{ {
public: public:
@ -59,7 +58,7 @@ public:
size_t get_width() const { return buffer_a.get_width(); } size_t get_width() const { return buffer_a.get_width(); }
size_t get_height() const { return buffer_a.get_height(); } 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<pixel>& active_buffer() { return active ? buffer_b : buffer_a; } buffer<pixel>& active_buffer() { return active ? buffer_b : buffer_a; }
@ -67,9 +66,10 @@ public:
void swap_buffers(); void swap_buffers();
protected: protected:
virtual void update_impl() = 0; virtual void update_impl() const = 0;
buffer<pixel>& inactive_buffer() { return active ? buffer_a : buffer_b; } buffer<pixel>& inactive_buffer() { return active ? buffer_a : buffer_b; }
const buffer<pixel>& inactive_buffer() const { return active ? buffer_a : buffer_b; }
private: private:
bool active; bool active;

View File

@ -1,8 +1,6 @@
#ifndef PICOLED_SSD1306_H #ifndef PICOLED_SSD1306_H
#define PICOLED_SSD1306_H #define PICOLED_SSD1306_H
#include <cstdint>
#include <hardware/i2c.h> #include <hardware/i2c.h>
#include "picoled.h" #include "picoled.h"
@ -17,7 +15,7 @@ public:
virtual ~ssd1306(); virtual ~ssd1306();
protected: protected:
void update_impl() override; void update_impl() const override;
private: private:
void command(uint8_t cmd) const; void command(uint8_t cmd) const;

39
include/picoled/ST7789.h Normal file
View File

@ -0,0 +1,39 @@
#ifndef PICOLED_ST7889_H
#define PICOLED_ST7889_H
#include <hardware/spi.h>
#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

View File

@ -2,10 +2,45 @@
#define PICOLED_BUFFER_H #define PICOLED_BUFFER_H
#include <cstddef> #include <cstddef>
#include <cstdint>
namespace picoled 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 // behavior when out of bounds
enum class buffer_read_mode enum class buffer_read_mode
{ {
@ -45,9 +80,9 @@ public:
data[x + y * width] = pixel; 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 T read(int x, int y, buffer_read_mode mode = buffer_read_mode::clamp) const
@ -67,6 +102,11 @@ public:
return T(); return T();
} }
const uint8_t* data_pointer() const
{
return reinterpret_cast<const uint8_t*>(data);
}
void clear() void clear()
{ {
for (int i = 0; i < width * height; i++) for (int i = 0; i < width * height; i++)

View File

@ -9,31 +9,31 @@ namespace picoled::gfx
class vec2 class vec2
{ {
public: public:
float x; fixed x;
float y; 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 }; }
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; } fixed 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.y - y * other.x; }
}; };
class vec3 class vec3
{ {
public: public:
float x; fixed x;
float y; fixed y;
float z; 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 }; }
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 vec3 operator^(const vec3& other) const
{ {
return return
@ -67,7 +67,7 @@ public:
vec2 max() const; vec2 max() const;
private: private:
float area_double() const; fixed area_double() const;
}; };
class gfx2d class gfx2d

View File

@ -10,7 +10,7 @@ namespace picoled::gfx
triangle triangle::fix_winding() const 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 }; return { a, c, b };
} }
@ -22,27 +22,27 @@ triangle triangle::fix_winding() const
vec3 triangle::barycentric(const vec2& p) const vec3 triangle::barycentric(const vec2& p) const
{ {
const float total = area_double(); const fixed total = area_double();
const float u = triangle{ b, a, p }.area_double() / total; const fixed u = triangle{ b, a, p }.area_double() / total;
const float v = triangle{ a, c, p }.area_double() / total; const fixed v = triangle{ a, c, p }.area_double() / total;
return vec3{ u, v, 1 - u - v }; return vec3{ u, v, fixed(1) - u - v };
} }
bool triangle::contains(const vec2& p) const bool triangle::contains(const vec2& p) const
{ {
const float abp = (b.position - a.position) ^ (p - a.position); const fixed abp = (b.position - a.position) ^ (p - a.position);
const float bcp = (c.position - b.position) ^ (p - b.position); const fixed bcp = (c.position - b.position) ^ (p - b.position);
const float cap = (a.position - c.position) ^ (p - c.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 vec2 triangle::min() const
{ {
return return
{ {
fminf(fminf(a.position.x, b.position.x), c.position.x), fixed::min(fixed::min(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.y, b.position.y), c.position.y)
}; };
} }
@ -50,12 +50,12 @@ vec2 triangle::max() const
{ {
return return
{ {
fmaxf(fmaxf(a.position.x, b.position.x), c.position.x), fixed::max(fixed::max(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.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 // don't divide by two since it would cancel out
// in the barycentric coordinate calculation anyway // in the barycentric coordinate calculation anyway
@ -69,16 +69,16 @@ void gfx2d::draw_triangle(triangle tri, const buffer<pixel>& texture, bool ignor
const vec2 min = tri.min(); const vec2 min = tri.min();
const vec2 max = tri.max(); 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 }; const vec2 position = { i, j };
if (tri.contains(position)) if (tri.contains(position))
{ {
const vec3 barycentric = tri.barycentric(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 fixed 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 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); const pixel read = texture.read_uv(u, v, buffer_read_mode::clamp);
screen->write(i, j, read); screen->write(i, j, read);
} }

View File

@ -78,7 +78,7 @@ ssd1306::~ssd1306()
delete[] buffer; delete[] buffer;
} }
void ssd1306::update_impl() void ssd1306::update_impl() const
{ {
// clear display to zero // clear display to zero
memset(&buffer[1], 0, get_width() * get_height() * sizeof(*buffer) / 8); memset(&buffer[1], 0, get_width() * get_height() * sizeof(*buffer) / 8);

92
src/ST7789.cpp Normal file
View File

@ -0,0 +1,92 @@
#include "picoled/ST7789.h"
#include <pico/stdlib.h>
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, &param, 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 };
}
}

110
src/buffer.cpp Normal file
View File

@ -0,0 +1,110 @@
#include "picoled/buffer.h"
#include <pico/stdio.h>
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;
}
}