Skip to content

samd/adc_dac: Implememt adc.read_timed() and dac.write_timed(). #9624

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
155 changes: 125 additions & 30 deletions docs/samd/quickref.rst
Original file line number Diff line number Diff line change
Expand Up @@ -261,30 +261,39 @@ an external ADC.
ADC Constructor
```````````````

.. class:: ADC(dest, *, average=16, vref=n)
.. class:: ADC(dest, *, average=16, bits=12, vref=3, callback=None)
:noindex:

Construct and return a new ADC object using the following parameters:

- *dest* is the Pin object on which the ADC is output.

Keyword arguments:
On the SAMD21/SAMD51 ADC functionality is available on Pins labelled 'Ann'.

- *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.
Use the :ref:`machine.ADC <machine.ADC>` class::

The default setting is for 3.3V. Other values are:
from machine import ADC

==== ============================== ===============================
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
==== ============================== ===============================
adc0 = ADC(Pin("A0")) # create ADC object on ADC pin, average=16
adc0.read_u16() # read value, 0-65536 across voltage range 0.0v - 3.3v
adc1 = ADC(Pin("A1"), average=1) # create ADC object on ADC pin, average=1

The resolution of the ADC is set by the bits keyword option. The default is 12.
Suitable values are 8, 10 and 12. If you need a higher resolution or better
accuracy, use an external ADC. The default value of average is 16.
Averaging is used to reduce the noise. With a value of 16 the LSB noise is
about 1 digit. The vref=n option 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
==== ============================== ===============================

The callback keyword option is used for timed ADC sampling. The callback is executed
when all data has been sampled.

ADC Methods
```````````
Expand All @@ -294,27 +303,66 @@ ADC Methods
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)
----------------------------------
.. method:: read_timed(data, freq)

The DAC class provides a fast digital to analog conversion. Usage example::
Read adc values into the data buffer at a supplied frequency. The buffer
must be preallocated. Values are stored as 16 bit quantities in the binary
range given by the bits option. If bits=12, the value range is 0-4095.
The voltage range is defined by the vref option.
The sampling frequency range depends on the bits and average setting. At bits=8
and average=1, the largest rate is >1 MHz for SAMD21 and 350kHz for SAMD21.
the lowest sampling rate is 1 Hz. The call to the method returns immediately,
The data transfer is done by DMA in the background, controlled by a hardware timer.
If in the constructor a callback was defined, it will be called after all data has been
read. Alternatively, the method busy() can be used to tell, if the capture has finished.

from machine import DAC
Example for a call to adc.read_timed() and a callback::

dac0 = DAC(0) # create DAC object on DAC pin A0
dac0.write(1023) # write value, 0-4095 across voltage range 0.0v - 3.3v
dac1 = DAC(1) # create DAC object on DAC pin A1
dac1.write(2000) # write value, 0-4095 across voltage range 0.0v - 3.3v
from machine import ADC
from array import array

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.
def finished(adc_o):
print("Sampling finished on ADC", adc_o)

# create ADC object on ADC pin A0, average=1
adc = ADC(Pin("A0"), average=1, callback=finished)
buffer = array("H", bytearray(512)) # create an array for 256 ADC values
adc.read_timed(buffer, 10000) # read 256 12 bit values at a frequency of
# 10 kHz and call finished() when done.

.. method:: busy()

busy() returns `True` while the data acquisition using read_timed() is ongoing, `False`
otherwise.

.. method deinit()

Deinitialize as ADC object and release the resources used by it, especially the ADC
channel and the timer used for read_timed().


DAC (digital to analogue conversion)
------------------------------------

DAC Constructor
```````````````

.. class:: DAC(id, *, vref=3)
.. class:: DAC(id, *, vref=3, callback=None)
:noindex:


The DAC class provides a fast digital to analogue conversion. Usage example::

from machine import DAC

dac0 = DAC(0) # create DAC object on DAC pin A0
dac0.write(1023) # write value, 0-4095 across voltage range 0.0V - 3.3V
dac1 = DAC(1) # create DAC object on DAC pin A1
dac1.write(2000) # write value, 0-4095 across voltage range 0.0V - 3.3V

The resolution of the DAC is 12 bit for SAMD51 and 10 bit for SAMD21. SAMD21 devices
have 1 DAC channel at GPIO PA02, accepting only 0 as id. SAMD51 devices have
2 DAC channels at GPIO PA02 and PA05 with values 0 and 1 for the id.
The vref arguments defines the output voltage range, the callback option is used for
dac_timed(). Suitable values for vref are:

Expand All @@ -327,6 +375,7 @@ vref SAMD21 SAMD51
3 - Buffered external reference
==== ============================ ================================


DAC Methods
```````````

