SAMD21g18: Can SERCOM be used with the 48Mhz clock for USART? - Page 1
Forum section: electronics / general

author: reneman
author_rank: Contributor
timestamp: July 15, 2021, 10:15:15 am
content: Hi,I'm pretty new to the SAMD21, I'm programming it baremetal and have some questions about peripherals and clock domains.I've been able to set up my samd21 so that the GCLK0 for the CPU is at 48MHzhttps://blog.thea.codes/understanding-the-sam-d21-clocks/I had previously the samd21 running at 8MHz and the SERCOM5 configured to use the OSCM8 clock as well, and UART was working fine. Now it just outputs gibberish. Why does this happen? I thought the peripheral was independent to the CPU, and since they now use independent clocks, shouldn't UART keep working?ThanksPS: Here's my code for referenceCode:void clock_init(){SYSCTRL->OSC8M.bit.PRESC = 0; // no prescaler (is 8 on reset)SYSCTRL->OSC8M.reg |= 1 << SYSCTRL_OSC8M_ENABLE_Pos; // enable sourceGCLK->GENDIV.bit.ID = 0x03; // select GCLK_GENGCLK->GENDIV.bit.DIV = 0; // no prescalerGCLK->GENCTRL.bit.ID = 0x03; // select GCLK_GENGCLK->GENCTRL.reg |= GCLK_GENCTRL_SRC_OSC8M; // OSC8M sourceGCLK->GENCTRL.bit.GENEN = 1; // enable generatorGCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID_SERCOM5_CORE; // SERCOM5 multiplexer GCLK_PERIPHERALGCLK->CLKCTRL.reg |= GCLK_CLKCTRL_GEN_GCLK0; // select multiplexer source GCLK_GENGCLK->CLKCTRL.bit.CLKEN = 1; // enable generic clock (for baud rate generation)PM->APBCSEL.bit.APBCDIV = 0; // no prescalerPM->APBCMASK.bit.SERCOM5_ = 1; // enable SERCOM5 synchronous clock}void port_init(){/* assign alternate function (SERCOM5 PAD2 and PAD3) to pin PB22 and PB23 */PORT->Group.PINCFG.bit.PMUXEN = 1; // enable alternate function on pin PB22PORT->Group.PINCFG.bit.PMUXEN = 1; // enable alternate function on pin PB23PORT->Group.PMUX.bit.PMUXO = 0x03; // pin PB23 is assigned to SERCOM5 PAD3}void USART_init(){// configure USART 8N1SERCOM5->USART.CTRLA.bit.MODE = 0x1; // USART internal clockSERCOM5->USART.CTRLA.bit.CMODE = 0; // asynchronous modeSERCOM5->USART.CTRLA.bit.RXPO = 0x3; // RX on PAD3SERCOM5->USART.CTRLA.bit.TXPO = 0x1; // TX on PAD2SERCOM5->USART.CTRLA.bit.DORD = 1; // data order: LSB firstSERCOM5->USART.CTRLB.bit.CHSIZE = 0x0; // 8-bit characterSERCOM5->USART.CTRLA.bit.FORM = 0; // no parity bitSERCOM5->USART.CTRLB.bit.SBMODE = 0; // 1 stop bitSERCOM5->USART.BAUD.reg = 50437; // 115200 baud rateSERCOM5->USART.CTRLB.bit.RXEN = 1; // enable receiverSERCOM5->USART.CTRLB.bit.TXEN = 1; // enable transmitterSERCOM5->USART.CTRLA.reg |= 1 << SERCOM_USART_CTRLA_ENABLE_Pos; // enable USART}

