Photon PulseIn Function

I was working with a PING sensor tonight and decided to get the pulseIn() function working… instead of a blocking call to pulseIn() which I had working but it just seemed kind of slow, I decided to use interrupts and this is what came out of it. It really needs a library written for it to make it more friendly, but it works rather well. The interrupts themselves will block to do the timing, but you don’t have to block waiting for the first edge. You kind of do for the FALLING edge though for whatever reason, you can see my comments in the example. For measuring short high pulses, this is pretty nice. If you’d like to improve this please do! Just thought I’d share in case anyone needed this.

//
// pulseIn() replacement for Photon
// 
// This is meant for clean debounced waveforms
// Noise in == noise out
//

/* SAMPLE OUTPUT

Pulse high measured: 1004
Pulse low measured: 1005
Pulse high measured: 1004
Pulse low measured: 1005
Pulse high measured: 998
Pulse low measured: 1005
Pulse high measured: 1004
Pulse low measured: 1005
Pulse high measured: 1004
Pulse low measured: 1005

*/

// Uncomment this to generate calibration pulses
// hook D1 to D6 and D1 to D4 to calibrate 
// TICKS_PER_1000US for pulseHigh() and pulseLow()
#define CALIBRATION_PULSES

#include "application.h"

STM32_Pin_Info* PIN_MAP2 = HAL_Pin_Map(); // Pointer required for highest access speed
#define pinLO(_pin) (PIN_MAP2[_pin].gpio_peripheral->BSRRH = PIN_MAP2[_pin].gpio_pin)
#define pinHI(_pin) (PIN_MAP2[_pin].gpio_peripheral->BSRRL = PIN_MAP2[_pin].gpio_pin)
#define pinSet(_pin, _hilo) (_hilo ? pinHI(_pin) : pinLO(_pin))

uint32_t pulseHighMeas = 0;
uint32_t pulseLowMeas = 0;

void setup() {
    
#ifdef CALIBRATION_PULSES    
    pinMode(D1, OUTPUT);
    analogWrite(D1, 128); // 50% 500Hz HIGH 1000us / LOW 1000us
#endif
    
    Serial.begin(115200);
    Serial.println("PulseIn Replacement");
    
    // NOTE: D5 seems kind of flaky.
    attachInterrupt(D6, pulseHigh, RISING);
    attachInterrupt(D4, pulseLow, FALLING);
}

void loop() {
    
    if (pulseHighMeas) {
        if (pulseHighMeas != 1) {
            Serial.print("Pulse high measured: ");
            Serial.println(pulseHighMeas);
        }
        pulseHighMeas = 0;
        attachInterrupt(D6, pulseHigh, RISING);
    }
    
    if (pulseLowMeas) {
        if (pulseLowMeas != 1) {
            Serial.print("Pulse low measured: ");
            Serial.println(pulseLowMeas);
        }
        pulseLowMeas = 0;
        attachInterrupt(D4, pulseLow, FALLING);
    }
    
    // Slow down the crazy output
    delay(1000);
}

void pulseHigh() {
    #define TICKS_PER_1000US 5966.0
    #define PULSE_INVALID 999999999
    uint16_t pin = D6;
    detachInterrupt(pin);
    STM32_Pin_Info* PIN_MAP2 = HAL_Pin_Map(); // Pointer required for highest access speed
    GPIO_TypeDef* portMask = (PIN_MAP2[pin].gpio_peripheral);
    uint16_t pinMask = (PIN_MAP2[pin].gpio_pin);
    uint32_t pulseCount = 0;
    uint32_t loopMax = TICKS_PER_1000US * 1000 * 10; // 10 seconds timeout to maintain the Particle Cloud connection.
    
    while (GPIO_ReadInputDataBit(portMask, pinMask) == HIGH) {
        if (pulseCount++ == loopMax) {
            pulseHighMeas = PULSE_INVALID;
            return;
        }
    }
    
    // pulseCount is TICKS_PER_1000US with a 1000us calibration pulse.
    // Add 1 as a minimum to let the application know this function was called.
    pulseHighMeas = (pulseCount * (double)(1000.0 / TICKS_PER_1000US)) + 1;
}

void pulseLow() {
    #define TICKS_PER_1000US 6250.0
    #define PULSE_INVALID 999999999
    uint16_t pin = D4;
    detachInterrupt(pin);
    STM32_Pin_Info* PIN_MAP2 = HAL_Pin_Map(); // Pointer required for highest access speed
    GPIO_TypeDef* portMask = (PIN_MAP2[pin].gpio_peripheral);
    uint16_t pinMask = (PIN_MAP2[pin].gpio_pin);
    uint32_t pulseCount = 0;
    uint32_t loopMax = TICKS_PER_1000US * 1000 * 10; // 10 seconds timeout to maintain the Particle Cloud connection.
    
    // Throw away the first LOW pulse, because there seems to be some false
    // triggering immediately after attaching a FALLING interrupt.
    // NOTE: This is fine for fast reocurring waveforms, but not ones that
    // are far and few between.
    while (GPIO_ReadInputDataBit(portMask, pinMask) == LOW);
    
    // Wait for 2nd FALLING edge
    while (GPIO_ReadInputDataBit(portMask, pinMask) == HIGH);
    
    while (GPIO_ReadInputDataBit(portMask, pinMask) == LOW) {
        if (pulseCount++ == loopMax) {
            pulseLowMeas = PULSE_INVALID;
            return;
        }
    }
    
    // pulseCount is TICKS_PER_1000US with a 1000us calibration pulse.
    // Add 1 as a minimum to let the application know this function was called.
    pulseLowMeas = (pulseCount * (double)(1000.0 / TICKS_PER_1000US)) + 1;
}