Counts points that hit a LIDAR sensor, and includes threshold sensing so a point doesn't get counted twice, much in the same spirit as debouncing.

Dependencies:   mbed mbed-rtos 4DGL-uLCD-SE X_NUCLEO_53L0A1

The Object Counter counts the number of objects (in our case, ping pong balls) that pass by the sensor. It features a high water mark that ensures only one ball is counted at any given time, and that a ball will only be counted once.

Partner Program

This is intended to be used with Ping Pong Robot, but can be used by itself if you wish

Installation

We use the following components:

All of these can be purchased from SparkFun with the exception of the VL53L0X sensor, wich is from AdaFruit.

To connect these, use the following pin outs:

Class D Amplifier & Speaker

MBEDClass D Audio AmpSpeaker
GndIN-
GndPWR-
5VPWR+
p25IN+
p20S
OUT-Speaker-
OUT+Speaker+

uLCD Screen

MBEDuLCD HeaderuLCD
GndGndGnd
+5V+5V+5V
p13TXRX
p14RXTX
p15RESRES

VL35L0X

MBEDVL35L0X
GndGnd
+3.3VVIN
p26SHDN
p27SDA
p28SCL

Usage

This is intended to be used to track the number of objects that have been collected. Because of this, the software features a "High Water Mark" mentality - after an object has gotten close enough, a flag is tripped - this flag only resets after the distance increases back past the high water mark. In this way, we ensure we count each object once - much in the same spirit as debouncing.

The VL53L0X is a low cost chip that measures distance via "Time of Flight" - basically how long it takes light to travel to the object and back. Because it uses a single laser, it can be quite precise. This makes it ideal for our purposes - tracking a single object as it falls.

Heads Up!

The largest issue is data collection time - while the chip does collect data quickly, if your object is moving at a high speed, it can sometimes miss the object entirely because it didn't "see" the object in the few fractions of a second that the object was close enough!

If you require more precision, there are plenty of higher quality (and higher cost) LIDAR systems - AdaFruit sells many of these.

As a workaround, ensure your object is not moving quickly - this can be accomplished by setting the LIDAR's side on the floor, so its line of sight is directly above the ground. When the object hits the ground, even if it bounces, it should be on the ground enough time for the LIDAR to pick it up. Slightly tilting the ground will make sure your object will roll away after falling.

As with everything else, experience is the best teacher - you might need to tweak the threshold in the code to get optimal results.

Demonstration

The circuit looks like this: /media/uploads/alexhrao/img_9521.jpeg

Committer:
alexhrao
Date:
Sun Apr 14 16:02:37 2019 +0000
Revision:
0:fc7ce4e596ff
Child:
1:e82997c13013
Initial Commit

Who changed what in which revision?

