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 17:36:20 2019 +0000
Revision:
5:b566bcb20d94
Parent:
4:44b4158fc494
Child:
6:b720519bdfe8
Revise virtual LED

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 5:b566bcb20d94 96 lcd_lock.lock();
alexhrao 5:b566bcb20d94 97 uLCD.circle(110, 10, 10, GREEN);
alexhrao 5:b566bcb20d94 98 lcd_lock.unlock();
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 5:b566bcb20d94 115 uLCD.filled_circle(110, 10, 9, GREEN);
alexhrao 3:efb572ef5f15 116 lcd_lock.unlock();
alexhrao 5:b566bcb20d94 117 prev_is_point = is_point;
alexhrao 3:efb572ef5f15 118 } else if (!is_point && prev_is_point) {
alexhrao 3:efb572ef5f15 119 lcd_lock.lock();
alexhrao 5:b566bcb20d94 120 uLCD.filled_circle(110, 10, 9, BLACK);
alexhrao 3:efb572ef5f15 121 lcd_lock.unlock();
alexhrao 5:b566bcb20d94 122 prev_is_point = is_point;
alexhrao 3:efb572ef5f15 123 }
alexhrao 0:fc7ce4e596ff 124 Thread::wait(100);
alexhrao 0:fc7ce4e596ff 125 }
alexhrao 3:efb572ef5f15 126 lcd_lock.lock();
alexhrao 5:b566bcb20d94 127 uLCD.filled_circle(110, 10, 10, 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 is_point = false;
alexhrao 0:fc7ce4e596ff 144
alexhrao 0:fc7ce4e596ff 145 DevI2C* device_i2c = new DevI2C(VL53L0_I2C_SDA, VL53L0_I2C_SCL);
alexhrao 0:fc7ce4e596ff 146 lidar = XNucleo53L0A1::instance(device_i2c, A2, D8, D2);
alexhrao 1:e82997c13013 147
alexhrao 0:fc7ce4e596ff 148 // Reset shdn (?):
alexhrao 0:fc7ce4e596ff 149 shdn = 0;
alexhrao 0:fc7ce4e596ff 150 wait(0.1);
alexhrao 0:fc7ce4e596ff 151 shdn = 1;
alexhrao 0:fc7ce4e596ff 152 wait(0.1);
alexhrao 0:fc7ce4e596ff 153
alexhrao 0:fc7ce4e596ff 154 int status = lidar->init_board();
alexhrao 0:fc7ce4e596ff 155 while (status) {
alexhrao 2:54e1a301de72 156 err_led = 1;
alexhrao 0:fc7ce4e596ff 157 status = lidar->init_board();
alexhrao 0:fc7ce4e596ff 158 wait(1);
alexhrao 0:fc7ce4e596ff 159 }
alexhrao 2:54e1a301de72 160 err_led = 0;
alexhrao 2:54e1a301de72 161
alexhrao 1:e82997c13013 162 uLCD.cls();
alexhrao 1:e82997c13013 163 uLCD.locate(0, 0);
alexhrao 1:e82997c13013 164 uLCD.color(GREEN);
alexhrao 1:e82997c13013 165 uLCD.text_height(2);
alexhrao 1:e82997c13013 166 uLCD.text_width(2);
alexhrao 2:54e1a301de72 167 uLCD.printf("Want To\nPlay?");
alexhrao 2:54e1a301de72 168
alexhrao 4:44b4158fc494 169 // Wait for the LIDAR
alexhrao 3:efb572ef5f15 170 while(true){
alexhrao 3:efb572ef5f15 171 if(lidar->sensor_centre->get_distance(&dist) == VL53L0X_ERROR_NONE){
alexhrao 3:efb572ef5f15 172 if(dist<100){
alexhrao 3:efb572ef5f15 173 break;
alexhrao 3:efb572ef5f15 174 }
alexhrao 3:efb572ef5f15 175 }
alexhrao 3:efb572ef5f15 176 }
alexhrao 3:efb572ef5f15 177
alexhrao 1:e82997c13013 178 uLCD.cls();
alexhrao 1:e82997c13013 179 uLCD.text_width(2);
alexhrao 1:e82997c13013 180 uLCD.text_height(2);
alexhrao 1:e82997c13013 181 uLCD.locate(1, 1);
alexhrao 1:e82997c13013 182 uLCD.color(GREEN);
alexhrao 1:e82997c13013 183 uLCD.printf("POINTS");
alexhrao 2:54e1a301de72 184
alexhrao 1:e82997c13013 185 uLCD.locate(2, 4);
alexhrao 1:e82997c13013 186 uLCD.color(RED);
alexhrao 1:e82997c13013 187 uLCD.printf("TIME");
alexhrao 2:54e1a301de72 188
alexhrao 2:54e1a301de72 189 // Start up threads
alexhrao 1:e82997c13013 190 time_thread.start(time);
alexhrao 1:e82997c13013 191 points_thread.start(points);
alexhrao 1:e82997c13013 192 sound_thread.start(sound);
alexhrao 1:e82997c13013 193
alexhrao 0:fc7ce4e596ff 194 // Continually measure distance
alexhrao 0:fc7ce4e596ff 195 while (1) {
alexhrao 1:e82997c13013 196 status = lidar->sensor_centre->get_distance(&dist);
alexhrao 1:e82997c13013 197 if (status == VL53L0X_ERROR_NONE) {
alexhrao 2:54e1a301de72 198
alexhrao 1:e82997c13013 199 // Determine if we've hit the threshold (4 cases):
alexhrao 1:e82997c13013 200 // 1. NOT in the threshold, current distance is above
alexhrao 1:e82997c13013 201 // 2. NOT in the threshold, current distance is below
alexhrao 1:e82997c13013 202 // 3. IN the threshold, current distance is above
alexhrao 1:e82997c13013 203 // 4. IN the threshold, current distance is below
alexhrao 1:e82997c13013 204 //pc.printf("D=%ld mm\r\n", dist);
alexhrao 1:e82997c13013 205 if (!is_point && dist <= threshold) {
alexhrao 3:efb572ef5f15 206 // We don't need to check this every time - save our previous
alexhrao 3:efb572ef5f15 207 // loop time!
alexhrao 1:e82997c13013 208 if (time_left == 0)
alexhrao 1:e82997c13013 209 break;
alexhrao 1:e82997c13013 210 // increment our points
alexhrao 1:e82997c13013 211 ++num_points;
alexhrao 1:e82997c13013 212 is_point = true;
alexhrao 1:e82997c13013 213 } else if (is_point && dist > threshold) {
alexhrao 1:e82997c13013 214 is_point = false;
alexhrao 1:e82997c13013 215 }
alexhrao 1:e82997c13013 216 } else {
alexhrao 0:fc7ce4e596ff 217 is_point = false;
alexhrao 0:fc7ce4e596ff 218 }
alexhrao 0:fc7ce4e596ff 219 }
alexhrao 3:efb572ef5f15 220 // We have nothing left to do... Just wait
alexhrao 3:efb572ef5f15 221 while(1);
alexhrao 2:54e1a301de72 222 }