Waking up Tracker One using the External VIN Power Source

I'm planning on putting a Tracker One on a system with an On/Off Switch. The goal is to sleep when the power is off, wake up and run the show when the power is on. I'm sure you can wake up the Tracker from sleep by applying power to it's single GPIO pin, but we'd like to use that elsewhere in the system if possible.

Is there a way to wake up the system when the power source is changed? My first gut is to sleep for timed intervals and do a check on the vitals (which I have no clue how to do yet) and see what the power source is. I just got API connectivity going to see that we can at least differentiate between VIN and Battery as a source, which is great. I'm assuming I have that access to that from the battery chip.

Ultimately, though, having to balance a timed sleep with device responsiveness is kind of yucky.
Anyone have luck sleeping based on input power source conditions?

You should be able to use the TrackerSleep class to register a pin wake on PMIC_INT. That interrupt should fire when power is applied.

1 Like

Sweet. That's exactly what I needed to know. Thanks!

I'm been messing around with the PMIC_INT pin as a wakeup pin. One of the issues I've run into is testing it, the main thing is trouble finding context behind the pin.
On a print statement with the PMIC_INT, I get a "23" coming from it. This seems to be the case with 12V on the VIN pins as well as USB power.

Ultimately, I'm trying to operate my loop in a switch case method that looks at whether I've got external power or not and shifts to different modes of operation. Really triming it down, here's some psudocode that conveys what I'd hope to do.

setup()
TrackerSleep::instance().wakeFor(PMIC_INT, RISING);
loop()
if (!PMIC_INT){
ParticleCase = 0; //sleep
TrackerSleep::instance().wakeAtMilliseconds(millis()+SleepDuration);
} else {
digitalWrite(D3_RELAY, HIGH); //click
delay(5000);
digitalWrite(D3_RELAY, LOW); //clack
}

In reality, we're switching between VIN to BATT for power, which I know is shown in vitals. If that were accessible, it could be used for state management, but I'd really like to have some sort of pin that wakes on getting power.

PMIC_INT is a pin number, apparently 23.

What you need to do is use wakeupReason() to see if you woke by GPIO. If you have more than one pin wake GPIO, you can check if wakeupPin() == PMIC_INT.

That makes total sense that it's a pin number. I get what you're saying. So with that in mind, I should be able to do a digitalRead on it I think.
Assuming that's true, I'm having trouble getting is when to tell whether my power has been disconnected. Doing a read on the PMIC_INT still gives me a 1/true at all points, even when I unplug my USB and VIN power. I haven't been able to get a condition where I have a 0/false on it. Is there a condition aside from total battery loss where it gets set to 0?

If the answer is 'sleepMode', Is there an ability to look at what the power source on the Tracker One in your loop?

Now that I've thought about it some more, something like this would probably work better. This compiles but I did not run it on a device:

