NuMaker tickless example

main.cpp

Committer:
ccli8
Date:
2017-09-12
Revision:
1:eb1da9d36e12
Parent:
0:d7c3d9c9dbe4
Child:
2:dbe00b3a7545

File content as of revision 1:eb1da9d36e12:

#include "mbed.h"
#include <limits.h>
#include <vector>

#include "wakeup.h"

static void flush_stdio_uart_fifo(void);
static void check_wakeup_source(uint32_t, bool deepsleep);
static void idle_daemon(void);

#if (MBED_MAJOR_VERSION <= 5 && MBED_MINOR_VERSION <= 5 && MBED_PATCH_VERSION <= 6)
EventFlags_ wakeup_eventflags;
#else
EventFlags wakeup_eventflags;
#endif

int main() {

    config_pwrctl();
    config_button_wakeup();
    config_wdt_wakeup();
    config_rtc_wakeup();
    /* TODO */
    //config_uart_wakeup();
    //config_i2c_wakeup();
    
    /* Register idle handler which supports tickless */
    Thread::attach_idle_hook(idle_daemon);
    
    while (true) {
        
        printf("I am going to shallow/deep sleep\n");
        /* Flush STDIO UART before entering Idle/Power-down mode */
        fflush(stdout);
        flush_stdio_uart_fifo();
        
        /* Wait for any wake-up event */
        uint32_t flags = wakeup_eventflags.wait_any(EventFlag_Wakeup_All, osWaitForever, true);
        if (flags & osFlagsError) {
            if (flags != osFlagsErrorTimeout) {
                printf("OS error code: 0x%08lX\n", flags);
            }
            continue;
        }

        /* EventFlag_Wakeup_UnID is set in PWRWU_IRQHandler to indicate unidentified wake-up from power-down.
         * It could be deferred. Wait for it. */
        uint32_t flags2 = wakeup_eventflags.wait_any(EventFlag_Wakeup_UnID, 100, true);
        if (flags2 & osFlagsError) {
            if (flags2 != osFlagsErrorTimeout) {
                printf("OS error code: 0x%08lX\n", flags2);
            }
            else {
                flags2 = 0;
            }
        }
        flags |= flags2;
        
        /* EventFlag_Wakeup_UnID means unidentified wake-up source from power-down.
         *
         * If EventFlag_Wakeup_UnID is set, we wake up from power-down mode (deep sleep) but its wake-up
         * source is not yet identified. Wait for it to be identified if no other wake-up sources have been 
         * identified.
         * 
         * If EventFlag_Wakeup_UnID is not set, we wake up from idle mode (shallow sleep). Wake-up source
         * would have been identified.
         */
        if (flags == EventFlag_Wakeup_UnID) {
            uint32_t flags3 = wakeup_eventflags.wait_any(EventFlag_Wakeup_All & ~EventFlag_Wakeup_UnID, 100, true);
            if (flags3 & osFlagsError) {
                if (flags3 != osFlagsErrorTimeout) {
                    printf("OS error code: 0x%08lX\n", flags3);
                }
                else {
                    flags3 = 0;
                }
            }
            flags |= flags3;
        }
        
        /* Clear wake-up flags caused by short time EventFlags::wait_any() above. These wake-up flags are 
         * just for program control and not actual wake-up events we want to track. This has a side effect of
         * losing actual wake-up events in this short time. */
        wakeup_eventflags.clear();
        
        bool deepsleep = (flags & EventFlag_Wakeup_UnID) ? true : false;

        /* Remove EventFlag_Wakeup_UnID if any other wake-up source is identified */
        if ((flags & ~EventFlag_Wakeup_UnID)) {
            flags &= ~EventFlag_Wakeup_UnID;
        }
        check_wakeup_source(flags, deepsleep);
        
        printf("\n");
    }
    
    return 0;
}

void flush_stdio_uart_fifo(void)
{
    UART_T *uart_base = (UART_T *) NU_MODBASE(STDIO_UART);
    
    while (! UART_IS_TX_EMPTY(uart_base));
}

void check_wakeup_source(uint32_t flags, bool deepsleep)
{
    typedef std::pair<uint32_t, const char *> WakeupName;
    
    static const WakeupName wakeup_name_arr[] = {
        WakeupName(EventFlag_Wakeup_Button1, "Button1"),
        WakeupName(EventFlag_Wakeup_Button2, "Button2"),
        WakeupName(EventFlag_Wakeup_LPTicker, "lp_ticker"),
        WakeupName(EventFlag_Wakeup_WDT_Timeout, "WDT timeout"),
        WakeupName(EventFlag_Wakeup_RTC_Alarm, "RTC alarm"),
        WakeupName(EventFlag_Wakeup_UART_CTS, "UART CTS"),
        WakeupName(EventFlag_Wakeup_I2C_AddrMatch, "I2C address match"),
        
        WakeupName(EventFlag_Wakeup_UnID, "Unidentified"),
    };
    
    const char *sleep_mode = deepsleep ? "deep sleep" : "shallow sleep";
    
    if (flags) {
        const WakeupName *wakeup_name_pos = wakeup_name_arr;
        const WakeupName *wakeup_name_end = wakeup_name_arr + (sizeof (wakeup_name_arr) / sizeof (wakeup_name_arr[0]));
        
        for (; wakeup_name_pos != wakeup_name_end; wakeup_name_pos ++) {
            if (flags & wakeup_name_pos->first) {
                printf("Wake up by %s from %s\n", wakeup_name_pos->second, sleep_mode);
            }
        }
    }
}

#define US_PER_SEC              (1000 * 1000)
#define US_PER_OS_TICK          (US_PER_SEC / OS_TICK_FREQ)

void dummy_cb(void) {}

void idle_daemon(void) {
    
    const int max_us_sleep = (INT_MAX / OS_TICK_FREQ) * OS_TICK_FREQ; 
    /* Keep track of the time asleep */
    LowPowerTimer asleep_watch;
    /* Will wake up the system (by lp_ticker) if no other wake-up event */
    LowPowerTimeout alarm_clock;

    /* Never ends. The rtos will suspend this thread when there is something to do
     * either before osKernelSuspend actually suspend the system (and is not in svc) 
     * or immediately after osKernelResume. */
    while (true) {
        /* Suspend the system */
        uint32_t ticks_to_sleep = osKernelSuspend();
        uint32_t elapsed_ticks = 0;

        if (ticks_to_sleep) {
            uint64_t us_to_sleep = ticks_to_sleep * US_PER_OS_TICK;

            if (us_to_sleep > max_us_sleep) { 
                us_to_sleep = max_us_sleep;
            }

            /* Start the asleep_watch and setup the alarm_clock to wake up the system in us_to_sleep */
            asleep_watch.start();
            alarm_clock.attach_us(dummy_cb, us_to_sleep);

            /* Go to deep sleep */
            deepsleep();
            
            /* Woken up by lp_ticker or other wake-up event */
            int us_asleep = asleep_watch.read_us();

            /* Clean up asleep_watch and alarm_clock */
            asleep_watch.stop();
            asleep_watch.reset();
            alarm_clock.detach();

            /* Translate us_asleep into ticks */
            elapsed_ticks = us_asleep / US_PER_OS_TICK;
        }

        /* Resume the system */
        osKernelResume(elapsed_ticks);
    }
}