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

main.cpp

Committer:
alexhrao
Date:
2019-04-18
Revision:
6:b720519bdfe8
Parent:
5:b566bcb20d94

File content as of revision 6:b720519bdfe8:

#include "mbed.h"
#include "rtos.h"
#include <stdio.h>
#include "uLCD_4DGL.h"
#include "XNucleo53L0A1.h"
#include <time.h>

#define VL53L0_I2C_SDA   p28
#define VL53L0_I2C_SCL   p27
#define INIT_TIME        90
#define THRESHOLD        100

// We need this for being able to reset the MBED (similar to watch dogs)
extern "C" void mbed_reset();

// Game Data
volatile int num_points = 0;
volatile int time_left = INIT_TIME;
volatile bool is_point = false;
volatile int player_1 = 0;
volatile int player_2 = 0;

int player_1_color = GREEN + RED;
int player_2_color = RED + (.707 * GREEN);

volatile int player_color = GREEN;



// LIDAR
static XNucleo53L0A1* lidar = NULL;
uint32_t dist;
DigitalOut shdn(p26);

// LCD
Mutex lcd_lock;
// **NOTE**: do NOT use p9, p10, p11
// There is a bug in LIDAR system and uLCD system - they can't BOTH BE ON
// I2C! It just so happens, p9 and p10 are I2C, so the systems will get 
// confused, and you'll get weird hangs with the uLCD screen!
uLCD_4DGL uLCD(p13, p14, p15); // serial tx, serial rx, reset pin;

// Speaker
PwmOut speaker(p25);

// Threads
Thread sound_thread;
Thread time_thread;
Thread points_thread;

// Debugging
Serial pc(USBTX, USBRX);
DigitalOut err_led(LED1);

void time(void) {
    while (1) {
        // every second, change LED and update screen
        if (time_left > 0) {
            lcd_lock.lock();
            uLCD.locate(4, 6);
            uLCD.text_height(2);
            uLCD.text_width(2);
            uLCD.color(RED);
            uLCD.printf("%03d", time_left);
            lcd_lock.unlock();
            Thread::wait(1000);
            time_left--;
        } else {
            break;
        }
    }
    lcd_lock.lock();
    uLCD.locate(4, 6);
    uLCD.text_height(2);
    uLCD.text_width(2);
    uLCD.color(RED);
    uLCD.printf("000");
    lcd_lock.unlock();
    // We're done - thread will die
}
void sound(void) {
    int is_new = 1;
    speaker.period(1.0 / 500.0);
    speaker = 0.5;
    Thread::wait(250);
    speaker = 0.0;
    Thread::wait(100);
    speaker = 0.5;
    Thread::wait(250);
    speaker = 0.0;
    speaker.period(1.0 / 900.0);
    while (1) {
        // if we have a point, sound
        // If there's no more time left, die
        if (time_left <= 0) {
            break;
        } else if (is_point && is_new == 1) {
            is_new = 0;
            // sound that
            speaker = 0.5;
            Thread::wait(100);
            speaker = 0;
        } else if (!is_point) {
            // We've engaged, get ready for next
            is_new = 1;
        }
        Thread::wait(10);
    }
    speaker.period(1.0 / 100.0);
    speaker = 0.5;
    Thread::wait(2000);
    speaker = 0.0;
}

void points(void) {
    int prev_points = -1;
    lcd_lock.lock();
    uLCD.circle(110, 10, 10, player_color);
    lcd_lock.unlock();
    while (1) {
        if (time_left <= 0) {
            break;
        } else if (num_points != prev_points) {
            lcd_lock.lock();
            uLCD.locate(4, 3);
            uLCD.text_width(2);
            uLCD.text_height(2);
            uLCD.color(player_color);
            uLCD.printf("%d", num_points);
            lcd_lock.unlock();
            prev_points = num_points;
        }
        if (is_point) {
            // changed from no to yes
            lcd_lock.lock();
            uLCD.filled_circle(110, 10, 9, player_color);
            lcd_lock.unlock();
        } else {
            lcd_lock.lock();
            uLCD.filled_circle(110, 10, 9, BLACK);
            lcd_lock.unlock();
        }
        Thread::wait(10);
    }
}

