Photon PulseIn Function

would LOVE to have a working PulseIn function for the Photon. The following compiled for the Photon, but results were inconsistent at best and more likely random. I successfully use this code (less the PIN_MAP declaration in the first line of the function) for the Spark Core at present.

unsigned long pulseIn(uint16_t pin, uint8_t state) {STM32_Pin_Info* PIN_MAP = HAL_Pin_Map(); // Pointer required for highest access speed
GPIO_TypeDef* portMask = (PIN_MAP[pin].gpio_peripheral);
uint16_t pinMask = (PIN_MAP[pin].gpio_pin);
unsigned long pulseCount = 0;
unsigned long loopCount = 0;
unsigned long loopMax = 1000000;  //about 0.5 seconds

if(state == HIGH) {
    // While the pin is *not* in the target state we make sure the timeout hasn't been reached.
    while ((portMask->IDR & pinMask) == 0) { // while the bit in the portmask is low
        if (loopCount++ == loopMax) {
            return 0;
        }
    }

    // When the pin *is* in the target state we bump the counter while still keeping track of the timeout.
    while (portMask->IDR & pinMask) { // while the bit in the portmask is high, might be 0x0080, 0x0040, etc..
        if (loopCount++ == loopMax) {
            return 0;
        }
        pulseCount++;
    }
}
else {
    // While the pin is *not* in the target state we make sure the timeout hasn't been reached.
    while (portMask->IDR & pinMask) { // while the bit in the portmask is high, might be 0x0080, 0x0040, etc..
        if (loopCount++ == loopMax) {
            return 0;
        }
    }

    // When the pin *is* in the target state we bump the counter while still keeping track of the timeout.
    while ((portMask->IDR & pinMask) == 0) { // while the bit in the portmask is low
        if (loopCount++ == loopMax) {
            return 0;
        }
        pulseCount++;
    }
}

// Return the pulse time in microsecond!
return pulseCount * 0.405; // Calculated the pulseCount++ loop to be about 0.405uS in length.}

Yeah, stuck at same error here.
I am working on a Photon remote control stream parser, and I was trying to use a pulseIn function.
I know the functions referenced belong to a STM library, but don’t know which is the right one for the photon and where I could find the sources (.h and .cpp).

This is the one which worked well for me on the core :

Yeah, but still I am getting the same errors:

    ../../../build/target/user/platform-6/libuser.a(remocon.o): In function `pulseIn(unsigned short, unsigned char)':
    remocon.cpp:70: undefined reference to `GPIO_ReadInputDataBit'
    remocon.cpp:77: undefined reference to `GPIO_ReadInputDataBit'

…and I had to include the missing:

STM32_Pin_Info* PIN_MAP = HAL_Pin_Map(); // Pointer required for highest access speed

What other #include statements had you put in your code for this piece to compile?

I have never got it to compile on the Photon, only the core.

K.

So I did a bit of Google searching and found this:

uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
    uint8_t bitstatus = 0x00;
  
    /* Check the parameters */
    assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
    assert_param(IS_GET_GPIO_PIN(GPIO_Pin));

    if ((GPIOx->IDR & GPIO_Pin) != (uint32_t)Bit_RESET)
        bitstatus = (uint8_t)Bit_SET;
    else
        bitstatus = (uint8_t)Bit_RESET;

    return bitstatus;
}

So I’ve put that in my code - no more compiler issues.
Still have to do some work in my main code in order to be able to test the pulseIn() function.

1 Like

So, good news is this code really works. In the Photon. Yes. That’s what I’ve got. Photon. Not Core.

I have rigged pin D4 of my Photon with an IR receiver. Then I’ve successfully entered some serial debug messages to know if my code was parsing serial bits into integer values. Looks like I’ve got some too.

The issue is: after so many button presses, the Photon goes into “SOS Mode” (has anyone got a better name for this? Does not seem this mode is documented - LED turns red and flashes “SOS” in Morse code…?)

I wish I knew more about it, so maybe I could figure out what would cause my Photon to go nuts. As a wild guess, I suspect maybe I’m overflowing some buffer or register (any close? in the ballpark?).
If so, it’s possible I’d need to perform a “flush” somewhere to avoid this (GPIO register? Anyone? Bueller?) before shoving any more values?

Help and advice is so welcome at this point.
-Werner

Still stuck at this, I found it is really hard to debug IR codes when the processor resets itself from time to time with no apparent reason given.

Maybe I’ll open up a topic for requesting that someone writes a port of the IRremote library for the Photon.

Good news, I’ve just realized (through a compilation error in my project - “duplicate definifion of void ReadInputDataBit() function” that the particle leprechauns have implemented this STM32Fxx GPIO function for the photon, as long as STM32_Pin_Info* HAL_Pin_Map() as well.

Perhaps soon we’ll have our own version of unsigned long pulseIn(uint16_t pin, uint8_t state) too?

@wmoecke, the new 0.4.4rc6 firmware includes FAST GPIO functions for writting and reading GPIO lines. The ideal pulseIn() function will exploit a hardware timer feature to measure a pulse. Work on a new timer library will start soon :smile:

1 Like

Hi Peekay. I am so glad to see you acknowledged the issues posted here!
Been a recent fan of your posts, I know for a fact you are on to the rescue. I am a noob at programming at hardware level, and especially with timing sensitive code like parsing IR pulses it’s been a nightmare.

I’m about to throw in the towel on this IR idea of mine… I currently have pulseIn() being fired by attachInterrupt(), but I can’t get it to work consistently, with the side effect that my Photon resets itself at certian (irregular) intervals.

Ah, the joy. :smile:

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;
}

This is great. Thanks a lot for working on it. How might you use something like this for PING sensor code?

Pretty soon you’ll be able to write code that uses pulseIn() natively, but until then (v0.4.7 firmware), you can plop this into your IDE and it should just work. When v0.4.7 comes out you’ll want to remove the pulseIn() function and recompile:

#include "application.h"

// establish variables for duration of the ping, 
// and the distance result in inches and centimeters:
#define trigPin D2
#define echoPin D6

void setup() {
    pinMode(trigPin, OUTPUT);
    digitalWriteFast(trigPin, LOW);
    delay(50);
    Serial.begin(115200);
}

void loop() {
    ping();
}

/*
 * pulseIn()
 * 
 * @brief: blocking call to measure a high or low pulse
 * @returns: uint32_t pulse width in microseconds up to 3 seconds
 *
 */
uint32_t pulseIn(pin_t pin, uint32_t state) {
    
    volatile uint32_t timeoutStart = SYSTEM_TICK_COUNTER; // total 3 seconds for entire function!
    
    /* If already on the state 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) != state) {
        if (SYSTEM_TICK_COUNTER - timeoutStart > 360000000UL) {
            return 1;
        }
    }
    
    /* Wait until this state 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) == state) {
        if (SYSTEM_TICK_COUNTER - timeoutStart > 360000000UL) {
            return 1;
        }
    }

    return (SYSTEM_TICK_COUNTER - pulseStart)/SYSTEM_US_TICKS;
}

void ping()
{
    uint32_t duration, inches, cm;
    pinMode(echoPin, INPUT);
    pinMode(trigPin, OUTPUT);

    // The sensor is triggered by a HIGH pulse of 10 or more microseconds.
    digitalWriteFast(trigPin, HIGH);
    delayMicroseconds(10);
    digitalWriteFast(trigPin, LOW);
  
    duration = pulseIn(echoPin, HIGH);
    
    // convert the time into a distance
    inches = microsecondsToInches(duration);
    cm = microsecondsToCentimeters(duration);
      
    Serial.print(inches);
    Serial.print("in, ");
    // Serial.print(cm);
    // Serial.print("cm");
    // Serial.println();
    Serial.print(duration);
    Serial.println("us");
}

uint32_t microsecondsToInches(uint32_t microseconds)
{
  // According to Parallax's datasheet for the PING))), there are
  // 73.746 microseconds per inch (i.e. sound travels at 1130 feet per
  // second).  This gives the distance travelled by the ping, outbound
  // and return, so we divide by 2 to get the distance of the obstacle.
  // See: http://www.parallax.com/dl/docs/prod/acc/28015-PING-v1.3.pdf
  return microseconds / 74 / 2;
}

uint32_t microsecondsToCentimeters(uint32_t microseconds)
{
  // The speed of sound is 340 m/s or 29 microseconds per centimeter.
  // The ping travels out and back, so to find the distance of the
  // object we take half of the distance travelled.
  return microseconds / 29 / 2;
}

Thanks! I deleted the PulseIn function and then recompiled with 4.7 with no errors. The PING (confirmed working on Arduino) triggers fine but I get values of 0 for the duration (and distance). I tried a variety of different power sources and pins to no avail. Can you confirm your PING works on 4.7? Thanks again.

Forgot to mention that I also tried using the same pin for trigger and echo, as well as sending a low pulse ahead of the high pulse for the trigger. But I think there is something off with the PulseIn, which appears to time out, and I temporarily get random non-zero values just after disconnecting the echo pin.

@tcb2 Yep, confirmed working :smile:

SUCCESS! Thanks for posting the sensor model. I am using the Parallax 28015, which does not have distinct trigger and echo pins, but rather a single SIG pin. When you connect the echo and trigger pins to the SIG pin simultaneously, the PulseIn times out, perhaps because the photon-to-sensor pulse or sensor-to-photon data is absorbed by the trigger pin. Whatever the case, the solution is to use a single pin for trigger and echo, similar to the Arduino code, by defining the pin as output for trigger and then input for PulseIn.

#define pingPin D0

void setup() {
    pinMode(pingPin, OUTPUT);
    digitalWriteFast(pingPin, LOW);
    delay(50);
    Serial.begin(115200);
}

void loop() {
    uint32_t duration, inches, cm;
    
    pinMode(pingPin, OUTPUT);
    digitalWriteFast(pingPin, HIGH);
    delayMicroseconds(10);
    digitalWriteFast(pingPin, LOW);
  
    pinMode(pingPin, INPUT);
    duration = pulseIn(pingPin, HIGH);
    
    inches = microsecondsToInches(duration);
    cm = microsecondsToCentimeters(duration);
      
    Serial.print(inches);
    Serial.print("in, ");
    // Serial.print(cm);
    // Serial.print("cm");
    // Serial.println();
    Serial.print(duration);
    Serial.println("us");
    
    delay (500);
}

uint32_t microsecondsToInches(uint32_t microseconds)
{
  // According to Parallax's datasheet for the PING))), there are
  // 73.746 microseconds per inch (i.e. sound travels at 1130 feet per
  // second).  This gives the distance travelled by the ping, outbound
  // and return, so we divide by 2 to get the distance of the obstacle.
  // See: http://www.parallax.com/dl/docs/prod/acc/28015-PING-v1.3.pdf
  return microseconds / 74 / 2;
}

uint32_t microsecondsToCentimeters(uint32_t microseconds)
{
  // The speed of sound is 340 m/s or 29 microseconds per centimeter.
  // The ping travels out and back, so to find the distance of the
  // object we take half of the distance travelled.
  return microseconds / 29 / 2;
}
2 Likes

I can confirm that the above code also works with the MaxBotix LV-Maxsonar-EZ when connected to the PW pin. Kudos to BDub for getting PulseIn up and running!