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; +}; diff --git a/libraries/ADC/examples/AsyncChipTemperature/AsyncChipTemperature.ino b/libraries/ADC/examples/AsyncChipTemperature/AsyncChipTemperature.ino new file mode 100644 index 000000000..1fadd9564 --- /dev/null +++ b/libraries/ADC/examples/AsyncChipTemperature/AsyncChipTemperature.ino @@ -0,0 +1,112 @@ +#include + +volatile bool g_readComplete = false; +volatile uint16_t g_lastValue = 0; + +void onTempRead(ChannelADC *channel, uint16_t result, void *userData) { + (void)channel; + (void)userData; + g_lastValue = result; + g_readComplete = true; +} + +ChannelADC g_temp; +#ifdef ADC_HAS_D5X_E5X_REGISTERS +ChannelADC g_ctat; +#endif + +void setup() { + Serial.begin(115200); + while (!Serial); + + g_temp.setReadCallback(onTempRead, nullptr); +#ifdef ADC_HAS_D5X_E5X_REGISTERS + // On SAMD5x/SAME5x, the PTAT/CTAT sensors are routed by SUPC.VREF.TSSEL + // with VREFOE disabled. Use a non-SUPC ADC reference for these channels. + g_temp.setReference(AdcRefSel::ADC_REFSEL_INTVCC1); +#else + g_temp.setReference(AdcRefSel::ADC_REFSEL_INT1V); +#endif + g_temp.setCtrlB(AdcResSel::ADC_RESSEL_12BIT, AdcPrescaler::ADC_PRESCALER_DIV32); + +#ifdef ADC_HAS_D5X_E5X_REGISTERS + g_ctat.setReadCallback(onTempRead, nullptr); + g_ctat.setReference(AdcRefSel::ADC_REFSEL_INTVCC1); + g_ctat.setCtrlB(AdcResSel::ADC_RESSEL_12BIT, AdcPrescaler::ADC_PRESCALER_DIV32); + + if (!g_temp.attach(AdcMuxPos::ADC_MUXPOS_TEMP, AdcMuxNeg::ADC_MUXNEG_GND, + AdcSampleNum::ADC_SAMPLENUM_16)) { + Serial.println("Attach PTAT failed"); + } + + if (!g_ctat.attach(AdcMuxPos::ADC_MUXPOS_TEMP_CTAT, AdcMuxNeg::ADC_MUXNEG_GND, + AdcSampleNum::ADC_SAMPLENUM_16)) { + Serial.println("Attach CTAT failed"); + } +#else + if (!g_temp.attach(AdcMuxPos::ADC_MUXPOS_TEMP, AdcMuxNeg::ADC_MUXNEG_GND, + AdcSampleNum::ADC_SAMPLENUM_16)) { + Serial.println("Attach temperature channel failed"); + } +#endif +} + +void loop() { +#ifdef ADC_HAS_D5X_E5X_REGISTERS + g_readComplete = false; + if (!g_temp.read()) { + Serial.println("PTAT async enqueue failed"); + delay(1000); + return; + } + while (!g_readComplete) { + AdcEngine::instance().service(); + } + const uint16_t ptat = g_lastValue; + + g_readComplete = false; + if (!g_ctat.read()) { + Serial.println("CTAT async enqueue failed"); + delay(1000); + return; + } + while (!g_readComplete) { + AdcEngine::instance().service(); + } + const uint16_t ctat = g_lastValue; + + const float tempC = analogReadTemperatureC(ptat, ctat); + Serial.print("PTAT raw: "); + Serial.print(ptat); + Serial.print(" CTAT raw: "); + Serial.print(ctat); + Serial.print(" "); + Serial.print("Async chip temp (C): "); + Serial.println(tempC, 2); +#else + g_readComplete = false; + if (!g_temp.read()) { + Serial.println("Async enqueue failed"); + delay(1000); + return; + } + while (!g_readComplete) { + AdcEngine::instance().service(); + } + + const uint16_t raw = g_lastValue; + const float tempC = analogReadTemperatureC(raw); + + Serial.print("Async raw: "); + Serial.print(raw); + Serial.print(" tempC: "); + Serial.print(tempC, 2); + if (tempC >= 20.0f && tempC <= 24.0f) { + Serial.println(" (PASS 20-24C)"); + } else { + Serial.println(" (OUTSIDE 20-24C)"); + } +#endif + + delay(1000); +} diff --git a/libraries/ADC/examples/BasicRead/BasicRead.ino b/libraries/ADC/examples/BasicRead/BasicRead.ino new file mode 100644 index 000000000..161d1c81d --- /dev/null +++ b/libraries/ADC/examples/BasicRead/BasicRead.ino @@ -0,0 +1,25 @@ +#include + +ChannelADC adc; + +void setup() { + Serial.begin(115200); + while (!Serial) + ; + + // Attach a single-ended channel on A0. + if (!adc.attach(A0)) + Serial.println("ADC attach failed"); +} + +void loop() { + if (adc.read()) { + delay(5); + Serial.print("A0: "); + Serial.println(adc.value()); + } else { + Serial.println("ADC read enqueue failed"); + } + + delay(250); +} diff --git a/libraries/ADC/examples/CorrectADCResponse/CorrectADCResponse.ino b/libraries/ADC/examples/CorrectADCResponse/CorrectADCResponse.ino new file mode 100644 index 000000000..1bbbd5990 --- /dev/null +++ b/libraries/ADC/examples/CorrectADCResponse/CorrectADCResponse.ino @@ -0,0 +1,201 @@ +/* + ADC correction calibration example using the ADC library. + + This follows the SAMD_AnalogCorrection workflow, but applies values through + ChannelADC::setCalibration() instead of analogReadCorrection(). + + Wiring: + - A1 -> GND + - A2 -> 3.3V +*/ + +#include + +#define ADC_GND_PIN A1 +#define ADC_3V3_PIN A2 + +#define ADC_READS_SHIFT 8 +#define ADC_READS_COUNT (1 << ADC_READS_SHIFT) + +#define ADC_MIN_GAIN 0x0400 +#define ADC_UNITY_GAIN 0x0800 +#define ADC_MAX_GAIN (0x1000 - 1) +#define ADC_RESOLUTION_BITS 12 +#define ADC_RANGE (1 << ADC_RESOLUTION_BITS) +#define ADC_TOP_VALUE (ADC_RANGE - 1) + +#define MAX_TOP_VALUE_READS 10 + +ChannelADC gndChannel; +ChannelADC vccChannel; + +static uint16_t readChannelAverage(ChannelADC &channel) { + uint32_t accumulator = 0; + + for (int i = 0; i < ADC_READS_COUNT; ++i) { + if (channel.read()) { + delayMicroseconds(200); + accumulator += channel.value(); + } + } + + return static_cast(accumulator >> ADC_READS_SHIFT); +} + +static uint16_t readGndLevel() { + const uint16_t value = readChannelAverage(gndChannel); + Serial.print("ADC(GND) = "); + Serial.println(value); + return value; +} + +static uint16_t read3V3Level() { + uint16_t value = readChannelAverage(vccChannel); + + if (value < (ADC_RANGE >> 1)) { + value += ADC_RANGE; + } + + Serial.print("ADC(3.3V) = "); + Serial.println(value); + return value; +} + +static void applyCalibration(int offset, uint16_t gain) { + gndChannel.setCalibration(gain, static_cast(offset), true); + vccChannel.setCalibration(gain, static_cast(offset), true); +} + +void setup() { + Serial.begin(115200); + while (!Serial) + ; + + if (!gndChannel.attach(ADC_GND_PIN)) { + Serial.println("Failed to attach GND channel"); + return; + } + + if (!vccChannel.attach(ADC_3V3_PIN)) { + Serial.println("Failed to attach 3.3V channel"); + return; + } + + gndChannel.setCtrlB(AdcResSel::ADC_RESSEL_12BIT, AdcPrescaler::ADC_PRESCALER_DIV32); + vccChannel.setCtrlB(AdcResSel::ADC_RESSEL_12BIT, AdcPrescaler::ADC_PRESCALER_DIV32); + + Serial.println("\nCalibrating ADC with default correction values"); + Serial.println("\nReading GND and 3.3V ADC levels"); + Serial.print(" "); + readGndLevel(); + Serial.print(" "); + read3V3Level(); + + int offsetCorrectionValue = 0; + uint16_t gainCorrectionValue = ADC_UNITY_GAIN; + + Serial.print("\nOffset correction (@gain = "); + Serial.print(gainCorrectionValue); + Serial.println(" unity)"); + + applyCalibration(offsetCorrectionValue, gainCorrectionValue); + + for (int offset = 0; offset < 2048; ++offset) { + applyCalibration(offset, gainCorrectionValue); + + Serial.print(" Offset = "); + Serial.print(offset); + Serial.print(", "); + + if (readGndLevel() == 0) { + offsetCorrectionValue = offset; + break; + } + } + + Serial.println("\nGain correction"); + + uint8_t topValueReadsCount = 0; + uint16_t minGain = 0; + uint16_t maxGain = 0; + + applyCalibration(offsetCorrectionValue, gainCorrectionValue); + Serial.print(" Gain = "); + Serial.print(gainCorrectionValue); + Serial.print(", "); + uint16_t highLevelRead = read3V3Level(); + + if (highLevelRead < ADC_TOP_VALUE) { + for (uint16_t gain = ADC_UNITY_GAIN + 1; gain <= ADC_MAX_GAIN; ++gain) { + applyCalibration(offsetCorrectionValue, gain); + + Serial.print(" Gain = "); + Serial.print(gain); + Serial.print(", "); + highLevelRead = read3V3Level(); + + if (highLevelRead == ADC_TOP_VALUE) { + if (minGain == 0) + minGain = gain; + + if (++topValueReadsCount >= MAX_TOP_VALUE_READS) { + maxGain = minGain; + break; + } + + maxGain = gain; + } + + if (highLevelRead > ADC_TOP_VALUE) + break; + } + } else { + if (highLevelRead == ADC_TOP_VALUE) + maxGain = ADC_UNITY_GAIN; + + for (int gain = ADC_UNITY_GAIN - 1; gain >= ADC_MIN_GAIN; --gain) { + applyCalibration(offsetCorrectionValue, gain); + + Serial.print(" Gain = "); + Serial.print(gain); + Serial.print(", "); + highLevelRead = read3V3Level(); + + if (highLevelRead == ADC_TOP_VALUE) { + if (maxGain == 0) + maxGain = gain; + + minGain = gain; + } + + if (highLevelRead < ADC_TOP_VALUE) + break; + } + } + + gainCorrectionValue = (minGain + maxGain) >> 1; + applyCalibration(offsetCorrectionValue, gainCorrectionValue); + + Serial.println("\nReadings after corrections"); + Serial.print(" "); + readGndLevel(); + Serial.print(" "); + read3V3Level(); + + Serial.println("\n=================="); + Serial.println("\nCorrection values:"); + Serial.print(" Offset = "); + Serial.println(offsetCorrectionValue); + Serial.print(" Gain = "); + Serial.println(gainCorrectionValue); + Serial.println("\nApply in your sketch:"); + Serial.print(" channel.setCalibration("); + Serial.print(gainCorrectionValue); + Serial.print(", "); + Serial.print(offsetCorrectionValue); + Serial.println(", true);"); + Serial.println("\n=================="); +} + +void loop() { +} diff --git a/libraries/ADC/examples/ReadCallback/ReadCallback.ino b/libraries/ADC/examples/ReadCallback/ReadCallback.ino new file mode 100644 index 000000000..3d6785d4d --- /dev/null +++ b/libraries/ADC/examples/ReadCallback/ReadCallback.ino @@ -0,0 +1,43 @@ +#include + +ChannelADC adc; +volatile uint16_t latestValue = 0; +volatile bool sampleReady = false; + +void onAdcRead(ChannelADC *channel, uint16_t result, void *userData) { + (void)channel; + (void)userData; + latestValue = result; + sampleReady = true; +} + +void setup() { + Serial.begin(115200); + while (!Serial) + ; + + if (!adc.attach(A0)) + Serial.println("ADC attach failed"); + adc.setReadCallback(onAdcRead, nullptr); +} + +void loop() { + static uint32_t lastTriggerMs = 0; + const uint32_t now = millis(); + + if (now - lastTriggerMs >= 100) { + lastTriggerMs = now; + if (!adc.read()) + Serial.println("ADC read enqueue failed"); + } + + if (sampleReady) { + noInterrupts(); + const uint16_t value = latestValue; + sampleReady = false; + interrupts(); + + Serial.print("A0: "); + Serial.println(value); + } +} diff --git a/libraries/ADC/examples/SyncTempRegisterRead/SyncTempRegisterRead.ino b/libraries/ADC/examples/SyncTempRegisterRead/SyncTempRegisterRead.ino new file mode 100644 index 000000000..8e0c1c07f --- /dev/null +++ b/libraries/ADC/examples/SyncTempRegisterRead/SyncTempRegisterRead.ino @@ -0,0 +1,81 @@ +#include + +#ifndef ADC_HAS_D5X_E5X_REGISTERS +static inline void waitAdcSyncRaw() { + while (ADC->STATUS.bit.SYNCBUSY) { + } +} + +uint16_t readTempRawDirectSync() { + SYSCTRL->VREF.reg |= SYSCTRL_VREF_TSEN; + + const uint16_t oldCtrlB = ADC->CTRLB.reg; + const uint8_t oldSampCtrl = ADC->SAMPCTRL.reg; + const uint8_t oldAvgCtrl = ADC->AVGCTRL.reg; + const uint8_t oldRefSel = ADC->REFCTRL.bit.REFSEL; + const uint8_t oldGain = ADC->INPUTCTRL.bit.GAIN; + + ADC->CTRLA.bit.ENABLE = 0; + waitAdcSyncRaw(); + + ADC->CTRLB.reg = ADC_CTRLB_RESSEL_12BIT | ADC_CTRLB_PRESCALER_DIV256; + ADC->SAMPCTRL.reg = ADC_SAMPCTRL_SAMPLEN(0x3F); + ADC->AVGCTRL.reg = ADC_AVGCTRL_SAMPLENUM_64 | ADC_AVGCTRL_ADJRES(0x4); + + ADC->INPUTCTRL.bit.GAIN = ADC_INPUTCTRL_GAIN_1X_Val; + ADC->REFCTRL.bit.REFSEL = ADC_REFCTRL_REFSEL_INT1V_Val; + ADC->INPUTCTRL.reg = ADC_INPUTCTRL_MUXPOS(ADC_INPUTCTRL_MUXPOS_TEMP_Val) | + ADC_INPUTCTRL_MUXNEG(ADC_INPUTCTRL_MUXNEG_GND_Val); + + waitAdcSyncRaw(); + + ADC->CTRLA.bit.ENABLE = 1; + waitAdcSyncRaw(); + + ADC->SWTRIG.bit.START = 1; + while (!ADC->INTFLAG.bit.RESRDY) { + } + ADC->INTFLAG.reg = ADC_INTFLAG_RESRDY; + + ADC->SWTRIG.bit.START = 1; + while (!ADC->INTFLAG.bit.RESRDY) { + } + const uint16_t value = ADC->RESULT.reg; + + ADC->INTFLAG.reg = ADC_INTFLAG_RESRDY; + ADC->CTRLA.bit.ENABLE = 0; + waitAdcSyncRaw(); + + ADC->CTRLB.reg = oldCtrlB; + ADC->SAMPCTRL.reg = oldSampCtrl; + ADC->AVGCTRL.reg = oldAvgCtrl; + ADC->INPUTCTRL.bit.GAIN = oldGain; + ADC->REFCTRL.bit.REFSEL = oldRefSel; + waitAdcSyncRaw(); + + return value; +} +#endif + +void setup() { + Serial.begin(115200); + while (!Serial) { + } + +#ifndef ADC_HAS_D5X_E5X_REGISTERS + Serial.println("Sync temperature register read example"); +#else + Serial.println("SyncTempRegisterRead is SAMD21-specific (direct ADC register path)."); +#endif +} + +void loop() { +#ifndef ADC_HAS_D5X_E5X_REGISTERS + const uint16_t raw = readTempRawDirectSync(); + Serial.print("Raw temp register ADC: "); + Serial.println(raw); +#else + Serial.println("Use AsyncChipTemperature example for SAMD5X."); +#endif + delay(1000); +} diff --git a/libraries/ADC/keywords.txt b/libraries/ADC/keywords.txt new file mode 100644 index 000000000..badcb3a7b --- /dev/null +++ b/libraries/ADC/keywords.txt @@ -0,0 +1,52 @@ +####################################### +# Syntax Coloring Map For ADC +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +ChannelADC KEYWORD1 +AdcEngine KEYWORD1 +AdcSampleNum KEYWORD1 +AdcWinMode KEYWORD1 +AdcRefSel KEYWORD1 +AdcGain KEYWORD1 +AdcResSel KEYWORD1 +AdcPrescaler KEYWORD1 +AdcMuxPos KEYWORD1 +AdcMuxNeg KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +analogReadCorrection KEYWORD2 +attach KEYWORD2 +read KEYWORD2 +value KEYWORD2 +setWindow KEYWORD2 +clearWindow KEYWORD2 +windowEnabled KEYWORD2 +setWindowCallback KEYWORD2 +setReadCallback KEYWORD2 +setEventControl KEYWORD2 +setCtrlB KEYWORD2 +setReference KEYWORD2 +setGain KEYWORD2 +setCalibration KEYWORD2 +end KEYWORD2 +begin KEYWORD2 +instance KEYWORD2 +enqueue KEYWORD2 +registerChannel KEYWORD2 +unregisterChannel KEYWORD2 +service KEYWORD2 +resultByteForMux KEYWORD2 +enabledMask KEYWORD2 + +####################################### +# Constants (LITERAL1) +####################################### + +ADC_MUX_SOURCE_INVALID LITERAL1 diff --git a/libraries/ADC/library.properties b/libraries/ADC/library.properties new file mode 100644 index 000000000..da11e1842 --- /dev/null +++ b/libraries/ADC/library.properties @@ -0,0 +1,10 @@ +name=ADC +version=1.0.0 +author=Cal ABEL +maintainer=Adafruit Industries +sentence=Asynchronous ADC channel wrapper for SAMD devices. +paragraph=Provides queued ADC reads with optional callback handling and per-channel configuration. +category=Signal Input/Output +url=https://github.com/adafruit/ArduinoCore-samd +architectures=samd +depends=Adafruit ZeroDMA diff --git a/libraries/ADC/src/ADC.cpp b/libraries/ADC/src/ADC.cpp new file mode 100644 index 000000000..3c9e63803 --- /dev/null +++ b/libraries/ADC/src/ADC.cpp @@ -0,0 +1,977 @@ +#include "ADC.h" + +#include +#include + +#ifdef ADC_HAS_D5X_E5X_REGISTERS +#ifndef NVMCTRL_TEMP_LOG +#ifdef NVMCTRL_TEMP_LOG_W0 +#define NVMCTRL_TEMP_LOG NVMCTRL_TEMP_LOG_W0 +#endif +#endif +#endif + +static inline float decToFrac(uint8_t val) { + // Temperature fuse decimal part is 4 bits only (0-15): + // 0-9 represent tenths (0.0-0.9), 10-15 represent hundredths (0.10-0.15) + if (val < 10) + return static_cast(val) / 10.0f; + return static_cast(val) / 100.0f; +} + +#ifdef ADC_HAS_D5X_E5X_REGISTERS +float analogReadTemperatureC(uint16_t tp, uint16_t tc) { + 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 ADC_HAS_D5X_E5X_REGISTERS + adc = ADC0; +#else + adc = ADC; +#endif + + adc->OFFSETCORR.reg = static_cast(offset); + adc->GAINCORR.reg = gain; + adc->CTRLB.bit.CORREN = 1; + +#ifdef ADC_HAS_D5X_E5X_REGISTERS + while (adc->SYNCBUSY.reg); +#else + while (adc->STATUS.bit.SYNCBUSY); +#endif +} + +namespace { +constexpr uint8_t kMaxAdjres = 4; +constexpr uint8_t kAdcPendSvServiceId = PendSV::kMaxServices - 1; +constexpr uint32_t kAdcNvicPriority = (1u << __NVIC_PRIO_BITS) - 1u; + +#ifdef ADC_HAS_D5X_E5X_REGISTERS +#ifdef ADC_EVCTRL_SYNCEI +constexpr uint8_t kAdcEvctrlSynceiBit = ADC_EVCTRL_SYNCEI; +#elif defined(ADC_EVCTRL_FLUSHEI) +constexpr uint8_t kAdcEvctrlSynceiBit = ADC_EVCTRL_FLUSHEI; +#else +constexpr uint8_t kAdcEvctrlSynceiBit = 0u; +#endif +#else +constexpr uint8_t kAdcEvctrlSynceiBit = ADC_EVCTRL_SYNCEI; +#endif + +uint8_t adcDmacResrdyTrigger() { +#ifdef ADC_HAS_D5X_E5X_REGISTERS + return ADC0_DMAC_ID_RESRDY; +#else + return ADC_DMAC_ID_RESRDY; +#endif +} + +inline Adc *adcInstance() { +#ifdef ADC_HAS_D5X_E5X_REGISTERS + return ADC0; +#else + return ADC; +#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: +// 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 ADC_HAS_D5X_E5X_REGISTERS + return 2; +#else + return 1; +#endif +} + +IRQn_Type adcIrqAt(uint8_t index) { +#ifdef ADC_HAS_D5X_E5X_REGISTERS + return (index == 0) ? ADC0_0_IRQn : ADC0_1_IRQn; +#else + (void)index; + return ADC_IRQn; +#endif +} + +void adcPendSvService(uint8_t serviceId, void *context) { + (void)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 ADC_MUX_SOURCE_INVALID; + + const EAnalogChannel channel = g_APinDescription[arduinoPin].ulADCChannelNumber; + if (channel == No_ADC_Channel) + return ADC_MUX_SOURCE_INVALID; + + return static_cast(channel); +} + +void configureAnalogPin(uint8_t arduinoPin) { + if (arduinoPin >= PINS_COUNT) + return; + + const EAnalogChannel channel = g_APinDescription[arduinoPin].ulADCChannelNumber; + if (channel == No_ADC_Channel) + return; + + pinPeripheral(arduinoPin, PIO_ANALOG); +} +} // namespace + +uint8_t ADC_MUXPOS_PIN(uint8_t pin) { + const uint8_t mux = pinToMux(pin); + if (mux == ADC_MUX_SOURCE_INVALID) + return ADC_MUX_SOURCE_INVALID; + + return mux; +} + +uint8_t ADC_MUXNEG_PIN(uint8_t pin) { + const uint8_t mux = pinToMux(pin); + if (mux == ADC_MUX_SOURCE_INVALID) + return ADC_MUX_SOURCE_INVALID; + + return mux; +} + +AdcEngine &AdcEngine::instance() { + static AdcEngine adcEngine; + return adcEngine; +} + +bool AdcEngine::begin() { + if (initialized_) + return true; + + queueHead_ = 0; + queueTail_ = 0; + queueCount_ = 0; + activeChannel_ = nullptr; + activeMonitorMode_ = false; + pendingChannel_ = nullptr; + pendingCallback_ = nullptr; + pendingUserData_ = nullptr; + pendingResult_ = 0; + enabledMask_ = 0; + registeredCount_ = 0; + monitorCursor_ = 0; + pendingStartConversion_ = false; + conversionState_ = ConversionState::Idle; + + for (uint8_t i = 0; i < kResultContainerSize; ++i) { + queue_[i] = nullptr; + resultContainer_[i] = 0; + registeredChannels_[i] = nullptr; + } + + if (!PendSV::instance().registerService(kAdcPendSvServiceId, adcPendSvService, nullptr)) + return false; + + if (dma_.allocate() != DMA_STATUS_OK) { + PendSV::instance().clearService(kAdcPendSvServiceId); + return false; + } + + dma_.setTrigger(adcDmacResrdyTrigger()); + dma_.setAction(DMA_TRIGGER_ACTON_BEAT); + dma_.setCallback(AdcEngine::dmaDoneCallback, DMA_CALLBACK_TRANSFER_DONE); + 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); + 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->INTENCLR.reg = 0x0F; + adc->INTFLAG.reg = ADC_INTFLAG_RESRDY | ADC_INTFLAG_WINMON; + 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 *const adc = adcInstance(); + + 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; + pendingStartConversion_ = false; + conversionState_ = ConversionState::Idle; + activeChannel_ = nullptr; + activeMonitorMode_ = false; + pendingChannel_ = nullptr; + pendingCallback_ = nullptr; + pendingUserData_ = nullptr; + pendingResult_ = 0; + enabledMask_ = 0; + queueHead_ = 0; + queueTail_ = 0; + queueCount_ = 0; + registeredCount_ = 0; + monitorCursor_ = 0; + + for (uint8_t i = 0; i < kResultContainerSize; ++i) + registeredChannels_[i] = nullptr; +} + +bool AdcEngine::enqueue(ChannelADC *channel) { + if (!initialized_ || channel == nullptr) + return false; + + const int8_t index = muxPosToResultIndex(channel->muxPos_); + if (index < 0 || channel->enqueued_) + return false; + + if (!pushQueue(channel)) + return false; + + channel->enqueued_ = true; + enabledMask_ |= (1u << channel->muxPos_); + + if (activeChannel_ == nullptr) { + ChannelADC *next = popQueue(); + if (next != nullptr && !applyChannelAndStart(next)) { + next->enqueued_ = false; + enabledMask_ &= ~(1u << next->muxPos_); + return false; + } + } + + return true; +} + +bool AdcEngine::registerChannel(ChannelADC *channel) { + if (channel == nullptr) + return false; + + for (uint8_t i = 0; i < registeredCount_; ++i) { + if (registeredChannels_[i] == channel) + return true; + } + + if (registeredCount_ >= kResultContainerSize) + return false; + + registeredChannels_[registeredCount_++] = channel; + return true; +} + +void AdcEngine::unregisterChannel(ChannelADC *channel) { + if (channel == nullptr || registeredCount_ == 0) + return; + + for (uint8_t i = 0; i < registeredCount_; ++i) { + if (registeredChannels_[i] != channel) + continue; + + for (uint8_t j = i; j + 1 < registeredCount_; ++j) + registeredChannels_[j] = registeredChannels_[j + 1]; + + registeredChannels_[registeredCount_ - 1] = nullptr; + --registeredCount_; + if (monitorCursor_ >= registeredCount_) + monitorCursor_ = 0; + return; + } +} + +void AdcEngine::service() { + if (!initialized_) + return; + + onPendSv(); + + if (activeChannel_ == nullptr) { + ChannelADC *next = popQueue(); + if (next != nullptr && !applyChannelAndStart(next)) { + next->enqueued_ = false; + enabledMask_ &= ~(1u << next->muxPos_); + } + } + + if (activeChannel_ == nullptr && queueCount_ == 0) + startMonitorIfIdle(); +} + +uint8_t AdcEngine::resultByteForMux(uint8_t muxPos) const { + const int8_t index = muxPosToResultIndex(muxPos); + if (index < 0) + return 0; + + return resultContainer_[index]; +} + +uint32_t AdcEngine::enabledMask() const { + return enabledMask_; +} + +void AdcEngine::onResrdyIsr() { + 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) { + 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; +} + +void AdcEngine::onPendSv() { + if (!pendSvPending_) + return; + + pendSvPending_ = false; + + if (pendingStartConversion_) { + pendingStartConversion_ = false; + if (!startActiveDmaConversion()) { + ChannelADC *channel = activeChannel_; + if (channel != nullptr) { + channel->enqueued_ = false; + enabledMask_ &= ~(1u << channel->muxPos_); + } + activeChannel_ = nullptr; + activeMonitorMode_ = false; + conversionState_ = ConversionState::Idle; + } + } + + if (pendingCallback_ != nullptr && pendingChannel_ != nullptr) + pendingCallback_(pendingChannel_, pendingResult_, pendingUserData_); + + pendingCallback_ = nullptr; + pendingChannel_ = nullptr; + pendingUserData_ = nullptr; + pendingResult_ = 0; +} + +int8_t AdcEngine::muxPosToResultIndex(uint8_t muxPos) { + if (muxPos > kMuxMax) + return -1; + + if (muxPos >= kSkippedMuxStart && muxPos <= kSkippedMuxEnd) + return -1; + + if (muxPos < kSkippedMuxStart) + return static_cast(muxPos); + + return static_cast(muxPos - (kSkippedMuxEnd - kSkippedMuxStart + 1)); +} + +bool AdcEngine::pushQueue(ChannelADC *channel) { + if (queueCount_ >= kResultContainerSize) + return false; + + queue_[queueTail_] = channel; + queueTail_ = static_cast((queueTail_ + 1) % kResultContainerSize); + ++queueCount_; + return true; +} + +ChannelADC *AdcEngine::popQueue() { + if (queueCount_ == 0) + return nullptr; + + ChannelADC *channel = queue_[queueHead_]; + queue_[queueHead_] = nullptr; + queueHead_ = static_cast((queueHead_ + 1) % kResultContainerSize); + --queueCount_; + return channel; +} + +bool AdcEngine::applyChannelAndStart(ChannelADC *channel, bool monitorMode) { + if (channel == nullptr) + return false; + + 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; + +#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)); + 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->SAMPCTRL.reg = sampleTimeForMux(channel->muxPos_); + + 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_ ? kAdcEvctrlSynceiBit : 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 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; + + 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_ ? kAdcEvctrlSynceiBit : 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->CTRLA.bit.ENABLE = 1; + waitAdcSync(); + + adc->INTENCLR.reg = 0x0F; + adc->INTFLAG.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; + + 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; + + dma_.changeDescriptor(dmaDescriptor_, (void *)&adc->RESULT.reg, + (void *)&dmaLatestResult_, 1); + if (dma_.startJob() != DMA_STATUS_OK) + return false; + + dmaActive_ = true; + conversionState_ = ConversionState::Sampling; + + startConversion(); + return true; +} + +bool AdcEngine::startMonitorIfIdle() { + if (!initialized_ || activeChannel_ != nullptr || registeredCount_ == 0) + return false; + + for (uint8_t i = 0; i < registeredCount_; ++i) { + const uint8_t idx = static_cast((monitorCursor_ + i) % registeredCount_); + ChannelADC *candidate = registeredChannels_[idx]; + if (candidate == nullptr || !candidate->initialized_ || !candidate->monitorConfigured()) + continue; + + monitorCursor_ = static_cast((idx + 1) % registeredCount_); + return applyChannelAndStart(candidate, true); + } + + return false; +} + +void AdcEngine::waitAdcSync() const { + Adc *const adc = adcInstance(); +#ifdef ADC_HAS_D5X_E5X_REGISTERS + 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; + engine.conversionState_ = ConversionState::Idle; + + ChannelADC *channel = engine.activeChannel_; + if (channel == nullptr) + return; + + const uint16_t result = engine.dmaLatestResult_; + const int8_t resultIndex = muxPosToResultIndex(channel->muxPos_); + if (resultIndex >= 0) + engine.resultContainer_[resultIndex] = static_cast(result & 0xFF); + + channel->value_ = result; + channel->hasFreshValue_ = true; + + if (engine.activeMonitorMode_) { + engine.activeChannel_ = nullptr; + engine.activeMonitorMode_ = false; + return; + } + + channel->enqueued_ = false; + engine.enabledMask_ &= ~(1u << channel->muxPos_); + + engine.pendingChannel_ = channel; + engine.pendingResult_ = result; + engine.pendingCallback_ = channel->onReadComplete_; + engine.pendingUserData_ = channel->userData_; + engine.activeChannel_ = nullptr; + engine.activeMonitorMode_ = false; + + engine.pendSvPending_ = true; + PendSV::instance().setPending(kAdcPendSvServiceId); +} + +bool ChannelADC::setAttachedSources(uint8_t muxPos, uint8_t muxNeg, AdcSampleNum sampleNum) { + if (muxPos == ADC_MUX_SOURCE_INVALID || muxNeg == ADC_MUX_SOURCE_INVALID) + return false; + + muxPos_ = muxPos; + muxNeg_ = muxNeg; + if (muxNeg_ != static_cast(AdcMuxNeg::ADC_MUXNEG_GND)) + differentialMode_ = true; + sampleNum_ = sampleNum; + adjres_ = sampleNumCodeToAdjres(sampleNumToCode(sampleNum)); + attached_ = true; + return true; +} + +bool ChannelADC::attach(uint8_t pinPos, uint8_t pinNeg, AdcSampleNum sampleNum) { + AdcEngine &engine = AdcEngine::instance(); + if (!engine.begin()) + return false; + + if (!registered_ && !engine.registerChannel(this)) + return false; + + configureAnalogPin(pinPos); + configureAnalogPin(pinNeg); + + registered_ = true; + initialized_ = true; + return setAttachedSources(ADC_MUXPOS_PIN(pinPos), ADC_MUXNEG_PIN(pinNeg), sampleNum); +} + +bool ChannelADC::attach(uint8_t pinPos, AdcMuxNeg muxNeg, AdcSampleNum sampleNum) { + AdcEngine &engine = AdcEngine::instance(); + if (!engine.begin()) + return false; + + if (!registered_ && !engine.registerChannel(this)) + return false; + + configureAnalogPin(pinPos); + + registered_ = true; + initialized_ = true; + return setAttachedSources(ADC_MUXPOS_PIN(pinPos), static_cast(muxNeg), sampleNum); +} + +bool ChannelADC::attach(AdcMuxPos muxPos, uint8_t pinNeg, AdcSampleNum sampleNum) { + AdcEngine &engine = AdcEngine::instance(); + if (!engine.begin()) + return false; + + if (!registered_ && !engine.registerChannel(this)) + return false; + + configureAnalogPin(pinNeg); + + registered_ = true; + initialized_ = true; + return setAttachedSources(static_cast(muxPos), ADC_MUXNEG_PIN(pinNeg), sampleNum); +} + +bool ChannelADC::attach(AdcMuxPos muxPos, AdcMuxNeg muxNeg, AdcSampleNum sampleNum) { + AdcEngine &engine = AdcEngine::instance(); + if (!engine.begin()) + return false; + + if (!registered_ && !engine.registerChannel(this)) + return false; + + registered_ = true; + initialized_ = true; + return setAttachedSources(static_cast(muxPos), static_cast(muxNeg), + sampleNum); +} + +void ChannelADC::setWindow(AdcWinMode mode, uint16_t lower, uint16_t upper) { + windowLower_ = lower; + windowUpper_ = upper; + winMode_ = mode; + windowEnabled_ = (mode != AdcWinMode::ADC_WINMODE_DISABLE); +} + +void ChannelADC::clearWindow() { + windowEnabled_ = false; + winMode_ = AdcWinMode::ADC_WINMODE_DISABLE; +} + +bool ChannelADC::windowEnabled() const { + return windowEnabled_; +} + +void ChannelADC::setWindowCallback(Callback callback, void *userData) { + onWindowMonitor_ = callback; + windowUserData_ = (userData != nullptr) ? userData : this; +} + +void ChannelADC::setReadCallback(Callback callback, void *userData) { + onReadComplete_ = callback; + userData_ = userData; +} + +void ChannelADC::setEventControl(bool winmonEo, bool resrdyEo, bool syncei, bool startei) { + evWinmonEo_ = winmonEo; + evResrdyEo_ = resrdyEo; + evSyncei_ = syncei; + evStartei_ = startei; +} + +void ChannelADC::setCtrlB(AdcResSel resolution, AdcPrescaler prescaler, bool freeRun, + bool leftAdjust, bool differentialMode, bool correctionEnable) { + ressel_ = resolution; + prescaler_ = prescaler; + freeRun_ = freeRun; + leftAdjust_ = leftAdjust; + differentialMode_ = differentialMode; + corrEnabled_ = correctionEnable; +} + +void ChannelADC::setReference(AdcRefSel reference) { + refSel_ = static_cast(reference); +} + +void ChannelADC::setGain(AdcGain gain) { + gain_ = gain; +} + +void ChannelADC::setCalibration(uint16_t gainCorr, int16_t offsetCorr, bool enableCorrection) { + gainCorr_ = gainCorr; + offsetCorr_ = offsetCorr; + corrEnabled_ = enableCorrection; +} + +bool ChannelADC::monitorConfigured() const { + return windowEnabled_; +} + +void ChannelADC::onSyncReadComplete(ChannelADC *channel, uint16_t result, void *userData) { + (void)result; + + ChannelADC *target = static_cast(userData); + if (target == nullptr) + target = channel; + + if (target != nullptr) + target->syncReadDone_ = true; +} + +bool ChannelADC::read() { + if (!initialized_ || !attached_) + return false; + + hasFreshValue_ = false; + syncReadDone_ = false; + + AdcEngine &engine = AdcEngine::instance(); + const bool syncRead = (onReadComplete_ == nullptr); + Callback savedCallback = onReadComplete_; + void *savedUserData = userData_; + + if (syncRead) { + onReadComplete_ = &ChannelADC::onSyncReadComplete; + userData_ = this; + } + + if (!engine.enqueue(this)) { + if (syncRead) { + onReadComplete_ = savedCallback; + userData_ = savedUserData; + } + return false; + } + + if (syncRead) { + const uint32_t startMs = millis(); + while (!syncReadDone_) { + engine.service(); + if ((millis() - startMs) > 50u) { + onReadComplete_ = savedCallback; + userData_ = savedUserData; + return false; + } + } + + onReadComplete_ = savedCallback; + userData_ = savedUserData; + } + + return true; +} + +uint16_t ChannelADC::value() const { + return value_; +} + +void ChannelADC::end() { + initialized_ = false; + attached_ = false; + registered_ = false; + enqueued_ = false; + hasFreshValue_ = false; + AdcEngine::instance().unregisterChannel(this); +} + +#ifdef ADC_HAS_D5X_E5X_REGISTERS +extern "C" void ADC0_0_Handler(void) { + AdcEngine::instance().onResrdyIsr(); +} + +extern "C" void ADC0_1_Handler(void) { + AdcEngine::instance().onResrdyIsr(); +} +#else +extern "C" void ADC_Handler(void) { + AdcEngine::instance().onResrdyIsr(); +} +#endif diff --git a/libraries/ADC/src/ADC.h b/libraries/ADC/src/ADC.h new file mode 100644 index 000000000..23ede462f --- /dev/null +++ b/libraries/ADC/src/ADC.h @@ -0,0 +1,345 @@ +#pragma once + +#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 ADC_HAS_D5X_E5X_REGISTERS +float analogReadTemperatureC(uint16_t ptatReading, uint16_t ctatReading); +#else +float analogReadTemperatureC(uint16_t adcReading); +#endif + +enum class AdcSampleNum : uint8_t { + ADC_SAMPLENUM_1 = ADC_AVGCTRL_SAMPLENUM_1_Val, + ADC_SAMPLENUM_2 = ADC_AVGCTRL_SAMPLENUM_2_Val, + ADC_SAMPLENUM_4 = ADC_AVGCTRL_SAMPLENUM_4_Val, + ADC_SAMPLENUM_8 = ADC_AVGCTRL_SAMPLENUM_8_Val, + ADC_SAMPLENUM_16 = ADC_AVGCTRL_SAMPLENUM_16_Val, + ADC_SAMPLENUM_32 = ADC_AVGCTRL_SAMPLENUM_32_Val, + ADC_SAMPLENUM_64 = ADC_AVGCTRL_SAMPLENUM_64_Val, + ADC_SAMPLENUM_128 = ADC_AVGCTRL_SAMPLENUM_128_Val, + ADC_SAMPLENUM_256 = ADC_AVGCTRL_SAMPLENUM_256_Val, + ADC_SAMPLENUM_512 = ADC_AVGCTRL_SAMPLENUM_512_Val, + ADC_SAMPLENUM_1024 = ADC_AVGCTRL_SAMPLENUM_1024_Val, +}; + +enum class AdcWinMode : uint8_t { +#ifdef ADC_HAS_D5X_E5X_REGISTERS + ADC_WINMODE_DISABLE = ADC_CTRLB_WINMODE_DISABLE_Val, ///< No window mode (disabled) + ADC_WINMODE_MODE1 = ADC_CTRLB_WINMODE_MODE1_Val, ///< Window mode 1: RESULT > WINLT. + ADC_WINMODE_MODE2 = ADC_CTRLB_WINMODE_MODE2_Val, ///< Window mode 2: RESULT < WINUT. + ADC_WINMODE_MODE3 = ADC_CTRLB_WINMODE_MODE3_Val, ///< Window mode 3: WINLT < RESULT < WINUT. + ADC_WINMODE_MODE4 = ADC_CTRLB_WINMODE_MODE4_Val, ///< Window mode 4: !(WINLT < RESULT < WINUT). +#else + ADC_WINMODE_DISABLE = ADC_WINCTRL_WINMODE_DISABLE_Val, ///< No window mode (disabled) + ADC_WINMODE_MODE1 = ADC_WINCTRL_WINMODE_MODE1_Val, ///< Window mode 1: RESULT > WINLT. + ADC_WINMODE_MODE2 = ADC_WINCTRL_WINMODE_MODE2_Val, ///< Window mode 2: RESULT < WINUT. + ADC_WINMODE_MODE3 = ADC_WINCTRL_WINMODE_MODE3_Val, ///< Window mode 3: WINLT < RESULT < WINUT. + ADC_WINMODE_MODE4 = + ADC_WINCTRL_WINMODE_MODE4_Val, ///< Window mode 4: !(WINLT < RESULT < WINUT). +#endif +}; + +enum class AdcRefSel : uint8_t { +#ifdef ADC_HAS_D5X_E5X_REGISTERS + ADC_REFSEL_INT1V = ADC_REFCTRL_REFSEL_INTREF_Val, +#else + ADC_REFSEL_INT1V = ADC_REFCTRL_REFSEL_INT1V_Val, +#endif + ADC_REFSEL_INTVCC0 = ADC_REFCTRL_REFSEL_INTVCC0_Val, + ADC_REFSEL_INTVCC1 = ADC_REFCTRL_REFSEL_INTVCC1_Val, + ADC_REFSEL_AREFA = ADC_REFCTRL_REFSEL_AREFA_Val, + ADC_REFSEL_AREFB = ADC_REFCTRL_REFSEL_AREFB_Val, +}; + +enum class AdcGain : uint8_t { +#ifdef ADC_HAS_D5X_E5X_REGISTERS + ADC_GAIN_1X = 0x0, + ADC_GAIN_2X = 0x1, + ADC_GAIN_4X = 0x2, + ADC_GAIN_8X = 0x3, + ADC_GAIN_16X = 0x4, + ADC_GAIN_1_DIV_2 = 0xF, +#else + ADC_GAIN_1X = ADC_INPUTCTRL_GAIN_1X_Val, + ADC_GAIN_2X = ADC_INPUTCTRL_GAIN_2X_Val, + ADC_GAIN_4X = ADC_INPUTCTRL_GAIN_4X_Val, + ADC_GAIN_8X = ADC_INPUTCTRL_GAIN_8X_Val, + ADC_GAIN_16X = ADC_INPUTCTRL_GAIN_16X_Val, + ADC_GAIN_1_DIV_2 = ADC_INPUTCTRL_GAIN_DIV2_Val, +#endif +}; + +enum class AdcResSel : uint8_t { + ADC_RESSEL_12BIT = ADC_CTRLB_RESSEL_12BIT_Val, + ADC_RESSEL_16BIT = ADC_CTRLB_RESSEL_16BIT_Val, + ADC_RESSEL_10BIT = ADC_CTRLB_RESSEL_10BIT_Val, + ADC_RESSEL_8BIT = ADC_CTRLB_RESSEL_8BIT_Val, +}; + +enum class AdcPrescaler : uint8_t { +#ifdef ADC_HAS_D5X_E5X_REGISTERS + ADC_PRESCALER_DIV4 = ADC_CTRLA_PRESCALER_DIV4_Val, + ADC_PRESCALER_DIV8 = ADC_CTRLA_PRESCALER_DIV8_Val, + ADC_PRESCALER_DIV16 = ADC_CTRLA_PRESCALER_DIV16_Val, + ADC_PRESCALER_DIV32 = ADC_CTRLA_PRESCALER_DIV32_Val, + ADC_PRESCALER_DIV64 = ADC_CTRLA_PRESCALER_DIV64_Val, + ADC_PRESCALER_DIV128 = ADC_CTRLA_PRESCALER_DIV128_Val, + ADC_PRESCALER_DIV256 = ADC_CTRLA_PRESCALER_DIV256_Val, +#else + ADC_PRESCALER_DIV4 = ADC_CTRLB_PRESCALER_DIV4_Val, + ADC_PRESCALER_DIV8 = ADC_CTRLB_PRESCALER_DIV8_Val, + ADC_PRESCALER_DIV16 = ADC_CTRLB_PRESCALER_DIV16_Val, + ADC_PRESCALER_DIV32 = ADC_CTRLB_PRESCALER_DIV32_Val, + ADC_PRESCALER_DIV64 = ADC_CTRLB_PRESCALER_DIV64_Val, + ADC_PRESCALER_DIV128 = ADC_CTRLB_PRESCALER_DIV128_Val, + ADC_PRESCALER_DIV256 = ADC_CTRLB_PRESCALER_DIV256_Val, + ADC_PRESCALER_DIV512 = ADC_CTRLB_PRESCALER_DIV512_Val, +#endif +}; + +enum class AdcMuxPos : uint8_t { +#ifdef ADC_HAS_D5X_E5X_REGISTERS + ADC_MUXPOS_PIN0 = ADC_INPUTCTRL_MUXPOS_AIN0_Val, + ADC_MUXPOS_PIN1 = ADC_INPUTCTRL_MUXPOS_AIN1_Val, + ADC_MUXPOS_PIN2 = ADC_INPUTCTRL_MUXPOS_AIN2_Val, + ADC_MUXPOS_PIN3 = ADC_INPUTCTRL_MUXPOS_AIN3_Val, + ADC_MUXPOS_PIN4 = ADC_INPUTCTRL_MUXPOS_AIN4_Val, + ADC_MUXPOS_PIN5 = ADC_INPUTCTRL_MUXPOS_AIN5_Val, + ADC_MUXPOS_PIN6 = ADC_INPUTCTRL_MUXPOS_AIN6_Val, + ADC_MUXPOS_PIN7 = ADC_INPUTCTRL_MUXPOS_AIN7_Val, + ADC_MUXPOS_PIN8 = ADC_INPUTCTRL_MUXPOS_AIN8_Val, + ADC_MUXPOS_PIN9 = ADC_INPUTCTRL_MUXPOS_AIN9_Val, + ADC_MUXPOS_PIN10 = ADC_INPUTCTRL_MUXPOS_AIN10_Val, + ADC_MUXPOS_PIN11 = ADC_INPUTCTRL_MUXPOS_AIN11_Val, + ADC_MUXPOS_PIN12 = ADC_INPUTCTRL_MUXPOS_AIN12_Val, + ADC_MUXPOS_PIN13 = ADC_INPUTCTRL_MUXPOS_AIN13_Val, + ADC_MUXPOS_PIN14 = ADC_INPUTCTRL_MUXPOS_AIN14_Val, + ADC_MUXPOS_PIN15 = ADC_INPUTCTRL_MUXPOS_AIN15_Val, + 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 ADC_HAS_D5X_E5X_REGISTERS + ADC_MUXNEG_PIN0 = ADC_INPUTCTRL_MUXNEG_AIN0_Val, + ADC_MUXNEG_PIN1 = ADC_INPUTCTRL_MUXNEG_AIN1_Val, + ADC_MUXNEG_PIN2 = ADC_INPUTCTRL_MUXNEG_AIN2_Val, + ADC_MUXNEG_PIN3 = ADC_INPUTCTRL_MUXNEG_AIN3_Val, + ADC_MUXNEG_PIN4 = ADC_INPUTCTRL_MUXNEG_AIN4_Val, + ADC_MUXNEG_PIN5 = ADC_INPUTCTRL_MUXNEG_AIN5_Val, + ADC_MUXNEG_PIN6 = ADC_INPUTCTRL_MUXNEG_AIN6_Val, + ADC_MUXNEG_PIN7 = ADC_INPUTCTRL_MUXNEG_AIN7_Val, + ADC_MUXNEG_IOGND = 0x19, +#else + ADC_MUXNEG_PIN0 = ADC_INPUTCTRL_MUXNEG_PIN0_Val, + ADC_MUXNEG_PIN1 = ADC_INPUTCTRL_MUXNEG_PIN1_Val, + ADC_MUXNEG_PIN2 = ADC_INPUTCTRL_MUXNEG_PIN2_Val, + ADC_MUXNEG_PIN3 = ADC_INPUTCTRL_MUXNEG_PIN3_Val, + ADC_MUXNEG_PIN4 = ADC_INPUTCTRL_MUXNEG_PIN4_Val, + ADC_MUXNEG_PIN5 = ADC_INPUTCTRL_MUXNEG_PIN5_Val, + ADC_MUXNEG_PIN6 = ADC_INPUTCTRL_MUXNEG_PIN6_Val, + ADC_MUXNEG_PIN7 = ADC_INPUTCTRL_MUXNEG_PIN7_Val, + ADC_MUXNEG_IOGND = ADC_INPUTCTRL_MUXNEG_IOGND_Val, +#endif + ADC_MUXNEG_GND = ADC_INPUTCTRL_MUXNEG_GND_Val, +}; + +class ChannelADC; + +class AdcEngine { + public: + static constexpr uint8_t kMuxMin = 0x00; + static constexpr uint8_t kMuxMax = 0x1D; + static constexpr uint8_t kSkippedMuxStart = 0x14; + static constexpr uint8_t kSkippedMuxEnd = 0x17; + static constexpr uint8_t kResultContainerSize = 26; + + static AdcEngine &instance(); + + bool begin(); + void end(); + + bool enqueue(ChannelADC *channel); + bool registerChannel(ChannelADC *channel); + void unregisterChannel(ChannelADC *channel); + void service(); + + uint8_t resultByteForMux(uint8_t muxPos) const; + uint32_t enabledMask() const; + + void onResrdyIsr(); + void onPendSv(); + + private: + AdcEngine() = default; + + static int8_t muxPosToResultIndex(uint8_t muxPos); + + bool pushQueue(ChannelADC *channel); + ChannelADC *popQueue(); + + bool applyChannelAndStart(ChannelADC *channel, bool monitorMode = false); + bool startMonitorIfIdle(); + bool startActiveDmaConversion(); + void waitAdcSync() const; + inline void startConversion() const { +#ifdef ADC_HAS_D5X_E5X_REGISTERS + ADC0->SWTRIG.bit.START = 1; +#else + ADC->SWTRIG.bit.START = 1; +#endif + } + static void dmaDoneCallback(Adafruit_ZeroDMA *dma); + + ChannelADC *queue_[kResultContainerSize]{}; + uint8_t queueHead_ = 0; + uint8_t queueTail_ = 0; + uint8_t queueCount_ = 0; + + volatile uint8_t resultContainer_[kResultContainerSize]{}; + volatile uint32_t enabledMask_ = 0; + + ChannelADC *activeChannel_ = nullptr; + bool activeMonitorMode_ = false; + + ChannelADC *registeredChannels_[kResultContainerSize]{}; + uint8_t registeredCount_ = 0; + uint8_t monitorCursor_ = 0; + + ChannelADC *pendingChannel_ = nullptr; + volatile uint16_t pendingResult_ = 0; + void (*pendingCallback_)(ChannelADC *channel, uint16_t result, void *userData) = nullptr; + void *pendingUserData_ = nullptr; + + Adafruit_ZeroDMA dma_; + DmacDescriptor *dmaDescriptor_ = nullptr; + volatile uint16_t dmaLatestResult_ = 0; + bool dmaActive_ = false; + bool initialized_ = false; + volatile bool pendSvPending_ = false; + volatile bool pendingStartConversion_ = false; + enum class ConversionState : uint8_t { + Idle, + Discarding, + Sampling, + }; + volatile ConversionState conversionState_ = ConversionState::Idle; +}; + +class ChannelADC { + public: + using Callback = void (*)(ChannelADC *channel, uint16_t result, void *userData); + + bool attach(uint8_t pinPos, uint8_t pinNeg, + AdcSampleNum sampleNum = AdcSampleNum::ADC_SAMPLENUM_16); + bool attach(uint8_t pinPos, AdcMuxNeg muxNeg = AdcMuxNeg::ADC_MUXNEG_GND, + AdcSampleNum sampleNum = AdcSampleNum::ADC_SAMPLENUM_16); + bool attach(AdcMuxPos muxPos, uint8_t pinNeg, + AdcSampleNum sampleNum = AdcSampleNum::ADC_SAMPLENUM_16); + bool attach(AdcMuxPos muxPos, AdcMuxNeg muxNeg = AdcMuxNeg::ADC_MUXNEG_GND, + AdcSampleNum sampleNum = AdcSampleNum::ADC_SAMPLENUM_16); + + bool read(); + uint16_t value() const; + + void setWindow(AdcWinMode mode, uint16_t lower, uint16_t upper); + void clearWindow(); + bool windowEnabled() const; + + void setWindowCallback(Callback callback, void *userData = nullptr); + void setReadCallback(Callback callback, void *userData = nullptr); + void setEventControl(bool winmonEo, bool resrdyEo, bool syncei, bool startei); + void setCtrlB(AdcResSel resolution, AdcPrescaler prescaler, bool freeRun = false, + bool leftAdjust = false, bool differentialMode = false, + bool correctionEnable = false); + void setReference(AdcRefSel reference); + void setGain(AdcGain gain); + void setCalibration(uint16_t gainCorr, int16_t offsetCorr, bool enableCorrection = true); + void end(); + + private: + friend class AdcEngine; + + bool setAttachedSources(uint8_t encodedPos, uint8_t encodedNeg, AdcSampleNum sampleNum); + bool monitorConfigured() const; + static void onSyncReadComplete(ChannelADC *channel, uint16_t result, void *userData); + + uint8_t muxPos_ = 0; + uint8_t muxNeg_ = 0x18; + AdcSampleNum sampleNum_ = AdcSampleNum::ADC_SAMPLENUM_16; + uint8_t adjres_ = 4; + uint8_t refSel_ = static_cast(AdcRefSel::ADC_REFSEL_INTVCC1); + AdcResSel ressel_ = AdcResSel::ADC_RESSEL_16BIT; + AdcPrescaler prescaler_ = AdcPrescaler::ADC_PRESCALER_DIV4; + bool freeRun_ = false; + bool leftAdjust_ = false; + bool differentialMode_ = false; + bool enqueued_ = false; + bool registered_ = false; + volatile bool syncReadDone_ = false; + Callback onReadComplete_ = nullptr; + void *userData_ = nullptr; + + bool windowEnabled_ = false; + uint16_t windowLower_ = 0; + uint16_t windowUpper_ = 0xFFFF; + AdcWinMode winMode_ = AdcWinMode::ADC_WINMODE_DISABLE; + Callback onWindowMonitor_ = nullptr; + void *windowUserData_ = nullptr; + + bool evWinmonEo_ = false; + bool evResrdyEo_ = false; + bool evSyncei_ = false; + bool evStartei_ = false; + + bool corrEnabled_ = false; + AdcGain gain_ = AdcGain::ADC_GAIN_1_DIV_2; + uint16_t gainCorr_ = 0; + int16_t offsetCorr_ = 0; + + uint16_t value_ = 0; + bool hasFreshValue_ = false; + bool attached_ = false; + bool initialized_ = false; +}; diff --git a/libraries/SAMD_AnalogCorrection/examples/CorrectADCResponse/CorrectADCResponse.ino b/libraries/SAMD_AnalogCorrection/examples/CorrectADCResponse/CorrectADCResponse.ino index 2b74538e1..3ea9a855c 100644 --- a/libraries/SAMD_AnalogCorrection/examples/CorrectADCResponse/CorrectADCResponse.ino +++ b/libraries/SAMD_AnalogCorrection/examples/CorrectADCResponse/CorrectADCResponse.ino @@ -21,7 +21,7 @@ - the instruction line to copy/paste in the final sketch */ -#include "SAMD_AnalogCorrection.h" +#include #define ADC_GND_PIN A1 #define ADC_3V3_PIN A2 diff --git a/libraries/SAMD_AnalogCorrection/src/SAMD_AnalogCorrection.cpp b/libraries/SAMD_AnalogCorrection/src/SAMD_AnalogCorrection.cpp deleted file mode 100644 index e39ab43e3..000000000 --- a/libraries/SAMD_AnalogCorrection/src/SAMD_AnalogCorrection.cpp +++ /dev/null @@ -1,48 +0,0 @@ -/* - Copyright (c) 2015 Arduino LLC. All right reserved. - SAMD51 support added by Adafruit - Copyright (c) 2018 Dean Miller for Adafruit Industries - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#include "SAMD_AnalogCorrection.h" - -#ifdef USE_TINYUSB -// For Serial when selecting TinyUSB -#include -#endif - -void analogReadCorrection (int offset, uint16_t gain) -{ - Adc *adc; -#if defined (__SAMD51__) -adc = ADC0; -#else -adc = ADC; -#endif - // Set correction values - adc->OFFSETCORR.reg = ADC_OFFSETCORR_OFFSETCORR(offset); - adc->GAINCORR.reg = ADC_GAINCORR_GAINCORR(gain); - - // Enable digital correction logic - adc->CTRLB.bit.CORREN = 1; - -#if defined (__SAMD51__) - while(adc->SYNCBUSY.bit.OFFSETCORR || adc->SYNCBUSY.bit.GAINCORR); -#else - while(adc->STATUS.bit.SYNCBUSY); -#endif -} - diff --git a/libraries/SAMD_AnalogCorrection/src/SAMD_AnalogCorrection.h b/libraries/SAMD_AnalogCorrection/src/SAMD_AnalogCorrection.h index 5edb91ee3..3733e3e60 100644 --- a/libraries/SAMD_AnalogCorrection/src/SAMD_AnalogCorrection.h +++ b/libraries/SAMD_AnalogCorrection/src/SAMD_AnalogCorrection.h @@ -18,7 +18,6 @@ #pragma once -#include - -void analogReadCorrection (int offset, uint16_t gain); +#include +void analogReadCorrection(int offset, uint16_t gain);