author: cv007
author_rank: Frequent Contributor
timestamp: July 15, 2021, 01:24:01 pm
content: I have a samD10, which is similar enough. You have specific requirements for reading/writing some of those gclk registers, including checking for syncbusy, so accessing these registers via anything other than the 'top' struct member (register size) will result in those requirements not being met.A couple examples from my gclk functions, which do the 32bit writes and do not preserve any bits (these are simple functions, and if other options needed in genctrl then modification or more functions needed)-Code:Gclk.generatorSource( Gclk.GEN3, Gclk.OSC8M );Gclk.generatorDivide( Gclk.GEN3, 0 );Gclk.generatorSource( Gclk.MAIN, Gclk.DFLL48M );Gclk.generatorDivide( Gclk.MAIN, mhz <= 12_MHz ? 2 : mhz <= 24_MHz ? 1 : 0 );Gclk.generatorUser( Gclk.GEN3, Gclk.SERCOM0 );//etc.//sourceautogeneratorSource (GENERATOR g, SOURCE s){while( busy() ){}reg_.GCLK_GENCTRL = GENENbm bitor (s<<8) bitor g; //32bit write}autogeneratorDivide (GENERATOR g, u16 d){while( busy() ){}reg_.GCLK_GENDIV = (d<<8) bitor g; //32bit write}autogeneratorUser (GENERATOR g, USER user, bool enable = true){while( busy() ){}reg_.GCLK_CLKCTRL = (enable ? CLKENbm : 0) bitor (g<<8) bitor user; //16bit write}You also have wait states to configure for various cpu speeds and voltages, so if you skip that and are running fast, then that would also cause problems.

author: ataradov
author_rank: Super Contributor
timestamp: July 15, 2021, 06:11:38 pm
content: Your code selects GCLK0 as the source of the cloc for the SERCOM CORE. And your commend does not match the code:GCLK->CLKCTRL.reg |= GCLK_CLKCTRL_GEN_GCLK0; // select multiplexer source GCLK_GENAlso, you don't need to write that much code, you can do all the settings in one write, which would make the code way more readable and efficient:Code:GCLK->GENCTRL.reg = GCLK_GENCTRL_ID(0) | GCLK_GENCTRL_SRC(GCLK_SOURCE_FDPLL) |GCLK_GENCTRL_RUNSTDBY | GCLK_GENCTRL_GENEN;while (GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY);Code:GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID(SERCOM0_GCLK_ID_CORE) |GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN(0);Code:SERCOM0->USART.CTRLA.reg =SERCOM_USART_CTRLA_DORD | SERCOM_USART_CTRLA_MODE_USART_INT_CLK |SERCOM_USART_CTRLA_RXPO(3/*PAD3*/) | SERCOM_USART_CTRLA_TXPO(1/*PAD2*/);SERCOM0->USART.CTRLB.reg = SERCOM_USART_CTRLB_RXEN | SERCOM_USART_CTRLB_TXEN |SERCOM_USART_CTRLB_CHSIZE(0/*8 bits*/);uint64_t br = (uint64_t)65536 * (F_CPU - 16 * baud) / F_CPU;SERCOM0->USART.BAUD.reg = (uint16_t)br+1;SERCOM0->USART.CTRLA.reg |= SERCOM_USART_CTRLA_ENABLE;

