Does waking up a sensor reset the device?

I am working on a project with HM3301 dust sensor (I2C). To save power, my Boron will wake up, take sample and go to sleep. To prevent the sensor from sourcing current when the system is in sleep mode, I found that HM3301 is providing a nice feature to put the dust sensor into sleep.

So, I connected a wire from one of the pins (D7) to the “set pin” of the dust sensor. But as soon as I set the pin to from LOW to HIGH (essentially waked up the sensor), the system seemed to reset.

I can see the cyan LED turned to flashing green once “TESTING 3” is published (and “TESTING 4” is never published). Does waking up a sensor resets the device?

@Steveliu29 ,

The short answer is “no, waking up a sensor should not reset the device”. But, that may well be what is happening here. I don’t have this sensor but here are a few things you might look at:

You could add this code to your sketch to get more information on the cause of the reset:

// before setup - enable feature
STARTUP(System.enableFeature(FEATURE_RESET_INFO));
// Enable logging as we ware looking at messages that will be off-line - need to connect to serial terminal
SerialLogHandler logHandler(LOG_LEVEL_INFO);


// In setup, you can then look at the reset reason:
if (System.resetReason() == RESET_REASON_PIN_RESET) {
    Log.info("Restarted due to a pin reset");
  }
else if (System.resetReason() == RESET_REASON_USER) { // Check to see if we are starting from a pin reset or a reset in the sketch
    Log.info("Restarted due to a user reset");
  }
else Log.info("System reset reason %i", System.resetReason());

Full documentation here: Device OS API | Reference Documentation | Particle

Once you know the reset reason, we may be able to help more.

One thing to look at too is power. Depending on how you are powering your Boron, the sensor may be drawing too much current at startup and causing a reset due to low voltage. This is just a guess however and you will see “BROWNOUT” or “POWER DOWN” as the reasons from the code above.

Hope this helps,

Chip

2 Likes

Hello,

Thanks for your help! I have tried your code and got the following result. The result shows the reset reason is 0. What might cause this problem? I am new to Boron, and I can’t really figure out the reason behind.

FYI, on my web console side, “T4” is never seen. That’s why I was guessing write a “HIGH” to wake up the sensor will cause a reset to the system.

Thanks again.

@Steveliu29 ,

OK, let’s try a few more things since a reset code of “0” is not that helpful.

