GCC Code Coverage Report


Directory: lib/fprime-samd/fprime-samd/Drv/AdcDriver/
File: AdcDriver.cpp
Date: 2026-10-20 16:48:56
Exec Total Coverage
Lines: 69 69 100.0%
Functions: 7 7 100.0%
Branches: 37 40 92.5%

Line Branch Exec Source
1 // ======================================================================
2 // \title AdcDriver.cpp
3 // \author crsmith
4 // \brief cpp file for AdcDriver component implementation class
5 // ======================================================================
6
7 #include "fprime-samd/Drv/AdcDriver/AdcDriver.hpp"
8 #include "fprime-samd/Drv/AdcDriver/AdcDriverHardware.hpp"
9
10 namespace Samd21 {
11
12 // Singleton instance for ISR access
13 AdcDriver* AdcDriver::s_instance = nullptr;
14
15 // ----------------------------------------------------------------------
16 // Component construction and destruction
17 // ----------------------------------------------------------------------
18
19 21 AdcDriver::AdcDriver(const char* const compName)
20 : AdcDriverComponentBase(compName),
21 21 m_configured(false),
22 21 m_gain(0),
23 21 m_state(State::IDLE),
24 21 m_lastResult(0),
25 42 m_pendingPortNum(0) {
26 // Initialize channel arrays
27
2/2
✓ Branch 0 taken 210 times.
✓ Branch 1 taken 21 times.
231 for (FwIndexType i = 0; i < ADC_CHANNEL_COUNT; i++) {
28 210 m_channels[i] = AdcChannel::AIN0;
29 210 m_channelConfigured[i] = false;
30 }
31 // Note: s_instance is set in configure() to ensure only configured driver handles interrupts
32 21 }
33
34 42 AdcDriver::~AdcDriver() {
35
2/2
✓ Branch 0 taken 20 times.
✓ Branch 1 taken 1 times.
42 if (s_instance == this) {
36 40 s_instance = nullptr;
37 }
38 42 }
39
40 // ----------------------------------------------------------------------
41 // Configuration methods
42 // ----------------------------------------------------------------------
43 // Convenient conversion time calculator https://blog.thea.codes/getting-the-most-out-of-the-samd21-adc/
44 37 void AdcDriver::configure(VoltageReference ref, Resolution res, SampleCount samples, U8 samplingTime, Gain gain) {
45 // Enforce singleton: only one AdcDriver instance can be configured at a time
46 // to ensure ISR routing works correctly. A reconfigure() method could be added
47 // in the future to allow runtime reconfiguration of ADC parameters (resolution,
48 // reference voltage, etc.) without violating the singleton constraint.
49 37 FW_ASSERT(s_instance == nullptr);
50
51 // Store gain for use in readAdc
52 37 m_gain = static_cast<U8>(gain);
53
54 // Delegate all hardware configuration to the HAL
55 37 AdcHardware::AdcHal::configure(ref, res, samples, samplingTime, gain);
56
57 // Register singleton for ISR access
58 37 s_instance = this;
59
60 // Enable interrupts via HAL (HAL handles priority calculation)
61 37 AdcHardware::AdcHal::enableInterrupt();
62 37 AdcHardware::AdcHal::enableAdcInterrupts();
63
64 37 m_configured = true;
65 37 }
66
67 25 void AdcDriver::configureChannel(FwIndexType portNum, AdcChannel channel) {
68 25 FW_ASSERT(m_configured);
69 25 FW_ASSERT(portNum < ADC_CHANNEL_COUNT);
70 25 FW_ASSERT(!m_channelConfigured[portNum]);
71
72 // Note: Pin muxing should be configured in topology BEFORE calling this for external channels (AIN0-AIN19)
73 // Use Samd21::PinMux::configure(PINMUX_xxx) in instances.fpp
74 // Internal channels (TEMP, BANDGAP, SCALEDIOVCC) don't require pin configuration
75
76 // Enable internal resources if needed (only TEMP and BANDGAP require hardware setup)
77
4/4
✓ Branch 0 taken 24 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 23 times.
25 if (channel == AdcChannel::TEMP || channel == AdcChannel::BANDGAP) {
78 2 AdcHardware::AdcHal::configureChannel(channel);
79 }
80
81 // Store channel mapping in component state
82 25 m_channels[portNum] = channel;
83 25 m_channelConfigured[portNum] = true;
84 25 }
85
86 // ----------------------------------------------------------------------
87 // Handler implementations for typed input ports
88 // ----------------------------------------------------------------------
89
90 27 Samd21::AdcStatus AdcDriver::readAdc_handler(FwIndexType portNum) {
91 // Validate configuration
92
3/4
✗ Branch 3 not taken.
✓ Branch 4 taken 27 times.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 26 times.
27 if (!m_configured) {
93 1 return Samd21::AdcStatus::ADC_NOT_CONFIGURED;
94 }
95
3/4
✗ Branch 4 not taken.
✓ Branch 5 taken 26 times.
✓ Branch 6 taken 1 times.
✓ Branch 7 taken 25 times.
26 if (!m_channelConfigured[portNum]) {
96 1 return Samd21::AdcStatus::ADC_INVALID_CHANNEL;
97 }
98
99 // Check if ADC is already busy
100
2/2
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 24 times.
25 if (m_state != State::IDLE) {
101 1 return Samd21::AdcStatus::ADC_BUSY;
102 }
103
104 // Select input channel with configured gain via HAL
105 24 AdcHardware::AdcHal::selectChannel(m_channels[portNum], m_gain);
106
107 // Transition to CONVERTING state
108 24 m_state = State::CONVERTING;
109 24 m_pendingPortNum = portNum;
110
111 // Start conversion via HAL (interrupt will fire when complete)
112 24 AdcHardware::AdcHal::startConversion();
113
114 24 return Samd21::AdcStatus::ADC_OK;
115 }
116
117 31 bool AdcDriver::activeIn_handler(FwIndexType portNum, U32 context) {
118 (void)portNum;
119 (void)context;
120
121 // Check if conversion is complete and result ready to deliver
122
6/6
✓ Branch 4 taken 11 times.
✓ Branch 5 taken 20 times.
✓ Branch 10 taken 2 times.
✓ Branch 11 taken 9 times.
✓ Branch 12 taken 22 times.
✓ Branch 13 taken 9 times.
31 if (m_state == State::COMPLETE || m_state == State::COMPLETE_OVERRUN) {
123 // Determine status based on whether overrun occurred
124 22 Samd21::AdcStatus status =
125
3/3
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 20 times.
✓ Branch 7 taken 22 times.
22 (m_state == State::COMPLETE_OVERRUN) ? Samd21::AdcStatus::ADC_OVERRUN : Samd21::AdcStatus::ADC_OK;
126
127 22 const FwIndexType portNumToReport = m_pendingPortNum;
128 22 const U32 resultToReport = m_lastResult;
129
130 22 m_state = State::IDLE;
131
132
1/1
✓ Branch 5 taken 22 times.
22 this->adcResult_out(portNumToReport, resultToReport, status);
133 22 } else {
134 9 return false;
135 }
136
137 22 return true;
138 }
139
140 // ----------------------------------------------------------------------
141 // Interrupt handler
142 // ----------------------------------------------------------------------
143
144 27 void AdcDriver::handleInterrupt() {
145 // Only process interrupt if we're actually waiting for a conversion
146
2/2
✓ Branch 4 taken 4 times.
✓ Branch 5 taken 23 times.
27 if (m_state != State::CONVERTING) {
147 // Spurious interrupt - clear flags but don't change state
148
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 3 times.
4 if (AdcHardware::AdcHal::isOverrun()) {
149 1 AdcHardware::AdcHal::clearOverrun();
150 }
151
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 if (AdcHardware::AdcHal::isResultReady()) {
152 4 (void)AdcHardware::AdcHal::readResult(); // Clear RESRDY by reading
153 }
154 4 return;
155 }
156
157 23 bool overrun = false;
158
159 // Check for overrun error first via HAL
160
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 21 times.
23 if (AdcHardware::AdcHal::isOverrun()) {
161 // Clear overrun flag via HAL
162 2 AdcHardware::AdcHal::clearOverrun();
163 2 overrun = true;
164 }
165
166 // Check if RESRDY flag is set via HAL (this happens even if OVERRUN occurred)
167
2/2
✓ Branch 1 taken 22 times.
✓ Branch 2 taken 1 times.
23 if (AdcHardware::AdcHal::isResultReady()) {
168 // Read result via HAL (reading RESULT automatically clears RESRDY flag per SAMD21 datasheet ยง33.6.5)
169 22 m_lastResult = AdcHardware::AdcHal::readResult();
170
171 // Transition to appropriate COMPLETE state
172
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 20 times.
22 m_state = overrun ? State::COMPLETE_OVERRUN : State::COMPLETE;
173 }
174 }
175
176 } // namespace Samd21
177