From 94a1988698016980dfd082b8c346125a8a8fec97 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Tue, 6 Sep 2022 16:53:45 +0200 Subject: [PATCH] samd/ADC: Add a adc.read_timed() method. Call: adc.read_timed(buffer, freq) buffer must be preallocated. The size determines the number of 16 bit words to be read. The numeric range of the results is that of the raw ADC. The call returns immediately, and the data transfer is done by DMA. The caller must wait sufficiently long until the data is sampled and can be noticed by a callback. No internal checks are made for a too-high freq value. Read speeds depends on Average and bit length setting: SAMD21: Max. 350kS/s (8 bit, Average 1) SAMD51: Max. 1 MS/s (8 bit, Average 1) Signed-off-by: robert-hh --- ports/samd/dma_manager.c | 2 +- ports/samd/machine_adc.c | 147 ++++++++++++++++++++++++++++++++++++--- ports/samd/machine_dac.c | 20 +++--- ports/samd/tc_manager.c | 5 +- ports/samd/tc_manager.h | 2 +- 5 files changed, 154 insertions(+), 22 deletions(-) diff --git a/ports/samd/dma_manager.c b/ports/samd/dma_manager.c index 916b629a52..868606e66b 100644 --- a/ports/samd/dma_manager.c +++ b/ports/samd/dma_manager.c @@ -52,7 +52,7 @@ static volatile DmacDescriptor dma_write_back[MICROPY_HW_DMA_CHANNELS] __attribu // List of channel flags: true: channel used, false: channel available static bool channel_list[MICROPY_HW_DMA_CHANNELS]; -STATIC bool dma_initialized = false; +static bool dma_initialized = false; // allocate_channel(): retrieve an available channel. Return the number or -1 int allocate_dma_channel(void) { diff --git a/ports/samd/machine_adc.c b/ports/samd/machine_adc.c index 665af6f6f2..afd9c5d29a 100644 --- a/ports/samd/machine_adc.c +++ b/ports/samd/machine_adc.c @@ -31,6 +31,10 @@ #include "py/mphal.h" #include "sam.h" #include "pin_af.h" +#include "modmachine.h" +#include "samd_soc.h" +#include "dma_manager.h" +#include "tc_manager.h" typedef struct _machine_adc_obj_t { mp_obj_base_t base; @@ -39,6 +43,8 @@ typedef struct _machine_adc_obj_t { uint8_t avg; uint8_t bits; uint8_t vref; + int8_t dma_channel; + int8_t tc_index; } machine_adc_obj_t; #define DEFAULT_ADC_BITS 12 @@ -85,6 +91,20 @@ static uint8_t resolution[] = { extern mp_int_t log2i(mp_int_t num); +// Active just for SAMD21, stops the freerun mode +// For SAMD51, just the INT flag is reset. +void adc_irq_handler(int dma_channel) { + + #if defined(MCU_SAMD21) + DMAC->CHID.reg = dma_channel; + DMAC->CHINTFLAG.reg = DMAC_CHINTFLAG_TCMPL; + ADC->EVCTRL.bit.STARTEI = 0; + + #elif defined(MCU_SAMD51) + DMAC->Channel[dma_channel].CHINTFLAG.reg = DMAC_CHINTFLAG_TCMPL; + #endif +} + static void mp_machine_adc_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { (void)kind; machine_adc_obj_t *self = MP_OBJ_TO_PTR(self_in); @@ -116,8 +136,9 @@ static mp_obj_t mp_machine_adc_make_new(const mp_obj_type_t *type, size_t n_args self->id = id; self->adc_config = adc_config; self->bits = DEFAULT_ADC_BITS; + uint16_t bits = args[ARG_bits].u_int; - if (bits >= 8 && bits <= 12) { + if (8 <= bits && bits <= 12) { self->bits = bits; } uint32_t avg = log2i(args[ARG_average].u_int); @@ -147,6 +168,14 @@ static mp_int_t mp_machine_adc_read_u16(machine_adc_obj_t *self) { adc->INPUTCTRL.reg = ADC_INPUTCTRL_MUXNEG_GND | self->adc_config.channel; // set resolution. Scale 8-16 to 0 - 4 for table access. adc->CTRLB.bit.RESSEL = resolution[(self->bits - 8) / 2]; + + #if defined(MCU_SAMD21) + // Stop the ADC sampling by timer + adc->EVCTRL.bit.STARTEI = 0; + #elif defined(MCU_SAMD51) + // Do not restart ADC after data has bee read + adc->DSEQCTRL.reg = 0; + #endif // Measure input voltage adc->SWTRIG.bit.START = 1; while (adc->INTFLAG.bit.RESRDY == 0) { @@ -155,9 +184,107 @@ static mp_int_t mp_machine_adc_read_u16(machine_adc_obj_t *self) { return adc->RESULT.reg * (65536 / (1 << self->bits)); } +static void machine_adc_read_timed(mp_obj_t self_in, mp_obj_t values, mp_obj_t freq_in) { + machine_adc_obj_t *self = self_in; + Adc *adc = adc_bases[self->adc_config.device]; + mp_buffer_info_t src; + mp_get_buffer_raise(values, &src, MP_BUFFER_READ); + if (src.len >= 2) { + int freq = mp_obj_get_int(freq_in); + if (self->dma_channel == -1) { + self->dma_channel = allocate_dma_channel(); + dma_init(); + } + if (self->tc_index == -1) { + self->tc_index = allocate_tc_instance(); + } + // Set Input channel and resolution + // Select the pin as positive input and gnd as negative input reference, non-diff mode by default + adc->INPUTCTRL.reg = ADC_INPUTCTRL_MUXNEG_GND | self->adc_config.channel; + // set resolution. Scale 8-16 to 0 - 4 for table access. + adc->CTRLB.bit.RESSEL = resolution[(self->bits - 8) / 2]; + + // Configure DMA for halfword output to the DAC + #if defined(MCU_SAMD21) + // dma irq just for SAMD21 to stop the timer based acquisition + dma_register_irq(self->dma_channel, adc_irq_handler); + configure_tc(self->tc_index, freq, TC_EVCTRL_OVFEO); + // Enable APBC clock + PM->APBCMASK.reg |= PM_APBCMASK_EVSYS; + // Set up the EVSYS channel + EVSYS->CTRL.bit.SWRST = 1; + EVSYS->USER.reg = EVSYS_USER_CHANNEL(ADC_EVSYS_CHANNEL + 1) | + EVSYS_USER_USER(EVSYS_ID_USER_ADC_START); + EVSYS->CHANNEL.reg = EVSYS_CHANNEL_CHANNEL(ADC_EVSYS_CHANNEL) | + EVSYS_CHANNEL_EVGEN(EVSYS_ID_GEN_TC3_OVF + 3 * self->tc_index) | + EVSYS_CHANNEL_PATH_ASYNCHRONOUS; + + dma_desc[self->dma_channel].BTCTRL.reg = + DMAC_BTCTRL_VALID | DMAC_BTCTRL_BLOCKACT_NOACT | + DMAC_BTCTRL_BEATSIZE_HWORD | DMAC_BTCTRL_DSTINC | DMAC_BTCTRL_STEPSEL | + DMAC_BTCTRL_STEPSIZE(DMAC_BTCTRL_STEPSIZE_X1_Val); + dma_desc[self->dma_channel].BTCNT.reg = src.len / 2; + dma_desc[self->dma_channel].SRCADDR.reg = (uint32_t)(&adc->RESULT.reg); + dma_desc[self->dma_channel].DSTADDR.reg = (uint32_t)(src.buf) + src.len; + dma_desc[self->dma_channel].DESCADDR.reg = 0; // ONE_SHOT + DMAC->CHID.reg = self->dma_channel; + DMAC->CHCTRLA.reg = 0; + while (DMAC->CHCTRLA.bit.ENABLE) { + } + DMAC->CHCTRLB.reg = + DMAC_CHCTRLB_LVL(0) | + DMAC_CHCTRLB_TRIGACT_BEAT | + DMAC_CHCTRLB_TRIGSRC(ADC_DMAC_ID_RESRDY); + + DMAC->CHINTENSET.reg = DMAC_CHINTFLAG_TCMPL; + DMAC->CHCTRLA.reg |= DMAC_CHCTRLA_ENABLE; + + NVIC_EnableIRQ(DMAC_IRQn); + adc->EVCTRL.bit.STARTEI = 1; + + #elif defined(MCU_SAMD51) + configure_tc(self->tc_index, freq, 0); + + // Restart ADC after data has bee read + adc->DSEQCTRL.reg = ADC_DSEQCTRL_AUTOSTART; + // Start the first sampling to ensure we get a proper first value. + adc->SWTRIG.bit.START = 1; + while (adc->INTFLAG.bit.RESRDY == 0) { + } + + dma_desc[self->dma_channel].BTCTRL.reg = + DMAC_BTCTRL_VALID | DMAC_BTCTRL_BLOCKACT_NOACT | + DMAC_BTCTRL_BEATSIZE_HWORD | DMAC_BTCTRL_DSTINC | DMAC_BTCTRL_STEPSEL | + DMAC_BTCTRL_STEPSIZE(DMAC_BTCTRL_STEPSIZE_X1_Val); + dma_desc[self->dma_channel].BTCNT.reg = src.len / 2; + dma_desc[self->dma_channel].SRCADDR.reg = (uint32_t)(&adc->RESULT.reg); + dma_desc[self->dma_channel].DSTADDR.reg = (uint32_t)(src.buf) + src.len; + dma_desc[self->dma_channel].DESCADDR.reg = 0; // ONE_SHOT + + DMAC->Channel[self->dma_channel].CHCTRLA.reg = + DMAC_CHCTRLA_BURSTLEN(DMAC_CHCTRLA_BURSTLEN_SINGLE_Val) | + DMAC_CHCTRLA_TRIGACT(DMAC_CHCTRLA_TRIGACT_BURST_Val) | + DMAC_CHCTRLA_TRIGSRC(TC0_DMAC_ID_OVF + 3 * self->tc_index); + DMAC->Channel[self->dma_channel].CHCTRLA.reg |= DMAC_CHCTRLA_ENABLE; + + #endif // defined SAMD21 or SAMD51 + + } + return mp_const_none; +} + // deinit() : release the ADC channel static void mp_machine_adc_deinit(machine_adc_obj_t *self) { busy_flags &= ~((1 << (self->adc_config.device * 16 + self->adc_config.channel))); + if (self->dma_channel >= 0) { + dac_stop_dma(self->dma_channel, true); + free_dma_channel(self->dma_channel); + self->dma_channel = -1; + } + if (self->tc_index >= 0) { + free_tc_instance(self->tc_index); + self->tc_index = -1; + } } void adc_deinit_all(void) { @@ -175,9 +302,9 @@ static void adc_init(machine_adc_obj_t *self) { #if defined(MCU_SAMD21) // Configuration SAMD21 - // Enable APBD clocks and PCHCTRL clocks; GCLK2 at 48 MHz + // Enable APBD clocks and PCHCTRL clocks; GCLK5 at 48 MHz PM->APBCMASK.reg |= PM_APBCMASK_ADC; - GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK2 | GCLK_CLKCTRL_ID_ADC; + GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK5 | GCLK_CLKCTRL_ID_ADC; while (GCLK->STATUS.bit.SYNCBUSY) { } // Reset ADC registers @@ -190,7 +317,7 @@ static void adc_init(machine_adc_obj_t *self) { linearity |= ((*((uint32_t *)ADC_FUSES_LINEARITY_1_ADDR) & ADC_FUSES_LINEARITY_1_Msk) >> ADC_FUSES_LINEARITY_1_Pos) << 5; /* Write the calibration data. */ ADC->CALIB.reg = ADC_CALIB_BIAS_CAL(bias) | ADC_CALIB_LINEARITY_CAL(linearity); - // Divide 48MHz clock by 32 to obtain 1.5 MHz clock to adc + // Divide a 48MHz clock by 32 to obtain 1.5 MHz clock to adc adc->CTRLB.reg = ADC_CTRLB_PRESCALER_DIV32; // Select external AREFA as reference voltage. adc->REFCTRL.reg = adc_vref_table[self->vref]; @@ -203,12 +330,12 @@ static void adc_init(machine_adc_obj_t *self) { #elif defined(MCU_SAMD51) // Configuration SAMD51 - // Enable APBD clocks and PCHCTRL clocks; GCLK2 at 48 MHz + // Enable APBD clocks and PCHCTRL clocks; GCLK5 at 48 MHz if (self->adc_config.device == 0) { - GCLK->PCHCTRL[ADC0_GCLK_ID].reg = GCLK_PCHCTRL_GEN_GCLK2 | GCLK_PCHCTRL_CHEN; + GCLK->PCHCTRL[ADC0_GCLK_ID].reg = GCLK_PCHCTRL_GEN_GCLK5 | GCLK_PCHCTRL_CHEN; MCLK->APBDMASK.bit.ADC0_ = 1; } else { - GCLK->PCHCTRL[ADC1_GCLK_ID].reg = GCLK_PCHCTRL_GEN_GCLK2 | GCLK_PCHCTRL_CHEN; + GCLK->PCHCTRL[ADC1_GCLK_ID].reg = GCLK_PCHCTRL_GEN_GCLK5 | GCLK_PCHCTRL_CHEN; MCLK->APBDMASK.bit.ADC1_ = 1; } // Reset ADC registers @@ -230,8 +357,10 @@ static void adc_init(machine_adc_obj_t *self) { } /* Write the calibration data. */ adc->CALIB.reg = ADC_CALIB_BIASCOMP(biascomp) | ADC_CALIB_BIASR2R(biasr2r) | ADC_CALIB_BIASREFBUF(biasrefbuf); - // Divide 48MHz clock by 32 to obtain 1.5 MHz clock to adc - adc->CTRLA.reg = ADC_CTRLA_PRESCALER_DIV32; + // Divide 48MHz clock by 4 to obtain 12 MHz clock to adc + adc->CTRLA.reg = ADC_CTRLA_PRESCALER_DIV4; + // Enable the offset compensation + adc->SAMPCTRL.reg = ADC_SAMPCTRL_OFFCOMP; // Set the reference voltage. Default: external AREFA. adc->REFCTRL.reg = adc_vref_table[self->vref]; // Average: Accumulate samples and scale them down accordingly diff --git a/ports/samd/machine_dac.c b/ports/samd/machine_dac.c index 0a9f71c6e7..fe8e0bc1a3 100644 --- a/ports/samd/machine_dac.c +++ b/ports/samd/machine_dac.c @@ -51,11 +51,11 @@ typedef struct _dac_obj_t { uint32_t count; } dac_obj_t; Dac *const dac_bases[] = DAC_INSTS; -STATIC void dac_init(dac_obj_t *self, Dac *dac); +static void dac_init(dac_obj_t *self, Dac *dac); #if defined(MCU_SAMD21) -STATIC dac_obj_t dac_obj[] = { +static dac_obj_t dac_obj[] = { {{&machine_dac_type}, 0, PIN_PA02}, }; @@ -65,7 +65,7 @@ STATIC dac_obj_t dac_obj[] = { #elif defined(MCU_SAMD51) -STATIC dac_obj_t dac_obj[] = { +static dac_obj_t dac_obj[] = { {{&machine_dac_type}, 0, PIN_PA02}, {{&machine_dac_type}, 1, PIN_PA05}, }; @@ -145,7 +145,7 @@ static mp_obj_t dac_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_ return MP_OBJ_FROM_PTR(self); } -STATIC void dac_init(dac_obj_t *self, Dac *dac) { +static void dac_init(dac_obj_t *self, Dac *dac) { // Init DAC if (self->initialized == false) { #if defined(MCU_SAMD21) @@ -190,7 +190,7 @@ STATIC void dac_init(dac_obj_t *self, Dac *dac) { self->initialized = true; } -STATIC void dac_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { +static void dac_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { dac_obj_t *self = self_in; mp_printf(print, "DAC(%u, Pin=%q, vref=%d)", self->id, pin_find_by_id(self->gpio_id)->name, self->vref); } @@ -215,7 +215,7 @@ static mp_obj_t dac_write(mp_obj_t self_in, mp_obj_t value_in) { } MP_DEFINE_CONST_FUN_OBJ_2(dac_write_obj, dac_write); -STATIC mp_obj_t dac_write_timed(size_t n_args, const mp_obj_t *args) { +static mp_obj_t dac_write_timed(size_t n_args, const mp_obj_t *args) { Dac *dac = dac_bases[0]; // Just one DAC used dac_obj_t *self = args[0]; mp_buffer_info_t src; @@ -239,7 +239,7 @@ STATIC mp_obj_t dac_write_timed(size_t n_args, const mp_obj_t *args) { self->tc_index = allocate_tc_instance(); } // Configure TC; no need to check the return value - configure_tc(self->tc_index, freq); + configure_tc(self->tc_index, freq, 0); // Configure DMA for halfword output to the DAC #if defined(MCU_SAMD21) @@ -301,9 +301,9 @@ STATIC mp_obj_t dac_write_timed(size_t n_args, const mp_obj_t *args) { } return mp_const_none; } -STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(dac_write_timed_obj, 3, 4, dac_write_timed); +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(dac_write_timed_obj, 3, 4, dac_write_timed); -STATIC mp_obj_t dac_deinit(mp_obj_t self_in) { +static mp_obj_t dac_deinit(mp_obj_t self_in) { dac_obj_t *self = self_in; self->initialized = false; // Reset the DAC to lower the current consumption as SAMD21 @@ -321,7 +321,7 @@ STATIC mp_obj_t dac_deinit(mp_obj_t self_in) { } MP_DEFINE_CONST_FUN_OBJ_1(dac_deinit_obj, dac_deinit); -STATIC const mp_rom_map_elem_t dac_locals_dict_table[] = { +static const mp_rom_map_elem_t dac_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&dac_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&dac_write_obj) }, { MP_ROM_QSTR(MP_QSTR_write_timed), MP_ROM_PTR(&dac_write_timed_obj) }, diff --git a/ports/samd/tc_manager.c b/ports/samd/tc_manager.c index 10cad8060a..fb0c3b77ff 100644 --- a/ports/samd/tc_manager.c +++ b/ports/samd/tc_manager.c @@ -57,7 +57,7 @@ void free_tc_instance(int tc_index) { } } -int configure_tc(int tc_index, int freq) { +int configure_tc(int tc_index, int freq, int event) { uint32_t clock = DFLL48M_FREQ; // Use the fixed 48M clock Tc *tc; @@ -105,6 +105,9 @@ int configure_tc(int tc_index, int freq) { TC_CTRLA_MODE_COUNT16 | TC_CTRLA_RUNSTDBY | TC_CTRLA_WAVEGEN_MFRQ; tc->COUNT16.CC[0].reg = period; + if (event) { + tc->COUNT16.EVCTRL.reg = event; + } tc->COUNT16.CTRLA.bit.ENABLE = 1; while (tc->COUNT16.STATUS.bit.SYNCBUSY) { diff --git a/ports/samd/tc_manager.h b/ports/samd/tc_manager.h index d90959fa49..04064f89e6 100644 --- a/ports/samd/tc_manager.h +++ b/ports/samd/tc_manager.h @@ -31,7 +31,7 @@ int allocate_tc_instance(void); void free_tc_instance(int tc_index); -int configure_tc(int tc_index, int freq); +int configure_tc(int tc_index, int freq, int event); void tc_deinit(void); extern Tc *tc_instance_list[];