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 21:05:21 2019 +0000
Revision:
2:54e1a301de72
Parent:
1:e82997c13013
Child:
3:efb572ef5f15
Clean up code

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