#pragma once #include #include #include #include #include #include #include "eink_spi.hpp" #include "font.hpp" #include "otp.hpp" #include "../clock.hpp" #include "../flash/flash.hpp" #include "../io/io.hpp" #include "../util/util.hpp" namespace eink { template class Eink { using word_t = typename Spi::word_t; struct original_lut_tag { }; static io::Pin m_rst; static io::Pin m_busy; public: enum class Cmd : std::uint8_t { DRIVER_OUTPUT_CONTROL = 0x01, DEEP_SLEEP_MODE = 0x10, DATA_ENTRY_MODE = 0x11, SW_RESET = 0x12, READ_TEMPERATURE_SENSOR = 0x18, UPDATE_DISPLAY = 0x20, DISPLAY_UPDATE_CONTROL_2 = 0x22, WRITE_RAM_BLACK = 0x24, WRITE_RAM_RED = 0x26, READ_RAM = 0x27, LOAD_OTP_TO_RAM = 0x31, WRITE_LUT = 0x32, BORDER_WAVEFORM_CONTROL = 0x3C, READ_RAM_CHANNEL = 0x41, SET_RAM_X_ADDR_POSITIONS = 0x44, SET_RAM_Y_ADDR_POSITIONS = 0x45, SET_RAM_X_ADDR = 0x4E, SET_RAM_Y_ADDR = 0x4F, }; enum class Color : std::uint8_t { BLACK = 0x00, RED = 0x01, WHITE = 0x02, }; enum class RamDirection : std::uint8_t { DECREMENT = 0, INCREMENT = 1, }; enum class FastestMovingIndex : std::uint8_t { X = 0, Y = 1, }; static void init() { m_rst.dir(io::Dir::OUT); m_busy.pullup(false); m_busy.dir(io::Dir::IN); Spi::init(); reset(); softReset(); sendCommand(Cmd::DRIVER_OUTPUT_CONTROL); sendData(0xC7); sendData(0x00); sendData(0x01); setDataEntryMode(RamDirection::DECREMENT, RamDirection::INCREMENT, FastestMovingIndex::X); setRamRange(); setRamXPos(); setRamYPos(); sendCommand(Cmd::BORDER_WAVEFORM_CONTROL); sendData(0x05); sendCommand(Cmd::READ_TEMPERATURE_SENSOR); sendData(0x80); waitUntilIdle(); } static void sendCommand(const Cmd command) { Spi::select(true); Spi::write(static_cast(command), true); Spi::select(false); } static void sendData(word_t data) { Spi::select(true); Spi::write(data, false); Spi::select(false); } static word_t readData() { Spi::select(true); const auto res = Spi::read(); Spi::select(false); return res; } static void waitUntilIdle() { while (m_busy) { _delay_ms(100); } } static void reset() { m_rst = true; _delay_ms(200); m_rst = false; _delay_ms(10); m_rst = true; _delay_ms(200); waitUntilIdle(); } static void softReset() { sendCommand(Cmd::SW_RESET); waitUntilIdle(); } static void update() { update(original_lut_tag{}); } template static void update(const Lut &lut) { constexpr auto USE_ORIGINAL_LUT = std::is_same_v; if constexpr (!USE_ORIGINAL_LUT) { writeLut(lut); } sendCommand(Cmd::DISPLAY_UPDATE_CONTROL_2); sendData(USE_ORIGINAL_LUT ? 0xF7 : 0xC7); sendCommand(Cmd::UPDATE_DISPLAY); waitUntilIdle(); } template static void draw(const RleImage &rleImage) { constexpr auto sendImageChannel = [](const auto command, const auto &image) { using image_t = std::remove_cvref_t; sendCommand(command); for (auto j = std::size_t{0}; j < std::tuple_size_v; ++j) { const auto [count, data] = flash::loadLike(image[j]); for (auto i = std::uint16_t{0}; i < count; ++i) { if (command == Cmd::WRITE_RAM_BLACK) { sendData(data); } else { sendData(~data); } } } }; sendImageChannel(Cmd::WRITE_RAM_BLACK, std::get<0>(rleImage.value)); sendImageChannel(Cmd::WRITE_RAM_RED, std::get<1>(rleImage.value)); } static void clear(const Color color = Color::WHITE) { constexpr auto getFillData = [](const auto &color) -> std::pair { switch (color) { case Color::BLACK: return {0x00, 0x00}; case Color::RED: return {0xFF, 0xFF}; case Color::WHITE: return {0xFF, 0x00}; } return {0xFF, 0x00}; }; const auto fillData = getFillData(color); sendCommand(Cmd::WRITE_RAM_BLACK); for (auto i = std::uint16_t{0}; i < Width * Height / 8; i++) { sendData(fillData.first); } sendCommand(Cmd::WRITE_RAM_RED); for (auto i = std::uint16_t{0}; i < Width * Height / 8; i++) { sendData(fillData.second); } } static void drawText(const std::pair &pos, const char *text, const Color textColor = Color::BLACK, const Color backgroundColor = Color::WHITE, const std::uint8_t scaling = 1) { const auto textLength = std::strlen(text); constexpr auto getXScreenCoordinates = [](const std::uint8_t pos) { return (Width - pos - 1) / 8; }; constexpr auto flipEndianness = [](const std::uint8_t value) { auto flippedVal = std::uint8_t{0}; for (auto i = 0; i < 8; ++i) { const auto oldBit = (value >> i) & 1; flippedVal |= oldBit << (8 - i - 1); } return flippedVal; }; const auto adjustColor = [&](const auto &data, const auto command) { auto backgroundData = data; auto outData = data & 0x00; if (command == Cmd::WRITE_RAM_BLACK) { if (backgroundColor == Color::BLACK) { backgroundData = 0x00; } else if (backgroundColor == Color::RED || backgroundColor == Color::WHITE) { backgroundData = 0xFF; } if (textColor == Color::BLACK) { outData = backgroundData & ~data; } else if (textColor == Color::RED || textColor == Color::WHITE) { outData = backgroundData | data; } } else { if (backgroundColor == Color::BLACK || backgroundColor == Color::WHITE) { backgroundData = 0x00; } else if (backgroundColor == Color::RED) { backgroundData = 0xFF; } if (textColor == Color::BLACK || textColor == Color::WHITE) { outData = backgroundData & ~data; } else if (textColor == Color::RED) { outData = backgroundData | data; } } return outData; }; const auto sendChannel = [&](const auto command) { for (auto i = std::size_t{0}; i < textLength; ++i) { const auto posX = pos.first + i * 8 * scaling; const auto screenPosX = getXScreenCoordinates(posX); setRamRange({screenPosX, screenPosX - (scaling - 1)}, {pos.second, pos.second + 8 * scaling - 1}); setRamXPos(screenPosX); setRamYPos(pos.second); sendCommand(command); for (auto j = std::uint8_t{0}; j < 8; ++j) { const auto fontByte = flash::loadLike(FONT_8X8.value[text[i]][j]); static_assert(std::is_same_v, std::uint8_t>); const auto dataByte = adjustColor(flipEndianness(fontByte), command); auto scaledByte = std::uint8_t{0}; auto bitCounter = 0; for (auto sy = std::uint8_t{0}; sy < scaling; ++sy) { for (auto b = std::uint8_t{0}; b < 8; ++b) { const auto currentBit = dataByte >> (8 - b - 1) & 1; for (auto sx = std::uint8_t{0}; sx < scaling; ++sx) { scaledByte |= currentBit << (8 - bitCounter - 1); if (++bitCounter == 8) { sendData(scaledByte); scaledByte = 0; bitCounter = 0; } } } } } } }; sendChannel(Cmd::WRITE_RAM_BLACK); sendChannel(Cmd::WRITE_RAM_RED); setRamRange(); setRamXPos(); setRamYPos(); } template static void writeLut(const Lut &lut) { static_assert(sizeof(lut) == sizeof(Waveform), "Invalid LUT size"); sendCommand(Cmd::WRITE_LUT); for (auto i = std::size_t{0}; i < sizeof(lut); ++i) { const auto lutByte = lut[i]; static_assert(std::is_same_v>, "Invalid LUT value type"); sendData(static_cast(lutByte)); } } static void autoPatternFill() { constexpr auto RED_PATTERN_FILL_CMD = static_cast(0x46); constexpr auto BLACK_PATTERN_FILL_CMD = static_cast(0x47); constexpr auto RED_PATTERN = 0b0'001'0'001; constexpr auto BLACK_PATTERN = 0b1'000'0'000; sendCommand(RED_PATTERN_FILL_CMD); sendData(RED_PATTERN); waitUntilIdle(); sendCommand(BLACK_PATTERN_FILL_CMD); sendData(BLACK_PATTERN); waitUntilIdle(); } static void setDataEntryMode(const RamDirection &xDir = RamDirection::INCREMENT, const RamDirection &yDir = RamDirection::INCREMENT, const FastestMovingIndex &fastestMovingIndex = FastestMovingIndex::X) { auto setting = static_cast(xDir) << 0; setting |= static_cast(yDir) << 1; setting |= static_cast(fastestMovingIndex) << 2; sendCommand(Cmd::DATA_ENTRY_MODE); sendData(setting); } static void setRamRange(const std::pair &xrange = {Width / 8 - 1, 0}, const std::pair &yrange = {0, Height - 1}) { sendCommand(Cmd::SET_RAM_X_ADDR_POSITIONS); sendData(xrange.first & 0b00111111); sendData(xrange.second & 0b00111111); sendCommand(Cmd::SET_RAM_Y_ADDR_POSITIONS); sendData(yrange.first & 0xFF); sendData((yrange.first >> 8) & 0b1); sendData(yrange.second & 0xFF); sendData((yrange.second >> 8) & 0b1); } static void setRamXPos(const std::uint8_t pos = Width / 8 - 1) { sendCommand(Cmd::SET_RAM_X_ADDR); sendData(pos & 0b00111111); } static void setRamYPos(const std::uint16_t pos = 0) { sendCommand(Cmd::SET_RAM_Y_ADDR); sendData(pos & 0xFF); sendData((pos >> 8) & 0b1); } template static void dumpOTP(PrintFn &&printFn) { constexpr auto byteWidth = Width / 8; constexpr auto ramHeight = Height + 46; constexpr auto xRamRange = std::pair{0, byteWidth - 1}; constexpr auto yRamRange = std::pair{0, ramHeight - 1}; setDataEntryMode(RamDirection::INCREMENT, RamDirection::INCREMENT, FastestMovingIndex::X); setRamRange(xRamRange, yRamRange); setRamXPos(0); setRamYPos(0); sendCommand(Cmd::WRITE_RAM_BLACK); for (auto i = std::uint16_t{0}; i < byteWidth * ramHeight; i++) { sendData(0xFF); } sendCommand(Cmd::WRITE_RAM_RED); for (auto i = std::uint16_t{0}; i < byteWidth * ramHeight; i++) { sendData(0x00); } ////////////////////////////////////////////////////////////////////////// sendCommand(Cmd::DISPLAY_UPDATE_CONTROL_2); sendData(0x80); sendCommand(Cmd::UPDATE_DISPLAY); waitUntilIdle(); sendCommand(Cmd::LOAD_OTP_TO_RAM); waitUntilIdle(); setRamXPos(0); setRamYPos(0); sendCommand(Cmd::READ_RAM_CHANNEL); sendData(static_cast(Color::BLACK)); sendCommand(Cmd::READ_RAM); readData(); // First byte must be discarded for (auto i = std::size_t{0}; i < sizeof(OTP); ++i) { printFn(readData()); } init(); } static void sleep() { sendCommand(Cmd::DEEP_SLEEP_MODE); sendData(0x01); _delay_ms(100); } }; } // namespace eink