To err is human.
To really foul up, use a computer.
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.
Here’s a picture of the bread board set up that I used for the following program.
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, ¤tS1Pulse), pulseIn(pulsesS2, ¤tS2Pulse)); 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.