diff --git a/Peripherals/Inc/peripherals_ssd1306_display.hpp b/Peripherals/Inc/peripherals_ssd1306_display.hpp new file mode 100644 index 0000000..1fdcc83 --- /dev/null +++ b/Peripherals/Inc/peripherals_ssd1306_display.hpp @@ -0,0 +1,255 @@ +/* + * peripherals_ssd1036.hpp + * + * Created on: May 14, 2021 + * Author: erki + */ + +#ifndef SKULLC_PERIPHERALS_SSD1036_DISPLAY_HPP_ +#define SKULLC_PERIPHERALS_SSD1036_DISPLAY_HPP_ + +#include +#include + +namespace Peripherals +{ + +struct SSD1306WriteAdapter +{ + template + struct Spi + { + using gpio = G; + using registers_handle = S; + + gpio dc; + gpio cs; + registers_handle registers; + + Spi() = delete; + Spi(const gpio& dc, const gpio& cs, const registers_handle& registers) + : dc(dc), cs(cs), registers(registers) + {} + + /** + * Writes a command byte. + * + * When writing data into the command registers, the DC/RS pin must be set LOW. + * + * @param data The command byte to write. + */ + void writeCommand(std::uint8_t data) + { + dc.Set(false); + + cs.Set(false); + registers.Transmit(&data, 1); + cs.Set(true); + } + + void writeCommand(std::uint8_t command, std::uint8_t data) + { + dc.Set(false); + + cs.Set(false); + registers.Transmit(&command, 1); + registers.Transmit(&data, 1); + cs.Set(true); + } + + void writeData(std::uint8_t* data, const std::uint32_t len) + { + dc.Set(true); + + cs.Set(false); + registers.Transmit(data, len); + cs.Set(true); + } + }; + + template + struct I2c + { + // TODO: implement + }; +}; + +/** + * A generalized version of the SSD1306 display. Works with both I2C and SPI interfaces. + * + * The data is held in a compressed manner, where each bit of a byte represents a single pixel. + * These bits are arrenged into a byte per column. + * + * Datasheet: https://cdn-shop.adafruit.com/datasheets/SSD1306.pdf + */ +template +class SSD1306Display +{ +public: + using gpio = G; + using write_adapter = A; + using hal = HAL; + constexpr static std::size_t width = W; + constexpr static std::size_t height = H; + constexpr static std::size_t rows = height / 8; + + SSD1306Display() = delete; + SSD1306Display(const gpio& reset, const write_adapter& registers) + : reset_(reset), registers_(registers) + {} + + void setup() + { + hal::Delay(1); + reset_.Set(false); + hal::Delay(5); + reset_.Set(true); + hal::Delay(5); + + // reference impl: https://gitlab.com/TTYRobotiklubi/micromouse/mousetrap2/-/blob/master/Src/Peripherals/lcd.c + //Set the display to sleep mode for the rest of the init. + registers_.writeCommand(Regs_::DISPLAY_OFF_YES_SLEEP); + + //Set the clock speed, nominal ~105FPS + //Low nibble is divide ratio + //High level is oscillator frequency. Should be about 177 Hz. + registers_.writeCommand(Regs_::CLOCK_DIVIDE_PREFIX, 0x80); + + //Set the multiplex ratio to 1/32 + //Default is 0x3F (1/64 Duty), we need 0x1F (1/32 Duty) + registers_.writeCommand(Regs_::MULTIPLEX_RATIO_PREFIX, 0x1F); + + //Set the display offset to 0 (default) + registers_.writeCommand(Regs_::DISPLAY_OFFSET_PREFIX, 0x00); + + //Set the display RAM display start line to 0 (default) + //Bits 0-5 can be set to 0-63 with a bitwise or + registers_.writeCommand(Regs_::DISPLAY_START_LINE); + + //Enable DC/DC converter, 7.5v + registers_.writeCommand(Regs_::DCDC_CONFIG_PRESET, Regs_::DCDC_CONFIG_7p5v); + + //Map the columns correctly for our OLED glass layout. + registers_.writeCommand(Regs_::SEG0_IS_COL_0); + + //Set COM output scan correctly for our OLED glass layout. + registers_.writeCommand(Regs_::SCAN_DIR_UP); + + //Set COM pins correctly for our OLED glass layout + registers_.writeCommand(Regs_::COM_CONFIG_PREFIX, Regs_::COM_CONFIG_SEQUENTIAL_LEFT); + + // Contrast. + registers_.writeCommand(Regs_::CONTRAST_PREFIX, 0xBF); + + // Set precharge (low nibble) / discharge (high nibble) timing. + // precharge = 1 clock + // discharge = 15 clocks + registers_.writeCommand(Regs_::PRECHARGE_PERIOD_PREFIX, 0xF1); + + // Set VCOM deselect level. + registers_.writeCommand(Regs_::VCOMH_DESELECT_PREFIX, Regs_::VCOMH_DESELECT_0p83xVCC); + + // Set display to normal. + registers_.writeCommand(Regs_::ENTIRE_DISPLAY_NORMAL); + + // Uninvert the display. + registers_.writeCommand(Regs_::INVERSION_NORMAL); + + // Unset sleep mode. + registers_.writeCommand(Regs_::DISPLAY_ON_NO_SLEEP); + } + + void blank(const bool black) + { + screen_buffer_.fill(black ? 0xFF : 0x00); + } + + void drawBuffer() + { + for (std::uint32_t row = 0; row < rows; row++) + { + registers_.writeCommand(0xB0 + row); + registers_.writeCommand(0x00); + registers_.writeCommand(0x10); + registers_.writeData(&screen_buffer_[width * row], width); + } + } + + template + void copyPixelBuffer(const PB& buffer) + { + static_assert(PB::width == width, "Pixel buffer's width doesn't match."); + static_assert(PB::height == height, "Pixel buffer's height doesn't match."); + static_assert(std::is_same::value, "Pixel buffer's value type does not match."); + + for (std::uint32_t row = 0; row < rows; row++) + { + for (std::uint32_t col = 0; col < width; col++) + { + std::uint8_t& byte = screen_buffer_[width * row + col]; + byte = 0; + + for (std::uint32_t bit = 0; bit < 8; bit++) + { + if (buffer.at(col, 8 * row + bit) != 0) + { + byte |= (1 << bit); + } + } + } + } + } + +private: + gpio reset_; + write_adapter registers_; + std::array screen_buffer_; + + struct Regs_ + { + static constexpr std::uint8_t DCDC_CONFIG_PRESET = 0x8D; + static constexpr std::uint8_t DCDC_CONFIG_7p5v = 0x14; + static constexpr std::uint8_t DCDC_CONFIG_6p0v = 0x15; + static constexpr std::uint8_t DCDC_CONFIG_8p5v = 0x94; + static constexpr std::uint8_t DCDC_CONFIG_9p0v = 0x95; + + static constexpr std::uint8_t DISPLAY_OFF_YES_SLEEP = 0xAE; + static constexpr std::uint8_t DISPLAY_ON_NO_SLEEP = 0xAF; + + static constexpr std::uint8_t CLOCK_DIVIDE_PREFIX = 0xD5; + static constexpr std::uint8_t MULTIPLEX_RATIO_PREFIX = 0xA8; + static constexpr std::uint8_t DISPLAY_OFFSET_PREFIX = 0xD3; + + static constexpr std::uint8_t DISPLAY_START_LINE = 0x40; + static constexpr std::uint8_t SEG0_IS_COL_0 = 0xA0; + static constexpr std::uint8_t SEG0_IS_COL_127 = 0xA1; + static constexpr std::uint8_t SCAN_DIR_UP = 0xC0; + static constexpr std::uint8_t SCAN_DIR_DOWN = 0xC8; + + static constexpr std::uint8_t COM_CONFIG_PREFIX = 0xDA; + static constexpr std::uint8_t COM_CONFIG_SEQUENTIAL_LEFT = 0x02; + static constexpr std::uint8_t COM_CONFIG_ALTERNATE_LEFT = 0x12; + static constexpr std::uint8_t COM_CONFIG_SEQUENTIAL_RIGHT = 0x22; + static constexpr std::uint8_t COM_CONFIG_ALTERNATE_RIGHT = 0x32; + + static constexpr std::uint8_t CONTRAST_PREFIX = 0x81; + static constexpr std::uint8_t PRECHARGE_PERIOD_PREFIX = 0xD9; + + static constexpr std::uint8_t VCOMH_DESELECT_PREFIX = 0xDB; + static constexpr std::uint8_t VCOMH_DESELECT_0p65xVCC = 0x00; + static constexpr std::uint8_t VCOMH_DESELECT_0p71xVCC = 0x10; + static constexpr std::uint8_t VCOMH_DESELECT_0p77xVCC = 0x20; + static constexpr std::uint8_t VCOMH_DESELECT_0p83xVCC = 0x30; + + static constexpr std::uint8_t ENTIRE_DISPLAY_FORCE_ON = 0xA5; + static constexpr std::uint8_t ENTIRE_DISPLAY_NORMAL = 0xA4; + + static constexpr std::uint8_t INVERSION_NORMAL = 0xA6; + static constexpr std::uint8_t INVERSION_INVERTED = 0xA7; + }; +}; + +}// namespace Peripherals + + +#endif /* SKULLC_PERIPHERALS_SSD1036_DISPLAY_HPP_ */