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:41:34 2019 +0000
Revision:
3:efb572ef5f15
Parent:
2:54e1a301de72
Child:
4:44b4158fc494
Move LED to be virtual on LCD

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 // LEDs
alexhrao 0:fc7ce4e596ff 36 PwmOut point_led(p21);
alexhrao 0:fc7ce4e596ff 37
alexhrao 0:fc7ce4e596ff 38 // Threads
alexhrao 0:fc7ce4e596ff 39 Thread sound_thread;
alexhrao 0:fc7ce4e596ff 40 Thread time_thread;
alexhrao 0:fc7ce4e596ff 41 Thread points_thread;
alexhrao 0:fc7ce4e596ff 42
alexhrao 0:fc7ce4e596ff 43 // Debugging
alexhrao 2:54e1a301de72 44 Serial pc(USBTX, USBRX);
alexhrao 0:fc7ce4e596ff 45 DigitalOut err_led(LED1);
alexhrao 1:e82997c13013 46
alexhrao 0:fc7ce4e596ff 47 void time(void) {
alexhrao 0:fc7ce4e596ff 48 while (1) {
alexhrao 0:fc7ce4e596ff 49 // every second, change LED and update screen
alexhrao 0:fc7ce4e596ff 50 if (time_left > 0) {
alexhrao 0:fc7ce4e596ff 51 lcd_lock.lock();
alexhrao 1:e82997c13013 52 uLCD.locate(4, 5);
alexhrao 0:fc7ce4e596ff 53 uLCD.text_height(2);
alexhrao 0:fc7ce4e596ff 54 uLCD.text_width(2);
alexhrao 1:e82997c13013 55 uLCD.color(RED);
alexhrao 1:e82997c13013 56 uLCD.printf("%02d", time_left);
alexhrao 0:fc7ce4e596ff 57 lcd_lock.unlock();
alexhrao 0:fc7ce4e596ff 58 Thread::wait(1000);
alexhrao 1:e82997c13013 59 time_left--;
alexhrao 0:fc7ce4e596ff 60 } else {
alexhrao 0:fc7ce4e596ff 61 break;
alexhrao 0:fc7ce4e596ff 62 }
alexhrao 0:fc7ce4e596ff 63 }
alexhrao 2:54e1a301de72 64
alexhrao 0:fc7ce4e596ff 65 lcd_lock.lock();
alexhrao 3:efb572ef5f15 66 uLCD.locate(0, 5);
alexhrao 0:fc7ce4e596ff 67 uLCD.text_height(2);
alexhrao 0:fc7ce4e596ff 68 uLCD.text_width(2);
alexhrao 1:e82997c13013 69 uLCD.color(RED);
alexhrao 3:efb572ef5f15 70 uLCD.printf("GAME OVER");
alexhrao 0:fc7ce4e596ff 71 lcd_lock.unlock();
alexhrao 2:54e1a301de72 72 // We're done - thread will die
alexhrao 0:fc7ce4e596ff 73 }
alexhrao 0:fc7ce4e596ff 74 void sound(void) {
alexhrao 0:fc7ce4e596ff 75 int is_new = 1;
alexhrao 0:fc7ce4e596ff 76 speaker.period(1.0 / 900.0);
alexhrao 0:fc7ce4e596ff 77 while (1) {
alexhrao 2:54e1a301de72 78 // if we have a point, sound
alexhrao 2:54e1a301de72 79 // If there's no more time left, die
alexhrao 2:54e1a301de72 80 if (time_left <= 0) {
alexhrao 2:54e1a301de72 81 break;
alexhrao 2:54e1a301de72 82 } else if (is_point && is_new == 1) {
alexhrao 0:fc7ce4e596ff 83 is_new = 0;
alexhrao 0:fc7ce4e596ff 84 // sound that
alexhrao 1:e82997c13013 85 speaker = 0.5;
alexhrao 1:e82997c13013 86 Thread::wait(100);
alexhrao 1:e82997c13013 87 speaker = 0;
alexhrao 0:fc7ce4e596ff 88 } else if (!is_point) {
alexhrao 2:54e1a301de72 89 // We've engaged, get ready for next
alexhrao 0:fc7ce4e596ff 90 is_new = 1;
alexhrao 0:fc7ce4e596ff 91 }
alexhrao 0:fc7ce4e596ff 92 Thread::wait(10);
alexhrao 0:fc7ce4e596ff 93 }
alexhrao 0:fc7ce4e596ff 94 }
alexhrao 0:fc7ce4e596ff 95
alexhrao 1:e82997c13013 96 void points(void) {
alexhrao 1:e82997c13013 97 int prev_points = -1;
alexhrao 3:efb572ef5f15 98 bool prev_is_point = false;
alexhrao 0:fc7ce4e596ff 99 while (1) {
alexhrao 2:54e1a301de72 100 if (time_left <= 0) {
alexhrao 1:e82997c13013 101 break;
alexhrao 2:54e1a301de72 102 } else if (num_points != prev_points) {
alexhrao 0:fc7ce4e596ff 103 lcd_lock.lock();
alexhrao 1:e82997c13013 104 uLCD.locate(4, 2);
alexhrao 0:fc7ce4e596ff 105 uLCD.text_width(2);
alexhrao 0:fc7ce4e596ff 106 uLCD.text_height(2);
alexhrao 0:fc7ce4e596ff 107 uLCD.color(GREEN);
alexhrao 1:e82997c13013 108 uLCD.printf("%d", num_points);
alexhrao 0:fc7ce4e596ff 109 lcd_lock.unlock();
alexhrao 1:e82997c13013 110 prev_points = num_points;
alexhrao 0:fc7ce4e596ff 111 }
alexhrao 3:efb572ef5f15 112 if (is_point && !prev_is_point) {
alexhrao 3:efb572ef5f15 113 // changed from no to yes
alexhrao 3:efb572ef5f15 114 lcd_lock.lock();
alexhrao 3:efb572ef5f15 115 uLCD.filled_circle(8, 0, 1, GREEN);
alexhrao 3:efb572ef5f15 116 lcd_lock.unlock();
alexhrao 3:efb572ef5f15 117 point_led = 1;
alexhrao 3:efb572ef5f15 118 } else if (!is_point && prev_is_point) {
alexhrao 3:efb572ef5f15 119 lcd_lock.lock();
alexhrao 3:efb572ef5f15 120 uLCD.filled_circle(8, 0, 1, BLACK);
alexhrao 3:efb572ef5f15 121 lcd_lock.unlock();
alexhrao 3:efb572ef5f15 122 point_led = 0;
alexhrao 3:efb572ef5f15 123 }
alexhrao 0:fc7ce4e596ff 124 Thread::wait(100);
alexhrao 0:fc7ce4e596ff 125 }
alexhrao 3:efb572ef5f15 126 lcd_lock.lock();
alexhrao 3:efb572ef5f15 127 uLCD.filled_circle(8, 0, 1, RED);
alexhrao 3:efb572ef5f15 128 lcd_lock.unlock();
alexhrao 0:fc7ce4e596ff 129 }
alexhrao 0:fc7ce4e596ff 130
alexhrao 0:fc7ce4e596ff 131 int main() {
alexhrao 0:fc7ce4e596ff 132 // Tell the user we're spinning up
alexhrao 0:fc7ce4e596ff 133 uLCD.cls();
alexhrao 0:fc7ce4e596ff 134 uLCD.text_height(2);
alexhrao 0:fc7ce4e596ff 135 uLCD.text_width(2);
alexhrao 2:54e1a301de72 136 uLCD.locate(0, 0);
alexhrao 0:fc7ce4e596ff 137 uLCD.color(RED);
alexhrao 2:54e1a301de72 138 uLCD.printf("SPINNING\nUP");
alexhrao 2:54e1a301de72 139
alexhrao 2:54e1a301de72 140 // Initialize
alexhrao 3:efb572ef5f15 141 int threshold = 100;
alexhrao 3:efb572ef5f15 142 time_left = 60;
alexhrao 3:efb572ef5f15 143 point_led = 0;
alexhrao 3:efb572ef5f15 144 is_point = false;
alexhrao 0:fc7ce4e596ff 145
alexhrao 0:fc7ce4e596ff 146 DevI2C* device_i2c = new DevI2C(VL53L0_I2C_SDA, VL53L0_I2C_SCL);
alexhrao 0:fc7ce4e596ff 147 lidar = XNucleo53L0A1::instance(device_i2c, A2, D8, D2);
alexhrao 1:e82997c13013 148
alexhrao 0:fc7ce4e596ff 149 // Reset shdn (?):
alexhrao 0:fc7ce4e596ff 150 shdn = 0;
alexhrao 0:fc7ce4e596ff 151 wait(0.1);
alexhrao 0:fc7ce4e596ff 152 shdn = 1;
alexhrao 0:fc7ce4e596ff 153 wait(0.1);
alexhrao 0:fc7ce4e596ff 154
alexhrao 0:fc7ce4e596ff 155 int status = lidar->init_board();
alexhrao 0:fc7ce4e596ff 156 while (status) {
alexhrao 2:54e1a301de72 157 err_led = 1;
alexhrao 0:fc7ce4e596ff 158 status = lidar->init_board();
alexhrao 0:fc7ce4e596ff 159 wait(1);
alexhrao 0:fc7ce4e596ff 160 }
alexhrao 2:54e1a301de72 161 err_led = 0;
alexhrao 2:54e1a301de72 162
alexhrao 1:e82997c13013 163 uLCD.cls();
alexhrao 1:e82997c13013 164 uLCD.locate(0, 0);
alexhrao 1:e82997c13013 165 uLCD.color(GREEN);
alexhrao 1:e82997c13013 166 uLCD.text_height(2);
alexhrao 1:e82997c13013 167 uLCD.text_width(2);
alexhrao 2:54e1a301de72 168 uLCD.printf("Want To\nPlay?");
alexhrao 2:54e1a301de72 169
alexhrao 3:efb572ef5f15 170 point_led = 1;
alexhrao 2:54e1a301de72 171 // Wait for the button
alexhrao 3:efb572ef5f15 172 while(true){
alexhrao 3:efb572ef5f15 173 if(lidar->sensor_centre->get_distance(&dist) == VL53L0X_ERROR_NONE){
alexhrao 3:efb572ef5f15 174 if(dist<100){
alexhrao 3:efb572ef5f15 175 break;
alexhrao 3:efb572ef5f15 176 }
alexhrao 3:efb572ef5f15 177 }
alexhrao 3:efb572ef5f15 178 }
alexhrao 3:efb572ef5f15 179
alexhrao 3:efb572ef5f15 180 point_led = 0;
alexhrao 2:54e1a301de72 181
alexhrao 1:e82997c13013 182 uLCD.cls();
alexhrao 1:e82997c13013 183 uLCD.text_width(2);
alexhrao 1:e82997c13013 184 uLCD.text_height(2);
alexhrao 1:e82997c13013 185 uLCD.locate(1, 1);
alexhrao 1:e82997c13013 186 uLCD.color(GREEN);
alexhrao 1:e82997c13013 187 uLCD.printf("POINTS");
alexhrao 2:54e1a301de72 188
alexhrao 1:e82997c13013 189 uLCD.locate(2, 4);
alexhrao 1:e82997c13013 190 uLCD.color(RED);
alexhrao 1:e82997c13013 191 uLCD.printf("TIME");
alexhrao 2:54e1a301de72 192
alexhrao 2:54e1a301de72 193 // Start up threads
alexhrao 1:e82997c13013 194 time_thread.start(time);
alexhrao 1:e82997c13013 195 points_thread.start(points);
alexhrao 1:e82997c13013 196 sound_thread.start(sound);
alexhrao 1:e82997c13013 197
alexhrao 0:fc7ce4e596ff 198 // Continually measure distance
alexhrao 0:fc7ce4e596ff 199 while (1) {
alexhrao 1:e82997c13013 200 status = lidar->sensor_centre->get_distance(&dist);
alexhrao 1:e82997c13013 201 if (status == VL53L0X_ERROR_NONE) {
alexhrao 2:54e1a301de72 202
alexhrao 1:e82997c13013 203 // Determine if we've hit the threshold (4 cases):
alexhrao 1:e82997c13013 204 // 1. NOT in the threshold, current distance is above
alexhrao 1:e82997c13013 205 // 2. NOT in the threshold, current distance is below
alexhrao 1:e82997c13013 206 // 3. IN the threshold, current distance is above
alexhrao 1:e82997c13013 207 // 4. IN the threshold, current distance is below
alexhrao 1:e82997c13013 208 //pc.printf("D=%ld mm\r\n", dist);
alexhrao 1:e82997c13013 209 if (!is_point && dist <= threshold) {
alexhrao 3:efb572ef5f15 210 // We don't need to check this every time - save our previous
alexhrao 3:efb572ef5f15 211 // loop time!
alexhrao 1:e82997c13013 212 if (time_left == 0)
alexhrao 1:e82997c13013 213 break;
alexhrao 1:e82997c13013 214 // increment our points
alexhrao 1:e82997c13013 215 ++num_points;
alexhrao 1:e82997c13013 216 is_point = true;
alexhrao 1:e82997c13013 217 } else if (is_point && dist > threshold) {
alexhrao 1:e82997c13013 218 is_point = false;
alexhrao 1:e82997c13013 219 }
alexhrao 1:e82997c13013 220 } else {
alexhrao 0:fc7ce4e596ff 221 is_point = false;
alexhrao 0:fc7ce4e596ff 222 }
alexhrao 0:fc7ce4e596ff 223 }
alexhrao 3:efb572ef5f15 224 // We have nothing left to do... Just wait
alexhrao 3:efb572ef5f15 225 while(1);
alexhrao 2:54e1a301de72 226 }