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).
This commit is contained in:
robert-hh 2022-12-02 21:08:46 +01:00 committed by Damien George
parent a73dcb3d22
commit e69313f89c
4 changed files with 143 additions and 33 deletions

View File

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

View File

@ -2,3 +2,4 @@
#define MICROPY_HW_MCU_NAME "SAMD21G18A"
#define MICROPY_HW_XOSC32K (1)
#define MICROPY_HW_ADC_VREF (2)

View File

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

View File

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