Cloud connection dropping when using `pulseIn`

I’m using the pulseIn method to check if a dial tone is being send to the handset of an intercom.

Most of the time the line it’s reading is at 0V, so the method call takes until the set timeout of 3 seconds.

I’m finding that when making the call to this method inside loop that the the device keeps disconnecting and reconnecting to the cloud. It’d doesn’t look to lose the WiFi connection.

Here’s the current firmware:

// Pin to ground the door release line.
int DR = D4;

// Pins to ground the microphone and speaker lines.
int S1 = D6;
int S2 = D5;

// Pin to monitor the call tone line.
int BZ = A1;

// The current state of the handset.
bool handsetNotificationSent = false;
bool handsetActive = false;

// The time we last checked the handset was active.
int lastHandsetCheck = 0;

// The time that the system was last primed.
int lastPrimed = 0;

// Specify the antenna to use for WiFi.
// @see https://docs.particle.io/reference/firmware/photon/#selectantenna-
// STARTUP(WiFi.selectAntenna(ANT_EXTERNAl));

// Danger Zone.
SYSTEM_THREAD(ENABLED);

void setup()
{
    Particle.function("doorOpen", doorOpen);
    Particle.function("primeSystem", primeSystem);
    
    // Setup the relay signals.
    pinMode(DR, OUTPUT);
    pinMode(S1, OUTPUT);
    pinMode(S2, OUTPUT);
    
    // System primed indicator.
    pinMode(D7, OUTPUT);
    
    // Setup the BZ line to read the call tone with `pulseIn`.
    pinMode(BZ, INPUT_PULLDOWN);
    
    // Initalise the output pins.
    digitalWrite(DR, LOW);
    digitalWrite(S1, LOW);
    digitalWrite(S2, LOW);
    digitalWrite(D7, LOW);
}

void loop()
{
    handsetActive = isHandsetActive();
    
    // Send an event for the current activation, if we haven't already.
    if (! handsetNotificationSent && handsetActive) {
        handsetNotificationSent = Particle.publish("handset-activated", NULL, 60, PRIVATE);
    }
    
    // Reset the notification global variable if the handset's no longer active after we've sent a notification.
    if (handsetNotificationSent && ! handsetActive) {
        handsetNotificationSent = false;
    }
    
    // Can we automatically unlock the door?
    if (handsetActive && isSystemPrimed()) {
        // Open the door!
        doorOpen("automatically");
        
        // Make prime a one use method.
        unprimeSystem("unlocked");
    }
    
    // Unprime the system if has timed-out.
    if (! isSystemPrimed()) {
        unprimeSystem("timeout");
    }
    
    // Reboot the device if it's been on for more than a day and it's around midnight.
    if (Time.hour() >= 1 && millis() > 86400000) {
        System.reset();
    }
}

/**
 * Unprime the system for the given reason.
 */
void unprimeSystem(String reason)
{
    if (lastPrimed != 0) {
        // Turn off the primed status visual indicator.
        digitalWrite(D7, LOW);
        
        // Publish why the system is now not primed.
        Particle.publish("system-unprimed", reason, 60, PRIVATE);
        
        // Reset the global timestamp.
        lastPrimed = 0;
    }
}

/**
 * Prime the system.
 * 
 * Allows us to check in the loop if we're allowed to automatically
 * open the door when the handset is called.
 */
int primeSystem(String command)
{
    // Update the last primed global variable.
    lastPrimed = Time.now();
    
    // Let's add a visual indicator that the system is primed.
    digitalWrite(D7, HIGH);
    
    Particle.publish("system-primed", NULL, 60, PRIVATE);
    
    return 0;
}

/**
 * Open the door!
 */
