Power.Sleep() Fails to wake on GPIO Interrupt

I have a problem where the SC20260N fails to wake on GPIO interrupt after a certain time. The test procedure is quite simple, put the system into Hibernate/Sleep with a button press, try and wake it up after a set time with another button press. Hardware basically dates back to the original EMX design with Up/Down/Right/Left/Select buttons, all set as inputs with pullups, falling edge interrupt trigger.

After extensive testing I’ve found that with the time between entering Sleep mode and pressing a button to wake it up of:

<= 4 min 10s   Always wakes up on button press
>= 4 min 15s   Never wakes
Between 4:10 and 4:15 Indeterminate - sometimes wakes, sometimes not

The board is in WinUSB device mode at the time, and the reason I think it may have something to do with that is that when it is in the indeterminate time range and does NOT wake up, the USB disconnects/connects from the PC, with the characteristic sound and double refresh of Device Manager. From this it would seem that the button interrupt is detected at some level but does not propagate through to fully waking the system so that the CLR resumes.

Accordingly this more serious problem may be relevant the one in to my previous thread:

Hi, we put it into our list and will look into soon.

Sleep doesn’t wake up after 4m:15s · Issue #1235 · ghi-electronics/TinyCLR-Libraries (github.com)

1 Like

Our first batch of meters using SitCore and TinyCLR have just arrived in Iceland, and I need to get working code to them ASAP.

I’ve written a workaround using a wrapper function that wakes up every 4 minutes if required so as to avoid the problem. It does have the side-effect of a USB insert/remove sound from the PC every 4 minutes if plugged in, but I doubt our users will mind.

It also syncs the System Time to the RTC, and has a return value that indicates whether the wakeup was from a button-press or timeout. I had to use a global variable to get this information - luckily the GPIO interrupt that causes the wakeup is still passed through - but it would help if the system provided it.
If not in a return value, then perhaps in a function similar to Power.GetResetSource(), perhaps Power.GetWakeupSource()?

    public override int Hibernate(int seconds = 60 * 60)
    {
        var rtc = RtcController.GetDefault();
        var StartTime = DateTime.Now;
        Globals.ButtonPressed = false;
        while(seconds > 0) 
        {
            // Wake up every 4 minutes to get around bug in GHI firmware
            int SecondsToSleep = (seconds > 4 * 60) ? 4 * 60 : seconds;
            Power.Sleep(rtc.Now.AddSeconds(SecondsToSleep));
            SystemTime.SetTime(rtc.Now);
            if (Globals.ButtonPressed) break;
            seconds -= SecondsToSleep;
        }
        Debug.WriteLine("Slept for " + (rtc.Now - StartTime).TotalSeconds + " Seconds");

        return seconds; // Zero if timed out, else buttonpush (and equals seconds before scheduled timeout)
    }

@Dat_Tran The above workaround still had problems, in the main program unrelated threads started throwing exceptions after resuming from sleep. The most obvious was the driver for the LCD, where the GPIO on the RS line decided it wasn’t actually a valid IO anymore.
Could it be that the system sees variables as out of scope after a certain time in Sleep mode, and they are being garbage collected?
I’ve been trying to whittle down to a minimal example, starting from the code I used to demonstrate the WinUSB/MS switch problem, and the working 1 second Sleep workaround. In this case it decides the the RtcController isn’t actually an RtcControler after a few Sleeps.

The program is basically

        private static RtcController rtc;

        static void Main()
        {
            rtc = RtcController.GetDefault();
            while (true)
            {   
                Thread.Sleep(100);
                if(WantSleep)
                {
                    WantSleep = false;
                    Debug.WriteLine("Now: " + DateTime.Now + " Rtc: " + rtc.Now + " SleepUntil: " + wTime);
                    MySleep2(270);
                    Debug.WriteLine("AwakeAt: " + DateTime.Now + " Rtc: " + rtc.Now);
                }
            }
        }

        private static int MySleep2(int seconds)
        {
            var rtc = RtcController.GetDefault();
            var StartTime = DateTime.Now;
            ButtonPressed = false;
            const int MaxSleep = 20; // 4 * 60;
            while (seconds > 0)
            {
                int SecondsToSleep = (seconds > MaxSleep) ? MaxSleep : seconds;
                Debug.WriteLine("Sleeping for: " + SecondsToSleep + "s of " + seconds);
                DateTime dt = rtc.Now;
                Debug.WriteLine("Sleep from " + dt + " (" + DateTime.Now + ")  to " + rtc.Now.AddSeconds(SecondsToSleep));
                var tStart = rtc.Now.Ticks;
                Power.Sleep(rtc.Now.AddSeconds(SecondsToSleep));
                var tEnd = rtc.Now.Ticks;
                Debug.WriteLine("Slept for " + (tEnd - tStart) / TimeSpan.TicksPerMillisecond + " mS");
                SystemTime.SetTime(rtc.Now);
                if (ButtonPressed) break;
                seconds -= SecondsToSleep;
                Debug.WriteLine("Sleep to go: " + seconds);
            }
            Debug.WriteLine("Slept for " + (rtc.Now - StartTime).TotalSeconds + " Seconds");

            return seconds; // Zero if timed out, else buttonpush (and equals seconds before scheduled timeout)
        }

