BriscoeTech / Arduino-FreeRTOS-SAMD21

A port of FreeRTOS that runs on Arduino Samd21 boards
64 stars 19 forks source link

sleep mode and rtos #19

Open robynjayqueerie opened 5 years ago

robynjayqueerie commented 5 years ago

I have been trying to implement this SAMD21 port of FreeRTOS in conjunction with sleep modes on and Adafruit Feather M0. It does not really work, which is why this post, I have broken my sketch down to setup(), loop(0 and one task which does nothing except suspend itself. I am using the Arduino Low Power library https://www.arduinolibraries.info/libraries/arduino-low-power and checked it for delay() functions (not thread safe) I have also noted the portSUPPRESS_TICKS_AND_SLEEP() Macro discussion on the FreeRTOS documentation https://www.freertos.org/low-power-tickless-rtos.html The SAMD21 implementation does not include the configUSE_TICKLESS_IDLE definition in FreeRTOSConfig.h I added that but no change to the problem. While I will keep trying to sort out what is happening, I was thinking you may have some guidance? Thanks

BriscoeTech commented 5 years ago

Hello @robynjayqueerie

Thanks for posting, I have been wondering if anyone was working on incorporating deep sleep into a project

So my port of the rtos does not include any deep sleep features. The article https://www.freertos.org/low-power-tickless-rtos.html mentions that this feature is only in some ports they directly developed, otherwise someone will have to implement them manually. It looks like some hooks have to be added to the rtos, and they have some sudo code on how you might go about doing it. As of now, the library does not manage anything to do with putting the microcontroller to sleep.

https://www.freertos.org/low-power-tickless-rtos.html

The following source code is an example of how portSUPPRESS_TICKS_AND_SLEEP() might be implemented by an application writer

I think you should be able to put the microcontroller to sleep regardless of what the rtos is doing, just bear in mind that the rtos has no say in when it wakes back up because we are using an external library, and the sleep features are not managed by the rtos.

I might try using the ExternalWakeup sleep command in the idle loop task, and see if you can wake the device up using a button. The external interrupt method should work regardless of how the rtos is setup. If that works, try replacing it with one of the timed sleep commands. I understand if that was not quite what you were looking for, it would be great to have everything sleep between task schedulings and let the rtos sort the sleep modes out.

I'm interested in hearing how this goes :-)

drewfish commented 5 years ago

I've been meaning to do some experiments with this but haven't gotten to it. I sometimes make battery powered devices, and in the future would like to use FreeRTOS on those.

ZZ-Cat commented 5 years ago

Hmmm. I'll give this a go with my Metro M4 Express (I've reformatted it back to C++ from Python) & I'll see where I go with it. I'll let y'all know how I get on with it.

Good thing that my projects are designed with minimal power consumption as a high priority. =^/.^=

ZZ-Cat commented 5 years ago

Update: I gave it a go with my usual way (I use switch-cases, acting as finite state machines to control every state the MCU is in, from reset to standby & everything in between). What I did was, I tried to halt the RTOS with a call to 'vTaskEndScheduler()' & apparently, that locked the entire system up.

I dug through the corresponding library files ('task.c' & 'port.c') & found that the function 'vPortEndScheduler()' doesn't implement anything beyond a simple call to 'configASSERT(uxCriticalNesting == 1000UL)'. This function is the culprit for locking the system up. Is this intentional, or is it a bug?

By the way, I thought it be more prudent to shut the RTOS down before puttin' the MCU to sleep, as that would put the system back to a single-thread environment & I could use my finite state machine to control the MCU from that point forward. Or... that was my theory, until I found this.

I have devised a temporary workaround, by puttin' the MCU to sleep in a dedicated RTOS thread, which works as expected. However, I haven't tested it beyond waking the MCU up with either an external interrupt, or somethin' the likes of an RTC wakeup or otherwise.

PS: I've found it handy to enable the Watchdog timer & read back from the MCU's reset cause register, when the Watchdog expires. That's saved my ass a couple of times already.

jmpmscorp commented 4 years ago

Hi everyone.

I have been playing with this Arduino port and sleep feature around a week. I will try to add my conclusions and maybe, someone finds them interesting.

First, I tried idle hook (arduino loop) to sleep MCU and wakes up from RTC interrupt (RTCZero library). Wake up/sleep cicles work as expected if RTC value update in its interrupt handler. However, task scheduler sometimes hangs. Why? This port use SysTimer as RTOS tick generator but SysTimer is feed with CPU clock and this clock is always power off in Standby Mode or Idle Mode (Refer to Table 16.4 in datasheet). While MCU is sleeping, SysTimer interrupts don't work so RTOS ticks doesn't execute. When MCU wakes up, scheduler doesn't know how many time MCU was sleeping and hangs.

After that, I changed RTOS tick timer from SysTimer to TC7 (Arduino use TC5 as Tone generator). Like Arduino use GCLKs 0 to 3, I used GCLK7 to feed TC7 and I configured TC7 to generate same interrupt frequency than SysTimer (1kHz). Also, clock source, TC7 and GCLK7 must be configured to keep run in Standby. Something like:

