From d8e11975a36200a2e6f531009154044245971ac9 Mon Sep 17 00:00:00 2001 From: Cal Abel Date: Sat, 20 Jun 2026 16:32:53 -0400 Subject: [PATCH 1/9] Add shared PendSV deferred service dispatcher --- cores/arduino/PendSV.cpp | 116 +++++++++++++++++++++++++++++++++++++++ cores/arduino/PendSV.h | 34 ++++++++++++ 2 files changed, 150 insertions(+) create mode 100644 cores/arduino/PendSV.cpp create mode 100644 cores/arduino/PendSV.h diff --git a/cores/arduino/PendSV.cpp b/cores/arduino/PendSV.cpp new file mode 100644 index 000000000..bb5f32c7b --- /dev/null +++ b/cores/arduino/PendSV.cpp @@ -0,0 +1,116 @@ +#include "PendSV.h" + +#include +#include + +namespace { +PendSV pendSv; +} + +PendSV &PendSV::instance() { + return pendSv; +} + +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::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..ab22a43e6 --- /dev/null +++ b/cores/arduino/PendSV.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include + +class PendSV { + public: + using ServiceFn = void (*)(uint8_t serviceId, void *context); + static constexpr uint8_t kMaxServices = 32; + static constexpr uint8_t kDispatchBudget = 16; + static_assert(kMaxServices <= 32, "PendSV pending mask supports at most 32 services"); + + static PendSV &instance(); + + bool registerService(uint8_t serviceId, ServiceFn fn, void *context = nullptr); + // Cancels queued work that has not started dispatching. If a callback was + // already copied for dispatch, its context must remain valid until it returns. + void clearService(uint8_t serviceId); + void dispatchPending(); + void setPending(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; +}; From 025d90d692025f02b16f465e6c01d2f8b5395be5 Mon Sep 17 00:00:00 2001 From: Cal Abel Date: Mon, 13 Apr 2026 21:45:49 -0400 Subject: [PATCH 2/9] add support for full ADC DMA control --- .../AsyncChipTemperature.ino | 101 +++ .../ADC/examples/BasicRead/BasicRead.ino | 25 + .../CorrectADCResponse/CorrectADCResponse.ino | 201 +++++ .../examples/ReadCallback/ReadCallback.ino | 43 + .../SyncTempRegisterRead.ino | 81 ++ libraries/ADC/keywords.txt | 52 ++ libraries/ADC/library.properties | 9 + libraries/ADC/src/ADC.cpp | 816 ++++++++++++++++++ libraries/ADC/src/ADC.h | 326 +++++++ .../CorrectADCResponse/CorrectADCResponse.ino | 2 +- .../src/SAMD_AnalogCorrection.cpp | 48 -- .../src/SAMD_AnalogCorrection.h | 5 +- 12 files changed, 1656 insertions(+), 53 deletions(-) create mode 100644 libraries/ADC/examples/AsyncChipTemperature/AsyncChipTemperature.ino create mode 100644 libraries/ADC/examples/BasicRead/BasicRead.ino create mode 100644 libraries/ADC/examples/CorrectADCResponse/CorrectADCResponse.ino create mode 100644 libraries/ADC/examples/ReadCallback/ReadCallback.ino create mode 100644 libraries/ADC/examples/SyncTempRegisterRead/SyncTempRegisterRead.ino create mode 100644 libraries/ADC/keywords.txt create mode 100644 libraries/ADC/library.properties create mode 100644 libraries/ADC/src/ADC.cpp create mode 100644 libraries/ADC/src/ADC.h delete mode 100644 libraries/SAMD_AnalogCorrection/src/SAMD_AnalogCorrection.cpp diff --git a/libraries/ADC/examples/AsyncChipTemperature/AsyncChipTemperature.ino b/libraries/ADC/examples/AsyncChipTemperature/AsyncChipTemperature.ino new file mode 100644 index 000000000..97e614c1a --- /dev/null +++ b/libraries/ADC/examples/AsyncChipTemperature/AsyncChipTemperature.ino @@ -0,0 +1,101 @@ +#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 FAMILY_SAMD5X +ChannelADC g_ctat; +#endif + +void setup() { + Serial.begin(115200); + while (!Serial); + + g_temp.setReadCallback(onTempRead, nullptr); + g_temp.setReference(AdcRefSel::ADC_REFSEL_INT1V); + g_temp.setCtrlB(AdcResSel::ADC_RESSEL_12BIT, AdcPrescaler::ADC_PRESCALER_DIV32); + +#ifdef FAMILY_SAMD5X + g_ctat.setReadCallback(onTempRead, nullptr); + g_ctat.setReference(AdcRefSel::ADC_REFSEL_INT1V); + g_ctat.setCtrlB(AdcResSel::ADC_RESSEL_12BIT, AdcPrescaler::ADC_PRESCALER_DIV32); + + if (!g_temp.attach(AdcMuxPos::ADC_MUXPOS_TEMP, AdcMuxNeg::ADC_MUXNEG_IOGND, + AdcSampleNum::ADC_SAMPLENUM_16)) { + Serial.println("Attach PTAT failed"); + } + + if (!g_ctat.attach(AdcMuxPos::ADC_MUXPOS_TEMP_CTAT, AdcMuxNeg::ADC_MUXNEG_IOGND, + 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 FAMILY_SAMD5X + 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("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..214b2c533 --- /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 (uint16_t 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 || gain == ADC_MIN_GAIN) + 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..121c37a56 --- /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..82c65b467 --- /dev/null +++ b/libraries/ADC/examples/SyncTempRegisterRead/SyncTempRegisterRead.ino @@ -0,0 +1,81 @@ +#include + +#ifndef FAMILY_SAMD5X +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 FAMILY_SAMD5X + Serial.println("Sync temperature register read example"); +#else + Serial.println("SyncTempRegisterRead is SAMD21-specific (direct ADC register path)."); +#endif +} + +void loop() { +#ifndef FAMILY_SAMD5X + 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..d9153694f --- /dev/null +++ b/libraries/ADC/library.properties @@ -0,0 +1,9 @@ +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 diff --git a/libraries/ADC/src/ADC.cpp b/libraries/ADC/src/ADC.cpp new file mode 100644 index 000000000..e5c871c78 --- /dev/null +++ b/libraries/ADC/src/ADC.cpp @@ -0,0 +1,816 @@ +#include "ADC.h" + +#include +#include + +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 FAMILY_SAMD5X +float analogReadTemperatureC(uint16_t tp, uint16_t tc) { + 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; + + 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) { + Adc *adc; +#ifdef FAMILY_SAMD5X + adc = ADC0; +#else + adc = ADC; +#endif + + adc->OFFSETCORR.reg = static_cast(offset); + adc->GAINCORR.reg = gain; + adc->CTRLB.bit.CORREN = 1; + +#ifdef FAMILY_SAMD5X + while (adc->SYNCBUSY.reg); +#else + while (adc->STATUS.bit.SYNCBUSY); +#endif +} + +namespace { +constexpr uint8_t kInvalidMux = 0xFF; +constexpr uint8_t kMaxAdjres = 4; +constexpr uint8_t kAdcPendSvServiceId = PendSV::kMaxServices - 1; +constexpr uint32_t kAdcNvicPriority = (1u << __NVIC_PRIO_BITS) - 1u; + +// ADC IRQ routing mirrors SERCOM family handling: +// - SAMD2x exposes a single ADC_IRQn vector. +// - SAME/SAMD5x splits ADC0 interrupts across two NVIC lines: +// ADC0_0 = OVERRUN/WINMON, ADC0_1 = RESRDY. +// Enabling/servicing both split lines ensures window-monitor and data-ready +// callbacks continue to work together on 5x devices. + +uint8_t adcIrqCount() { +#ifdef FAMILY_SAMD5X + return 2; +#else + return 1; +#endif +} + +IRQn_Type adcIrqAt(uint8_t index) { +#ifdef FAMILY_SAMD5X + return (index == 0) ? ADC0_0_IRQn : ADC0_1_IRQn; +#else + (void)index; + return ADC_IRQn; +#endif +} + +void adcPendSvService(uint8_t serviceId, void *context) { + (void)serviceId; + (void)context; + 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 kInvalidMux; + + const EAnalogChannel channel = g_APinDescription[arduinoPin].ulADCChannelNumber; + if (channel == No_ADC_Channel) + return kInvalidMux; + + 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 == kInvalidMux) + return ADC_MUX_SOURCE_INVALID; + + return mux; +} + +uint8_t ADC_MUXNEG_PIN(uint8_t pin) { + const uint8_t mux = pinToMux(pin); + if (mux == kInvalidMux) + return ADC_MUX_SOURCE_INVALID; + + return mux; +} + +AdcEngine &AdcEngine::instance() { + static AdcEngine adcEngine; + return adcEngine; +} + +bool AdcEngine::begin() { + if (initialized_) + return true; + + queueHead_ = 0; + queueTail_ = 0; + queueCount_ = 0; + activeChannel_ = nullptr; + activeMonitorMode_ = false; + pendingChannel_ = nullptr; + pendingCallback_ = nullptr; + pendingUserData_ = nullptr; + pendingResult_ = 0; + enabledMask_ = 0; + registeredCount_ = 0; + monitorCursor_ = 0; + + 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(ADC_DMAC_ID_RESRDY); + dma_.setAction(DMA_TRIGGER_ACTON_BEAT); + dma_.setCallback(AdcEngine::dmaDoneCallback, DMA_CALLBACK_TRANSFER_DONE); + dmaDescriptor_ = dma_.addDescriptor((void *)&ADC->RESULT.reg, (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); + } + + ADC->INTFLAG.reg = ADC_INTFLAG_RESRDY | ADC_INTFLAG_WINMON; + ADC->INTENSET.bit.RESRDY = 1; + 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); + } + ADC->INTENCLR.bit.RESRDY = 1; + + if (dmaActive_) + dma_.abort(); + + dma_.free(); + dmaDescriptor_ = nullptr; + + ADC->CTRLA.bit.SWRST = 1; + while (ADC->CTRLA.bit.SWRST); + waitAdcSync(); + + PendSV::instance().clearService(kAdcPendSvServiceId); + + initialized_ = false; + dmaActive_ = false; + 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() { + const uint8_t flags = static_cast(ADC->INTFLAG.reg & 0x0F); + + if ((flags & ADC_INTFLAG_WINMON) != 0u) { + ChannelADC *channel = activeChannel_; + if (channel != nullptr && channel->windowEnabled_ && channel->onWindowMonitor_ != nullptr) { + const uint16_t result = ADC->RESULT.reg; + channel->onWindowMonitor_(channel, result, channel->windowUserData_); + } + } + + if ((flags & ADC_INTFLAG_RESRDY) != 0u) + ADC->INTFLAG.reg = ADC_INTFLAG_RESRDY; + + ADC->INTFLAG.reg = 0x0F; +} + +void AdcEngine::onPendSv() { + if (!pendSvPending_) + return; + + pendSvPending_ = false; + + 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; + + ADC->CTRLA.bit.ENABLE = 0; + waitAdcSync(); + + const uint8_t refctrlReg = static_cast(ADC_REFCTRL_REFSEL(channel->refSel_)); + ADC->REFCTRL.reg = refctrlReg; + +#ifdef FAMILY_SAMD5X + const uint16_t inputCtrlReg = static_cast( + ADC_INPUTCTRL_MUXPOS(channel->muxPos_) | ADC_INPUTCTRL_MUXNEG(channel->muxNeg_) | + (channel->differentialMode_ ? ADC_INPUTCTRL_DIFFMODE : 0u)); + ADC->INPUTCTRL.reg = inputCtrlReg; + + const uint8_t avgCtrlReg = + static_cast(ADC_AVGCTRL_SAMPLENUM(sampleNumToCode(channel->sampleNum_)) | + ADC_AVGCTRL_ADJRES(channel->adjres_)); + ADC->AVGCTRL.reg = avgCtrlReg; + + uint16_t ctrlaReg = ADC->CTRLA.reg; + ctrlaReg = + static_cast((ctrlaReg & ~ADC_CTRLA_PRESCALER_Msk) | + ADC_CTRLA_PRESCALER(static_cast(channel->prescaler_))); + ADC->CTRLA.reg = ctrlaReg; + + const uint16_t ctrlbReg = static_cast( + (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_WINMODE(channel->windowEnabled_ + ? static_cast(channel->winMode_) + : static_cast(AdcWinMode::ADC_WINMODE_DISABLE))); + ADC->CTRLB.reg = ctrlbReg; + + const uint8_t evctrlReg = + static_cast((channel->evWinmonEo_ ? ADC_EVCTRL_WINMONEO : 0u) | + (channel->evResrdyEo_ ? ADC_EVCTRL_RESRDYEO : 0u) | + (channel->evSyncei_ ? ADC_EVCTRL_SYNCEI : 0u) | + (channel->evStartei_ ? ADC_EVCTRL_STARTEI : 0u)); + ADC->EVCTRL.reg = evctrlReg; + + ADC->WINLT.reg = channel->windowLower_; + ADC->WINUT.reg = channel->windowUpper_; + + ADC->OFFSETCORR.reg = static_cast(channel->offsetCorr_); + ADC->GAINCORR.reg = channel->gainCorr_; +#else + const uint16_t inputCtrlReg = static_cast( + ADC_INPUTCTRL_MUXPOS(channel->muxPos_) | ADC_INPUTCTRL_MUXNEG(channel->muxNeg_) | + ADC_INPUTCTRL_GAIN(static_cast(channel->gain_))); + ADC->INPUTCTRL.reg = inputCtrlReg; + + const uint8_t avgCtrlReg = + static_cast(ADC_AVGCTRL_SAMPLENUM(sampleNumToCode(channel->sampleNum_)) | + ADC_AVGCTRL_ADJRES(channel->adjres_)); + ADC->AVGCTRL.reg = 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_))); + ADC->CTRLB.reg = ctrlbReg; + + const uint8_t evctrlReg = + static_cast((channel->evWinmonEo_ ? ADC_EVCTRL_WINMONEO : 0u) | + (channel->evResrdyEo_ ? ADC_EVCTRL_RESRDYEO : 0u) | + (channel->evSyncei_ ? ADC_EVCTRL_SYNCEI : 0u) | + (channel->evStartei_ ? ADC_EVCTRL_STARTEI : 0u)); + ADC->EVCTRL.reg = evctrlReg; + + ADC->WINLT.reg = channel->windowLower_; + ADC->WINUT.reg = channel->windowUpper_; + + const uint8_t winctrlReg = static_cast( + ADC_WINCTRL_WINMODE(channel->windowEnabled_ ? static_cast(channel->winMode_) : 0)); + ADC->WINCTRL.reg = winctrlReg; + + ADC->OFFSETCORR.reg = static_cast(channel->offsetCorr_); + ADC->GAINCORR.reg = channel->gainCorr_; +#endif + + waitAdcSync(); + + ADC->INTENCLR.reg = 0x0F; + const uint8_t intensetReg = + static_cast(ADC_INTENSET_RESRDY | + ((monitorMode && channel->windowEnabled_) ? ADC_INTENSET_WINMON : 0u)); + ADC->INTENSET.reg = intensetReg; + + ADC->INTFLAG.reg = 0x0F; + + if (dmaDescriptor_ == nullptr) + return false; + + dma_.changeDescriptor(dmaDescriptor_, (void *)&ADC->RESULT.reg, (void *)&dmaLatestResult_, 1); + if (dma_.startJob() != DMA_STATUS_OK) + return false; + + dmaActive_ = true; + activeChannel_ = channel; + activeMonitorMode_ = monitorMode; + + ADC->CTRLA.bit.ENABLE = 1; + waitAdcSync(); + 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 { +#ifdef FAMILY_SAMD5X + while (ADC->SYNCBUSY.reg); +#else + while (ADC->STATUS.bit.SYNCBUSY); +#endif +} + +void AdcEngine::dmaDoneCallback(Adafruit_ZeroDMA *dma) { + (void)dma; + + AdcEngine &engine = AdcEngine::instance(); + engine.dmaActive_ = false; + + 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; + + if (muxPos == kInvalidMux || muxNeg == kInvalidMux) + 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_; +} + +bool ChannelADC::read() { + if (!initialized_ || !attached_) + return false; + + hasFreshValue_ = false; + + AdcEngine &engine = AdcEngine::instance(); + if (!engine.enqueue(this)) + return false; + + // No callback means caller requested blocking/synchronous behavior. + if (onReadComplete_ == nullptr) { + while (!hasFreshValue_) + engine.service(); + } + + 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 FAMILY_SAMD5X +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 diff --git a/libraries/ADC/src/ADC.h b/libraries/ADC/src/ADC.h new file mode 100644 index 000000000..c3f67b48e --- /dev/null +++ b/libraries/ADC/src/ADC.h @@ -0,0 +1,326 @@ +#pragma once + +#include +#include + +#include +#include + +static constexpr uint8_t ADC_MUX_SOURCE_INVALID = 0xFF; + +void analogReadCorrection(int offset, uint16_t gain); +#ifdef FAMILY_SAMD5X +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 FAMILY_SAMD5X + 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 FAMILY_SAMD5X + 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 FAMILY_SAMD5X + 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 FAMILY_SAMD5X + 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 FAMILY_SAMD5X + 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, + ADC_MUXPOS_PIN16 = ADC_INPUTCTRL_MUXPOS_AIN16_Val, + ADC_MUXPOS_PIN17 = ADC_INPUTCTRL_MUXPOS_AIN17_Val, + ADC_MUXPOS_PIN18 = ADC_INPUTCTRL_MUXPOS_AIN18_Val, + ADC_MUXPOS_PIN19 = 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 FAMILY_SAMD5X + 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 = 0x1C; + static constexpr uint8_t kSkippedMuxStart = 0x14; + static constexpr uint8_t kSkippedMuxEnd = 0x17; + static constexpr uint8_t kResultContainerSize = 25; + + 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(); + void waitAdcSync() const; + inline void startConversion() const { ADC->SWTRIG.bit.START = 1;} + 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; +}; + +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; + + 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; + AdcPrescaler prescaler_ = AdcPrescaler::ADC_PRESCALER_DIV4; + bool freeRun_ = false; + bool leftAdjust_ = false; + bool differentialMode_ = false; + bool enqueued_ = false; + bool registered_ = 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/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..756e66733 100644 --- a/libraries/SAMD_AnalogCorrection/src/SAMD_AnalogCorrection.h +++ b/libraries/SAMD_AnalogCorrection/src/SAMD_AnalogCorrection.h @@ -18,7 +18,4 @@ #pragma once -#include - -void analogReadCorrection (int offset, uint16_t gain); - +#include From 855d09a5e1b8422243ac20f2f27d2cf8e94cf302 Mon Sep 17 00:00:00 2001 From: Cal Abel Date: Mon, 13 Apr 2026 23:17:00 -0400 Subject: [PATCH 3/9] fix bugs in samd51 ADC paths for register names --- libraries/ADC/src/ADC.cpp | 146 +++++++++++++++++++++++++------------- libraries/ADC/src/ADC.h | 8 ++- 2 files changed, 105 insertions(+), 49 deletions(-) diff --git a/libraries/ADC/src/ADC.cpp b/libraries/ADC/src/ADC.cpp index e5c871c78..19ac0ecc6 100644 --- a/libraries/ADC/src/ADC.cpp +++ b/libraries/ADC/src/ADC.cpp @@ -3,6 +3,14 @@ #include #include +#ifdef FAMILY_SAMD5X +#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) @@ -113,6 +121,34 @@ constexpr uint8_t kMaxAdjres = 4; constexpr uint8_t kAdcPendSvServiceId = PendSV::kMaxServices - 1; constexpr uint32_t kAdcNvicPriority = (1u << __NVIC_PRIO_BITS) - 1u; +#ifdef FAMILY_SAMD5X +#ifdef ADC_EVCTRL_SYNCEI +constexpr uint8_t kAdcEvctrlSynceiBit = ADC_EVCTRL_SYNCEI; +#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 FAMILY_SAMD5X + return ADC0_DMAC_ID_RESRDY; +#else + return ADC_DMAC_ID_RESRDY; +#endif +} + +inline Adc *adcInstance() { +#ifdef FAMILY_SAMD5X + return ADC0; +#else + return ADC; +#endif +} + // ADC IRQ routing mirrors SERCOM family handling: // - SAMD2x exposes a single ADC_IRQn vector. // - SAME/SAMD5x splits ADC0 interrupts across two NVIC lines: @@ -230,11 +266,14 @@ bool AdcEngine::begin() { return false; } - dma_.setTrigger(ADC_DMAC_ID_RESRDY); + dma_.setTrigger(adcDmacResrdyTrigger()); dma_.setAction(DMA_TRIGGER_ACTON_BEAT); dma_.setCallback(AdcEngine::dmaDoneCallback, DMA_CALLBACK_TRANSFER_DONE); - dmaDescriptor_ = dma_.addDescriptor((void *)&ADC->RESULT.reg, (void *)&dmaLatestResult_, 1, - DMA_BEAT_SIZE_HWORD, false, false); + Adc *const adc = adcInstance(); + + dmaDescriptor_ = + dma_.addDescriptor((void *)&adc->RESULT.reg, (void *)&dmaLatestResult_, + 1, DMA_BEAT_SIZE_HWORD, false, false); if (dmaDescriptor_ == nullptr) { dma_.free(); PendSV::instance().clearService(kAdcPendSvServiceId); @@ -249,8 +288,8 @@ bool AdcEngine::begin() { NVIC_EnableIRQ(irq); } - ADC->INTFLAG.reg = ADC_INTFLAG_RESRDY | ADC_INTFLAG_WINMON; - ADC->INTENSET.bit.RESRDY = 1; + adc->INTFLAG.reg = ADC_INTFLAG_RESRDY | ADC_INTFLAG_WINMON; + adc->INTENSET.bit.RESRDY = 1; initialized_ = true; return true; } @@ -264,7 +303,9 @@ void AdcEngine::end() { NVIC_DisableIRQ(irq); NVIC_ClearPendingIRQ(irq); } - ADC->INTENCLR.bit.RESRDY = 1; + Adc *const adc = adcInstance(); + + adc->INTENCLR.bit.RESRDY = 1; if (dmaActive_) dma_.abort(); @@ -272,8 +313,9 @@ void AdcEngine::end() { dma_.free(); dmaDescriptor_ = nullptr; - ADC->CTRLA.bit.SWRST = 1; - while (ADC->CTRLA.bit.SWRST); + adc->CTRLA.bit.SWRST = 1; + while (adc->CTRLA.bit.SWRST) + ; waitAdcSync(); PendSV::instance().clearService(kAdcPendSvServiceId); @@ -389,20 +431,22 @@ uint32_t AdcEngine::enabledMask() const { } void AdcEngine::onResrdyIsr() { - const uint8_t flags = static_cast(ADC->INTFLAG.reg & 0x0F); - - if ((flags & ADC_INTFLAG_WINMON) != 0u) { - ChannelADC *channel = activeChannel_; - if (channel != nullptr && channel->windowEnabled_ && channel->onWindowMonitor_ != nullptr) { - const uint16_t result = ADC->RESULT.reg; - channel->onWindowMonitor_(channel, result, channel->windowUserData_); - } + Adc *const adc = adcInstance(); + const uint8_t flags = static_cast(adc->INTFLAG.reg & 0x0F); + + if ((flags & ADC_INTFLAG_WINMON) != 0u) { + ChannelADC *channel = activeChannel_; + if (channel != nullptr && channel->windowEnabled_ && + channel->onWindowMonitor_ != nullptr) { + const uint16_t result = adc->RESULT.reg; + channel->onWindowMonitor_(channel, result, channel->windowUserData_); } + } if ((flags & ADC_INTFLAG_RESRDY) != 0u) - ADC->INTFLAG.reg = ADC_INTFLAG_RESRDY; + adc->INTFLAG.reg = ADC_INTFLAG_RESRDY; - ADC->INTFLAG.reg = 0x0F; + adc->INTFLAG.reg = 0x0F; } void AdcEngine::onPendSv() { @@ -458,28 +502,30 @@ bool AdcEngine::applyChannelAndStart(ChannelADC *channel, bool monitorMode) { if (channel == nullptr) return false; - ADC->CTRLA.bit.ENABLE = 0; + Adc *const adc = adcInstance(); + + adc->CTRLA.bit.ENABLE = 0; waitAdcSync(); const uint8_t refctrlReg = static_cast(ADC_REFCTRL_REFSEL(channel->refSel_)); - ADC->REFCTRL.reg = refctrlReg; + adc->REFCTRL.reg = refctrlReg; #ifdef FAMILY_SAMD5X const uint16_t inputCtrlReg = static_cast( ADC_INPUTCTRL_MUXPOS(channel->muxPos_) | ADC_INPUTCTRL_MUXNEG(channel->muxNeg_) | (channel->differentialMode_ ? ADC_INPUTCTRL_DIFFMODE : 0u)); - ADC->INPUTCTRL.reg = inputCtrlReg; + adc->INPUTCTRL.reg = inputCtrlReg; const uint8_t avgCtrlReg = static_cast(ADC_AVGCTRL_SAMPLENUM(sampleNumToCode(channel->sampleNum_)) | ADC_AVGCTRL_ADJRES(channel->adjres_)); - ADC->AVGCTRL.reg = avgCtrlReg; + adc->AVGCTRL.reg = avgCtrlReg; - uint16_t ctrlaReg = ADC->CTRLA.reg; + uint16_t ctrlaReg = adc->CTRLA.reg; ctrlaReg = static_cast((ctrlaReg & ~ADC_CTRLA_PRESCALER_Msk) | ADC_CTRLA_PRESCALER(static_cast(channel->prescaler_))); - ADC->CTRLA.reg = ctrlaReg; + adc->CTRLA.reg = ctrlaReg; const uint16_t ctrlbReg = static_cast( (channel->leftAdjust_ ? ADC_CTRLB_LEFTADJ : 0u) | @@ -489,30 +535,30 @@ bool AdcEngine::applyChannelAndStart(ChannelADC *channel, bool monitorMode) { ADC_CTRLB_WINMODE(channel->windowEnabled_ ? static_cast(channel->winMode_) : static_cast(AdcWinMode::ADC_WINMODE_DISABLE))); - ADC->CTRLB.reg = ctrlbReg; + adc->CTRLB.reg = ctrlbReg; const uint8_t evctrlReg = static_cast((channel->evWinmonEo_ ? ADC_EVCTRL_WINMONEO : 0u) | (channel->evResrdyEo_ ? ADC_EVCTRL_RESRDYEO : 0u) | - (channel->evSyncei_ ? ADC_EVCTRL_SYNCEI : 0u) | + (channel->evSyncei_ ? kAdcEvctrlSynceiBit : 0u) | (channel->evStartei_ ? ADC_EVCTRL_STARTEI : 0u)); - ADC->EVCTRL.reg = evctrlReg; + adc->EVCTRL.reg = evctrlReg; - ADC->WINLT.reg = channel->windowLower_; - ADC->WINUT.reg = channel->windowUpper_; + adc->WINLT.reg = channel->windowLower_; + adc->WINUT.reg = channel->windowUpper_; - ADC->OFFSETCORR.reg = static_cast(channel->offsetCorr_); - ADC->GAINCORR.reg = channel->gainCorr_; + adc->OFFSETCORR.reg = static_cast(channel->offsetCorr_); + adc->GAINCORR.reg = channel->gainCorr_; #else const uint16_t inputCtrlReg = static_cast( ADC_INPUTCTRL_MUXPOS(channel->muxPos_) | ADC_INPUTCTRL_MUXNEG(channel->muxNeg_) | ADC_INPUTCTRL_GAIN(static_cast(channel->gain_))); - ADC->INPUTCTRL.reg = inputCtrlReg; + adc->INPUTCTRL.reg = inputCtrlReg; const uint8_t avgCtrlReg = static_cast(ADC_AVGCTRL_SAMPLENUM(sampleNumToCode(channel->sampleNum_)) | ADC_AVGCTRL_ADJRES(channel->adjres_)); - ADC->AVGCTRL.reg = avgCtrlReg; + adc->AVGCTRL.reg = avgCtrlReg; const uint16_t ctrlbReg = static_cast((channel->differentialMode_ ? ADC_CTRLB_DIFFMODE : 0u) | @@ -521,40 +567,41 @@ bool AdcEngine::applyChannelAndStart(ChannelADC *channel, bool monitorMode) { (channel->corrEnabled_ ? ADC_CTRLB_CORREN : 0u) | ADC_CTRLB_RESSEL(static_cast(channel->ressel_)) | ADC_CTRLB_PRESCALER(static_cast(channel->prescaler_))); - ADC->CTRLB.reg = ctrlbReg; + adc->CTRLB.reg = ctrlbReg; const uint8_t evctrlReg = static_cast((channel->evWinmonEo_ ? ADC_EVCTRL_WINMONEO : 0u) | (channel->evResrdyEo_ ? ADC_EVCTRL_RESRDYEO : 0u) | - (channel->evSyncei_ ? ADC_EVCTRL_SYNCEI : 0u) | + (channel->evSyncei_ ? kAdcEvctrlSynceiBit : 0u) | (channel->evStartei_ ? ADC_EVCTRL_STARTEI : 0u)); - ADC->EVCTRL.reg = evctrlReg; + adc->EVCTRL.reg = evctrlReg; - ADC->WINLT.reg = channel->windowLower_; - ADC->WINUT.reg = channel->windowUpper_; + adc->WINLT.reg = channel->windowLower_; + adc->WINUT.reg = channel->windowUpper_; const uint8_t winctrlReg = static_cast( ADC_WINCTRL_WINMODE(channel->windowEnabled_ ? static_cast(channel->winMode_) : 0)); - ADC->WINCTRL.reg = winctrlReg; + adc->WINCTRL.reg = winctrlReg; - ADC->OFFSETCORR.reg = static_cast(channel->offsetCorr_); - ADC->GAINCORR.reg = channel->gainCorr_; + adc->OFFSETCORR.reg = static_cast(channel->offsetCorr_); + adc->GAINCORR.reg = channel->gainCorr_; #endif waitAdcSync(); - ADC->INTENCLR.reg = 0x0F; + adc->INTENCLR.reg = 0x0F; const uint8_t intensetReg = static_cast(ADC_INTENSET_RESRDY | ((monitorMode && channel->windowEnabled_) ? ADC_INTENSET_WINMON : 0u)); - ADC->INTENSET.reg = intensetReg; + adc->INTENSET.reg = intensetReg; - ADC->INTFLAG.reg = 0x0F; + adc->INTFLAG.reg = 0x0F; if (dmaDescriptor_ == nullptr) return false; - dma_.changeDescriptor(dmaDescriptor_, (void *)&ADC->RESULT.reg, (void *)&dmaLatestResult_, 1); + dma_.changeDescriptor(dmaDescriptor_, (void *)&adc->RESULT.reg, + (void *)&dmaLatestResult_, 1); if (dma_.startJob() != DMA_STATUS_OK) return false; @@ -562,7 +609,7 @@ bool AdcEngine::applyChannelAndStart(ChannelADC *channel, bool monitorMode) { activeChannel_ = channel; activeMonitorMode_ = monitorMode; - ADC->CTRLA.bit.ENABLE = 1; + adc->CTRLA.bit.ENABLE = 1; waitAdcSync(); startConversion(); return true; @@ -586,10 +633,13 @@ bool AdcEngine::startMonitorIfIdle() { } void AdcEngine::waitAdcSync() const { + Adc *const adc = adcInstance(); #ifdef FAMILY_SAMD5X - while (ADC->SYNCBUSY.reg); + while (adc->SYNCBUSY.reg) + ; #else - while (ADC->STATUS.bit.SYNCBUSY); + while (adc->STATUS.bit.SYNCBUSY) + ; #endif } diff --git a/libraries/ADC/src/ADC.h b/libraries/ADC/src/ADC.h index c3f67b48e..e707ba476 100644 --- a/libraries/ADC/src/ADC.h +++ b/libraries/ADC/src/ADC.h @@ -219,7 +219,13 @@ class AdcEngine { bool applyChannelAndStart(ChannelADC *channel, bool monitorMode = false); bool startMonitorIfIdle(); void waitAdcSync() const; - inline void startConversion() const { ADC->SWTRIG.bit.START = 1;} + inline void startConversion() const { + #ifdef FAMILY_SAMD5X + ADC0->SWTRIG.bit.START = 1; + #else + ADC->SWTRIG.bit.START = 1; + #endif + } static void dmaDoneCallback(Adafruit_ZeroDMA *dma); ChannelADC *queue_[kResultContainerSize]{}; From e11b67d1af130e70f5ce1717a38def2abc0e8473 Mon Sep 17 00:00:00 2001 From: Cal Abel Date: Thu, 18 Jun 2026 16:27:43 -0400 Subject: [PATCH 4/9] bug fix in register length being truncated --- libraries/ADC/src/ADC.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/ADC/src/ADC.cpp b/libraries/ADC/src/ADC.cpp index 19ac0ecc6..f2a315a0d 100644 --- a/libraries/ADC/src/ADC.cpp +++ b/libraries/ADC/src/ADC.cpp @@ -550,7 +550,7 @@ bool AdcEngine::applyChannelAndStart(ChannelADC *channel, bool monitorMode) { adc->OFFSETCORR.reg = static_cast(channel->offsetCorr_); adc->GAINCORR.reg = channel->gainCorr_; #else - const uint16_t inputCtrlReg = static_cast( + const uint32_t inputCtrlReg = static_cast( ADC_INPUTCTRL_MUXPOS(channel->muxPos_) | ADC_INPUTCTRL_MUXNEG(channel->muxNeg_) | ADC_INPUTCTRL_GAIN(static_cast(channel->gain_))); adc->INPUTCTRL.reg = inputCtrlReg; From f736871d7113db0572e4b932719628f52c4e5d90 Mon Sep 17 00:00:00 2001 From: Cal Abel Date: Thu, 18 Jun 2026 16:31:27 -0400 Subject: [PATCH 5/9] correctly write the ADC gain --- libraries/ADC/src/ADC.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/libraries/ADC/src/ADC.cpp b/libraries/ADC/src/ADC.cpp index f2a315a0d..c9e4fa8ef 100644 --- a/libraries/ADC/src/ADC.cpp +++ b/libraries/ADC/src/ADC.cpp @@ -589,7 +589,18 @@ bool AdcEngine::applyChannelAndStart(ChannelADC *channel, bool monitorMode) { waitAdcSync(); + adc->CTRLA.bit.ENABLE = 1; + waitAdcSync(); + + // The first conversion after changing the mux/reference can be stale. + // Match Arduino analogRead() by discarding it before arming DMA. adc->INTENCLR.reg = 0x0F; + adc->INTFLAG.reg = 0x0F; + startConversion(); + while ((adc->INTFLAG.reg & ADC_INTFLAG_RESRDY) == 0u) + ; + adc->INTFLAG.reg = ADC_INTFLAG_RESRDY; + const uint8_t intensetReg = static_cast(ADC_INTENSET_RESRDY | ((monitorMode && channel->windowEnabled_) ? ADC_INTENSET_WINMON : 0u)); @@ -609,8 +620,6 @@ bool AdcEngine::applyChannelAndStart(ChannelADC *channel, bool monitorMode) { activeChannel_ = channel; activeMonitorMode_ = monitorMode; - adc->CTRLA.bit.ENABLE = 1; - waitAdcSync(); startConversion(); return true; } From 9c5c217c230c8d4182e331ac3ac52bb5c8078e1d Mon Sep 17 00:00:00 2001 From: Cal Abel Date: Sat, 20 Jun 2026 17:12:24 -0400 Subject: [PATCH 6/9] Fix ADC library chip-family guards --- .../AsyncChipTemperature.ino | 6 ++--- .../examples/ReadCallback/ReadCallback.ino | 4 ++-- .../SyncTempRegisterRead.ino | 6 ++--- libraries/ADC/src/ADC.cpp | 24 +++++++++---------- libraries/ADC/src/ADC.h | 21 +++++++++------- 5 files changed, 32 insertions(+), 29 deletions(-) diff --git a/libraries/ADC/examples/AsyncChipTemperature/AsyncChipTemperature.ino b/libraries/ADC/examples/AsyncChipTemperature/AsyncChipTemperature.ino index 97e614c1a..3b2f51a51 100644 --- a/libraries/ADC/examples/AsyncChipTemperature/AsyncChipTemperature.ino +++ b/libraries/ADC/examples/AsyncChipTemperature/AsyncChipTemperature.ino @@ -11,7 +11,7 @@ void onTempRead(ChannelADC *channel, uint16_t result, void *userData) { } ChannelADC g_temp; -#ifdef FAMILY_SAMD5X +#ifdef ADC_HAS_D5X_E5X_REGISTERS ChannelADC g_ctat; #endif @@ -23,7 +23,7 @@ void setup() { g_temp.setReference(AdcRefSel::ADC_REFSEL_INT1V); g_temp.setCtrlB(AdcResSel::ADC_RESSEL_12BIT, AdcPrescaler::ADC_PRESCALER_DIV32); -#ifdef FAMILY_SAMD5X +#ifdef ADC_HAS_D5X_E5X_REGISTERS g_ctat.setReadCallback(onTempRead, nullptr); g_ctat.setReference(AdcRefSel::ADC_REFSEL_INT1V); g_ctat.setCtrlB(AdcResSel::ADC_RESSEL_12BIT, AdcPrescaler::ADC_PRESCALER_DIV32); @@ -46,7 +46,7 @@ void setup() { } void loop() { -#ifdef FAMILY_SAMD5X +#ifdef ADC_HAS_D5X_E5X_REGISTERS g_readComplete = false; if (!g_temp.read()) { Serial.println("PTAT async enqueue failed"); diff --git a/libraries/ADC/examples/ReadCallback/ReadCallback.ino b/libraries/ADC/examples/ReadCallback/ReadCallback.ino index 121c37a56..3d6785d4d 100644 --- a/libraries/ADC/examples/ReadCallback/ReadCallback.ino +++ b/libraries/ADC/examples/ReadCallback/ReadCallback.ino @@ -16,7 +16,7 @@ void setup() { while (!Serial) ; - if (!adc.attach(A0)) + if (!adc.attach(A0)) Serial.println("ADC attach failed"); adc.setReadCallback(onAdcRead, nullptr); } @@ -27,7 +27,7 @@ void loop() { if (now - lastTriggerMs >= 100) { lastTriggerMs = now; - if (!adc.read()) + if (!adc.read()) Serial.println("ADC read enqueue failed"); } diff --git a/libraries/ADC/examples/SyncTempRegisterRead/SyncTempRegisterRead.ino b/libraries/ADC/examples/SyncTempRegisterRead/SyncTempRegisterRead.ino index 82c65b467..8e0c1c07f 100644 --- a/libraries/ADC/examples/SyncTempRegisterRead/SyncTempRegisterRead.ino +++ b/libraries/ADC/examples/SyncTempRegisterRead/SyncTempRegisterRead.ino @@ -1,6 +1,6 @@ #include -#ifndef FAMILY_SAMD5X +#ifndef ADC_HAS_D5X_E5X_REGISTERS static inline void waitAdcSyncRaw() { while (ADC->STATUS.bit.SYNCBUSY) { } @@ -62,7 +62,7 @@ void setup() { while (!Serial) { } -#ifndef FAMILY_SAMD5X +#ifndef ADC_HAS_D5X_E5X_REGISTERS Serial.println("Sync temperature register read example"); #else Serial.println("SyncTempRegisterRead is SAMD21-specific (direct ADC register path)."); @@ -70,7 +70,7 @@ void setup() { } void loop() { -#ifndef FAMILY_SAMD5X +#ifndef ADC_HAS_D5X_E5X_REGISTERS const uint16_t raw = readTempRawDirectSync(); Serial.print("Raw temp register ADC: "); Serial.println(raw); diff --git a/libraries/ADC/src/ADC.cpp b/libraries/ADC/src/ADC.cpp index c9e4fa8ef..2fdb5f490 100644 --- a/libraries/ADC/src/ADC.cpp +++ b/libraries/ADC/src/ADC.cpp @@ -3,7 +3,7 @@ #include #include -#ifdef FAMILY_SAMD5X +#ifdef ADC_HAS_D5X_E5X_REGISTERS #ifndef NVMCTRL_TEMP_LOG #ifdef NVMCTRL_TEMP_LOG_W0 #define NVMCTRL_TEMP_LOG NVMCTRL_TEMP_LOG_W0 @@ -19,7 +19,7 @@ static inline float decToFrac(uint8_t val) { return static_cast(val) / 100.0f; } -#ifdef FAMILY_SAMD5X +#ifdef ADC_HAS_D5X_E5X_REGISTERS float analogReadTemperatureC(uint16_t tp, uint16_t tc) { const uint32_t roomInt = (*(uint32_t *)FUSES_ROOM_TEMP_VAL_INT_ADDR & FUSES_ROOM_TEMP_VAL_INT_Msk) >> FUSES_ROOM_TEMP_VAL_INT_Pos; @@ -98,7 +98,7 @@ float analogReadTemperatureC(uint16_t adcReading) { void analogReadCorrection(int offset, uint16_t gain) { Adc *adc; -#ifdef FAMILY_SAMD5X +#ifdef ADC_HAS_D5X_E5X_REGISTERS adc = ADC0; #else adc = ADC; @@ -108,7 +108,7 @@ void analogReadCorrection(int offset, uint16_t gain) { adc->GAINCORR.reg = gain; adc->CTRLB.bit.CORREN = 1; -#ifdef FAMILY_SAMD5X +#ifdef ADC_HAS_D5X_E5X_REGISTERS while (adc->SYNCBUSY.reg); #else while (adc->STATUS.bit.SYNCBUSY); @@ -121,7 +121,7 @@ constexpr uint8_t kMaxAdjres = 4; constexpr uint8_t kAdcPendSvServiceId = PendSV::kMaxServices - 1; constexpr uint32_t kAdcNvicPriority = (1u << __NVIC_PRIO_BITS) - 1u; -#ifdef FAMILY_SAMD5X +#ifdef ADC_HAS_D5X_E5X_REGISTERS #ifdef ADC_EVCTRL_SYNCEI constexpr uint8_t kAdcEvctrlSynceiBit = ADC_EVCTRL_SYNCEI; #elif defined(ADC_EVCTRL_FLUSHEI) @@ -134,7 +134,7 @@ constexpr uint8_t kAdcEvctrlSynceiBit = ADC_EVCTRL_SYNCEI; #endif uint8_t adcDmacResrdyTrigger() { -#ifdef FAMILY_SAMD5X +#ifdef ADC_HAS_D5X_E5X_REGISTERS return ADC0_DMAC_ID_RESRDY; #else return ADC_DMAC_ID_RESRDY; @@ -142,7 +142,7 @@ uint8_t adcDmacResrdyTrigger() { } inline Adc *adcInstance() { -#ifdef FAMILY_SAMD5X +#ifdef ADC_HAS_D5X_E5X_REGISTERS return ADC0; #else return ADC; @@ -157,7 +157,7 @@ inline Adc *adcInstance() { // callbacks continue to work together on 5x devices. uint8_t adcIrqCount() { -#ifdef FAMILY_SAMD5X +#ifdef ADC_HAS_D5X_E5X_REGISTERS return 2; #else return 1; @@ -165,7 +165,7 @@ uint8_t adcIrqCount() { } IRQn_Type adcIrqAt(uint8_t index) { -#ifdef FAMILY_SAMD5X +#ifdef ADC_HAS_D5X_E5X_REGISTERS return (index == 0) ? ADC0_0_IRQn : ADC0_1_IRQn; #else (void)index; @@ -510,7 +510,7 @@ bool AdcEngine::applyChannelAndStart(ChannelADC *channel, bool monitorMode) { const uint8_t refctrlReg = static_cast(ADC_REFCTRL_REFSEL(channel->refSel_)); adc->REFCTRL.reg = refctrlReg; -#ifdef FAMILY_SAMD5X +#ifdef ADC_HAS_D5X_E5X_REGISTERS const uint16_t inputCtrlReg = static_cast( ADC_INPUTCTRL_MUXPOS(channel->muxPos_) | ADC_INPUTCTRL_MUXNEG(channel->muxNeg_) | (channel->differentialMode_ ? ADC_INPUTCTRL_DIFFMODE : 0u)); @@ -643,7 +643,7 @@ bool AdcEngine::startMonitorIfIdle() { void AdcEngine::waitAdcSync() const { Adc *const adc = adcInstance(); -#ifdef FAMILY_SAMD5X +#ifdef ADC_HAS_D5X_E5X_REGISTERS while (adc->SYNCBUSY.reg) ; #else @@ -860,7 +860,7 @@ void ChannelADC::end() { AdcEngine::instance().unregisterChannel(this); } -#ifdef FAMILY_SAMD5X +#ifdef ADC_HAS_D5X_E5X_REGISTERS extern "C" void ADC0_0_Handler(void) { AdcEngine::instance().onResrdyIsr(); } diff --git a/libraries/ADC/src/ADC.h b/libraries/ADC/src/ADC.h index e707ba476..a65225b0b 100644 --- a/libraries/ADC/src/ADC.h +++ b/libraries/ADC/src/ADC.h @@ -4,12 +4,15 @@ #include #include -#include + +#if defined(__SAMD51__) || defined(__SAME51__) || defined(__SAME53__) || defined(__SAME54__) +#define ADC_HAS_D5X_E5X_REGISTERS +#endif static constexpr uint8_t ADC_MUX_SOURCE_INVALID = 0xFF; void analogReadCorrection(int offset, uint16_t gain); -#ifdef FAMILY_SAMD5X +#ifdef ADC_HAS_D5X_E5X_REGISTERS float analogReadTemperatureC(uint16_t ptatReading, uint16_t ctatReading); #else float analogReadTemperatureC(uint16_t adcReading); @@ -30,7 +33,7 @@ enum class AdcSampleNum : uint8_t { }; enum class AdcWinMode : uint8_t { -#ifdef FAMILY_SAMD5X +#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. @@ -47,7 +50,7 @@ enum class AdcWinMode : uint8_t { }; enum class AdcRefSel : uint8_t { -#ifdef FAMILY_SAMD5X +#ifdef ADC_HAS_D5X_E5X_REGISTERS ADC_REFSEL_INT1V = ADC_REFCTRL_REFSEL_INTREF_Val, #else ADC_REFSEL_INT1V = ADC_REFCTRL_REFSEL_INT1V_Val, @@ -59,7 +62,7 @@ enum class AdcRefSel : uint8_t { }; enum class AdcGain : uint8_t { -#ifdef FAMILY_SAMD5X +#ifdef ADC_HAS_D5X_E5X_REGISTERS ADC_GAIN_1X = 0x0, ADC_GAIN_2X = 0x1, ADC_GAIN_4X = 0x2, @@ -84,7 +87,7 @@ enum class AdcResSel : uint8_t { }; enum class AdcPrescaler : uint8_t { -#ifdef FAMILY_SAMD5X +#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, @@ -105,7 +108,7 @@ enum class AdcPrescaler : uint8_t { }; enum class AdcMuxPos : uint8_t { -#ifdef FAMILY_SAMD5X +#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, @@ -158,7 +161,7 @@ enum class AdcMuxPos : uint8_t { }; enum class AdcMuxNeg : uint8_t { -#ifdef FAMILY_SAMD5X +#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, @@ -220,7 +223,7 @@ class AdcEngine { bool startMonitorIfIdle(); void waitAdcSync() const; inline void startConversion() const { - #ifdef FAMILY_SAMD5X + #ifdef ADC_HAS_D5X_E5X_REGISTERS ADC0->SWTRIG.bit.START = 1; #else ADC->SWTRIG.bit.START = 1; From 258417e5b685449d368a5fb29de54cbd1b797477 Mon Sep 17 00:00:00 2001 From: Cal Abel Date: Sun, 21 Jun 2026 07:53:23 -0400 Subject: [PATCH 7/9] Polish ADC library metadata and compatibility --- libraries/ADC/library.properties | 1 + libraries/ADC/src/ADC.h | 10 +++++----- .../SAMD_AnalogCorrection/src/SAMD_AnalogCorrection.h | 2 ++ 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/libraries/ADC/library.properties b/libraries/ADC/library.properties index d9153694f..da11e1842 100644 --- a/libraries/ADC/library.properties +++ b/libraries/ADC/library.properties @@ -7,3 +7,4 @@ paragraph=Provides queued ADC reads with optional callback handling and per-chan category=Signal Input/Output url=https://github.com/adafruit/ArduinoCore-samd architectures=samd +depends=Adafruit ZeroDMA diff --git a/libraries/ADC/src/ADC.h b/libraries/ADC/src/ADC.h index a65225b0b..54dc77264 100644 --- a/libraries/ADC/src/ADC.h +++ b/libraries/ADC/src/ADC.h @@ -222,13 +222,13 @@ class AdcEngine { bool applyChannelAndStart(ChannelADC *channel, bool monitorMode = false); bool startMonitorIfIdle(); void waitAdcSync() const; - inline void startConversion() const { - #ifdef ADC_HAS_D5X_E5X_REGISTERS + inline void startConversion() const { +#ifdef ADC_HAS_D5X_E5X_REGISTERS ADC0->SWTRIG.bit.START = 1; - #else +#else ADC->SWTRIG.bit.START = 1; - #endif - } +#endif + } static void dmaDoneCallback(Adafruit_ZeroDMA *dma); ChannelADC *queue_[kResultContainerSize]{}; diff --git a/libraries/SAMD_AnalogCorrection/src/SAMD_AnalogCorrection.h b/libraries/SAMD_AnalogCorrection/src/SAMD_AnalogCorrection.h index 756e66733..3733e3e60 100644 --- a/libraries/SAMD_AnalogCorrection/src/SAMD_AnalogCorrection.h +++ b/libraries/SAMD_AnalogCorrection/src/SAMD_AnalogCorrection.h @@ -19,3 +19,5 @@ #pragma once #include + +void analogReadCorrection(int offset, uint16_t gain); From 7616f3caec1bb349b183fff5b807ca85017e998d Mon Sep 17 00:00:00 2001 From: Cal Abel Date: Sun, 21 Jun 2026 08:01:27 -0400 Subject: [PATCH 8/9] Clean up ADC mux sentinel handling --- .../CorrectADCResponse/CorrectADCResponse.ino | 4 ++-- libraries/ADC/src/ADC.cpp | 12 ++++-------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/libraries/ADC/examples/CorrectADCResponse/CorrectADCResponse.ino b/libraries/ADC/examples/CorrectADCResponse/CorrectADCResponse.ino index 214b2c533..1bbbd5990 100644 --- a/libraries/ADC/examples/CorrectADCResponse/CorrectADCResponse.ino +++ b/libraries/ADC/examples/CorrectADCResponse/CorrectADCResponse.ino @@ -153,7 +153,7 @@ void setup() { if (highLevelRead == ADC_TOP_VALUE) maxGain = ADC_UNITY_GAIN; - for (uint16_t gain = ADC_UNITY_GAIN - 1; gain >= ADC_MIN_GAIN; --gain) { + for (int gain = ADC_UNITY_GAIN - 1; gain >= ADC_MIN_GAIN; --gain) { applyCalibration(offsetCorrectionValue, gain); Serial.print(" Gain = "); @@ -168,7 +168,7 @@ void setup() { minGain = gain; } - if (highLevelRead < ADC_TOP_VALUE || gain == ADC_MIN_GAIN) + if (highLevelRead < ADC_TOP_VALUE) break; } } diff --git a/libraries/ADC/src/ADC.cpp b/libraries/ADC/src/ADC.cpp index 2fdb5f490..224516cda 100644 --- a/libraries/ADC/src/ADC.cpp +++ b/libraries/ADC/src/ADC.cpp @@ -116,7 +116,6 @@ void analogReadCorrection(int offset, uint16_t gain) { } namespace { -constexpr uint8_t kInvalidMux = 0xFF; constexpr uint8_t kMaxAdjres = 4; constexpr uint8_t kAdcPendSvServiceId = PendSV::kMaxServices - 1; constexpr uint32_t kAdcNvicPriority = (1u << __NVIC_PRIO_BITS) - 1u; @@ -193,11 +192,11 @@ uint8_t sampleNumToCode(AdcSampleNum sampleNum) { uint8_t pinToMux(uint8_t arduinoPin) { if (arduinoPin >= PINS_COUNT) - return kInvalidMux; + return ADC_MUX_SOURCE_INVALID; const EAnalogChannel channel = g_APinDescription[arduinoPin].ulADCChannelNumber; if (channel == No_ADC_Channel) - return kInvalidMux; + return ADC_MUX_SOURCE_INVALID; return static_cast(channel); } @@ -216,7 +215,7 @@ void configureAnalogPin(uint8_t arduinoPin) { uint8_t ADC_MUXPOS_PIN(uint8_t pin) { const uint8_t mux = pinToMux(pin); - if (mux == kInvalidMux) + if (mux == ADC_MUX_SOURCE_INVALID) return ADC_MUX_SOURCE_INVALID; return mux; @@ -224,7 +223,7 @@ uint8_t ADC_MUXPOS_PIN(uint8_t pin) { uint8_t ADC_MUXNEG_PIN(uint8_t pin) { const uint8_t mux = pinToMux(pin); - if (mux == kInvalidMux) + if (mux == ADC_MUX_SOURCE_INVALID) return ADC_MUX_SOURCE_INVALID; return mux; @@ -694,9 +693,6 @@ bool ChannelADC::setAttachedSources(uint8_t muxPos, uint8_t muxNeg, AdcSampleNum if (muxPos == ADC_MUX_SOURCE_INVALID || muxNeg == ADC_MUX_SOURCE_INVALID) return false; - if (muxPos == kInvalidMux || muxNeg == kInvalidMux) - return false; - muxPos_ = muxPos; muxNeg_ = muxNeg; if (muxNeg_ != static_cast(AdcMuxNeg::ADC_MUXNEG_GND)) From 65e7f1911ec2292973701e4a7947c3f6504ebe43 Mon Sep 17 00:00:00 2001 From: Cal Abel Date: Mon, 22 Jun 2026 07:29:25 -0400 Subject: [PATCH 9/9] Fix ADC async sampling and temperature setup --- .../AsyncChipTemperature.ino | 17 ++- libraries/ADC/src/ADC.cpp | 136 ++++++++++++++++-- libraries/ADC/src/ADC.h | 14 +- 3 files changed, 147 insertions(+), 20 deletions(-) diff --git a/libraries/ADC/examples/AsyncChipTemperature/AsyncChipTemperature.ino b/libraries/ADC/examples/AsyncChipTemperature/AsyncChipTemperature.ino index 3b2f51a51..1fadd9564 100644 --- a/libraries/ADC/examples/AsyncChipTemperature/AsyncChipTemperature.ino +++ b/libraries/ADC/examples/AsyncChipTemperature/AsyncChipTemperature.ino @@ -20,20 +20,26 @@ void setup() { 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_INT1V); + 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_IOGND, + 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_IOGND, + if (!g_ctat.attach(AdcMuxPos::ADC_MUXPOS_TEMP_CTAT, AdcMuxNeg::ADC_MUXNEG_GND, AdcSampleNum::ADC_SAMPLENUM_16)) { Serial.println("Attach CTAT failed"); } @@ -70,6 +76,11 @@ void loop() { 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 diff --git a/libraries/ADC/src/ADC.cpp b/libraries/ADC/src/ADC.cpp index 224516cda..3c9e63803 100644 --- a/libraries/ADC/src/ADC.cpp +++ b/libraries/ADC/src/ADC.cpp @@ -148,6 +148,35 @@ inline Adc *adcInstance() { #endif } +#ifdef ADC_HAS_D5X_E5X_REGISTERS +void configureInternalReference(uint8_t refSel) { + if (refSel != ADC_REFCTRL_REFSEL_INTREF_Val) + return; + + SUPC->VREF.bit.SEL = SUPC_VREF_SEL_1V0_Val; + SUPC->VREF.bit.VREFOE = 1; +} + +void configureTemperatureSensor(uint8_t muxPos) { + if (muxPos != ADC_INPUTCTRL_MUXPOS_PTAT_Val && + muxPos != ADC_INPUTCTRL_MUXPOS_CTAT_Val) + return; + + 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; +} + +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. // - SAME/SAMD5x splits ADC0 interrupts across two NVIC lines: @@ -250,6 +279,8 @@ bool AdcEngine::begin() { enabledMask_ = 0; registeredCount_ = 0; monitorCursor_ = 0; + pendingStartConversion_ = false; + conversionState_ = ConversionState::Idle; for (uint8_t i = 0; i < kResultContainerSize; ++i) { queue_[i] = nullptr; @@ -287,8 +318,8 @@ bool AdcEngine::begin() { NVIC_EnableIRQ(irq); } + adc->INTENCLR.reg = 0x0F; adc->INTFLAG.reg = ADC_INTFLAG_RESRDY | ADC_INTFLAG_WINMON; - adc->INTENSET.bit.RESRDY = 1; initialized_ = true; return true; } @@ -321,6 +352,8 @@ void AdcEngine::end() { initialized_ = false; dmaActive_ = false; + pendingStartConversion_ = false; + conversionState_ = ConversionState::Idle; activeChannel_ = nullptr; activeMonitorMode_ = false; pendingChannel_ = nullptr; @@ -442,8 +475,17 @@ void AdcEngine::onResrdyIsr() { } } - if ((flags & ADC_INTFLAG_RESRDY) != 0u) - adc->INTFLAG.reg = ADC_INTFLAG_RESRDY; + if ((flags & ADC_INTFLAG_RESRDY) != 0u) { + if (conversionState_ == ConversionState::Discarding) { + (void)adc->RESULT.reg; + adc->INTENCLR.bit.RESRDY = 1; + pendingStartConversion_ = true; + pendSvPending_ = true; + PendSV::instance().setPending(kAdcPendSvServiceId); + } else { + adc->INTFLAG.reg = ADC_INTFLAG_RESRDY; + } + } adc->INTFLAG.reg = 0x0F; } @@ -454,6 +496,20 @@ void AdcEngine::onPendSv() { 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_); @@ -510,6 +566,9 @@ bool AdcEngine::applyChannelAndStart(ChannelADC *channel, bool monitorMode) { adc->REFCTRL.reg = 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 : 0u)); @@ -519,6 +578,7 @@ bool AdcEngine::applyChannelAndStart(ChannelADC *channel, bool monitorMode) { static_cast(ADC_AVGCTRL_SAMPLENUM(sampleNumToCode(channel->sampleNum_)) | ADC_AVGCTRL_ADJRES(channel->adjres_)); adc->AVGCTRL.reg = avgCtrlReg; + adc->SAMPCTRL.reg = sampleTimeForMux(channel->muxPos_); uint16_t ctrlaReg = adc->CTRLA.reg; ctrlaReg = @@ -591,14 +651,8 @@ bool AdcEngine::applyChannelAndStart(ChannelADC *channel, bool monitorMode) { adc->CTRLA.bit.ENABLE = 1; waitAdcSync(); - // The first conversion after changing the mux/reference can be stale. - // Match Arduino analogRead() by discarding it before arming DMA. adc->INTENCLR.reg = 0x0F; adc->INTFLAG.reg = 0x0F; - startConversion(); - while ((adc->INTFLAG.reg & ADC_INTFLAG_RESRDY) == 0u) - ; - adc->INTFLAG.reg = ADC_INTFLAG_RESRDY; const uint8_t intensetReg = static_cast(ADC_INTENSET_RESRDY | @@ -607,6 +661,23 @@ bool AdcEngine::applyChannelAndStart(ChannelADC *channel, bool monitorMode) { adc->INTFLAG.reg = 0x0F; + activeChannel_ = channel; + activeMonitorMode_ = monitorMode; + conversionState_ = ConversionState::Discarding; + + startConversion(); + return true; +} + +bool AdcEngine::startActiveDmaConversion() { + ChannelADC *channel = activeChannel_; + if (channel == nullptr) + return false; + + Adc *const adc = adcInstance(); + adc->INTENCLR.bit.RESRDY = 1; + adc->INTFLAG.reg = 0x0F; + if (dmaDescriptor_ == nullptr) return false; @@ -616,8 +687,7 @@ bool AdcEngine::applyChannelAndStart(ChannelADC *channel, bool monitorMode) { return false; dmaActive_ = true; - activeChannel_ = channel; - activeMonitorMode_ = monitorMode; + conversionState_ = ConversionState::Sampling; startConversion(); return true; @@ -656,6 +726,7 @@ void AdcEngine::dmaDoneCallback(Adafruit_ZeroDMA *dma) { AdcEngine &engine = AdcEngine::instance(); engine.dmaActive_ = false; + engine.conversionState_ = ConversionState::Idle; ChannelADC *channel = engine.activeChannel_; if (channel == nullptr) @@ -824,20 +895,55 @@ 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(); - if (!engine.enqueue(this)) + 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; + } - // No callback means caller requested blocking/synchronous behavior. - if (onReadComplete_ == nullptr) { - while (!hasFreshValue_) + 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; diff --git a/libraries/ADC/src/ADC.h b/libraries/ADC/src/ADC.h index 54dc77264..23ede462f 100644 --- a/libraries/ADC/src/ADC.h +++ b/libraries/ADC/src/ADC.h @@ -190,10 +190,10 @@ class ChannelADC; class AdcEngine { public: static constexpr uint8_t kMuxMin = 0x00; - static constexpr uint8_t kMuxMax = 0x1C; + static constexpr uint8_t kMuxMax = 0x1D; static constexpr uint8_t kSkippedMuxStart = 0x14; static constexpr uint8_t kSkippedMuxEnd = 0x17; - static constexpr uint8_t kResultContainerSize = 25; + static constexpr uint8_t kResultContainerSize = 26; static AdcEngine &instance(); @@ -221,6 +221,7 @@ class AdcEngine { bool applyChannelAndStart(ChannelADC *channel, bool monitorMode = false); bool startMonitorIfIdle(); + bool startActiveDmaConversion(); void waitAdcSync() const; inline void startConversion() const { #ifdef ADC_HAS_D5X_E5X_REGISTERS @@ -257,6 +258,13 @@ class AdcEngine { 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 { @@ -295,6 +303,7 @@ class ChannelADC { 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; @@ -308,6 +317,7 @@ class ChannelADC { bool differentialMode_ = false; bool enqueued_ = false; bool registered_ = false; + volatile bool syncReadDone_ = false; Callback onReadComplete_ = nullptr; void *userData_ = nullptr;