From 43d33289df744f2e15a441f8882ff0c0995512bd Mon Sep 17 00:00:00 2001 From: robert-hh Date: Mon, 5 Sep 2022 11:24:03 +0200 Subject: [PATCH 01/13] samd/DMA: Add a DMA manager. Used for allocation of DMA channels. It will be needed for planned modules and methods like adc_timed(), dac_timed(), I2S. It includes management code for DMA IRQ handlers, similar to what was made for Sercom. Signed-off-by: robert-hh --- ports/samd/dma_manager.c | 131 +++++++++++++++++++++++++++++++++++++++ ports/samd/dma_manager.h | 38 ++++++++++++ ports/samd/samd_isr.c | 57 +++++++++++++++-- ports/samd/samd_soc.h | 1 + 4 files changed, 221 insertions(+), 6 deletions(-) create mode 100644 ports/samd/dma_manager.c create mode 100644 ports/samd/dma_manager.h diff --git a/ports/samd/dma_manager.c b/ports/samd/dma_manager.c new file mode 100644 index 0000000000..916b629a52 --- /dev/null +++ b/ports/samd/dma_manager.c @@ -0,0 +1,131 @@ +/* + * 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" + +// 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 +} diff --git a/ports/samd/dma_manager.h b/ports/samd/dma_manager.h new file mode 100644 index 0000000000..33c950ebd7 --- /dev/null +++ b/ports/samd/dma_manager.h @@ -0,0 +1,38 @@ +/* + * 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); + +extern volatile DmacDescriptor dma_desc[]; + +#endif // MICROPY_INCLUDED_SAMD_DMACHANNEL_H 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.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. From 9226188a46094a70dc55769def95855155acfaf4 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Mon, 5 Sep 2022 11:25:28 +0200 Subject: [PATCH 02/13] samd/TC_timer: Add a tc_manager function set. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These functions are use to allocate, free and configure a set of TC counter instances. The SAMxx MCU have between 3 to 5 (SAMD21) and 4 to 8 (SAMD51) TC instances. Two of them are used for the µs counter, the remaining 1 - 6 instances are administered here for use by various functions, like timed DMA transfers. Signed-off-by: robert-hh --- ports/samd/tc_manager.c | 178 ++++++++++++++++++++++++++++++++++++++++ ports/samd/tc_manager.h | 39 +++++++++ 2 files changed, 217 insertions(+) create mode 100644 ports/samd/tc_manager.c create mode 100644 ports/samd/tc_manager.h diff --git a/ports/samd/tc_manager.c b/ports/samd/tc_manager.c new file mode 100644 index 0000000000..10cad8060a --- /dev/null +++ b/ports/samd/tc_manager.c @@ -0,0 +1,178 @@ +/* + * 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" + +// 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) { + 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; + + 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 +} diff --git a/ports/samd/tc_manager.h b/ports/samd/tc_manager.h new file mode 100644 index 0000000000..d90959fa49 --- /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); +void tc_deinit(void); + +extern Tc *tc_instance_list[]; + +#endif // MICROPY_INCLUDED_SAMD_TCINSTANCE_H From 3b08312c162308a8fa46a29e254b36d2849848e5 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Mon, 5 Sep 2022 11:29:08 +0200 Subject: [PATCH 03/13] samd/DAC: Add the dac.write_timed(). Call: dac.write_timed(data, freq [, count]) dac.deinit() Working range for dac_timed(): SAMD21: 1 Hz - 100 kHz (1 MHz clock, 10 bit) SAMD51: 1 Hz - ~500 kHz (8 MHz clock, 12 bit) The buffer has to be a byte array or a halfword array, and the data is sent once. The default for count is 1. If set to a value > 0, the data will be transmitted count times. If set to 0 or < 0, the date will be transmitted until deliberately stopped. The playback can be stopped with dac.deinit(). dac.deinit() just releases the timer and DMA channel needed by dac_timed(). The DAC object itself does not have to be released. Signed-off-by: robert-hh --- ports/samd/Makefile | 2 + ports/samd/dma_manager.h | 1 + ports/samd/machine_dac.c | 261 ++++++++++++++++++++++++++++++--------- ports/samd/main.c | 4 + ports/samd/samd_soc.c | 1 - 5 files changed, 213 insertions(+), 56 deletions(-) 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.h b/ports/samd/dma_manager.h index 33c950ebd7..3dea43e41e 100644 --- a/ports/samd/dma_manager.h +++ b/ports/samd/dma_manager.h @@ -32,6 +32,7 @@ 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[]; diff --git a/ports/samd/machine_dac.c b/ports/samd/machine_dac.c index c611f95e65..0a9f71c6e7 100644 --- a/ports/samd/machine_dac.c +++ b/ports/samd/machine_dac.c @@ -36,32 +36,39 @@ #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; + bool initialized; mp_hal_pin_obj_t gpio_id; uint8_t vref; + int8_t dma_channel; + int8_t tc_index; + uint32_t count; } 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}, - #endif -}; Dac *const dac_bases[] = DAC_INSTS; +STATIC void dac_init(dac_obj_t *self, Dac *dac); #if defined(MCU_SAMD21) +STATIC dac_obj_t dac_obj[] = { + {{&machine_dac_type}, 0, PIN_PA02}, +}; + #define MAX_DAC_VALUE (1023) #define DEFAULT_DAC_VREF (1) #define MAX_DAC_VREF (2) #elif defined(MCU_SAMD51) +STATIC dac_obj_t dac_obj[] = { + {{&machine_dac_type}, 0, PIN_PA02}, + {{&machine_dac_type}, 1, 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 @@ -72,9 +79,37 @@ static uint8_t dac_vref_table[] = { #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 + + +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; + } + + #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; + } + #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) { @@ -103,68 +138,76 @@ static mp_obj_t dac_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_ } 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) { - } - #endif - + dac_init(self, dac); // 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_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { +STATIC void dac_init(dac_obj_t *self, Dac *dac) { + // Init DAC + if (self->initialized == false) { + #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 + 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) { + } + + #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); } 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; 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")); } + // Re-init, if required + dac_init(self, dac); #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 +215,116 @@ static mp_obj_t dac_write(mp_obj_t self_in, mp_obj_t value_in) { } MP_DEFINE_CONST_FUN_OBJ_2(dac_write_obj, dac_write); -static const mp_rom_map_elem_t dac_locals_dict_table[] = { +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; + // Re-init, if required + dac_init(self, dac); + + 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->dma_channel == -1) { + self->dma_channel = allocate_dma_channel(); + dma_init(); + dma_register_irq(self->dma_channel, dac_irq_handler); + } + if (self->tc_index == -1) { + self->tc_index = allocate_tc_instance(); + } + // Configure TC; no need to check the return value + configure_tc(self->tc_index, freq); + // 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 mp_obj_t dac_deinit(mp_obj_t self_in) { + dac_obj_t *self = self_in; + self->initialized = false; + // Reset the DAC to lower the current consumption as SAMD21 + dac_bases[0]->CTRLA.bit.SWRST = 1; + 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; + } + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_1(dac_deinit_obj, dac_deinit); + +STATIC const mp_rom_map_elem_t dac_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&dac_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&dac_write_obj) }, + { MP_ROM_QSTR(MP_QSTR_write_timed), MP_ROM_PTR(&dac_write_timed_obj) }, }; 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..a22a2520f2 100644 --- a/ports/samd/main.c +++ b/ports/samd/main.c @@ -35,6 +35,8 @@ #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); @@ -87,6 +89,8 @@ void samd_main(void) { soft_reset_exit: mp_printf(MP_PYTHON_PRINTER, "MPY: soft reboot\n"); + dma_deinit(); + tc_deinit(); #if MICROPY_PY_MACHINE_ADC adc_deinit_all(); #endif 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; From 94a1988698016980dfd082b8c346125a8a8fec97 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Tue, 6 Sep 2022 16:53:45 +0200 Subject: [PATCH 04/13] samd/ADC: Add a adc.read_timed() method. Call: adc.read_timed(buffer, freq) buffer must be preallocated. The size determines the number of 16 bit words to be read. The numeric range of the results is that of the raw ADC. The call returns immediately, and the data transfer is done by DMA. The caller must wait sufficiently long until the data is sampled and can be noticed by a callback. No internal checks are made for a too-high freq value. Read speeds depends on Average and bit length setting: SAMD21: Max. 350kS/s (8 bit, Average 1) SAMD51: Max. 1 MS/s (8 bit, Average 1) Signed-off-by: robert-hh --- ports/samd/dma_manager.c | 2 +- ports/samd/machine_adc.c | 147 ++++++++++++++++++++++++++++++++++++--- ports/samd/machine_dac.c | 20 +++--- ports/samd/tc_manager.c | 5 +- ports/samd/tc_manager.h | 2 +- 5 files changed, 154 insertions(+), 22 deletions(-) diff --git a/ports/samd/dma_manager.c b/ports/samd/dma_manager.c index 916b629a52..868606e66b 100644 --- a/ports/samd/dma_manager.c +++ b/ports/samd/dma_manager.c @@ -52,7 +52,7 @@ static volatile DmacDescriptor dma_write_back[MICROPY_HW_DMA_CHANNELS] __attribu // List of channel flags: true: channel used, false: channel available static bool channel_list[MICROPY_HW_DMA_CHANNELS]; -STATIC bool dma_initialized = false; +static bool dma_initialized = false; // allocate_channel(): retrieve an available channel. Return the number or -1 int allocate_dma_channel(void) { diff --git a/ports/samd/machine_adc.c b/ports/samd/machine_adc.c index 665af6f6f2..afd9c5d29a 100644 --- a/ports/samd/machine_adc.c +++ b/ports/samd/machine_adc.c @@ -31,6 +31,10 @@ #include "py/mphal.h" #include "sam.h" #include "pin_af.h" +#include "modmachine.h" +#include "samd_soc.h" +#include "dma_manager.h" +#include "tc_manager.h" typedef struct _machine_adc_obj_t { mp_obj_base_t base; @@ -39,6 +43,8 @@ typedef struct _machine_adc_obj_t { uint8_t avg; uint8_t bits; uint8_t vref; + int8_t dma_channel; + int8_t tc_index; } machine_adc_obj_t; #define DEFAULT_ADC_BITS 12 @@ -85,6 +91,20 @@ static uint8_t resolution[] = { extern mp_int_t log2i(mp_int_t num); +// Active just for SAMD21, stops the freerun mode +// For SAMD51, just the INT flag is reset. +void adc_irq_handler(int dma_channel) { + + #if defined(MCU_SAMD21) + DMAC->CHID.reg = dma_channel; + DMAC->CHINTFLAG.reg = DMAC_CHINTFLAG_TCMPL; + ADC->EVCTRL.bit.STARTEI = 0; + + #elif defined(MCU_SAMD51) + DMAC->Channel[dma_channel].CHINTFLAG.reg = DMAC_CHINTFLAG_TCMPL; + #endif +} + static void mp_machine_adc_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { (void)kind; machine_adc_obj_t *self = MP_OBJ_TO_PTR(self_in); @@ -116,8 +136,9 @@ static mp_obj_t mp_machine_adc_make_new(const mp_obj_type_t *type, size_t n_args self->id = id; self->adc_config = adc_config; self->bits = DEFAULT_ADC_BITS; + uint16_t bits = args[ARG_bits].u_int; - if (bits >= 8 && bits <= 12) { + if (8 <= bits && bits <= 12) { self->bits = bits; } uint32_t avg = log2i(args[ARG_average].u_int); @@ -147,6 +168,14 @@ static mp_int_t mp_machine_adc_read_u16(machine_adc_obj_t *self) { adc->INPUTCTRL.reg = ADC_INPUTCTRL_MUXNEG_GND | self->adc_config.channel; // set resolution. Scale 8-16 to 0 - 4 for table access. adc->CTRLB.bit.RESSEL = resolution[(self->bits - 8) / 2]; + + #if defined(MCU_SAMD21) + // Stop the ADC sampling by timer + adc->EVCTRL.bit.STARTEI = 0; + #elif defined(MCU_SAMD51) + // Do not restart ADC after data has bee read + adc->DSEQCTRL.reg = 0; + #endif // Measure input voltage adc->SWTRIG.bit.START = 1; while (adc->INTFLAG.bit.RESRDY == 0) { @@ -155,9 +184,107 @@ static mp_int_t mp_machine_adc_read_u16(machine_adc_obj_t *self) { return adc->RESULT.reg * (65536 / (1 << self->bits)); } +static void machine_adc_read_timed(mp_obj_t self_in, mp_obj_t values, mp_obj_t freq_in) { + machine_adc_obj_t *self = self_in; + Adc *adc = adc_bases[self->adc_config.device]; + mp_buffer_info_t src; + mp_get_buffer_raise(values, &src, MP_BUFFER_READ); + if (src.len >= 2) { + int freq = mp_obj_get_int(freq_in); + if (self->dma_channel == -1) { + self->dma_channel = allocate_dma_channel(); + dma_init(); + } + if (self->tc_index == -1) { + self->tc_index = allocate_tc_instance(); + } + // Set Input channel and resolution + // Select the pin as positive input and gnd as negative input reference, non-diff mode by default + adc->INPUTCTRL.reg = ADC_INPUTCTRL_MUXNEG_GND | self->adc_config.channel; + // set resolution. Scale 8-16 to 0 - 4 for table access. + adc->CTRLB.bit.RESSEL = resolution[(self->bits - 8) / 2]; + + // Configure DMA for halfword output to the DAC + #if defined(MCU_SAMD21) + // dma irq just for SAMD21 to stop the timer based acquisition + dma_register_irq(self->dma_channel, adc_irq_handler); + configure_tc(self->tc_index, freq, TC_EVCTRL_OVFEO); + // Enable APBC clock + PM->APBCMASK.reg |= PM_APBCMASK_EVSYS; + // Set up the EVSYS channel + EVSYS->CTRL.bit.SWRST = 1; + EVSYS->USER.reg = EVSYS_USER_CHANNEL(ADC_EVSYS_CHANNEL + 1) | + EVSYS_USER_USER(EVSYS_ID_USER_ADC_START); + EVSYS->CHANNEL.reg = EVSYS_CHANNEL_CHANNEL(ADC_EVSYS_CHANNEL) | + EVSYS_CHANNEL_EVGEN(EVSYS_ID_GEN_TC3_OVF + 3 * self->tc_index) | + EVSYS_CHANNEL_PATH_ASYNCHRONOUS; + + dma_desc[self->dma_channel].BTCTRL.reg = + DMAC_BTCTRL_VALID | DMAC_BTCTRL_BLOCKACT_NOACT | + DMAC_BTCTRL_BEATSIZE_HWORD | DMAC_BTCTRL_DSTINC | DMAC_BTCTRL_STEPSEL | + DMAC_BTCTRL_STEPSIZE(DMAC_BTCTRL_STEPSIZE_X1_Val); + dma_desc[self->dma_channel].BTCNT.reg = src.len / 2; + dma_desc[self->dma_channel].SRCADDR.reg = (uint32_t)(&adc->RESULT.reg); + dma_desc[self->dma_channel].DSTADDR.reg = (uint32_t)(src.buf) + src.len; + dma_desc[self->dma_channel].DESCADDR.reg = 0; // ONE_SHOT + DMAC->CHID.reg = self->dma_channel; + DMAC->CHCTRLA.reg = 0; + while (DMAC->CHCTRLA.bit.ENABLE) { + } + DMAC->CHCTRLB.reg = + DMAC_CHCTRLB_LVL(0) | + DMAC_CHCTRLB_TRIGACT_BEAT | + DMAC_CHCTRLB_TRIGSRC(ADC_DMAC_ID_RESRDY); + + DMAC->CHINTENSET.reg = DMAC_CHINTFLAG_TCMPL; + DMAC->CHCTRLA.reg |= DMAC_CHCTRLA_ENABLE; + + NVIC_EnableIRQ(DMAC_IRQn); + adc->EVCTRL.bit.STARTEI = 1; + + #elif defined(MCU_SAMD51) + configure_tc(self->tc_index, freq, 0); + + // Restart ADC after data has bee read + adc->DSEQCTRL.reg = ADC_DSEQCTRL_AUTOSTART; + // Start the first sampling to ensure we get a proper first value. + adc->SWTRIG.bit.START = 1; + while (adc->INTFLAG.bit.RESRDY == 0) { + } + + dma_desc[self->dma_channel].BTCTRL.reg = + DMAC_BTCTRL_VALID | DMAC_BTCTRL_BLOCKACT_NOACT | + DMAC_BTCTRL_BEATSIZE_HWORD | DMAC_BTCTRL_DSTINC | DMAC_BTCTRL_STEPSEL | + DMAC_BTCTRL_STEPSIZE(DMAC_BTCTRL_STEPSIZE_X1_Val); + dma_desc[self->dma_channel].BTCNT.reg = src.len / 2; + dma_desc[self->dma_channel].SRCADDR.reg = (uint32_t)(&adc->RESULT.reg); + dma_desc[self->dma_channel].DSTADDR.reg = (uint32_t)(src.buf) + src.len; + dma_desc[self->dma_channel].DESCADDR.reg = 0; // ONE_SHOT + + DMAC->Channel[self->dma_channel].CHCTRLA.reg = + DMAC_CHCTRLA_BURSTLEN(DMAC_CHCTRLA_BURSTLEN_SINGLE_Val) | + DMAC_CHCTRLA_TRIGACT(DMAC_CHCTRLA_TRIGACT_BURST_Val) | + DMAC_CHCTRLA_TRIGSRC(TC0_DMAC_ID_OVF + 3 * self->tc_index); + DMAC->Channel[self->dma_channel].CHCTRLA.reg |= DMAC_CHCTRLA_ENABLE; + + #endif // defined SAMD21 or SAMD51 + + } + return mp_const_none; +} + // deinit() : release the ADC channel static void mp_machine_adc_deinit(machine_adc_obj_t *self) { busy_flags &= ~((1 << (self->adc_config.device * 16 + self->adc_config.channel))); + if (self->dma_channel >= 0) { + dac_stop_dma(self->dma_channel, true); + free_dma_channel(self->dma_channel); + self->dma_channel = -1; + } + if (self->tc_index >= 0) { + free_tc_instance(self->tc_index); + self->tc_index = -1; + } } void adc_deinit_all(void) { @@ -175,9 +302,9 @@ static void adc_init(machine_adc_obj_t *self) { #if defined(MCU_SAMD21) // Configuration SAMD21 - // Enable APBD clocks and PCHCTRL clocks; GCLK2 at 48 MHz + // Enable APBD clocks and PCHCTRL clocks; GCLK5 at 48 MHz PM->APBCMASK.reg |= PM_APBCMASK_ADC; - GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK2 | GCLK_CLKCTRL_ID_ADC; + GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK5 | GCLK_CLKCTRL_ID_ADC; while (GCLK->STATUS.bit.SYNCBUSY) { } // Reset ADC registers @@ -190,7 +317,7 @@ static void adc_init(machine_adc_obj_t *self) { linearity |= ((*((uint32_t *)ADC_FUSES_LINEARITY_1_ADDR) & ADC_FUSES_LINEARITY_1_Msk) >> ADC_FUSES_LINEARITY_1_Pos) << 5; /* Write the calibration data. */ ADC->CALIB.reg = ADC_CALIB_BIAS_CAL(bias) | ADC_CALIB_LINEARITY_CAL(linearity); - // Divide 48MHz clock by 32 to obtain 1.5 MHz clock to adc + // Divide a 48MHz clock by 32 to obtain 1.5 MHz clock to adc adc->CTRLB.reg = ADC_CTRLB_PRESCALER_DIV32; // Select external AREFA as reference voltage. adc->REFCTRL.reg = adc_vref_table[self->vref]; @@ -203,12 +330,12 @@ static void adc_init(machine_adc_obj_t *self) { #elif defined(MCU_SAMD51) // Configuration SAMD51 - // Enable APBD clocks and PCHCTRL clocks; GCLK2 at 48 MHz + // Enable APBD clocks and PCHCTRL clocks; GCLK5 at 48 MHz if (self->adc_config.device == 0) { - GCLK->PCHCTRL[ADC0_GCLK_ID].reg = GCLK_PCHCTRL_GEN_GCLK2 | GCLK_PCHCTRL_CHEN; + GCLK->PCHCTRL[ADC0_GCLK_ID].reg = GCLK_PCHCTRL_GEN_GCLK5 | GCLK_PCHCTRL_CHEN; MCLK->APBDMASK.bit.ADC0_ = 1; } else { - GCLK->PCHCTRL[ADC1_GCLK_ID].reg = GCLK_PCHCTRL_GEN_GCLK2 | GCLK_PCHCTRL_CHEN; + GCLK->PCHCTRL[ADC1_GCLK_ID].reg = GCLK_PCHCTRL_GEN_GCLK5 | GCLK_PCHCTRL_CHEN; MCLK->APBDMASK.bit.ADC1_ = 1; } // Reset ADC registers @@ -230,8 +357,10 @@ static void adc_init(machine_adc_obj_t *self) { } /* Write the calibration data. */ adc->CALIB.reg = ADC_CALIB_BIASCOMP(biascomp) | ADC_CALIB_BIASR2R(biasr2r) | ADC_CALIB_BIASREFBUF(biasrefbuf); - // Divide 48MHz clock by 32 to obtain 1.5 MHz clock to adc - adc->CTRLA.reg = ADC_CTRLA_PRESCALER_DIV32; + // Divide 48MHz clock by 4 to obtain 12 MHz clock to adc + adc->CTRLA.reg = ADC_CTRLA_PRESCALER_DIV4; + // Enable the offset compensation + adc->SAMPCTRL.reg = ADC_SAMPCTRL_OFFCOMP; // Set the reference voltage. Default: external AREFA. adc->REFCTRL.reg = adc_vref_table[self->vref]; // Average: Accumulate samples and scale them down accordingly diff --git a/ports/samd/machine_dac.c b/ports/samd/machine_dac.c index 0a9f71c6e7..fe8e0bc1a3 100644 --- a/ports/samd/machine_dac.c +++ b/ports/samd/machine_dac.c @@ -51,11 +51,11 @@ typedef struct _dac_obj_t { uint32_t count; } dac_obj_t; Dac *const dac_bases[] = DAC_INSTS; -STATIC void dac_init(dac_obj_t *self, Dac *dac); +static void dac_init(dac_obj_t *self, Dac *dac); #if defined(MCU_SAMD21) -STATIC dac_obj_t dac_obj[] = { +static dac_obj_t dac_obj[] = { {{&machine_dac_type}, 0, PIN_PA02}, }; @@ -65,7 +65,7 @@ STATIC dac_obj_t dac_obj[] = { #elif defined(MCU_SAMD51) -STATIC dac_obj_t dac_obj[] = { +static dac_obj_t dac_obj[] = { {{&machine_dac_type}, 0, PIN_PA02}, {{&machine_dac_type}, 1, PIN_PA05}, }; @@ -145,7 +145,7 @@ static mp_obj_t dac_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_ return MP_OBJ_FROM_PTR(self); } -STATIC void dac_init(dac_obj_t *self, Dac *dac) { +static void dac_init(dac_obj_t *self, Dac *dac) { // Init DAC if (self->initialized == false) { #if defined(MCU_SAMD21) @@ -190,7 +190,7 @@ STATIC void dac_init(dac_obj_t *self, Dac *dac) { self->initialized = true; } -STATIC void dac_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { +static void dac_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { dac_obj_t *self = self_in; mp_printf(print, "DAC(%u, Pin=%q, vref=%d)", self->id, pin_find_by_id(self->gpio_id)->name, self->vref); } @@ -215,7 +215,7 @@ static mp_obj_t dac_write(mp_obj_t self_in, mp_obj_t value_in) { } MP_DEFINE_CONST_FUN_OBJ_2(dac_write_obj, dac_write); -STATIC mp_obj_t dac_write_timed(size_t n_args, const mp_obj_t *args) { +static mp_obj_t dac_write_timed(size_t n_args, const mp_obj_t *args) { Dac *dac = dac_bases[0]; // Just one DAC used dac_obj_t *self = args[0]; mp_buffer_info_t src; @@ -239,7 +239,7 @@ STATIC mp_obj_t dac_write_timed(size_t n_args, const mp_obj_t *args) { self->tc_index = allocate_tc_instance(); } // Configure TC; no need to check the return value - configure_tc(self->tc_index, freq); + configure_tc(self->tc_index, freq, 0); // Configure DMA for halfword output to the DAC #if defined(MCU_SAMD21) @@ -301,9 +301,9 @@ STATIC mp_obj_t dac_write_timed(size_t n_args, const mp_obj_t *args) { } return mp_const_none; } -STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(dac_write_timed_obj, 3, 4, dac_write_timed); +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(dac_write_timed_obj, 3, 4, dac_write_timed); -STATIC mp_obj_t dac_deinit(mp_obj_t self_in) { +static mp_obj_t dac_deinit(mp_obj_t self_in) { dac_obj_t *self = self_in; self->initialized = false; // Reset the DAC to lower the current consumption as SAMD21 @@ -321,7 +321,7 @@ STATIC mp_obj_t dac_deinit(mp_obj_t self_in) { } MP_DEFINE_CONST_FUN_OBJ_1(dac_deinit_obj, dac_deinit); -STATIC const mp_rom_map_elem_t dac_locals_dict_table[] = { +static const mp_rom_map_elem_t dac_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&dac_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&dac_write_obj) }, { MP_ROM_QSTR(MP_QSTR_write_timed), MP_ROM_PTR(&dac_write_timed_obj) }, diff --git a/ports/samd/tc_manager.c b/ports/samd/tc_manager.c index 10cad8060a..fb0c3b77ff 100644 --- a/ports/samd/tc_manager.c +++ b/ports/samd/tc_manager.c @@ -57,7 +57,7 @@ void free_tc_instance(int tc_index) { } } -int configure_tc(int tc_index, int freq) { +int configure_tc(int tc_index, int freq, int event) { uint32_t clock = DFLL48M_FREQ; // Use the fixed 48M clock Tc *tc; @@ -105,6 +105,9 @@ int configure_tc(int tc_index, int freq) { TC_CTRLA_MODE_COUNT16 | TC_CTRLA_RUNSTDBY | TC_CTRLA_WAVEGEN_MFRQ; tc->COUNT16.CC[0].reg = period; + if (event) { + tc->COUNT16.EVCTRL.reg = event; + } tc->COUNT16.CTRLA.bit.ENABLE = 1; while (tc->COUNT16.STATUS.bit.SYNCBUSY) { diff --git a/ports/samd/tc_manager.h b/ports/samd/tc_manager.h index d90959fa49..04064f89e6 100644 --- a/ports/samd/tc_manager.h +++ b/ports/samd/tc_manager.h @@ -31,7 +31,7 @@ int allocate_tc_instance(void); void free_tc_instance(int tc_index); -int configure_tc(int tc_index, int freq); +int configure_tc(int tc_index, int freq, int event); void tc_deinit(void); extern Tc *tc_instance_list[]; From f6c41257e9f4b66dbfef02cb46b8cec03fae0032 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Tue, 13 Sep 2022 18:50:42 +0200 Subject: [PATCH 05/13] samd/DAC: Add a callback keyword option to machine.DAC(). The callback is called when a dac_timed() sequence finishes. It will be reset with callback=None or omitting the callback option in the constructor. Side change: Set the clock freq. to 48Mhz. Signed-off-by: robert-hh --- ports/samd/machine_dac.c | 46 ++++++++++++++++++++++++++++++++-------- ports/samd/main.c | 4 ++++ 2 files changed, 41 insertions(+), 9 deletions(-) diff --git a/ports/samd/machine_dac.c b/ports/samd/machine_dac.c index fe8e0bc1a3..d350205459 100644 --- a/ports/samd/machine_dac.c +++ b/ports/samd/machine_dac.c @@ -49,14 +49,16 @@ typedef struct _dac_obj_t { int8_t dma_channel; int8_t tc_index; uint32_t count; + mp_obj_t callback; } dac_obj_t; Dac *const dac_bases[] = DAC_INSTS; static void dac_init(dac_obj_t *self, Dac *dac); +static mp_obj_t dac_deinit(mp_obj_t self_in); #if defined(MCU_SAMD21) static dac_obj_t dac_obj[] = { - {{&machine_dac_type}, 0, PIN_PA02}, + {{&machine_dac_type}, 0, 0, PIN_PA02}, }; #define MAX_DAC_VALUE (1023) @@ -66,8 +68,8 @@ static dac_obj_t dac_obj[] = { #elif defined(MCU_SAMD51) static dac_obj_t dac_obj[] = { - {{&machine_dac_type}, 0, PIN_PA02}, - {{&machine_dac_type}, 1, PIN_PA05}, + {{&machine_dac_type}, 0, 0, PIN_PA02, 2, -1, -1, 1, NULL}, + {{&machine_dac_type}, 1, 0, PIN_PA05, 2, -1, -1, 1, NULL}, }; // 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 @@ -82,7 +84,6 @@ static uint8_t dac_vref_table[] = { #endif // defined SAMD21 or SAMD51 - void dac_irq_handler(int dma_channel) { dac_obj_t *self; @@ -94,6 +95,10 @@ void dac_irq_handler(int dma_channel) { self->count -= 1; dma_desc[self->dma_channel].BTCTRL.reg |= DMAC_BTCTRL_VALID; DMAC->CHCTRLA.reg |= DMAC_CHCTRLA_ENABLE; + } else { + if (self->callback != MP_OBJ_NULL) { + mp_sched_schedule(self->callback, self); + } } #elif defined(MCU_SAMD51) @@ -107,6 +112,10 @@ void dac_irq_handler(int dma_channel) { self->count -= 1; dma_desc[self->dma_channel].BTCTRL.reg |= DMAC_BTCTRL_VALID; DMAC->Channel[self->dma_channel].CHCTRLA.reg |= DMAC_CHCTRLA_ENABLE; + } else { + if (self->callback != MP_OBJ_NULL) { + mp_sched_schedule(self->callback, self); + } } #endif } @@ -114,10 +123,11 @@ void dac_irq_handler(int dma_channel) { 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} }, + { MP_QSTR_callback, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, }; // Parse the arguments. @@ -137,6 +147,15 @@ static mp_obj_t dac_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_ self->vref = vref; } + 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->initialized = false; + Dac *dac = dac_bases[0]; // Just one DAC dac_init(self, dac); // Set the port as given in self->gpio_id as DAC @@ -151,9 +170,9 @@ static void dac_init(dac_obj_t *self, Dac *dac) { #if defined(MCU_SAMD21) // Configuration SAMD21 - // Enable APBC clocks and PCHCTRL clocks; GCLK3 at 1 MHz + // 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_GCLK3 | GCLK_CLKCTRL_ID_DAC; + GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK5 | GCLK_CLKCTRL_ID_DAC; while (GCLK->STATUS.bit.SYNCBUSY) { } // Reset DAC registers @@ -169,9 +188,9 @@ static void dac_init(dac_obj_t *self, Dac *dac) { #elif defined(MCU_SAMD51) // Configuration SAMD51 - // Enable APBD clocks and PCHCTRL clocks; GCLK3 at 8 MHz + // 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_GCLK3 | GCLK_PCHCTRL_CHEN; + GCLK->PCHCTRL[DAC_GCLK_ID].reg = GCLK_PCHCTRL_GEN_GCLK5 | GCLK_PCHCTRL_CHEN; // Reset DAC registers dac->CTRLA.bit.SWRST = 1; @@ -317,10 +336,19 @@ static mp_obj_t dac_deinit(mp_obj_t self_in) { free_tc_instance(self->tc_index); self->tc_index = -1; } + self->callback = MP_OBJ_NULL; return mp_const_none; } MP_DEFINE_CONST_FUN_OBJ_1(dac_deinit_obj, dac_deinit); +// Clear the DMA channel entry in the DAC object. +void dac_deinit_channel(void) { + dac_obj[0].dma_channel = -1; + #if defined(MCU_SAMD51) + dac_obj[1].dma_channel = -1; + #endif +} + static const mp_rom_map_elem_t dac_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&dac_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&dac_write_obj) }, diff --git a/ports/samd/main.c b/ports/samd/main.c index a22a2520f2..802838784d 100644 --- a/ports/samd/main.c +++ b/ports/samd/main.c @@ -40,6 +40,7 @@ extern uint8_t _sstack, _estack, _sheap, _eheap; extern void adc_deinit_all(void); +extern void dac_deinit_channel(void); extern void pin_irq_deinit_all(void); extern void pwm_deinit_all(void); extern void sercom_deinit_all(void); @@ -94,6 +95,9 @@ void samd_main(void) { #if MICROPY_PY_MACHINE_ADC adc_deinit_all(); #endif + #if MICROPY_PY_MACHINE_DAC + dac_deinit_channel(); + #endif pin_irq_deinit_all(); #if MICROPY_PY_MACHINE_PWM pwm_deinit_all(); From 7197c8adc026312686f76cc3c0475dea90341ad3 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Tue, 13 Sep 2022 21:26:54 +0200 Subject: [PATCH 06/13] samd/ADC: Add a callback keyword option to machine.ADC(). Enabling a callback that will be called when a adc.read_timed_into() run is finished. That's especially useful with slow sampling rates and/or many samples, avoiding to guess the sampling time. Raise an error is adc.read_u16() is called while a read_timed_into() is active. Other ADC changes: - SAMD51: use ADC1 if both ADC1 and ADC0 are available at a Pin. Signed-off-by: robert-hh --- ports/samd/machine_adc.c | 118 ++++++++++++++++++++++++++++++++------- 1 file changed, 99 insertions(+), 19 deletions(-) diff --git a/ports/samd/machine_adc.c b/ports/samd/machine_adc.c index afd9c5d29a..b64d53a43f 100644 --- a/ports/samd/machine_adc.c +++ b/ports/samd/machine_adc.c @@ -28,6 +28,13 @@ // This file is never compiled standalone, it's included directly from // extmod/machine_adc.c via MICROPY_PY_MACHINE_ADC_INCLUDEFILE. +#if MICROPY_PY_MACHINE_ADC + +#include +#include "py/obj.h" +#include "py/runtime.h" +#include "py/mperrno.h" + #include "py/mphal.h" #include "sam.h" #include "pin_af.h" @@ -51,6 +58,7 @@ typedef struct _machine_adc_obj_t { #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 @@ -60,9 +68,19 @@ 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; + bool busy; + mp_obj_t callback; + mp_obj_t self; +} device_mgmt_t; + +device_mgmt_t device_mgmt[ADC_INST_NUM]; + #elif defined(MCU_SAMD51) static uint8_t adc_vref_table[] = { @@ -75,6 +93,20 @@ 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] = { + { 0, 0, -1, MP_OBJ_NULL, MP_OBJ_NULL}, + { 0, 0, -1, MP_OBJ_NULL, MP_OBJ_NULL} +}; #endif // defined(MCU_SAMD21) @@ -82,12 +114,12 @@ 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); @@ -97,11 +129,27 @@ void adc_irq_handler(int dma_channel) { #if defined(MCU_SAMD21) DMAC->CHID.reg = dma_channel; - DMAC->CHINTFLAG.reg = DMAC_CHINTFLAG_TCMPL; + 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->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 } @@ -114,13 +162,16 @@ static void mp_machine_adc_print(const mp_print_t *print, mp_obj_t self_in, mp_p self->adc_config.channel, self->bits, 1 << self->avg, self->vref); } -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 }; +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, 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} }, + { MP_QSTR_callback, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, }; // Parse the arguments. @@ -129,7 +180,7 @@ 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); @@ -145,13 +196,20 @@ static mp_obj_t mp_machine_adc_make_new(const mp_obj_type_t *type, size_t n_args 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; } + 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; + } // 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)); + self->dma_channel = -1; + self->tc_index = -1; adc_init(self); @@ -163,6 +221,10 @@ 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 (device_mgmt[self->adc_config.device].busy != 0) { + mp_raise_OSError(MP_EBUSY); + } + // 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; @@ -194,6 +256,7 @@ static void machine_adc_read_timed(mp_obj_t self_in, mp_obj_t values, mp_obj_t f if (self->dma_channel == -1) { self->dma_channel = allocate_dma_channel(); dma_init(); + dma_register_irq(self->dma_channel, adc_irq_handler); } if (self->tc_index == -1) { self->tc_index = allocate_tc_instance(); @@ -206,8 +269,6 @@ static void machine_adc_read_timed(mp_obj_t self_in, mp_obj_t values, mp_obj_t f // Configure DMA for halfword output to the DAC #if defined(MCU_SAMD21) - // dma irq just for SAMD21 to stop the timer based acquisition - dma_register_irq(self->dma_channel, adc_irq_handler); configure_tc(self->tc_index, freq, TC_EVCTRL_OVFEO); // Enable APBC clock PM->APBCMASK.reg |= PM_APBCMASK_EVSYS; @@ -241,9 +302,11 @@ static void machine_adc_read_timed(mp_obj_t self_in, mp_obj_t values, mp_obj_t f 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; @@ -265,8 +328,16 @@ static void machine_adc_read_timed(mp_obj_t self_in, mp_obj_t values, mp_obj_t f 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 } @@ -277,6 +348,11 @@ static void machine_adc_read_timed(mp_obj_t self_in, mp_obj_t values, mp_obj_t f static void mp_machine_adc_deinit(machine_adc_obj_t *self) { busy_flags &= ~((1 << (self->adc_config.device * 16 + self->adc_config.channel))); if (self->dma_channel >= 0) { + #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; + } + #endif dac_stop_dma(self->dma_channel, true); free_dma_channel(self->dma_channel); self->dma_channel = -1; @@ -288,17 +364,21 @@ static void mp_machine_adc_deinit(machine_adc_obj_t *self) { } void adc_deinit_all(void) { - busy_flags = 0; - init_flags[0] = 0; - init_flags[1] = 0; + 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 } 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 @@ -320,7 +400,7 @@ static void adc_init(machine_adc_obj_t *self) { // 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]; + adc->REFCTRL.reg = 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 From 53c640c0ce03b7620b2c04ac9f04fb3fc8db0906 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Fri, 14 Oct 2022 12:18:28 +0200 Subject: [PATCH 07/13] samd/ADC_DAC: Add adc.busy() and dac.busy() methods. These return True, while a timed action is ongoing. Side change: Reorder some code in machine_dac.c and do not reset DAC twice. Signed-off-by: robert-hh --- ports/samd/machine_adc.c | 7 +++++ ports/samd/machine_dac.c | 58 ++++++++++++++++++++++++++++++---------- 2 files changed, 51 insertions(+), 14 deletions(-) diff --git a/ports/samd/machine_adc.c b/ports/samd/machine_adc.c index b64d53a43f..854260927b 100644 --- a/ports/samd/machine_adc.c +++ b/ports/samd/machine_adc.c @@ -351,6 +351,7 @@ static void mp_machine_adc_deinit(machine_adc_obj_t *self) { #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); @@ -363,6 +364,12 @@ static void mp_machine_adc_deinit(machine_adc_obj_t *self) { } } +// busy() : Report, if the ADC device is busy +static mp_int_t machine_adc_busy(mp_obj_t self_in) { + machine_adc_obj_t *self = MP_OBJ_TO_PTR(self_in); + return device_mgmt[self->adc_config.device].busy ? true : false; +} + void adc_deinit_all(void) { ch_busy_flags = 0; device_mgmt[0].init = 0; diff --git a/ports/samd/machine_dac.c b/ports/samd/machine_dac.c index d350205459..a8fa4a0e8c 100644 --- a/ports/samd/machine_dac.c +++ b/ports/samd/machine_dac.c @@ -31,6 +31,8 @@ #include #include "py/obj.h" +#include "py/runtime.h" +#include "py/mperrno.h" #include "py/mphal.h" #include "sam.h" @@ -48,6 +50,7 @@ typedef struct _dac_obj_t { uint8_t vref; int8_t dma_channel; int8_t tc_index; + bool busy; uint32_t count; mp_obj_t callback; } dac_obj_t; @@ -57,19 +60,23 @@ static mp_obj_t dac_deinit(mp_obj_t self_in); #if defined(MCU_SAMD21) -static dac_obj_t dac_obj[] = { - {{&machine_dac_type}, 0, 0, PIN_PA02}, -}; - #define MAX_DAC_VALUE (1023) #define DEFAULT_DAC_VREF (1) #define MAX_DAC_VREF (2) +static dac_obj_t dac_obj[] = { + {{&machine_dac_type}, 0, 0, PIN_PA02, DEFAULT_DAC_VREF, -1, -1, false, 1, NULL}, +}; + #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, PIN_PA02, 2, -1, -1, 1, NULL}, - {{&machine_dac_type}, 1, 0, PIN_PA05, 2, -1, -1, 1, NULL}, + {{&machine_dac_type}, 0, 0, PIN_PA02, DEFAULT_DAC_VREF, -1, -1, false, 1, NULL}, + {{&machine_dac_type}, 1, 0, PIN_PA05, DEFAULT_DAC_VREF, -1, -1, false, 1, NULL}, }; // 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 @@ -78,9 +85,6 @@ 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) #endif // defined SAMD21 or SAMD51 @@ -96,6 +100,7 @@ void dac_irq_handler(int dma_channel) { 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); } @@ -113,6 +118,7 @@ void dac_irq_handler(int dma_channel) { 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); } @@ -136,10 +142,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; @@ -192,9 +198,18 @@ static void dac_init(dac_obj_t *self, Dac *dac) { 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) { + // 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 { + // 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; @@ -217,6 +232,10 @@ 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->busy != false) { + mp_raise_OSError(MP_EBUSY); + } + int value = mp_obj_get_int(value_in); if (value < 0 || value > MAX_DAC_VALUE) { @@ -259,6 +278,8 @@ static mp_obj_t dac_write_timed(size_t n_args, const mp_obj_t *args) { } // 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) @@ -337,10 +358,18 @@ static mp_obj_t dac_deinit(mp_obj_t self_in) { self->tc_index = -1; } self->callback = MP_OBJ_NULL; + self->busy = false; 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); + // Clear the DMA channel entry in the DAC object. void dac_deinit_channel(void) { dac_obj[0].dma_channel = -1; @@ -350,6 +379,7 @@ void dac_deinit_channel(void) { } static const mp_rom_map_elem_t dac_locals_dict_table[] = { + { 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), MP_ROM_PTR(&dac_write_obj) }, { MP_ROM_QSTR(MP_QSTR_write_timed), MP_ROM_PTR(&dac_write_timed_obj) }, From 16f3c54bb89bdb1311701f02a05764ca8d52e4c6 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Wed, 26 Oct 2022 20:39:44 +0200 Subject: [PATCH 08/13] samd/docs: Document the extensions to DAC and ADC. Signed-off-by: robert-hh --- docs/samd/quickref.rst | 150 +++++++++++++++++++++++++++++++++-------- 1 file changed, 122 insertions(+), 28 deletions(-) diff --git a/docs/samd/quickref.rst b/docs/samd/quickref.rst index 60c57b3a47..998fad386d 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,51 @@ 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 the resources used by it, especially the DMA channel +and the Timer. 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. + + Software SPI bus ---------------- From 115a9ba9508d5b30f88e06fbf42aeb5cc609c707 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Sat, 3 Dec 2022 17:51:34 +0100 Subject: [PATCH 09/13] samd/ADC: Apply the channel's VRef setting at each read. Signed-off-by: robert-hh --- ports/samd/machine_adc.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ports/samd/machine_adc.c b/ports/samd/machine_adc.c index 854260927b..4b1c38797b 100644 --- a/ports/samd/machine_adc.c +++ b/ports/samd/machine_adc.c @@ -208,6 +208,7 @@ static mp_obj_t adc_obj_make_new(const mp_obj_type_t *type, size_t n_args, size_ // flag the device/channel as being in use. ch_busy_flags |= (1 << (self->adc_config.device * 16 + self->adc_config.channel)); + device_mgmt[self->adc_config.device].init = false; self->dma_channel = -1; self->tc_index = -1; @@ -225,6 +226,8 @@ static mp_int_t mp_machine_adc_read_u16(machine_adc_obj_t *self) { mp_raise_OSError(MP_EBUSY); } + // 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; @@ -261,6 +264,8 @@ static void machine_adc_read_timed(mp_obj_t self_in, mp_obj_t values, mp_obj_t f if (self->tc_index == -1) { self->tc_index = allocate_tc_instance(); } + // 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; @@ -407,7 +412,7 @@ static void adc_init(machine_adc_obj_t *self) { // 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 = self->vref; + 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 From f5fd6fbe941df3601b1c8b17435081a057dece9c Mon Sep 17 00:00:00 2001 From: robert-hh Date: Fri, 24 Mar 2023 11:52:11 +0100 Subject: [PATCH 10/13] samd/DAC: Rework the DAC deinit() semantics. Since the two channels of a SAMD51 are not completely independent, dac.deinit() now clears both channels, and both channels have to be re-instantiated after a deinit(). Side change: - rearrange some code lines. Signed-off-by: robert-hh --- docs/samd/quickref.rst | 7 +++-- ports/samd/machine_dac.c | 65 +++++++++++++++++++++++----------------- ports/samd/main.c | 4 +-- 3 files changed, 43 insertions(+), 33 deletions(-) diff --git a/docs/samd/quickref.rst b/docs/samd/quickref.rst index 998fad386d..907f611ffb 100644 --- a/docs/samd/quickref.rst +++ b/docs/samd/quickref.rst @@ -415,11 +415,12 @@ otherwise. .. method:: deinit() -Deinitialize the DAC and release the resources used by it, especially the DMA channel -and the Timer. On most SAMD21 boards, there is just one timer available for +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. +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/ports/samd/machine_dac.c b/ports/samd/machine_dac.c index a8fa4a0e8c..3b98c3cc41 100644 --- a/ports/samd/machine_dac.c +++ b/ports/samd/machine_dac.c @@ -46,8 +46,8 @@ typedef struct _dac_obj_t { mp_obj_base_t base; uint8_t id; bool initialized; - mp_hal_pin_obj_t gpio_id; uint8_t vref; + mp_hal_pin_obj_t gpio_id; int8_t dma_channel; int8_t tc_index; bool busy; @@ -55,7 +55,7 @@ typedef struct _dac_obj_t { mp_obj_t callback; } dac_obj_t; Dac *const dac_bases[] = DAC_INSTS; -static void dac_init(dac_obj_t *self, Dac *dac); +static void dac_init(dac_obj_t *self); static mp_obj_t dac_deinit(mp_obj_t self_in); #if defined(MCU_SAMD21) @@ -65,7 +65,7 @@ static mp_obj_t dac_deinit(mp_obj_t self_in); #define MAX_DAC_VREF (2) static dac_obj_t dac_obj[] = { - {{&machine_dac_type}, 0, 0, PIN_PA02, DEFAULT_DAC_VREF, -1, -1, false, 1, NULL}, + {{&machine_dac_type}, 0, 0, DEFAULT_DAC_VREF, PIN_PA02}, }; #elif defined(MCU_SAMD51) @@ -75,8 +75,8 @@ static dac_obj_t dac_obj[] = { #define MAX_DAC_VREF (3) static dac_obj_t dac_obj[] = { - {{&machine_dac_type}, 0, 0, PIN_PA02, DEFAULT_DAC_VREF, -1, -1, false, 1, NULL}, - {{&machine_dac_type}, 1, 0, PIN_PA05, DEFAULT_DAC_VREF, -1, -1, false, 1, NULL}, + {{&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 @@ -161,18 +161,20 @@ static mp_obj_t dac_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_ self->dma_channel = -1; self->tc_index = -1; self->initialized = false; + self->busy = false; - Dac *dac = dac_bases[0]; // Just one DAC - dac_init(self, dac); + 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, Dac *dac) { +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 @@ -194,9 +196,6 @@ static void dac_init(dac_obj_t *self, Dac *dac) { #elif defined(MCU_SAMD51) // Configuration SAMD51 - // 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; // If the DAC is enabled it was already reset // In that case just disable it. @@ -206,6 +205,9 @@ static void dac_init(dac_obj_t *self, Dac *dac) { 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) { @@ -232,6 +234,10 @@ 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 (self->busy != false) { mp_raise_OSError(MP_EBUSY); } @@ -241,8 +247,6 @@ static mp_obj_t dac_write(mp_obj_t self_in, mp_obj_t value_in) { if (value < 0 || value > MAX_DAC_VALUE) { mp_raise_ValueError(MP_ERROR_TEXT("value out of range")); } - // Re-init, if required - dac_init(self, dac); #if defined(MCU_SAMD21) dac->DATA.reg = value; #elif defined(MCU_SAMD51) @@ -257,8 +261,10 @@ 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; - // Re-init, if required - dac_init(self, dac); + + if (self->initialized == false) { + mp_raise_OSError(MP_ENODEV); + } mp_get_buffer_raise(args[1], &src, MP_BUFFER_READ); if (n_args > 3) { @@ -343,11 +349,8 @@ static mp_obj_t dac_write_timed(size_t n_args, const mp_obj_t *args) { } static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(dac_write_timed_obj, 3, 4, dac_write_timed); -static mp_obj_t dac_deinit(mp_obj_t self_in) { - dac_obj_t *self = self_in; +static void dac_deinit_channel(dac_obj_t *self) { self->initialized = false; - // Reset the DAC to lower the current consumption as SAMD21 - dac_bases[0]->CTRLA.bit.SWRST = 1; if (self->dma_channel >= 0) { dac_stop_dma(self->dma_channel, true); free_dma_channel(self->dma_channel); @@ -359,6 +362,20 @@ static mp_obj_t dac_deinit(mp_obj_t self_in) { } 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); @@ -370,18 +387,10 @@ static mp_obj_t machine_dac_busy(mp_obj_t self_in) { } static MP_DEFINE_CONST_FUN_OBJ_1(machine_dac_busy_obj, machine_dac_busy); -// Clear the DMA channel entry in the DAC object. -void dac_deinit_channel(void) { - dac_obj[0].dma_channel = -1; - #if defined(MCU_SAMD51) - dac_obj[1].dma_channel = -1; - #endif -} - static const mp_rom_map_elem_t dac_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&dac_write_obj) }, { 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), MP_ROM_PTR(&dac_write_obj) }, { MP_ROM_QSTR(MP_QSTR_write_timed), MP_ROM_PTR(&dac_write_timed_obj) }, }; diff --git a/ports/samd/main.c b/ports/samd/main.c index 802838784d..1c1b6c7d6e 100644 --- a/ports/samd/main.c +++ b/ports/samd/main.c @@ -40,7 +40,7 @@ extern uint8_t _sstack, _estack, _sheap, _eheap; extern void adc_deinit_all(void); -extern void dac_deinit_channel(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); @@ -96,7 +96,7 @@ void samd_main(void) { adc_deinit_all(); #endif #if MICROPY_PY_MACHINE_DAC - dac_deinit_channel(); + dac_deinit_all(); #endif pin_irq_deinit_all(); #if MICROPY_PY_MACHINE_PWM From 0bc34e70aeae1ef12bdd56c2cd12c9d61eff8cb8 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Sat, 25 Mar 2023 16:41:21 +0100 Subject: [PATCH 11/13] samd/ADC_DAC: Make adc.read_timed() and dac.write_timed() configurable. Both together require ~1.9k of flash space, including the DMA-manager and the TC-manager. adc.read_timed() uses ~700 bytes, dac.write_timed() ~600 bytes. Signed-off-by: robert-hh --- ports/samd/dma_manager.c | 4 ++++ ports/samd/machine_adc.c | 39 +++++++++++++++++++++++++++++++-------- ports/samd/machine_dac.c | 33 ++++++++++++++++++++++++++++++--- ports/samd/main.c | 4 ++++ ports/samd/mpconfigport.h | 11 +++++++++++ ports/samd/tc_manager.c | 4 ++++ 6 files changed, 84 insertions(+), 11 deletions(-) diff --git a/ports/samd/dma_manager.c b/ports/samd/dma_manager.c index 868606e66b..2ca7f7ba10 100644 --- a/ports/samd/dma_manager.c +++ b/ports/samd/dma_manager.c @@ -30,6 +30,8 @@ #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. @@ -129,3 +131,5 @@ void dac_stop_dma(int dma_channel, bool wait) { } #endif } + +#endif diff --git a/ports/samd/machine_adc.c b/ports/samd/machine_adc.c index 4b1c38797b..efaf2b708a 100644 --- a/ports/samd/machine_adc.c +++ b/ports/samd/machine_adc.c @@ -50,8 +50,10 @@ typedef struct _machine_adc_obj_t { uint8_t avg; uint8_t bits; uint8_t vref; + #if MICROPY_PY_MACHINE_ADC_TIMED int8_t dma_channel; int8_t tc_index; + #endif } machine_adc_obj_t; #define DEFAULT_ADC_BITS 12 @@ -74,9 +76,11 @@ static uint8_t adc_vref_table[] = { typedef struct _device_mgmt_t { bool init; + #if MICROPY_PY_MACHINE_ADC_TIMED bool busy; mp_obj_t callback; mp_obj_t self; + #endif } device_mgmt_t; device_mgmt_t device_mgmt[ADC_INST_NUM]; @@ -103,10 +107,7 @@ typedef struct _device_mgmt_t { mp_obj_t self; } device_mgmt_t; -device_mgmt_t device_mgmt[ADC_INST_NUM] = { - { 0, 0, -1, MP_OBJ_NULL, MP_OBJ_NULL}, - { 0, 0, -1, MP_OBJ_NULL, MP_OBJ_NULL} -}; +device_mgmt_t device_mgmt[ADC_INST_NUM]; #endif // defined(MCU_SAMD21) @@ -123,6 +124,8 @@ static void adc_init(machine_adc_obj_t *self); extern mp_int_t log2i(mp_int_t num); +#if MICROPY_PY_MACHINE_ADC_TIMED + // Active just for SAMD21, stops the freerun mode // For SAMD51, just the INT flag is reset. void adc_irq_handler(int dma_channel) { @@ -152,6 +155,7 @@ void adc_irq_handler(int dma_channel) { } #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; @@ -171,7 +175,9 @@ static mp_obj_t adc_obj_make_new(const mp_obj_type_t *type, size_t n_args, size_ { 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_TIMED { MP_QSTR_callback, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + #endif }; // Parse the arguments. @@ -199,18 +205,20 @@ static mp_obj_t adc_obj_make_new(const mp_obj_type_t *type, size_t n_args, size_ if (0 <= vref && vref <= MAX_ADC_VREF) { self->vref = vref; } + // flag the device/channel as being in use. + 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_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; } - - // flag the device/channel as being in use. - ch_busy_flags |= (1 << (self->adc_config.device * 16 + self->adc_config.channel)); - device_mgmt[self->adc_config.device].init = false; self->dma_channel = -1; self->tc_index = -1; + #endif adc_init(self); @@ -222,9 +230,12 @@ 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_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]; @@ -375,6 +386,9 @@ static mp_int_t machine_adc_busy(mp_obj_t self_in) { return device_mgmt[self->adc_config.device].busy ? true : false; } +#endif + +#if MICROPY_PY_MACHINE_ADC_TIMED void adc_deinit_all(void) { ch_busy_flags = 0; device_mgmt[0].init = 0; @@ -384,6 +398,15 @@ void adc_deinit_all(void) { 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 diff --git a/ports/samd/machine_dac.c b/ports/samd/machine_dac.c index 3b98c3cc41..21b0e67d4d 100644 --- a/ports/samd/machine_dac.c +++ b/ports/samd/machine_dac.c @@ -48,15 +48,16 @@ typedef struct _dac_obj_t { bool initialized; uint8_t vref; 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); -static mp_obj_t dac_deinit(mp_obj_t self_in); #if defined(MCU_SAMD21) @@ -88,6 +89,8 @@ static uint8_t dac_vref_table[] = { #endif // defined SAMD21 or SAMD51 +#if MICROPY_PY_MACHINE_DAC_TIMED + void dac_irq_handler(int dma_channel) { dac_obj_t *self; @@ -126,6 +129,8 @@ void dac_irq_handler(int dma_channel) { #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) { @@ -133,7 +138,9 @@ static mp_obj_t dac_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_ 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. @@ -153,15 +160,17 @@ static mp_obj_t dac_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_ self->vref = vref; } + #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->initialized = false; self->busy = false; + #endif + + self->initialized = false; dac_init(self); // Set the port as given in self->gpio_id as DAC @@ -238,9 +247,11 @@ static mp_obj_t dac_write(mp_obj_t self_in, mp_obj_t value_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); @@ -257,6 +268,8 @@ 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]; @@ -386,12 +399,26 @@ static mp_obj_t machine_dac_busy(mp_obj_t 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 1c1b6c7d6e..b9758c2eb7 100644 --- a/ports/samd/main.c +++ b/ports/samd/main.c @@ -90,8 +90,12 @@ 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 diff --git a/ports/samd/mpconfigport.h b/ports/samd/mpconfigport.h index 0b47500bf7..1a3f5c32fb 100644 --- a/ports/samd/mpconfigport.h +++ b/ports/samd/mpconfigport.h @@ -128,6 +128,17 @@ #define MICROPY_PY_MACHINE_WDT_TIMEOUT_MS (1) #define MICROPY_PLATFORM_VERSION "ASF4" +#ifndef MICROPY_PY_MACHINE_DAC_TIMED +#define MICROPY_PY_MACHINE_DAC_TIMED (1) +#endif +#ifndef MICROPY_PY_MACHINE_ADC_TIMED +#define MICROPY_PY_MACHINE_ADC_TIMED (1) +#endif +#if MICROPY_PY_MACHINE_DAC_TIMED || MICROPY_PY_MACHINE_ADC_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/tc_manager.c b/ports/samd/tc_manager.c index fb0c3b77ff..b6b981087c 100644 --- a/ports/samd/tc_manager.c +++ b/ports/samd/tc_manager.c @@ -29,6 +29,8 @@ #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) @@ -179,3 +181,5 @@ void tc_deinit(void) { instance_flag[0] = instance_flag[1] = true; #endif } + +#endif From 42aef1a6ce0673ca0d0c8bc2e27d3cdd7ace3f30 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Sat, 26 Aug 2023 10:43:09 +0200 Subject: [PATCH 12/13] samd: Change the init sequence for adc_timed() and dac_timed(). To leave no half-initialized device if init fails. Signed-off-by: robert-hh --- ports/samd/machine_adc.c | 8 +++++--- ports/samd/machine_dac.c | 6 +++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/ports/samd/machine_adc.c b/ports/samd/machine_adc.c index efaf2b708a..8eb3fcc72a 100644 --- a/ports/samd/machine_adc.c +++ b/ports/samd/machine_adc.c @@ -267,14 +267,14 @@ static void machine_adc_read_timed(mp_obj_t self_in, mp_obj_t values, mp_obj_t f mp_get_buffer_raise(values, &src, MP_BUFFER_READ); if (src.len >= 2) { int freq = mp_obj_get_int(freq_in); + 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); } - if (self->tc_index == -1) { - self->tc_index = allocate_tc_instance(); - } // Set the reference voltage. Default: external AREFA. adc->REFCTRL.reg = adc_vref_table[self->vref]; // Set Input channel and resolution @@ -330,6 +330,8 @@ static void machine_adc_read_timed(mp_obj_t self_in, mp_obj_t values, mp_obj_t f 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 | diff --git a/ports/samd/machine_dac.c b/ports/samd/machine_dac.c index 21b0e67d4d..425c01f4a8 100644 --- a/ports/samd/machine_dac.c +++ b/ports/samd/machine_dac.c @@ -287,14 +287,14 @@ static mp_obj_t dac_write_timed(size_t n_args, const mp_obj_t *args) { } 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); } - if (self->tc_index == -1) { - self->tc_index = allocate_tc_instance(); - } // Configure TC; no need to check the return value configure_tc(self->tc_index, freq, 0); self->busy = true; From a7edf0107c13697e35b540cb9723681f2fe0f699 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Tue, 24 Oct 2023 10:01:25 +0200 Subject: [PATCH 13/13] samd/machine_adc.c: Factor out machine.adc_timed(). After machine.ADC has been moved to extmod/machine_adc.c. Adding adc.read_timed() and adc.busy() to extmod/machine_adc.c with a corresponding flag to enable them. ADC/DAC timed are by default enabled only at all SAMD51 devices and at SAMD21 devices with an external flash for the file system. Side changes: - Fix a type in pin_af.c, preventing to use a second ADC channel. - Remove a duplicate definition in mcu/samd21/mpconfigmcu.h. Signed-off-by: robert-hh --- extmod/machine_adc.c | 22 +++++++++++++ ports/samd/machine_adc.c | 49 +++++++++++++---------------- ports/samd/mcu/samd21/mpconfigmcu.h | 7 +++++ ports/samd/mcu/samd51/mpconfigmcu.h | 14 ++++++--- ports/samd/mpconfigport.h | 8 +---- 5 files changed, 62 insertions(+), 38 deletions(-) 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/machine_adc.c b/ports/samd/machine_adc.c index 8eb3fcc72a..e5028f2935 100644 --- a/ports/samd/machine_adc.c +++ b/ports/samd/machine_adc.c @@ -28,17 +28,13 @@ // This file is never compiled standalone, it's included directly from // extmod/machine_adc.c via MICROPY_PY_MACHINE_ADC_INCLUDEFILE. -#if MICROPY_PY_MACHINE_ADC - #include #include "py/obj.h" -#include "py/runtime.h" #include "py/mperrno.h" -#include "py/mphal.h" +#include "mphalport.h" #include "sam.h" #include "pin_af.h" -#include "modmachine.h" #include "samd_soc.h" #include "dma_manager.h" #include "tc_manager.h" @@ -50,7 +46,7 @@ typedef struct _machine_adc_obj_t { uint8_t avg; uint8_t bits; uint8_t vref; - #if MICROPY_PY_MACHINE_ADC_TIMED + #if MICROPY_PY_MACHINE_ADC_READ_TIMED int8_t dma_channel; int8_t tc_index; #endif @@ -76,7 +72,7 @@ static uint8_t adc_vref_table[] = { typedef struct _device_mgmt_t { bool init; - #if MICROPY_PY_MACHINE_ADC_TIMED + #if MICROPY_PY_MACHINE_ADC_READ_TIMED bool busy; mp_obj_t callback; mp_obj_t self; @@ -124,7 +120,7 @@ static void adc_init(machine_adc_obj_t *self); extern mp_int_t log2i(mp_int_t num); -#if MICROPY_PY_MACHINE_ADC_TIMED +#if MICROPY_PY_MACHINE_ADC_READ_TIMED // Active just for SAMD21, stops the freerun mode // For SAMD51, just the INT flag is reset. @@ -166,8 +162,7 @@ static void mp_machine_adc_print(const mp_print_t *print, mp_obj_t self_in, mp_p 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) { +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, ARG_callback }; static const mp_arg_t allowed_args[] = { @@ -175,7 +170,7 @@ static mp_obj_t adc_obj_make_new(const mp_obj_type_t *type, size_t n_args, size_ { 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_TIMED + #if MICROPY_PY_MACHINE_ADC_READ_TIMED { MP_QSTR_callback, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, #endif }; @@ -209,7 +204,7 @@ static mp_obj_t adc_obj_make_new(const mp_obj_type_t *type, size_t n_args, size_ 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_TIMED + #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; @@ -231,7 +226,7 @@ static mp_int_t mp_machine_adc_read_u16(machine_adc_obj_t *self) { // Set the reference voltage. Default: external AREFA. adc->REFCTRL.reg = adc_vref_table[self->vref]; - #if MICROPY_PY_MACHINE_ADC_TIMED + #if MICROPY_PY_MACHINE_ADC_READ_TIMED if (device_mgmt[self->adc_config.device].busy != 0) { mp_raise_OSError(MP_EBUSY); } @@ -260,13 +255,13 @@ static mp_int_t mp_machine_adc_read_u16(machine_adc_obj_t *self) { return adc->RESULT.reg * (65536 / (1 << self->bits)); } -static void machine_adc_read_timed(mp_obj_t self_in, mp_obj_t values, mp_obj_t freq_in) { - machine_adc_obj_t *self = self_in; +#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) { - int freq = mp_obj_get_int(freq_in); if (self->tc_index == -1) { self->tc_index = allocate_tc_instance(); } @@ -359,12 +354,19 @@ static void machine_adc_read_timed(mp_obj_t self_in, mp_obj_t values, mp_obj_t f #endif // defined SAMD21 or SAMD51 } - return mp_const_none; } +// 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) { - busy_flags &= ~((1 << (self->adc_config.device * 16 + self->adc_config.channel))); + 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) { @@ -380,17 +382,10 @@ static void mp_machine_adc_deinit(machine_adc_obj_t *self) { free_tc_instance(self->tc_index); self->tc_index = -1; } + #endif } -// busy() : Report, if the ADC device is busy -static mp_int_t machine_adc_busy(mp_obj_t self_in) { - machine_adc_obj_t *self = MP_OBJ_TO_PTR(self_in); - return device_mgmt[self->adc_config.device].busy ? true : false; -} - -#endif - -#if MICROPY_PY_MACHINE_ADC_TIMED +#if MICROPY_PY_MACHINE_ADC_READ_TIMED void adc_deinit_all(void) { ch_busy_flags = 0; device_mgmt[0].init = 0; 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 1a3f5c32fb..1a4a2741a3 100644 --- a/ports/samd/mpconfigport.h +++ b/ports/samd/mpconfigport.h @@ -128,13 +128,7 @@ #define MICROPY_PY_MACHINE_WDT_TIMEOUT_MS (1) #define MICROPY_PLATFORM_VERSION "ASF4" -#ifndef MICROPY_PY_MACHINE_DAC_TIMED -#define MICROPY_PY_MACHINE_DAC_TIMED (1) -#endif -#ifndef MICROPY_PY_MACHINE_ADC_TIMED -#define MICROPY_PY_MACHINE_ADC_TIMED (1) -#endif -#if MICROPY_PY_MACHINE_DAC_TIMED || MICROPY_PY_MACHINE_ADC_TIMED +#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