ARM CM0: MCU appears to crash on the second wakeup from standby, help? - Page 1
Forum section: electronics / general

author: XFDDesign
author_rank: Frequent Contributor
timestamp: September 01, 2017, 03:48:03 pm
content: I have an ATSAMD09 CortexM0 I've been developing my understanding of, and am presently sorting out the standby feature.I have a simple program which configures some clocks, mainly setting up the timer and the core clock. Once they're setup, I configure the timer to run on the ULP32k clock and turn on the interrupts (timer2 overflow interrupt). I set the 'standby' bit on SCR, and then send "WFI" in my deepSleep() routine. Once I call it, boom. The part goes into standby as intended. After N counts of the ULP32k timer, the interrupt trips. The system wakes up into the interrupt routine, I clear the interrupt flag. I rerun my GCLK and SYSCTRL startup routines and toggle a pin a few times to signal that it's back in operation, and then it goes back to sleep. To here, it's doing fine. While in standby, it's 2.5uA. When active, it's about 750uA. Now the second time it's supposed to wake up.... It stalls, and burns 384uA.The external oscillator doesn't fire up, so I've had it run on the OSC8M. No change in current.I figured that maybe it's falling into a trap state, and since the Debug (Atmel-ICE) stops working once you go into standby, I'd have it just toggle a pin from the default interrupt handler. It never gets called.Do any of you experienced ARM folk have any idea what I'm doing wrong? Is there anything I can look for?Here is my source (not in total, but covers the program aspect):Code:void deepSleep(void){pGCLK->GENCTRL=0x00290300;// turn on GEN0 and drive it from OSC32ULPpGCLK->GENCTRL=0x00290303;// turn on GEN3 and drive it from OSC32ULP (needed for timer)pSysCtrl->XOSC = 0xAB44;// turn off XOSCpSysCtrl->OSC8M = 0x880e0380;// turn off OSC8M*pSCR |= 0x4;// DEEEP SLEEP!asm volatile("WFI");}void wakeUp(void){volatileuint32_t testResult;pSysCtrl->OSC8M = 0x880e0382;// turn on OSC8Mwhile(1){ // wait until OSC8M is ready...testResult = pSysCtrl->INTFLAG&0x08;if(0!=testResult)break;}pPort->DIRSET = 0xFFFFFFFF; // set to OUTPUTpPort->OUTCLR = 0xFFFFFFFF; // turn all the outputs to 0 - try and reduce current/powerpPort->OUTSET = (1 << 10);/*pPort->PINCFG=0x01;// setup the pin to be multiplexed.pPort->PMUX=0x77;// set PA10 and PA11 to be muxed to peripheral HpPort->PINCFG=0x01;pPort->PMUX=0x22;// set PA06 (SERCOMPAD0) to output and mux it.*/pSysCtrl->XOSC = 0xAB46;// turn on XOSCwhile(1){ // wait until XOSC is ready...testResult = pSysCtrl->INTFLAG&0x01;if(0!=testResult)break;}pGCLK->GENCTRL=0x00290001;// turn on GEN1 and drive it from XOSCpGCLK->GENCTRL=0x00090204;// now drive GEN4 from GEN1pGCLK->GENCTRL=0x00290600;// turn on GEN0 and drive it from GCLK1 (OSC8M for now, again)//pGCLK->GENDIV =0x00000A00;// divide by 10 for 1.356MHz operationpGCLK->GENCTRL=0x00290303;// turn on GEN3 and drive it from OSC32ULP (needed for timer)pPM->APBCMASK = 0x84;setupTimer(65535-350);/*pGCLK->CLKCTRL = 0x400E;// route the clock to the core.pCOM->CTRLA = 0x40120004;// setup the serial portpCOM->BAUD =64793;// 9600 bpspCOM->CTRLA = 0x40120006;// setup the serial portpCOM->CTRLB = 0x00010000;// turn on the transmitter*/}void setupTimer(uint16_t count){//route clock.pGCLK->GENCTRL = 0x00290303;// route the 32kHz ULP osc to GEN3. Run while in standbypGCLK->CLKCTRL = 0x4312;// route Gen3 to TC2 (for some reason there is no tie-in to TC1?)pPM->APBCMASK = 0x84;// enable the clock on the power map// now setup the timer, but don't turn it onpTC2->CTRLA = 0x0F00;// just a simple counter. 32,000Hz / 1024 = 31.25Hz / 32mS tickpTC2->INTFLAG = 0x01;// make sure the flag is cleared.pTC2->INTENSET=0x01;// enable "OVERFLOW INTERRUPT"pTC2->COUNT = count;// load up the valuepNVIC->NVIC_ISER = (1 << 14);// set the interrupt for timer 14resetCount = count;}void startTimer(void){pTC2->CTRLA = 0x0F02;// turn on the timer.}void resetTimer(void){//route clock.pGCLK->GENCTRL = 0x00290303;// route the 32kHz ULP osc to GEN3. Run while in standbypGCLK->CLKCTRL = 0x4312;// route Gen3 to TC2 (for some reason there is no tie-in to TC1?)pPM->APBCMASK = 0x84;// enable the clock on the power map// now setup the timer, but don't turn it onpTC2->INTFLAG = 0x39;// make sure the flag is cleared.pTC2->INTENSET=0x01;// enable "OVERFLOW INTERRUPT"pTC2->COUNT = resetCount;// load up the valuepTC2->CTRLA = 0x0F02;// just a simple counter. 32,000Hz / 1024 = 31.25Hz / 32mS tickpTC2->COUNT = resetCount;// load up the value// turn it on}void __attribute__((interrupt(14))) TC2_Handler(void) {// do nothing!pTC2->INTFLAG = 0x01;wakeUp();for(uint32_t x=0;x<10;x++){pPort->OUTTGL=(1<<10);for(uint32_t q=0;q<100;q++);}resetTimer();deepSleep();}int main(void){wakeUp();startTimer();// trigger it.while (1){deepSleep();}}

author: ataradov
author_rank: Super Contributor
timestamp: September 01, 2017, 05:39:46 pm
content: Please, use defines from header files. It is impossible to read this code with all those magic numbers.And where this "__attribute__((interrupt(14))) " even come from? This is nonsense for ARM, and compiler should probably complain about this.You can't (or should not) call deepSleep() from the IRQ handler itself. Let it exit the interrupt normally and your main while (1) loop will take care of the sleep.Now your code blocks because the same interrupt handler can't be called twice without extra messing around.

author: XFDDesign
author_rank: Frequent Contributor
timestamp: September 01, 2017, 06:03:25 pm
content: Quote from: ataradov on September 01, 2017, 05:39:46 pmThe structures are from the included Atmel source, and thus I omitted them. I'm not sure what else you're trying to get to.QuoteIt appears to be compliant from the examples in source I've found, and ISR 14 is Timer2's interrupt vector.QuoteSince posting this, I'm able to skip that function call and have instead simply set SCB.SLEEPONEXIT. Still get the exact same behavior though.QuoteIt shouldn't be called twice, all things considered, unless something else goes seriously wrong. It's the _only_ ISR used, and given the lag developed by the timer (typ >1s at least) should not be a problem.----------------------------------------------------------------------------------One thing I found since the original post, is that if I don't set SCB.DEEPSLEEP and let it just 'sleep,' the sleep current is the same 358uA as when the device crashes. I'm unable to determine any correlation to the root issue, but it was an interesting observation. As mentioned above, digging into the SCB reg, there is a "SLEEPONEXIT" bit, which upon leaving the ISR returns to sleep mode, essentially allowing me to do all my business within the timer ISR (just had to make sure to turn *off* the Interrupt Service once within it). I do still get the same unwanted outcome, however. One deep sleep cycle, one wake up, a second sleep cycle, and then 'unknown' state.

author: ataradov
author_rank: Super Contributor
timestamp: September 01, 2017, 06:07:42 pm
content: They are standard CMSIS headers (made by Atmel, obviously, who else will make them?). If you refuse to use them, then I personally won't be able to help you, sorry.The main problem is that you are calling issuing WFI from within the interrupt handler.Attribute stuff is not need on Cortex devices, it all works with plain C.

author: XFDDesign
author_rank: Frequent Contributor
timestamp: September 01, 2017, 06:17:24 pm
content: Quote from: ataradov on September 01, 2017, 06:07:42 pmAs mentioned, I no longer need to call WFI within the handler, and it still fails, so it follows that WFI from within the handler isnotthe problem.QuoteOmitting the __attribute__ factor, as used inAtmel's own codeproduces the following error:"Error expected declaration specifiers or '...' before numeric constant"Just for completeness' sake, here is an excerpt from the startup_samd09.c as generated by Atmel:Code:/* Cortex-M0+ core handlers */void NMI_Handler ( void ) __attribute__ ((weak, alias("Dummy_Handler")));void HardFault_Handler ( void ) __attribute__ ((weak, alias("Dummy_Handler")));void SVC_Handler ( void ) __attribute__ ((weak, alias("Dummy_Handler")));void PendSV_Handler ( void ) __attribute__ ((weak, alias("Dummy_Handler")));void SysTick_Handler ( void ) __attribute__ ((weak, alias("Dummy_Handler")));QuoteI'm not even understanding what or where your complaint is and by the discrepancies above, I too do not think your efforts will be purposeful as well.

author: ataradov
author_rank: Super Contributor
timestamp: September 01, 2017, 06:23:18 pm
content: Quote from: XFDDesign on September 01, 2017, 06:17:24 pmThen you should probably post up to date code. It is hard to guess what changes you have made.Quote from: XFDDesign on September 01, 2017, 06:17:24 pmyou need to remove the whole thing. Just leave the function alone. I can guarantee that this construct is never used in Atmel code for ARM, since it is an AVR thing.Quote from: XFDDesign on September 01, 2017, 06:17:24 pmYes, I know how Atmel code looks like.Quote from: XFDDesign on September 01, 2017, 06:17:24 pmI'm complaing about magic numbers everywhere. Here is how reasonable written GCLK initialization looks like, IMO:Code:GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID(TC1_GCLK_ID) | GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN(0);

author: XFDDesign
author_rank: Frequent Contributor
timestamp: September 01, 2017, 06:33:44 pm
content: Quote from: ataradov on September 01, 2017, 06:23:18 pmThis much is reasonable.Changed items:Code:void __attribute__((interrupt(14))) TC2_Handler(void) {// do nothing!pTC2->INTFLAG = 0x01; // clear the ISRwakeUp(); // Restore all of the clock configurations.for(uint32_t x=0;x<10;x++){pPort->OUTTGL=(1<<10); // perform a simple togglefor(uint32_t q=0;q<100;q++); // perform a bit of time delay}resetTimer(); // stuff the timer with appropriate reset information and start itpGCLK->GENCTRL=0x00290300;// turn on GEN0 and drive it from OSC32ULPpGCLK->GENCTRL=0x00290303;// turn on GEN3 and drive it from OSC32ULP (needed for timer)pSysCtrl->XOSC = 0xAB44;// turn off XOSCpSysCtrl->OSC8M = 0x880e0380;// turn off OSC8M}andCode:void deepSleep(void){pGCLK->GENCTRL=0x00290300;// turn on GEN0 and drive it from OSC32ULPpGCLK->GENCTRL=0x00290303;// turn on GEN3 and drive it from OSC32ULP (needed for timer)pSysCtrl->XOSC = 0xAB44;// turn off XOSCpSysCtrl->OSC8M = 0x880e0380;// turn off OSC8M*pSCR |= 0x06;// DEEEP SLEEP, but now use SLEEPONEXIT. When an ISR finishes,// the uC will resume sleeping rather than continuing with the// programasm volatile("WFI");}

author: ataradov
author_rank: Super Contributor
timestamp: September 01, 2017, 06:51:07 pm
content: There is no need to enable/disable clocks, just configure them once and let the MCU handle the rest. If you don't want some clocks to run when MCU is sleeping, just don't set RUNSTDBY bit.If for whatever reason you need to do clock control manually, you have to wait for synchronization before going to sleep (or exiting the IRQ handler in this case).But I do suspect that the problems come from this manual clock management without waiting for synchronization or any of the status bits.And just useCode:void TC2_Handler(void) {This attribute is meaningless on ARM.

author: XFDDesign
author_rank: Frequent Contributor
timestamp: September 01, 2017, 07:00:11 pm
content: Quote from: ataradov on September 01, 2017, 06:51:07 pmI'll go through and correct the code to release the manual control from the code and follow up.On the 'magic numbers,' for what it's worth, those terms you used are not present in the datasheet (http://www.atmel.com/Images/Atmel-42414-SAM-D09_Datasheet.pdf) . What I get are bit locations, not pseudonyms (not sure the right term for it) as you used. My efforts in finding good documentation for ARM has been largely unfruitful.

author: ataradov
author_rank: Super Contributor
timestamp: September 01, 2017, 07:13:44 pm
content: Those defines have very well defined structure. It is always possible to infer a name from a register name (which you already use in your code) and a bit name mentioned in the datasheet. And you can just look in the corresponding header file, of course.I have some examples for Atmel ARM MCUs on my GitHubhttps://github.com/ataradov/mcu-starter-projectsI can come up with the demo code you want, but im on vacation this week, away from any hardware.

author: westfw
author_rank: Super Contributor
timestamp: September 02, 2017, 01:26:59 am
content: Code:pTC2->INTENSET=0x01;// enable "OVERFLOW INTERRUPT"pTC2->CTRLA = 0x0F02;// just a simple counter. 32,000Hz / 1024 = 31.25Hz / 32mS tickWe want that to look like:Code:pTC2->INTENSET= TC_INTENSET_OVF;// enable "OVERFLOW INTERRUPT"pTC2->CTRLA = TC_CTRLA_PRESCALER_DIV1024 | TC_CTRLA_RUNSTDNY | TC_CTRLA_MODE_COUNT16 | TC_CTRLA_ENABLE;Otherwise we have to open up the datasheet and stare a long time to see if your constants really do what your comment says they do (at least, you DID have comments!)

author: XFDDesign
author_rank: Frequent Contributor
timestamp: September 05, 2017, 06:23:17 pm
content: Quote from: westfw on September 02, 2017, 01:26:59 amFair enough, but without a reliableand consistentsource book to derive this kind of context from, this is asking a lot. What we get are the Datasheets. When I first got into these parts, I tried with the mess that is ASF. No documentation. Half the programs didn't even compile correctly. Everything from Atmel in terms of header files showed itself to be without credit.As such, would it be a reasonable compromise to then do up a mess of #defines, so it might not be "TC_CTRLA_PRESCALAR_DIV1024" but would be "PRESCALARDIV1024"?

author: westfw
author_rank: Super Contributor
timestamp: September 06, 2017, 08:43:55 am
content: QuoteThere is, though as you point out it's not so easy to find, nor well documented.You want the CMSIS-Atmel/<version>/CMSIS/Device/ATMEL/<device>/include/component/<peripheral>.h(For example "CMSIS-Atmel/1.1.0/CMSIS/Device/ATMEL/samd10/include/component/tc.h")It's the same file you got (or that should be used for) the structure definitions needed to use "pTC2->INTENSET" and such.QuoteIt's all machine-generated standard format stuff, so it should be predictable once you get used to it.Code:TC_CTRLA_PRESCALAR_DIV1024xx Peripheral typexxxxx Register namexxxxxxxxx Field namexxxxxxx Value(Those names are all exactly as they are documented in the datasheet.)(Personally, I'm still at the "open the .h file in another window" stage. I guess that since the registers are strongly typed, the "right" IDE will have autocomplete and context-sensitive help?)It's also true that the .h file is obfuscated with a bunch of other, less useful, and occasionally misleading symbol definitions. Things like TC_CTRLA_PRESCALER_Pos, TC_CTRLA_PRESCALER_Msk, TC_CTRLA_PRESCALER(value)...You might findhttps://github.com/WestfW/SAMD10-experimentsuseful. It's where I whine about things that annoyed me in my experiments with a SAMD10 Xplained Mini board, and some of the code I ended up with... It hasn't gotten updated as much as I would have liked, but it's a start.Philosophically, I've been told (but never 100% convinced, I guess), that it would be worthwhile to have your own set of symbols defined, even if you don't use the Atmel-provided .h files, and even if the definitions are subject to your own errors. Ie:Code:#define TC_DIV1024 0x0700#define TC_RUNSTDBY 0x0800#define TC_16BIT 0x0000#define TC_RUN 0x0002pTC2->CTRLA = TC_DIV1024 | TC_RUNSTDBY | TC_16BIT | TC_RUN;