samd/clock_config: Add HW_DFLL_USB_SYNC and HW_MCU_OSC32KULP extensions.

Two new compile flags are:

MICROPY_HW_DFLL_USB_SYNC: Effective only if DFLL48 does not run from the
crystal.  It will synchronize the DFLL48M clock with the USB's SOF pulse.
If no USB is connected, it will fall back to open loop mode.  The DFLL48M
clock is then pretty precise, but with a higher clock jitter at SAMD51
devices.

MICROPY_HW_MCU_OSC32KULP: Effective only if the devics uses a crystal as
clock source.  Run the MCU clock from the ULP 32kHz oszillator instead of
the crystal.  This flag was added to cater for a interference problem of
the crystal and Neopixel/Debug pins at Adafruit FEATHER Mx boards, which
causes the board to crash.  Drawback: ticks_ms() and time.time() vs. than
ticks_us() and the peripherals like PWM run at not synchronous clocks.
This commit is contained in:
robert-hh 2022-06-15 18:58:02 +02:00 committed by Damien George
parent f00356a486
commit 20e7313453
7 changed files with 137 additions and 18 deletions

View File

@ -1,4 +1,5 @@
#define MICROPY_HW_BOARD_NAME "Feather M4 Express"
#define MICROPY_HW_MCU_NAME "SAMD51J19A"
#define MICROPY_HW_XOSC32K (1)
#define MICROPY_HW_XOSC32K (1)
#define MICROPY_HW_MCU_OSC32KULP (1)

View File

@ -1,2 +1,4 @@
#define MICROPY_HW_BOARD_NAME "ItsyBitsy M0 Express"
#define MICROPY_HW_MCU_NAME "SAMD21G18A"
#define MICROPY_HW_DFLL_USB_SYNC (1)

View File

@ -1,2 +1,4 @@
#define MICROPY_HW_BOARD_NAME "ItsyBitsy M4 Express"
#define MICROPY_HW_MCU_NAME "SAMD51G19A"
#define MICROPY_HW_DFLL_USB_SYNC (1)

View File

@ -30,4 +30,5 @@ void init_clocks(uint32_t cpu_freq);
void set_cpu_freq(uint32_t cpu_freq);
uint32_t get_cpu_freq(void);
uint32_t get_apb_freq(void);
void check_usb_recovery_mode(void);
void enable_sercom_clock(int id);

View File

