113 lines
2.8 KiB
C++
113 lines
2.8 KiB
C++
#pragma once
|
|
|
|
#include "../clock.hpp"
|
|
|
|
#include <stdint.h>
|
|
|
|
#include <avr/interrupt.h>
|
|
|
|
#include "../io/io.hpp"
|
|
#include "../util/type.hpp"
|
|
#include "../util/util.hpp"
|
|
|
|
namespace spi {
|
|
|
|
template <typename Cfg>
|
|
class Software {
|
|
static constexpr double calcClockDelay()
|
|
{
|
|
constexpr auto staticClockCycles = 10;
|
|
constexpr auto maxFrequency = F_CPU / staticClockCycles;
|
|
static_assert(Cfg::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 / Cfg::FREQ) - staticDelay) / 2;
|
|
return (delayUs > 0 ? delayUs : 0);
|
|
}
|
|
|
|
static_assert(Cfg::BITS >= 1 && Cfg::BITS <= 64, "Word size must be in range [1-64]");
|
|
// clang-format off
|
|
using word_t = util::conditional_t<Cfg::BITS <= 8, uint8_t,
|
|
util::conditional_t<Cfg::BITS <= 16, uint16_t,
|
|
util::conditional_t<Cfg::BITS <= 32, uint32_t,
|
|
uint64_t>>>;
|
|
// clang-format on
|
|
|
|
public:
|
|
static void init()
|
|
{
|
|
sm_sck = ((Cfg::MODE == Mode::MODE_0 || Cfg::MODE == Mode::MODE_1) ? false : true);
|
|
sm_ss = true;
|
|
|
|
sm_sck.dir(io::Dir::OUT);
|
|
sm_miso.dir(io::Dir::IN);
|
|
sm_miso.pullup(Cfg::PULLUP);
|
|
sm_mosi.dir(io::Dir::OUT);
|
|
sm_ss.dir(io::Dir::OUT);
|
|
}
|
|
|
|
static word_t transfer(const word_t data)
|
|
{
|
|
const auto oldInterruptState = disableInterrupts();
|
|
|
|
auto dataIn = word_t{};
|
|
|
|
util::for_constexpr(
|
|
[&](const auto idx) {
|
|
constexpr auto i = idx.value;
|
|
constexpr auto bitPos = (Cfg::BIT_ORDER == BitOrder::MSB_FIRST ? Cfg::BITS - i - 1 : i);
|
|
|
|
if constexpr (Cfg::MODE == Mode::MODE_0 || Cfg::MODE == Mode::MODE_2) {
|
|
sm_mosi = data >> bitPos & 1;
|
|
_delay_us(DELAY_US);
|
|
sm_sck.toggle();
|
|
|
|
const auto receivedBit = sm_miso.read();
|
|
dataIn |= word_t{receivedBit} << bitPos;
|
|
_delay_us(DELAY_US);
|
|
sm_sck.toggle();
|
|
} else {
|
|
sm_sck.toggle();
|
|
sm_mosi = data >> bitPos & 1;
|
|
_delay_us(DELAY_US);
|
|
|
|
sm_sck.toggle();
|
|
const auto receivedBit = sm_miso.read();
|
|
dataIn |= word_t{receivedBit} << bitPos;
|
|
_delay_us(DELAY_US);
|
|
}
|
|
},
|
|
util::make_index_sequence<Cfg::BITS>{});
|
|
|
|
enableInterrupts(oldInterruptState);
|
|
|
|
return dataIn;
|
|
}
|
|
|
|
static void select(const bool selectState)
|
|
{
|
|
sm_ss = !selectState;
|
|
}
|
|
|
|
private:
|
|
static io::Pin<Cfg::SCK_PIN> sm_sck;
|
|
static io::Pin<Cfg::MISO_PIN> sm_miso;
|
|
static io::Pin<Cfg::MOSI_PIN> sm_mosi;
|
|
static io::Pin<Cfg::SS_PIN> sm_ss;
|
|
|
|
static constexpr auto DELAY_US = calcClockDelay();
|
|
|
|
static inline uint8_t disableInterrupts()
|
|
{
|
|
const auto oldInterruptState = SREG;
|
|
cli();
|
|
return oldInterruptState;
|
|
}
|
|
|
|
static inline void enableInterrupts(const uint8_t oldInterruptState)
|
|
{
|
|
SREG = oldInterruptState;
|
|
}
|
|
};
|
|
|
|
} // namespace spi
|