Expand All @@ -335,6 +384,52 @@ DAC Methods
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.

.. method:: write_timed(data, freq [, count=1])

The call to dac_timed() allows to output a series of analogue values at a given rate.
data must be a buffer with 16 bit values in the range of the DAC (10 bit of 12 bit).
freq may have a range of 1Hz to ~200kHz for SAMD21 and 1 Hz to ~500kHz for SAMD51.
The optional argument count specifies, how often data output will be repeated. The
range is 1 - 2**32. If count == 0, the data output will be repeated until stopped
by a call to deinit(). If the data has been output count times, a callback will
be called, if given.

Example::

from machine import DAC
from array import array

data = array("H", [i for i in range(0, 4096, 256)]) # create a step sequence

def done(dac_o):
print("Sequence done at", dac_o)

dac = DAC(0, callback=done)
dac.write_timed(data, 1000, 10) # output data 10 times at a rate of 1000 values/s
# and call done() when finished.

The data transfer is done by DMA and not affected by python code execution.
It is possible to restart dac.write_timed() in the callback function with changed
parameters.


.. method:: busy()
:noindex:

Tell, whether a write_timed() activity is ongoing. It returns `True` if yes, `False`
otherwise.


.. method:: deinit()

Deinitialize the DAC and release all resources used by it, especially the DMA channels
and the Timers. On most SAMD21 boards, there is just one timer available for
dac.write_timed() and adc.read_timed_into(). So they cannot run both at the same time,
and releasing the timer may be important. The DAC driver consumes a substantial amount
of current. deinit() will reduce that as well. After calling deinit(), the
DAC objects cannot be used any more and must be recreated.


Software SPI bus
----------------