void run_game() {
        // Initialize
    time_left = INIT_TIME;
    is_point = false;
    
    uLCD.text_width(2);
    uLCD.text_height(2);
    uLCD.locate(1, 2);
    uLCD.color(player_color);
    uLCD.printf("POINTS");
    
    uLCD.locate(2, 5);
    uLCD.color(RED);
    uLCD.printf("TIME");
    
    // Start up threads
    time_thread.start(time);
    points_thread.start(points);
    sound_thread.start(sound);

    // Continually measure distance
    Thread::wait(2000);
    while (1) {
        if (time_left == 0)
            break;
        int status = lidar->sensor_centre->get_distance(&dist);
        if (status == VL53L0X_ERROR_NONE) {

            // Determine if we've hit the threshold (4 cases):
            // 1. NOT in the threshold, current distance is above
            // 2. NOT in the threshold, current distance is below
            // 3. IN the threshold, current distance is above
            // 4. IN the threshold, current distance is below
            //pc.printf("D=%ld mm\r\n", dist);
            if (!is_point && dist <= THRESHOLD) {
                // We don't need to check this every time - save our previous 
                // loop time!
                if (time_left == 0)
                    break;
                // increment our points
                ++num_points;
                is_point = true;
            } else if (is_point && dist > THRESHOLD) {
                is_point = false;
            }
        } else {
            is_point = false;
        }
    }
    // wait for time_left (should be handled above, but just in case?
}
int main() {
    // Tell the user we're spinning up
    uLCD.cls();
    uLCD.text_height(2);
    uLCD.text_width(2);
    uLCD.locate(0, 0);
    uLCD.color(RED);
    uLCD.printf("SPINNING\nUP");
    
    DevI2C* device_i2c = new DevI2C(VL53L0_I2C_SDA, VL53L0_I2C_SCL);
    lidar = XNucleo53L0A1::instance(device_i2c, A2, D8, D2);

    // Reset shdn (?):
    shdn = 0;
    wait(0.1);
    shdn = 1;
    wait(0.1);
    
    int status = lidar->init_board();
    while (status) {
        err_led = 1;
        status = lidar->init_board();
        wait(1);
    }
    err_led = 0;
    // We have nothing left to do... Just wait
    // Wait for the LIDAR
    player_color = player_1_color;
    uLCD.cls();
    uLCD.locate(0, 0);
    uLCD.color(player_color);
    uLCD.text_height(2);
    uLCD.text_width(2);
    uLCD.printf("Ready\nPlayer\n1");
    
    // Wait for the LIDAR
    while(true){
       if(lidar->sensor_centre->get_distance(&dist) == VL53L0X_ERROR_NONE){
           if(dist<THRESHOLD){
               break;
            }
        }
    }
    uLCD.cls();
    uLCD.locate(0, 0);
    uLCD.color(player_color);
    uLCD.text_width(2);
    uLCD.text_height(2);
    uLCD.printf("P1");
    run_game();
    player_1 = num_points;
    Thread::wait(5000);
    player_color = player_2_color;
    num_points = 0;
    lcd_lock.lock();
    uLCD.cls();
    uLCD.locate(0, 0);
    uLCD.color(player_color);
    uLCD.text_height(2);
    uLCD.text_width(2);
    uLCD.printf("Ready\nPlayer\n2");
    lcd_lock.unlock();
    // Wait for the LIDAR
    while(true){
       if(lidar->sensor_centre->get_distance(&dist) == VL53L0X_ERROR_NONE){
           if(dist<THRESHOLD){
               break;
            }
        }
    }
    uLCD.cls();
    uLCD.locate(0, 0);
    uLCD.color(player_color);
    uLCD.text_width(2);
    uLCD.text_height(2);
    uLCD.printf("P2");
    run_game();
    player_2 = num_points;
    
    lcd_lock.lock();
    // Lock foever
    uLCD.cls();
    uLCD.color(GREEN);
    uLCD.locate(0, 0);
    uLCD.text_width(3);
    uLCD.text_height(3);
    if (player_1 > player_2) {
        uLCD.text_string("Player", 0, 0, FONT_7X8, player_1_color);
        uLCD.text_string("1", 0, 1, FONT_7X8, player_1_color);
        uLCD.text_string("Wins!", 0, 2, FONT_7X8, player_1_color);
    } else if (player_2 > player_1) {
        uLCD.text_string("Player", 0, 0, FONT_7X8, player_2_color);
        uLCD.text_string("2", 0, 1, FONT_7X8, player_2_color);
        uLCD.text_string("Wins!", 0, 2, FONT_7X8, player_2_color);
    } else {
        uLCD.printf("Draw!");
    }
    uLCD.locate(0, 3);
    uLCD.color(player_1_color);
    uLCD.printf("P1: %d", player_1);
    uLCD.locate(0, 4);
    uLCD.color(player_2_color);
    uLCD.printf("P2: %d", player_2);
    lcd_lock.unlock();
    
    Thread::wait(5000);
    while(true){
       if(lidar->sensor_centre->get_distance(&dist) == VL53L0X_ERROR_NONE){
           if(dist<THRESHOLD){
               break;
            }
        }
    }
    mbed_reset();
}