__attribute__(( weak )) void vPortSetupTimerInterrupt( void )
{
    /* Configure SysTick to interrupt at the requested rate. */
    // *(portNVIC_SYSTICK_VAL)  = 0; /* Load the SysTick Counter Value */
    // *(portNVIC_SYSTICK_LOAD) = ( configCPU_CLOCK_HZ / configTICK_RATE_HZ ) - 1UL;
    // *(portNVIC_SYSTICK_CTRL) = portNVIC_SYSTICK_CLK | portNVIC_SYSTICK_INT | portNVIC_SYSTICK_ENABLE;

    SYSCTRL->XOSC32K.reg |= SYSCTRL_XOSC32K_RUNSTDBY;

    GCLK->GENDIV.reg = GCLK_GENDIV_ID( 7 ) | GCLK_GENDIV_DIV(16);

    GCLK->GENCTRL.reg = GCLK_GENCTRL_ID(7) |
                        GCLK_GENCTRL_OE |
                        GCLK_GENCTRL_SRC_XOSC32K | 
                        GCLK_GENCTRL_RUNSTDBY |
                        GCLK_GENCTRL_GENEN;

    while(GCLK->STATUS.bit.SYNCBUSY);

    GCLK->CLKCTRL.reg = GCLK_CLKCTRL_GEN_GCLK7 |
                        GCLK_CLKCTRL_ID_TC6_TC7 |
                        GCLK_CLKCTRL_CLKEN;

    while(GCLK->STATUS.bit.SYNCBUSY);

    PM->APBCMASK.bit.TC7_ = 1;

    TC7->COUNT16.CTRLA.reg = TC_CTRLA_MODE_COUNT16 | TC_CTRLA_PRESCALER_DIV1 | TC_CTRLA_WAVEGEN_MFRQ;
    while(TC7->COUNT16.STATUS.bit.SYNCBUSY);

    TC7->COUNT16.CTRLBSET.bit.DIR = 1;
    TC7->COUNT16.CC[0].reg = 1; 
    while(TC7->COUNT16.STATUS.bit.SYNCBUSY);

    TC7->COUNT16.CTRLA.bit.RUNSTDBY = 1;
    while(TC7->COUNT16.STATUS.bit.SYNCBUSY);

    TC7->COUNT16.INTENSET.reg = TC_INTENSET_OVF;

    NVIC_SetPriority(TC7_IRQn, 3);
    NVIC_EnableIRQ(TC7_IRQn);

    TC7->COUNT16.CTRLA.bit.ENABLE = 1;
    while(TC7->COUNT16.STATUS.bit.SYNCBUSY);

}

void TC7_Handler() {
  if (TC7->COUNT8.INTFLAG.bit.OVF && TC7->COUNT8.INTENSET.bit.OVF)             
  {
    xPortSysTickHandler();

    REG_TC7_INTFLAG = TC_INTFLAG_OVF;         // Clear the OVF interrupt flag
  }
}

This way, I can use idle hook to sleep MCU and scheduler doesn't hang. However, this approach doesn't reduce power comsuption as I want. Due to TC7 (RTOS tick timer now) interrupt frequency, 1kHz, MCU wake up/sleep cycles have been produced at same rate. MCU can't sleep more than 1ms so power consumption isn't as low as It could be.

I haven't tried to change RTOS tick frequency but I think, It could be lowered, maybe, to 100Hz and power comsuption using idle hook would be lower too.

Now, following an example from Atmel, I have been trying to implement portSUPRESS_TICK_AND_SLEEP feature, but, today, I haven't success yet.

If anyone can or want to add anything, It will be appreciated.

jmpmscorp commented 4 years ago

Hi again.

After a long figth, I have sleep power FreeRTOS feature working. I have finally portSUPRESS_TICK_AND_SLEEP working implementation. I have uploaded it to github here https://github.com/jmpmscorp/Arduino-FreeRTOS-SAMD21-Tickless-Idle. Feel free to test it and comment anything. I have added three examples.

@BriscoeTech. If you want and you are free, test it. If you like it, we can try to integrate together. We have the inconvenience that Arduino hasn't any configuration tool and I can't find now how we can configure library to use or not tickless feature but, maybe, you know any way.

nbarlowhall commented 3 years ago

Hi. I know this is an old thread but its the right one to post to so I hope someone is still looking.

I took the work from @jmpmscorp and integrated it into the latest @BriscoeTech release using the portSUPRESS_TICK_AND_SLEEP method described in FreeRTOS. It works.. to an extent... The CPU schedules and runs and when idle it goes to deep sleep and I can get an Arduino MKRFOX1200 down to 970uA (still seems high but its a good start...) But after running for a while it locks up. Best I can tell it goes into deepsleep and never returns. (yes, a watchdog timer will recover but that's not a proper fix.)

So I was wondering if you had had a similar issue and resolved it or could cast any light on what the problem might be. If it makes sense I can push the code to a branch or just copy cut and paste the code changes but it's really just a straight integration of the 2 branches from @jmpmscorp and @BriscoeTech

Feels like this would be a significant next step for this port so would be great to get it working fully. Thoughts?

robynjayqueerie commented 3 years ago

Currently I have moved on to other things so I can't comment here at the moment