SAMD51 clock configuration 120MHz with START

Go To Last Post
15 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I have a project with a SAMD51 that crashes within the first few seconds of a restart. It uses an external 32k oscillator connected to xosc32k through PA0 and PA1, this is fed into DPLL0 running at 120MHz (with 0xe4d as the lower divider ratio integer), which then goes into GCLK0 and to the CPU. GCLK3 is fed from XOSC32k at 32.768khz, which feeds DFFL48, which then goes into GCLK1 to the USARTs and USB.

 

This seems to be fairly simple as compared to other Samd51 projects I've looked at. 

 

I am aware of the SamD51 errata where you need to use LBYPASS; this seems to be done in the adafruit code everyone steals, but I have no idea how to configure this with START. I think this could be done in hri_oscctrl_d51.h, but.... I have no idea where. This doesn't really make much sense, though, because these library files are downloaded from the Internet, and Microchip _could_ just fix these library files to include fixes from the errata, so that's probably a red herring anyway.

 

Has anyone used START to configure a SAMD51 to run at 120MHz? I see references to it on this board, but no code.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Lock bypass is a setting in start according to pictures here:

https://community.atmel.com/foru...

/Lars

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

 

Alright, well it's not the Lock Bypass, but it appears it's something related to the clocks. I don't have a trace debug because I'm seeing the D51 / Atmel ICE doesn't really support it, but I can check hit 'attach to target' whenever it crashes:

 

Case 1: I set up the clocks per the pic below. DFFL goes to clockgen 2, which goes into USART0. 

 

 

The problem line of code in this case is in hri_gclk_d51.h:

 

static inline void hri_gclk_wait_for_sync(const void *const hw, hri_gclk_syncbusy_reg_t reg)
{
    //Crashes on this line
	while (((Gclk *)hw)->SYNCBUSY.reg & reg) {
	};
}

...So I inspect reg, that's reg = 16381, which means I'm looking at clockgen2 (this is kind of from memory, I have too many windows and tabs open right now). So the problem is with the DFFL48, right?

 

 

Case 2: Thinking this was a problem with the DFFL, I changed the clock config so the USART is driven by clockgen2, which is driven by DPLL0, divided down by 3:

 

 

Compile and launch the project again, and same thing happens: it crashes in a few seconds. Hit attach to target, and now it crashes on a different line in hri_oscctrl_d51.h:

 

static inline bool hri_oscctrl_get_DPLLSTATUS_CLKRDY_bit(const void *const hw, uint8_t submodule_index)
{
    //crashes on this statement
	return (((Oscctrl *)hw)->Dpll[submodule_index].DPLLSTATUS.reg & OSCCTRL_DPLLSTATUS_CLKRDY)
	       >> OSCCTRL_DPLLSTATUS_CLKRDY_Pos;
}

So again it's a clock bug. I don't know what to do here.

 

Just for curiosity, I tried out the code with only clockgen0, running at 120mhz and disabling the USART init:

 

And here it faults on this line in hri_oscctrl_d51.h:

 

static inline bool hri_oscctrl_get_DPLLSTATUS_LOCK_bit(const void *const hw, uint8_t submodule_index)
{
	return (((Oscctrl *)hw)->Dpll[submodule_index].DPLLSTATUS.reg & OSCCTRL_DPLLSTATUS_LOCK)
	       >> OSCCTRL_DPLLSTATUS_LOCK_Pos;
}

THE LOCK BIT! AND LOCK BYPASS IS SET ON DPLL0!

 

I just don't know here. Is this a clock issue? Is this even accurate, and if so how do I get trace debug on a D51? What the hell?

 

The _only_ other thing this code is doing is toggling pins with direct register access; DIRSET and DIRCLR. All of this works, until it doesn't. I just don't know anymore.

 

Last Edited: Sun. Jun 13, 2021 - 01:13 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi,

you didn't specify the hang-up  - what exactly happens.

Just to be sure:
Did you disable the watchdog? -> Fuses!
Because I had also such "strange" behaviour...

Surprise: As soon as one's doing it correctly - it works!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

DrDatasalad wrote:

Hi,

you didn't specify the hang-up  - what exactly happens.

 

My app is just driving a tft display. Once everything is initialized, I just clock through and push pixels to the display. That's it; it's just looking up values from a table and pushing bits over a 16-bit parallel connection. There's nothing _weird_ here, so just think of this as toggling pins really fast. 

 

The problem happens within a few seconds of starting up. From what I can tell, the CPU just stops, and I don't have a WDT enabled. What happens after it stops is in my post above. Note the halt is random; each time I reset the CPU, it stops in a different place. 

 

I've done some earlier tests on this same board, pushing bytes out of the UART to a serial connection; This works, and doesn't stop after a few seconds. I'm sure the problem isn't in the LCD display code, because I have that same code running on a samd21 and it's bulletproof. There's nothing wrong with the LCD code.

 

So I have a hard fault randomly when I'm making the CPU blink pins a lot. There's no watchdog, and the only thing I can think it could be is a synchronization error between the clocks. I'm about to get rid of this START nonsense and do my own bare metal initialization for this chip. 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