/*
 * Copyright (c) 2020 Particle Industries, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "Particle.h"
#include "tracker_config.h"
#include "tracker.h"

SYSTEM_THREAD(ENABLED);
SYSTEM_MODE(SEMI_AUTOMATIC);

#if TRACKER_PRODUCT_NEEDED
PRODUCT_ID(TRACKER_PRODUCT_ID);
#endif // TRACKER_PRODUCT_NEEDED
PRODUCT_VERSION(TRACKER_PRODUCT_VERSION);

STARTUP(
    Tracker::startup();
);

SerialLogHandler logHandler(115200, LOG_LEVEL_TRACE, {
    { "app.gps.nmea", LOG_LEVEL_INFO },
    { "app.gps.ubx",  LOG_LEVEL_INFO },
    { "ncp.at", LOG_LEVEL_INFO },
    { "net.ppp.client", LOG_LEVEL_INFO },
});

int lastPowerSource = POWER_SOURCE_UNKNOWN;

void setup()
{
    // Wake for power change
    TrackerSleep::instance().wakeFor(PMIC_INT, FALLING);

    Tracker::instance().init();
}

void loop()
{
    Tracker::instance().loop();

    // Code to check if power source changed
    int currentPowerSource = System.powerSource();
    if (currentPowerSource != POWER_SOURCE_UNKNOWN) {        
        if (currentPowerSource != lastPowerSource) {
            lastPowerSource = currentPowerSource;

            if (currentPowerSource == POWER_SOURCE_BATTERY) {
                Log.info("is now battery powered");        
            }
            else {
                Log.info("is now externally powered (%d)", currentPowerSource);
            }
        }
    }
}

Seems like it should work fine. I got it flipping between 'states' for me based on battery conditions, but am currently stuck in working with the TrackerSleep API, which is a little less straightforward than the normal system.sleep.
If we got into battery, I've got it on call to 'resumeSleep', which doesn't immediately drop it into sleep like I had expected. I initially also thought it would sleep for a duration using TrackerSleep::instance().wakeAtMilliseconds(System.millis()+SleepDuration);
But it seems that's more of a specifier for when it goes to sleep, which seems to be managed externally via fleet settings in the cloud.

Unfortunately, I've not been able to manage my fleet settings because of some firmware versioning issues. I'll make a new topic for that.

There were a few lessons I learned. If anything seems off, let me know.

  • PNIC_INT doesn't trip upon USB plug-in. To trip the PNIC_INT, you need to supply power over the VIN pin. In my case, it was 12V, but it woke it up near immediately.
  • I've been managing the devices 'state' int (0 = sleep, 100 = normal...) and found the System.powerSouce() very helpful for shifting state and shifting to sleep accordingly. The enumerations for power sources for were very helpful. If we were on POWER_SOURCE_BATTERY or POWER_SOURCE_UNKNOWN, for instance, we would switch to a timed shutting down state, eventually ending with resume sleep.
  • It was a bit of a learning curve with the default being 'pause' and 'resume'. There were a number of times where I tried to publish as a last message and the device slept before that was published. Perhaps conflicting with that want - it's also interesting that sleep isn't instant. I've got to keep it in a state until it falls asleep on its own, but also move on from that same state when it wakes up to publish a message.
1 Like

@rickkas7
I may have spoken too soon. In my light testing yesterday, I might have actually timed it perfectly with a scheduled sleep wakeup. Today on mucking with things further, I can't seem to get the PMIC_INT to shift up in value reliably. When put to sleep, it seems to sleep for ~15 minutes of the 60 minute sleep time after applying power before actually waking up.

Is there an option to look at the value of the charge LED for a rise? It doesn't look like CHRG is in the pinmap definitions, so I'm kind of doubting this for now. I don't know if there are other steps needed to drop the PMIC_INT pin while going to sleep. I've tried both unplugging the M8 Pin and dropping out the 12V power supply we're using for some time.

PMIC_INT will only change state for a short period of time on the order of 100 milliseconds or something like that for non-fault conditions. And it should trigger for PGOOD, which should include USB power.

The charge LED is only connected to the LED, but you can check the status of charging from the PMIC. However, you should not do that instead only use the charging state from the system power manager because if the battery is disconnected the PMIC will go into charging state dozens of times per second.

Alright, sounds CHRG is out of the question. I'm just trying to look for a reliable alternative before I resort to more complex solutions (maybe a CAN message sent from an arduino on the same CAN network...)

I'm still testing the PMIC_INT, but it doesn't seem to trigger a Wake reliably, which is frustrating. I don't know if the system isn't catching it or what. It's taken 15 minutes to wake up prior with direct 12V, it was 12 minutes this last time. Regardless, it's not catching the pin rising until way after the fact. Turning the power on and off doesn't trigger anything.

Would it help to see if we can wakeup via the GPIO pin or something? The device is pretty wired in, but I could probably test it to see if that would still work.

Oh, wait. How do you know it's not waking? And what is your minimum wake cycle set to in the console?

When you wake for an interrupt, if the elapsed time since the last full wake cycle (with publish) has not occurred yet, then a short wake cycle is done. The device will wake, but not connect to the cloud and go right back to sleep. See wakeForPin.

You can change this behavior - if you were wakened for a pin interrupt and you're now on power, for example, you could do a forceFullWakeCycle().

That's a good question. I was judging its awakeness by the GPS & Cloud LEDs being on and the ability to connect to it via usb serial.

Looking at the minimum wake cycle, it could be waking up to some extent. I haven't set the minimum wake cycle to anything but the default. That said, it should wake up to a loop where it checks the power source and (if I've coded it right) pauses sleep. It sounds like that won't push it to a full wake cycle though? If it only scans the code once, it might not even get back to the switch case to run that specific pause code. I haven't quite gotten what extent of code actually runs during a partial wake

On the console, I set Maximum location update frequency (every n seconds)* was set previously to 900s. I changed it to 15s, which might be too often knowing the consequences of too many full wake cycles too often. I also set in setup() to force full wake cycles. It's still hasn't woken up yet, from forceFullWakeCycle() alone. It actually woke itself up without having power applied a second ago, so maybe that's sort of weird as well.
Worth noting is that it's probably the settings, as you mentioned. I'll plunk around, but assume that's the likely issue.

Sorry to keep asking questions about this, but I'm still having issues waking up on the interrupt. For settings:

  • I set Max Location Update Frequency to 20s.
  • I set Minimum Location Update Frequency to 1h or 3600s.
  • In my WakeCallback, I have TrackerSleep::instance().forceFullWakeCycle(); Not sure if this is the right place for it or if I need to keep re-using the function. I've also tried putting it in the setup() section to no avail.
  • I set TrackerSleep::instance().wakeFor(PMIC_INT, RISING); as my wakeup. It doesn't generate a Full Wake Cycle to my knowledge.
  • Sleep mode is enabled, Post-publish execution is 5s, Maximum Connecting Time is 90s
  • I initially had TrackerSleep::instance().wakeAtMilliseconds(), but I realize now that the Minimum Location Update should probably trigger the wakeup for a long sleep.

Is there something I'm missing from a wakeup perspective to get a full wakeup cycle from the PMIC_INT?

It took me a while to come to terms, but the PMIC_INT pin isn't working as hoped (really at all as a 'wake-up'). The Tracker One wakes up great with the extra GPIO pin, not at all with the PMIC_INT I've found.

We were anticipating using the GPIO for a relay to trip the rest of the system, but I think we'll have to somehow tap off of the CAN_PWR instead to get that moved ahead. Shame that the GPIO seems to be the best way to wake up when it's kind of a commodity as is.

Did you ever test it with FALLING instead of RISING? It just occurred to me that it probably will not work with RISING. The bq24195 interrupt output is open collector. The Tracker SoM has a 10K hardware pull-up to 3V3 on PMIC/INT.

When you attach a RISING interrupt to a pin, a software pull-down is applied to the pin. On the nRF52, this is 13K. This essentially makes a voltage divider which may prevent the signal from ever going high.

If you use a FALLING interrupt, a software pull-up is applied, so this will be in parallel with the 10K hardware pull-up, so the two will not fight. If you use CHANGE, no software pull is applied.

I finally got around to testing this and YES. FALLING is the correct choice!

Called in Setup, the following will wake that particle up for a full cycle. In our case, we have a physical switch that was cutting power, which we wanted to prompt going into a sleep state.

TrackerSleep::instance().wakeFor(PMIC_INT, FALLING);

As noted above in the post:

  • The minimum location update frequency should be higher than your sleep interval.
  • Tracker Sleep needs to be resumed (assuming you might pause it during normal operations).
  • User caution: On wake callback, add TrackerSleep::instance().forceFullWakeCycle(); if there's a chance someone might immediately want to switch back to normal operation.

Thanks again @rickkas7, super excited that I got this to work.

2 Likes