2828
2929using namespace arduino ;
3030
31+ static bool dma_transfer_finished_cb (unsigned int channel, unsigned int sequenceNo, void *userParam);
32+
3133AdcClass::AdcClass () :
32- initialized(false ),
34+ initialized_single(false ),
35+ initialized_scan(false ),
36+ paused_transfer(false ),
3337 current_adc_pin(PD2),
3438 current_adc_reference(AR_VDD),
3539 current_read_resolution(this ->max_read_resolution_bits),
40+ user_onsampling_finished_callback(nullptr ),
3641 adc_mutex(nullptr )
3742{
3843 this ->adc_mutex = xSemaphoreCreateMutexStatic (&this ->adc_mutex_buf );
3944 configASSERT (this ->adc_mutex );
4045}
4146
42- void AdcClass::init (PinName pin, uint8_t reference)
47+ void AdcClass::init_single (PinName pin, uint8_t reference)
4348{
4449 // Set up the ADC pin as an input
4550 pinMode (pin, INPUT);
@@ -126,16 +131,180 @@ void AdcClass::init(PinName pin, uint8_t reference)
126131 }
127132 }
128133
129- this ->initialized = true ;
134+ this ->initialized_scan = false ;
135+ this ->initialized_single = true ;
136+ }
137+
138+ void AdcClass::init_scan (PinName pin, uint8_t reference)
139+ {
140+ // Set up the ADC pin as an input
141+ pinMode (pin, INPUT);
142+
143+ // Create ADC init structs with default values
144+ IADC_Init_t init = IADC_INIT_DEFAULT;
145+ IADC_AllConfigs_t all_configs = IADC_ALLCONFIGS_DEFAULT;
146+ IADC_InitScan_t init_scan = IADC_INITSCAN_DEFAULT;
147+
148+ // Scan table structure
149+ IADC_ScanTable_t scanTable = IADC_SCANTABLE_DEFAULT;
150+
151+ // Enable IADC0, GPIO and PRS clock branches
152+ CMU_ClockEnable (cmuClock_IADC0, true );
153+ CMU_ClockEnable (cmuClock_GPIO, true );
154+ CMU_ClockEnable (cmuClock_PRS, true );
155+
156+ // Shutdown between conversions to reduce current
157+ init.warmup = iadcWarmupNormal;
158+
159+ // Set the HFSCLK prescale value here
160+ init.srcClkPrescale = IADC_calcSrcClkPrescale (IADC0, 20000000 , 0 );
161+
162+ IADC_CfgReference_t sl_adc_reference;
163+ uint32_t sl_adc_vref;
164+
165+ // Set the voltage reference
166+ switch (reference) {
167+ case AR_INTERNAL1V2:
168+ sl_adc_reference = iadcCfgReferenceInt1V2;
169+ sl_adc_vref = 1200 ;
170+ break ;
171+
172+ case AR_EXTERNAL_1V25:
173+ sl_adc_reference = iadcCfgReferenceExt1V25;
174+ sl_adc_vref = 1250 ;
175+ break ;
176+
177+ case AR_VDD:
178+ sl_adc_reference = iadcCfgReferenceVddx;
179+ sl_adc_vref = 3300 ;
180+ break ;
181+
182+ case AR_08VDD:
183+ sl_adc_reference = iadcCfgReferenceVddX0P8Buf;
184+ sl_adc_vref = 2640 ;
185+ break ;
186+
187+ default :
188+ return ;
189+ }
190+
191+ // Set the voltage reference
192+ all_configs.configs [0 ].reference = sl_adc_reference;
193+ all_configs.configs [0 ].vRef = sl_adc_vref;
194+ all_configs.configs [0 ].osrHighSpeed = iadcCfgOsrHighSpeed2x;
195+ all_configs.configs [0 ].analogGain = iadcCfgAnalogGain1x;
196+
197+ /*
198+ * CLK_SRC_ADC must be prescaled by some value greater than 1 to
199+ * derive the intended CLK_ADC frequency.
200+ * Based on the default 2x oversampling rate (OSRHS)...
201+ * conversion time = ((4 * OSRHS) + 2) / fCLK_ADC
202+ * ...which results in a maximum sampling rate of 833 ksps with the
203+ * 2-clock input multiplexer switching time is included.
204+ */
205+ all_configs.configs [0 ].adcClkPrescale = IADC_calcAdcClkPrescale (IADC0,
206+ 10000000 ,
207+ 0 ,
208+ iadcCfgModeNormal,
209+ init.srcClkPrescale );
210+
211+ // Reset the ADC
212+ IADC_reset (IADC0);
213+
214+ // Only configure the ADC if it is not already running
215+ if (IADC0->CTRL == _IADC_CTRL_RESETVALUE) {
216+ IADC_init (IADC0, &init, &all_configs);
217+ }
218+
219+ // Assign the input pin
220+ uint32_t pin_index = pin - PIN_NAME_MIN;
221+
222+ // Trigger continuously once scan is started
223+ init_scan.triggerAction = iadcTriggerActionContinuous;
224+ // Set the SCANFIFODVL flag when scan FIFO holds 2 entries
225+ // The interrupt associated with the SCANFIFODVL flag in the IADC_IF register is not used
226+ init_scan.dataValidLevel = iadcFifoCfgDvl1;
227+ // Enable DMA wake-up to save the results when the specified FIFO level is hit
228+ init_scan.fifoDmaWakeup = true ;
229+
230+ scanTable.entries [0 ].posInput = GPIO_to_ADC_pin_map[pin_index];
231+ scanTable.entries [0 ].includeInScan = true ;
232+
233+ // Initialize scan
234+ IADC_initScan (IADC0, &init_scan, &scanTable);
235+ IADC_enableInt (IADC0, IADC_IEN_SCANTABLEDONE);
236+
237+ // Allocate the analog bus for ADC0 inputs
238+ // Port C and D are handled together
239+ // Even and odd pins on the same port have a different register value
240+ bool pin_is_even = (pin % 2 == 0 );
241+ if (pin >= PD0 || pin >= PC0) {
242+ if (pin_is_even) {
243+ GPIO->CDBUSALLOC |= GPIO_CDBUSALLOC_CDEVEN0_ADC0;
244+ } else {
245+ GPIO->CDBUSALLOC |= GPIO_CDBUSALLOC_CDODD0_ADC0;
246+ }
247+ } else if (pin >= PB0) {
248+ if (pin_is_even) {
249+ GPIO->BBUSALLOC |= GPIO_BBUSALLOC_BEVEN0_ADC0;
250+ } else {
251+ GPIO->BBUSALLOC |= GPIO_BBUSALLOC_BODD0_ADC0;
252+ }
253+ } else {
254+ if (pin_is_even) {
255+ GPIO->ABUSALLOC |= GPIO_ABUSALLOC_AEVEN0_ADC0;
256+ } else {
257+ GPIO->ABUSALLOC |= GPIO_ABUSALLOC_AODD0_ADC0;
258+ }
259+ }
260+
261+ this ->initialized_single = false ;
262+ this ->initialized_scan = true ;
263+ }
264+
265+ sl_status_t AdcClass::init_dma (uint32_t *buffer, uint32_t size)
266+ {
267+ sl_status_t status;
268+ if (!this ->initialized_scan ) {
269+ return SL_STATUS_NOT_INITIALIZED;
270+ }
271+
272+ // Initialize DMA with default parameters
273+ DMADRV_Init ();
274+
275+ // Allocate DMA channel
276+ status = DMADRV_AllocateChannel (&this ->dma_channel , NULL );
277+ if (status != ECODE_EMDRV_DMADRV_OK) {
278+ return SL_STATUS_FAIL;
279+ }
280+
281+ // Trigger LDMA transfer on IADC scan completion
282+ LDMA_TransferCfg_t transferCfg = LDMA_TRANSFER_CFG_PERIPHERAL (ldmaPeripheralSignal_IADC0_IADC_SCAN);
283+
284+ /*
285+ * Set up a linked descriptor to save scan results to the
286+ * user-specified buffer. By linking the descriptor to itself
287+ * (the last argument is the relative jump in terms of the number of
288+ * descriptors), transfers will run continuously.
289+ */
290+ #pragma GCC diagnostic ignored "-Wmissing-field-initializers"
291+ this ->ldma_descriptor = (LDMA_Descriptor_t)LDMA_DESCRIPTOR_LINKREL_P2M_WORD (&(IADC0->SCANFIFODATA ), buffer, size, 0 );
292+
293+ DMADRV_LdmaStartTransfer ((int )this ->dma_channel , &transferCfg, &this ->ldma_descriptor , dma_transfer_finished_cb, NULL );
294+ return SL_STATUS_OK;
130295}
131296
132297uint16_t AdcClass::get_sample (PinName pin)
133298{
134299 xSemaphoreTake (this ->adc_mutex , portMAX_DELAY);
135300
136- if (!this ->initialized || pin != this ->current_adc_pin ) {
301+ if (this ->initialized_scan ) {
302+ this ->scan_stop ();
303+ }
304+
305+ if (!this ->initialized_single || (pin != this ->current_adc_pin )) {
137306 this ->current_adc_pin = pin;
138- this ->init (this ->current_adc_pin , this ->current_adc_reference );
307+ this ->init_single (this ->current_adc_pin , this ->current_adc_reference );
139308 }
140309 // Clear single done interrupt
141310 IADC_clearInt (IADC0, IADC_IF_SINGLEDONE);
@@ -162,18 +331,99 @@ void AdcClass::set_reference(uint8_t reference)
162331 }
163332 xSemaphoreTake (this ->adc_mutex , portMAX_DELAY);
164333 this ->current_adc_reference = reference;
165- this ->init (this ->current_adc_pin , this ->current_adc_reference );
334+ if (this ->initialized_single ) {
335+ this ->init_single (this ->current_adc_pin , this ->current_adc_reference );
336+ } else if (this ->initialized_scan ) {
337+ this ->init_scan (this ->current_adc_pin , this ->current_adc_reference );
338+ }
166339 xSemaphoreGive (this ->adc_mutex );
167340}
168341
169- void AdcClass::set_read_resolution (uint8_t resolution) {
342+ void AdcClass::set_read_resolution (uint8_t resolution)
343+ {
170344 if (resolution > this ->max_read_resolution_bits ) {
171345 this ->current_read_resolution = this ->max_read_resolution_bits ;
172346 return ;
173347 }
174348 this ->current_read_resolution = resolution;
175349}
176350
351+ sl_status_t AdcClass::scan_start (PinName pin, uint32_t *buffer, uint32_t size, void (*user_onsampling_finished_callback)())
352+ {
353+ sl_status_t status = SL_STATUS_FAIL;
354+ xSemaphoreTake (this ->adc_mutex , portMAX_DELAY);
355+
356+ if ((!this ->initialized_scan && !this ->initialized_single ) || (pin != this ->current_adc_pin )) {
357+ // Initialize in scan mode
358+ this ->current_adc_pin = pin;
359+ this ->user_onsampling_finished_callback = user_onsampling_finished_callback;
360+ this ->init_scan (this ->current_adc_pin , this ->current_adc_reference );
361+ status = this ->init_dma (buffer, size);
362+ } else if (this ->initialized_scan && this ->paused_transfer ) {
363+ // Resume DMA transfer if paused
364+ status = DMADRV_ResumeTransfer (this ->dma_channel );
365+ this ->paused_transfer = false ;
366+ } else if (this ->initialized_single ) {
367+ // Initialize in scan mode if it was initialized in single mode
368+ this ->deinit ();
369+ this ->current_adc_pin = pin;
370+ this ->user_onsampling_finished_callback = user_onsampling_finished_callback;
371+ this ->init_scan (this ->current_adc_pin , this ->current_adc_reference );
372+ status = this ->init_dma (buffer, size);
373+ } else {
374+ xSemaphoreGive (this ->adc_mutex );
375+ return status;
376+ }
377+
378+ // Start the conversion and wait for results
379+ IADC_command (IADC0, iadcCmdStartScan);
380+
381+ xSemaphoreGive (this ->adc_mutex );
382+ return status;
383+ }
384+
385+ void AdcClass::scan_stop ()
386+ {
387+ // Pause sampling
388+ DMADRV_PauseTransfer (this ->dma_channel );
389+ this ->paused_transfer = true ;
390+ }
391+
392+ void AdcClass::deinit ()
393+ {
394+ // Stop sampling
395+ DMADRV_StopTransfer (this ->dma_channel );
396+
397+ // Free resources
398+ DMADRV_FreeChannel (this ->dma_channel );
399+
400+ // Reset the ADC
401+ IADC_reset (IADC0);
402+
403+ this ->initialized_scan = false ;
404+ this ->initialized_single = false ;
405+ this ->current_adc_pin = PIN_NAME_NC;
406+ }
407+
408+ void AdcClass::handle_dma_finished_callback ()
409+ {
410+ if (!this ->user_onsampling_finished_callback ) {
411+ return ;
412+ }
413+
414+ this ->user_onsampling_finished_callback ();
415+ }
416+
417+ bool dma_transfer_finished_cb (unsigned int channel, unsigned int sequenceNo, void *userParam)
418+ {
419+ (void )channel;
420+ (void )sequenceNo;
421+ (void )userParam;
422+
423+ ADC.handle_dma_finished_callback ();
424+ return false ;
425+ }
426+
177427const IADC_PosInput_t AdcClass::GPIO_to_ADC_pin_map[64 ] = {
178428 // Port A
179429 iadcPosInputPortAPin0,
0 commit comments