From e69313f89c607976da2a7e06ad49b8db9fe1caf9 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Fri, 2 Dec 2022 21:08:46 +0100 Subject: [PATCH] samd: Add a vref=num option to the ADC and DAC constructor. ADC: The argument of vref=num is an integer. Values for num are: SAMD21: 0 INT1V 1.0V voltage reference 1 INTVCC0 1/1.48 Analog voltage supply 2 INTVCC1 1/2 Analog voltage supply (only for VDDANA > 2.0V) 3 VREFA External reference 4 VREFB External reference SAMD51: 0 INTREF internal bandgap reference 1 INTVCC1 Analog voltage supply 2 INTVCC0 1/2 Analog voltage supply (only for VDDANA > 2.0v) 3 AREFA External reference A 4 AREFB External reference B 5 AREFC External reference C (ADC1 only) DAC: The argument of vref=num is an integer. Suitable values: SAMD21: 0 INT1V Internal voltage reference 1 VDDANA Analog voltage supply 2 VREFA External reference SAMD51: 0 INTREF Internal bandgap reference 1 VDDANA Analog voltage supply 2 VREFAU Unbuffered external voltage reference (not buffered in DAC) 4 VREFAB Buffered external voltage reference (buffered in DAC). --- docs/samd/quickref.rst | 58 +++++++++++++++-- ports/samd/boards/SEEED_XIAO/mpconfigboard.h | 1 + ports/samd/machine_adc.c | 50 +++++++++++++-- ports/samd/machine_dac.c | 67 +++++++++++++------- 4 files changed, 143 insertions(+), 33 deletions(-) diff --git a/docs/samd/quickref.rst b/docs/samd/quickref.rst index 5e8298d1b3..aa3d563f7c 100644 --- a/docs/samd/quickref.rst +++ b/docs/samd/quickref.rst @@ -254,16 +254,38 @@ an external ADC. ADC Constructor ``````````````` -.. class:: ADC(dest, *, average=16) +.. class:: ADC(dest, *, average=16, vref=n) :noindex: - Construct and return a new ADC object using the following parameters: +Construct and return a new ADC object using the following parameters: - - *dest* is the Pin object on which the ADC is output. + - *dest* is the Pin object on which the ADC is output. - Keyword arguments: +Keyword arguments: - - *average* is used to reduce the noise. With a value of 16 the LSB noise is about 1 digit. + - *average* is used to reduce the noise. With a value of 16 the LSB noise is about 1 digit. + - *vref* sets the reference voltage for the ADC. + + The default setting is for 3.3V. Other values are: + + ==== ============================== =============================== + vref SAMD21 SAMD51 + ==== ============================== =============================== + 0 1.0V voltage reference internal bandgap reference (1V) + 1 1/1.48 Analogue voltage supply Analogue voltage supply + 2 1/2 Analogue voltage supply 1/2 Analogue voltage supply + 3 External reference A External reference A + 4 External reference B External reference B + 5 - External reference C + ==== ============================== =============================== + +ADC Methods +``````````` + +.. method:: read_u16() + +Read a single ADC value as unsigned 16 bit quantity. The voltage range is defined +by the vref option of the constructor, the resolutions by the bits option. DAC (digital to analog conversion) ---------------------------------- @@ -280,6 +302,32 @@ The DAC class provides a fast digital to analog conversion. Usage example:: The resolution of the DAC is 12 bit for SAMD51 and 10 bit for SAMD21. SAMD21 devices have 1 DAC channel at GPIO PA02, SAMD51 devices have 2 DAC channels at GPIO PA02 and PA05. +DAC Constructor +``````````````` + +.. class:: DAC(id, *, vref=3) + :noindex: + +The vref arguments defines the output voltage range, the callback option is used for +dac_timed(). Suitable values for vref are: + +==== ============================ ================================ +vref SAMD21 SAMD51 +==== ============================ ================================ +0 Internal voltage reference Internal bandgap reference (~1V) +1 Analogue voltage supply Analogue voltage supply +2 External reference Unbuffered external reference +3 - Buffered external reference +==== ============================ ================================ + +DAC Methods +``````````` + +.. method:: write(value) + +Write a single value to the selected DAC output. The value range is 0-1023 for +SAMD21 and 0-4095 for SAMD51. The voltage range depends on the vref setting. + Software SPI bus ---------------- diff --git a/ports/samd/boards/SEEED_XIAO/mpconfigboard.h b/ports/samd/boards/SEEED_XIAO/mpconfigboard.h index 98994a5bc0..7447c5c3ac 100644 --- a/ports/samd/boards/SEEED_XIAO/mpconfigboard.h +++ b/ports/samd/boards/SEEED_XIAO/mpconfigboard.h @@ -2,3 +2,4 @@ #define MICROPY_HW_MCU_NAME "SAMD21G18A" #define MICROPY_HW_XOSC32K (1) +#define MICROPY_HW_ADC_VREF (2) diff --git a/ports/samd/machine_adc.c b/ports/samd/machine_adc.c index 8fd3e3bf4e..d407882529 100644 --- a/ports/samd/machine_adc.c +++ b/ports/samd/machine_adc.c @@ -40,11 +40,40 @@ typedef struct _machine_adc_obj_t { uint8_t id; uint8_t avg; uint8_t bits; + uint8_t vref; } machine_adc_obj_t; #define DEFAULT_ADC_BITS 12 #define DEFAULT_ADC_AVG 16 +#if defined(MCU_SAMD21) +static uint8_t adc_vref_table[] = { + ADC_REFCTRL_REFSEL_INT1V_Val, ADC_REFCTRL_REFSEL_INTVCC0_Val, + ADC_REFCTRL_REFSEL_INTVCC1_Val, ADC_REFCTRL_REFSEL_AREFA_Val, ADC_REFCTRL_REFSEL_AREFB_Val +}; +#if MICROPY_HW_ADC_VREF +#define DEFAULT_ADC_VREF MICROPY_HW_ADC_VREF +#else +#define DEFAULT_ADC_VREF (3) +#endif + +#define ADC_EVSYS_CHANNEL 0 + +#elif defined(MCU_SAMD51) + +static uint8_t adc_vref_table[] = { + ADC_REFCTRL_REFSEL_INTREF_Val, ADC_REFCTRL_REFSEL_INTVCC1_Val, + ADC_REFCTRL_REFSEL_INTVCC0_Val, ADC_REFCTRL_REFSEL_AREFA_Val, + ADC_REFCTRL_REFSEL_AREFB_Val, ADC_REFCTRL_REFSEL_AREFC_Val +}; +#if MICROPY_HW_ADC_VREF +#define DEFAULT_ADC_VREF MICROPY_HW_ADC_VREF +#else +#define DEFAULT_ADC_VREF (3) +#endif + +#endif // defined(MCU_SAMD21) + Adc *const adc_bases[] = ADC_INSTS; uint32_t busy_flags = 0; bool init_flags[2] = {false, false}; @@ -66,17 +95,18 @@ STATIC void adc_obj_print(const mp_print_t *print, mp_obj_t o, mp_print_kind_t k (void)kind; machine_adc_obj_t *self = MP_OBJ_TO_PTR(o); - mp_printf(print, "ADC(%s, device=%u, channel=%u, bits=%u, average=%u)", + mp_printf(print, "ADC(%s, device=%u, channel=%u, bits=%u, average=%u, vref=%d)", pin_name(self->id), self->adc_config.device, - self->adc_config.channel, self->bits, 1 << self->avg); + self->adc_config.channel, self->bits, 1 << self->avg, self->vref); } STATIC mp_obj_t adc_obj_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { - enum { ARG_id, ARG_bits, ARG_average }; + enum { ARG_id, ARG_bits, ARG_average, ARG_vref }; static const mp_arg_t allowed_args[] = { { MP_QSTR_id, MP_ARG_REQUIRED | MP_ARG_OBJ }, { MP_QSTR_bits, MP_ARG_INT, {.u_int = DEFAULT_ADC_BITS} }, { MP_QSTR_average, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = DEFAULT_ADC_AVG} }, + { MP_QSTR_vref, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = DEFAULT_ADC_VREF} }, }; // Parse the arguments. @@ -99,8 +129,14 @@ STATIC mp_obj_t adc_obj_make_new(const mp_obj_type_t *type, size_t n_args, size_ uint32_t avg = log2i(args[ARG_average].u_int); self->avg = (avg <= 10 ? avg : 10); + uint8_t vref = args[ARG_vref].u_int; + if (0 <= vref && vref < sizeof(adc_vref_table)) { + self->vref = vref; + } + // flag the device/channel as being in use. busy_flags |= (1 << (self->adc_config.device * 16 + self->adc_config.channel)); + init_flags[self->adc_config.device] = false; adc_init(self); @@ -111,6 +147,8 @@ STATIC mp_obj_t adc_obj_make_new(const mp_obj_type_t *type, size_t n_args, size_ STATIC mp_obj_t machine_adc_read_u16(mp_obj_t self_in) { machine_adc_obj_t *self = MP_OBJ_TO_PTR(self_in); Adc *adc = adc_bases[self->adc_config.device]; + // Set the reference voltage. Default: external AREFA. + adc->REFCTRL.reg = adc_vref_table[self->vref]; // 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; @@ -183,7 +221,7 @@ static void adc_init(machine_adc_obj_t *self) { // Divide 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_REFCTRL_REFSEL_AREFA; + adc->REFCTRL.reg = adc_vref_table[self->vref]; // Average: Accumulate samples and scale them down accordingly adc->AVGCTRL.reg = self->avg | ADC_AVGCTRL_ADJRES(self->avg); // Enable ADC and wait to be ready @@ -222,8 +260,8 @@ static void adc_init(machine_adc_obj_t *self) { 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; - // Select external AREFA as reference voltage. - adc->REFCTRL.reg = ADC_REFCTRL_REFSEL_AREFA; + // Set the reference voltage. Default: external AREFA. + adc->REFCTRL.reg = adc_vref_table[self->vref]; // Average: Accumulate samples and scale them down accordingly adc->AVGCTRL.reg = self->avg | ADC_AVGCTRL_ADJRES(self->avg); // Enable ADC and wait to be ready diff --git a/ports/samd/machine_dac.c b/ports/samd/machine_dac.c index 53407c0d37..a1da9285e5 100644 --- a/ports/samd/machine_dac.c +++ b/ports/samd/machine_dac.c @@ -38,9 +38,10 @@ typedef struct _dac_obj_t { mp_obj_base_t base; uint8_t id; mp_hal_pin_obj_t gpio_id; + uint8_t vref; } dac_obj_t; -STATIC const dac_obj_t dac_obj[] = { +STATIC dac_obj_t dac_obj[] = { #if defined(MCU_SAMD21) {{&machine_dac_type}, 0, PIN_PA02}, #elif defined(MCU_SAMD51) @@ -51,25 +52,53 @@ STATIC const dac_obj_t dac_obj[] = { Dac *const dac_bases[] = DAC_INSTS; #if defined(MCU_SAMD21) -#define MAX_DAC_VALUE (1023) + +#define MAX_DAC_VALUE (1023) +#define DEFAULT_DAC_VREF (1) +#define MAX_DAC_VREF (2) + #elif defined(MCU_SAMD51) -#define MAX_DAC_VALUE (4095) + +// According to Errata 2.9.2, VDDANA as ref value is not available. However it worked +// in tests. So I keep the selection here but set the default to Aref, which is usually +// connected at the Board to VDDANA +static uint8_t dac_vref_table[] = { + DAC_CTRLB_REFSEL_INTREF_Val, DAC_CTRLB_REFSEL_VDDANA_Val, + DAC_CTRLB_REFSEL_VREFPU_Val, DAC_CTRLB_REFSEL_VREFPB_Val +}; +#define MAX_DAC_VALUE (4095) +#define DEFAULT_DAC_VREF (2) +#define MAX_DAC_VREF (3) static bool dac_init = false; #endif STATIC mp_obj_t dac_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, - const mp_obj_t *args) { + const mp_obj_t *all_args) { - mp_arg_check_num(n_args, n_kw, 1, 1, true); - uint8_t id = mp_obj_get_int(args[0]); - const dac_obj_t *self = NULL; + enum { ARG_id, ARG_vref }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_id, MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_vref, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = DEFAULT_DAC_VREF} }, + }; + + // Parse the arguments. + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + uint8_t id = args[ARG_id].u_int; + dac_obj_t *self = NULL; if (0 <= id && id <= MP_ARRAY_SIZE(dac_obj)) { self = &dac_obj[id]; } else { mp_raise_ValueError(MP_ERROR_TEXT("invalid Pin for DAC")); } + uint8_t vref = args[ARG_vref].u_int; + if (0 <= vref && vref <= MAX_DAC_VREF) { + self->vref = vref; + } + Dac *dac = dac_bases[0]; // Just one DAC // Init DAC @@ -85,7 +114,7 @@ STATIC mp_obj_t dac_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_ dac->CTRLA.bit.SWRST = 1; while (dac->CTRLA.bit.SWRST) { } - dac->CTRLB.reg = DAC_CTRLB_EOEN | DAC_CTRLB_REFSEL(DAC_CTRLB_REFSEL_AVCC_Val); + dac->CTRLB.reg = DAC_CTRLB_EOEN | DAC_CTRLB_REFSEL(self->vref); // Enable DAC and wait to be ready dac->CTRLA.bit.ENABLE = 1; while (dac->STATUS.bit.SYNCBUSY) { @@ -95,21 +124,15 @@ STATIC mp_obj_t dac_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_ // Configuration SAMD51 // Enable APBD clocks and PCHCTRL clocks; GCLK3 at 8 MHz - if (!dac_init) { - dac_init = true; - MCLK->APBDMASK.reg |= MCLK_APBDMASK_DAC; - GCLK->PCHCTRL[DAC_GCLK_ID].reg = GCLK_PCHCTRL_GEN_GCLK3 | GCLK_PCHCTRL_CHEN; + dac_init = true; + MCLK->APBDMASK.reg |= MCLK_APBDMASK_DAC; + GCLK->PCHCTRL[DAC_GCLK_ID].reg = GCLK_PCHCTRL_GEN_GCLK3 | GCLK_PCHCTRL_CHEN; - // Reset DAC registers - dac->CTRLA.bit.SWRST = 1; - while (dac->CTRLA.bit.SWRST) { - } - dac->CTRLB.reg = DAC_CTRLB_REFSEL(DAC_CTRLB_REFSEL_VDDANA_Val); - } else { - dac->CTRLA.bit.ENABLE = 0; - while (dac->SYNCBUSY.bit.ENABLE) { - } + // Reset DAC registers + dac->CTRLA.bit.SWRST = 1; + while (dac->CTRLA.bit.SWRST) { } + dac->CTRLB.reg = DAC_CTRLB_REFSEL(dac_vref_table[self->vref]); dac->DACCTRL[self->id].reg = DAC_DACCTRL_ENABLE | DAC_DACCTRL_REFRESH(2) | DAC_DACCTRL_CCTRL_CC12M; // Enable DAC and wait to be ready @@ -126,7 +149,7 @@ STATIC mp_obj_t dac_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_ 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=%s)", self->id, pin_name(self->gpio_id)); + mp_printf(print, "DAC(%u, Pin=%s, vref=%d)", self->id, pin_name(self->gpio_id), self->vref); } STATIC mp_obj_t dac_write(mp_obj_t self_in, mp_obj_t value_in) {