Implement custom spi protocol

This commit is contained in:
BlackMark 2022-06-01 20:16:09 +02:00
parent 83a064ddc1
commit 113931f8ba
2 changed files with 192 additions and 51 deletions

View File

@ -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
View 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