diff --git a/eink.hpp b/eink.hpp index 111de6f..fcf7ffa 100644 --- a/eink.hpp +++ b/eink.hpp @@ -8,16 +8,22 @@ #include +#include "eink_spi.hpp" + #include "../clock.hpp" #include "../io/io.hpp" #include "../util/util.hpp" namespace eink { -template +template class Eink { using word_t = typename Spi::word_t; + 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, @@ -35,44 +41,11 @@ class Eink { SET_RAM_Y_ADDR = 0x4F, }; - static io::Pin m_rst; - static io::Pin m_dc; - static io::Pin m_bsy; - static constexpr auto THREE_WIRE_SPI = (DcPin == io::P::NONE ? true : false); - static_assert((THREE_WIRE_SPI && sizeof(word_t) > 1) || !THREE_WIRE_SPI, - "Three wire SPI requires SPI word size of at least 9 bits"); - - static constexpr auto BLOCK_SIZE = 5; - - enum class Color : std::uint8_t { - BLACK = 0b00, - WHITE = 0b01, - RED = 0b10, - ERROR = 0b11, - }; - - class ImageBlock { - public: - inline Color &operator[](const std::size_t idx) - { - return data[idx]; - } - - inline const Color &operator[](const std::size_t idx) const - { - return data[idx]; - } - - private: - Color data[BLOCK_SIZE]; - }; - - public: static void init() { m_rst.dir(io::Dir::OUT); - m_dc.dir(io::Dir::OUT); - m_bsy.dir(io::Dir::IN); + m_busy.pullup(false); + m_busy.dir(io::Dir::IN); Spi::init(); reset(); @@ -117,23 +90,29 @@ class Eink { static void sendCommand(const Cmd command) { - m_dc = false; - spiTransfer(static_cast(command)); + Spi::select(true); + Spi::write(static_cast(command), true); + Spi::select(false); } static void sendData(word_t data) { - if constexpr (THREE_WIRE_SPI) { - data |= 1 << 8; - } + Spi::select(true); + Spi::write(data, false); + Spi::select(false); + } - m_dc = true; - spiTransfer(data); + static word_t readData() + { + Spi::select(true); + const auto res = Spi::read(); + Spi::select(false); + return res; } static void waitUntilIdle() { - while (m_bsy) { + while (m_busy) { _delay_ms(100); } } @@ -201,19 +180,34 @@ class Eink { waitUntilIdle(); } + 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(); + + sendCommand(Cmd::DISPLAY_UPDATE_CONTROL_2); + sendData(0xF7); + sendCommand(Cmd::UPDATE_DISPLAY); + waitUntilIdle(); + } + static void sleep() { sendCommand(Cmd::DEEP_SLEEP_MODE); sendData(0x01); _delay_ms(100); } - - static void spiTransfer(const word_t data) - { - Spi::select(true); - Spi::transfer(data); - Spi::select(false); - } }; } // namespace eink diff --git a/eink_spi.hpp b/eink_spi.hpp new file mode 100644 index 0000000..b3e22ae --- /dev/null +++ b/eink_spi.hpp @@ -0,0 +1,147 @@ +#pragma once + +#include "../clock.hpp" + +#include + +#include + +#include + +#include "../io/io.hpp" +#include "../util/util.hpp" + +namespace eink { + +template +class Spi { + static constexpr double calcClockDelay() + { + // TODO: Verify static clock cycles + constexpr auto staticClockCycles = 10; + constexpr auto maxFrequency = F_CPU / staticClockCycles; + static_assert(Freq <= maxFrequency, "SPI frequency not achievable using selected clock speed"); + constexpr auto staticDelay = (1.0 * 1000 * 1000 / maxFrequency); + const auto delayUs = ((1.0 * 1000 * 1000 / Freq) - staticDelay) / 2; + return (delayUs > 0 ? delayUs : 0); + } + + static constexpr auto THREE_WIRE_SPI = (DcPin == io::P::NONE); + + public: + using word_t = std::conditional_t; + + static void init() + { + sm_scl = false; + sm_cs = true; + + sm_scl.dir(io::Dir::OUT); + sm_sda.dir(io::Dir::OUT); + sm_cs.dir(io::Dir::OUT); + sm_dc.dir(io::Dir::OUT); + } + + static void write(const word_t data, const bool command = true) + { + constexpr auto numBits = THREE_WIRE_SPI ? 9 : 8; + + const auto oldInterruptState = disableInterrupts(); + + sm_sda.dir(io::Dir::OUT); + + if constexpr (THREE_WIRE_SPI) { + if (command) { + data &= ~(1 << 8); + } else { + data |= 1 << 8; + } + } + + sm_dc = !command; + + util::for_constexpr( + [&](const auto idx) { + constexpr auto bitPos = numBits - idx.value - 1; + + sm_sda = data >> bitPos & 1; + + _delay_us(DELAY_US); + sm_scl.toggle(); + + _delay_us(DELAY_US); + sm_scl.toggle(); + }, + std::make_index_sequence{}); + + enableInterrupts(oldInterruptState); + } + + static word_t read() + { + constexpr auto numBits = 8; + + const auto oldInterruptState = disableInterrupts(); + + if constexpr (THREE_WIRE_SPI) { + sm_sda = true; + _delay_us(DELAY_US); + sm_scl.toggle(); + _delay_us(DELAY_US); + sm_scl.toggle(); + } + + sm_sda.pullup(false); + sm_sda.dir(io::Dir::IN); + + sm_dc = true; + + auto res = word_t{}; + + util::for_constexpr( + [&](const auto idx) { + constexpr auto bitPos = numBits - idx.value - 1; + + _delay_us(DELAY_US); + sm_scl.toggle(); + + const auto receivedBit = sm_sda.read(); + res |= word_t{receivedBit} << bitPos; + + _delay_us(DELAY_US); + sm_scl.toggle(); + }, + std::make_index_sequence{}); + + enableInterrupts(oldInterruptState); + + return res; + } + + static inline void select(const bool selectState) + { + sm_cs = !selectState; + } + + private: + static io::Pin sm_scl; + static io::Pin sm_sda; + static io::Pin sm_cs; + static io::Pin sm_dc; + + static constexpr auto DELAY_US = calcClockDelay(); + + static inline std::uint8_t disableInterrupts() + { + const auto oldInterruptState = SREG; + cli(); + return oldInterruptState; + } + + static inline void enableInterrupts(const std::uint8_t oldInterruptState) + { + SREG = oldInterruptState; + } +}; + +} // namespace eink