First, it would be helpful if you posted your code here in its entirety using the ``` before and after your code block

Second, to minimize the possibility of messages being lost, it may be easier to simply convert your Particle.publishes to Log.info then your delays can be minimized.

Third, Particle code does not like to be blocked from transiting the main loop frequently. It is possible that the cumulative effect of your 10 second delays may be an issue. You may want to use a delay like this one which will ensure that Particle functions are serviced. If this was production code, you would want to write it so the main loop is not blocked.


void safeDelay(unsigned long delayMillis) { // Ensures Particle functions operate during long delays
   unsigned long timeStamp = millis();
   while (millis() - timeStamp < delayMillis) {
      delay(10);
      Particle.process();
   }
}

Finally, how are you powering your setup? In particular, what voltage are you providing to the dust sensor and the Boron? While this sensor has low power usage, it can draw up to ~200mA peak at 3.3V. Power outages are reset code 40 but I would still not rule out a power issue.

Hope this is helpful and let us know how you are doing.

Thanks,

Chip

2 Likes

Hello,

Sorry for the late reply. Firstly, thank you so much for your advice!

Below is my code. I apologize in advance for my terrible coding style (it is not in production right now, and I am still testing the integration of sensor and Boron).

void setup() {
    // In setup, you can then look at the reset reason:
    if (System.resetReason() == RESET_REASON_PIN_RESET) {
        Log.info("Restarted due to a pin reset");
    } else if (System.resetReason() == RESET_REASON_USER) { // Check to see if we are starting from a pin reset or a reset in the sketch
        Log.info("Restarted due to a user reset"); 
    } else 
        Log.info("System reset reason %i", System.resetReason());

    
    pinMode(DUST_SENSOR, OUTPUT);
    digitalWrite(DUST_SENSOR, HIGH);

    // Wait for the sensor stablizes after its first boot up
    delay(10000);
    
    if (sensor.init()) {
        Particle.publish("Init Failed");
        while (1);
    } else {
        Particle.publish("Init Success");
    }

}

void loop() {  
    
    Data new_sample;
    
    delay(30000); // The sensor needs 30s to stablize

    // Read sensor 5 times, 1 second delay in between reads
    for (int i = 0; i < 5; i++){
		if (sensor.read_sensor_value(buf, 29)) {
            if (Particle.connected()){
        	    Particle.publish("Read Result Failed");
            }
    	}
        parse_result(buf, new_sample);
		delay(1000);
    }

    // Average out the readings
    for (int i = 0; i < 3; i++){
	new_sample.ae_value[i] = new_sample.ae_value[i] / 5;
    }

    // Get the time stamp
    new_sample.day = Time.day();
    new_sample.hour = Time.hour();
    new_sample.minute = Time.minute();
    sensor_record.emplace_back(new_sample);

    sample_counter = sample_counter + 1;

    // Publish records all at once every day
    if (sample_counter == MAX_SAMPLE){

        publish_daily_record();
        sample_counter = 0;
    }
   
    digitalWrite(DUST_SENSOR, LOW);

    delay(10000);

    // ISSUE: Device resets at this line.
    digitalWrite(DUST_SENSOR, HIGH);
    
}

For your second and third answer, I think I will test it out this week, and get back to you as soon as possible.

For the last answer, I am power the Boron with a 3.7V 1800mah battery (the one included in the Boron purchase). The sensor should operate at both 3.3V and 5V (I believe Boron can only provide 3.3V).

Thank you again for your support.

Regards

Unless you are using SYSTEM_THREAD(ENABLED) this will not let the event be published as the while(1); keeps the code flow trapped preventing the system thread from doing it’s job. Particle.publish() only queues the event for the system task doing the heavy lifting.

If you really want your code to block indefinitely you can do this instead

    if (sensor.init()) {
        Particle.publish("Init Failed");
        while(1) Particle.process();
    } else {

I’d also second @chipmc’s recommendation to avoid Particle.publish() for debugging and rather go with a more immediate way like Log.xxx() or Serial.print(). Even with USB Serial output you may need to add some 10-100ms delays after emitting a debug message to have it actually be delivered before a potential crash happens.
With Partilce.publish() the delay would need to be even longer and the system task needs the chance to run at all for the reason outlined above.

With the code snippet from your first post the problem may actually be in sensor.init() preventing the already queued event from the preceding line from actually being published.
This may create the impression that the Particle.publish("TESTING 4") line never was reached when in fact it was but the subsequent code kept the system from delivering it.
Swapping the Particle.publish() and delay(10000) line would help removing this possible scenario from the check list.

BTW, if you intended to post your full code the definitions would also be part of that :wink:
Also, what library are you using for the sensor?

BTW²

When SYSTEM_THREAD(ENABLED) is not used delay() will initiate the cloud process after every 1000ms of accumulated delay time to prevent the cloud connection from breaking and keeping the device somewhat responsive.
So, while not being good practice to us delay() extensively it shouldn’t be causing this kind of trouble.

1 Like

Hello,

Thank you for your advice! I have re-worked my code as suggested by you and @chipmc

#include <Seeed_HM330X.h>
#include <vector>

// #define SERIAL_OUTPUT_ENABLE
// #define TIME_SYNC_ENABLE
// #define SLEEP_ENABLE

#define SLEEP_TIME 30min
#define ONE_DAY_MILLIS (24 * 60 * 60 * 1000)
#define MAX_SAMPLE 48 
unsigned long lastSync = millis();

uint8_t sample_counter = 0;
int DUST_SENSOR = D6;

HM330X sensor;
uint8_t buf[30];

// before setup - enable feature
STARTUP(System.enableFeature(FEATURE_RESET_INFO));
// Enable logging as we ware looking at messages that will be off-line - need to connect to serial terminal
SerialLogHandler logHandler(LOG_LEVEL_INFO);

typedef struct data {
    uint16_t day = 0;
    uint16_t hour = 0;
    uint16_t minute = 0;
    uint16_t ae_value [3] = {0,0,0};
} Data;


std::vector<Data> sensor_record;

const char* str[] = {
                     "PM1.0 concentration(Atmospheric environment,unit:ug/m3): ",
                     "PM2.5 concentration(Atmospheric environment,unit:ug/m3): ",
                     "PM10 concentration(Atmospheric environment,unit:ug/m3): ",
                    };


HM330XErrorCode publish_daily_record() {
    while(!sensor_record.empty()){
        Data data_to_publish = sensor_record.front();
        
        // Some String manipulation
        String to_publish = ((String)data_to_publish.day);
        to_publish.concat(",");
        to_publish.concat((String)data_to_publish.hour);
        to_publish.concat(",");
        to_publish.concat((String)data_to_publish.minute);
        to_publish.concat(",");
        to_publish.concat((String)data_to_publish.ae_value[0]);
        to_publish.concat(",");
        to_publish.concat((String)data_to_publish.ae_value[1]);
        to_publish.concat(",");
        to_publish.concat((String)data_to_publish.ae_value[2]);
        
        delay(1000);

        // Check for connection
        // if YES, transmitted everything in the buffer
        // if NO, leave the current data in the buffer, send it next day
        if (Particle.connected()){
            Particle.publish(to_publish);
            sensor_record.erase(sensor_record.begin());
        } else {
            break;
        }
    }

    return NO_ERR;
}


/*parse buf with 29 uint8_t-data*/
HM330XErrorCode parse_result(uint8_t* data, Data& new_sample) {
    uint16_t value = 0;
    if (NULL == data) {
        return ERROR_PARAM;
    }
    
    // my_record.sensor_no = (uint16_t) data[2] << 8 | data[5];

    for (int i = 5; i < 8; i++) {
        value = (uint16_t) data[i * 2] << 8 | data[i * 2 + 1];

        new_sample.ae_value[i - 5] = new_sample.ae_value[i - 5] + value;
         
        
    }

    return NO_ERR;
}


HM330XErrorCode print_result(const char* str, uint16_t value) {
    if (NULL == str) {
        return ERROR_PARAM;
    }
    Log.info(str + String(value));

    return NO_ERR;
}


void safeDelay(unsigned long delayMillis) { // Ensures Particle functions operate during long delays
   unsigned long timeStamp = millis();
   while (millis() - timeStamp < delayMillis) {
      delay(10);
      Particle.process();
   }
}

SYSTEM_MODE(SEMI_AUTOMATIC);
SYSTEM_THREAD(ENABLED);

void setup() {
    
    pinMode(DUST_SENSOR, OUTPUT);
    digitalWrite(DUST_SENSOR, HIGH);
    safeDelay(10000);

    if (System.resetReason() == RESET_REASON_PIN_RESET) {
        Log.info("Restarted due to a pin reset");
    } else if (System.resetReason() == RESET_REASON_USER) { // Check to see if we are starting from a pin reset or a reset in the sketch
        Log.info("Restarted due to a user reset");
    } else Log.info("System reset reason %i", System.resetReason());


    if (sensor.init()) {
        Log.info("Init Failed");
        Particle.disconnect();
        Cellular.off();
        while (1){
            Particle.process();
        };
    } else {
        Log.info("Init Success");
        Particle.disconnect();
        Cellular.off();
    }

}


void loop() {  
    
    Data new_sample;

    // Take 5 samples with 1s interval 
    for (int i = 0; i < 5; i++){
		if (sensor.read_sensor_value(buf, 29)) {
        	Log.info("Read Result Failed");
    	}
        parse_result(buf, new_sample);
		safeDelay(1000);
    }

    // Average 5 samples
	for (int i = 0; i < 3; i++){
		new_sample.ae_value[i] = new_sample.ae_value[i] / 5;
    }

    // Record the time
    new_sample.day = Time.day();
    new_sample.hour = Time.hour();
    new_sample.minute = Time.minute();
    
    // Add it to the sensor record and increment the sampler counter
    sensor_record.emplace_back(new_sample);
    sample_counter = sample_counter + 1;

    // For the second last sample taken, open up the connection with cloud
    if (sample_counter == MAX_SAMPLE - 1){
        Log.info("attemp to connect");
        Particle.connect();
        safeDelay(20000);

#ifdef TIME_SYNC_ENABLE
    // Code taken from Particle's tutorial
    // https://docs.particle.io/cards/firmware/cloud-functions/particle-synctime/
    //
    if (millis() - lastSync > ONE_DAY_MILLIS) {
        // Request time synchronization from the Particle Device Cloud
        Particle.syncTime();
        lastSync = millis();
    }
#endif

    }

    // Publish the record to the cloud every 48 samples (48 samples * 30 min/sample = 24 hr)
    // Reset all the counters
    if (sample_counter == MAX_SAMPLE){    
        Log.info("ready to publish");        
        publish_daily_record();
        sample_counter = 0;
        Particle.disconnect();
        Cellular.off();
        Log.info("Switch off network facility");
    }

    Log.info("Turning off the sensor");
    digitalWrite(DUST_SENSOR, LOW);

#ifdef SLEEP_ENABLE
    // Last cycle will have network enabled for potential OTA
    if (sample_counter == MAX_SAMPLE - 1){
        SystemSleepConfiguration config;
        config.mode(SystemSleepMode::STOP)
              .network(NETWORK_INTERFACE_CELLULAR)
              .duration(SLEEP_TIME);
        System.sleep(config);
    } else {
        SystemSleepConfiguration config;
        config.mode(SystemSleepMode::STOP)
              .duration(SLEEP_TIME);
        System.sleep(config);
    }
#else
    safeDelay(10000);
#endif
    Log.info("Turning on the sensor");
    digitalWrite(DUST_SENSOR, HIGH);
    Log.info("Waiting for sensor starting up...");
    safeDelay(30000);

    Log.info("Counter number is " + String(sample_counter));
}

For the Seeed_HM330X.h and subsequent dependencies, please take at look at

I used the sensor’s demo code, and wrote my code based on their demo.

For now, I am still having the problem, it seems right after I set the pin back to high in the loop (the line: digitalWrite(DUST_SENSOR, HIGH);), Boron resets (it ran the setup and the loop again, and resets at the same position).

Thank you for your time looking at my code :grinning:

Please let me know if you spot anything that might go wrong.

I don’t see anything obvious so I’m suggesting some more “esoteric” things to try :wink:

Change int DUST_SENSOR = D6 to const int DUST_SENSOR = D6. This way you can ensure that the variable used for your digitalWrite() cannot be altered by some accidental memory corruption (e.g. within the used library).

Although the digitalWrite(HIGH) in setup() doesn’t cause any problems, it still might be worth a try to add a 330 Ohm resistor in the line between the SET pin and D6 to limit the current.

Alternatively (or in addition) you can try pinMode(DUST_SENSOR, INPUT) or pinMode(DUST_SENSOR, INPUT_PULLUP) (to activate the sensor) and pinMode(DUST_SENSOR, INPUT_PULLDOWN) or pinMode(DUST_SENSOR, OUTPUT) (with a preceding pinResetFast(DUST_SENSOR) in setup() to deactivate it) and never set the pin OUTPUT, HIGH.

For the sake of testing, you can also add Wire.end() before powering down the sensor and Wire.begin() after powering it back up.

BTW, what does the RGB LED do during the process?

I’d also rewrite this to avoid the use of String and its inherent risk for heap fragmentation

I’d rather do this

const char* msgPattern[] = 
// compact event format
"%02u,%02u,%02u,%5u,%5u,%5u"
// elaborate event format
//"%02u. %02u:%02u\r\n"
//"PM1.0 concentration(Atmospheric environment, unit:ug/m3): %5u\r\n"
//"PM2.5 concentration(Atmospheric environment, unit:ug/m3): %5u\r\n"
//"PM10  concentration(Atmospheric environment, unit:ug/m3): %5u\r\n"
;

HM330XErrorCode publish_daily_record() {
    while(!sensor_record.empty()){
        Data data_to_publish = sensor_record.front();
        char msg[sizeof(msgPattern) + 3*2 + 3*5 + 1];

        snprintf(msg, sizeof(msg)
                , msgPattern
                , data_to_publish.day
                , data_to_publish.hour
                , data_to_publish.minute
                , data_to_publish.ae_value[0]
                , data_to_publish.ae_value[1]
                , data_to_publish.ae_value[2]
                );
        if (Particle.connected()) {
            Particle.publish(msg);
            safeDelay(1000);
            sensor_record.erase(sensor_record.begin());
        }
        else { 
            break;
        }
    }

    return NO_ERR;
}

also this

could look a bit neater like this

    switch(System.resetReason()) {
      case RESET_REASON_PIN_RESET: 
        Log.info("Restarted due to a pin reset");
        break;
      case RESET_REASON_USER: // Check to see if we are starting from a pin reset or a reset in the sketch
        Log.info("Restarted due to a user reset");
        break;
      default:
        Log.info("System reset reason %i", System.resetReason());
    }
1 Like

Thanks for reviewing my code.

I assume you mean the status LED for “RGB LED”. Since I don’t need Boron to turn on its Cellular before it has collected all the data, I disconnect the Cellular in the setup. Therefore, the RGB LED stays white (breathing) all the time, but I can see a quick flash (not breathing, just one quick flash) during the unwanted reset.

By the way, for I2C connection, I am using the Grove Shield for Particle Mesh.

Will this be the source of the reset problem?

Also, I observe similar reset behaviour when I physically plug in the sensor. Can this be a hardware problem (e.g. large current sourcing etc.).

Thank you.

I don’t think this board should contribute to the problem - providing it is in good nick :wink:

What colour? Is it white too or maybe red?

It could be. But to be sure, we’d need to know the exact sensor board you are using.
I guess there are multiple carriers for that sort of sensor.

The datasheet provided by SeeedStudio mentions that the sensor can be used with 3.3V signal levels, but the sensor should be powered with 5V

And the inrush of the fan when waking the sensor may well cause the brown-out

For above reasons, you may want to add a step-up boost converter (ideally with sleep control) and some extra caps to handle the current surge.
When you attach its Vcc to the Li+ pin it should be capable of drawing the needed startup current directly from the battery rather than going via the Boron’s onboard 3v3 regulator which itself will act as a bottle neck.

2 Likes

@ScruffR,

This is a great list of things to look at. There are a few other items I was thinking and please let me know if you agree:

  1. @Steveliu29 should try to make the code as simple as possible. Perhaps, during debugging, the code could be written to be disconnected. This can reduce the code (and some of the #ifdef blocks) and it would eliminate the current draw of the Boron’s cellular modem from the mix.

  2. Since there is a fan, I wonder if it might make sense to add a flyback diode in addition to the extra caps you suggested. This is typically an issue on power down but just trying to think of all the possibilities.

  3. I wonder if the Boron’s blue led on D7 could be used to signal the state of the dust sensor enable line. Would be quicker than even a Log.info line.

Thanks,

Chip

2 Likes

These are all good points although I’d think the sensor board would already take precautions against kick-back voltage and IIRC @Steveliu29’s initial test code was already using D7 and was quite stripped down.

That was the reason why I didn’t suggest these points :wink:

1 Like

Thanks again for looking at my problem.

Do you think it’s possible to limit the current output from Boron? Since my ultimate goal is to save power, if I can decrease the current output from 3V3 pin, then I believe it also saves some of my battery.

BTW, for now, my code supports sleeping (I enable the sleep function in the #ifdef part of my code). However, Boron seems to disconnect from the serial and my PuTTy becomes inactive once Boron enters sleep. In this way, I cannot receive the logged info from serial anymore. It makes debugging much tougher.


Is it a problem with PuTTy? Is there any way to get around this problem?

Thanks,
Steveliu29

The limited output capacity of the 3v3 regulator is your problem I’d say.
So that’s not what you actually want, you want to limit the current demand of your external components - particularly the maximum transient load during startup - “flatten the curve” :see_no_evil:

This isn’t how it works.
Current is not pushed by the Boron but drawn by the components.
Your external components need a certain amount of power to work. Reducing the output below their need may “save” you some power but ultimately will not give you any results and thus defeats the whole purpose of such a project :wink:

You’d need a terminal program that can automatically reconnect. There is a PuTTY fork called ExtraPuTTY which can don that.
Or you use Particle’s CLI with particle serial monitor --follow

1 Like

I’d agree with @ScruffR comment below.

A quick and easy check would be to Sleep/Wake the Dust Sensor with the SET pin (Low/High 3V3) and let it source it’s power directly from the Li+ Pin (Li-Po).

This will add about 200 uA of sleeping current demand to your Power Budget, in addition to the Boron (per your final Sleep method).

You’d probably want to eventually switch the source directly to avoid the possibility/vulnerability of completely draining the Li-Po… if you can’t provide provisions for Recharging.

Typically, these Dust Sensors are a larger demand on your Power Budget than the Boron, given the required Warm-Up Time.

Hello @Rftop ,

Thank you for providing the suggestion.

Since I am using Grove Shield for Particle Mesh to connect the sensor to Boron, will it be possible for me to also connect the Li+ pin to the sensor? If so, what connection should I make?