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 <robert@hammelrath.com>
pull/9624/head
robert-hh 2022-09-06 16:53:45 +02:00
rodzic 3b08312c16
commit 94a1988698
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 32EC5F755D5A3A93
5 zmienionych plików z 154 dodań i 22 usunięć

Wyświetl plik

@ -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) {

Wyświetl plik

@ -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

Wyświetl plik

@ -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) },

Wyświetl plik

@ -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) {

Wyświetl plik

@ -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[];