@ -29,10 +29,12 @@
#include <stdint.h>
#include "py/runtime.h"
#include "py/mphal.h"
#include "samd_soc.h"
static uint32_t cpu_freq = CPU_FREQ;
static uint32_t apb_freq = APB_FREQ;
static uint32_t dfll48m_calibration;
int sercom_gclk_id[] = {
GCLK_CLKCTRL_ID_SERCOM0_CORE, GCLK_CLKCTRL_ID_SERCOM1_CORE,
@ -52,14 +54,28 @@ void set_cpu_freq(uint32_t cpu_freq_arg) {
cpu_freq = cpu_freq_arg;
}
void check_usb_recovery_mode(void) {
#if !MICROPY_HW_XOSC32K
mp_hal_delay_ms(500);
// Check USB status. If not connected, switch DFLL48M back to open loop
if (USB->DEVICE.DeviceEndpoint[0].EPCFG.reg == 0) {
// Set/keep the open loop mode of the device.
SYSCTRL->DFLLVAL.reg = dfll48m_calibration;
SYSCTRL->DFLLCTRL.reg = SYSCTRL_DFLLCTRL_CCDIS | SYSCTRL_DFLLCTRL_ENABLE;
}
#endif // MICROPY_HW_XOSC32K
}
void init_clocks(uint32_t cpu_freq) {
dfll48m_calibration = 0; // please the compiler
// SAMD21 Clock settings
// GCLK0: 48MHz from DFLL open loop mode or closed loop mode from 32k Crystal
// GCLK1: 32768 Hz from 32K ULP or 32k Crystal
// GCLK1: 32768 Hz from 32K ULP or DFLL48M
// GCLK2: 48MHz from DFLL for Peripherals
// GCLK3: 1Mhz for the us-counter (TC4/TC5)
// GCLK4: 32kHz from crystal, if present
// GCLK8: 1kHz clock for WDT
NVMCTRL->CTRLB.bit.MANW = 1; // errata "Spurious Writes"
@ -74,19 +90,34 @@ void init_clocks(uint32_t cpu_freq) {
}
// Set up the DFLL48 according to the data sheet 17.6.7.1.2
// Step 1: Set up the reference clock
// Connect the OSC32K via GCLK1 to the DFLL input and for further use.
#if MICROPY_HW_MCU_OSC32KULP
// Connect the GCLK1 to the XOSC32KULP
GCLK->GENDIV.reg = GCLK_GENDIV_ID(1) | GCLK_GENDIV_DIV(1);
GCLK->GENCTRL.reg = GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_OSCULP32K | GCLK_GENCTRL_ID(1);
#else
// Connect the GCLK1 to OSC32K via GCLK1 to the DFLL input and for further use.
GCLK->GENDIV.reg = GCLK_GENDIV_ID(1) | GCLK_GENDIV_DIV(1);
GCLK->GENCTRL.reg = GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_XOSC32K | GCLK_GENCTRL_ID(1);
#endif
while (GCLK->STATUS.bit.SYNCBUSY) {
}
GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID_DFLL48 | GCLK_CLKCTRL_GEN_GCLK1 | GCLK_CLKCTRL_CLKEN;
// Connect the GCLK4 to OSC32K via GCLK1 to the DFLL input and for further use.
GCLK->GENDIV.reg = GCLK_GENDIV_ID(4) | GCLK_GENDIV_DIV(1);
GCLK->GENCTRL.reg = GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_XOSC32K | GCLK_GENCTRL_ID(4);
while (GCLK->STATUS.bit.SYNCBUSY) {
}
// Connect GCLK4 to the DFLL input and for further use.
GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID_DFLL48 | GCLK_CLKCTRL_GEN_GCLK4 | GCLK_CLKCTRL_CLKEN;
// Enable access to the DFLLCTRL reg acc. to Errata 1.2.1
SYSCTRL->DFLLCTRL.reg = SYSCTRL_DFLLCTRL_ENABLE;
while (SYSCTRL->PCLKSR.bit.DFLLRDY == 0) {
}
// Step 2: Set the coarse and fine values.
// The coarse setting will be taken from the calibration data. So the value used here
// does not matter. Get the coarse value from the calib data. In case it is not set,
// Get the coarse value from the calib data. In case it is not set,
// set a midrange value.
uint32_t coarse = (*((uint32_t *)FUSES_DFLL48M_COARSE_CAL_ADDR) & FUSES_DFLL48M_COARSE_CAL_Msk)
>> FUSES_DFLL48M_COARSE_CAL_Pos;
@ -103,7 +134,7 @@ void init_clocks(uint32_t cpu_freq) {
}
// Step 4: Start the DFLL and wait for the PLL lock. We just wait for the fine lock, since
// coarse adjusting is bypassed.
SYSCTRL->DFLLCTRL.reg |= SYSCTRL_DFLLCTRL_MODE | SYSCTRL_DFLLCTRL_WAITLOCK |
SYSCTRL->DFLLCTRL.reg |= SYSCTRL_DFLLCTRL_MODE | SYSCTRL_DFLLCTRL_WAITLOCK | SYSCTRL_DFLLCTRL_STABLE |
SYSCTRL_DFLLCTRL_BPLCKC | SYSCTRL_DFLLCTRL_ENABLE;
while (SYSCTRL->PCLKSR.bit.DFLLLCKF == 0) {
}
@ -114,18 +145,33 @@ void init_clocks(uint32_t cpu_freq) {
SYSCTRL->DFLLCTRL.reg = SYSCTRL_DFLLCTRL_ENABLE;
while (!SYSCTRL->PCLKSR.bit.DFLLRDY) {
}
SYSCTRL->DFLLMUL.reg = SYSCTRL_DFLLMUL_CSTEP(1) | SYSCTRL_DFLLMUL_FSTEP(1)
| SYSCTRL_DFLLMUL_MUL(48000);
uint32_t coarse = (*((uint32_t *)FUSES_DFLL48M_COARSE_CAL_ADDR) & FUSES_DFLL48M_COARSE_CAL_Msk)
>> FUSES_DFLL48M_COARSE_CAL_Pos;
if (coarse == 0x3f) {
coarse = 0x1f;
}
SYSCTRL->DFLLVAL.reg = SYSCTRL_DFLLVAL_COARSE(coarse) | SYSCTRL_DFLLVAL_FINE(512);
SYSCTRL->DFLLCTRL.reg = SYSCTRL_DFLLCTRL_CCDIS | SYSCTRL_DFLLCTRL_USBCRM
SYSCTRL->DFLLVAL.reg = SYSCTRL_DFLLVAL_COARSE(coarse) | SYSCTRL_DFLLVAL_FINE(511);
#if MICROPY_HW_DFLL_USB_SYNC
// Configure the DFLL48M for USB clock recovery.
// Will have to switch back if no USB
SYSCTRL->DFLLSYNC.bit.READREQ = 1;
dfll48m_calibration = SYSCTRL->DFLLVAL.reg;
// Set the Multiplication factor.
SYSCTRL->DFLLMUL.reg = SYSCTRL_DFLLMUL_CSTEP(1) | SYSCTRL_DFLLMUL_FSTEP(1)
| SYSCTRL_DFLLMUL_MUL(48000);
// Set the mode to closed loop USB Recovery mode
SYSCTRL->DFLLCTRL.reg = SYSCTRL_DFLLCTRL_USBCRM | SYSCTRL_DFLLCTRL_CCDIS
| SYSCTRL_DFLLCTRL_MODE | SYSCTRL_DFLLCTRL_ENABLE;
#else
// Set/keep the open loop mode of the device.
SYSCTRL->DFLLCTRL.reg = SYSCTRL_DFLLCTRL_CCDIS | SYSCTRL_DFLLCTRL_ENABLE;
#endif
while (!SYSCTRL->PCLKSR.bit.DFLLRDY) {
}
// Enable 32768 Hz on GCLK1 for consistency
GCLK->GENDIV.reg = GCLK_GENDIV_ID(1) | GCLK_GENDIV_DIV(48016384 / 32768);
GCLK->GENCTRL.reg = GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_DFLL48M | GCLK_GENCTRL_ID(1);
@ -154,10 +200,10 @@ void init_clocks(uint32_t cpu_freq) {
GCLK->GENCTRL.reg = GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_OSCULP32K | GCLK_GENCTRL_ID(8);
while (GCLK->STATUS.bit.SYNCBUSY) {
}
}
void enable_sercom_clock(int id) {
// Next: Set up the clocks
// Enable synchronous clock. The bits are nicely arranged
PM->APBCMASK.reg |= 0x04 << id;
// Select multiplexer generic clock source and enable.

View File

@ -29,10 +29,12 @@
#include <stdint.h>
#include "py/runtime.h"
#include "py/mphal.h"
#include "samd_soc.h"
static uint32_t cpu_freq = CPU_FREQ;
static uint32_t apb_freq = APB_FREQ;
static uint32_t dfll48m_calibration;
int sercom_gclk_id[] = {
SERCOM0_GCLK_ID_CORE, SERCOM1_GCLK_ID_CORE,
@ -84,12 +86,43 @@ void set_cpu_freq(uint32_t cpu_freq_arg) {
}
}
void check_usb_recovery_mode(void) {
#if !MICROPY_HW_XOSC32K
mp_hal_delay_ms(500);
// Check USB status. If not connected, switch DFLL48M back to open loop
if (USB->DEVICE.DeviceEndpoint[0].EPCFG.reg == 0) {
// as per Errata 2.8.3
OSCCTRL->DFLLMUL.reg = 0;
while (OSCCTRL->DFLLSYNC.bit.DFLLMUL == 1) {
}
// Set the mode to open loop mode
OSCCTRL->DFLLCTRLB.reg = 0;
while (OSCCTRL->DFLLSYNC.bit.DFLLCTRLB == 1) {
}
OSCCTRL->DFLLCTRLA.reg = OSCCTRL_DFLLCTRLA_RUNSTDBY | OSCCTRL_DFLLCTRLA_ENABLE;
while (OSCCTRL->DFLLSYNC.bit.ENABLE == 1) {
}
OSCCTRL->DFLLVAL.reg = dfll48m_calibration; // Reload DFLLVAL register
while (OSCCTRL->DFLLSYNC.bit.DFLLVAL == 1) {
}
// Set the mode to open loop mode
OSCCTRL->DFLLCTRLB.reg = 0;
while (OSCCTRL->DFLLSYNC.bit.DFLLCTRLB == 1) {
}
}
#endif // MICROPY_HW_XOSC32K
}
void init_clocks(uint32_t cpu_freq) {
dfll48m_calibration = 0; // please the compiler
// SAMD51 clock settings
// GCLK0: 48MHz from DFLL48M or 48 - 200 MHz from DPLL0 (SAMD51)
// GCLK1: DPLLx_REF_FREQ 32768 Hz from 32KULP or 32k Crystal
// GCLK1: 32768 Hz from 32KULP or DFLL48M
// GCLK2: 48MHz from DFLL48M for Peripheral devices
// GCLK3: 16Mhz for the us-counter (TC0/TC1)
// GCLK4: 32kHz from crystal, if present
// DPLL0: 48 - 200 MHz
// Steps to set up clocks:
@ -102,6 +135,7 @@ void init_clocks(uint32_t cpu_freq) {
// Setup GCLK0 to 120MHz
// Setup GCLK2 to 48MHz for Peripherals
// Setup GCLK3 to 8MHz for TC0/TC1
// Setup GCLK4 to 32kHz crystal, if present
// Setup GCLK0 for 48MHz as default state to keep the MCU running during config change.
GCLK->GENCTRL[0].reg = GCLK_GENCTRL_RUNSTDBY | GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_DFLL;
@ -124,15 +158,26 @@ void init_clocks(uint32_t cpu_freq) {
while (OSC32KCTRL->STATUS.bit.XOSC32KRDY == 0) {
}
#if MICROPY_HW_MCU_OSC32KULP
// Setup GCLK1 for 32kHz ULP
GCLK->GENCTRL[1].reg = GCLK_GENCTRL_RUNSTDBY | GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_OSCULP32K;
#else
// Setup GCLK1 for 32kHz crystal
GCLK->GENCTRL[1].reg = GCLK_GENCTRL_RUNSTDBY | GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_XOSC32K;
#endif
while (GCLK->SYNCBUSY.bit.GENCTRL1) {
}
// Setup GCLK4 for 32kHz crystal
GCLK->GENCTRL[4].reg = GCLK_GENCTRL_RUNSTDBY | GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_XOSC32K;
while (GCLK->SYNCBUSY.bit.GENCTRL4) {
}
// Set-up the DFLL48M in closed loop mode with input from the 32kHz crystal
// Step 1: Peripheral channel 0 is driven by GCLK1 and it feeds DFLL48M
GCLK->PCHCTRL[0].reg = GCLK_PCHCTRL_GEN_GCLK1 | GCLK_PCHCTRL_CHEN;
// Step 1: Peripheral channel 0 is driven by GCLK4 and it feeds DFLL48M
GCLK->PCHCTRL[0].reg = GCLK_PCHCTRL_GEN_GCLK4 | GCLK_PCHCTRL_CHEN;
while (GCLK->PCHCTRL[0].bit.CHEN == 0) {
}
// Step 2: Set the multiplication values. The offset of 16384 to the freq is for rounding.
@ -141,7 +186,7 @@ void init_clocks(uint32_t cpu_freq) {
while (OSCCTRL->DFLLSYNC.bit.DFLLMUL == 1) {
}
// Step 3: Set the mode to closed loop
OSCCTRL->DFLLCTRLB.reg = OSCCTRL_DFLLCTRLB_BPLCKC | OSCCTRL_DFLLCTRLB_MODE;
OSCCTRL->DFLLCTRLB.reg = OSCCTRL_DFLLCTRLB_BPLCKC | OSCCTRL_DFLLCTRLB_STABLE | OSCCTRL_DFLLCTRLB_MODE;
while (OSCCTRL->DFLLSYNC.bit.DFLLCTRLB == 1) {
}
// Wait for lock fine
@ -154,12 +199,34 @@ void init_clocks(uint32_t cpu_freq) {
#else // MICROPY_HW_XOSC32K
// Set GCLK1 to DPLL0_REF_FREQ as defined in mpconfigboard.h (e.g. 32768 Hz)
// Derive GCLK1 from DFLL48M at DPLL0_REF_FREQ as defined in mpconfigboard.h (e.g. 32768 Hz)
GCLK->GENCTRL[1].reg = ((APB_FREQ + DPLLx_REF_FREQ / 2) / DPLLx_REF_FREQ) << GCLK_GENCTRL_DIV_Pos
| GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_DFLL;
while (GCLK->SYNCBUSY.bit.GENCTRL1) {
}
OSCCTRL->DFLLCTRLA.bit.RUNSTDBY = 1;
OSCCTRL->DFLLCTRLA.bit.ONDEMAND = 0;
OSCCTRL->DFLLCTRLA.bit.ENABLE = 1;
while (OSCCTRL->DFLLSYNC.bit.ENABLE == 1) {
}
#if MICROPY_HW_DFLL_USB_SYNC
// Configure the DFLL48M for USB clock recovery.
// Will have to switch back if no USB
dfll48m_calibration = OSCCTRL->DFLLVAL.reg;
// Set the Multiplication factor.
OSCCTRL->DFLLMUL.reg = OSCCTRL_DFLLMUL_MUL(48000) |
OSCCTRL_DFLLMUL_FSTEP(1) | OSCCTRL_DFLLMUL_CSTEP(1);
while (OSCCTRL->DFLLSYNC.bit.DFLLMUL == 1) {
}
// Set the mode to closed loop USB Recovery
OSCCTRL->DFLLCTRLB.reg = OSCCTRL_DFLLCTRLB_USBCRM | OSCCTRL_DFLLCTRLB_CCDIS | OSCCTRL_DFLLCTRLB_MODE;
while (OSCCTRL->DFLLSYNC.bit.DFLLCTRLB == 1) {
}
#endif
#endif // MICROPY_HW_XOSC32K
// Peripheral channel 1 is driven by GCLK1 and it feeds DPLL0
@ -183,7 +250,6 @@ void init_clocks(uint32_t cpu_freq) {
}
void enable_sercom_clock(int id) {
// Next: Set up the clocks
GCLK->PCHCTRL[sercom_gclk_id[id]].reg = GCLK_PCHCTRL_CHEN | GCLK_PCHCTRL_GEN_GCLK2;
// no easy way to set the clocks, except enabling all of them
switch (id) {

View File

@ -111,6 +111,7 @@ void samd_init(void) {
SysTick_Config(get_cpu_freq() / 1000);
init_us_counter();
usb_init();
check_usb_recovery_mode();
#if defined(MCU_SAMD51)
mp_hal_ticks_cpu_enable();
#endif