There is a thing in the errata.

 

// errata: When using a low-frequency input clock on FDPLLn, several FDPLL unlocks may occur while the output
    // frequency is stable. Workaround: when using a low-frequency input clock on FDPLLn, enable the lock bypass
    // feature to avoid FDPLL unlocks.
    
    REG_OSCCTRL_DPLLCTRLB0 = 0;
    
    REG_OSCCTRL_DPLLCTRLB0 = OSCCTRL_DPLLCTRLB_LBYPASS | OSCCTRL_DPLLCTRLB_REFCLK(0);              // errata says to bypass lock when using internal 32k oscillator as source

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

This is my 120 mhz setup for samd51

 

REG_NVMCTRL_CTRLA = NVMCTRL_CTRLA_AUTOWS; 

	REG_GCLK_GENCTRL3 = GCLK_GENCTRL_SRC_OSCULP32K |  GCLK_GENCTRL_GENEN;     // GCLK3 is driven by internal 32k and feeds DPLL0.
	while(REG_GCLK_SYNCBUSY & GCLK_SYNCBUSY_GENCTRL3 );	
	
	
	REG_GCLK_PCHCTRL1 = GCLK_PCHCTRL_GEN_GCLK3;                                  // GCLK3 feeds DPLL0
	REG_GCLK_PCHCTRL1 |= GCLK_PCHCTRL_GEN_GCLK3 |  GCLK_PCHCTRL_CHEN;
	

	REG_OSCCTRL_DPLLRATIO0 = OSCCTRL_DPLLRATIO_LDR(0xe4d) | OSCCTRL_DPLLRATIO_LDRFRAC(0x4);    
	while(REG_OSCCTRL_DPLLSYNCBUSY0 & OSCCTRL_DPLLSYNCBUSY_DPLLRATIO );
	

	// errata: When using a low-frequency input clock on FDPLLn, several FDPLL unlocks may occur while the output
	// frequency is stable. Workaround: when using a low-frequency input clock on FDPLLn, enable the lock bypass
	// feature to avoid FDPLL unlocks.
	
	REG_OSCCTRL_DPLLCTRLB0 = 0;
	
	REG_OSCCTRL_DPLLCTRLB0 = OSCCTRL_DPLLCTRLB_LBYPASS | OSCCTRL_DPLLCTRLB_REFCLK(0);              // errata says to bypass lock when using internal 32k oscillator as source
	
	REG_OSCCTRL_DPLLCTRLA0 = OSCCTRL_DPLLCTRLA_ENABLE;
	while(REG_OSCCTRL_DPLLSYNCBUSY0 & OSCCTRL_DPLLSYNCBUSY_ENABLE);

	REG_GCLK_GENCTRL0 = GCLK_GENCTRL_SRC_DPLL0| GCLK_GENCTRL_DIV(0) | GCLK_GENCTRL_GENEN; 
	while(REG_GCLK_SYNCBUSY & GCLK_SYNCBUSY_GENCTRL_GCLK0);

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

snarkysparky wrote:

This is my 120 mhz setup for samd51...

 

 

Hi, I've started a new project using your code, that function is the first thing called. It's locking up on this line:

 

	//GCLK 0 is driven by DPLL0 and runs at 120MHz
	REG_GCLK_GENCTRL0 = GCLK_GENCTRL_SRC_DPLL0 | GCLK_GENCTRL_DIV(0) | GCLK_GENCTRL_GENEN;
	while(REG_GCLK_SYNCBUSY & GCLK_SYNCBUSY_GENCTRL_GCLK0);  <- LOCKS ON THIS LINE

Have you found a solution for this?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

 

Some progress, but apparently not enough. I've modified snarkysparky's code to work from an external 32khz crystal, but the code is still locking up on the DPLLSTATUS clkrdy bit. Here's the code:

 

