Implement custom spi protocol
This commit is contained in:
parent
83a064ddc1
commit
113931f8ba
96
eink.hpp
96
eink.hpp
@ -8,16 +8,22 @@
|
|||||||
|
|
||||||
#include <avr/pgmspace.h>
|
#include <avr/pgmspace.h>
|
||||||
|
|
||||||
|
#include "eink_spi.hpp"
|
||||||
|
|
||||||
#include "../clock.hpp"
|
#include "../clock.hpp"
|
||||||
#include "../io/io.hpp"
|
#include "../io/io.hpp"
|
||||||
#include "../util/util.hpp"
|
#include "../util/util.hpp"
|
||||||
|
|
||||||
namespace eink {
|
namespace eink {
|
||||||
|
|
||||||
template <std::uint16_t Width, std::uint16_t Height, typename Spi, io::P RstPin, io::P DcPin, io::P BusyPin>
|
template <std::uint16_t Width, std::uint16_t Height, typename Spi, io::P RstPin, io::P BusyPin>
|
||||||
class Eink {
|
class Eink {
|
||||||
using word_t = typename Spi::word_t;
|
using word_t = typename Spi::word_t;
|
||||||
|
|
||||||
|
static io::Pin<RstPin> m_rst;
|
||||||
|
static io::Pin<BusyPin> m_busy;
|
||||||
|
|
||||||
|
public:
|
||||||
enum class Cmd : std::uint8_t {
|
enum class Cmd : std::uint8_t {
|
||||||
DRIVER_OUTPUT_CONTROL = 0x01,
|
DRIVER_OUTPUT_CONTROL = 0x01,
|
||||||
DEEP_SLEEP_MODE = 0x10,
|
DEEP_SLEEP_MODE = 0x10,
|
||||||
@ -35,44 +41,11 @@ class Eink {
|
|||||||
SET_RAM_Y_ADDR = 0x4F,
|
SET_RAM_Y_ADDR = 0x4F,
|
||||||
};
|
};
|
||||||
|
|
||||||
static io::Pin<RstPin> m_rst;
|
|
||||||
static io::Pin<DcPin> m_dc;
|
|
||||||
static io::Pin<BusyPin> 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()
|
static void init()
|
||||||
{
|
{
|
||||||
m_rst.dir(io::Dir::OUT);
|
m_rst.dir(io::Dir::OUT);
|
||||||
m_dc.dir(io::Dir::OUT);
|
m_busy.pullup(false);
|
||||||
m_bsy.dir(io::Dir::IN);
|
m_busy.dir(io::Dir::IN);
|
||||||
Spi::init();
|
Spi::init();
|
||||||
|
|
||||||
reset();
|
reset();
|
||||||
@ -117,23 +90,29 @@ class Eink {
|
|||||||
|
|
||||||
static void sendCommand(const Cmd command)
|
static void sendCommand(const Cmd command)
|
||||||
{
|
{
|
||||||
m_dc = false;
|
Spi::select(true);
|
||||||
spiTransfer(static_cast<word_t>(command));
|
Spi::write(static_cast<word_t>(command), true);
|
||||||
|
Spi::select(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void sendData(word_t data)
|
static void sendData(word_t data)
|
||||||
{
|
{
|
||||||
if constexpr (THREE_WIRE_SPI) {
|
Spi::select(true);
|
||||||
data |= 1 << 8;
|
Spi::write(data, false);
|
||||||
}
|
Spi::select(false);
|
||||||
|
}
|
||||||
|
|
||||||
m_dc = true;
|
static word_t readData()
|
||||||
spiTransfer(data);
|
{
|
||||||
|
Spi::select(true);
|
||||||
|
const auto res = Spi::read();
|
||||||
|
Spi::select(false);
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void waitUntilIdle()
|
static void waitUntilIdle()
|
||||||
{
|
{
|
||||||
while (m_bsy) {
|
while (m_busy) {
|
||||||
_delay_ms(100);
|
_delay_ms(100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -201,19 +180,34 @@ class Eink {
|
|||||||
waitUntilIdle();
|
waitUntilIdle();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void autoPatternFill()
|
||||||
|
{
|
||||||
|
constexpr auto RED_PATTERN_FILL_CMD = static_cast<Cmd>(0x46);
|
||||||
|
constexpr auto BLACK_PATTERN_FILL_CMD = static_cast<Cmd>(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()
|
static void sleep()
|
||||||
{
|
{
|
||||||
sendCommand(Cmd::DEEP_SLEEP_MODE);
|
sendCommand(Cmd::DEEP_SLEEP_MODE);
|
||||||
sendData(0x01);
|
sendData(0x01);
|
||||||
_delay_ms(100);
|
_delay_ms(100);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void spiTransfer(const word_t data)
|
|
||||||
{
|
|
||||||
Spi::select(true);
|
|
||||||
Spi::transfer(data);
|
|
||||||
Spi::select(false);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace eink
|
} // namespace eink
|
||||||
|
147
eink_spi.hpp
Normal file
147
eink_spi.hpp
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../clock.hpp"
|
||||||
|
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
#include <avr/interrupt.h>
|
||||||
|
|
||||||
|
#include "../io/io.hpp"
|
||||||
|
#include "../util/util.hpp"
|
||||||
|
|
||||||
|
namespace eink {
|
||||||
|
|
||||||
|
template <io::P SclPin, io::P SdaPin, io::P CsPin, io::P DcPin, std::uint32_t Freq = 100'000>
|
||||||
|
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<THREE_WIRE_SPI, std::uint16_t, std::uint8_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<numBits>{});
|
||||||
|
|
||||||
|
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<numBits>{});
|
||||||
|
|
||||||
|
enableInterrupts(oldInterruptState);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void select(const bool selectState)
|
||||||
|
{
|
||||||
|
sm_cs = !selectState;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static io::Pin<SclPin> sm_scl;
|
||||||
|
static io::Pin<SdaPin> sm_sda;
|
||||||
|
static io::Pin<CsPin> sm_cs;
|
||||||
|
static io::Pin<DcPin> 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
|
Loading…
Reference in New Issue
Block a user