Expand Down
22 changes: 22 additions & 0 deletions extmod/machine_adc.c
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,24 @@ static mp_obj_t machine_adc_read(mp_obj_t self_in) {
static MP_DEFINE_CONST_FUN_OBJ_1(machine_adc_read_obj, machine_adc_read);
#endif

#if MICROPY_PY_MACHINE_ADC_READ_TIMED
// ADC.atten(value) -- this is a legacy method.
static mp_obj_t machine_adc_read_timed(mp_obj_t self_in, mp_obj_t values, mp_obj_t freq_in) {
machine_adc_obj_t *self = MP_OBJ_TO_PTR(self_in);
mp_int_t freq = mp_obj_get_int(freq_in);
mp_machine_adc_read_timed(self, values, freq);
return mp_const_none;
}
static MP_DEFINE_CONST_FUN_OBJ_3(machine_adc_read_timed_obj, machine_adc_read_timed);

// ADC.busy())
static mp_obj_t machine_adc_busy(mp_obj_t self_in) {
machine_adc_obj_t *self = MP_OBJ_TO_PTR(self_in);
return mp_machine_adc_busy(self);
}
static MP_DEFINE_CONST_FUN_OBJ_1(machine_adc_busy_obj, machine_adc_busy);
#endif

static const mp_rom_map_elem_t machine_adc_locals_dict_table[] = {
#if MICROPY_PY_MACHINE_ADC_INIT
{ MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&machine_adc_init_obj) },
Expand All @@ -164,6 +182,10 @@ static const mp_rom_map_elem_t machine_adc_locals_dict_table[] = {
#if MICROPY_PY_MACHINE_ADC_READ
{ MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&machine_adc_read_obj) },
#endif
#if MICROPY_PY_MACHINE_ADC_READ_TIMED
{ MP_ROM_QSTR(MP_QSTR_read_timed), MP_ROM_PTR(&machine_adc_read_timed_obj) },
{ MP_ROM_QSTR(MP_QSTR_busy), MP_ROM_PTR(&machine_adc_busy_obj) },
#endif

// A port must add ADC class constants defining the following macro.
// It can be defined to nothing if there are no constants.
Expand Down
2 changes: 2 additions & 0 deletions ports/samd/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ MPY_CROSS_FLAGS += -march=$(MPY_CROSS_MCU_ARCH)

SRC_C += \
mcu/$(MCU_SERIES_LOWER)/clock_config.c \
dma_manager.c \
help.c \
machine_bitstream.c \
machine_dac.c \
Expand All @@ -137,6 +138,7 @@ SRC_C += \
samd_soc.c \
samd_spiflash.c \
usbd.c \
tc_manager.c \

SHARED_SRC_C += \
drivers/dht/dht.c \
Expand Down
135 changes: 135 additions & 0 deletions ports/samd/dma_manager.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2022 Robert Hammelrath
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

#include <string.h>
#include "py/mpconfig.h"
#include "sam.h"
#include "dma_manager.h"
#include "samd_soc.h"

#if MICROPY_HW_DMA_MANAGER

// Set a number of dma channels managed here. samd21 has 21 dma channels, samd51
// has 32 channels, as defined by the lib macro DMAC_CH_NUM.
// At first, we use a smaller number here to save RAM. May be increased as needed.

#ifndef MICROPY_HW_DMA_CHANNELS
#if defined(MCU_SAMD21)
#define MICROPY_HW_DMA_CHANNELS 2
#elif defined(MCU_SAMD51)
#define MICROPY_HW_DMA_CHANNELS 4
#endif
#endif

#if MICROPY_HW_DMA_CHANNELS > DMAC_CH_NUM
#error Number of DMA channels too large
#endif

volatile DmacDescriptor dma_desc[MICROPY_HW_DMA_CHANNELS] __attribute__ ((aligned(16)));
static volatile DmacDescriptor dma_write_back[MICROPY_HW_DMA_CHANNELS] __attribute__ ((aligned(16)));

// List of channel flags: true: channel used, false: channel available
static bool channel_list[MICROPY_HW_DMA_CHANNELS];

static bool dma_initialized = false;

// allocate_channel(): retrieve an available channel. Return the number or -1
int allocate_dma_channel(void) {
for (int i = 0; i < MP_ARRAY_SIZE(channel_list); i++) {
if (channel_list[i] == false) { // Channel available
channel_list[i] = true;
return i;
}
}
mp_raise_ValueError(MP_ERROR_TEXT("no dma channel available"));
}

// free_channel(n): Declare channel as free
void free_dma_channel(int n) {
if (n >= 0 && n < MP_ARRAY_SIZE(channel_list)) {
channel_list[n] = false;
}
}

void dma_init(void) {
if (!dma_initialized) {

// Enable the DMA clock
#if defined(MCU_SAMD21)
PM->AHBMASK.reg |= PM_AHBMASK_DMAC;
PM->APBBMASK.reg |= PM_APBBMASK_DMAC;
#elif defined(MCU_SAMD51)
MCLK->AHBMASK.reg |= MCLK_AHBMASK_DMAC;
#endif
// Setup the initial DMA configuration
DMAC->CTRL.reg = DMAC_CTRL_SWRST;
while (DMAC->CTRL.reg & DMAC_CTRL_SWRST) {
}
// Set the DMA descriptor pointers
DMAC->BASEADDR.reg = (uint32_t)dma_desc;
DMAC->WRBADDR.reg = (uint32_t)dma_write_back;
// Enable the DMA
DMAC->CTRL.reg = DMAC_CTRL_DMAENABLE | DMAC_CTRL_LVLEN(0xf);

dma_initialized = true;
}
}

void dma_deinit(void) {
memset((uint8_t *)dma_desc, 0, sizeof(dma_desc));
memset((uint8_t *)dma_write_back, 0, sizeof(dma_write_back));
memset((uint8_t *)channel_list, 0, sizeof(channel_list));
dma_initialized = false;
// Disable DMA
DMAC->CTRL.reg = 0;
for (int ch = 0; ch < DMAC_CH_NUM; ch++) {
dma_register_irq(ch, NULL);
}
}

void dac_stop_dma(int dma_channel, bool wait) {
#if defined(MCU_SAMD21)
NVIC_DisableIRQ(DMAC_IRQn);
DMAC->CHID.reg = dma_channel;
DMAC->CHINTENCLR.reg = DMAC_CHINTENSET_TCMPL | DMAC_CHINTENSET_TERR | DMAC_CHINTENSET_SUSP;
DMAC->CHCTRLA.reg = 0;
while (wait && DMAC->CHCTRLA.bit.ENABLE) {
}
#elif defined(MCU_SAMD51)
if (0 <= dma_channel && dma_channel < 4) {
NVIC_DisableIRQ(DMAC_0_IRQn + dma_channel);
} else if (dma_channel >= 4) {
NVIC_DisableIRQ(DMAC_4_IRQn);
}
DMAC->Channel[dma_channel].CHINTENCLR.reg =
DMAC_CHINTENSET_TCMPL | DMAC_CHINTENSET_TERR | DMAC_CHINTENSET_SUSP;
DMAC->Channel[dma_channel].CHCTRLA.reg = 0;
while (wait && DMAC->Channel[dma_channel].CHCTRLA.bit.ENABLE) {
}
#endif
}

#endif
Loading
Loading
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy