diff --git a/src/daisy.h b/src/daisy.h index c629ba8b3..98af3c581 100644 --- a/src/daisy.h +++ b/src/daisy.h @@ -37,6 +37,8 @@ #include "per/rng.h" #include "hid/disp/display.h" #include "hid/disp/oled_display.h" +#include "hid/disp/color_display.h" +#include "hid/disp/oled_color_display.h" #include "hid/disp/graphics_common.h" #include "hid/wavplayer.h" #include "hid/led.h" diff --git a/src/dev/oled_ssd1327.h b/src/dev/oled_ssd1327.h new file mode 100644 index 000000000..079973b82 --- /dev/null +++ b/src/dev/oled_ssd1327.h @@ -0,0 +1,233 @@ +#pragma once + +#include "per/spi.h" +#include "per/gpio.h" +#include "sys/system.h" + +namespace daisy +{ + +/** + * 4 Wire SPI Transport for SSD1327 OLED display devices + */ +class SSD13274WireSpiTransport +{ + public: + struct Config + { + Config() + { + // Initialize using defaults + Defaults(); + } + SpiHandle::Config spi_config; + struct + { + dsy_gpio_pin dc; /**< & */ + dsy_gpio_pin reset; /**< & */ + } pin_config; + void Defaults() + { + // SPI peripheral config + spi_config.periph = SpiHandle::Config::Peripheral::SPI_1; + spi_config.mode = SpiHandle::Config::Mode::MASTER; + spi_config.direction + = SpiHandle::Config::Direction::TWO_LINES_TX_ONLY; + spi_config.datasize = 8; + spi_config.clock_polarity = SpiHandle::Config::ClockPolarity::LOW; + spi_config.clock_phase = SpiHandle::Config::ClockPhase::ONE_EDGE; + spi_config.nss = SpiHandle::Config::NSS::HARD_OUTPUT; + spi_config.baud_prescaler = SpiHandle::Config::BaudPrescaler::PS_8; + // SPI pin config + spi_config.pin_config.sclk = {DSY_GPIOG, 11}; + spi_config.pin_config.miso = {DSY_GPIOX, 0}; + spi_config.pin_config.mosi = {DSY_GPIOB, 5}; + spi_config.pin_config.nss = {DSY_GPIOG, 10}; + // SSD1327 control pin config + pin_config.dc = {DSY_GPIOB, 4}; + pin_config.reset = {DSY_GPIOB, 15}; + } + }; + void Init(const Config& config) + { + // Initialize both GPIO + pin_dc_.mode = DSY_GPIO_MODE_OUTPUT_PP; + pin_dc_.pin = config.pin_config.dc; + dsy_gpio_init(&pin_dc_); + pin_reset_.mode = DSY_GPIO_MODE_OUTPUT_PP; + pin_reset_.pin = config.pin_config.reset; + dsy_gpio_init(&pin_reset_); + + // Initialize SPI + spi_.Init(config.spi_config); + + // Reset and Configure OLED. + dsy_gpio_write(&pin_reset_, 0); + System::Delay(10); + dsy_gpio_write(&pin_reset_, 1); + System::Delay(10); + }; + void SendCommand(uint8_t cmd) + { + dsy_gpio_write(&pin_dc_, 0); + spi_.BlockingTransmit(&cmd, 1); + }; + + void SendData(uint8_t* buff, size_t size) + { + dsy_gpio_write(&pin_dc_, 1); + spi_.BlockingTransmit(buff, size); + }; + + private: + SpiHandle spi_; + dsy_gpio pin_reset_; + dsy_gpio pin_dc_; +}; + + +/** + * A driver implementation for the SSD1327 + */ +template +class SSD1327Driver +{ + public: + struct Config + { + typename Transport::Config transport_config; + }; + + void Init(Config config) + { + color_ = 0x0f; + transport_.Init(config.transport_config); + + transport_.SendCommand(0x15); // set column address + transport_.SendCommand(0x00); // start column 0 + transport_.SendCommand(0x3f); // end column 63*2 (two pixels / byte) + + transport_.SendCommand(0x75); // set row address + transport_.SendCommand(0x00); // start row 0 + transport_.SendCommand(0x7f); // end row 127 + + transport_.SendCommand(0x81); // set contrast control + transport_.SendCommand(0x80); + + transport_.SendCommand(0xa0); // Set Re-map (0x51) + transport_.SendCommand(0x51); // Column Address Remapping, COM Remapping, Splitting of Odd / Even COM Signals + + transport_.SendCommand(0xa1); // start line + transport_.SendCommand(0x00); + + transport_.SendCommand(0xa2); // display offset + transport_.SendCommand(0x00); + + transport_.SendCommand(0xa4); // normal display + transport_.SendCommand(0xa8); // set multiplex ratio + transport_.SendCommand(0x7f); + + transport_.SendCommand(0xb1); // set phase length + transport_.SendCommand(0xf1); + + transport_.SendCommand(0xb3); // set dclk + transport_.SendCommand(0x00); // 80Hz:0xc1 / 90Hz:0xe1 / 100Hz:0x00 / 110Hz:0x30 / 120Hz:0x50 / 130Hz:0x70 + + transport_.SendCommand(0xab); // Function Selection A + transport_.SendCommand(0x01); + + transport_.SendCommand(0xb6); // set phase length + transport_.SendCommand(0x0f); + + transport_.SendCommand(0xbe); // Set VCOMH + transport_.SendCommand(0x0f); + + transport_.SendCommand(0xbc); // Set Pre-charge voltage + transport_.SendCommand(0x08); + + transport_.SendCommand(0xd5); // Function Selection B + transport_.SendCommand(0x62); // Enable second pre-charge + + transport_.SendCommand(0xfd); // unlock command + transport_.SendCommand(0x12); + + System::Delay(200); // wait 200ms + transport_.SendCommand(0xaf); // turn on display + Fill(false); + }; + + size_t Width() const { return width; }; + size_t Height() const { return height; }; + + void DrawPixel(uint_fast8_t x, uint_fast8_t y, bool on) + { + uint8_t pixel; + uint32_t line = width/2; + + if ((x >= width) || (y >= height)) + return; + + if (on) { + pixel = buffer_[y * line + (x / 2)]; + if (x % 2) { + pixel &= 0xf0; + pixel |= color_; + } else { + pixel &= 0x0f; + pixel |= color_ << 4; + } + buffer_[y * line + (x / 2)] = pixel; + } + else { + pixel = buffer_[y * line + (x / 2)]; + if (x % 2) { + pixel &= 0xf0; + } else { + pixel &= 0x0f; + } + buffer_[y * line + (x / 2)] = pixel; + } + }; + + void Fill(bool on) + { + for(size_t i = 0; i < sizeof(buffer_); i++) + { + buffer_[i] = on ? 0xff : 0x00; + } + }; + + /** + * Update the display + */ + void Update() + { + transport_.SendCommand(0x15); // column + transport_.SendCommand(0x00); + transport_.SendCommand((width/2)-1); + + transport_.SendCommand(0x75); // row + transport_.SendCommand(0x00); + transport_.SendCommand(height-1); + + //write data + transport_.SendData(buffer_, 8192); + }; + + void Set_Color(uint8_t in_col) + { + color_ = in_col & 0x0f; + }; + + protected: + Transport transport_; + uint8_t buffer_[width/2 * height]; + uint8_t color_; +}; + +/** + * A driver for the SSD1327 128x128 OLED displays connected via 4 wire SPI + */ +using SSD13274WireSpi128x128Driver = daisy::SSD1327Driver<128, 128, SSD13274WireSpiTransport>; + +}; // namespace daisy diff --git a/src/dev/oled_ssd1351.h b/src/dev/oled_ssd1351.h new file mode 100644 index 000000000..7f0995f35 --- /dev/null +++ b/src/dev/oled_ssd1351.h @@ -0,0 +1,266 @@ +#pragma once + +#include "per/spi.h" +#include "per/gpio.h" +#include "sys/system.h" + +#define oled_white 0xffff +#define oled_black 0x0000 +#define oled_red 0x00f1 +#define oled_green 0xe007 +#define oled_blue 0x1f00 +#define oled_cyan (oled_green|oled_blue) +#define oled_yellow (oled_green|oled_red) +#define oled_magenta (oled_red|oled_blue) + +namespace daisy +{ + +/** + * 4 Wire SPI Transport for SSD1351 OLED display devices + */ +class SSD13514WireSpiTransport +{ + public: + struct Config + { + Config() + { + // Initialize using defaults + Defaults(); + } + SpiHandle::Config spi_config; + struct + { + dsy_gpio_pin dc; /**< & */ + dsy_gpio_pin reset; /**< & */ + } pin_config; + void Defaults() + { + // SPI peripheral config + spi_config.periph = SpiHandle::Config::Peripheral::SPI_1; + spi_config.mode = SpiHandle::Config::Mode::MASTER; + spi_config.direction + = SpiHandle::Config::Direction::TWO_LINES_TX_ONLY; + spi_config.datasize = 8; + spi_config.clock_polarity = SpiHandle::Config::ClockPolarity::LOW; + spi_config.clock_phase = SpiHandle::Config::ClockPhase::ONE_EDGE; + spi_config.nss = SpiHandle::Config::NSS::HARD_OUTPUT; + spi_config.baud_prescaler = SpiHandle::Config::BaudPrescaler::PS_8; + // SPI pin config + spi_config.pin_config.sclk = {DSY_GPIOG, 11}; + spi_config.pin_config.miso = {DSY_GPIOX, 0}; + spi_config.pin_config.mosi = {DSY_GPIOB, 5}; + spi_config.pin_config.nss = {DSY_GPIOG, 10}; + // SSD1351 control pin config + pin_config.dc = {DSY_GPIOB, 4}; + pin_config.reset = {DSY_GPIOB, 15}; + } + }; + void Init(const Config& config) + { + // Initialize both GPIO + pin_dc_.mode = DSY_GPIO_MODE_OUTPUT_PP; + pin_dc_.pin = config.pin_config.dc; + dsy_gpio_init(&pin_dc_); + pin_reset_.mode = DSY_GPIO_MODE_OUTPUT_PP; + pin_reset_.pin = config.pin_config.reset; + dsy_gpio_init(&pin_reset_); + + // Initialize SPI + spi_.Init(config.spi_config); + + // Reset and Configure OLED. + dsy_gpio_write(&pin_reset_, 0); + System::Delay(10); + dsy_gpio_write(&pin_reset_, 1); + System::Delay(10); + }; + void SendCommand(uint8_t cmd) + { + dsy_gpio_write(&pin_dc_, 0); + spi_.BlockingTransmit(&cmd, 1); + }; + + void SendData(uint8_t* buff, size_t size) + { + dsy_gpio_write(&pin_dc_, 1); + spi_.BlockingTransmit(buff, size); + }; + + void SendData(uint8_t data) + { + dsy_gpio_write(&pin_dc_, 1); + spi_.BlockingTransmit(&data, 1); + }; + + private: + SpiHandle spi_; + dsy_gpio pin_reset_; + dsy_gpio pin_dc_; +}; + + +/** + * A driver implementation for the SSD1351 + */ +template +class SSD1351Driver +{ + public: + struct Config + { + typename Transport::Config transport_config; + }; + + void Init(Config config) + { + fg_color_ = oled_white; + bg_color_ = oled_black; + transport_.Init(config.transport_config); + + transport_.SendCommand(0xfd); // lock IC + transport_.SendData(0x12); + transport_.SendCommand(0xfd); // unlock IC + transport_.SendData(0xb1); // + + transport_.SendCommand(0xae); // display off + + transport_.SendCommand(0x15); // set column address + transport_.SendData(0x00); // column address start 00 + transport_.SendData(0x7f); // column address end 127 + + transport_.SendCommand(0x75); // set row address + transport_.SendData(0x00); // row address start 00 + transport_.SendData(0x7f); // row address end 127 + + transport_.SendCommand(0xB3); // Set Front Clock Divider / Oscillator Frequency + transport_.SendData(0xF1); + + transport_.SendCommand(0xCA); // Set Multiplex Ratio + transport_.SendData(0x7F); + + transport_.SendCommand(0xa0); // Set Re-map & Dual COM Line Mode + transport_.SendData(0x74); // color mode 64k, enable com split, reverse com scan, color swapped, hz scan + + transport_.SendCommand(0xa1); // set display start line + transport_.SendData(0x00); // line 0 + + transport_.SendCommand(0xa2); // set display offset + transport_.SendData(0x00); // column 0 + + transport_.SendCommand(0xAB); // Function Selection + transport_.SendData(0x01); + + transport_.SendCommand(0xB4); // Set Segment Low Voltage + transport_.SendData(0xA0); + transport_.SendData(0xB5); + transport_.SendData(0x55); + + transport_.SendCommand(0xC1); // Set Contrast Current for Color A,B,C + transport_.SendData(0xC8); + transport_.SendData(0x80); + transport_.SendData(0xC0); + + transport_.SendCommand(0xC7); // Master Contrast Current Control + transport_.SendData(0x0F); + + transport_.SendCommand(0xB1); // Set Reset (Phase 1) / Pre-charge (Phase 2) period + transport_.SendData(0x32); + + transport_.SendCommand(0xB2); // Display Enhancement + transport_.SendData(0xA4); + transport_.SendData(0x00); + transport_.SendData(0x00); + + transport_.SendCommand(0xBB); // Set Pre-charge voltage + transport_.SendData(0x17); + + transport_.SendCommand(0xB6); // Set Second Precharge Period + transport_.SendData(0x01); + + transport_.SendCommand(0xBE); // Set VCOMH Voltage + transport_.SendData(0x05); + + transport_.SendCommand(0xA6); // Normal display + + System::Delay(300); // wait 300ms + transport_.SendCommand(0xaf); // turn on display + Fill(false); + }; + + size_t Width() const { return width; }; + size_t Height() const { return height; }; + + void DrawPixel(uint_fast8_t x, uint_fast8_t y, bool on) + { + if ((x >= width) || (y >= height)) + return; + + if (on) { + buffer_[(y * width) + x] = fg_color_; + } + else { + buffer_[(y * width) + x] = bg_color_; + } + }; + + void Fill(bool on) + { + for(size_t i = 0; i < sizeof(buffer_)/2; i++) + { + buffer_[i] = on ? fg_color_ : bg_color_; + } + }; + + /** + * Update the display + */ + void Update() + { + transport_.SendCommand(0x15); // column + transport_.SendData(0x00); + transport_.SendData(width-1); + + transport_.SendCommand(0x75); // row + transport_.SendData(0x00); + transport_.SendData(height-1); + + transport_.SendCommand(0x5c); // write display buffer + transport_.SendData((uint8_t*)buffer_, sizeof(buffer_)); + }; + + void SetColorFG(uint8_t red, uint8_t green, uint8_t blue) + { + uint16_t t1, t2; + + fg_color_ = (red & 0x1f) << 11 | (green & 0x3f) << 5 | (blue & 0x1f); + t1 = (fg_color_ >> 8) & 0xff; + t2 = (fg_color_ & 0xff); + fg_color_ = t2<<8 | t1; + + }; + + void SetColorBG(uint8_t red, uint8_t green, uint8_t blue) + { + uint16_t t1, t2; + + bg_color_ = (red & 0x1f) << 11 | (green & 0x3f) << 5 | (blue & 0x1f); + t1 = (bg_color_ >> 8) & 0xff; + t2 = (bg_color_ & 0xff); + bg_color_ = t2<<8 | t1; + }; + + protected: + Transport transport_; + uint16_t buffer_[width * height]; + uint16_t fg_color_; + uint16_t bg_color_; +}; + +/** + * A driver for the SSD1351 128x128 OLED displays connected via 4 wire SPI + */ +using SSD13514WireSpi128x128Driver = daisy::SSD1351Driver<128, 128, SSD13514WireSpiTransport>; + +}; // namespace daisy diff --git a/src/hid/disp/color_display.h b/src/hid/disp/color_display.h new file mode 100644 index 000000000..17c27b641 --- /dev/null +++ b/src/hid/disp/color_display.h @@ -0,0 +1,503 @@ +#pragma once +#include +#include "util/oled_fonts.h" +#include "daisy_core.h" +#include "graphics_common.h" + +#ifndef deg2rad +#define deg2rad(deg) ((deg)*3.141592 / 180.0) +#endif + +namespace daisy +{ +/** + * This interface is used as a base class for color + * graphics displays. +*/ +class ColorGraphicsDisplay +{ + public: + ColorGraphicsDisplay() {} + virtual ~ColorGraphicsDisplay() {} + + virtual uint16_t Height() const = 0; + virtual uint16_t Width() const = 0; + + Rectangle GetBounds() const + { + return Rectangle(int16_t(Width()), int16_t(Height())); + } + + + size_t CurrentX() { return currentX_; }; + size_t CurrentY() { return currentY_; }; + + /** + Fills the entire display with either on/off. + \param on Sets on or off. + */ + virtual void Fill(bool on) = 0; + + /** + Sets the pixel at the specified coordinate to be on/off. + \param x x Coordinate + \param y y coordinate + \param on on or off + */ + virtual void DrawPixel(uint_fast8_t x, uint_fast8_t y, bool on) = 0; + + /** + Set foreground color + \param red Red color + \param green Green color + \param blue Blue color + */ + virtual void SetColorFG(uint8_t red, uint8_t green, uint8_t blue) = 0; + + /** + Set background color + \param red Red color + \param green Green color + \param blue Blue color + */ + virtual void SetColorBG(uint8_t red, uint8_t green, uint8_t blue) = 0; + + /** + Draws a line from (x1, y1) to (y1, y2) + \param x1 x Coordinate of the starting point + \param y1 y Coordinate of the starting point + \param x2 x Coordinate of the ending point + \param y2 y Coordinate of the ending point + \param on on or off + */ + virtual void DrawLine(uint_fast8_t x1, + uint_fast8_t y1, + uint_fast8_t x2, + uint_fast8_t y2, + bool on) + = 0; + + /** + Draws a rectangle based on two coordinates. + \param x1 x Coordinate of the first point + \param y1 y Coordinate of the first point + \param x2 x Coordinate of the second point + \param y2 y Coordinate of the second point + \param on on or off + \param fill fill the rectangle or draw only the outline + */ + virtual void DrawRect(uint_fast8_t x1, + uint_fast8_t y1, + uint_fast8_t x2, + uint_fast8_t y2, + bool on, + bool fill = false) + = 0; + + /** + Draws a rectangle. + \param rect the rectangle + \param on on or off + \param fill fill the rectangle or draw only the outline + */ + void DrawRect(const Rectangle& rect, bool on, bool fill = false) + { + DrawRect(rect.GetX(), + rect.GetY(), + rect.GetRight(), + rect.GetBottom(), + on, + fill); + } + + /** + Draws an arc around the specified coordinate + \param x x Coordinate of the center of the arc + \param y y Coordinate of the center of the arc + \param radius radius of the arc + \param start_angle angle where to start the arc + \param sweep total angle of the arc + \param on on or off + */ + virtual void DrawArc(uint_fast8_t x, + uint_fast8_t y, + uint_fast8_t radius, + int_fast16_t start_angle, + int_fast16_t sweep, + bool on) + = 0; + + /** + Draws a circle around the specified coordinate + \param x x Coordinate of the center of the circle + \param y y Coordinate of the center of the circle + \param radius radius of the circle + \param on on or off + */ + void + DrawCircle(uint_fast8_t x, uint_fast8_t y, uint_fast8_t radius, bool on) + { + DrawArc(x, y, radius, 0, 360, on); + }; + + /** + Writes the character with the specific FontDef + to the display buffer at the current Cursor position. + \param ch character to be written + \param font font to be written in + \param on on or off + \return & + */ + virtual char WriteChar(char ch, FontDef font, bool on) = 0; + + /** + Similar to WriteChar, except it will handle an entire String. + Wrapping does not happen automatically, so the width + of the string must be kept within the dimensions of the screen. + \param str string to be written + \param font font to use + \param on on or off + \return & + */ + virtual char WriteString(const char* str, FontDef font, bool on) = 0; + + /** + Similar to WriteString but justified within a bounding box. + \param str string to be written + \param font font to use + \param boundingBox the bounding box to draw the text in + \param alignment the alignment to use + \param on on or off + \return The rectangle that was drawn to + */ + virtual Rectangle WriteStringAligned(const char* str, + const FontDef& font, + Rectangle boundingBox, + Alignment alignment, + bool on) + = 0; + + /** + Moves the 'Cursor' position used for WriteChar, and WriteStr to the specified coordinate. + \param x x pos + \param y y pos + */ + void SetCursor(uint16_t x, uint16_t y) + { + currentX_ = (x >= Width()) ? Width() - 1 : x; + currentY_ = (y >= Height()) ? Height() - 1 : y; + } + + /** + Writes the current display buffer to the OLED device using SPI or I2C depending on + how the object was initialized. + */ + virtual void Update() = 0; + + protected: + uint16_t currentX_; + uint16_t currentY_; +}; + +/** This class is intended as a intermediary class for your actual implementation of the ColorGraphicsDisplay + * interface. It uses the CRTP design pattern where the template argument is the child class. It provides + * implementations for most of the functions, except DrawPixel(), SetColorFG(), SetColorBG, Update() and + * Fill(), which you'll have to provide in your child class. + * The main goal of this class is to provide common drawing functions without relying on massive amounts of + * virtual function calls that would result in a performance loss. To achieve this, any drawing function that + * is implemented here and internally calls other drawing functions (e.g. DrawRect() which internally calls + * DrawPixel() and DrawLine()) makes these calls via the qualified name of these functions to explicitly + * suppress the virtual dispatch mechanism like this: + * + * ChildType::DrawPixel(...); // no virtual function call; direct call into the child class function + * + * To create a custom ColorGraphicsDisplay implementation, you can + * A) inherit from ColorGraphicsDisplay directly and provide all the drawing functions yourself + * B) Inherit from ColorGraphicsDisplayImpl and only provide DrawPixel(), Fill() and Update() + * like this: + * + * class MyDisplayClass : public ColorGraphicsDisplayImpl + * { + * public: + * void Fill() override { ... }; + * void DrawPixel(uint_fast8_t x, uint_fast8_t y, bool on) override { ... }; + * void Update() override { ... } + * }; + * + */ +template +class ColorGraphicsDisplayImpl : public ColorGraphicsDisplay +{ + public: + ColorGraphicsDisplayImpl() {} + virtual ~ColorGraphicsDisplayImpl() {} + + void DrawLine(uint_fast8_t x1, + uint_fast8_t y1, + uint_fast8_t x2, + uint_fast8_t y2, + bool on) override + { + int_fast16_t deltaX = abs((int_fast16_t)x2 - (int_fast16_t)x1); + int_fast16_t deltaY = abs((int_fast16_t)y2 - (int_fast16_t)y1); + int_fast16_t signX = ((x1 < x2) ? 1 : -1); + int_fast16_t signY = ((y1 < y2) ? 1 : -1); + int_fast16_t error = deltaX - deltaY; + int_fast16_t error2; + + // If we write "ChildType::DrawPixel(x2, y2, on);", we end up with + // all sorts of weird compiler errors when the Child class is a template + // class. The only way around this is to use this very verbose syntax: + ((ChildType*)(this))->ChildType::DrawPixel(x2, y2, on); + + while((x1 != x2) || (y1 != y2)) + { + ((ChildType*)(this))->ChildType::DrawPixel(x1, y1, on); + error2 = error * 2; + if(error2 > -deltaY) + { + error -= deltaY; + x1 += signX; + } + + if(error2 < deltaX) + { + error += deltaX; + y1 += signY; + } + } + } + + void DrawRect(uint_fast8_t x1, + uint_fast8_t y1, + uint_fast8_t x2, + uint_fast8_t y2, + bool on, + bool fill = false) override + { + if(fill) + { + for(uint_fast8_t x = x1; x <= x2; x++) + { + for(uint_fast8_t y = y1; y <= y2; y++) + { + ((ChildType*)(this))->ChildType::DrawPixel(x, y, on); + } + } + } + else + { + ((ChildType*)(this))->ChildType::DrawLine(x1, y1, x2, y1, on); + ((ChildType*)(this))->ChildType::DrawLine(x2, y1, x2, y2, on); + ((ChildType*)(this))->ChildType::DrawLine(x2, y2, x1, y2, on); + ((ChildType*)(this))->ChildType::DrawLine(x1, y2, x1, y1, on); + } + } + + void DrawArc(uint_fast8_t x, + uint_fast8_t y, + uint_fast8_t radius, + int_fast16_t start_angle, + int_fast16_t sweep, + bool on) override + { + // Values to calculate the circle + int_fast16_t t_x, t_y, err, e2; + + // Temporary values to speed up comparisons + float t_sxy, t_syx, t_sxny, t_synx; + float t_exy, t_eyx, t_exny, t_eynx; + + float start_angle_rad, end_angle_rad; + float start_x, start_y, end_x, end_y; + + bool d1, d2, d3, d4; + + d1 = d2 = d3 = d4 = true; + + bool circle = false; + + if(sweep < 0) + { + start_angle += sweep; + sweep = -sweep; + } + + start_angle_rad = deg2rad(start_angle); + end_angle_rad = deg2rad(start_angle + sweep); + + start_x = cos(start_angle_rad) * radius; + start_y = -sin(start_angle_rad) * radius; + end_x = cos(end_angle_rad) * radius; + end_y = -sin(end_angle_rad) * radius; + + // Check if start and endpoint are very near + if((end_x - start_x) * (end_x - start_x) + + (end_y - start_y) * (end_y - start_y) + < 2.0f) + { + if(sweep > 180) + circle = true; + else + // Nothing to draw + return; + } + + t_x = -radius; + t_y = 0; + err = 2 - 2 * radius; + + do + { + if(!circle) + { + t_sxy = start_x * t_y; + t_syx = start_y * t_x; + t_sxny = start_x * -t_y; + t_synx = start_y * -t_x; + t_exy = end_x * t_y; + t_eyx = end_y * t_x; + t_exny = end_x * -t_y; + t_eynx = end_y * -t_x; + + if(sweep > 180) + { + d1 = (t_sxy - t_synx < 0 || t_exy - t_eynx > 0); + d2 = (t_sxy - t_syx < 0 || t_exy - t_eyx > 0); + d3 = (t_sxny - t_syx < 0 || t_exny - t_eyx > 0); + d4 = (t_sxny - t_synx < 0 || t_exny - t_eynx > 0); + } + else + { + d1 = (t_sxy - t_synx < 0 && t_exy - t_eynx > 0); + d2 = (t_sxy - t_syx < 0 && t_exy - t_eyx > 0); + d3 = (t_sxny - t_syx < 0 && t_exny - t_eyx > 0); + d4 = (t_sxny - t_synx < 0 && t_exny - t_eynx > 0); + } + } + + if(d1) + ((ChildType*)(this)) + ->ChildType::DrawPixel(x - t_x, y + t_y, on); + if(d2) + ((ChildType*)(this)) + ->ChildType::DrawPixel(x + t_x, y + t_y, on); + if(d3) + ((ChildType*)(this)) + ->ChildType::DrawPixel(x + t_x, y - t_y, on); + if(d4) + ((ChildType*)(this)) + ->ChildType::DrawPixel(x - t_x, y - t_y, on); + + e2 = err; + if(e2 <= t_y) + { + t_y++; + err = err + (t_y * 2 + 1); + if(-t_x == t_y && e2 <= t_x) + { + e2 = 0; + } + } + if(e2 > t_x) + { + t_x++; + err = err + (t_x * 2 + 1); + } + } while(t_x <= 0); + } + + char WriteChar(char ch, FontDef font, bool on) override + { + uint32_t i, b, j; + + // Check if character is valid + if(ch < 32 || ch > 126) + return 0; + + // Check remaining space on current line + if(Width() < (currentX_ + font.FontWidth) + || Height() < (currentY_ + font.FontHeight)) + { + // Not enough space on current line + return 0; + } + + // Use the font to write + for(i = 0; i < font.FontHeight; i++) + { + b = font.data[(ch - 32) * font.FontHeight + i]; + for(j = 0; j < font.FontWidth; j++) + { + if((b << j) & 0x8000) + { + ((ChildType*)(this)) + ->ChildType::DrawPixel( + currentX_ + j, (currentY_ + i), on); + } + else + { + ((ChildType*)(this)) + ->ChildType::DrawPixel( + currentX_ + j, (currentY_ + i), !on); + } + } + } + + // The current space is now taken + SetCursor(currentX_ + font.FontWidth, currentY_); + + // Return written char for validation + return ch; + } + + char WriteString(const char* str, FontDef font, bool on) override + { + // Write until null-byte + while(*str) + { + if(((ChildType*)(this))->ChildType::WriteChar(*str, font, on) + != *str) + { + // Char could not be written + return *str; + } + + // Next char + str++; + } + + // Everything ok + return *str; + } + + Rectangle WriteStringAligned(const char* str, + const FontDef& font, + Rectangle boundingBox, + Alignment alignment, + bool on) override + { + const auto alignedRect + = GetTextRect(str, font).AlignedWithin(boundingBox, alignment); + SetCursor(alignedRect.GetX(), alignedRect.GetY()); + ((ChildType*)(this))->ChildType::WriteString(str, font, on); + return alignedRect; + } + + private: + uint32_t strlen(const char* string) + { + uint32_t result = 0; + while(*string++ != '\0') + result++; + return result; + } + + Rectangle GetTextRect(const char* text, const FontDef& font) + { + const auto numChars = strlen(text); + return {int16_t(numChars * font.FontWidth), font.FontHeight}; + } +}; + +} // namespace daisy diff --git a/src/hid/disp/oled_color_display.h b/src/hid/disp/oled_color_display.h new file mode 100644 index 000000000..e1031d2cb --- /dev/null +++ b/src/hid/disp/oled_color_display.h @@ -0,0 +1,80 @@ +#pragma once +#include "color_display.h" + +namespace daisy +{ +/** + * This class is for drawing to a monochrome OLED display. + * @ingroup device +*/ +template +class OledColorDisplay : public ColorGraphicsDisplayImpl> +{ + public: + OledColorDisplay() {} + virtual ~OledColorDisplay() {} + + struct Config + { + typename DisplayDriver::Config driver_config; + }; + + void Init(Config config) { driver_.Init(config.driver_config); } + + uint16_t Height() const override { return driver_.Height(); } + uint16_t Width() const override { return driver_.Width(); } + + /** + Fills the entire display with either on/off. + \param on Sets on or off. + */ + void Fill(bool on) override { driver_.Fill(on); } + + /** + Sets the pixel at the specified coordinate to be on/off. + \param x x Coordinate + \param y y coordinate + \param on on or off + */ + void DrawPixel(uint_fast8_t x, uint_fast8_t y, bool on) override + { + driver_.DrawPixel(x, y, on); + } + + /** + Set foreground color + \param red Red color + \param green Green color + \param blue Blue color + */ + void SetColorFG(uint8_t red, uint8_t green, uint8_t blue) + { + driver_.SetColorFG(red, green, blue); + } + + /** + Set background color + \param red Red color + \param green Green color + \param blue Blue color + */ + void SetColorBG(uint8_t red, uint8_t green, uint8_t blue) + { + driver_.SetColorBG(red, green, blue); + } + + /** + Writes the current display buffer to the OLED device using SPI or I2C depending on + how the object was initialized. + */ + void Update() override { driver_.Update(); } + + private: + DisplayDriver driver_; + + void Reset() { driver_.Reset(); }; + void SendCommand(uint8_t cmd) { driver_.SendCommand(cmd); }; + void SendData(uint8_t* buff, size_t size) { driver_.SendData(buff, size); }; +}; + +} // namespace daisy