With WantSleep and ButtonPressed set from a couple of button interrupts.

Debug output is as expected for a while, until suddenly RtcController is the wrong type?

Down Button!
Now: 01/30/2023 08:35:12 Rtc: 01/30/2023 08:35:13 SleepUntil: 01/30/2023 08:35:18
Sleeping for: 20s of 270
Sleep from 01/30/2023 08:35:13 (01/30/2023 08:35:12)  to 01/30/2023 08:35:33
Slept for 21000 mS
Sleep to go: 250
Sleeping for: 20s of 250
Sleep from 01/30/2023 08:35:34 (01/30/2023 08:35:34)  to 01/30/2023 08:35:54
Slept for 21000 mS
Sleep to go: 230
Sleeping for: 20s of 230
Sleep from 01/30/2023 08:35:55 (01/30/2023 08:35:55)  to 01/30/2023 08:36:15
Slept for 21000 mS
Sleep to go: 210
Sleeping for: 20s of 210
Sleep from 01/30/2023 08:36:16 (01/30/2023 08:36:16)  to 01/30/2023 08:36:36
Slept for 21000 mS
Sleep to go: 190
Sleeping for: 20s of 190
Sleep from 01/30/2023 08:36:37 (01/30/2023 08:36:37)  to 01/30/2023 08:36:57
Slept for 21000 mS
Sleep to go: 170
Sleeping for: 20s of 170
Sleep from 01/30/2023 08:36:58 (01/30/2023 08:36:58)  to 01/30/2023 08:37:18
Slept for 21000 mS
Sleep to go: 150
Sleeping for: 20s of 150
Sleep from 01/30/2023 08:37:19 (01/30/2023 08:37:19)  to 01/30/2023 08:37:39
Slept for 21000 mS
Sleep to go: 130
Sleeping for: 20s of 130
Sleep from 01/30/2023 08:37:40 (01/30/2023 08:37:40)  to 01/30/2023 08:38:00
Slept for 21000 mS
Sleep to go: 110
Sleeping for: 20s of 110
Sleep from 01/30/2023 08:38:01 (01/30/2023 08:38:01)  to 01/30/2023 08:38:21
Slept for 21000 mS
Sleep to go: 90
Sleeping for: 20s of 90
Sleep from 01/30/2023 08:38:22 (01/30/2023 08:38:22)  to 01/30/2023 08:38:42
Slept for 21000 mS
Sleep to go: 70
Sleeping for: 20s of 70
Sleep from 01/30/2023 08:38:43 (01/30/2023 08:38:43)  to 01/30/2023 08:39:03
Slept for 21000 mS
Sleep to go: 50
Sleeping for: 20s of 50
Sleep from 01/30/2023 08:39:04 (01/30/2023 08:39:04)  to 01/30/2023 08:39:24
Slept for 21000 mS
Sleep to go: 30
Sleeping for: 20s of 30
Sleep from 01/30/2023 08:39:25 (01/30/2023 08:39:25)  to 01/30/2023 08:39:45
Slept for 21000 mS
Sleep to go: 10
Sleeping for: 10s of 10
Sleep from 01/30/2023 08:39:46 (01/30/2023 08:39:46)  to 01/30/2023 08:39:56
Slept for 11000 mS
Sleep to go: 0
Slept for 284.8318526 Seconds
    #### Exception System.Exception - CLR_E_WRONG_TYPE (1) ####
    #### Message: 
    #### GHIElectronics.TinyCLR.Devices.Rtc.RtcController::get_IsValid [IP: 0007] ####
    #### GHIElectronics.TinyCLR.Devices.Rtc.RtcController::get_Now [IP: 0004] ####