int doorOpen(String command)
{
    // If the handset is not active we can't open the door.
    if (! handsetActive) {
        return -1;
    }
    
    // "Pickup" the handset and "press" the door release button.
    digitalWrite(S1, HIGH);
    digitalWrite(S2, HIGH);
    digitalWrite(DR, HIGH);
    
    delay(100);
    
    // Finally resetting all the relays.
    digitalWrite(DR, LOW);
    digitalWrite(S1, LOW);
    digitalWrite(S2, LOW);
    
    Particle.publish("door-unlocked", (command != "") ? command : "manually", 60, PRIVATE);
    
    return 0;
}

/**
 * Check if the system is currently in a "primed" state.
 */
bool isSystemPrimed()
{
    return (lastPrimed > Time.now() - 60 * 3);
}

/**
 * Check if the handset is current being called.
 * 
 * Slow function, will take up to 3 seconds to return if there is no pulse to read.
 */
bool isHandsetActive()
{
    // When active the BZ line should have a voltage between 0V and 2.5V as a square wave.
    // @see https://docs.particle.io/reference/firmware/photon/#pulsein-.
    return (pulseIn(BZ, HIGH) > 0);
}

I’ve also tried (perhaps naively) putting the call in a thread that I kick off in start.

Having system threading enabled doesn’t seem to make a difference.

Would love any suggestions! Otherwise I’ll look to open a PR to pass in a timeout to pulseIn.

@sparkinson, pulseIn() is a (semi)blocking function which will wait for the pulse event for a max of 3 seconds per transition event:

   /* If already on the value we want to measure, wait for the next one.
     * Time out after 3 seconds so we don't block the background tasks
     */
    while (pinReadFast(pin) == value) {
        if (SYSTEM_TICK_COUNTER - timeoutStart > 360000000UL) {
            return 0;
        }
    }

    /* Wait until the start of the pulse.
     * Time out after 3 seconds so we don't block the background tasks
     */
    while (pinReadFast(pin) != value) {
        if (SYSTEM_TICK_COUNTER - timeoutStart > 360000000UL) {
            return 0;
        }
    }

    /* Wait until this value changes, this will be our elapsed pulse width.
     * Time out after 3 seconds so we don't block the background tasks
     */
    volatile uint32_t pulseStart = SYSTEM_TICK_COUNTER;
    while (pinReadFast(pin) == value) {
        if (SYSTEM_TICK_COUNTER - timeoutStart > 360000000UL) {
            return 0;
        }
    }

Depending on the input conditions, pulseIn could return after anywhere from 3 to 9 seconds! What frequency square wave are you measuring? What signal is present otherwise? Have you measured the amplitude to be sure it is 2.5v?

Hi @peekay123, oh 9 seconds is longer that I though!

So I’m using it to read the call tone of a phone, to see if it’s currently being called or not. When it is the line has a 0V to 2.5V square wave signal with I think a frequency of about half a second (confirmed with a multimeter, and some manual testing) .

Most of the time it’s not though and just sits at 0V, and so I guess is actually timing out after 9 seconds!

@sparkinson, measuring a square wave with a multimeter is not ideal since the multimeter can’t sample fast enough to measure “true” amplitude.

You may want to use an interrupt instead of pulseIn(). You could trigger on a RISING edge and use millis() to debouce so it would not “trigger” your code unless it re-triggered within a given interval (defined by the period). In the ISR, once the debouce is done, you simply set a global flag indicating a valid tone event. :wink:

Brilliant, thanks for the alternative solution, time to do some serious reading!

Any reason why I was still having issues with something like the following?

void setup() {
    Thread* thread = new Thread("is-active-thread", threadFunc);
}

os_thread_return_t threadFunc() {
    while (true) {
        handsetActive = isHandsetActive();
    }
}

void loop() {}

I'll be grabbing an oscilloscope soon too.

@sparkinson, not sure why you need another thread since using an interrupt gives you an asynchronous event that you can sample in loop(). Even with a thread, you still need to DO something with the event, which is done in loop()!

It was just the first thing I came across that I thought might prevent the pulseIn call from blocking loop.

1 Like