Compare commits
10 Commits
Author | SHA1 | Date | |
---|---|---|---|
5e9dac872a | |||
7fe32b9717 | |||
5d38b97254 | |||
bcae00c0f3 | |||
05c799c466 | |||
c1eadf4cb1 | |||
045efedd45 | |||
f7f02b76a8 | |||
2f54f9217f | |||
c741dc98a1 |
224
adc.hpp
Normal file
224
adc.hpp
Normal file
@ -0,0 +1,224 @@
|
||||
#ifndef ADC_HPP
|
||||
#define ADC_HPP
|
||||
|
||||
#include "config.hpp"
|
||||
#include "hardware.hpp"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "../io/io.hpp"
|
||||
|
||||
namespace adc {
|
||||
|
||||
namespace detail {
|
||||
|
||||
extern void (*fnAdcIntHandler)(const uint16_t &);
|
||||
|
||||
using reg_ptr_t = volatile uint8_t *;
|
||||
|
||||
template <uintptr_t Address>
|
||||
static inline reg_ptr_t getRegPtr()
|
||||
{
|
||||
return reinterpret_cast<reg_ptr_t>(Address);
|
||||
}
|
||||
|
||||
template <typename Cfg>
|
||||
class AdcImpl {
|
||||
public:
|
||||
static uint16_t read()
|
||||
{
|
||||
*getRegPtr<Registers::CTRL_STAT_A_ADDR>() |= 1 << ControlFlagsA::START_CONV;
|
||||
while (*getRegPtr<Registers::CTRL_STAT_A_ADDR>() & (1 << ControlFlagsA::START_CONV))
|
||||
;
|
||||
|
||||
uint16_t adcSample = *getRegPtr<Registers::DATA_L_ADDR>();
|
||||
adcSample |= *getRegPtr<Registers::DATA_H_ADDR>() << 8;
|
||||
return adcSample;
|
||||
}
|
||||
|
||||
protected:
|
||||
static void init(uint8_t muxVal)
|
||||
{
|
||||
*getRegPtr<Registers::MUX_SEL_ADDR>() = muxVal | calcRef();
|
||||
|
||||
auto ctrlStatA = calcCtrlStatA(detail::fnAdcIntHandler != nullptr);
|
||||
*getRegPtr<Registers::CTRL_STAT_A_ADDR>() = ctrlStatA;
|
||||
|
||||
constexpr auto ctrlStatB = calcCtrlStatB();
|
||||
*getRegPtr<Registers::CTRL_STAT_B_ADDR>() = ctrlStatB;
|
||||
|
||||
if constexpr (Cfg::MODE == Mode::FREE_RUNNING) {
|
||||
*getRegPtr<Registers::CTRL_STAT_A_ADDR>() |= 1 << ControlFlagsA::START_CONV;
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr auto calcRef()
|
||||
{
|
||||
uint8_t muxVal = 0;
|
||||
|
||||
if constexpr (Cfg::VREF == VoltageRef::AVCC) {
|
||||
muxVal |= 1 << ControlFlagsMUX::REF_SEL_0;
|
||||
} else if constexpr (Cfg::VREF == VoltageRef::INTERNAL) {
|
||||
muxVal |= (1 << ControlFlagsMUX::REF_SEL_0) | (1 << ControlFlagsMUX::REF_SEL_1);
|
||||
}
|
||||
|
||||
return muxVal;
|
||||
}
|
||||
|
||||
static constexpr uint8_t calcPrescaler()
|
||||
{
|
||||
constexpr auto validPrescaler = Cfg::PRESCALER == 2 || Cfg::PRESCALER == 4 || Cfg::PRESCALER == 8 ||
|
||||
Cfg::PRESCALER == 16 || Cfg::PRESCALER == 32 || Cfg::PRESCALER == 64 ||
|
||||
Cfg::PRESCALER == 128;
|
||||
static_assert(validPrescaler, "Invalid prescaler");
|
||||
|
||||
// clang-format off
|
||||
switch (Cfg::PRESCALER) {
|
||||
case 2: return 1;
|
||||
case 4: return 2;
|
||||
case 8: return 3;
|
||||
case 16: return 4;
|
||||
case 32: return 5;
|
||||
case 64: return 6;
|
||||
case 128: return 7;
|
||||
}
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
static auto calcCtrlStatA(bool interruptEnable)
|
||||
{
|
||||
uint8_t ctrlStatA = 1 << ControlFlagsA::ENABLE;
|
||||
|
||||
if constexpr (Cfg::MODE == Mode::AUTO || Cfg::MODE == Mode::FREE_RUNNING) {
|
||||
ctrlStatA |= 1 << ControlFlagsA::AUTO_TRIGGER;
|
||||
}
|
||||
if (interruptEnable) {
|
||||
ctrlStatA |= 1 << ControlFlagsA::CONV_COMPLETE_INT_ENABLE;
|
||||
}
|
||||
|
||||
return ctrlStatA | calcPrescaler();
|
||||
}
|
||||
|
||||
static constexpr uint8_t calcCtrlStatB()
|
||||
{
|
||||
if constexpr (Cfg::MODE == Mode::AUTO) {
|
||||
// clang-format off
|
||||
switch (Cfg::TRIGGER_SRC) {
|
||||
case TriggerSource::FREE_RUNNING: return 0;
|
||||
case TriggerSource::ANALOG_COMP: return 1;
|
||||
case TriggerSource::EXTERNAL_INT_0: return 2;
|
||||
case TriggerSource::TIMER0_COMP_A: return 3;
|
||||
case TriggerSource::TIMER0_OVERFLOW: return 4;
|
||||
case TriggerSource::TIMER1_COMP_B: return 5;
|
||||
case TriggerSource::TIMER1_OVERFLOW: return 6;
|
||||
case TriggerSource::TIMER1_CAPTURE: return 7;
|
||||
}
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <typename Cfg, typename Input, Input src>
|
||||
class Adc {
|
||||
public:
|
||||
static_assert(sizeof(Input) == -1, "Invalid input source selected");
|
||||
};
|
||||
|
||||
template <typename Cfg, io::P pin>
|
||||
class Adc<Cfg, io::P, pin> : public detail::AdcImpl<Cfg> {
|
||||
using callback_t = void (*)(const uint16_t &);
|
||||
|
||||
public:
|
||||
static_assert(detail::supports_adc_v<pin>, "Pin does not support ADC");
|
||||
|
||||
static void init(callback_t callback)
|
||||
{
|
||||
detail::fnAdcIntHandler = callback;
|
||||
init();
|
||||
}
|
||||
|
||||
static void init()
|
||||
{
|
||||
constexpr auto muxVal = calcChannel();
|
||||
detail::AdcImpl<Cfg>::init(muxVal);
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr auto calcChannel()
|
||||
{
|
||||
return static_cast<uint8_t>(pin) - static_cast<uint8_t>(io::P::C0);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Cfg, InputSource src>
|
||||
class Adc<Cfg, InputSource, src> : public detail::AdcImpl<Cfg> {
|
||||
using callback_t = void (*)(const uint16_t &);
|
||||
|
||||
public:
|
||||
static void init(callback_t callback)
|
||||
{
|
||||
detail::fnAdcIntHandler = callback;
|
||||
init();
|
||||
}
|
||||
|
||||
static void init()
|
||||
{
|
||||
constexpr auto muxVal = calcChannel();
|
||||
detail::AdcImpl<Cfg>::init(muxVal);
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr auto calcChannel()
|
||||
{
|
||||
using mx = detail::ControlFlagsMUX;
|
||||
// clang-format off
|
||||
switch (src) {
|
||||
case InputSource::TEMP: return 1 << mx::CHANNEL_SEL_3;
|
||||
case InputSource::VBG: return (1 << mx::CHANNEL_SEL_3) | (1 << mx::CHANNEL_SEL_2) | (1 << mx::CHANNEL_SEL_1);
|
||||
case InputSource::GND: return (1 << mx::CHANNEL_SEL_3) | (1 << mx::CHANNEL_SEL_2) | (1 << mx::CHANNEL_SEL_1) | (1 << mx::CHANNEL_SEL_0);
|
||||
}
|
||||
// clang-format on
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace adc
|
||||
|
||||
#endif
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifdef ADC_INT_VECTOR
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <avr/interrupt.h>
|
||||
|
||||
namespace adc {
|
||||
namespace detail {
|
||||
|
||||
#if defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega328P__)
|
||||
|
||||
void (*fnAdcIntHandler)(const uint16_t &) = nullptr;
|
||||
|
||||
ISR(ADC_vect)
|
||||
{
|
||||
if (fnAdcIntHandler) {
|
||||
const auto adcSample = *getRegPtr<Registers::DATA_L_ADDR>() | (*getRegPtr<Registers::DATA_H_ADDR>() << 8);
|
||||
fnAdcIntHandler(adcSample);
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
#error "This chip is not supported"
|
||||
#endif
|
||||
|
||||
} // namespace detail
|
||||
} // namespace adc
|
||||
|
||||
#undef ADC_INT_VECTORS
|
||||
|
||||
#endif
|
73
config.hpp
Normal file
73
config.hpp
Normal file
@ -0,0 +1,73 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace adc {
|
||||
|
||||
namespace detail {
|
||||
|
||||
enum class Mode {
|
||||
SINGLE,
|
||||
AUTO,
|
||||
FREE_RUNNING,
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
enum class TriggerSource {
|
||||
FREE_RUNNING,
|
||||
ANALOG_COMP,
|
||||
EXTERNAL_INT_0,
|
||||
TIMER0_COMP_A,
|
||||
TIMER0_OVERFLOW,
|
||||
TIMER1_COMP_B,
|
||||
TIMER1_OVERFLOW,
|
||||
TIMER1_CAPTURE,
|
||||
};
|
||||
|
||||
template <TriggerSource src = TriggerSource::FREE_RUNNING>
|
||||
struct AutoMode {
|
||||
static constexpr auto SRC = src;
|
||||
};
|
||||
|
||||
struct FreeRunningMode {
|
||||
};
|
||||
|
||||
struct SingleMode {
|
||||
};
|
||||
|
||||
enum class VoltageRef {
|
||||
EXTERNAL,
|
||||
AVCC,
|
||||
INTERNAL,
|
||||
};
|
||||
|
||||
enum class InputSource {
|
||||
TEMP,
|
||||
VBG,
|
||||
GND,
|
||||
};
|
||||
|
||||
template <class Mode, VoltageRef vref = VoltageRef::AVCC, uint8_t prescaler = 128>
|
||||
struct Config {
|
||||
static constexpr auto MODE = detail::Mode::AUTO;
|
||||
static constexpr auto TRIGGER_SRC = Mode::SRC;
|
||||
static constexpr auto VREF = vref;
|
||||
static constexpr auto PRESCALER = prescaler;
|
||||
};
|
||||
|
||||
template <VoltageRef vref, uint8_t prescaler>
|
||||
struct Config<FreeRunningMode, vref, prescaler> {
|
||||
static constexpr auto MODE = detail::Mode::FREE_RUNNING;
|
||||
static constexpr auto VREF = vref;
|
||||
static constexpr auto PRESCALER = prescaler;
|
||||
};
|
||||
|
||||
template <VoltageRef vref, uint8_t prescaler>
|
||||
struct Config<SingleMode, vref, prescaler> {
|
||||
static constexpr auto MODE = detail::Mode::SINGLE;
|
||||
static constexpr auto VREF = vref;
|
||||
static constexpr auto PRESCALER = prescaler;
|
||||
};
|
||||
|
||||
} // namespace adc
|
95
hardware.hpp
Normal file
95
hardware.hpp
Normal file
@ -0,0 +1,95 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "../io/io.hpp"
|
||||
|
||||
namespace adc {
|
||||
|
||||
namespace detail {
|
||||
|
||||
#if defined(__AVR_ATmega328P__)
|
||||
|
||||
/*
|
||||
The following works in avr-gcc 5.4.0, but is not legal C++, because ptr's are not legal constexpr's:
|
||||
constexpr auto *foo = ptr;
|
||||
|
||||
Workaround is to store the address of the ptr in a uintptr_t and reinterpret_cast it at call site.
|
||||
The _SFR_ADDR macro in sfr_defs.h would give the address, but it does that by taking the address of the dereferenced
|
||||
pointer and casts it to uint16_t, which is still not a legal constexpr.
|
||||
The workaround therefore is to disable the pointer-cast-and-dereference macro _MMIO_BYTE temporarily.
|
||||
*/
|
||||
|
||||
#pragma push_macro("_MMIO_BYTE")
|
||||
#undef _MMIO_BYTE
|
||||
#define _MMIO_BYTE
|
||||
|
||||
struct Registers {
|
||||
static constexpr uintptr_t MUX_SEL_ADDR = ADMUX;
|
||||
static constexpr uintptr_t CTRL_STAT_A_ADDR = ADCSRA;
|
||||
static constexpr uintptr_t DATA_L_ADDR = ADCL;
|
||||
static constexpr uintptr_t DATA_H_ADDR = ADCH;
|
||||
static constexpr uintptr_t CTRL_STAT_B_ADDR = ADCSRB;
|
||||
static constexpr uintptr_t DIG_IN_DIS_ADDR = DIDR0;
|
||||
};
|
||||
|
||||
enum class ControlFlagsMUX {
|
||||
CHANNEL_SEL_0 = MUX0,
|
||||
CHANNEL_SEL_1 = MUX1,
|
||||
CHANNEL_SEL_2 = MUX2,
|
||||
CHANNEL_SEL_3 = MUX3,
|
||||
LEFT_ADJ_RES = ADLAR,
|
||||
REF_SEL_0 = REFS0,
|
||||
REF_SEL_1 = REFS1,
|
||||
};
|
||||
|
||||
enum class ControlFlagsA {
|
||||
PRESCALER_SEL_0 = ADPS0,
|
||||
PRESCALER_SEL_1 = ADPS1,
|
||||
PRESCALER_SEL_2 = ADPS2,
|
||||
CONV_COMPLETE_INT_ENABLE = ADIE,
|
||||
CONV_COMPLETE = ADIF,
|
||||
AUTO_TRIGGER = ADATE,
|
||||
START_CONV = ADSC,
|
||||
ENABLE = ADEN,
|
||||
};
|
||||
|
||||
enum class ControlFlagsB {
|
||||
TRIGGER_SRC_0 = ADTS0,
|
||||
TRIGGER_SRC_1 = ADTS1,
|
||||
TRIGGER_SRC_2 = ADTS2,
|
||||
ANALOG_COMP_MUX_ENABLE = ACME,
|
||||
};
|
||||
|
||||
enum class ControlFlagsDigInDis {
|
||||
DIGITAL_INPUT_DISABLE_0 = ADC0D,
|
||||
DIGITAL_INPUT_DISABLE_1 = ADC1D,
|
||||
DIGITAL_INPUT_DISABLE_2 = ADC2D,
|
||||
DIGITAL_INPUT_DISABLE_3 = ADC3D,
|
||||
DIGITAL_INPUT_DISABLE_4 = ADC4D,
|
||||
DIGITAL_INPUT_DISABLE_5 = ADC5D,
|
||||
};
|
||||
|
||||
// clang-format off
|
||||
constexpr int operator<<(const int &lhs, const ControlFlagsMUX &rhs) { return lhs << static_cast<int>(rhs); }
|
||||
constexpr int operator<<(const int &lhs, const ControlFlagsA &rhs) { return lhs << static_cast<int>(rhs); }
|
||||
constexpr int operator<<(const int &lhs, const ControlFlagsB &rhs) { return lhs << static_cast<int>(rhs); }
|
||||
// clang-format on
|
||||
|
||||
template <io::P pin>
|
||||
struct supports_adc {
|
||||
static constexpr auto value = (io::detail::getBus(pin) == io::Bus::C) ? true : false;
|
||||
};
|
||||
|
||||
template <io::P pin>
|
||||
constexpr auto supports_adc_v = supports_adc<pin>::value;
|
||||
|
||||
#pragma pop_macro("_MMIO_BYTE")
|
||||
|
||||
#else
|
||||
#error "This chip is not supported"
|
||||
#endif
|
||||
|
||||
} // namespace detail
|
||||
|
||||
} // namespace adc
|
Loading…
Reference in New Issue
Block a user