Exception thrown: 'System.Exception' in GHIElectronics.TinyCLR.Devices.Rtc.dll
An unhandled exception of type 'System.Exception' occurred in GHIElectronics.TinyCLR.Devices.Rtc.dll

    #### Exception System.Exception - CLR_E_WRONG_TYPE (5) ####
    #### Message: 
    #### GHIElectronics.TinyCLR.Devices.Rtc.RtcController::get_IsValid [IP: 0007] ####
    #### GHIElectronics.TinyCLR.Devices.Rtc.RtcController::GetTime [IP: 0004] ####
 Uncaught exception 
    #### Exception System.Exception - CLR_E_WRONG_TYPE (7) ####
    #### Message: 
    #### GHIElectronics.TinyCLR.Devices.Rtc.RtcController::get_BackupMemorySize [IP: 0007] ####
 Uncaught exception 
    #### Exception System.Exception - CLR_E_WRONG_TYPE (8) ####
    #### Message: 
    #### GHIElectronics.TinyCLR.Devices.Rtc.RtcController::get_InternalRC [IP: 0007] ####
 Uncaught exception 
    #### Exception System.Exception - CLR_E_WRONG_TYPE (9) ####
    #### Message: 
    #### GHIElectronics.TinyCLR.Devices.Rtc.RtcController::get_IsValid [IP: 0007] ####
 Uncaught exception 
    #### Exception System.Exception - CLR_E_WRONG_TYPE (10) ####
    #### Message: 
    #### GHIElectronics.TinyCLR.Devices.Rtc.RtcController::get_IsValid [IP: 0007] ####
    #### GHIElectronics.TinyCLR.Devices.Rtc.RtcController::GetTime [IP: 0004] ####
 Uncaught exception 

Hopefully the above information will give you some clues to what is going wrong, if there is any more I can help with please let me know.
However I can’t spend more time on it just now, as I’ll be doing on-site commissioning on some of our other equipment at another plant (Australia this time) over the coming week.
As they have expressed interest in upgrading their meters to the new SitCore boards too, I really want to get some runs on the board with Iceland ASAP so we can hold them out as an example.

Hi, I think there is something wrong with your code or you didn’t post full code. I see you called

var rtc = RtcController.GetDefault();

and use “Power.Sleep(rtc.Now.AddSeconds(…));”

It is OK if rtc was set or valid before, if not then this could be an issue.

Anyway, below is full project to test your issue. We did test 4min, 4m15s, 4m20s… and all case are just fine . Please run and let us know.

public class Program
    {
        const int LED1 = SC20260.GpioPin.PH11; // PB0
        const int LDR1 = SC20260.GpioPin.PE3; // PE3;
        const int APP = SC20260.GpioPin.PB7; // PB7;

        static GpioPin led1;
        static GpioPin buttonInterrupt;
        static GpioPin buttonEnterSleep;

        static void Main()
        {
            var controller = GpioController.GetDefault();

            led1 = controller.OpenPin(LED1);
            buttonInterrupt = controller.OpenPin(LDR1);
            buttonEnterSleep = controller.OpenPin(APP);

            led1.SetDriveMode(GpioPinDriveMode.Output);

            buttonInterrupt.SetDriveMode(GpioPinDriveMode.InputPullUp);
            buttonEnterSleep.SetDriveMode(GpioPinDriveMode.InputPullUp);

            buttonInterrupt.ValueChanged += Ldr1_ValueChanged;

            int cnt = 0;
            while (buttonEnterSleep.Read() == GpioPinValue.High)

            {

                led1.Write(led1.Read() == GpioPinValue.Low ? GpioPinValue.High : GpioPinValue.Low);

                var dt = DateTime.Now;

                Thread.Sleep(25);

                cnt++;


                if (cnt % 10 == 0)
                {                    

                    Debug.WriteLine("Press button APP to Enter sleep");

                }
            }

            while (buttonEnterSleep.Read() == GpioPinValue.Low) ;            

            DoTestWakeup();

            while (true)
            {
                Debug.WriteLine("Run from wakeup ");
                Thread.Sleep(250);
                led1.Write(led1.Read() == GpioPinValue.Low ? GpioPinValue.High : GpioPinValue.Low);
            }

        }

        private static void Ldr1_ValueChanged(GpioPin sender, GpioPinValueChangedEventArgs e)
        {
            Debug.WriteLine("Found interrupt");
        }

        static void DoTestWakeup()
        {                        
            Debug.WriteLine("Entering wakeup ");

            var st1 = new DateTime(2023, 1, 1, 0, 0, 0);

            var rtc = RtcController.GetDefault();

            var rtcDateTime = RtcDateTime.FromDateTime(st1);

            rtc.SetTime(rtcDateTime);

            SystemTime.SetTime(st1);

            Power.WakeupEdge = WakeupEdge.Rising;

            Power.Sleep(DateTime.Now.AddSeconds(4*60 + 20));
            
        }
    }