author: reneman
author_rank: Contributor
timestamp: July 16, 2021, 08:19:39 am
content: Quote from: ataradov on July 15, 2021, 06:11:38 pmYes, I think I forgot to update the comment, but the code was right in the sense that I want to use GCLK0 (the main CPU's clock, that is 48MHz) as well for the SERCOM5 peripheral.I get an error with your code saying that GCLK_SOURCE_FDPLL is not defined so I'm guessing the SAMD21g18adoesn't have that specific clock source. Should I use GCLK_SOURCE_DFLL48M (The 48MHz clock source) instead?This is my sys_init for reference:Code:static void sys_init(void){// Switch to 8MHz clock (disable prescaler)//SYSCTRL->OSC8M.bit.PRESC = 0;// COnfiguramos los wait states de la NVM a 1 (para correr entre 2.7v y 3.3v a 48MHz)NVMCTRL->CTRLB.bit.RWS = 1;SYSCTRL->XOSC32K.reg =/* Crystal oscillators can take a long time to startup. Thiswaits the maximum amount of time (4 seconds). This can bereduced depending on your crystal oscillator. */SYSCTRL_XOSC32K_STARTUP(0x7) |SYSCTRL_XOSC32K_EN32K |SYSCTRL_XOSC32K_XTALEN;/* This has to be a separate write as per datasheet section 17.6.3 */SYSCTRL->XOSC32K.bit.ENABLE = 1;/* Wait for the external crystal to be ready */while(!SYSCTRL->PCLKSR.bit.XOSC32KRDY);/* Configure GCLK1's divider - in this case, no division - so just divide by one */GCLK->GENDIV.reg =GCLK_GENDIV_ID(1) |GCLK_GENDIV_DIV(1);/* Setup GCLK1 using the external 32.768 kHz oscillator */GCLK->GENCTRL.reg =GCLK_GENCTRL_ID(1) |GCLK_GENCTRL_SRC_XOSC32K |/* Improve the duty cycle. */GCLK_GENCTRL_IDC |GCLK_GENCTRL_GENEN;/* Wait for the write to complete */while(GCLK->STATUS.bit.SYNCBUSY);GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID_DFLL48 | GCLK_CLKCTRL_GEN_GCLK1 | GCLK_CLKCTRL_CLKEN;/* This works around a quirk in the hardware (errata 1.2.1) -the DFLLCTRL register must be manually reset to this value beforeconfiguration. */while(!SYSCTRL->PCLKSR.bit.DFLLRDY);SYSCTRL->DFLLCTRL.reg = SYSCTRL_DFLLCTRL_ENABLE;while(!SYSCTRL->PCLKSR.bit.DFLLRDY);/* Set up the multiplier. This tells the DFLL to multiply the 32.768 kHzreference clock to 48 MHz */SYSCTRL->DFLLMUL.reg =/* This value is output frequency / reference clock frequency,so 48 MHz / 32.768 kHz */SYSCTRL_DFLLMUL_MUL(1465) |/* The coarse and fine step are used by the DFLL to lockon to the target frequency. These are set to halfof the maximum value. Lower values mean less overshoot,whereas higher values typically result in some overshoot butfaster locking. */SYSCTRL_DFLLMUL_FSTEP(511) | // max value: 1023SYSCTRL_DFLLMUL_CSTEP(31); // max value: 63/* Wait for the write to finish */while(!SYSCTRL->PCLKSR.bit.DFLLRDY);uint32_t coarse = (*((uint32_t *)FUSES_DFLL48M_COARSE_CAL_ADDR) & FUSES_DFLL48M_COARSE_CAL_Msk) >> FUSES_DFLL48M_COARSE_CAL_Pos;SYSCTRL->DFLLVAL.bit.COARSE = coarse;/* Wait for the write to finish */while(!SYSCTRL->PCLKSR.bit.DFLLRDY);SYSCTRL->DFLLCTRL.reg |=/* Closed loop mode */SYSCTRL_DFLLCTRL_MODE |/* Wait for the frequency to be locked before outputting the clock */SYSCTRL_DFLLCTRL_WAITLOCK |/* Enable it */SYSCTRL_DFLLCTRL_ENABLE;/* Wait for the frequency to lock */while (!SYSCTRL->PCLKSR.bit.DFLLLCKC || !SYSCTRL->PCLKSR.bit.DFLLLCKF) {}/* Setup GCLK0 using the DFLL @ 48 MHz */GCLK->GENCTRL.reg =GCLK_GENCTRL_ID(0) |GCLK_GENCTRL_SRC_DFLL48M |/* Improve the duty cycle. */GCLK_GENCTRL_IDC |GCLK_GENCTRL_GENEN;/* Wait for the write to complete */while(GCLK->STATUS.bit.SYNCBUSY);}

author: reneman
author_rank: Contributor
timestamp: July 16, 2021, 09:04:04 am
content: I've just noticed I posted the wrong old code for the functions. This is the code thatworks with the main clock at 8MHzCode:void clock_init(){SYSCTRL->OSC8M.bit.PRESC = 0; // no prescaler (is 8 on reset)SYSCTRL->OSC8M.reg |= 1 << SYSCTRL_OSC8M_ENABLE_Pos; // enable sourceGCLK->GENDIV.bit.ID = 0x03; // select GCLK_GENGCLK->GENDIV.bit.DIV = 0; // no prescalerGCLK->GENCTRL.bit.ID = 0x03; // select GCLK_GENGCLK->GENCTRL.reg |= GCLK_GENCTRL_SRC_OSC8M; // OSC8M sourceGCLK->GENCTRL.bit.GENEN = 1; // enable generatorGCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID_SERCOM5_CORE; // SERCOM5 multiplexer GCLK_PERIPHERALGCLK->CLKCTRL.reg |= GCLK_CLKCTRL_GEN_GCLK3; // select multiplexer source GCLK_GENGCLK->CLKCTRL.bit.CLKEN = 1; // enable generic clock (for baud rate generation)PM->APBCSEL.bit.APBCDIV = 0; // no prescalerPM->APBCMASK.bit.SERCOM5_ = 1; // enable SERCOM5 synchronous clock}void port_init(){// assign alternate function (SERCOM5 PAD2 and PAD3) to pin PB22 and PB23PORT->Group.PINCFG.bit.PMUXEN = 1; // enable alternate function on pin PB22PORT->Group.PINCFG.bit.PMUXEN = 1; // enable alternate function on pin PB23PORT->Group.PMUX.bit.PMUXE = 0x03; // pin PB22 is assigned to SERCOM5 PAD2PORT->Group.PMUX.bit.PMUXO = 0x03; // pin PB23 is assigned to SERCOM5 PAD3}void USART_init(){// configure USART 8N1SERCOM5->USART.CTRLA.bit.MODE = 0x1; // USART internal clockSERCOM5->USART.CTRLA.bit.CMODE = 0; // asynchronous modeSERCOM5->USART.CTRLA.bit.RXPO = 0x3; // RX on PAD3SERCOM5->USART.CTRLA.bit.TXPO = 0x1; // TX on PAD2SERCOM5->USART.CTRLA.bit.DORD = 1; // data order: LSB firstSERCOM5->USART.CTRLB.bit.CHSIZE = 0x0; // 8-bit characterSERCOM5->USART.CTRLA.bit.FORM = 0; // no parity bitSERCOM5->USART.CTRLB.bit.SBMODE = 0; // 1 stop bitSERCOM5->USART.BAUD.reg = 50437; // 115200 baud rateSERCOM5->USART.CTRLB.bit.RXEN = 1; // enable receiverSERCOM5->USART.CTRLB.bit.TXEN = 1; // enable transmitterSERCOM5->USART.CTRLA.reg |= 1 << SERCOM_USART_CTRLA_ENABLE_Pos; // enable USART}When I changed the main CPU's clock (GCLK0) to work at 48MHz that's when it stopped working. I get gibberish on the uart:

author: ataradov
author_rank: Super Contributor
timestamp: July 16, 2021, 04:22:21 pm
content: I'm lost in what you are doing. Did you change SERCOM clock to 48 MHz? If so, did you recalculate the BAUD value for the new clock?

author: cv007
author_rank: Frequent Contributor
timestamp: July 16, 2021, 04:57:05 pm
content: You will have to figure out what your usart has for its clock. If its outputting data then it has some kind of clock, so continuously send out a 'U' and measure what you have ( 1/bitwidth *16 = usart clock freq ). You then can see what your usart clock freq is, and either your clock source is obviously wrong or your baud register value needs changing to account for the clock error.I have a samd10 xplained mini hooked up, and although I do not have sercom code as I do with other peripherals, I used what I have and then just set the usart registers up as needed. My dfll48m is running in open loop using the flash stored coarse values, and when I switched from osc8m@8MHz to dfll48m I had to figure out why the calculated value did not work. So I continuously output a 'U' and measured it, and it turns out the cpu is running at 40MHZ. Adjusted the baud register value for actual freq and it works ok.You still need to look at the gclk register requirements for writing/reading (and any register for that matter), as using the bit members will result in a read/modify/write. The results may be ok, but you could end up with reads for a previous id and writes that may not be wanted even if the next write gets the correct value in place. Pretty confusing, so just do as they say- a single 32bit/16bit write as requested by the datasheet.Code:int main(){Sysctrl.clockInit( 48_MHz ); //dfll48(open loop)->MAIN/GEN0Gclk.generatorUser( Gclk.MAIN, Gclk.SERCOM0 ); //MAIN/GEN0->SERCOM0Pm.clockEnable( Pm.SERCOM0 );//63019 baud value for 48MHz, actual fcpu is 40MHz so needed a new valueSERCOM0_REGS->USART_INT.SERCOM_BAUD = 62516;SERCOM0_REGS->USART_INT.SERCOM_CTRLB = 0x00010000; //txenSERCOM0_REGS->USART_INT.SERCOM_CTRLA = 0x40010006; //lsb,pa10/pad2/tx,intclk,enablePin(PA10).altFunction(ALTFUNC_C); //pa10 alt function c, sercom0/pad2/txfor(char c = 'a';;){while( (SERCOM0_REGS->USART_INT.SERCOM_INTFLAG & 1) == 0 ){}SERCOM0_REGS->USART_INT.SERCOM_DATA = c; //or 'U' for timingif( ++c > 'z' ) c = 'a';delayMS(48_MHz, 5);}}

author: reneman
author_rank: Contributor
timestamp: July 19, 2021, 08:04:19 am
content: Quote from: ataradov on July 16, 2021, 04:22:21 pmYes, I changed the clock to 48MHz, but I did not recalculate the BAUD value. You and cv007 are right on that.What is the formula to compute the new baud?I've seen this one on some people's code:Code:uint64_t br = (uint64_t)65536 * (F_CPU - 16 * baud) / F_CPU;where F_CPU would be my clock frequency If I'm not mistaken. Is this the value I need to update for the register SERCOM5->USART.BAUD.reg ? What is the reasoning behind this formula?On another note and replying to @cv007 I have this line in my USART_Init routine:Code:void USART_init(){uint32_t baud = 115200;// configure USART 8N1SERCOM5->USART.CTRLA.bit.MODE = 0x1; // USART internal clock <-------That I'm not sure what it does, but may be setting a different clock or something.ThanksEDIT: I posted the sys_init code in previous messages but in case it is relevant and wasn't obvious, I'm running the 48MHz clock in closed-lopp mode

author: ataradov
author_rank: Super Contributor
timestamp: July 19, 2021, 08:23:14 am
content: Quote from: reneman on July 19, 2021, 08:04:19 amThat's my code.Quote from: reneman on July 19, 2021, 08:04:19 amDirectly transcribed from the datasheet.Quote from: reneman on July 19, 2021, 08:04:19 amIt selects the internal clock for the UART as opposed to the external clock supplied on the SCK pin (USART mode only).

author: reneman
author_rank: Contributor
timestamp: July 19, 2021, 09:39:23 am
content: Quote from: ataradov on July 19, 2021, 08:23:14 amHow would I proceed to select the 48MHz clock instead?

author: ataradov
author_rank: Super Contributor
timestamp: July 19, 2021, 04:03:25 pm
content: What do you mean? You have selected 48 MHz clock, just use calculator and calculate a new BAUD value for that frequency.

author: cv007
author_rank: Frequent Contributor
timestamp: July 19, 2021, 09:58:25 pm
content: GCLK CLKCTRL selects your sercom (ID) baud rate generator clock from whatever selection of generators (GEN) you have setup, unless you select the external clock (pin) in the sercom peripheral. The GCLK main clock runs the sercom peripheral (not baud rate generator) through another path, which may need enabling for some peripherals that are not enabled by default (unmask via PM).You already have the sercom clock setup via gclk, and if you make any mistake in its clock selection process you will not have a working baud rate generator so not too hard pin down if a problem is due to a missing clock (nothing) vs an inaccurate clock (something), assuming tx/rx pin selection is correct.

author: reneman
author_rank: Contributor
timestamp: July 20, 2021, 07:41:54 am
content: Thanks to the two.It's solved. I've ended up using cv007's baud value for 115200 shown in the code above (I tried using the formula fisrt, but for some reason it wasn't computing the proper value) and now it works fine with that hardcoded value.I had a bit of confussion regarding the two clocks (the one used to generate the bauds as cv007 points, and the one to drive the peripherals)

author: cv007
author_rank: Frequent Contributor
timestamp: July 20, 2021, 02:45:30 pm
content: QuoteSeems odd in closed loop mode you would be that far off. I'm using open loop mode, and it appears the stored nvm calibration coarse value gives me ~40MHz (they may be playing it safe in those values, and the 'close to 48MHz' claim gives no definition of 'close'). I don't use that clock for anything that depends on its timing except to get 'fast' cpu speed at times, so have not paid much attention to what I was really getting.If using closed loop mode with an xtal reference, you should be quite close to 48MHz, and your baud calculation should be valid for that. You can get the clocks to output to a pin, or indirectly measure in some other way. Setting the baud register to 0 and continuously outputting a 'U' is one way, where the bit period is used to calculate the freq (bit freq*16=ref freq). Maybe you are not getting the xtal running (assume you have one connected) and the dfll is 'stuck' using the coarse value which it appears you are setting to get an initial value in the ballpark.

author: ataradov
author_rank: Super Contributor
timestamp: July 20, 2021, 05:25:23 pm
content: Quote from: reneman on July 20, 2021, 07:41:54 amCases like this is when you stop and investigate what is wrong. You just plugged some random value that possibly moved your marginal system just enough into the band to function.The formula works, it represents how hardware designed. If it does not work for you, it means that some of the inputs to the formula are wrong.

author: cv007
author_rank: Frequent Contributor
timestamp: July 20, 2021, 06:54:07 pm
content: Your init code has SYSCTRL_DFLLCTRL_MODE, but that is a macro that takes a parameter, at least in my headers (and the latest pack for sad21). Not sure how that would compile, so must be something else. There are 2 modes, and the define doesn't really indicate what mode means. I would be suspect of this in any case, and since you have a working uart, even if held together by a string, you could dump some of these register values out in sysctrl, starting with DFLLCTRL to see if you are in open or closed loop mode.SYSCTRL->DFLLCTRL.reg |=/* Closed loop mode */SYSCTRL_DFLLCTRL_MODE |/* Wait for the frequency to be locked before outputting the clock */SYSCTRL_DFLLCTRL_WAITLOCK |/* Enable it */SYSCTRL_DFLLCTRL_ENABLE;

author: ataradov
author_rank: Super Contributor
timestamp: July 20, 2021, 07:01:53 pm
content: Single bit defines from Atmel headers became macros with Microchip headers. I think there are some compatibility defines too.

author: reneman
author_rank: Contributor
timestamp: July 21, 2021, 09:26:58 am
content: Quote from: cv007 on July 20, 2021, 02:45:30 pmTo be clear, I used the 63019 baud value for 48MHz, in your code's comments. Not the one you computed for your 40MHz. I assumed that if my clock was well configured and close to 48MHz then it should be fine, and indeed it works.

author: reneman
author_rank: Contributor
timestamp: July 21, 2021, 09:36:13 am
content: Quote from: ataradov on July 20, 2021, 05:25:23 pmYou are right, and I haven't gave up on the issue.My assumption is there is something funky going on with division. I've observed other problems in parts of the code that fail when I use a predefined formula in run-time, but work if I Just compute the number with my calculator and then hardcode it into the code.For example, I Had this code to set up the SysTick interrupt:Code:if ( SysTick_Config( SystemcoreClock / 1000 ) ) //. 1ms interrupts{while ( 1 );} //Catch errors.This code failed when entering the SysTIck_Config function:Code:__STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks){if ((ticks - 1) > SysTick_LOAD_RELOAD_Msk) return (1); /* Reload value impossible */...}Because it returned 1 (reload value impossible). SystemcoreClock definition was this:Code:uint32_t SystemCoreClock=48000000ul;As you see, something was going on when dividing by 1000 and passing the result to the Systick_config function. Using the actual value however, solved the problem (48000000 / 1000 =48000)So this could be something similar, where the result of the formula wasn't 63019 but something different. I'll try to debug the formula now that at least I have a working UARTEDITPerhaps division is not the problem. Just tested this code:Code:uint32_t baud = 115200;uint64_t br = (uint64_t)65536 * (F_CPU - 16 * baud) / F_CPU;char str2;sprintf(str2,"%d",br);Hal.uart_puts(str2);And the UART output is 50436, which is obviously far away from 63019. F_CPU is the only suspect here, and I couldn't find it in the code. It is apparently in the Makefile I use from @ataradov example repos (no doubt I had missed it).Code:DEFINES += \-DDONT_USE_CMSIS_INIT \-DF_CPU=8000000 //<----- THIS SHOULD BE 48000000 nowAnd the formula now computes the proper BAUD value