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:
Thu Apr 18 16:43:34 2019 +0000
Revision:
4:44b4158fc494
Parent:
3:efb572ef5f15
Child:
5:b566bcb20d94
Remove LED completely

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 1:e82997c13013 6 #include <time.h>
alexhrao 0:fc7ce4e596ff 7
alexhrao 0:fc7ce4e596ff 8 #define VL53L0_I2C_SDA p28
alexhrao 0:fc7ce4e596ff 9 #define VL53L0_I2C_SCL p27
alexhrao 0:fc7ce4e596ff 10
alexhrao 2:54e1a301de72 11 // We need this for being able to reset the MBED (similar to watch dogs)
alexhrao 3:efb572ef5f15 12 // extern "C" void mbed_reset();
alexhrao 0:fc7ce4e596ff 13
alexhrao 2:54e1a301de72 14 // Game Data
alexhrao 1:e82997c13013 15 volatile int num_points = 0;
alexhrao 0:fc7ce4e596ff 16 volatile int time_left = 0;
alexhrao 0:fc7ce4e596ff 17 volatile bool is_point = false;
alexhrao 0:fc7ce4e596ff 18
alexhrao 0:fc7ce4e596ff 19 // LIDAR
alexhrao 0:fc7ce4e596ff 20 static XNucleo53L0A1* lidar = NULL;
alexhrao 0:fc7ce4e596ff 21 uint32_t dist;
alexhrao 0:fc7ce4e596ff 22 DigitalOut shdn(p26);
alexhrao 0:fc7ce4e596ff 23
alexhrao 0:fc7ce4e596ff 24 // LCD
alexhrao 0:fc7ce4e596ff 25 Mutex lcd_lock;
alexhrao 2:54e1a301de72 26 // **NOTE**: do NOT use p9, p10, p11
alexhrao 1:e82997c13013 27 // There is a bug in LIDAR system and uLCD system - they can't BOTH BE ON
alexhrao 1:e82997c13013 28 // I2C! It just so happens, p9 and p10 are I2C, so the systems will get
alexhrao 2:54e1a301de72 29 // confused, and you'll get weird hangs with the uLCD screen!
alexhrao 1:e82997c13013 30 uLCD_4DGL uLCD(p13, p14, p15); // serial tx, serial rx, reset pin;
alexhrao 0:fc7ce4e596ff 31
alexhrao 0:fc7ce4e596ff 32 // Speaker
alexhrao 0:fc7ce4e596ff 33 PwmOut speaker(p25);
alexhrao 0:fc7ce4e596ff 34
alexhrao 0:fc7ce4e596ff 35 // Threads
alexhrao 0:fc7ce4e596ff 36 Thread sound_thread;
alexhrao 0:fc7ce4e596ff 37 Thread time_thread;
alexhrao 0:fc7ce4e596ff 38 Thread points_thread;
alexhrao 0:fc7ce4e596ff 39
alexhrao 0:fc7ce4e596ff 40 // Debugging
alexhrao 2:54e1a301de72 41 Serial pc(USBTX, USBRX);
alexhrao 0:fc7ce4e596ff 42 DigitalOut err_led(LED1);
alexhrao 1:e82997c13013 43
alexhrao 0:fc7ce4e596ff 44 void time(void) {
alexhrao 0:fc7ce4e596ff 45 while (1) {
alexhrao 0:fc7ce4e596ff 46 // every second, change LED and update screen
alexhrao 0:fc7ce4e596ff 47 if (time_left > 0) {
alexhrao 0:fc7ce4e596ff 48 lcd_lock.lock();
alexhrao 1:e82997c13013 49 uLCD.locate(4, 5);
alexhrao 0:fc7ce4e596ff 50 uLCD.text_height(2);
alexhrao 0:fc7ce4e596ff 51 uLCD.text_width(2);
alexhrao 1:e82997c13013 52 uLCD.color(RED);
alexhrao 1:e82997c13013 53 uLCD.printf("%02d", time_left);
alexhrao 0:fc7ce4e596ff 54 lcd_lock.unlock();
alexhrao 0:fc7ce4e596ff 55 Thread::wait(1000);
alexhrao 1:e82997c13013 56 time_left--;
alexhrao 0:fc7ce4e596ff 57 } else {
alexhrao 0:fc7ce4e596ff 58 break;
alexhrao 0:fc7ce4e596ff 59 }
alexhrao 0:fc7ce4e596ff 60 }
alexhrao 2:54e1a301de72 61
alexhrao 0:fc7ce4e596ff 62 lcd_lock.lock();
alexhrao 3:efb572ef5f15 63 uLCD.locate(0, 5);
alexhrao 0:fc7ce4e596ff 64 uLCD.text_height(2);
alexhrao 0:fc7ce4e596ff 65 uLCD.text_width(2);
alexhrao 1:e82997c13013 66 uLCD.color(RED);
alexhrao 3:efb572ef5f15 67 uLCD.printf("GAME OVER");
alexhrao 0:fc7ce4e596ff 68 lcd_lock.unlock();
alexhrao 2:54e1a301de72 69 // We're done - thread will die
alexhrao 0:fc7ce4e596ff 70 }
alexhrao 0:fc7ce4e596ff 71 void sound(void) {
alexhrao 0:fc7ce4e596ff 72 int is_new = 1;
alexhrao 0:fc7ce4e596ff 73 speaker.period(1.0 / 900.0);
alexhrao 0:fc7ce4e596ff 74 while (1) {
alexhrao 2:54e1a301de72 75 // if we have a point, sound
alexhrao 2:54e1a301de72 76 // If there's no more time left, die
alexhrao 2:54e1a301de72 77 if (time_left <= 0) {
alexhrao 2:54e1a301de72 78 break;
alexhrao 2:54e1a301de72 79 } else if (is_point && is_new == 1) {
alexhrao 0:fc7ce4e596ff 80 is_new = 0;
alexhrao 0:fc7ce4e596ff 81 // sound that
alexhrao 1:e82997c13013 82 speaker = 0.5;
alexhrao 1:e82997c13013 83 Thread::wait(100);
alexhrao 1:e82997c13013 84 speaker = 0;
alexhrao 0:fc7ce4e596ff 85 } else if (!is_point) {
alexhrao 2:54e1a301de72 86 // We've engaged, get ready for next
alexhrao 0:fc7ce4e596ff 87 is_new = 1;
alexhrao 0:fc7ce4e596ff 88 }
alexhrao 0:fc7ce4e596ff 89 Thread::wait(10);
alexhrao 0:fc7ce4e596ff 90 }
alexhrao 0:fc7ce4e596ff 91 }
alexhrao 0:fc7ce4e596ff 92
alexhrao 1:e82997c13013 93 void points(void) {
alexhrao 1:e82997c13013 94 int prev_points = -1;
alexhrao 3:efb572ef5f15 95 bool prev_is_point = false;
alexhrao 0:fc7ce4e596ff 96 while (1) {
alexhrao 2:54e1a301de72 97 if (time_left <= 0) {
alexhrao 1:e82997c13013 98 break;
alexhrao 2:54e1a301de72 99 } else if (num_points != prev_points) {
alexhrao 0:fc7ce4e596ff 100 lcd_lock.lock();
alexhrao 1:e82997c13013 101 uLCD.locate(4, 2);
alexhrao 0:fc7ce4e596ff 102 uLCD.text_width(2);
alexhrao 0:fc7ce4e596ff 103 uLCD.text_height(2);
alexhrao 0:fc7ce4e596ff 104 uLCD.color(GREEN);
alexhrao 1:e82997c13013 105 uLCD.printf("%d", num_points);
alexhrao 0:fc7ce4e596ff 106 lcd_lock.unlock();
alexhrao 1:e82997c13013 107 prev_points = num_points;
alexhrao 0:fc7ce4e596ff 108 }
alexhrao 3:efb572ef5f15 109 if (is_point && !prev_is_point) {
alexhrao 3:efb572ef5f15 110 // changed from no to yes
alexhrao 3:efb572ef5f15 111 lcd_lock.lock();
alexhrao 3:efb572ef5f15 112 uLCD.filled_circle(8, 0, 1, GREEN);
alexhrao 3:efb572ef5f15 113 lcd_lock.unlock();
alexhrao 3:efb572ef5f15 114 } else if (!is_point && prev_is_point) {
alexhrao 3:efb572ef5f15 115 lcd_lock.lock();
alexhrao 3:efb572ef5f15 116 uLCD.filled_circle(8, 0, 1, BLACK);
alexhrao 3:efb572ef5f15 117 lcd_lock.unlock();
alexhrao 3:efb572ef5f15 118 }
alexhrao 0:fc7ce4e596ff 119 Thread::wait(100);
alexhrao 0:fc7ce4e596ff 120 }
alexhrao 3:efb572ef5f15 121 lcd_lock.lock();
alexhrao 3:efb572ef5f15 122 uLCD.filled_circle(8, 0, 1, RED);
alexhrao 3:efb572ef5f15 123 lcd_lock.unlock();
alexhrao 0:fc7ce4e596ff 124 }
alexhrao 0:fc7ce4e596ff 125
alexhrao 0:fc7ce4e596ff 126 int main() {
alexhrao 0:fc7ce4e596ff 127 // Tell the user we're spinning up
alexhrao 0:fc7ce4e596ff 128 uLCD.cls();
alexhrao 0:fc7ce4e596ff 129 uLCD.text_height(2);
alexhrao 0:fc7ce4e596ff 130 uLCD.text_width(2);
alexhrao 2:54e1a301de72 131 uLCD.locate(0, 0);
alexhrao 0:fc7ce4e596ff 132 uLCD.color(RED);
alexhrao 2:54e1a301de72 133 uLCD.printf("SPINNING\nUP");
alexhrao 2:54e1a301de72 134
alexhrao 2:54e1a301de72 135 // Initialize
alexhrao 3:efb572ef5f15 136 int threshold = 100;
alexhrao 3:efb572ef5f15 137 time_left = 60;
alexhrao 3:efb572ef5f15 138 is_point = false;
alexhrao 0:fc7ce4e596ff 139
alexhrao 0:fc7ce4e596ff 140 DevI2C* device_i2c = new DevI2C(VL53L0_I2C_SDA, VL53L0_I2C_SCL);
alexhrao 0:fc7ce4e596ff 141 lidar = XNucleo53L0A1::instance(device_i2c, A2, D8, D2);
alexhrao 1:e82997c13013 142
alexhrao 0:fc7ce4e596ff 143 // Reset shdn (?):
alexhrao 0:fc7ce4e596ff 144 shdn = 0;
alexhrao 0:fc7ce4e596ff 145 wait(0.1);
alexhrao 0:fc7ce4e596ff 146 shdn = 1;
alexhrao 0:fc7ce4e596ff 147 wait(0.1);
alexhrao 0:fc7ce4e596ff 148
alexhrao 0:fc7ce4e596ff 149 int status = lidar->init_board();
alexhrao 0:fc7ce4e596ff 150 while (status) {
alexhrao 2:54e1a301de72 151 err_led = 1;
alexhrao 0:fc7ce4e596ff 152 status = lidar->init_board();
alexhrao 0:fc7ce4e596ff 153 wait(1);
alexhrao 0:fc7ce4e596ff 154 }
alexhrao 2:54e1a301de72 155 err_led = 0;
alexhrao 2:54e1a301de72 156
alexhrao 1:e82997c13013 157 uLCD.cls();
alexhrao 1:e82997c13013 158 uLCD.locate(0, 0);
alexhrao 1:e82997c13013 159 uLCD.color(GREEN);
alexhrao 1:e82997c13013 160 uLCD.text_height(2);
alexhrao 1:e82997c13013 161 uLCD.text_width(2);
alexhrao 2:54e1a301de72 162 uLCD.printf("Want To\nPlay?");
alexhrao 2:54e1a301de72 163
alexhrao 4:44b4158fc494 164 // Wait for the LIDAR
alexhrao 3:efb572ef5f15 165 while(true){
alexhrao 3:efb572ef5f15 166 if(lidar->sensor_centre->get_distance(&dist) == VL53L0X_ERROR_NONE){
alexhrao 3:efb572ef5f15 167 if(dist<100){
alexhrao 3:efb572ef5f15 168 break;
alexhrao 3:efb572ef5f15 169 }
alexhrao 3:efb572ef5f15 170 }
alexhrao 3:efb572ef5f15 171 }
alexhrao 3:efb572ef5f15 172
alexhrao 1:e82997c13013 173 uLCD.cls();
alexhrao 1:e82997c13013 174 uLCD.text_width(2);
alexhrao 1:e82997c13013 175 uLCD.text_height(2);
alexhrao 1:e82997c13013 176 uLCD.locate(1, 1);
alexhrao 1:e82997c13013 177 uLCD.color(GREEN);
alexhrao 1:e82997c13013 178 uLCD.printf("POINTS");
alexhrao 2:54e1a301de72 179
alexhrao 1:e82997c13013 180 uLCD.locate(2, 4);
alexhrao 1:e82997c13013 181 uLCD.color(RED);
alexhrao 1:e82997c13013 182 uLCD.printf("TIME");
alexhrao 2:54e1a301de72 183
alexhrao 2:54e1a301de72 184 // Start up threads
alexhrao 1:e82997c13013 185 time_thread.start(time);
alexhrao 1:e82997c13013 186 points_thread.start(points);
alexhrao 1:e82997c13013 187 sound_thread.start(sound);
alexhrao 1:e82997c13013 188
alexhrao 0:fc7ce4e596ff 189 // Continually measure distance
alexhrao 0:fc7ce4e596ff 190 while (1) {
alexhrao 1:e82997c13013 191 status = lidar->sensor_centre->get_distance(&dist);
alexhrao 1:e82997c13013 192 if (status == VL53L0X_ERROR_NONE) {
alexhrao 2:54e1a301de72 193
alexhrao 1:e82997c13013 194 // Determine if we've hit the threshold (4 cases):
alexhrao 1:e82997c13013 195 // 1. NOT in the threshold, current distance is above
alexhrao 1:e82997c13013 196 // 2. NOT in the threshold, current distance is below
alexhrao 1:e82997c13013 197 // 3. IN the threshold, current distance is above
alexhrao 1:e82997c13013 198 // 4. IN the threshold, current distance is below
alexhrao 1:e82997c13013 199 //pc.printf("D=%ld mm\r\n", dist);
alexhrao 1:e82997c13013 200 if (!is_point && dist <= threshold) {
alexhrao 3:efb572ef5f15 201 // We don't need to check this every time - save our previous
alexhrao 3:efb572ef5f15 202 // loop time!
alexhrao 1:e82997c13013 203 if (time_left == 0)
alexhrao 1:e82997c13013 204 break;
alexhrao 1:e82997c13013 205 // increment our points
alexhrao 1:e82997c13013 206 ++num_points;
alexhrao 1:e82997c13013 207 is_point = true;
alexhrao 1:e82997c13013 208 } else if (is_point && dist > threshold) {
alexhrao 1:e82997c13013 209 is_point = false;
alexhrao 1:e82997c13013 210 }
alexhrao 1:e82997c13013 211 } else {
alexhrao 0:fc7ce4e596ff 212 is_point = false;
alexhrao 0:fc7ce4e596ff 213 }
alexhrao 0:fc7ce4e596ff 214 }
alexhrao 3:efb572ef5f15 215 // We have nothing left to do... Just wait
alexhrao 3:efb572ef5f15 216 while(1);
alexhrao 2:54e1a301de72 217 }