148 lines
3.0 KiB
C++
148 lines
3.0 KiB
C++
|
#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
|