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 "eink_spi.hpp"
|
||||
|
||||
#include "../clock.hpp"
|
||||
#include "../io/io.hpp"
|
||||
#include "../util/util.hpp"
|
||||
|
||||
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 {
|
||||
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 {
|
||||
DRIVER_OUTPUT_CONTROL = 0x01,
|
||||
DEEP_SLEEP_MODE = 0x10,
|
||||
@ -35,44 +41,11 @@ class Eink {
|
||||
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()
|
||||
{
|
||||
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<word_t>(command));
|
||||
Spi::select(true);
|
||||
Spi::write(static_cast<word_t>(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<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()
|
||||
{
|
||||
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
|
||||
|
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