diff --git a/docs/samd/quickref.rst b/docs/samd/quickref.rst index 60c57b3a47..907f611ffb 100644 --- a/docs/samd/quickref.rst +++ b/docs/samd/quickref.rst @@ -254,30 +254,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: +On the SAMD21/SAMD51 ADC functionality is available on Pins labelled 'Ann'. - - *dest* is the Pin object on which the ADC is output. +Use the :ref:`machine.ADC ` class:: -Keyword arguments: + from machine import ADC - - *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. + 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 default setting is for 3.3V. Other values are: +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 - ==== ============================== =============================== +==== ============================== =============================== +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 ``````````` @@ -287,27 +296,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: @@ -320,6 +368,7 @@ vref SAMD21 SAMD51 3 - Buffered external reference ==== ============================ ================================ + DAC Methods ``````````` @@ -328,6 +377,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 ---------------- diff --git a/extmod/machine_adc.c b/extmod/machine_adc.c index 11f1123dc7..bb7d2461f2 100644 --- a/extmod/machine_adc.c +++ b/extmod/machine_adc.c @@ -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) }, @@ -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. diff --git a/ports/samd/Makefile b/ports/samd/Makefile index b678cd9828..8f6f7dc92a 100644 --- a/ports/samd/Makefile +++ b/ports/samd/Makefile @@ -101,6 +101,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 \ @@ -119,6 +120,7 @@ SRC_C += \ samd_soc.c \ samd_spiflash.c \ usbd.c \ + tc_manager.c \ SHARED_SRC_C += \ drivers/dht/dht.c \ diff --git a/ports/samd/dma_manager.c b/ports/samd/dma_manager.c new file mode 100644 index 0000000000..2ca7f7ba10 --- /dev/null +++ b/ports/samd/dma_manager.c @@ -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 +#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 diff --git a/ports/samd/dma_manager.h b/ports/samd/dma_manager.h new file mode 100644 index 0000000000..3dea43e41e --- /dev/null +++ b/ports/samd/dma_manager.h @@ -0,0 +1,39 @@ +/* + * 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. + */ +#ifndef MICROPY_INCLUDED_SAMD_DMACHANNEL_H +#define MICROPY_INCLUDED_SAMD_DMACHANNEL_H + +#include "py/runtime.h" + +int allocate_dma_channel(void); +void free_dma_channel(int n); +void dma_init(void); +void dma_deinit(void); +void dac_stop_dma(int dma_channel, bool wait); + +extern volatile DmacDescriptor dma_desc[]; + +#endif // MICROPY_INCLUDED_SAMD_DMACHANNEL_H diff --git a/ports/samd/machine_adc.c b/ports/samd/machine_adc.c index 665af6f6f2..e5028f2935 100644 --- a/ports/samd/machine_adc.c +++ b/ports/samd/machine_adc.c @@ -28,9 +28,16 @@ // This file is never compiled standalone, it's included directly from // extmod/machine_adc.c via MICROPY_PY_MACHINE_ADC_INCLUDEFILE. -#include "py/mphal.h" +#include +#include "py/obj.h" +#include "py/mperrno.h" + +#include "mphalport.h" #include "sam.h" #include "pin_af.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,12 +46,17 @@ typedef struct _machine_adc_obj_t { uint8_t avg; uint8_t bits; uint8_t vref; + #if MICROPY_PY_MACHINE_ADC_READ_TIMED + int8_t dma_channel; + int8_t tc_index; + #endif } 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 @@ -54,9 +66,21 @@ static uint8_t adc_vref_table[] = { #else #define DEFAULT_ADC_VREF (3) #endif +#define MAX_ADC_VREF (4) #define ADC_EVSYS_CHANNEL 0 +typedef struct _device_mgmt_t { + bool init; + #if MICROPY_PY_MACHINE_ADC_READ_TIMED + bool busy; + mp_obj_t callback; + mp_obj_t self; + #endif +} device_mgmt_t; + +device_mgmt_t device_mgmt[ADC_INST_NUM]; + #elif defined(MCU_SAMD51) static uint8_t adc_vref_table[] = { @@ -69,6 +93,17 @@ static uint8_t adc_vref_table[] = { #else #define DEFAULT_ADC_VREF (3) #endif +#define MAX_ADC_VREF (5) + +typedef struct _device_mgmt_t { + bool init; + bool busy; + int8_t dma_channel; + mp_obj_t callback; + mp_obj_t self; +} device_mgmt_t; + +device_mgmt_t device_mgmt[ADC_INST_NUM]; #endif // defined(MCU_SAMD21) @@ -76,15 +111,48 @@ static uint8_t adc_vref_table[] = { #define MICROPY_PY_MACHINE_ADC_CLASS_CONSTANTS Adc *const adc_bases[] = ADC_INSTS; -uint32_t busy_flags = 0; -bool init_flags[2] = {false, false}; -static void adc_init(machine_adc_obj_t *self); +uint32_t ch_busy_flags = 0; + static uint8_t resolution[] = { ADC_CTRLB_RESSEL_8BIT_Val, ADC_CTRLB_RESSEL_10BIT_Val, ADC_CTRLB_RESSEL_12BIT_Val }; +static void adc_init(machine_adc_obj_t *self); extern mp_int_t log2i(mp_int_t num); +#if MICROPY_PY_MACHINE_ADC_READ_TIMED + +// 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 | DMAC_CHINTFLAG_TERR | DMAC_CHINTFLAG_SUSP; + ADC->EVCTRL.bit.STARTEI = 0; + device_mgmt[0].busy = 0; + if (device_mgmt[0].callback != MP_OBJ_NULL) { + mp_sched_schedule(device_mgmt[0].callback, device_mgmt[0].self); + } + + #elif defined(MCU_SAMD51) + DMAC->Channel[dma_channel].CHINTFLAG.reg = + DMAC_CHINTFLAG_TCMPL | DMAC_CHINTFLAG_TERR | DMAC_CHINTFLAG_SUSP; + if (device_mgmt[0].dma_channel == dma_channel) { + device_mgmt[0].busy = 0; + if (device_mgmt[0].callback != MP_OBJ_NULL) { + mp_sched_schedule(device_mgmt[0].callback, device_mgmt[0].self); + } + } else if (device_mgmt[1].dma_channel == dma_channel) { + device_mgmt[1].busy = 0; + if (device_mgmt[1].callback != MP_OBJ_NULL) { + mp_sched_schedule(device_mgmt[1].callback, device_mgmt[1].self); + } + } + #endif +} +#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); @@ -95,12 +163,16 @@ static void mp_machine_adc_print(const mp_print_t *print, mp_obj_t self_in, mp_p } static mp_obj_t mp_machine_adc_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, ARG_vref }; + + enum { ARG_id, ARG_bits, ARG_average, ARG_vref, ARG_callback }; 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} }, + #if MICROPY_PY_MACHINE_ADC_READ_TIMED + { MP_QSTR_callback, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + #endif }; // Parse the arguments. @@ -109,28 +181,39 @@ static mp_obj_t mp_machine_adc_make_new(const mp_obj_type_t *type, size_t n_args // Unpack and check, whether the pin has ADC capability int id = mp_hal_get_pin_obj(args[ARG_id].u_obj); - adc_config_t adc_config = get_adc_config(id, busy_flags); + adc_config_t adc_config = get_adc_config(id, ch_busy_flags); // Now that we have a valid device and channel, create and populate the ADC instance machine_adc_obj_t *self = mp_obj_malloc(machine_adc_obj_t, &machine_adc_type); 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); self->avg = (avg <= 10 ? avg : 10); uint8_t vref = args[ARG_vref].u_int; - if (0 <= vref && vref < sizeof(adc_vref_table)) { + if (0 <= vref && vref <= MAX_ADC_VREF) { 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; + ch_busy_flags |= (1 << (self->adc_config.device * 16 + self->adc_config.channel)); + device_mgmt[self->adc_config.device].init = false; + + #if MICROPY_PY_MACHINE_ADC_READ_TIMED + device_mgmt[adc_config.device].callback = args[ARG_callback].u_obj; + if (device_mgmt[adc_config.device].callback == mp_const_none) { + device_mgmt[adc_config.device].callback = MP_OBJ_NULL; + } else { + device_mgmt[adc_config.device].self = self; + } + self->dma_channel = -1; + self->tc_index = -1; + #endif adc_init(self); @@ -140,6 +223,15 @@ static mp_obj_t mp_machine_adc_make_new(const mp_obj_type_t *type, size_t n_args // read_u16() static mp_int_t mp_machine_adc_read_u16(machine_adc_obj_t *self) { Adc *adc = adc_bases[self->adc_config.device]; + // Set the reference voltage. Default: external AREFA. + adc->REFCTRL.reg = adc_vref_table[self->vref]; + + #if MICROPY_PY_MACHINE_ADC_READ_TIMED + if (device_mgmt[self->adc_config.device].busy != 0) { + mp_raise_OSError(MP_EBUSY); + } + #endif + // Set the reference voltage. Default: external AREFA. adc->REFCTRL.reg = adc_vref_table[self->vref]; // Set Input channel and resolution @@ -147,6 +239,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,29 +255,168 @@ static mp_int_t mp_machine_adc_read_u16(machine_adc_obj_t *self) { return adc->RESULT.reg * (65536 / (1 << self->bits)); } -// 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 MICROPY_PY_MACHINE_ADC_READ_TIMED + +static void mp_machine_adc_read_timed(machine_adc_obj_t *self, mp_obj_t values, mp_int_t freq) { + 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) { + if (self->tc_index == -1) { + self->tc_index = allocate_tc_instance(); + } + if (self->dma_channel == -1) { + self->dma_channel = allocate_dma_channel(); + dma_init(); + dma_register_irq(self->dma_channel, adc_irq_handler); + } + // 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; + // 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) + 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; + device_mgmt[0].busy = 1; + + #elif defined(MCU_SAMD51) + configure_tc(self->tc_index, freq, 0); + device_mgmt[self->adc_config.device].dma_channel = self->dma_channel; + + // 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) { + } + // Wait a little bit allowing the ADC to settle. + mp_hal_delay_us(15); + + 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].CHINTENSET.reg = DMAC_CHINTENSET_TCMPL; + DMAC->Channel[self->dma_channel].CHCTRLA.reg |= DMAC_CHCTRLA_ENABLE; + + if (self->dma_channel < 4) { + NVIC_EnableIRQ(DMAC_0_IRQn + self->dma_channel); + } else { + NVIC_EnableIRQ(DMAC_4_IRQn); + } + device_mgmt[self->adc_config.device].busy = 1; + + #endif // defined SAMD21 or SAMD51 + + } } -void adc_deinit_all(void) { - busy_flags = 0; - init_flags[0] = 0; - init_flags[1] = 0; +// busy() : Report, if the ADC device is busy +static mp_obj_t mp_machine_adc_busy(machine_adc_obj_t *self) { + return device_mgmt[self->adc_config.device].busy ? mp_const_true : mp_const_false; } +#endif + +// deinit() : release the ADC channel +static void mp_machine_adc_deinit(machine_adc_obj_t *self) { + ch_busy_flags &= ~((1 << (self->adc_config.device * 16 + self->adc_config.channel))); + #if MICROPY_PY_MACHINE_ADC_READ_TIMED + if (self->dma_channel >= 0) { + #if defined(MCU_SAMD51) + if (self->dma_channel == device_mgmt[self->adc_config.device].dma_channel) { + device_mgmt[self->adc_config.device].dma_channel = -1; + device_mgmt[self->adc_config.device].busy = 0; + } + #endif + 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; + } + #endif +} + +#if MICROPY_PY_MACHINE_ADC_READ_TIMED +void adc_deinit_all(void) { + ch_busy_flags = 0; + device_mgmt[0].init = 0; + #if defined(MCU_SAMD51) + device_mgmt[0].dma_channel = -1; + device_mgmt[1].init = 0; + device_mgmt[1].dma_channel = -1; + #endif +} +#else +void adc_deinit_all(void) { + ch_busy_flags = 0; + device_mgmt[0].init = 0; + #if defined(MCU_SAMD51) + device_mgmt[1].init = 0; + #endif +} +#endif + static void adc_init(machine_adc_obj_t *self) { // ADC & clock init is done only once per ADC - if (init_flags[self->adc_config.device] == false) { + if (device_mgmt[self->adc_config.device].init == false) { Adc *adc = adc_bases[self->adc_config.device]; - init_flags[self->adc_config.device] = true; + device_mgmt[self->adc_config.device].init = true; #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 +429,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 +442,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 +469,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 c611f95e65..425c01f4a8 100644 --- a/ports/samd/machine_dac.c +++ b/ports/samd/machine_dac.c @@ -31,28 +31,33 @@ #include #include "py/obj.h" +#include "py/runtime.h" +#include "py/mperrno.h" #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 _dac_obj_t { mp_obj_base_t base; uint8_t id; - mp_hal_pin_obj_t gpio_id; + bool initialized; uint8_t vref; -} dac_obj_t; - -static dac_obj_t dac_obj[] = { - #if defined(MCU_SAMD21) - {{&machine_dac_type}, 0, PIN_PA02}, - #elif defined(MCU_SAMD51) - {{&machine_dac_type}, 0, PIN_PA02}, - {{&machine_dac_type}, 1, PIN_PA05}, + mp_hal_pin_obj_t gpio_id; + #if MICROPY_PY_MACHINE_DAC_TIMED + int8_t dma_channel; + int8_t tc_index; + bool busy; + uint32_t count; + mp_obj_t callback; #endif -}; +} dac_obj_t; Dac *const dac_bases[] = DAC_INSTS; +static void dac_init(dac_obj_t *self); #if defined(MCU_SAMD21) @@ -60,8 +65,20 @@ Dac *const dac_bases[] = DAC_INSTS; #define DEFAULT_DAC_VREF (1) #define MAX_DAC_VREF (2) +static dac_obj_t dac_obj[] = { + {{&machine_dac_type}, 0, 0, DEFAULT_DAC_VREF, PIN_PA02}, +}; + #elif defined(MCU_SAMD51) +#define MAX_DAC_VALUE (4095) +#define DEFAULT_DAC_VREF (2) +#define MAX_DAC_VREF (3) + +static dac_obj_t dac_obj[] = { + {{&machine_dac_type}, 0, 0, DEFAULT_DAC_VREF, PIN_PA02}, + {{&machine_dac_type}, 1, 0, DEFAULT_DAC_VREF, PIN_PA05}, +}; // 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 @@ -69,20 +86,61 @@ 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 +#endif // defined SAMD21 or SAMD51 + +#if MICROPY_PY_MACHINE_DAC_TIMED + +void dac_irq_handler(int dma_channel) { + dac_obj_t *self; + + #if defined(MCU_SAMD21) + DMAC->CHID.reg = dma_channel; + DMAC->CHINTFLAG.reg = DMAC_CHINTFLAG_TCMPL; + self = &dac_obj[0]; + if (self->count > 1) { + self->count -= 1; + dma_desc[self->dma_channel].BTCTRL.reg |= DMAC_BTCTRL_VALID; + DMAC->CHCTRLA.reg |= DMAC_CHCTRLA_ENABLE; + } else { + self->busy = false; + if (self->callback != MP_OBJ_NULL) { + mp_sched_schedule(self->callback, self); + } + } + + #elif defined(MCU_SAMD51) + DMAC->Channel[dma_channel].CHINTFLAG.reg = DMAC_CHINTFLAG_TCMPL; + if (dac_obj[0].dma_channel == dma_channel) { + self = &dac_obj[0]; + } else { + self = &dac_obj[1]; + } + if (self->count > 1) { + self->count -= 1; + dma_desc[self->dma_channel].BTCTRL.reg |= DMAC_BTCTRL_VALID; + DMAC->Channel[self->dma_channel].CHCTRLA.reg |= DMAC_CHCTRLA_ENABLE; + } else { + self->busy = false; + if (self->callback != MP_OBJ_NULL) { + mp_sched_schedule(self->callback, self); + } + } + #endif +} + +#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 *all_args) { - enum { ARG_id, ARG_vref }; + enum { ARG_id, ARG_vref, ARG_callback }; 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} }, + #if MICROPY_PY_MACHINE_DAC_TIMED + { MP_QSTR_callback, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + #endif }; // Parse the arguments. @@ -91,10 +149,10 @@ static mp_obj_t dac_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_ uint8_t id = args[ARG_id].u_int; dac_obj_t *self = NULL; - if (0 <= id && id <= MP_ARRAY_SIZE(dac_obj)) { + if (0 <= id && id < MP_ARRAY_SIZE(dac_obj)) { self = &dac_obj[id]; } else { - mp_raise_ValueError(MP_ERROR_TEXT("invalid Pin for DAC")); + mp_raise_ValueError(MP_ERROR_TEXT("invalid DAC ID")); } uint8_t vref = args[ARG_vref].u_int; @@ -102,54 +160,81 @@ static mp_obj_t dac_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_ self->vref = vref; } - Dac *dac = dac_bases[0]; // Just one DAC - - // Init DAC - #if defined(MCU_SAMD21) - - // Configuration SAMD21 - // Enable APBC clocks and PCHCTRL clocks; GCLK3 at 1 MHz - PM->APBCMASK.reg |= PM_APBCMASK_DAC; - GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK3 | GCLK_CLKCTRL_ID_DAC; - while (GCLK->STATUS.bit.SYNCBUSY) { - } - // Reset DAC registers - dac->CTRLA.bit.SWRST = 1; - while (dac->CTRLA.bit.SWRST) { - } - 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) { - } - - #elif defined(MCU_SAMD51) - - // Configuration SAMD51 - // Enable APBD clocks and PCHCTRL clocks; GCLK3 at 8 MHz - 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_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 - dac->CTRLA.bit.ENABLE = 1; - while (dac->SYNCBUSY.bit.ENABLE) { + #if MICROPY_PY_MACHINE_DAC_TIMED + self->callback = args[ARG_callback].u_obj; + if (self->callback == mp_const_none) { + self->callback = MP_OBJ_NULL; } + self->dma_channel = -1; + self->tc_index = -1; + self->busy = false; #endif + self->initialized = false; + + dac_init(self); // Set the port as given in self->gpio_id as DAC mp_hal_set_pin_mux(self->gpio_id, ALT_FCT_DAC); return MP_OBJ_FROM_PTR(self); } +static void dac_init(dac_obj_t *self) { + // Init DAC + if (self->initialized == false) { + Dac *dac = dac_bases[0]; // Just one DAC + + #if defined(MCU_SAMD21) + + // Configuration SAMD21 + // Enable APBC clocks and PCHCTRL clocks; GCLK5 at 48 MHz + PM->APBCMASK.reg |= PM_APBCMASK_DAC; + GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK5 | GCLK_CLKCTRL_ID_DAC; + while (GCLK->STATUS.bit.SYNCBUSY) { + } + // Reset DAC registers + dac->CTRLA.bit.SWRST = 1; + while (dac->CTRLA.bit.SWRST) { + } + 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) { + } + + #elif defined(MCU_SAMD51) + + // Configuration SAMD51 + + // If the DAC is enabled it was already reset + // In that case just disable it. + if (dac->CTRLA.bit.ENABLE) { + // Enable DAC and wait to be ready + dac->CTRLA.bit.ENABLE = 0; + while (dac->SYNCBUSY.bit.ENABLE) { + } + } else { + // Enable APBD clocks and PCHCTRL clocks; GCLK5 at 48 MHz + MCLK->APBDMASK.reg |= MCLK_APBDMASK_DAC; + GCLK->PCHCTRL[DAC_GCLK_ID].reg = GCLK_PCHCTRL_GEN_GCLK5 | GCLK_PCHCTRL_CHEN; + // 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 + dac->CTRLA.bit.ENABLE = 1; + while (dac->SYNCBUSY.bit.ENABLE) { + } + + #endif // defined SAMD21 or SAMD51 + } + self->initialized = true; +} + 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); @@ -157,14 +242,25 @@ static void dac_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t static mp_obj_t dac_write(mp_obj_t self_in, mp_obj_t value_in) { Dac *dac = dac_bases[0]; // Just one DAC + dac_obj_t *self = self_in; + + if (self->initialized == false) { + mp_raise_OSError(MP_ENODEV); + } + #if MICROPY_PY_MACHINE_DAC_TIMED + if (self->busy != false) { + mp_raise_OSError(MP_EBUSY); + } + #endif + int value = mp_obj_get_int(value_in); + if (value < 0 || value > MAX_DAC_VALUE) { mp_raise_ValueError(MP_ERROR_TEXT("value out of range")); } #if defined(MCU_SAMD21) dac->DATA.reg = value; #elif defined(MCU_SAMD51) - dac_obj_t *self = self_in; dac->DATA[self->id].reg = value; #endif @@ -172,8 +268,157 @@ 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); +#if MICROPY_PY_MACHINE_DAC_TIMED + +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; + + if (self->initialized == false) { + mp_raise_OSError(MP_ENODEV); + } + + mp_get_buffer_raise(args[1], &src, MP_BUFFER_READ); + if (n_args > 3) { + self->count = mp_obj_get_int(args[3]); + } else { + self->count = 1; + } + if (src.len >= 2) { + int freq = mp_obj_get_int(args[2]); + if (self->tc_index == -1) { + self->tc_index = allocate_tc_instance(); + } + if (self->dma_channel == -1) { + self->dma_channel = allocate_dma_channel(); + dma_init(); + dma_register_irq(self->dma_channel, dac_irq_handler); + } + // Configure TC; no need to check the return value + configure_tc(self->tc_index, freq, 0); + self->busy = true; + + // Configure DMA for halfword output to the DAC + #if defined(MCU_SAMD21) + + dma_desc[self->dma_channel].BTCTRL.reg = + DMAC_BTCTRL_VALID | DMAC_BTCTRL_BLOCKACT_NOACT | + DMAC_BTCTRL_BEATSIZE_HWORD | DMAC_BTCTRL_SRCINC | 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)(src.buf) + src.len; + dma_desc[self->dma_channel].DSTADDR.reg = (uint32_t)(&dac->DATA.reg); + if (self->count >= 1) { + dma_desc[self->dma_channel].DESCADDR.reg = 0; // ONE_SHOT + } else { + dma_desc[self->dma_channel].DESCADDR.reg = (uint32_t)(&dma_desc[self->dma_channel].BTCTRL.reg); + } + + 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(TC3_DMAC_ID_OVF + 3 * self->tc_index); + DMAC->CHINTENSET.reg = DMAC_CHINTFLAG_TCMPL; + DMAC->CHCTRLA.reg |= DMAC_CHCTRLA_ENABLE; + + NVIC_EnableIRQ(DMAC_IRQn); + + #elif defined(MCU_SAMD51) + + dma_desc[self->dma_channel].BTCTRL.reg = + DMAC_BTCTRL_VALID | DMAC_BTCTRL_BLOCKACT_NOACT | + DMAC_BTCTRL_BEATSIZE_HWORD | DMAC_BTCTRL_SRCINC | 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)(src.buf) + src.len; + dma_desc[self->dma_channel].DSTADDR.reg = (uint32_t)(&dac->DATA[self->id].reg); + if (self->count >= 1) { + dma_desc[self->dma_channel].DESCADDR.reg = 0; // ONE_SHOT + } else { + dma_desc[self->dma_channel].DESCADDR.reg = (uint32_t)(&dma_desc[self->dma_channel].BTCTRL.reg); + } + + 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].CHINTENSET.reg = DMAC_CHINTENSET_TCMPL; + DMAC->Channel[self->dma_channel].CHCTRLA.reg |= DMAC_CHCTRLA_ENABLE; + + if (self->dma_channel < 4) { + NVIC_EnableIRQ(DMAC_0_IRQn + self->dma_channel); + } else { + NVIC_EnableIRQ(DMAC_4_IRQn); + } + + #endif // defined SAMD21 or SAMD51 + } + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(dac_write_timed_obj, 3, 4, dac_write_timed); + +static void dac_deinit_channel(dac_obj_t *self) { + self->initialized = false; + 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; + } + self->callback = MP_OBJ_NULL; + self->busy = false; +} + +// Reset DAC and clear the DMA channel entries in the DAC objects. +void dac_deinit_all(void) { + // Reset the DAC to lower the current consumption as SAMD21 + dac_bases[0]->CTRLA.bit.SWRST = 1; + dac_deinit_channel(&dac_obj[0]); + #if defined(MCU_SAMD51) + dac_deinit_channel(&dac_obj[1]); + #endif +} + +static mp_obj_t dac_deinit(mp_obj_t self_in) { + dac_deinit_all(); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_1(dac_deinit_obj, dac_deinit); + +// busy() : Report, if the DAC device is busy +static mp_obj_t machine_dac_busy(mp_obj_t self_in) { + dac_obj_t *self = MP_OBJ_TO_PTR(self_in); + return self->busy ? mp_const_true : mp_const_false; +} +static MP_DEFINE_CONST_FUN_OBJ_1(machine_dac_busy_obj, machine_dac_busy); +#else + +void dac_deinit_all(void) { + // Reset the DAC to lower the current consumption as SAMD21 + dac_bases[0]->CTRLA.bit.SWRST = 1; + dac_obj[0].initialized = false; + #if defined(MCU_SAMD51) + dac_obj[1].initialized = false; + #endif +} + +#endif + static const mp_rom_map_elem_t dac_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&dac_write_obj) }, + #if MICROPY_PY_MACHINE_DAC_TIMED + { MP_ROM_QSTR(MP_QSTR_busy), MP_ROM_PTR(&machine_dac_busy_obj) }, + { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&dac_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR_write_timed), MP_ROM_PTR(&dac_write_timed_obj) }, + #endif }; static MP_DEFINE_CONST_DICT(dac_locals_dict, dac_locals_dict_table); diff --git a/ports/samd/main.c b/ports/samd/main.c index 2bbaf63e6e..b9758c2eb7 100644 --- a/ports/samd/main.c +++ b/ports/samd/main.c @@ -35,9 +35,12 @@ #include "shared/runtime/softtimer.h" #include "shared/tinyusb/mp_usbd.h" #include "clock_config.h" +#include "dma_manager.h" +#include "tc_manager.h" extern uint8_t _sstack, _estack, _sheap, _eheap; extern void adc_deinit_all(void); +extern void dac_deinit_all(void); extern void pin_irq_deinit_all(void); extern void pwm_deinit_all(void); extern void sercom_deinit_all(void); @@ -87,9 +90,18 @@ void samd_main(void) { soft_reset_exit: mp_printf(MP_PYTHON_PRINTER, "MPY: soft reboot\n"); + #if MICROPY_HW_DMA_MANAGER + dma_deinit(); + #endif + #if MICROPY_HW_TC_MANAGER + tc_deinit(); + #endif #if MICROPY_PY_MACHINE_ADC adc_deinit_all(); #endif + #if MICROPY_PY_MACHINE_DAC + dac_deinit_all(); + #endif pin_irq_deinit_all(); #if MICROPY_PY_MACHINE_PWM pwm_deinit_all(); diff --git a/ports/samd/mcu/samd21/mpconfigmcu.h b/ports/samd/mcu/samd21/mpconfigmcu.h index fdad3fee4a..43da221e13 100644 --- a/ports/samd/mcu/samd21/mpconfigmcu.h +++ b/ports/samd/mcu/samd21/mpconfigmcu.h @@ -70,6 +70,13 @@ unsigned long trng_random_u32(int delay); #define MICROPY_PY_DEFLATE (SAMD21_EXTRA_FEATURES) #define MICROPY_PY_ONEWIRE (SAMD21_EXTRA_FEATURES) +#ifndef MICROPY_PY_MACHINE_ADC_READ_TIMED +#define MICROPY_PY_MACHINE_ADC_READ_TIMED (SAMD21_EXTRA_FEATURES) +#endif +#ifndef MICROPY_PY_MACHINE_DAC_TIMED +#define MICROPY_PY_MACHINE_DAC_TIMED (SAMD21_EXTRA_FEATURES) +#endif + #ifndef MICROPY_PY_MACHINE_PIN_BOARD_CPU #define MICROPY_PY_MACHINE_PIN_BOARD_CPU (1) #endif diff --git a/ports/samd/mcu/samd51/mpconfigmcu.h b/ports/samd/mcu/samd51/mpconfigmcu.h index d567f28eb4..7c0cc0da52 100644 --- a/ports/samd/mcu/samd51/mpconfigmcu.h +++ b/ports/samd/mcu/samd51/mpconfigmcu.h @@ -17,10 +17,10 @@ unsigned long trng_random_u32(void); // fatfs configuration used in ffconf.h -#define MICROPY_FATFS_ENABLE_LFN (1) -#define MICROPY_FATFS_RPATH (2) -#define MICROPY_FATFS_MAX_SS (4096) -#define MICROPY_FATFS_LFN_CODE_PAGE 437 /* 1=SFN/ANSI 437=LFN/U.S.(OEM) */ +#define MICROPY_FATFS_ENABLE_LFN (1) +#define MICROPY_FATFS_RPATH (2) +#define MICROPY_FATFS_MAX_SS (4096) +#define MICROPY_FATFS_LFN_CODE_PAGE 437 /* 1=SFN/ANSI 437=LFN/U.S.(OEM) */ #define VFS_BLOCK_SIZE_BYTES (1536) // @@ -30,6 +30,12 @@ unsigned long trng_random_u32(void); #ifndef MICROPY_HW_UART_RTSCTS #define MICROPY_HW_UART_RTSCTS (1) #endif +#ifndef MICROPY_PY_MACHINE_ADC_READ_TIMED +#define MICROPY_PY_MACHINE_ADC_READ_TIMED (1) +#endif +#ifndef MICROPY_PY_MACHINE_DAC_TIMED +#define MICROPY_PY_MACHINE_DAC_TIMED (1) +#endif #define CPU_FREQ (120000000) #define DFLL48M_FREQ (48000000) diff --git a/ports/samd/mpconfigport.h b/ports/samd/mpconfigport.h index 0b47500bf7..1a4a2741a3 100644 --- a/ports/samd/mpconfigport.h +++ b/ports/samd/mpconfigport.h @@ -128,6 +128,11 @@ #define MICROPY_PY_MACHINE_WDT_TIMEOUT_MS (1) #define MICROPY_PLATFORM_VERSION "ASF4" +#if MICROPY_PY_MACHINE_DAC_TIMED || MICROPY_PY_MACHINE_ADC_READ_TIMED +#define MICROPY_HW_DMA_MANAGER (1) +#define MICROPY_HW_TC_MANAGER (1) +#endif + #define MP_STATE_PORT MP_STATE_VM // Miscellaneous settings diff --git a/ports/samd/samd_isr.c b/ports/samd/samd_isr.c index 7c4c1d060f..6888d8a1dc 100644 --- a/ports/samd/samd_isr.c +++ b/ports/samd/samd_isr.c @@ -164,6 +164,51 @@ void Sercom7_Handler(void) { } #endif +// DMAC IRQ handler support +#if defined(MCU_SAMD21) +#define DMAC_FIRST_CHANNEL 0 +#else +#define DMAC_FIRST_CHANNEL 4 +#endif + +void (*dma_irq_handler_table[DMAC_CH_NUM])(int num) = {}; + +void dma_register_irq(int dma_channel, void (*dma_irq_handler)) { + if (dma_channel < DMAC_CH_NUM) { + dma_irq_handler_table[dma_channel] = dma_irq_handler; + } +} + +void DMAC0_Handler(void) { + if (dma_irq_handler_table[0]) { + dma_irq_handler_table[0](0); + } +} +void DMAC1_Handler(void) { + if (dma_irq_handler_table[1]) { + dma_irq_handler_table[1](1); + } +} +void DMAC2_Handler(void) { + if (dma_irq_handler_table[2]) { + dma_irq_handler_table[2](2); + } +} +void DMAC3_Handler(void) { + if (dma_irq_handler_table[3]) { + dma_irq_handler_table[3](3); + } +} +void DMACn_Handler(void) { + for (uint32_t mask = 1 << DMAC_FIRST_CHANNEL, dma_channel = DMAC_FIRST_CHANNEL; + dma_channel < DMAC_CH_NUM; + mask <<= 1, dma_channel += 1) { + if ((DMAC->INTSTATUS.reg & mask) && dma_irq_handler_table[dma_channel]) { + dma_irq_handler_table[dma_channel](dma_channel); + } + } +} + #if defined(MCU_SAMD21) const ISR isr_vector[] __attribute__((section(".isr_vector"))) = { (ISR)&_estack, @@ -188,7 +233,7 @@ const ISR isr_vector[] __attribute__((section(".isr_vector"))) = { 0, // 3 Real-Time Counter (RTC) &EIC_Handler, // 4 External Interrupt Controller (EIC) 0, // 5 Non-Volatile Memory Controller (NVMCTRL) - 0, // 6 Direct Memory Access Controller (DMAC) + &DMACn_Handler, // 6 Direct Memory Access Controller (DMAC) USB_Handler_wrapper,// 7 Universal Serial Bus (USB) 0, // 8 Event System Interface (EVSYS) &Sercom0_Handler, // 9 Serial Communication Interface 0 (SERCOM0) @@ -261,11 +306,11 @@ const ISR isr_vector[] __attribute__((section(".isr_vector"))) = { 0, // 28 Frequency Meter (FREQM) 0, // 29 Non-Volatile Memory Controller (NVMCTRL): NVMCTRL_0 - _7 0, // 30 Non-Volatile Memory Controller (NVMCTRL): NVMCTRL_8 - _10 - 0, // 31 Direct Memory Access Controller (DMAC): DMAC_SUSP_0, DMAC_TCMPL_0, DMAC_TERR_0 - 0, // 32 Direct Memory Access Controller (DMAC): DMAC_SUSP_1, DMAC_TCMPL_1, DMAC_TERR_1 - 0, // 33 Direct Memory Access Controller (DMAC): DMAC_SUSP_2, DMAC_TCMPL_2, DMAC_TERR_2 - 0, // 34 Direct Memory Access Controller (DMAC): DMAC_SUSP_3, DMAC_TCMPL_3, DMAC_TERR_3 - 0, // 35 Direct Memory Access Controller (DMAC): DMAC_SUSP_4 - _31, DMAC_TCMPL_4 _31, DMAC_TERR_4- _31 + &DMAC0_Handler, // 31 Direct Memory Access Controller (DMAC): DMAC_SUSP_0, DMAC_TCMPL_0, DMAC_TERR_0 + &DMAC1_Handler, // 32 Direct Memory Access Controller (DMAC): DMAC_SUSP_1, DMAC_TCMPL_1, DMAC_TERR_1 + &DMAC2_Handler, // 33 Direct Memory Access Controller (DMAC): DMAC_SUSP_2, DMAC_TCMPL_2, DMAC_TERR_2 + &DMAC3_Handler, // 34 Direct Memory Access Controller (DMAC): DMAC_SUSP_3, DMAC_TCMPL_3, DMAC_TERR_3 + &DMACn_Handler, // 35 Direct Memory Access Controller (DMAC): DMAC_SUSP_4 - _31, DMAC_TCMPL_4 _31, DMAC_TERR_4- _31 0, // 36 Event System Interface (EVSYS): EVSYS_EVD_0, EVSYS_OVR_0 0, // 37 Event System Interface (EVSYS): EVSYS_EVD_1, EVSYS_OVR_1 0, // 38 Event System Interface (EVSYS): EVSYS_EVD_2, EVSYS_OVR_2 diff --git a/ports/samd/samd_soc.c b/ports/samd/samd_soc.c index 1c8d49d157..7e8c48356e 100644 --- a/ports/samd/samd_soc.c +++ b/ports/samd/samd_soc.c @@ -68,7 +68,6 @@ static void usb_init(void) { void init_us_counter(void) { #if defined(MCU_SAMD21) - PM->APBCMASK.bit.TC3_ = 1; // Enable TC3 clock PM->APBCMASK.bit.TC4_ = 1; // Enable TC4 clock // Select multiplexer generic clock source and enable. GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK3 | GCLK_CLKCTRL_ID_TC4_TC5; diff --git a/ports/samd/samd_soc.h b/ports/samd/samd_soc.h index 707d2f8edf..2477775735 100644 --- a/ports/samd/samd_soc.h +++ b/ports/samd/samd_soc.h @@ -43,6 +43,7 @@ void USB_Handler_wrapper(void); void sercom_enable(Sercom *spi, int state); void sercom_register_irq(int sercom_id, void (*sercom_irq_handler)); +void dma_register_irq(int dma_channel, void (*dma_irq_handler)); // Each device has a unique 128-bit serial number. The uniqueness of the serial number is // guaranteed only when using all 128 bits. diff --git a/ports/samd/tc_manager.c b/ports/samd/tc_manager.c new file mode 100644 index 0000000000..b6b981087c --- /dev/null +++ b/ports/samd/tc_manager.c @@ -0,0 +1,185 @@ +/* + * 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 +#include "py/mpconfig.h" +#include "sam.h" +#include "tc_manager.h" + +#if MICROPY_HW_TC_MANAGER + +// List of channel flags: true: channel used, false: channel available +// Two Tc instances are used by the usec counter and cannot be assigned. +#if defined(MCU_SAMD21) +static bool instance_flag[TC_INST_NUM] = {false, true, true}; +#elif defined(MCU_SAMD51) +static bool instance_flag[TC_INST_NUM] = {true, true}; +#endif +Tc *tc_instance_list[TC_INST_NUM] = TC_INSTS; +extern const uint16_t prescaler_table[]; + +// allocate_tc_instance(): retrieve an available instance. Return the pointer or NULL +int allocate_tc_instance(void) { + for (int i = 0; i < MP_ARRAY_SIZE(instance_flag); i++) { + if (instance_flag[i] == false) { // available + instance_flag[i] = true; + return i; + } + } + mp_raise_ValueError(MP_ERROR_TEXT("no Timer available")); +} + +// free_tc_instance(n): Declare instance as free +void free_tc_instance(int tc_index) { + if (tc_index >= 0 && tc_index < MP_ARRAY_SIZE(instance_flag)) { + instance_flag[tc_index] = false; + } +} + +int configure_tc(int tc_index, int freq, int event) { + uint32_t clock = DFLL48M_FREQ; // Use the fixed 48M clock + + Tc *tc; + if (tc_index < MP_ARRAY_SIZE(instance_flag)) { + tc = tc_instance_list[tc_index]; + } else { + return -1; + } + // Check for the right prescaler + uint8_t index; + uint32_t period; + + for (index = 0; index < 8; index++) { + period = clock / prescaler_table[index] / freq; + if (period < (1 << 16)) { + break; + } + } + + #if defined(MCU_SAMD21) + + // Set up the clocks + if (tc == TC3) { + PM->APBCMASK.bit.TC3_ = 1; // Enable TC3 clock + GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK5 | GCLK_CLKCTRL_ID_TCC2_TC3; + #if TC_INST_NUM > 3 + } else { + if (tc == TC6) { + PM->APBCMASK.bit.TC6_ = 1; // Enable TC6 clock + } else if (tc == TC7) { + PM->APBCMASK.bit.TC7_ = 1; // Enable TC7 clock + } + // Select multiplexer generic clock source and enable. + GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK5 | GCLK_CLKCTRL_ID_TC6_TC7; + #endif // TC_INST_NUM > 3 + } + // Wait while it updates synchronously. + while (GCLK->STATUS.bit.SYNCBUSY) { + } + // Configure the timer. + tc->COUNT16.CTRLA.reg = TC_CTRLA_SWRST; + while (tc->COUNT16.CTRLA.bit.SWRST || tc->COUNT16.STATUS.bit.SYNCBUSY) { + } + tc->COUNT16.CTRLA.reg = TC_CTRLA_PRESCALER(index) | + 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) { + } + + #elif defined(MCU_SAMD51) + + int gclk_id = TC2_GCLK_ID; + // Enable MCLK + switch (tc_index) { + case 2: + MCLK->APBBMASK.bit.TC2_ = 1; // Enable TC2 clock + gclk_id = TC2_GCLK_ID; + break; + case 3: + MCLK->APBBMASK.bit.TC3_ = 1; // Enable TC3 clock + gclk_id = TC3_GCLK_ID; + break; + #if TC_INST_NUM > 4 + case 4: + MCLK->APBCMASK.bit.TC4_ = 1; // Enable TC4 clock + gclk_id = TC4_GCLK_ID; + break; + case 5: + MCLK->APBCMASK.bit.TC5_ = 1; // Enable TC5 clock + gclk_id = TC5_GCLK_ID; + break; + #if TC_INST_NUM > 6 + case 6: + MCLK->APBDMASK.bit.TC6_ = 1; // Enable TC6 clock + gclk_id = TC6_GCLK_ID; + break; + case 7: + MCLK->APBDMASK.bit.TC7_ = 1; // Enable TC7 clock + gclk_id = TC7_GCLK_ID; + break; + #endif // TC_INST_NUM > 6 + #endif // TC_INST_NUM > 4 + } + // Enable the 48Mhz clock. + GCLK->PCHCTRL[gclk_id].reg = GCLK_PCHCTRL_GEN_GCLK5 | GCLK_PCHCTRL_CHEN; + while (GCLK->PCHCTRL[gclk_id].bit.CHEN == 0) { + } + // Configure the timer. + tc->COUNT16.CTRLA.reg = TC_CTRLA_SWRST; + while (tc->COUNT16.SYNCBUSY.bit.SWRST) { + } + tc->COUNT16.CTRLA.reg = TC_CTRLA_PRESCALER(index) | + TC_CTRLA_MODE_COUNT16 | TC_CTRLA_RUNSTDBY | TC_CTRLA_PRESCSYNC_PRESC; + tc->COUNT16.WAVE.reg = TC_WAVE_WAVEGEN_MFRQ; + tc->COUNT16.CC[0].reg = period; + tc->COUNT16.CTRLA.bit.ENABLE = 1; + while (tc->COUNT16.SYNCBUSY.bit.ENABLE) { + } + + #endif // SAMD21 or SAMD51 + + return 0; +} + +void tc_deinit(void) { + memset((uint8_t *)instance_flag, 0, sizeof(instance_flag)); + // The tc instances used by the us counter have to be locked. + // That's TC4 and TC5 for SAMD21 with the list starting at TC3 + // and TC0 and TC1 for SAMD51, with the list starting at TC0 + #if defined(MCU_SAMD21) + instance_flag[1] = instance_flag[2] = true; + #elif defined(MCU_SAMD51) + instance_flag[0] = instance_flag[1] = true; + #endif +} + +#endif diff --git a/ports/samd/tc_manager.h b/ports/samd/tc_manager.h new file mode 100644 index 0000000000..04064f89e6 --- /dev/null +++ b/ports/samd/tc_manager.h @@ -0,0 +1,39 @@ +/* + * 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. + */ +#ifndef MICROPY_INCLUDED_SAMD_TCINSTANCE_H +#define MICROPY_INCLUDED_SAMD_TCINSTANCE_H + +#include "py/runtime.h" + + +int allocate_tc_instance(void); +void free_tc_instance(int tc_index); +int configure_tc(int tc_index, int freq, int event); +void tc_deinit(void); + +extern Tc *tc_instance_list[]; + +#endif // MICROPY_INCLUDED_SAMD_TCINSTANCE_H