diff --git a/cores/arduino/PendSV.cpp b/cores/arduino/PendSV.cpp new file mode 100644 index 000000000..6eb84af90 --- /dev/null +++ b/cores/arduino/PendSV.cpp @@ -0,0 +1,162 @@ +#include "PendSV.h" + +#include +#include + +#if defined(USE_TINYUSB) +extern "C" void TinyUSB_Device_Task(void); +#endif + +namespace { +PendSV pendSv; + +#if defined(USE_TINYUSB) +void tinyUsbDeviceTaskService(uint8_t serviceId, void *context) { + (void)serviceId; + (void)context; + // One invocation drains TinyUSB's queued controller events under OPT_OS_NONE. + TinyUSB_Device_Task(); +} +#endif +} + +PendSV &PendSV::instance() { + return pendSv; +} + +#if defined(USE_TINYUSB) +extern "C" void tud_event_hook_cb(uint8_t rhport, uint32_t eventId, bool inIsr) { + (void)rhport; + (void)eventId; + (void)inIsr; + // TinyUSB owns the event queue. PendSV is only its deferred wake signal, so + // multiple controller events before dispatch require only one service call. + PendSV::instance().setPendingOnce(PendSVChannels::Usb); +} +#endif + +bool PendSV::initializeCoreServices() { + NVIC_SetPriority(PendSV_IRQn, (1 << __NVIC_PRIO_BITS) - 1); +#if defined(USE_TINYUSB) + return instance().registerService(PendSVChannels::Usb, tinyUsbDeviceTaskService, nullptr); +#else + return true; +#endif +} + +uint32_t PendSV::enterCritical() { + const uint32_t primask = __get_PRIMASK(); + __disable_irq(); + return primask; +} + +void PendSV::exitCritical(uint32_t primask) { + __set_PRIMASK(primask); +} + +bool PendSV::registerService(uint8_t serviceId, ServiceFn fn, void *context) { + if (serviceId >= kMaxServices || fn == nullptr) + return false; + + const uint32_t primask = enterCritical(); + pendingCount_[serviceId] = 0; + pendingMask_ &= ~(1u << serviceId); + services_[serviceId].fn = fn; + services_[serviceId].context = context; + exitCritical(primask); + + return true; +} + +void PendSV::clearService(uint8_t serviceId) { + if (serviceId >= kMaxServices) + return; + + const uint32_t primask = enterCritical(); + services_[serviceId].fn = nullptr; + services_[serviceId].context = nullptr; + pendingCount_[serviceId] = 0; + pendingMask_ &= ~(1u << serviceId); + exitCritical(primask); +} + +void PendSV::setPending(uint8_t serviceId) { + if (serviceId >= kMaxServices) + return; + + const uint32_t primask = enterCritical(); + uint16_t &pendingCount = pendingCount_[serviceId]; + if (pendingCount < UINT16_MAX) + ++pendingCount; + pendingMask_ |= (1u << serviceId); + exitCritical(primask); + + __DMB(); + SCB->ICSR = SCB_ICSR_PENDSVSET_Msk; +} + +void PendSV::setPendingOnce(uint8_t serviceId) { + if (serviceId >= kMaxServices) + return; + + const uint32_t primask = enterCritical(); + pendingCount_[serviceId] = 1; + pendingMask_ |= (1u << serviceId); + exitCritical(primask); + + __DMB(); + SCB->ICSR = SCB_ICSR_PENDSVSET_Msk; +} + +void PendSV::dispatchPending() { + // Bound one PendSV entry so high-rate producers do not monopolize return to + // thread mode. Remaining work re-pends PendSV below. + uint8_t dispatched = 0; + + while (dispatched < kDispatchBudget) { + uint32_t primask = enterCritical(); + const uint32_t pending = pendingMask_; + if (pending == 0) { + exitCritical(primask); + return; + } + + const uint8_t serviceId = static_cast(__builtin_ctz(pending)); + uint16_t &pendingCount = pendingCount_[serviceId]; + + if (pendingCount == 0) { + // Defensive scrub in case mask and count drift out of sync. + pendingMask_ &= ~(1u << serviceId); + exitCritical(primask); + continue; + } + + --pendingCount; + if (pendingCount == 0) + pendingMask_ &= ~(1u << serviceId); + + ServiceEntry entry = services_[serviceId]; + exitCritical(primask); + + // Pending work without a registered service is intentionally dropped. + // Producers are expected to register before calling setPending(), and + // clearService() cancels queued work for that service. + if (entry.fn != nullptr) + entry.fn(serviceId, entry.context); + + ++dispatched; + } + + const uint32_t primask = enterCritical(); + const bool hasRemaining = (pendingMask_ != 0); + exitCritical(primask); + + if (hasRemaining) { + __DMB(); + SCB->ICSR = SCB_ICSR_PENDSVSET_Msk; + } +} + +extern "C" void PendSV_Handler(void) { + PendSV::instance().dispatchPending(); +} diff --git a/cores/arduino/PendSV.h b/cores/arduino/PendSV.h new file mode 100644 index 000000000..98222b2c7 --- /dev/null +++ b/cores/arduino/PendSV.h @@ -0,0 +1,189 @@ +#pragma once + +#include + +#include +#include + +#if defined(SERCOM0) || defined(SERCOM0_REGS) +#define SIMIO_PENDSV_HAS_SERCOM0 true +#else +#define SIMIO_PENDSV_HAS_SERCOM0 false +#endif +#if defined(SERCOM1) || defined(SERCOM1_REGS) +#define SIMIO_PENDSV_HAS_SERCOM1 true +#else +#define SIMIO_PENDSV_HAS_SERCOM1 false +#endif +#if defined(SERCOM2) || defined(SERCOM2_REGS) +#define SIMIO_PENDSV_HAS_SERCOM2 true +#else +#define SIMIO_PENDSV_HAS_SERCOM2 false +#endif +#if defined(SERCOM3) || defined(SERCOM3_REGS) +#define SIMIO_PENDSV_HAS_SERCOM3 true +#else +#define SIMIO_PENDSV_HAS_SERCOM3 false +#endif +#if defined(SERCOM4) || defined(SERCOM4_REGS) +#define SIMIO_PENDSV_HAS_SERCOM4 true +#else +#define SIMIO_PENDSV_HAS_SERCOM4 false +#endif +#if defined(SERCOM5) || defined(SERCOM5_REGS) +#define SIMIO_PENDSV_HAS_SERCOM5 true +#else +#define SIMIO_PENDSV_HAS_SERCOM5 false +#endif +#if defined(SERCOM6) || defined(SERCOM6_REGS) +#define SIMIO_PENDSV_HAS_SERCOM6 true +#else +#define SIMIO_PENDSV_HAS_SERCOM6 false +#endif +#if defined(SERCOM7) || defined(SERCOM7_REGS) +#define SIMIO_PENDSV_HAS_SERCOM7 true +#else +#define SIMIO_PENDSV_HAS_SERCOM7 false +#endif +#if defined(DMAC) || defined(DMAC_REGS) +#define SIMIO_PENDSV_HAS_DMAC true +#else +#define SIMIO_PENDSV_HAS_DMAC false +#endif +#if defined(AES) || defined(AES_REGS) +#define SIMIO_PENDSV_HAS_AES true +#else +#define SIMIO_PENDSV_HAS_AES false +#endif +#if defined(ICM) || defined(ICM_REGS) +#define SIMIO_PENDSV_HAS_ICM true +#else +#define SIMIO_PENDSV_HAS_ICM false +#endif +#if defined(PUKCC) || defined(PUKCC_REGS) || defined(ID_PUKCC) || defined(PUKCC_INSTANCE_ID) || \ + defined(PUKCC_IRQn) +#define SIMIO_PENDSV_HAS_PUKCC true +#else +#define SIMIO_PENDSV_HAS_PUKCC false +#endif +#if defined(TRNG) || defined(TRNG_REGS) +#define SIMIO_PENDSV_HAS_TRNG true +#else +#define SIMIO_PENDSV_HAS_TRNG false +#endif +#if defined(GMAC) || defined(GMAC_REGS) +#define SIMIO_PENDSV_HAS_GMAC true +#define SIMIO_PENDSV_HAS_PHY true +#else +#define SIMIO_PENDSV_HAS_GMAC false +#define SIMIO_PENDSV_HAS_PHY false +#endif + +template +struct PendSVChannelMap { + static constexpr uint8_t kUnavailable = 0xFF; + +private: + static constexpr uint8_t assign(bool present, uint8_t next) { + return present ? next : kUnavailable; + } + static constexpr uint8_t advance(bool present, uint8_t next) { + return present ? static_cast(next + 1) : next; + } + + static constexpr uint8_t kBase = 0; + static constexpr uint8_t kAfterSercom0 = advance(HasSercom0, kBase); + static constexpr uint8_t kAfterSercom1 = advance(HasSercom1, kAfterSercom0); + static constexpr uint8_t kAfterSercom2 = advance(HasSercom2, kAfterSercom1); + static constexpr uint8_t kAfterSercom3 = advance(HasSercom3, kAfterSercom2); + static constexpr uint8_t kAfterSercom4 = advance(HasSercom4, kAfterSercom3); + static constexpr uint8_t kAfterSercom5 = advance(HasSercom5, kAfterSercom4); + static constexpr uint8_t kAfterSercom6 = advance(HasSercom6, kAfterSercom5); + static constexpr uint8_t kAfterSercom7 = advance(HasSercom7, kAfterSercom6); + static constexpr uint8_t kAfterRtc = advance(true, kAfterSercom7); + static constexpr uint8_t kAfterUsb = advance(true, kAfterRtc); + static constexpr uint8_t kAfterAdc = advance(true, kAfterUsb); + static constexpr uint8_t kAfterDmac = advance(HasDmac, kAfterAdc); + static constexpr uint8_t kAfterAes = advance(HasAes, kAfterDmac); + static constexpr uint8_t kAfterIcm = advance(HasIcm, kAfterAes); + static constexpr uint8_t kAfterPukcc = advance(HasPukcc, kAfterIcm); + static constexpr uint8_t kAfterTrng = advance(HasTrng, kAfterPukcc); + static constexpr uint8_t kAfterGmac = advance(HasGmac, kAfterTrng); + static constexpr uint8_t kAfterPhy = advance(HasPhy, kAfterGmac); + static constexpr uint8_t kAfterUsbProtocol = advance(true, kAfterPhy); + static constexpr uint8_t kAfterDiscreteInput = advance(true, kAfterUsbProtocol); + +public: + static constexpr uint8_t Sercom0 = assign(HasSercom0, kBase); + static constexpr uint8_t Sercom1 = assign(HasSercom1, kAfterSercom0); + static constexpr uint8_t Sercom2 = assign(HasSercom2, kAfterSercom1); + static constexpr uint8_t Sercom3 = assign(HasSercom3, kAfterSercom2); + static constexpr uint8_t Sercom4 = assign(HasSercom4, kAfterSercom3); + static constexpr uint8_t Sercom5 = assign(HasSercom5, kAfterSercom4); + static constexpr uint8_t Sercom6 = assign(HasSercom6, kAfterSercom5); + static constexpr uint8_t Sercom7 = assign(HasSercom7, kAfterSercom6); + static constexpr uint8_t Rtc = kAfterSercom7; + static constexpr uint8_t Usb = kAfterRtc; + static constexpr uint8_t Adc = kAfterUsb; + static constexpr uint8_t Dmac = assign(HasDmac, kAfterAdc); + static constexpr uint8_t Aes = assign(HasAes, kAfterDmac); + static constexpr uint8_t Icm = assign(HasIcm, kAfterAes); + static constexpr uint8_t Pukcc = assign(HasPukcc, kAfterIcm); + static constexpr uint8_t Trng = assign(HasTrng, kAfterPukcc); + static constexpr uint8_t Gmac = assign(HasGmac, kAfterTrng); + static constexpr uint8_t Phy = assign(HasPhy, kAfterGmac); + static constexpr uint8_t UsbProtocol = kAfterPhy; + static constexpr uint8_t DiscreteInput = kAfterUsbProtocol; + static constexpr uint8_t Count = kAfterDiscreteInput; + + static constexpr bool isAvailable(uint8_t channel) { return channel != kUnavailable; } + static constexpr uint8_t sercom(uint8_t index) { + return index == 0 ? Sercom0 : index == 1 ? Sercom1 : index == 2 ? Sercom2 + : index == 3 ? Sercom3 : index == 4 ? Sercom4 : index == 5 ? Sercom5 + : index == 6 ? Sercom6 : index == 7 ? Sercom7 : kUnavailable; + } + static constexpr uint8_t sercomIndex(uint8_t channel) { + return channel == Sercom0 ? 0 : channel == Sercom1 ? 1 : channel == Sercom2 ? 2 + : channel == Sercom3 ? 3 : channel == Sercom4 ? 4 : channel == Sercom5 ? 5 + : channel == Sercom6 ? 6 : channel == Sercom7 ? 7 : kUnavailable; + } +}; + +using PendSVChannels = PendSVChannelMap< + SIMIO_PENDSV_HAS_SERCOM0, SIMIO_PENDSV_HAS_SERCOM1, SIMIO_PENDSV_HAS_SERCOM2, + SIMIO_PENDSV_HAS_SERCOM3, SIMIO_PENDSV_HAS_SERCOM4, SIMIO_PENDSV_HAS_SERCOM5, + SIMIO_PENDSV_HAS_SERCOM6, SIMIO_PENDSV_HAS_SERCOM7, SIMIO_PENDSV_HAS_DMAC, + SIMIO_PENDSV_HAS_AES, SIMIO_PENDSV_HAS_ICM, SIMIO_PENDSV_HAS_PUKCC, + SIMIO_PENDSV_HAS_TRNG, SIMIO_PENDSV_HAS_GMAC, SIMIO_PENDSV_HAS_PHY>; + +class PendSV { +public: + using ServiceFn = void (*)(uint8_t serviceId, void *context); + static constexpr uint8_t kMaxServices = PendSVChannels::Count; + static constexpr uint8_t kDispatchBudget = 16; + static_assert(kMaxServices <= 32, "PendSV pending mask supports at most 32 services"); + + static PendSV &instance(); + static bool initializeCoreServices(); + + bool registerService(uint8_t serviceId, ServiceFn fn, void *context = nullptr); + void clearService(uint8_t serviceId); + void dispatchPending(); + void setPending(uint8_t serviceId); + void setPendingOnce(uint8_t serviceId); + +private: + static uint32_t enterCritical(); + static void exitCritical(uint32_t primask); + + struct ServiceEntry { + ServiceFn fn = nullptr; + void *context = nullptr; + }; + + std::array services_{}; + std::array pendingCount_{}; + volatile uint32_t pendingMask_ = 0; +}; diff --git a/cores/arduino/PendSVChannelMap.h b/cores/arduino/PendSVChannelMap.h new file mode 100644 index 000000000..441bb92cb --- /dev/null +++ b/cores/arduino/PendSVChannelMap.h @@ -0,0 +1,61 @@ +#pragma once + +#include + +template +struct PendSVChannelMap { + static constexpr uint8_t kMaxServices = 32; + static constexpr uint8_t kUnavailable = 0xFF; + +private: + static constexpr uint8_t assign(bool present, uint8_t next) { + return present ? next : kUnavailable; + } + + static constexpr uint8_t advance(bool present, uint8_t next) { + return present ? static_cast(next + 1) : next; + } + + static constexpr uint8_t kBase = 0; + static constexpr uint8_t kAfterSercom0 = advance(HasSercom0, kBase); + static constexpr uint8_t kAfterSercom1 = advance(HasSercom1, kAfterSercom0); + static constexpr uint8_t kAfterSercom2 = advance(HasSercom2, kAfterSercom1); + static constexpr uint8_t kAfterSercom3 = advance(HasSercom3, kAfterSercom2); + static constexpr uint8_t kAfterSercom4 = advance(HasSercom4, kAfterSercom3); + static constexpr uint8_t kAfterSercom5 = advance(HasSercom5, kAfterSercom4); + static constexpr uint8_t kAfterSercom6 = advance(HasSercom6, kAfterSercom5); + static constexpr uint8_t kAfterSercom7 = advance(HasSercom7, kAfterSercom6); + static constexpr uint8_t kAfterUsb = advance(HasUsb, kAfterSercom7); + static constexpr uint8_t kAfterGmac = advance(HasGmac, kAfterUsb); + static constexpr uint8_t kAfterPhy = advance(HasPhy, kAfterGmac); + static constexpr uint8_t kAfterAdc = advance(HasAdc, kAfterPhy); + static constexpr uint8_t kAfterAes = advance(HasAes, kAfterAdc); + static constexpr uint8_t kAfterPukcc = advance(HasPukcc, kAfterAes); + static constexpr uint8_t kAfterTrng = advance(HasTrng, kAfterPukcc); + +public: + static constexpr uint8_t Sercom0 = assign(HasSercom0, kBase); + static constexpr uint8_t Sercom1 = assign(HasSercom1, kAfterSercom0); + static constexpr uint8_t Sercom2 = assign(HasSercom2, kAfterSercom1); + static constexpr uint8_t Sercom3 = assign(HasSercom3, kAfterSercom2); + static constexpr uint8_t Sercom4 = assign(HasSercom4, kAfterSercom3); + static constexpr uint8_t Sercom5 = assign(HasSercom5, kAfterSercom4); + static constexpr uint8_t Sercom6 = assign(HasSercom6, kAfterSercom5); + static constexpr uint8_t Sercom7 = assign(HasSercom7, kAfterSercom6); + static constexpr uint8_t Usb = assign(HasUsb, kAfterSercom7); + static constexpr uint8_t Gmac = assign(HasGmac, kAfterUsb); + static constexpr uint8_t Phy = assign(HasPhy, kAfterGmac); + static constexpr uint8_t Adc = assign(HasAdc, kAfterPhy); + static constexpr uint8_t Aes = assign(HasAes, kAfterAdc); + static constexpr uint8_t Pukcc = assign(HasPukcc, kAfterAes); + static constexpr uint8_t Trng = assign(HasTrng, kAfterPukcc); + static constexpr uint8_t Rtc = assign(HasRtc, kAfterTrng); + static constexpr uint8_t Count = advance(HasRtc, kAfterTrng); + + static constexpr bool isAvailable(uint8_t channel) { + return channel != kUnavailable; + } +}; diff --git a/libraries/ADC/examples/AsyncChipTemperature/AsyncChipTemperature.ino b/libraries/ADC/examples/AsyncChipTemperature/AsyncChipTemperature.ino new file mode 100644 index 000000000..1fadd9564 --- /dev/null +++ b/libraries/ADC/examples/AsyncChipTemperature/AsyncChipTemperature.ino @@ -0,0 +1,112 @@ +#include + +volatile bool g_readComplete = false; +volatile uint16_t g_lastValue = 0; + +void onTempRead(ChannelADC *channel, uint16_t result, void *userData) { + (void)channel; + (void)userData; + g_lastValue = result; + g_readComplete = true; +} + +ChannelADC g_temp; +#ifdef ADC_HAS_D5X_E5X_REGISTERS +ChannelADC g_ctat; +#endif + +void setup() { + Serial.begin(115200); + while (!Serial); + + g_temp.setReadCallback(onTempRead, nullptr); +#ifdef ADC_HAS_D5X_E5X_REGISTERS + // On SAMD5x/SAME5x, the PTAT/CTAT sensors are routed by SUPC.VREF.TSSEL + // with VREFOE disabled. Use a non-SUPC ADC reference for these channels. + g_temp.setReference(AdcRefSel::ADC_REFSEL_INTVCC1); +#else + g_temp.setReference(AdcRefSel::ADC_REFSEL_INT1V); +#endif + g_temp.setCtrlB(AdcResSel::ADC_RESSEL_12BIT, AdcPrescaler::ADC_PRESCALER_DIV32); + +#ifdef ADC_HAS_D5X_E5X_REGISTERS + g_ctat.setReadCallback(onTempRead, nullptr); + g_ctat.setReference(AdcRefSel::ADC_REFSEL_INTVCC1); + g_ctat.setCtrlB(AdcResSel::ADC_RESSEL_12BIT, AdcPrescaler::ADC_PRESCALER_DIV32); + + if (!g_temp.attach(AdcMuxPos::ADC_MUXPOS_TEMP, AdcMuxNeg::ADC_MUXNEG_GND, + AdcSampleNum::ADC_SAMPLENUM_16)) { + Serial.println("Attach PTAT failed"); + } + + if (!g_ctat.attach(AdcMuxPos::ADC_MUXPOS_TEMP_CTAT, AdcMuxNeg::ADC_MUXNEG_GND, + AdcSampleNum::ADC_SAMPLENUM_16)) { + Serial.println("Attach CTAT failed"); + } +#else + if (!g_temp.attach(AdcMuxPos::ADC_MUXPOS_TEMP, AdcMuxNeg::ADC_MUXNEG_GND, + AdcSampleNum::ADC_SAMPLENUM_16)) { + Serial.println("Attach temperature channel failed"); + } +#endif +} + +void loop() { +#ifdef ADC_HAS_D5X_E5X_REGISTERS + g_readComplete = false; + if (!g_temp.read()) { + Serial.println("PTAT async enqueue failed"); + delay(1000); + return; + } + while (!g_readComplete) { + AdcEngine::instance().service(); + } + const uint16_t ptat = g_lastValue; + + g_readComplete = false; + if (!g_ctat.read()) { + Serial.println("CTAT async enqueue failed"); + delay(1000); + return; + } + while (!g_readComplete) { + AdcEngine::instance().service(); + } + const uint16_t ctat = g_lastValue; + + const float tempC = analogReadTemperatureC(ptat, ctat); + Serial.print("PTAT raw: "); + Serial.print(ptat); + Serial.print(" CTAT raw: "); + Serial.print(ctat); + Serial.print(" "); + Serial.print("Async chip temp (C): "); + Serial.println(tempC, 2); +#else + g_readComplete = false; + if (!g_temp.read()) { + Serial.println("Async enqueue failed"); + delay(1000); + return; + } + while (!g_readComplete) { + AdcEngine::instance().service(); + } + + const uint16_t raw = g_lastValue; + const float tempC = analogReadTemperatureC(raw); + + Serial.print("Async raw: "); + Serial.print(raw); + Serial.print(" tempC: "); + Serial.print(tempC, 2); + if (tempC >= 20.0f && tempC <= 24.0f) { + Serial.println(" (PASS 20-24C)"); + } else { + Serial.println(" (OUTSIDE 20-24C)"); + } +#endif + + delay(1000); +} diff --git a/libraries/ADC/examples/BasicRead/BasicRead.ino b/libraries/ADC/examples/BasicRead/BasicRead.ino new file mode 100644 index 000000000..161d1c81d --- /dev/null +++ b/libraries/ADC/examples/BasicRead/BasicRead.ino @@ -0,0 +1,25 @@ +#include + +ChannelADC adc; + +void setup() { + Serial.begin(115200); + while (!Serial) + ; + + // Attach a single-ended channel on A0. + if (!adc.attach(A0)) + Serial.println("ADC attach failed"); +} + +void loop() { + if (adc.read()) { + delay(5); + Serial.print("A0: "); + Serial.println(adc.value()); + } else { + Serial.println("ADC read enqueue failed"); + } + + delay(250); +} diff --git a/libraries/ADC/examples/CorrectADCResponse/CorrectADCResponse.ino b/libraries/ADC/examples/CorrectADCResponse/CorrectADCResponse.ino new file mode 100644 index 000000000..1bbbd5990 --- /dev/null +++ b/libraries/ADC/examples/CorrectADCResponse/CorrectADCResponse.ino @@ -0,0 +1,201 @@ +/* + ADC correction calibration example using the ADC library. + + This follows the SAMD_AnalogCorrection workflow, but applies values through + ChannelADC::setCalibration() instead of analogReadCorrection(). + + Wiring: + - A1 -> GND + - A2 -> 3.3V +*/ + +#include + +#define ADC_GND_PIN A1 +#define ADC_3V3_PIN A2 + +#define ADC_READS_SHIFT 8 +#define ADC_READS_COUNT (1 << ADC_READS_SHIFT) + +#define ADC_MIN_GAIN 0x0400 +#define ADC_UNITY_GAIN 0x0800 +#define ADC_MAX_GAIN (0x1000 - 1) +#define ADC_RESOLUTION_BITS 12 +#define ADC_RANGE (1 << ADC_RESOLUTION_BITS) +#define ADC_TOP_VALUE (ADC_RANGE - 1) + +#define MAX_TOP_VALUE_READS 10 + +ChannelADC gndChannel; +ChannelADC vccChannel; + +static uint16_t readChannelAverage(ChannelADC &channel) { + uint32_t accumulator = 0; + + for (int i = 0; i < ADC_READS_COUNT; ++i) { + if (channel.read()) { + delayMicroseconds(200); + accumulator += channel.value(); + } + } + + return static_cast(accumulator >> ADC_READS_SHIFT); +} + +static uint16_t readGndLevel() { + const uint16_t value = readChannelAverage(gndChannel); + Serial.print("ADC(GND) = "); + Serial.println(value); + return value; +} + +static uint16_t read3V3Level() { + uint16_t value = readChannelAverage(vccChannel); + + if (value < (ADC_RANGE >> 1)) { + value += ADC_RANGE; + } + + Serial.print("ADC(3.3V) = "); + Serial.println(value); + return value; +} + +static void applyCalibration(int offset, uint16_t gain) { + gndChannel.setCalibration(gain, static_cast(offset), true); + vccChannel.setCalibration(gain, static_cast(offset), true); +} + +void setup() { + Serial.begin(115200); + while (!Serial) + ; + + if (!gndChannel.attach(ADC_GND_PIN)) { + Serial.println("Failed to attach GND channel"); + return; + } + + if (!vccChannel.attach(ADC_3V3_PIN)) { + Serial.println("Failed to attach 3.3V channel"); + return; + } + + gndChannel.setCtrlB(AdcResSel::ADC_RESSEL_12BIT, AdcPrescaler::ADC_PRESCALER_DIV32); + vccChannel.setCtrlB(AdcResSel::ADC_RESSEL_12BIT, AdcPrescaler::ADC_PRESCALER_DIV32); + + Serial.println("\nCalibrating ADC with default correction values"); + Serial.println("\nReading GND and 3.3V ADC levels"); + Serial.print(" "); + readGndLevel(); + Serial.print(" "); + read3V3Level(); + + int offsetCorrectionValue = 0; + uint16_t gainCorrectionValue = ADC_UNITY_GAIN; + + Serial.print("\nOffset correction (@gain = "); + Serial.print(gainCorrectionValue); + Serial.println(" unity)"); + + applyCalibration(offsetCorrectionValue, gainCorrectionValue); + + for (int offset = 0; offset < 2048; ++offset) { + applyCalibration(offset, gainCorrectionValue); + + Serial.print(" Offset = "); + Serial.print(offset); + Serial.print(", "); + + if (readGndLevel() == 0) { + offsetCorrectionValue = offset; + break; + } + } + + Serial.println("\nGain correction"); + + uint8_t topValueReadsCount = 0; + uint16_t minGain = 0; + uint16_t maxGain = 0; + + applyCalibration(offsetCorrectionValue, gainCorrectionValue); + Serial.print(" Gain = "); + Serial.print(gainCorrectionValue); + Serial.print(", "); + uint16_t highLevelRead = read3V3Level(); + + if (highLevelRead < ADC_TOP_VALUE) { + for (uint16_t gain = ADC_UNITY_GAIN + 1; gain <= ADC_MAX_GAIN; ++gain) { + applyCalibration(offsetCorrectionValue, gain); + + Serial.print(" Gain = "); + Serial.print(gain); + Serial.print(", "); + highLevelRead = read3V3Level(); + + if (highLevelRead == ADC_TOP_VALUE) { + if (minGain == 0) + minGain = gain; + + if (++topValueReadsCount >= MAX_TOP_VALUE_READS) { + maxGain = minGain; + break; + } + + maxGain = gain; + } + + if (highLevelRead > ADC_TOP_VALUE) + break; + } + } else { + if (highLevelRead == ADC_TOP_VALUE) + maxGain = ADC_UNITY_GAIN; + + for (int gain = ADC_UNITY_GAIN - 1; gain >= ADC_MIN_GAIN; --gain) { + applyCalibration(offsetCorrectionValue, gain); + + Serial.print(" Gain = "); + Serial.print(gain); + Serial.print(", "); + highLevelRead = read3V3Level(); + + if (highLevelRead == ADC_TOP_VALUE) { + if (maxGain == 0) + maxGain = gain; + + minGain = gain; + } + + if (highLevelRead < ADC_TOP_VALUE) + break; + } + } + + gainCorrectionValue = (minGain + maxGain) >> 1; + applyCalibration(offsetCorrectionValue, gainCorrectionValue); + + Serial.println("\nReadings after corrections"); + Serial.print(" "); + readGndLevel(); + Serial.print(" "); + read3V3Level(); + + Serial.println("\n=================="); + Serial.println("\nCorrection values:"); + Serial.print(" Offset = "); + Serial.println(offsetCorrectionValue); + Serial.print(" Gain = "); + Serial.println(gainCorrectionValue); + Serial.println("\nApply in your sketch:"); + Serial.print(" channel.setCalibration("); + Serial.print(gainCorrectionValue); + Serial.print(", "); + Serial.print(offsetCorrectionValue); + Serial.println(", true);"); + Serial.println("\n=================="); +} + +void loop() { +} diff --git a/libraries/ADC/examples/ReadCallback/ReadCallback.ino b/libraries/ADC/examples/ReadCallback/ReadCallback.ino new file mode 100644 index 000000000..3d6785d4d --- /dev/null +++ b/libraries/ADC/examples/ReadCallback/ReadCallback.ino @@ -0,0 +1,43 @@ +#include + +ChannelADC adc; +volatile uint16_t latestValue = 0; +volatile bool sampleReady = false; + +void onAdcRead(ChannelADC *channel, uint16_t result, void *userData) { + (void)channel; + (void)userData; + latestValue = result; + sampleReady = true; +} + +void setup() { + Serial.begin(115200); + while (!Serial) + ; + + if (!adc.attach(A0)) + Serial.println("ADC attach failed"); + adc.setReadCallback(onAdcRead, nullptr); +} + +void loop() { + static uint32_t lastTriggerMs = 0; + const uint32_t now = millis(); + + if (now - lastTriggerMs >= 100) { + lastTriggerMs = now; + if (!adc.read()) + Serial.println("ADC read enqueue failed"); + } + + if (sampleReady) { + noInterrupts(); + const uint16_t value = latestValue; + sampleReady = false; + interrupts(); + + Serial.print("A0: "); + Serial.println(value); + } +} diff --git a/libraries/ADC/examples/SyncTempRegisterRead/SyncTempRegisterRead.ino b/libraries/ADC/examples/SyncTempRegisterRead/SyncTempRegisterRead.ino new file mode 100644 index 000000000..8e0c1c07f --- /dev/null +++ b/libraries/ADC/examples/SyncTempRegisterRead/SyncTempRegisterRead.ino @@ -0,0 +1,81 @@ +#include + +#ifndef ADC_HAS_D5X_E5X_REGISTERS +static inline void waitAdcSyncRaw() { + while (ADC->STATUS.bit.SYNCBUSY) { + } +} + +uint16_t readTempRawDirectSync() { + SYSCTRL->VREF.reg |= SYSCTRL_VREF_TSEN; + + const uint16_t oldCtrlB = ADC->CTRLB.reg; + const uint8_t oldSampCtrl = ADC->SAMPCTRL.reg; + const uint8_t oldAvgCtrl = ADC->AVGCTRL.reg; + const uint8_t oldRefSel = ADC->REFCTRL.bit.REFSEL; + const uint8_t oldGain = ADC->INPUTCTRL.bit.GAIN; + + ADC->CTRLA.bit.ENABLE = 0; + waitAdcSyncRaw(); + + ADC->CTRLB.reg = ADC_CTRLB_RESSEL_12BIT | ADC_CTRLB_PRESCALER_DIV256; + ADC->SAMPCTRL.reg = ADC_SAMPCTRL_SAMPLEN(0x3F); + ADC->AVGCTRL.reg = ADC_AVGCTRL_SAMPLENUM_64 | ADC_AVGCTRL_ADJRES(0x4); + + ADC->INPUTCTRL.bit.GAIN = ADC_INPUTCTRL_GAIN_1X_Val; + ADC->REFCTRL.bit.REFSEL = ADC_REFCTRL_REFSEL_INT1V_Val; + ADC->INPUTCTRL.reg = ADC_INPUTCTRL_MUXPOS(ADC_INPUTCTRL_MUXPOS_TEMP_Val) | + ADC_INPUTCTRL_MUXNEG(ADC_INPUTCTRL_MUXNEG_GND_Val); + + waitAdcSyncRaw(); + + ADC->CTRLA.bit.ENABLE = 1; + waitAdcSyncRaw(); + + ADC->SWTRIG.bit.START = 1; + while (!ADC->INTFLAG.bit.RESRDY) { + } + ADC->INTFLAG.reg = ADC_INTFLAG_RESRDY; + + ADC->SWTRIG.bit.START = 1; + while (!ADC->INTFLAG.bit.RESRDY) { + } + const uint16_t value = ADC->RESULT.reg; + + ADC->INTFLAG.reg = ADC_INTFLAG_RESRDY; + ADC->CTRLA.bit.ENABLE = 0; + waitAdcSyncRaw(); + + ADC->CTRLB.reg = oldCtrlB; + ADC->SAMPCTRL.reg = oldSampCtrl; + ADC->AVGCTRL.reg = oldAvgCtrl; + ADC->INPUTCTRL.bit.GAIN = oldGain; + ADC->REFCTRL.bit.REFSEL = oldRefSel; + waitAdcSyncRaw(); + + return value; +} +#endif + +void setup() { + Serial.begin(115200); + while (!Serial) { + } + +#ifndef ADC_HAS_D5X_E5X_REGISTERS + Serial.println("Sync temperature register read example"); +#else + Serial.println("SyncTempRegisterRead is SAMD21-specific (direct ADC register path)."); +#endif +} + +void loop() { +#ifndef ADC_HAS_D5X_E5X_REGISTERS + const uint16_t raw = readTempRawDirectSync(); + Serial.print("Raw temp register ADC: "); + Serial.println(raw); +#else + Serial.println("Use AsyncChipTemperature example for SAMD5X."); +#endif + delay(1000); +} diff --git a/libraries/ADC/keywords.txt b/libraries/ADC/keywords.txt new file mode 100644 index 000000000..badcb3a7b --- /dev/null +++ b/libraries/ADC/keywords.txt @@ -0,0 +1,52 @@ +####################################### +# Syntax Coloring Map For ADC +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +ChannelADC KEYWORD1 +AdcEngine KEYWORD1 +AdcSampleNum KEYWORD1 +AdcWinMode KEYWORD1 +AdcRefSel KEYWORD1 +AdcGain KEYWORD1 +AdcResSel KEYWORD1 +AdcPrescaler KEYWORD1 +AdcMuxPos KEYWORD1 +AdcMuxNeg KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +analogReadCorrection KEYWORD2 +attach KEYWORD2 +read KEYWORD2 +value KEYWORD2 +setWindow KEYWORD2 +clearWindow KEYWORD2 +windowEnabled KEYWORD2 +setWindowCallback KEYWORD2 +setReadCallback KEYWORD2 +setEventControl KEYWORD2 +setCtrlB KEYWORD2 +setReference KEYWORD2 +setGain KEYWORD2 +setCalibration KEYWORD2 +end KEYWORD2 +begin KEYWORD2 +instance KEYWORD2 +enqueue KEYWORD2 +registerChannel KEYWORD2 +unregisterChannel KEYWORD2 +service KEYWORD2 +resultByteForMux KEYWORD2 +enabledMask KEYWORD2 + +####################################### +# Constants (LITERAL1) +####################################### + +ADC_MUX_SOURCE_INVALID LITERAL1 diff --git a/libraries/ADC/library.properties b/libraries/ADC/library.properties new file mode 100644 index 000000000..da11e1842 --- /dev/null +++ b/libraries/ADC/library.properties @@ -0,0 +1,10 @@ +name=ADC +version=1.0.0 +author=Cal ABEL +maintainer=Adafruit Industries +sentence=Asynchronous ADC channel wrapper for SAMD devices. +paragraph=Provides queued ADC reads with optional callback handling and per-channel configuration. +category=Signal Input/Output +url=https://github.com/adafruit/ArduinoCore-samd +architectures=samd +depends=Adafruit ZeroDMA diff --git a/libraries/ADC/src/ADC.cpp b/libraries/ADC/src/ADC.cpp new file mode 100644 index 000000000..534c02294 --- /dev/null +++ b/libraries/ADC/src/ADC.cpp @@ -0,0 +1,1289 @@ +#include "ADC.h" + +#include +#include + +#ifdef ADC_HAS_D5X_E5X_REGISTERS +#ifndef NVMCTRL_TEMP_LOG +#ifdef NVMCTRL_TEMP_LOG_W0 +#define NVMCTRL_TEMP_LOG NVMCTRL_TEMP_LOG_W0 +#endif +#endif +#endif + +static inline float decToFrac(uint8_t val) { + // Temperature fuse decimal part is 4 bits only (0-15): + // 0-9 represent tenths (0.0-0.9), 10-15 represent hundredths (0.10-0.15) + if (val < 10) + return static_cast(val) / 10.0f; + return static_cast(val) / 100.0f; +} + +#ifdef ADC_HAS_D5X_E5X_REGISTERS +float analogReadTemperatureC(uint16_t tp, uint16_t tc) { +#ifdef ADC_HAS_SAME53_E54_REGISTERS + const uint32_t tempLog0 = *(uint32_t *)(TEMP_LOG_ADDR + 0u); + const uint32_t tempLog1 = *(uint32_t *)(TEMP_LOG_ADDR + 4u); + const uint32_t tempLog2 = *(uint32_t *)(TEMP_LOG_ADDR + 8u); + + const uint32_t roomInt = + (tempLog0 & FUSES_TEMP_LOG_WORD_0_ROOM_TEMP_VAL_INT_Msk) >> + FUSES_TEMP_LOG_WORD_0_ROOM_TEMP_VAL_INT_Pos; + const uint8_t roomDec = static_cast( + (tempLog0 & FUSES_TEMP_LOG_WORD_0_ROOM_TEMP_VAL_DEC_Msk) >> + FUSES_TEMP_LOG_WORD_0_ROOM_TEMP_VAL_DEC_Pos); + const float roomTemp = static_cast(roomInt) + decToFrac(roomDec); + + const uint32_t hotInt = + (tempLog0 & FUSES_TEMP_LOG_WORD_0_HOT_TEMP_VAL_INT_Msk) >> + FUSES_TEMP_LOG_WORD_0_HOT_TEMP_VAL_INT_Pos; + const uint8_t hotDec = static_cast( + (tempLog0 & FUSES_TEMP_LOG_WORD_0_HOT_TEMP_VAL_DEC_Msk) >> + FUSES_TEMP_LOG_WORD_0_HOT_TEMP_VAL_DEC_Pos); + const float hotTemp = static_cast(hotInt) + decToFrac(hotDec); + + const uint16_t vpl = + (tempLog1 & FUSES_TEMP_LOG_WORD_1_ROOM_ADC_VAL_PTAT_Msk) >> + FUSES_TEMP_LOG_WORD_1_ROOM_ADC_VAL_PTAT_Pos; + const uint16_t vph = + (tempLog1 & FUSES_TEMP_LOG_WORD_1_HOT_ADC_VAL_PTAT_Msk) >> + FUSES_TEMP_LOG_WORD_1_HOT_ADC_VAL_PTAT_Pos; + const uint16_t vcl = + (tempLog2 & FUSES_TEMP_LOG_WORD_2_ROOM_ADC_VAL_CTAT_Msk) >> + FUSES_TEMP_LOG_WORD_2_ROOM_ADC_VAL_CTAT_Pos; + const uint16_t vch = + (tempLog2 & FUSES_TEMP_LOG_WORD_2_HOT_ADC_VAL_CTAT_Msk) >> + FUSES_TEMP_LOG_WORD_2_HOT_ADC_VAL_CTAT_Pos; +#else + const uint32_t roomInt = (*(uint32_t *)FUSES_ROOM_TEMP_VAL_INT_ADDR & FUSES_ROOM_TEMP_VAL_INT_Msk) >> + FUSES_ROOM_TEMP_VAL_INT_Pos; + const uint8_t roomDec = static_cast((*(uint32_t *)FUSES_ROOM_TEMP_VAL_DEC_ADDR & FUSES_ROOM_TEMP_VAL_DEC_Msk) >> + FUSES_ROOM_TEMP_VAL_DEC_Pos); + const float roomTemp = static_cast(roomInt) + decToFrac(roomDec); + + const uint32_t hotInt = (*(uint32_t *)FUSES_HOT_TEMP_VAL_INT_ADDR & FUSES_HOT_TEMP_VAL_INT_Msk) >> + FUSES_HOT_TEMP_VAL_INT_Pos; + const uint8_t hotDec = static_cast((*(uint32_t *)FUSES_HOT_TEMP_VAL_DEC_ADDR & FUSES_HOT_TEMP_VAL_DEC_Msk) >> + FUSES_HOT_TEMP_VAL_DEC_Pos); + const float hotTemp = static_cast(hotInt) + decToFrac(hotDec); + + const uint16_t vpl = (*(uint32_t *)FUSES_ROOM_ADC_VAL_PTAT_ADDR & FUSES_ROOM_ADC_VAL_PTAT_Msk) >> + FUSES_ROOM_ADC_VAL_PTAT_Pos; + const uint16_t vph = (*(uint32_t *)FUSES_HOT_ADC_VAL_PTAT_ADDR & FUSES_HOT_ADC_VAL_PTAT_Msk) >> + FUSES_HOT_ADC_VAL_PTAT_Pos; + const uint16_t vcl = (*(uint32_t *)FUSES_ROOM_ADC_VAL_CTAT_ADDR & FUSES_ROOM_ADC_VAL_CTAT_Msk) >> + FUSES_ROOM_ADC_VAL_CTAT_Pos; + const uint16_t vch = (*(uint32_t *)FUSES_HOT_ADC_VAL_CTAT_ADDR & FUSES_HOT_ADC_VAL_CTAT_Msk) >> + FUSES_HOT_ADC_VAL_CTAT_Pos; +#endif // ADC_HAS_SAME53_E54_REGISTERS + + return (roomTemp * vph * tc - vpl * hotTemp * tc - roomTemp * vch * tp + hotTemp * vcl * tp) / + (vcl * tp - vch * tp - vpl * tc + vph * tc); +} +#else +float analogReadTemperatureC(uint16_t adcReading) { + const uint8_t roomInt = + static_cast((*((uint32_t *)FUSES_ROOM_TEMP_VAL_INT_ADDR) & FUSES_ROOM_TEMP_VAL_INT_Msk) >> + FUSES_ROOM_TEMP_VAL_INT_Pos); + const uint8_t roomDec = + static_cast((*((uint32_t *)FUSES_ROOM_TEMP_VAL_DEC_ADDR) & FUSES_ROOM_TEMP_VAL_DEC_Msk) >> + FUSES_ROOM_TEMP_VAL_DEC_Pos); + const float roomTemp = static_cast(roomInt) + decToFrac(roomDec); + + const uint8_t hotInt = + static_cast((*((uint32_t *)FUSES_HOT_TEMP_VAL_INT_ADDR) & FUSES_HOT_TEMP_VAL_INT_Msk) >> + FUSES_HOT_TEMP_VAL_INT_Pos); + const uint8_t hotDec = + static_cast((*((uint32_t *)FUSES_HOT_TEMP_VAL_DEC_ADDR) & FUSES_HOT_TEMP_VAL_DEC_Msk) >> + FUSES_HOT_TEMP_VAL_DEC_Pos); + const float hotTemp = static_cast(hotInt) + decToFrac(hotDec); + + const uint16_t roomAdc = + static_cast((*((uint32_t *)FUSES_ROOM_ADC_VAL_ADDR) & FUSES_ROOM_ADC_VAL_Msk) >> + FUSES_ROOM_ADC_VAL_Pos); + const uint16_t hotAdc = + static_cast((*((uint32_t *)FUSES_HOT_ADC_VAL_ADDR) & FUSES_HOT_ADC_VAL_Msk) >> + FUSES_HOT_ADC_VAL_Pos); + + const int8_t roomInt1vRaw = + static_cast((*((uint32_t *)FUSES_ROOM_INT1V_VAL_ADDR) & FUSES_ROOM_INT1V_VAL_Msk) >> + FUSES_ROOM_INT1V_VAL_Pos); + const int8_t hotInt1vRaw = + static_cast((*((uint32_t *)FUSES_HOT_INT1V_VAL_ADDR) & FUSES_HOT_INT1V_VAL_Msk) >> + FUSES_HOT_INT1V_VAL_Pos); + + const float roomInt1v = 1.0f - (static_cast(roomInt1vRaw) / 1000.0f); + const float hotInt1v = 1.0f - (static_cast(hotInt1vRaw) / 1000.0f); + + const float roomVoltageComp = (static_cast(roomAdc) * roomInt1v) / 4095.0f; + const float hotVoltageComp = (static_cast(hotAdc) * hotInt1v) / 4095.0f; + + const float measurementVoltage = static_cast(adcReading) / 4095.0f; + const float coarseTemp = roomTemp + (((hotTemp - roomTemp) / (hotVoltageComp - roomVoltageComp)) * + (measurementVoltage - roomVoltageComp)); + + const float ref1vAtMeasurement = + roomInt1v + (((hotInt1v - roomInt1v) * (coarseTemp - roomTemp)) / (hotTemp - roomTemp)); + const float measurementVoltageComp = (static_cast(adcReading) * ref1vAtMeasurement) / 4095.0f; + + return roomTemp + (((hotTemp - roomTemp) / (hotVoltageComp - roomVoltageComp)) * + (measurementVoltageComp - roomVoltageComp)); +} +#endif + +void analogReadCorrection(int offset, uint16_t gain) { +#ifdef ADC_HAS_SAME53_E54_REGISTERS + adc_registers_t *adc = ADC0_REGS; + + adc->ADC_OFFSETCORR = static_cast(offset); + adc->ADC_GAINCORR = gain; + adc->ADC_CTRLB |= ADC_CTRLB_CORREN_Msk; + + while (adc->ADC_SYNCBUSY); +#else + Adc *adc; +#ifdef ADC_HAS_D5X_E5X_REGISTERS + adc = ADC0; +#else + adc = ADC; +#endif + + adc->OFFSETCORR.reg = static_cast(offset); + adc->GAINCORR.reg = gain; + adc->CTRLB.bit.CORREN = 1; + +#ifdef ADC_HAS_D5X_E5X_REGISTERS + while (adc->SYNCBUSY.reg); +#else + while (adc->STATUS.bit.SYNCBUSY); +#endif +#endif // ADC_HAS_SAME53_E54_REGISTERS +} + +namespace { +constexpr uint8_t kMaxAdjres = 4; +constexpr uint8_t kAdcPendSvServiceId = PendSVChannels::Adc; +constexpr uint32_t kAdcNvicPriority = (1u << __NVIC_PRIO_BITS) - 1u; + +#ifdef ADC_HAS_D5X_E5X_REGISTERS +#ifdef ADC_EVCTRL_SYNCEI +constexpr uint8_t kAdcEvctrlSynceiBit = ADC_EVCTRL_SYNCEI; +#elif defined(ADC_EVCTRL_FLUSHEI_Msk) +constexpr uint8_t kAdcEvctrlSynceiBit = ADC_EVCTRL_FLUSHEI_Msk; +#elif defined(ADC_EVCTRL_FLUSHEI) +constexpr uint8_t kAdcEvctrlSynceiBit = ADC_EVCTRL_FLUSHEI; +#else +constexpr uint8_t kAdcEvctrlSynceiBit = 0u; +#endif +#else +constexpr uint8_t kAdcEvctrlSynceiBit = ADC_EVCTRL_SYNCEI; +#endif + +uint8_t adcDmacResrdyTrigger() { +#ifdef ADC_HAS_D5X_E5X_REGISTERS + return ADC0_DMAC_ID_RESRDY; +#else + return ADC_DMAC_ID_RESRDY; +#endif +} + +#ifdef ADC_HAS_SAME53_E54_REGISTERS +inline adc_registers_t *adcInstance() { + return ADC0_REGS; +} + +inline void *adcResultRegister(adc_registers_t *adc) { + return (void *)&adc->ADC_RESULT; +} + +inline uint16_t adcReadResult(adc_registers_t *adc) { + return adc->ADC_RESULT; +} + +inline void adcSetEnable(adc_registers_t *adc, bool enabled) { + if (enabled) + adc->ADC_CTRLA |= ADC_CTRLA_ENABLE_Msk; + else + adc->ADC_CTRLA &= ~ADC_CTRLA_ENABLE_Msk; +} + +inline void adcSoftwareReset(adc_registers_t *adc) { + adc->ADC_CTRLA |= ADC_CTRLA_SWRST_Msk; +} + +inline bool adcSoftwareResetBusy(adc_registers_t *adc) { + return (adc->ADC_SYNCBUSY & ADC_SYNCBUSY_SWRST_Msk) != 0; +} + +inline void adcDisableResultReady(adc_registers_t *adc) { + adc->ADC_INTENCLR = ADC_INTENCLR_RESRDY_Msk; +} + +inline uint8_t adcReadFlags(adc_registers_t *adc) { + return static_cast(adc->ADC_INTFLAG & ADC_INTFLAG_Msk); +} + +inline void adcClearFlags(adc_registers_t *adc, uint8_t flags) { + adc->ADC_INTFLAG = flags; +} + +inline bool adcFlagWinmon(uint8_t flags) { + return (flags & ADC_INTFLAG_WINMON_Msk) != 0u; +} + +inline bool adcFlagResrdy(uint8_t flags) { + return (flags & ADC_INTFLAG_RESRDY_Msk) != 0u; +} + +inline void adcDisableInterrupts(adc_registers_t *adc, uint8_t mask) { + adc->ADC_INTENCLR = mask; +} + +inline void adcEnableInterrupts(adc_registers_t *adc, uint8_t mask) { + adc->ADC_INTENSET = mask; +} + +inline void adcWriteRefctrl(adc_registers_t *adc, uint8_t value) { + adc->ADC_REFCTRL = value; +} + +inline void adcWriteInputctrl(adc_registers_t *adc, uint16_t value) { + adc->ADC_INPUTCTRL = value; +} + +inline void adcWriteAvgctrl(adc_registers_t *adc, uint8_t value) { + adc->ADC_AVGCTRL = value; +} + +inline void adcWriteSampctrl(adc_registers_t *adc, uint8_t value) { + adc->ADC_SAMPCTRL = value; +} + +inline uint16_t adcReadCtrla(adc_registers_t *adc) { + return adc->ADC_CTRLA; +} + +inline void adcWriteCtrla(adc_registers_t *adc, uint16_t value) { + adc->ADC_CTRLA = value; +} + +inline void adcWriteCtrlb(adc_registers_t *adc, uint16_t value) { + adc->ADC_CTRLB = value; +} + +inline void adcWriteEvctrl(adc_registers_t *adc, uint8_t value) { + adc->ADC_EVCTRL = value; +} + +inline void adcWriteWinlt(adc_registers_t *adc, uint16_t value) { + adc->ADC_WINLT = value; +} + +inline void adcWriteWinut(adc_registers_t *adc, uint16_t value) { + adc->ADC_WINUT = value; +} + +inline void adcWriteOffsetcorr(adc_registers_t *adc, uint16_t value) { + adc->ADC_OFFSETCORR = value; +} + +inline void adcWriteGaincorr(adc_registers_t *adc, uint16_t value) { + adc->ADC_GAINCORR = value; +} + +inline void adcStartConversion(adc_registers_t *adc) { + adc->ADC_SWTRIG = ADC_SWTRIG_START_Msk; +} +#else +inline Adc *adcInstance() { +#ifdef ADC_HAS_D5X_E5X_REGISTERS + return ADC0; +#else + return ADC; +#endif +} + +inline void *adcResultRegister(Adc *adc) { + return (void *)&adc->RESULT.reg; +} + +inline uint16_t adcReadResult(Adc *adc) { + return adc->RESULT.reg; +} + +inline void adcSetEnable(Adc *adc, bool enabled) { + adc->CTRLA.bit.ENABLE = enabled ? 1 : 0; +} + +inline void adcSoftwareReset(Adc *adc) { + adc->CTRLA.bit.SWRST = 1; +} + +inline bool adcSoftwareResetBusy(Adc *adc) { + return adc->CTRLA.bit.SWRST; +} + +inline void adcDisableResultReady(Adc *adc) { + adc->INTENCLR.bit.RESRDY = 1; +} + +inline uint8_t adcReadFlags(Adc *adc) { + return static_cast(adc->INTFLAG.reg & 0x0F); +} + +inline void adcClearFlags(Adc *adc, uint8_t flags) { + adc->INTFLAG.reg = flags; +} + +inline bool adcFlagWinmon(uint8_t flags) { + return (flags & ADC_INTFLAG_WINMON) != 0u; +} + +inline bool adcFlagResrdy(uint8_t flags) { + return (flags & ADC_INTFLAG_RESRDY) != 0u; +} + +inline void adcDisableInterrupts(Adc *adc, uint8_t mask) { + adc->INTENCLR.reg = mask; +} + +inline void adcEnableInterrupts(Adc *adc, uint8_t mask) { + adc->INTENSET.reg = mask; +} + +inline void adcWriteRefctrl(Adc *adc, uint8_t value) { + adc->REFCTRL.reg = value; +} + +inline void adcWriteInputctrl(Adc *adc, uint32_t value) { + adc->INPUTCTRL.reg = value; +} + +inline void adcWriteAvgctrl(Adc *adc, uint8_t value) { + adc->AVGCTRL.reg = value; +} + +inline void adcWriteSampctrl(Adc *adc, uint8_t value) { +#ifdef ADC_HAS_D5X_E5X_REGISTERS + adc->SAMPCTRL.reg = value; +#else + (void)adc; + (void)value; +#endif +} + +inline uint16_t adcReadCtrla(Adc *adc) { + return adc->CTRLA.reg; +} + +inline void adcWriteCtrla(Adc *adc, uint16_t value) { + adc->CTRLA.reg = value; +} + +inline void adcWriteCtrlb(Adc *adc, uint16_t value) { + adc->CTRLB.reg = value; +} + +inline void adcWriteEvctrl(Adc *adc, uint8_t value) { + adc->EVCTRL.reg = value; +} + +inline void adcWriteWinlt(Adc *adc, uint16_t value) { + adc->WINLT.reg = value; +} + +inline void adcWriteWinut(Adc *adc, uint16_t value) { + adc->WINUT.reg = value; +} + +inline void adcWriteOffsetcorr(Adc *adc, uint16_t value) { + adc->OFFSETCORR.reg = value; +} + +inline void adcWriteGaincorr(Adc *adc, uint16_t value) { + adc->GAINCORR.reg = value; +} + +inline void adcStartConversion(Adc *adc) { + adc->SWTRIG.bit.START = 1; +} +#endif // ADC_HAS_SAME53_E54_REGISTERS + +#ifdef ADC_HAS_D5X_E5X_REGISTERS +void configureInternalReference(uint8_t refSel) { + if (refSel != ADC_REFCTRL_REFSEL_INTREF_Val) + return; + +#ifdef ADC_HAS_SAME53_E54_REGISTERS + SUPC_REGS->SUPC_VREF = + (SUPC_REGS->SUPC_VREF & ~SUPC_VREF_SEL_Msk) | + SUPC_VREF_SEL_1V0 | + SUPC_VREF_VREFOE_Msk; +#else + SUPC->VREF.bit.SEL = SUPC_VREF_SEL_1V0_Val; + SUPC->VREF.bit.VREFOE = 1; +#endif // ADC_HAS_SAME53_E54_REGISTERS +} + +void configureTemperatureSensor(uint8_t muxPos) { + if (muxPos != ADC_INPUTCTRL_MUXPOS_PTAT_Val && + muxPos != ADC_INPUTCTRL_MUXPOS_CTAT_Val) + return; + +#ifdef ADC_HAS_SAME53_E54_REGISTERS + uint32_t vref = SUPC_REGS->SUPC_VREF; + vref &= ~(SUPC_VREF_ONDEMAND_Msk | SUPC_VREF_VREFOE_Msk | SUPC_VREF_TSSEL_Msk); + if (muxPos == ADC_INPUTCTRL_MUXPOS_CTAT_Val) + vref |= SUPC_VREF_TSSEL_Msk; + vref |= SUPC_VREF_TSEN_Msk; + SUPC_REGS->SUPC_VREF = vref; +#else + SUPC->VREF.bit.ONDEMAND = 0; + SUPC->VREF.bit.VREFOE = 0; + SUPC->VREF.bit.TSSEL = (muxPos == ADC_INPUTCTRL_MUXPOS_CTAT_Val) ? 1 : 0; + SUPC->VREF.bit.TSEN = 1; +#endif // ADC_HAS_SAME53_E54_REGISTERS +} + +uint8_t sampleTimeForMux(uint8_t muxPos) { + if (muxPos == ADC_INPUTCTRL_MUXPOS_PTAT_Val || + muxPos == ADC_INPUTCTRL_MUXPOS_CTAT_Val) + return 0x3Fu; + + return 5u; +} +#endif + +// ADC IRQ routing mirrors SERCOM family handling: +// - SAMD2x exposes a single ADC_IRQn vector. +// - SAMD51/E51 splits ADC0 interrupts across ADC0_0/ADC0_1. +// - SAME53/E54 splits ADC0 interrupts across ADC0_OTHER/ADC0_RESRDY. +// Enabling/servicing both split lines ensures window-monitor and data-ready +// callbacks continue to work together on 5x devices. + +uint8_t adcIrqCount() { +#ifdef ADC_HAS_D5X_E5X_REGISTERS + return 2; +#else + return 1; +#endif +} + +IRQn_Type adcIrqAt(uint8_t index) { +#ifdef ADC_HAS_SAME53_E54_REGISTERS + return (index == 0) ? ADC0_OTHER_IRQn : ADC0_RESRDY_IRQn; +#elif defined(ADC_HAS_D5X_E5X_REGISTERS) + return (index == 0) ? ADC0_0_IRQn : ADC0_1_IRQn; +#else + (void)index; + return ADC_IRQn; +#endif +} + +void adcPendSvService(uint8_t serviceId, void *context) { + (void)context; + if (serviceId != kAdcPendSvServiceId) + return; + + AdcEngine::instance().onPendSv(); +} + +uint8_t sampleNumCodeToAdjres(uint8_t sampleNumCode) { + if (sampleNumCode == 0) + return 0; + if (sampleNumCode > kMaxAdjres) + return kMaxAdjres; + return sampleNumCode; +} + +uint8_t sampleNumToCode(AdcSampleNum sampleNum) { + return static_cast(sampleNum); +} + +uint8_t pinToMux(uint8_t arduinoPin) { + if (arduinoPin >= PINS_COUNT) + return ADC_MUX_SOURCE_INVALID; + + const EAnalogChannel channel = g_APinDescription[arduinoPin].ulADCChannelNumber; + if (channel == No_ADC_Channel) + return ADC_MUX_SOURCE_INVALID; + + return static_cast(channel); +} + +void configureAnalogPin(uint8_t arduinoPin) { + if (arduinoPin >= PINS_COUNT) + return; + + const EAnalogChannel channel = g_APinDescription[arduinoPin].ulADCChannelNumber; + if (channel == No_ADC_Channel) + return; + + pinPeripheral(arduinoPin, PIO_ANALOG); +} +} // namespace + +uint8_t ADC_MUXPOS_PIN(uint8_t pin) { + const uint8_t mux = pinToMux(pin); + if (mux == ADC_MUX_SOURCE_INVALID) + return ADC_MUX_SOURCE_INVALID; + + return mux; +} + +uint8_t ADC_MUXNEG_PIN(uint8_t pin) { + const uint8_t mux = pinToMux(pin); + if (mux == ADC_MUX_SOURCE_INVALID) + return ADC_MUX_SOURCE_INVALID; + + return mux; +} + +AdcEngine &AdcEngine::instance() { + static AdcEngine adcEngine; + return adcEngine; +} + +bool AdcEngine::begin() { + if (initialized_) + return true; + + if (!PendSVChannels::isAvailable(kAdcPendSvServiceId)) + return false; + + queueHead_ = 0; + queueTail_ = 0; + queueCount_ = 0; + activeChannel_ = nullptr; + activeMonitorMode_ = false; + pendingChannel_ = nullptr; + pendingCallback_ = nullptr; + pendingUserData_ = nullptr; + pendingResult_ = 0; + enabledMask_ = 0; + registeredCount_ = 0; + monitorCursor_ = 0; + pendingStartConversion_ = false; + conversionState_ = ConversionState::Idle; + + for (uint8_t i = 0; i < kResultContainerSize; ++i) { + queue_[i] = nullptr; + resultContainer_[i] = 0; + registeredChannels_[i] = nullptr; + } + + if (!PendSV::instance().registerService(kAdcPendSvServiceId, adcPendSvService, nullptr)) + return false; + + if (dma_.allocate() != DMA_STATUS_OK) { + PendSV::instance().clearService(kAdcPendSvServiceId); + return false; + } + + dma_.setTrigger(adcDmacResrdyTrigger()); + dma_.setAction(DMA_TRIGGER_ACTON_BEAT); + dma_.setCallback(AdcEngine::dmaDoneCallback, DMA_CALLBACK_TRANSFER_DONE); + auto *const adc = adcInstance(); + + dmaDescriptor_ = + dma_.addDescriptor(adcResultRegister(adc), (void *)&dmaLatestResult_, 1, + DMA_BEAT_SIZE_HWORD, false, false); + if (dmaDescriptor_ == nullptr) { + dma_.free(); + PendSV::instance().clearService(kAdcPendSvServiceId); + return false; + } + + for (uint8_t i = 0; i < adcIrqCount(); ++i) { + IRQn_Type irq = adcIrqAt(i); + NVIC_DisableIRQ(irq); + NVIC_ClearPendingIRQ(irq); + NVIC_SetPriority(irq, kAdcNvicPriority); + NVIC_EnableIRQ(irq); + } + + adcDisableInterrupts(adc, 0x0F); +#ifdef ADC_HAS_SAME53_E54_REGISTERS + adcClearFlags(adc, ADC_INTFLAG_RESRDY_Msk | ADC_INTFLAG_WINMON_Msk); +#else + adcClearFlags(adc, ADC_INTFLAG_RESRDY | ADC_INTFLAG_WINMON); +#endif // ADC_HAS_SAME53_E54_REGISTERS + initialized_ = true; + return true; +} + +void AdcEngine::end() { + if (!initialized_) + return; + + for (uint8_t i = 0; i < adcIrqCount(); ++i) { + IRQn_Type irq = adcIrqAt(i); + NVIC_DisableIRQ(irq); + NVIC_ClearPendingIRQ(irq); + } + auto *const adc = adcInstance(); + + adcDisableResultReady(adc); + + if (dmaActive_) + dma_.abort(); + + dma_.free(); + dmaDescriptor_ = nullptr; + + adcSoftwareReset(adc); + while (adcSoftwareResetBusy(adc)) + ; + waitAdcSync(); + + PendSV::instance().clearService(kAdcPendSvServiceId); + + initialized_ = false; + dmaActive_ = false; + pendingStartConversion_ = false; + conversionState_ = ConversionState::Idle; + activeChannel_ = nullptr; + activeMonitorMode_ = false; + pendingChannel_ = nullptr; + pendingCallback_ = nullptr; + pendingUserData_ = nullptr; + pendingResult_ = 0; + enabledMask_ = 0; + queueHead_ = 0; + queueTail_ = 0; + queueCount_ = 0; + registeredCount_ = 0; + monitorCursor_ = 0; + + for (uint8_t i = 0; i < kResultContainerSize; ++i) + registeredChannels_[i] = nullptr; +} + +bool AdcEngine::enqueue(ChannelADC *channel) { + if (!initialized_ || channel == nullptr) + return false; + + const int8_t index = muxPosToResultIndex(channel->muxPos_); + if (index < 0 || channel->enqueued_) + return false; + + if (!pushQueue(channel)) + return false; + + channel->enqueued_ = true; + enabledMask_ |= (1u << channel->muxPos_); + + if (activeChannel_ == nullptr) { + ChannelADC *next = popQueue(); + if (next != nullptr && !applyChannelAndStart(next)) { + next->enqueued_ = false; + enabledMask_ &= ~(1u << next->muxPos_); + return false; + } + } + + return true; +} + +bool AdcEngine::registerChannel(ChannelADC *channel) { + if (channel == nullptr) + return false; + + for (uint8_t i = 0; i < registeredCount_; ++i) { + if (registeredChannels_[i] == channel) + return true; + } + + if (registeredCount_ >= kResultContainerSize) + return false; + + registeredChannels_[registeredCount_++] = channel; + return true; +} + +void AdcEngine::unregisterChannel(ChannelADC *channel) { + if (channel == nullptr || registeredCount_ == 0) + return; + + for (uint8_t i = 0; i < registeredCount_; ++i) { + if (registeredChannels_[i] != channel) + continue; + + for (uint8_t j = i; j + 1 < registeredCount_; ++j) + registeredChannels_[j] = registeredChannels_[j + 1]; + + registeredChannels_[registeredCount_ - 1] = nullptr; + --registeredCount_; + if (monitorCursor_ >= registeredCount_) + monitorCursor_ = 0; + return; + } +} + +void AdcEngine::service() { + if (!initialized_) + return; + + onPendSv(); + + if (activeChannel_ == nullptr) { + ChannelADC *next = popQueue(); + if (next != nullptr && !applyChannelAndStart(next)) { + next->enqueued_ = false; + enabledMask_ &= ~(1u << next->muxPos_); + } + } + + if (activeChannel_ == nullptr && queueCount_ == 0) + startMonitorIfIdle(); +} + +uint8_t AdcEngine::resultByteForMux(uint8_t muxPos) const { + const int8_t index = muxPosToResultIndex(muxPos); + if (index < 0) + return 0; + + return resultContainer_[index]; +} + +uint32_t AdcEngine::enabledMask() const { + return enabledMask_; +} + +void AdcEngine::onResrdyIsr() { + auto *const adc = adcInstance(); + const uint8_t flags = adcReadFlags(adc); + + if (adcFlagWinmon(flags)) { + ChannelADC *channel = activeChannel_; + if (channel != nullptr && channel->windowEnabled_ && + channel->onWindowMonitor_ != nullptr) { + const uint16_t result = adcReadResult(adc); + channel->onWindowMonitor_(channel, result, channel->windowUserData_); + } + } + + if (adcFlagResrdy(flags)) { + if (conversionState_ == ConversionState::Discarding) { + (void)adcReadResult(adc); + adcDisableResultReady(adc); + pendingStartConversion_ = true; + pendSvPending_ = true; + PendSV::instance().setPending(kAdcPendSvServiceId); + } else { +#ifdef ADC_HAS_SAME53_E54_REGISTERS + adcClearFlags(adc, ADC_INTFLAG_RESRDY_Msk); +#else + adcClearFlags(adc, ADC_INTFLAG_RESRDY); +#endif // ADC_HAS_SAME53_E54_REGISTERS + } + } + + adcClearFlags(adc, 0x0F); +} + +void AdcEngine::onPendSv() { + if (!pendSvPending_) + return; + + pendSvPending_ = false; + + if (pendingStartConversion_) { + pendingStartConversion_ = false; + if (!startActiveDmaConversion()) { + ChannelADC *channel = activeChannel_; + if (channel != nullptr) { + channel->enqueued_ = false; + enabledMask_ &= ~(1u << channel->muxPos_); + } + activeChannel_ = nullptr; + activeMonitorMode_ = false; + conversionState_ = ConversionState::Idle; + } + } + + if (pendingCallback_ != nullptr && pendingChannel_ != nullptr) + pendingCallback_(pendingChannel_, pendingResult_, pendingUserData_); + + pendingCallback_ = nullptr; + pendingChannel_ = nullptr; + pendingUserData_ = nullptr; + pendingResult_ = 0; +} + +int8_t AdcEngine::muxPosToResultIndex(uint8_t muxPos) { + if (muxPos > kMuxMax) + return -1; + + if (muxPos >= kSkippedMuxStart && muxPos <= kSkippedMuxEnd) + return -1; + + if (muxPos < kSkippedMuxStart) + return static_cast(muxPos); + + return static_cast(muxPos - (kSkippedMuxEnd - kSkippedMuxStart + 1)); +} + +bool AdcEngine::pushQueue(ChannelADC *channel) { + if (queueCount_ >= kResultContainerSize) + return false; + + queue_[queueTail_] = channel; + queueTail_ = static_cast((queueTail_ + 1) % kResultContainerSize); + ++queueCount_; + return true; +} + +ChannelADC *AdcEngine::popQueue() { + if (queueCount_ == 0) + return nullptr; + + ChannelADC *channel = queue_[queueHead_]; + queue_[queueHead_] = nullptr; + queueHead_ = static_cast((queueHead_ + 1) % kResultContainerSize); + --queueCount_; + return channel; +} + +bool AdcEngine::applyChannelAndStart(ChannelADC *channel, bool monitorMode) { + if (channel == nullptr) + return false; + + auto *const adc = adcInstance(); + + adcSetEnable(adc, false); + waitAdcSync(); + + const uint8_t refctrlReg = static_cast(ADC_REFCTRL_REFSEL(channel->refSel_)); + adcWriteRefctrl(adc, refctrlReg); + +#ifdef ADC_HAS_D5X_E5X_REGISTERS + configureInternalReference(channel->refSel_); + configureTemperatureSensor(channel->muxPos_); + + const uint16_t inputCtrlReg = static_cast( + ADC_INPUTCTRL_MUXPOS(channel->muxPos_) | ADC_INPUTCTRL_MUXNEG(channel->muxNeg_) | + (channel->differentialMode_ ? ADC_INPUTCTRL_DIFFMODE_Msk : 0u)); + adcWriteInputctrl(adc, inputCtrlReg); + + const uint8_t avgCtrlReg = + static_cast(ADC_AVGCTRL_SAMPLENUM(sampleNumToCode(channel->sampleNum_)) | + ADC_AVGCTRL_ADJRES(channel->adjres_)); + adcWriteAvgctrl(adc, avgCtrlReg); + adcWriteSampctrl(adc, sampleTimeForMux(channel->muxPos_)); + + uint16_t ctrlaReg = adcReadCtrla(adc); + ctrlaReg = + static_cast((ctrlaReg & ~ADC_CTRLA_PRESCALER_Msk) | + ADC_CTRLA_PRESCALER(static_cast(channel->prescaler_))); + adcWriteCtrla(adc, ctrlaReg); + + const uint16_t ctrlbReg = static_cast( + (channel->leftAdjust_ ? ADC_CTRLB_LEFTADJ_Msk : 0u) | + (channel->freeRun_ ? ADC_CTRLB_FREERUN_Msk : 0u) | + (channel->corrEnabled_ ? ADC_CTRLB_CORREN_Msk : 0u) | + ADC_CTRLB_RESSEL(static_cast(channel->ressel_)) | + ADC_CTRLB_WINMODE(channel->windowEnabled_ + ? static_cast(channel->winMode_) + : static_cast(AdcWinMode::ADC_WINMODE_DISABLE))); + adcWriteCtrlb(adc, ctrlbReg); + + const uint8_t evctrlReg = + static_cast((channel->evWinmonEo_ ? ADC_EVCTRL_WINMONEO_Msk : 0u) | + (channel->evResrdyEo_ ? ADC_EVCTRL_RESRDYEO_Msk : 0u) | + (channel->evSyncei_ ? kAdcEvctrlSynceiBit : 0u) | + (channel->evStartei_ ? ADC_EVCTRL_STARTEI_Msk : 0u)); + adcWriteEvctrl(adc, evctrlReg); + + adcWriteWinlt(adc, channel->windowLower_); + adcWriteWinut(adc, channel->windowUpper_); + + adcWriteOffsetcorr(adc, static_cast(channel->offsetCorr_)); + adcWriteGaincorr(adc, channel->gainCorr_); +#else + const uint32_t inputCtrlReg = static_cast( + ADC_INPUTCTRL_MUXPOS(channel->muxPos_) | ADC_INPUTCTRL_MUXNEG(channel->muxNeg_) | + ADC_INPUTCTRL_GAIN(static_cast(channel->gain_))); + adcWriteInputctrl(adc, inputCtrlReg); + + const uint8_t avgCtrlReg = + static_cast(ADC_AVGCTRL_SAMPLENUM(sampleNumToCode(channel->sampleNum_)) | + ADC_AVGCTRL_ADJRES(channel->adjres_)); + adcWriteAvgctrl(adc, avgCtrlReg); + + const uint16_t ctrlbReg = + static_cast((channel->differentialMode_ ? ADC_CTRLB_DIFFMODE : 0u) | + (channel->leftAdjust_ ? ADC_CTRLB_LEFTADJ : 0u) | + (channel->freeRun_ ? ADC_CTRLB_FREERUN : 0u) | + (channel->corrEnabled_ ? ADC_CTRLB_CORREN : 0u) | + ADC_CTRLB_RESSEL(static_cast(channel->ressel_)) | + ADC_CTRLB_PRESCALER(static_cast(channel->prescaler_))); + adcWriteCtrlb(adc, ctrlbReg); + + const uint8_t evctrlReg = + static_cast((channel->evWinmonEo_ ? ADC_EVCTRL_WINMONEO : 0u) | + (channel->evResrdyEo_ ? ADC_EVCTRL_RESRDYEO : 0u) | + (channel->evSyncei_ ? kAdcEvctrlSynceiBit : 0u) | + (channel->evStartei_ ? ADC_EVCTRL_STARTEI : 0u)); + adcWriteEvctrl(adc, evctrlReg); + + adcWriteWinlt(adc, channel->windowLower_); + adcWriteWinut(adc, channel->windowUpper_); + + const uint8_t winctrlReg = static_cast( + ADC_WINCTRL_WINMODE(channel->windowEnabled_ ? static_cast(channel->winMode_) : 0)); + adc->WINCTRL.reg = winctrlReg; + + adcWriteOffsetcorr(adc, static_cast(channel->offsetCorr_)); + adcWriteGaincorr(adc, channel->gainCorr_); +#endif + + waitAdcSync(); + + adcSetEnable(adc, true); + waitAdcSync(); + + adcDisableInterrupts(adc, 0x0F); + adcClearFlags(adc, 0x0F); + +#ifdef ADC_HAS_SAME53_E54_REGISTERS + const uint8_t intensetReg = + static_cast(ADC_INTENSET_RESRDY_Msk | + ((monitorMode && channel->windowEnabled_) ? ADC_INTENSET_WINMON_Msk : 0u)); +#else + const uint8_t intensetReg = + static_cast(ADC_INTENSET_RESRDY | + ((monitorMode && channel->windowEnabled_) ? ADC_INTENSET_WINMON : 0u)); +#endif // ADC_HAS_SAME53_E54_REGISTERS + adcEnableInterrupts(adc, intensetReg); + + adcClearFlags(adc, 0x0F); + + activeChannel_ = channel; + activeMonitorMode_ = monitorMode; + conversionState_ = ConversionState::Discarding; + + startConversion(); + return true; +} + +bool AdcEngine::startActiveDmaConversion() { + ChannelADC *channel = activeChannel_; + if (channel == nullptr) + return false; + + auto *const adc = adcInstance(); + adcDisableResultReady(adc); + adcClearFlags(adc, 0x0F); + + if (dmaDescriptor_ == nullptr) + return false; + + dma_.changeDescriptor(dmaDescriptor_, adcResultRegister(adc), + (void *)&dmaLatestResult_, 1); + if (dma_.startJob() != DMA_STATUS_OK) + return false; + + dmaActive_ = true; + conversionState_ = ConversionState::Sampling; + + startConversion(); + return true; +} + +bool AdcEngine::startMonitorIfIdle() { + if (!initialized_ || activeChannel_ != nullptr || registeredCount_ == 0) + return false; + + for (uint8_t i = 0; i < registeredCount_; ++i) { + const uint8_t idx = static_cast((monitorCursor_ + i) % registeredCount_); + ChannelADC *candidate = registeredChannels_[idx]; + if (candidate == nullptr || !candidate->initialized_ || !candidate->monitorConfigured()) + continue; + + monitorCursor_ = static_cast((idx + 1) % registeredCount_); + return applyChannelAndStart(candidate, true); + } + + return false; +} + +void AdcEngine::waitAdcSync() const { + auto *const adc = adcInstance(); +#ifdef ADC_HAS_D5X_E5X_REGISTERS + while ( +#ifdef ADC_HAS_SAME53_E54_REGISTERS + adc->ADC_SYNCBUSY +#else + adc->SYNCBUSY.reg +#endif // ADC_HAS_SAME53_E54_REGISTERS + ) + ; +#else + while (adc->STATUS.bit.SYNCBUSY) + ; +#endif +} + +void AdcEngine::dmaDoneCallback(Adafruit_ZeroDMA *dma) { + (void)dma; + + AdcEngine &engine = AdcEngine::instance(); + engine.dmaActive_ = false; + engine.conversionState_ = ConversionState::Idle; + + ChannelADC *channel = engine.activeChannel_; + if (channel == nullptr) + return; + + const uint16_t result = engine.dmaLatestResult_; + const int8_t resultIndex = muxPosToResultIndex(channel->muxPos_); + if (resultIndex >= 0) + engine.resultContainer_[resultIndex] = static_cast(result & 0xFF); + + channel->value_ = result; + channel->hasFreshValue_ = true; + + if (engine.activeMonitorMode_) { + engine.activeChannel_ = nullptr; + engine.activeMonitorMode_ = false; + return; + } + + channel->enqueued_ = false; + engine.enabledMask_ &= ~(1u << channel->muxPos_); + + engine.pendingChannel_ = channel; + engine.pendingResult_ = result; + engine.pendingCallback_ = channel->onReadComplete_; + engine.pendingUserData_ = channel->userData_; + engine.activeChannel_ = nullptr; + engine.activeMonitorMode_ = false; + + engine.pendSvPending_ = true; + PendSV::instance().setPending(kAdcPendSvServiceId); +} + +bool ChannelADC::setAttachedSources(uint8_t muxPos, uint8_t muxNeg, AdcSampleNum sampleNum) { + if (muxPos == ADC_MUX_SOURCE_INVALID || muxNeg == ADC_MUX_SOURCE_INVALID) + return false; + + muxPos_ = muxPos; + muxNeg_ = muxNeg; + if (muxNeg_ != static_cast(AdcMuxNeg::ADC_MUXNEG_GND)) + differentialMode_ = true; + sampleNum_ = sampleNum; + adjres_ = sampleNumCodeToAdjres(sampleNumToCode(sampleNum)); + attached_ = true; + return true; +} + +bool ChannelADC::attach(uint8_t pinPos, uint8_t pinNeg, AdcSampleNum sampleNum) { + AdcEngine &engine = AdcEngine::instance(); + if (!engine.begin()) + return false; + + if (!registered_ && !engine.registerChannel(this)) + return false; + + configureAnalogPin(pinPos); + configureAnalogPin(pinNeg); + + registered_ = true; + initialized_ = true; + return setAttachedSources(ADC_MUXPOS_PIN(pinPos), ADC_MUXNEG_PIN(pinNeg), sampleNum); +} + +bool ChannelADC::attach(uint8_t pinPos, AdcMuxNeg muxNeg, AdcSampleNum sampleNum) { + AdcEngine &engine = AdcEngine::instance(); + if (!engine.begin()) + return false; + + if (!registered_ && !engine.registerChannel(this)) + return false; + + configureAnalogPin(pinPos); + + registered_ = true; + initialized_ = true; + return setAttachedSources(ADC_MUXPOS_PIN(pinPos), static_cast(muxNeg), sampleNum); +} + +bool ChannelADC::attach(AdcMuxPos muxPos, uint8_t pinNeg, AdcSampleNum sampleNum) { + AdcEngine &engine = AdcEngine::instance(); + if (!engine.begin()) + return false; + + if (!registered_ && !engine.registerChannel(this)) + return false; + + configureAnalogPin(pinNeg); + + registered_ = true; + initialized_ = true; + return setAttachedSources(static_cast(muxPos), ADC_MUXNEG_PIN(pinNeg), sampleNum); +} + +bool ChannelADC::attach(AdcMuxPos muxPos, AdcMuxNeg muxNeg, AdcSampleNum sampleNum) { + AdcEngine &engine = AdcEngine::instance(); + if (!engine.begin()) + return false; + + if (!registered_ && !engine.registerChannel(this)) + return false; + + registered_ = true; + initialized_ = true; + return setAttachedSources(static_cast(muxPos), static_cast(muxNeg), + sampleNum); +} + +void ChannelADC::setWindow(AdcWinMode mode, uint16_t lower, uint16_t upper) { + windowLower_ = lower; + windowUpper_ = upper; + winMode_ = mode; + windowEnabled_ = (mode != AdcWinMode::ADC_WINMODE_DISABLE); +} + +void ChannelADC::clearWindow() { + windowEnabled_ = false; + winMode_ = AdcWinMode::ADC_WINMODE_DISABLE; +} + +bool ChannelADC::windowEnabled() const { + return windowEnabled_; +} + +void ChannelADC::setWindowCallback(Callback callback, void *userData) { + onWindowMonitor_ = callback; + windowUserData_ = (userData != nullptr) ? userData : this; +} + +void ChannelADC::setReadCallback(Callback callback, void *userData) { + onReadComplete_ = callback; + userData_ = userData; +} + +void ChannelADC::setEventControl(bool winmonEo, bool resrdyEo, bool syncei, bool startei) { + evWinmonEo_ = winmonEo; + evResrdyEo_ = resrdyEo; + evSyncei_ = syncei; + evStartei_ = startei; +} + +void ChannelADC::setCtrlB(AdcResSel resolution, AdcPrescaler prescaler, bool freeRun, + bool leftAdjust, bool differentialMode, bool correctionEnable) { + ressel_ = resolution; + prescaler_ = prescaler; + freeRun_ = freeRun; + leftAdjust_ = leftAdjust; + differentialMode_ = differentialMode; + corrEnabled_ = correctionEnable; +} + +void ChannelADC::setReference(AdcRefSel reference) { + refSel_ = static_cast(reference); +} + +void ChannelADC::setGain(AdcGain gain) { + gain_ = gain; +} + +void ChannelADC::setCalibration(uint16_t gainCorr, int16_t offsetCorr, bool enableCorrection) { + gainCorr_ = gainCorr; + offsetCorr_ = offsetCorr; + corrEnabled_ = enableCorrection; +} + +bool ChannelADC::monitorConfigured() const { + return windowEnabled_; +} + +void ChannelADC::onSyncReadComplete(ChannelADC *channel, uint16_t result, void *userData) { + (void)result; + + ChannelADC *target = static_cast(userData); + if (target == nullptr) + target = channel; + + if (target != nullptr) + target->syncReadDone_ = true; +} + +bool ChannelADC::read() { + if (!initialized_ || !attached_) + return false; + + hasFreshValue_ = false; + syncReadDone_ = false; + + AdcEngine &engine = AdcEngine::instance(); + const bool syncRead = (onReadComplete_ == nullptr); + Callback savedCallback = onReadComplete_; + void *savedUserData = userData_; + + if (syncRead) { + onReadComplete_ = &ChannelADC::onSyncReadComplete; + userData_ = this; + } + + if (!engine.enqueue(this)) { + if (syncRead) { + onReadComplete_ = savedCallback; + userData_ = savedUserData; + } + return false; + } + + if (syncRead) { + const uint32_t startMs = millis(); + while (!syncReadDone_) { + engine.service(); + if ((millis() - startMs) > 50u) { + onReadComplete_ = savedCallback; + userData_ = savedUserData; + return false; + } + } + + onReadComplete_ = savedCallback; + userData_ = savedUserData; + } + + return true; +} + +uint16_t ChannelADC::value() const { + return value_; +} + +void ChannelADC::end() { + initialized_ = false; + attached_ = false; + registered_ = false; + enqueued_ = false; + hasFreshValue_ = false; + AdcEngine::instance().unregisterChannel(this); +} + +#ifdef ADC_HAS_SAME53_E54_REGISTERS +extern "C" void ADC0_OTHER_Handler(void) { + AdcEngine::instance().onResrdyIsr(); +} + +extern "C" void ADC0_RESRDY_Handler(void) { + AdcEngine::instance().onResrdyIsr(); +} +#elif defined(ADC_HAS_D5X_E5X_REGISTERS) +extern "C" void ADC0_0_Handler(void) { + AdcEngine::instance().onResrdyIsr(); +} + +extern "C" void ADC0_1_Handler(void) { + AdcEngine::instance().onResrdyIsr(); +} +#else +extern "C" void ADC_Handler(void) { + AdcEngine::instance().onResrdyIsr(); +} +#endif // ADC_HAS_SAME53_E54_REGISTERS / ADC_HAS_D5X_E5X_REGISTERS diff --git a/libraries/ADC/src/ADC.h b/libraries/ADC/src/ADC.h new file mode 100644 index 000000000..f21e32a76 --- /dev/null +++ b/libraries/ADC/src/ADC.h @@ -0,0 +1,366 @@ +#pragma once + +#include +#include + +#include + +#if defined(__SAMD51__) || defined(__SAME51__) || defined(__SAME53__) || defined(__SAME54__) +#define ADC_HAS_D5X_E5X_REGISTERS +#endif + +#if defined(__SAME53__) || defined(__SAME54__) || defined(ARDUINO_SAME53_E54) +#define ADC_HAS_SAME53_E54_REGISTERS +#endif + +static constexpr uint8_t ADC_MUX_SOURCE_INVALID = 0xFF; + +void analogReadCorrection(int offset, uint16_t gain); +#ifdef ADC_HAS_D5X_E5X_REGISTERS +float analogReadTemperatureC(uint16_t ptatReading, uint16_t ctatReading); +#else +float analogReadTemperatureC(uint16_t adcReading); +#endif + +enum class AdcSampleNum : uint8_t { + ADC_SAMPLENUM_1 = ADC_AVGCTRL_SAMPLENUM_1_Val, + ADC_SAMPLENUM_2 = ADC_AVGCTRL_SAMPLENUM_2_Val, + ADC_SAMPLENUM_4 = ADC_AVGCTRL_SAMPLENUM_4_Val, + ADC_SAMPLENUM_8 = ADC_AVGCTRL_SAMPLENUM_8_Val, + ADC_SAMPLENUM_16 = ADC_AVGCTRL_SAMPLENUM_16_Val, + ADC_SAMPLENUM_32 = ADC_AVGCTRL_SAMPLENUM_32_Val, + ADC_SAMPLENUM_64 = ADC_AVGCTRL_SAMPLENUM_64_Val, + ADC_SAMPLENUM_128 = ADC_AVGCTRL_SAMPLENUM_128_Val, + ADC_SAMPLENUM_256 = ADC_AVGCTRL_SAMPLENUM_256_Val, + ADC_SAMPLENUM_512 = ADC_AVGCTRL_SAMPLENUM_512_Val, + ADC_SAMPLENUM_1024 = ADC_AVGCTRL_SAMPLENUM_1024_Val, +}; + +enum class AdcWinMode : uint8_t { +#ifdef ADC_HAS_D5X_E5X_REGISTERS + ADC_WINMODE_DISABLE = ADC_CTRLB_WINMODE_DISABLE_Val, ///< No window mode (disabled) + ADC_WINMODE_MODE1 = ADC_CTRLB_WINMODE_MODE1_Val, ///< Window mode 1: RESULT > WINLT. + ADC_WINMODE_MODE2 = ADC_CTRLB_WINMODE_MODE2_Val, ///< Window mode 2: RESULT < WINUT. + ADC_WINMODE_MODE3 = ADC_CTRLB_WINMODE_MODE3_Val, ///< Window mode 3: WINLT < RESULT < WINUT. + ADC_WINMODE_MODE4 = ADC_CTRLB_WINMODE_MODE4_Val, ///< Window mode 4: !(WINLT < RESULT < WINUT). +#else + ADC_WINMODE_DISABLE = ADC_WINCTRL_WINMODE_DISABLE_Val, ///< No window mode (disabled) + ADC_WINMODE_MODE1 = ADC_WINCTRL_WINMODE_MODE1_Val, ///< Window mode 1: RESULT > WINLT. + ADC_WINMODE_MODE2 = ADC_WINCTRL_WINMODE_MODE2_Val, ///< Window mode 2: RESULT < WINUT. + ADC_WINMODE_MODE3 = ADC_WINCTRL_WINMODE_MODE3_Val, ///< Window mode 3: WINLT < RESULT < WINUT. + ADC_WINMODE_MODE4 = + ADC_WINCTRL_WINMODE_MODE4_Val, ///< Window mode 4: !(WINLT < RESULT < WINUT). +#endif +}; + +enum class AdcRefSel : uint8_t { +#ifdef ADC_HAS_D5X_E5X_REGISTERS + ADC_REFSEL_INT1V = ADC_REFCTRL_REFSEL_INTREF_Val, +#else + ADC_REFSEL_INT1V = ADC_REFCTRL_REFSEL_INT1V_Val, +#endif + ADC_REFSEL_INTVCC0 = ADC_REFCTRL_REFSEL_INTVCC0_Val, + ADC_REFSEL_INTVCC1 = ADC_REFCTRL_REFSEL_INTVCC1_Val, + ADC_REFSEL_AREFA = ADC_REFCTRL_REFSEL_AREFA_Val, + ADC_REFSEL_AREFB = ADC_REFCTRL_REFSEL_AREFB_Val, +}; + +enum class AdcGain : uint8_t { +#ifdef ADC_HAS_D5X_E5X_REGISTERS + ADC_GAIN_1X = 0x0, + ADC_GAIN_2X = 0x1, + ADC_GAIN_4X = 0x2, + ADC_GAIN_8X = 0x3, + ADC_GAIN_16X = 0x4, + ADC_GAIN_1_DIV_2 = 0xF, +#else + ADC_GAIN_1X = ADC_INPUTCTRL_GAIN_1X_Val, + ADC_GAIN_2X = ADC_INPUTCTRL_GAIN_2X_Val, + ADC_GAIN_4X = ADC_INPUTCTRL_GAIN_4X_Val, + ADC_GAIN_8X = ADC_INPUTCTRL_GAIN_8X_Val, + ADC_GAIN_16X = ADC_INPUTCTRL_GAIN_16X_Val, + ADC_GAIN_1_DIV_2 = ADC_INPUTCTRL_GAIN_DIV2_Val, +#endif +}; + +enum class AdcResSel : uint8_t { + ADC_RESSEL_12BIT = ADC_CTRLB_RESSEL_12BIT_Val, + ADC_RESSEL_16BIT = ADC_CTRLB_RESSEL_16BIT_Val, + ADC_RESSEL_10BIT = ADC_CTRLB_RESSEL_10BIT_Val, + ADC_RESSEL_8BIT = ADC_CTRLB_RESSEL_8BIT_Val, +}; + +enum class AdcPrescaler : uint8_t { +#ifdef ADC_HAS_D5X_E5X_REGISTERS + ADC_PRESCALER_DIV4 = ADC_CTRLA_PRESCALER_DIV4_Val, + ADC_PRESCALER_DIV8 = ADC_CTRLA_PRESCALER_DIV8_Val, + ADC_PRESCALER_DIV16 = ADC_CTRLA_PRESCALER_DIV16_Val, + ADC_PRESCALER_DIV32 = ADC_CTRLA_PRESCALER_DIV32_Val, + ADC_PRESCALER_DIV64 = ADC_CTRLA_PRESCALER_DIV64_Val, + ADC_PRESCALER_DIV128 = ADC_CTRLA_PRESCALER_DIV128_Val, + ADC_PRESCALER_DIV256 = ADC_CTRLA_PRESCALER_DIV256_Val, +#else + ADC_PRESCALER_DIV4 = ADC_CTRLB_PRESCALER_DIV4_Val, + ADC_PRESCALER_DIV8 = ADC_CTRLB_PRESCALER_DIV8_Val, + ADC_PRESCALER_DIV16 = ADC_CTRLB_PRESCALER_DIV16_Val, + ADC_PRESCALER_DIV32 = ADC_CTRLB_PRESCALER_DIV32_Val, + ADC_PRESCALER_DIV64 = ADC_CTRLB_PRESCALER_DIV64_Val, + ADC_PRESCALER_DIV128 = ADC_CTRLB_PRESCALER_DIV128_Val, + ADC_PRESCALER_DIV256 = ADC_CTRLB_PRESCALER_DIV256_Val, + ADC_PRESCALER_DIV512 = ADC_CTRLB_PRESCALER_DIV512_Val, +#endif +}; + +enum class AdcMuxPos : uint8_t { +#ifdef ADC_HAS_D5X_E5X_REGISTERS + ADC_MUXPOS_PIN0 = ADC_INPUTCTRL_MUXPOS_AIN0_Val, + ADC_MUXPOS_PIN1 = ADC_INPUTCTRL_MUXPOS_AIN1_Val, + ADC_MUXPOS_PIN2 = ADC_INPUTCTRL_MUXPOS_AIN2_Val, + ADC_MUXPOS_PIN3 = ADC_INPUTCTRL_MUXPOS_AIN3_Val, + ADC_MUXPOS_PIN4 = ADC_INPUTCTRL_MUXPOS_AIN4_Val, + ADC_MUXPOS_PIN5 = ADC_INPUTCTRL_MUXPOS_AIN5_Val, + ADC_MUXPOS_PIN6 = ADC_INPUTCTRL_MUXPOS_AIN6_Val, + ADC_MUXPOS_PIN7 = ADC_INPUTCTRL_MUXPOS_AIN7_Val, + ADC_MUXPOS_PIN8 = ADC_INPUTCTRL_MUXPOS_AIN8_Val, + ADC_MUXPOS_PIN9 = ADC_INPUTCTRL_MUXPOS_AIN9_Val, + ADC_MUXPOS_PIN10 = ADC_INPUTCTRL_MUXPOS_AIN10_Val, + ADC_MUXPOS_PIN11 = ADC_INPUTCTRL_MUXPOS_AIN11_Val, + ADC_MUXPOS_PIN12 = ADC_INPUTCTRL_MUXPOS_AIN12_Val, + ADC_MUXPOS_PIN13 = ADC_INPUTCTRL_MUXPOS_AIN13_Val, + ADC_MUXPOS_PIN14 = ADC_INPUTCTRL_MUXPOS_AIN14_Val, + ADC_MUXPOS_PIN15 = ADC_INPUTCTRL_MUXPOS_AIN15_Val, +#ifdef ADC_INPUTCTRL_MUXPOS_AIN16_Val + ADC_MUXPOS_PIN16 = ADC_INPUTCTRL_MUXPOS_AIN16_Val, +#endif // ADC_INPUTCTRL_MUXPOS_AIN16_Val +#ifdef ADC_INPUTCTRL_MUXPOS_AIN17_Val + ADC_MUXPOS_PIN17 = ADC_INPUTCTRL_MUXPOS_AIN17_Val, +#endif // ADC_INPUTCTRL_MUXPOS_AIN17_Val +#ifdef ADC_INPUTCTRL_MUXPOS_AIN18_Val + ADC_MUXPOS_PIN18 = ADC_INPUTCTRL_MUXPOS_AIN18_Val, +#endif // ADC_INPUTCTRL_MUXPOS_AIN18_Val +#ifdef ADC_INPUTCTRL_MUXPOS_AIN19_Val + ADC_MUXPOS_PIN19 = ADC_INPUTCTRL_MUXPOS_AIN19_Val, +#endif // ADC_INPUTCTRL_MUXPOS_AIN19_Val + ADC_MUXPOS_TEMP = ADC_INPUTCTRL_MUXPOS_PTAT_Val, + ADC_MUXPOS_TEMP_CTAT = ADC_INPUTCTRL_MUXPOS_CTAT_Val, +#else + ADC_MUXPOS_PIN0 = ADC_INPUTCTRL_MUXPOS_PIN0_Val, + ADC_MUXPOS_PIN1 = ADC_INPUTCTRL_MUXPOS_PIN1_Val, + ADC_MUXPOS_PIN2 = ADC_INPUTCTRL_MUXPOS_PIN2_Val, + ADC_MUXPOS_PIN3 = ADC_INPUTCTRL_MUXPOS_PIN3_Val, + ADC_MUXPOS_PIN4 = ADC_INPUTCTRL_MUXPOS_PIN4_Val, + ADC_MUXPOS_PIN5 = ADC_INPUTCTRL_MUXPOS_PIN5_Val, + ADC_MUXPOS_PIN6 = ADC_INPUTCTRL_MUXPOS_PIN6_Val, + ADC_MUXPOS_PIN7 = ADC_INPUTCTRL_MUXPOS_PIN7_Val, + ADC_MUXPOS_PIN8 = ADC_INPUTCTRL_MUXPOS_PIN8_Val, + ADC_MUXPOS_PIN9 = ADC_INPUTCTRL_MUXPOS_PIN9_Val, + ADC_MUXPOS_PIN10 = ADC_INPUTCTRL_MUXPOS_PIN10_Val, + ADC_MUXPOS_PIN11 = ADC_INPUTCTRL_MUXPOS_PIN11_Val, + ADC_MUXPOS_PIN12 = ADC_INPUTCTRL_MUXPOS_PIN12_Val, + ADC_MUXPOS_PIN13 = ADC_INPUTCTRL_MUXPOS_PIN13_Val, + ADC_MUXPOS_PIN14 = ADC_INPUTCTRL_MUXPOS_PIN14_Val, + ADC_MUXPOS_PIN15 = ADC_INPUTCTRL_MUXPOS_PIN15_Val, + ADC_MUXPOS_PIN16 = ADC_INPUTCTRL_MUXPOS_PIN16_Val, + ADC_MUXPOS_PIN17 = ADC_INPUTCTRL_MUXPOS_PIN17_Val, + ADC_MUXPOS_PIN18 = ADC_INPUTCTRL_MUXPOS_PIN18_Val, + ADC_MUXPOS_PIN19 = ADC_INPUTCTRL_MUXPOS_PIN19_Val, + ADC_MUXPOS_TEMP = ADC_INPUTCTRL_MUXPOS_TEMP_Val, +#endif + ADC_MUXPOS_BANDGAP = ADC_INPUTCTRL_MUXPOS_BANDGAP_Val, + ADC_MUXPOS_SCALEDCOREVCC = ADC_INPUTCTRL_MUXPOS_SCALEDCOREVCC_Val, + ADC_MUXPOS_SCALEDIOVCC = ADC_INPUTCTRL_MUXPOS_SCALEDIOVCC_Val, + ADC_MUXPOS_DAC = ADC_INPUTCTRL_MUXPOS_DAC_Val, +}; + +enum class AdcMuxNeg : uint8_t { +#ifdef ADC_HAS_D5X_E5X_REGISTERS + ADC_MUXNEG_PIN0 = ADC_INPUTCTRL_MUXNEG_AIN0_Val, + ADC_MUXNEG_PIN1 = ADC_INPUTCTRL_MUXNEG_AIN1_Val, + ADC_MUXNEG_PIN2 = ADC_INPUTCTRL_MUXNEG_AIN2_Val, + ADC_MUXNEG_PIN3 = ADC_INPUTCTRL_MUXNEG_AIN3_Val, + ADC_MUXNEG_PIN4 = ADC_INPUTCTRL_MUXNEG_AIN4_Val, + ADC_MUXNEG_PIN5 = ADC_INPUTCTRL_MUXNEG_AIN5_Val, + ADC_MUXNEG_PIN6 = ADC_INPUTCTRL_MUXNEG_AIN6_Val, + ADC_MUXNEG_PIN7 = ADC_INPUTCTRL_MUXNEG_AIN7_Val, + ADC_MUXNEG_IOGND = 0x19, +#else + ADC_MUXNEG_PIN0 = ADC_INPUTCTRL_MUXNEG_PIN0_Val, + ADC_MUXNEG_PIN1 = ADC_INPUTCTRL_MUXNEG_PIN1_Val, + ADC_MUXNEG_PIN2 = ADC_INPUTCTRL_MUXNEG_PIN2_Val, + ADC_MUXNEG_PIN3 = ADC_INPUTCTRL_MUXNEG_PIN3_Val, + ADC_MUXNEG_PIN4 = ADC_INPUTCTRL_MUXNEG_PIN4_Val, + ADC_MUXNEG_PIN5 = ADC_INPUTCTRL_MUXNEG_PIN5_Val, + ADC_MUXNEG_PIN6 = ADC_INPUTCTRL_MUXNEG_PIN6_Val, + ADC_MUXNEG_PIN7 = ADC_INPUTCTRL_MUXNEG_PIN7_Val, + ADC_MUXNEG_IOGND = ADC_INPUTCTRL_MUXNEG_IOGND_Val, +#endif + ADC_MUXNEG_GND = ADC_INPUTCTRL_MUXNEG_GND_Val, +}; + +class ChannelADC; + +class AdcEngine { + public: + static constexpr uint8_t kMuxMin = 0x00; + static constexpr uint8_t kMuxMax = 0x1D; + static constexpr uint8_t kSkippedMuxStart = 0x14; + static constexpr uint8_t kSkippedMuxEnd = 0x17; + static constexpr uint8_t kResultContainerSize = 26; + + static AdcEngine &instance(); + + bool begin(); + void end(); + + bool enqueue(ChannelADC *channel); + bool registerChannel(ChannelADC *channel); + void unregisterChannel(ChannelADC *channel); + void service(); + + uint8_t resultByteForMux(uint8_t muxPos) const; + uint32_t enabledMask() const; + + void onResrdyIsr(); + void onPendSv(); + + private: + AdcEngine() = default; + + static int8_t muxPosToResultIndex(uint8_t muxPos); + + bool pushQueue(ChannelADC *channel); + ChannelADC *popQueue(); + + bool applyChannelAndStart(ChannelADC *channel, bool monitorMode = false); + bool startMonitorIfIdle(); + bool startActiveDmaConversion(); + void waitAdcSync() const; + inline void startConversion() const { +#ifdef ADC_HAS_SAME53_E54_REGISTERS + ADC0_REGS->ADC_SWTRIG = ADC_SWTRIG_START_Msk; +#elif defined(ADC_HAS_D5X_E5X_REGISTERS) + ADC0->SWTRIG.bit.START = 1; +#else + ADC->SWTRIG.bit.START = 1; +#endif // ADC_HAS_SAME53_E54_REGISTERS / ADC_HAS_D5X_E5X_REGISTERS + } + static void dmaDoneCallback(Adafruit_ZeroDMA *dma); + + ChannelADC *queue_[kResultContainerSize]{}; + uint8_t queueHead_ = 0; + uint8_t queueTail_ = 0; + uint8_t queueCount_ = 0; + + volatile uint8_t resultContainer_[kResultContainerSize]{}; + volatile uint32_t enabledMask_ = 0; + + ChannelADC *activeChannel_ = nullptr; + bool activeMonitorMode_ = false; + + ChannelADC *registeredChannels_[kResultContainerSize]{}; + uint8_t registeredCount_ = 0; + uint8_t monitorCursor_ = 0; + + ChannelADC *pendingChannel_ = nullptr; + volatile uint16_t pendingResult_ = 0; + void (*pendingCallback_)(ChannelADC *channel, uint16_t result, void *userData) = nullptr; + void *pendingUserData_ = nullptr; + + Adafruit_ZeroDMA dma_; + DmacDescriptor *dmaDescriptor_ = nullptr; + volatile uint16_t dmaLatestResult_ = 0; + bool dmaActive_ = false; + bool initialized_ = false; + volatile bool pendSvPending_ = false; + volatile bool pendingStartConversion_ = false; + enum class ConversionState : uint8_t { + Idle, + Discarding, + Sampling, + }; + volatile ConversionState conversionState_ = ConversionState::Idle; +}; + +class ChannelADC { + public: + using Callback = void (*)(ChannelADC *channel, uint16_t result, void *userData); + + bool attach(uint8_t pinPos, uint8_t pinNeg, + AdcSampleNum sampleNum = AdcSampleNum::ADC_SAMPLENUM_16); + bool attach(uint8_t pinPos, AdcMuxNeg muxNeg = AdcMuxNeg::ADC_MUXNEG_GND, + AdcSampleNum sampleNum = AdcSampleNum::ADC_SAMPLENUM_16); + bool attach(AdcMuxPos muxPos, uint8_t pinNeg, + AdcSampleNum sampleNum = AdcSampleNum::ADC_SAMPLENUM_16); + bool attach(AdcMuxPos muxPos, AdcMuxNeg muxNeg = AdcMuxNeg::ADC_MUXNEG_GND, + AdcSampleNum sampleNum = AdcSampleNum::ADC_SAMPLENUM_16); + + bool read(); + uint16_t value() const; + + void setWindow(AdcWinMode mode, uint16_t lower, uint16_t upper); + void clearWindow(); + bool windowEnabled() const; + + void setWindowCallback(Callback callback, void *userData = nullptr); + void setReadCallback(Callback callback, void *userData = nullptr); + void setEventControl(bool winmonEo, bool resrdyEo, bool syncei, bool startei); + void setCtrlB(AdcResSel resolution, AdcPrescaler prescaler, bool freeRun = false, + bool leftAdjust = false, bool differentialMode = false, + bool correctionEnable = false); + void setReference(AdcRefSel reference); + void setGain(AdcGain gain); + void setCalibration(uint16_t gainCorr, int16_t offsetCorr, bool enableCorrection = true); + void end(); + + private: + friend class AdcEngine; + + bool setAttachedSources(uint8_t encodedPos, uint8_t encodedNeg, AdcSampleNum sampleNum); + bool monitorConfigured() const; + static void onSyncReadComplete(ChannelADC *channel, uint16_t result, void *userData); + + uint8_t muxPos_ = 0; + uint8_t muxNeg_ = 0x18; + AdcSampleNum sampleNum_ = AdcSampleNum::ADC_SAMPLENUM_16; + uint8_t adjres_ = 4; + uint8_t refSel_ = static_cast(AdcRefSel::ADC_REFSEL_INTVCC1); + AdcResSel ressel_ = AdcResSel::ADC_RESSEL_16BIT; +#ifdef ADC_HAS_D5X_E5X_REGISTERS + AdcPrescaler prescaler_ = AdcPrescaler::ADC_PRESCALER_DIV4; +#else + // SAMD21 runs GCLK_ADC at 48 MHz. DIV32 yields 1.5 MHz, within the + // datasheet ADC clock limit; DIV4 drove the converter at 12 MHz and + // produced invalid, weakly input-dependent results. + AdcPrescaler prescaler_ = AdcPrescaler::ADC_PRESCALER_DIV32; +#endif + bool freeRun_ = false; + bool leftAdjust_ = false; + bool differentialMode_ = false; + bool enqueued_ = false; + bool registered_ = false; + volatile bool syncReadDone_ = false; + Callback onReadComplete_ = nullptr; + void *userData_ = nullptr; + + bool windowEnabled_ = false; + uint16_t windowLower_ = 0; + uint16_t windowUpper_ = 0xFFFF; + AdcWinMode winMode_ = AdcWinMode::ADC_WINMODE_DISABLE; + Callback onWindowMonitor_ = nullptr; + void *windowUserData_ = nullptr; + + bool evWinmonEo_ = false; + bool evResrdyEo_ = false; + bool evSyncei_ = false; + bool evStartei_ = false; + + bool corrEnabled_ = false; + AdcGain gain_ = AdcGain::ADC_GAIN_1_DIV_2; + uint16_t gainCorr_ = 0; + int16_t offsetCorr_ = 0; + + uint16_t value_ = 0; + bool hasFreshValue_ = false; + bool attached_ = false; + bool initialized_ = false; +}; diff --git a/libraries/Adafruit_ZeroDMA b/libraries/Adafruit_ZeroDMA index 5b48867c1..5a8ec1581 160000 --- a/libraries/Adafruit_ZeroDMA +++ b/libraries/Adafruit_ZeroDMA @@ -1 +1 @@ -Subproject commit 5b48867c1d3015ec63cd8f31def45f5a2d8e49bd +Subproject commit 5a8ec158176c7c3be7313169c9c13d3c9eecb99f diff --git a/libraries/SAMD_AnalogCorrection/examples/CorrectADCResponse/CorrectADCResponse.ino b/libraries/SAMD_AnalogCorrection/examples/CorrectADCResponse/CorrectADCResponse.ino index 2b74538e1..3ea9a855c 100644 --- a/libraries/SAMD_AnalogCorrection/examples/CorrectADCResponse/CorrectADCResponse.ino +++ b/libraries/SAMD_AnalogCorrection/examples/CorrectADCResponse/CorrectADCResponse.ino @@ -21,7 +21,7 @@ - the instruction line to copy/paste in the final sketch */ -#include "SAMD_AnalogCorrection.h" +#include #define ADC_GND_PIN A1 #define ADC_3V3_PIN A2 diff --git a/libraries/SAMD_AnalogCorrection/src/SAMD_AnalogCorrection.cpp b/libraries/SAMD_AnalogCorrection/src/SAMD_AnalogCorrection.cpp deleted file mode 100644 index e39ab43e3..000000000 --- a/libraries/SAMD_AnalogCorrection/src/SAMD_AnalogCorrection.cpp +++ /dev/null @@ -1,48 +0,0 @@ -/* - Copyright (c) 2015 Arduino LLC. All right reserved. - SAMD51 support added by Adafruit - Copyright (c) 2018 Dean Miller for Adafruit Industries - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#include "SAMD_AnalogCorrection.h" - -#ifdef USE_TINYUSB -// For Serial when selecting TinyUSB -#include -#endif - -void analogReadCorrection (int offset, uint16_t gain) -{ - Adc *adc; -#if defined (__SAMD51__) -adc = ADC0; -#else -adc = ADC; -#endif - // Set correction values - adc->OFFSETCORR.reg = ADC_OFFSETCORR_OFFSETCORR(offset); - adc->GAINCORR.reg = ADC_GAINCORR_GAINCORR(gain); - - // Enable digital correction logic - adc->CTRLB.bit.CORREN = 1; - -#if defined (__SAMD51__) - while(adc->SYNCBUSY.bit.OFFSETCORR || adc->SYNCBUSY.bit.GAINCORR); -#else - while(adc->STATUS.bit.SYNCBUSY); -#endif -} - diff --git a/libraries/SAMD_AnalogCorrection/src/SAMD_AnalogCorrection.h b/libraries/SAMD_AnalogCorrection/src/SAMD_AnalogCorrection.h index 5edb91ee3..3733e3e60 100644 --- a/libraries/SAMD_AnalogCorrection/src/SAMD_AnalogCorrection.h +++ b/libraries/SAMD_AnalogCorrection/src/SAMD_AnalogCorrection.h @@ -18,7 +18,6 @@ #pragma once -#include - -void analogReadCorrection (int offset, uint16_t gain); +#include +void analogReadCorrection(int offset, uint16_t gain);