Hello everyone
I have been happy user of Arduino boards for many years and have utilized them in number of hobby projects. But recently I stumbled across a problem I just can't figure out.
The situation is this (a bit complicated but hopefully I will get it clear): I have a custom board with SAMD21E16. I have got it running with Arduino environment, programming via openocd, custom board variant, etc - everything looks awesome. But I wasn't too happy with the RTC performance - although I got the power consumption down in 2-3 uA it wasn't very accurate. In fact it easily went off by several seconds a day when in standby. The reason turns out to be that in low-power-mode the RTC uses the internal LP oscillator. Switching to the external gives better precision but worse power consumption. On top of that I had trouble with crashes on wake-up and etc. So I gave up on that and switched to an external MCP7940N. Well-known IC that gives me all the RTC features I need. However due to lack of board space (and just because I thought it was a good idea) I removed the 32kHz crystal that is directly connected to the SAMD21. So I had to build it with CRYSTALESS flag set. That got everything working again. Pretty cool!
And here comes the interesting bit - the MCP has option to output the clock on its MFP pin. So I connected it to the SAMD21 and I have verified I have good signal. But the question is - how can I switch the SAMD to use that input as reference clock instead of the usual 32kHz crystal that is connected to it? And as a matter of fact - should I even bother?
The way I think it should be done is this - the Arduino core (built with CRYSTALESS) sets up the clocks properly on boot, then in my setup() I enable the MCP output and then reconfigure all the GCLKs and whatnot to use its output. But unfortunately so far copying a lot of code from the Arduino core SystemInit() resulted only in crashes (stuff that is not executed if no crystal is present). I believe the reason is that once the IC is setup in 48MHz mode I have to reset it back to boot configuration to reconfigure everything, which is not something I particularly like, because at this point I have at least i2c working (to talk to the MCP). So I have to be selective about it.
So the question in short is - is it doable? If yes - any suggestions on approaching that? I am not much a firmware developer so I am getting lost in all these GCLKs and etc so any hints would be great.
On top of that - I am not certain I should even bother, because from what I gather this crystal is used only as sort of "discipline" to the DFLL so unless I have to be extremely precise about it - why bother? Or perhaps without it I can expect some deviations to bite me at one point?
Thanks in advance to anyone willing to drop a piece of a brain on this topic ![]()
P.S.: This is the first part of the code that goes well
// This code is an excerpt from the SystemInit() function in the Arduino core
// see packages/arduino/hardware/samd/1.6.18/cores/arduino/startup.c
SYSCTRL->XOSC32K.reg = SYSCTRL_XOSC32K_STARTUP( 0x6u ) | /* cf table 15.10 of product datasheet in chapter 15.8.6 */
SYSCTRL_XOSC32K_EN32K ; // do not set SYSCTRL_XOSC32K_XTALEN - we are using external clock input
SYSCTRL->XOSC32K.bit.ENABLE = 1 ; /* separate call, as described in chapter 15.6.3 */
while ( (SYSCTRL->PCLKSR.reg & SYSCTRL_PCLKSR_XOSC32KRDY) == 0 ) { } /* Wait for oscillator stabilization */
#define GENERIC_CLOCK_GENERATOR_MAIN (0u)
#define GENERIC_CLOCK_GENERATOR_OSC32K (1u)
#define GENERIC_CLOCK_GENERATOR_XOSC32K (1u)
// Put XOSC32K as source of Generic Clock Generator 1
GCLK->GENDIV.reg = GCLK_GENDIV_ID( GENERIC_CLOCK_GENERATOR_XOSC32K ) ;
while ( GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY ) {}
GCLK->GENCTRL.reg = GCLK_GENCTRL_ID( GENERIC_CLOCK_GENERATOR_OSC32K ) | GCLK_GENCTRL_SRC_XOSC32K | GCLK_GENCTRL_GENEN;
while ( GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY ) {}
This is the second part, where I try to actually reconfigure the DFLL, but it crashes:
// Now we should reconfigure the DFLL. First switch it back to slow 8MHz mode
GCLK->GENDIV.reg = GCLK_GENDIV_ID( GENERIC_CLOCK_GENERATOR_MAIN ) ;
while ( GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY ) {};
GCLK->GENCTRL.reg = GCLK_GENCTRL_ID( GENERIC_CLOCK_GENERATOR_MAIN ) |
GCLK_GENCTRL_SRC_OSC8M |
GCLK_GENCTRL_IDC |
GCLK_GENCTRL_GENEN;
while ( GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY ) {};
// reconfig the DFLL
SYSCTRL->DFLLCTRL.reg |= SYSCTRL_DFLLCTRL_MODE | // Enable the closed loop mode
SYSCTRL_DFLLCTRL_WAITLOCK |
SYSCTRL_DFLLCTRL_QLDIS ;
while ( (SYSCTRL->PCLKSR.reg & SYSCTRL_PCLKSR_DFLLRDY) == 0 )
SYSCTRL->DFLLCTRL.reg |= SYSCTRL_DFLLCTRL_ENABLE;
while ( (SYSCTRL->PCLKSR.reg & SYSCTRL_PCLKSR_DFLLLCKC) == 0 ||
(SYSCTRL->PCLKSR.reg & SYSCTRL_PCLKSR_DFLLLCKF) == 0 ) {}
// switch back to 48MHz
GCLK->GENDIV.reg = GCLK_GENDIV_ID( GENERIC_CLOCK_GENERATOR_MAIN ) ;
while ( GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY ) {};
GCLK->GENCTRL.reg = GCLK_GENCTRL_ID( GENERIC_CLOCK_GENERATOR_MAIN ) |
GCLK_GENCTRL_SRC_DFLL48M |
GCLK_GENCTRL_IDC |
GCLK_GENCTRL_GENEN;
while ( GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY ) {};