Notifications
Clear all

Pico Robotics: Distance Sensors w/ Pulse Width Output

2 Posts
1 Users
0 Reactions
572 Views
THRandell
(@thrandell)
Brain Donor
Joined: 3 years ago
Posts: 242
Topic starter  

To err is human.
To really foul up, use a computer.


   
Quote
THRandell
(@thrandell)
Brain Donor
Joined: 3 years ago
Posts: 242
Topic starter  

This is my second attempt to write up something about using pulse width output distance sensors with the RPi Pico, so hopefully this one sticks;-)

 

I wanted to come up with a small robot with 8 distance sensors and after some research I decided on these from Pololu Robotics & Electronics:  Pololu Distance Sensor with Pulse Width Output, 50cm Max

https://www.pololu.com/product/4064

They draw 30mA each when active, so I had to be mindful that I didn’t stress out the Pico which has a 300mA limit on its traces.  These sensors are enabled by default but have an enable/disable feature so I made use of that to enable two of the sensors only when needed.

Here’s a picture of the sensor with a wire soldered to the enable pad.

IMG 2441

Here’s a picture of the bread board set up that I used for the following program. 

IMG 2360

The following program is a Reader’s Digest version of the one I use on the robot.

To make use of the output from these sensors I took the approach of using interrupts on the Pico to gather the time that the sensor OUT pin was high and the time that the OUT pin was pulled low.  These values are stored in a two dimensional array and a variable is used to keep track of the number of pulse timings stored.  The caveat here is that you must set the counter variables to zero periodically.  

I’ll cover the program as three snippets of code.  To compile just copy them into one file and let it rip.

So this first snippet shows the pulsesSx arrays and their indexes the currentSxPulse variables.   Also shown is the rec_callback() routine which is called by the GPIO pin interrupts when the S1_INP and S2_INP pins rise or fall.  In it the pulsesSx arrays are populated with the timings that are used later to calculate the distance.

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "pico/stdlib.h"
#include "hardware/irq.h"
#include <string.h>

#define S1_INP  4
#define S2_INP  5

static uint16_t  pulsesS1[256][2];   // pair is time of low[0]; and high[1] signals
static uint16_t  pulsesS2[256][2];
static uint8_t   currentS1Pulse = 0; // index for pulses we're storing
static uint8_t   currentS2Pulse = 0;

void rec_callback(uint gpio, uint32_t events) {
  static uint64_t highS1TimeStart = 0;
  static uint64_t lowS1TimeStart  = 0;
  static uint64_t highS2TimeStart = 0;
  static uint64_t lowS2TimeStart  = 0;

  if (gpio == S1_INP) {
    if (events == 0b1000) {   // edge rise  __--   This indicates there is a reflection
      highS1TimeStart = time_us_64();
      pulsesS1[currentS1Pulse][0] = highS1TimeStart - lowS1TimeStart; // set the LOW time
    }
    if (events == 0b0100) {  // edge fall   --__
      lowS1TimeStart = time_us_64();
      pulsesS1[currentS1Pulse][1] = lowS1TimeStart - highS1TimeStart; // set the HIGH time
      currentS1Pulse++;
    }
  }
  if (gpio == S2_INP) {
    if (events == 0b1000) {
      highS2TimeStart = time_us_64();
      pulsesS2[currentS2Pulse][0] = highS2TimeStart - lowS2TimeStart;
    }
    if (events == 0b0100) {
      lowS2TimeStart = time_us_64();
      pulsesS2[currentS2Pulse][1] = lowS2TimeStart - highS2TimeStart;
      currentS2Pulse++;
    }
  }
}

This second snippet has two routines.  resetPulseCnts() to set the array index variables to zero and pulseIn() which reads a pulseSx array and returns the distance detected.

void resetPulseCnts() {

  currentS1Pulse = 0;
  currentS2Pulse = 0;

  return;
}

// Don't use the latest currentPulse value as we may still be reading the sensor
float pulseIn(uint16_t pulses[256][2], uint8_t *currentPulse) {
  float dist =  0.0f;
  uint8_t tempPulse = 0;

  if (*currentPulse > 0) {  // uses zero based indexing into pulses array
    tempPulse = *currentPulse - 1;
    dist = (pulses[tempPulse][1] - 1000) * 3 / 4;
    // printf("Pulse cnt = %d, Time = %d ms and Distance = %f mm\n", tempPulse, pulses[tempPulse][1], dist);
    if (dist <= 0.0) dist = 0.0f;   // Very short pulse...
    if (dist > 500.0) dist = 500.0f; // Long pulse possibly 2ms
  }
  else // *currentPulse = 0 so no pulses recorded between resetPulseCnts(). Seems unlikely
    dist = 500.0f;

  return  dist;
}

 

Last is the main() routine which sets up the GPIO pins and enables them with the callback routine we saw earlier.  The while loop reports the current distance detected by the two sensors, spins for 1 second, set the indexes to zero and repeats.

int main() {
  stdio_init_all();

  gpio_init(S1_INP);     // enable I/O set to GPIO_FUNC_SIO and set to input
  gpio_set_dir(S1_INP, GPIO_IN);
  gpio_pull_down(S1_INP);  // the IR goes high when there is a reflection
  gpio_init(S2_INP);
  gpio_set_dir(S2_INP, GPIO_IN);
  gpio_pull_down(S2_INP);

  // because i frequently don't get the screen session started in time
  printf("waiting for usb host");
  while (!stdio_usb_connected()) {
    printf(".");
    sleep_ms(500);
  }
  sleep_ms(2000);

  gpio_set_irq_enabled_with_callback(S2_INP, GPIO_IRQ_EDGE_FALL | GPIO_IRQ_EDGE_RISE, true, &rec_callback);
  gpio_set_irq_enabled(S1_INP, GPIO_IRQ_EDGE_FALL | GPIO_IRQ_EDGE_RISE, true);
  gpio_set_irq_enabled(S2_INP, GPIO_IRQ_EDGE_FALL | GPIO_IRQ_EDGE_RISE, true);

  uint64_t wait_time;

  resetPulseCnts();
  while (true) {

    printf("Sensor 1 %3.2fmm - Sensor 2 %3.2fmm\n",pulseIn(pulsesS1, &currentS1Pulse), pulseIn(pulsesS2, &currentS2Pulse));

    wait_time = time_us_64() + 800000;  // 800ms
    while (time_us_64() < wait_time) { }
    wait_time = time_us_64() + 200000;  // 200ms  So that there is time to catch some pulses
    resetPulseCnts();
    while (time_us_64() < wait_time) { }
  }

  return 0;
}

 

So that’s it.  I’ve used this technique a few times on robots with up to 8 sensors plus 4 more interrupts on wheel encoders and it seems to get the job done.  The Pico is a great little MCU for robotics!

 

Tom

 

To err is human.
To really foul up, use a computer.


   
ReplyQuote