void initClocks(void)
{
	//Set wait states
	NVMCTRL->CTRLA.bit.RWS = 5;

	//Gclk reset
	GCLK->CTRLA.bit.SWRST;

	//OSCILLATOR CONTROL
	//Setup XOSC
	OSC32KCTRL->XOSC32K.bit.CGM = 01;
	OSC32KCTRL->XOSC32K.bit.XTALEN = 1;
	OSC32KCTRL->XOSC32K.bit.EN32K = 0;
	OSC32KCTRL->XOSC32K.bit.ONDEMAND = 0;
	OSC32KCTRL->XOSC32K.bit.RUNSTDBY = 1;
	OSC32KCTRL->XOSC32K.bit.STARTUP = 6;
	OSC32KCTRL->XOSC32K.bit.ENABLE = 1;

	OSC32KCTRL->CFDCTRL.bit.CFDPRESC = 0;
	OSC32KCTRL->CFDCTRL.bit.SWBACK = 0;
	OSC32KCTRL->CFDCTRL.bit.CFDEN = 0;

	OSC32KCTRL->EVCTRL.bit.CFDEO = 0;

	// make sure osc32kcrtl is ready
	while (!OSC32KCTRL->INTFLAG.bit.XOSC32KRDY)
		;

	//Enable DPLL
	OSCCTRL->Dpll[0].DPLLCTRLA.bit.ENABLE = 0;
	while (OSCCTRL->Dpll[0].DPLLSYNCBUSY.bit.ENABLE)
		;

	OSCCTRL->Dpll[0].DPLLRATIO.reg = (12<<16) + 0xe4d;
	while (OSCCTRL->Dpll[0].DPLLSYNCBUSY.bit.DPLLRATIO)
		;

	OSCCTRL->Dpll[0].DPLLCTRLB.bit.DIV = 1;
	OSCCTRL->Dpll[0].DPLLCTRLB.bit.DCOEN = 0;
	OSCCTRL->Dpll[0].DPLLCTRLB.bit.LBYPASS = 1;
	OSCCTRL->Dpll[0].DPLLCTRLB.bit.LTIME = 0;
	OSCCTRL->Dpll[0].DPLLCTRLB.bit.REFCLK = 1;
	OSCCTRL->Dpll[0].DPLLCTRLB.bit.WUF = 0;	

	OSCCTRL->Dpll[0].DPLLCTRLA.bit.ONDEMAND = 0;
	OSCCTRL->Dpll[0].DPLLCTRLA.bit.RUNSTDBY = 1;
	OSCCTRL->Dpll[0].DPLLCTRLA.bit.ENABLE = 1;
	while (OSCCTRL->Dpll[0].DPLLSYNCBUSY.bit.ENABLE)
		;

	////LOCKS UP ON LINE BELOW
	while (! (OSCCTRL->INTFLAG.bit.DPLL0LDRTO || OSCCTRL->Dpll[0].DPLLSTATUS.bit.CLKRDY))
		;

	GCLK->GENCTRL[0].reg = (0 << 16) | (0x21 << 8) | 7; // dpll0 = 7, enabled and standby = 0x21, divide by 1
	while (GCLK->SYNCBUSY.bit.GENCTRL0)
		;

	GCLK->GENCTRL[1].reg = (100 << 16) | (0x21 << 8) | 7; // dpll0 = 7, enabled and standby = 0x21, divide by 100
	while (GCLK->SYNCBUSY.bit.GENCTRL1)
		;

}

I'm starting to wonder if the D51 is worth the effort. This is just a standard, bare-metal project using a samd51, and I can find *nothing* on Github with this setup except the U2F bootloader. Is anyone doing bare-metal D51 development?

 

 

 

 

Last Edited: Tue. Jun 15, 2021 - 09:30 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
	OSC32KCTRL->XOSC32K.bit.EN32K = 0;

Should be enabled.

/Lars

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Alright, it appears I have clocks working. Printf, however, doesn't.

 

This is for a board where TX and RX are connected to PA09 and PA08. The ultimate clock source is a 32k _external_ oscillator.

 

Can someone take a look at this and see what I'm doing wrong?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

But is SERCOM0_write even working, surly you test that before printf? The PORT_WRCONFIG_HWSEL setting looks wrong.

/Lars

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

Missing call to "print_init()" in main()? Alternatively, "fflush(stdout);" after the call to printf.

 

Put a breakpoint on SERCOM0_write() to verify it's being called.

 

The SAMD/E5x series are fine SoCs with very flexible clocking options. With that flexibility comes a fair bit of complexity, so it can seem like there's a high barrier to entry. To help others, I added a simple "bare-metal" clock configuration API in the projects section: https://community.atmel.com/proj...

 

Steve

Maverick Embedded Technologies Ltd. Home of Maven and wAVR.

Maven: WiFi ARM Cortex-M Debugger/Programmer

wAVR: WiFi AVR ISP/PDI/uPDI Programmer

https://www.maverick-embedded.co...

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0


Okay, I'm close, I think. scdoubleu, I looked at your code first, a month ago. That was a good starting point, but it doesn't use xosc32k; that's what I need. Good call on the print_init() though

 

I've configured the clocks, the SERCOM, and I have prinf working. The code for that is here: https://github.com/bbenchoff/Sam...

 

The problem comes when using printf, and It's due to a baud rate error. Trying to print "this works\n" results in this:

 

I've verified the baud rate generator calculation code is correct (used two different implementations, resulted in the same garbage printed out), so the problem is probably with the clocks. I don't see it, though.

 

I'll submit this one last time, because if I can't get this working I'll just give up. Anyone see a problem

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You can always output the clocks on a pin and verify they are ok. For example GCL0 on PB14:
 

        PORT->Group[1].PMUX[14/2].bit.PMUXE = MUX_PB14M_GCLK_IO0;
        PORT->Group[1].PINCFG[14].bit.PMUXEN = 1;
        GCLK->GENCTRL[0].bit.OE = 1;

(just need to include the OE bit in any other GENCTRL[0] writing also).

 

One problem with your setup is that the SERCOM core input frequency limit is 100MHz. I don't know if that is the problem (i.e., I don't know what overclocking a SERCOM looks like).

"54.6 Maximum Clock Frequencies" is the source of such information btw.

/Lars