UserRevisionLine numberNew contents of line
alexhrao 0:fc7ce4e596ff 1 #include "mbed.h"
alexhrao 0:fc7ce4e596ff 2 #include "rtos.h"
alexhrao 0:fc7ce4e596ff 3 #include <stdio.h>
alexhrao 0:fc7ce4e596ff 4 #include "uLCD_4DGL.h"
alexhrao 0:fc7ce4e596ff 5 #include "XNucleo53L0A1.h"
alexhrao 0:fc7ce4e596ff 6
alexhrao 0:fc7ce4e596ff 7 #define VL53L0_I2C_SDA p28
alexhrao 0:fc7ce4e596ff 8 #define VL53L0_I2C_SCL p27
alexhrao 0:fc7ce4e596ff 9
alexhrao 0:fc7ce4e596ff 10 // Light Thread
alexhrao 0:fc7ce4e596ff 11 // Only shine light when
alexhrao 0:fc7ce4e596ff 12 // Sound Thread
alexhrao 0:fc7ce4e596ff 13
alexhrao 0:fc7ce4e596ff 14 // Game
alexhrao 0:fc7ce4e596ff 15 volatile int points = 0;
alexhrao 0:fc7ce4e596ff 16 volatile int time_left = 0;
alexhrao 0:fc7ce4e596ff 17 int threshold = 0.05f; // ???
alexhrao 0:fc7ce4e596ff 18 volatile bool is_point = false;
alexhrao 0:fc7ce4e596ff 19
alexhrao 0:fc7ce4e596ff 20 // LIDAR
alexhrao 0:fc7ce4e596ff 21 static XNucleo53L0A1* lidar = NULL;
alexhrao 0:fc7ce4e596ff 22 uint32_t dist;
alexhrao 0:fc7ce4e596ff 23 DigitalOut shdn(p26);
alexhrao 0:fc7ce4e596ff 24
alexhrao 0:fc7ce4e596ff 25 // LCD
alexhrao 0:fc7ce4e596ff 26 Mutex lcd_lock;
alexhrao 0:fc7ce4e596ff 27 uLCD_4DGL uLCD(p9, p10, p11); // serial tx, serial rx, reset pin;
alexhrao 0:fc7ce4e596ff 28
alexhrao 0:fc7ce4e596ff 29 // Speaker
alexhrao 0:fc7ce4e596ff 30 PwmOut speaker(p25);
alexhrao 0:fc7ce4e596ff 31
alexhrao 0:fc7ce4e596ff 32 // LEDs
alexhrao 0:fc7ce4e596ff 33 PwmOut point_led(p21);
alexhrao 0:fc7ce4e596ff 34 PwmOut time_led(p22);
alexhrao 0:fc7ce4e596ff 35
alexhrao 0:fc7ce4e596ff 36 // Threads
alexhrao 0:fc7ce4e596ff 37 Thread sound_thread;
alexhrao 0:fc7ce4e596ff 38 Thread time_thread;
alexhrao 0:fc7ce4e596ff 39 Thread points_thread;
alexhrao 0:fc7ce4e596ff 40
alexhrao 0:fc7ce4e596ff 41 // Debugging
alexhrao 0:fc7ce4e596ff 42 DigitalOut err_led(LED1);
alexhrao 0:fc7ce4e596ff 43 DigitalOut status_led(LED2);
alexhrao 0:fc7ce4e596ff 44 Serial pc(USBTX, USBRX);
alexhrao 0:fc7ce4e596ff 45
alexhrao 0:fc7ce4e596ff 46 void time(void) {
alexhrao 0:fc7ce4e596ff 47 lcd_lock.lock();
alexhrao 0:fc7ce4e596ff 48 uLCD.locate(3, 5);
alexhrao 0:fc7ce4e596ff 49 uLCD.text_height(2);
alexhrao 0:fc7ce4e596ff 50 uLCD.text_width(2);
alexhrao 0:fc7ce4e596ff 51 uLCD.printf("%d", time_left);
alexhrao 0:fc7ce4e596ff 52 lcd_lock.unlock();
alexhrao 0:fc7ce4e596ff 53 while (1) {
alexhrao 0:fc7ce4e596ff 54 // every second, change LED and update screen
alexhrao 0:fc7ce4e596ff 55 time_led = !time_led;
alexhrao 0:fc7ce4e596ff 56 if (time_left > 0) {
alexhrao 0:fc7ce4e596ff 57 lcd_lock.lock();
alexhrao 0:fc7ce4e596ff 58 uLCD.locate(3, 5);
alexhrao 0:fc7ce4e596ff 59 uLCD.text_height(2);
alexhrao 0:fc7ce4e596ff 60 uLCD.text_width(2);
alexhrao 0:fc7ce4e596ff 61 uLCD.printf("%d", time_left);
alexhrao 0:fc7ce4e596ff 62 lcd_lock.unlock();
alexhrao 0:fc7ce4e596ff 63 Thread::wait(1000);
alexhrao 0:fc7ce4e596ff 64 } else {
alexhrao 0:fc7ce4e596ff 65 break;
alexhrao 0:fc7ce4e596ff 66 }
alexhrao 0:fc7ce4e596ff 67 }
alexhrao 0:fc7ce4e596ff 68 lcd_lock.lock();
alexhrao 0:fc7ce4e596ff 69 uLCD.locate(3, 5);
alexhrao 0:fc7ce4e596ff 70 uLCD.text_height(2);
alexhrao 0:fc7ce4e596ff 71 uLCD.text_width(2);
alexhrao 0:fc7ce4e596ff 72 uLCD.printf("0");
alexhrao 0:fc7ce4e596ff 73 lcd_lock.unlock();
alexhrao 0:fc7ce4e596ff 74 }
alexhrao 0:fc7ce4e596ff 75
alexhrao 0:fc7ce4e596ff 76 void sound(void) {
alexhrao 0:fc7ce4e596ff 77 int is_new = 1;
alexhrao 0:fc7ce4e596ff 78 speaker.period(1.0 / 900.0);
alexhrao 0:fc7ce4e596ff 79 while (1) {
alexhrao 0:fc7ce4e596ff 80 // if we have a point, sound?
alexhrao 0:fc7ce4e596ff 81 if (is_point && is_new) {
alexhrao 0:fc7ce4e596ff 82 is_new = 0;
alexhrao 0:fc7ce4e596ff 83 // sound that
alexhrao 0:fc7ce4e596ff 84 speaker = 1;
alexhrao 0:fc7ce4e596ff 85
alexhrao 0:fc7ce4e596ff 86 } else if (!is_point) {
alexhrao 0:fc7ce4e596ff 87 is_new = 1;
alexhrao 0:fc7ce4e596ff 88 speaker = 0;
alexhrao 0:fc7ce4e596ff 89 }
alexhrao 0:fc7ce4e596ff 90 Thread::wait(10);
alexhrao 0:fc7ce4e596ff 91 }
alexhrao 0:fc7ce4e596ff 92 }
alexhrao 0:fc7ce4e596ff 93
alexhrao 0:fc7ce4e596ff 94 void print_points(void) {
alexhrao 0:fc7ce4e596ff 95 int prev_points = points;
alexhrao 0:fc7ce4e596ff 96 while (1) {
alexhrao 0:fc7ce4e596ff 97 if (points != prev_points) {
alexhrao 0:fc7ce4e596ff 98 lcd_lock.lock();
alexhrao 0:fc7ce4e596ff 99 uLCD.locate(1, 2);
alexhrao 0:fc7ce4e596ff 100 uLCD.text_width(2);
alexhrao 0:fc7ce4e596ff 101 uLCD.text_height(2);
alexhrao 0:fc7ce4e596ff 102 uLCD.color(GREEN);
alexhrao 0:fc7ce4e596ff 103 uLCD.printf("%d", points);
alexhrao 0:fc7ce4e596ff 104 lcd_lock.unlock();
alexhrao 0:fc7ce4e596ff 105 prev_points = points;
alexhrao 0:fc7ce4e596ff 106 }
alexhrao 0:fc7ce4e596ff 107 Thread::wait(100);
alexhrao 0:fc7ce4e596ff 108 }
alexhrao 0:fc7ce4e596ff 109 }
alexhrao 0:fc7ce4e596ff 110
alexhrao 0:fc7ce4e596ff 111 int main() {
alexhrao 0:fc7ce4e596ff 112 // Tell the user we're spinning up
alexhrao 0:fc7ce4e596ff 113
alexhrao 0:fc7ce4e596ff 114 uLCD.cls();
alexhrao 0:fc7ce4e596ff 115 uLCD.text_height(2);
alexhrao 0:fc7ce4e596ff 116 uLCD.text_width(2);
alexhrao 0:fc7ce4e596ff 117 uLCD.locate(1, 2);
alexhrao 0:fc7ce4e596ff 118 uLCD.color(RED);
alexhrao 0:fc7ce4e596ff 119 uLCD.printf("SPINNING UP");
alexhrao 0:fc7ce4e596ff 120
alexhrao 0:fc7ce4e596ff 121 time_left = 60;
alexhrao 0:fc7ce4e596ff 122
alexhrao 0:fc7ce4e596ff 123 // Normally, we should worry about deleting this object. However, it will
alexhrao 0:fc7ce4e596ff 124 // survive for the lifetime of the program!
alexhrao 0:fc7ce4e596ff 125 DevI2C* device_i2c = new DevI2C(VL53L0_I2C_SDA, VL53L0_I2C_SCL);
alexhrao 0:fc7ce4e596ff 126 lidar = XNucleo53L0A1::instance(device_i2c, A2, D8, D2);
alexhrao 0:fc7ce4e596ff 127 // Reset shdn (?):
alexhrao 0:fc7ce4e596ff 128 shdn = 0;
alexhrao 0:fc7ce4e596ff 129 wait(0.1);
alexhrao 0:fc7ce4e596ff 130 shdn = 1;
alexhrao 0:fc7ce4e596ff 131 wait(0.1);
alexhrao 0:fc7ce4e596ff 132
alexhrao 0:fc7ce4e596ff 133 int status = lidar->init_board();
alexhrao 0:fc7ce4e596ff 134 while (status) {
alexhrao 0:fc7ce4e596ff 135 pc.printf("Initializing Board...\r\n");
alexhrao 0:fc7ce4e596ff 136 status = lidar->init_board();
alexhrao 0:fc7ce4e596ff 137 status_led = !status_led;
alexhrao 0:fc7ce4e596ff 138 wait(1);
alexhrao 0:fc7ce4e596ff 139 }
alexhrao 0:fc7ce4e596ff 140 pc.printf("Finished Initialization\r\n");
alexhrao 0:fc7ce4e596ff 141
alexhrao 0:fc7ce4e596ff 142 // Continually measure distance
alexhrao 0:fc7ce4e596ff 143 while (1) {
alexhrao 0:fc7ce4e596ff 144 uint32_t dist2;
alexhrao 0:fc7ce4e596ff 145 status = lidar->sensor_centre->get_distance(&dist2);
alexhrao 0:fc7ce4e596ff 146 if (status != VL53L0X_ERROR_NONE) {
alexhrao 0:fc7ce4e596ff 147 pc.printf("Status: %d\r\n", status);
alexhrao 0:fc7ce4e596ff 148 err_led = 1;
alexhrao 0:fc7ce4e596ff 149 }
alexhrao 0:fc7ce4e596ff 150 // Determine if we've hit the threshold (4 cases):
alexhrao 0:fc7ce4e596ff 151 // 1. NOT in the threshold, current distance is above
alexhrao 0:fc7ce4e596ff 152 // 2. NOT in the threshold, current distance is below
alexhrao 0:fc7ce4e596ff 153 // 3. IN the threshold, current distance is above
alexhrao 0:fc7ce4e596ff 154 // 4. IN the threshold, current distance is below
alexhrao 0:fc7ce4e596ff 155 if (!is_point && dist2 <= threshold) {
alexhrao 0:fc7ce4e596ff 156 // increment our points
alexhrao 0:fc7ce4e596ff 157 ++points;
alexhrao 0:fc7ce4e596ff 158 is_point = true;
alexhrao 0:fc7ce4e596ff 159 point_led = 1;
alexhrao 0:fc7ce4e596ff 160 } else if (is_point && dist2 > threshold) {
alexhrao 0:fc7ce4e596ff 161 is_point = false;
alexhrao 0:fc7ce4e596ff 162 point_led = 0;
alexhrao 0:fc7ce4e596ff 163 }
alexhrao 0:fc7ce4e596ff 164 }
alexhrao 0:fc7ce4e596ff 165 }