Samuel Alexander
Published © CC BY-SA

AI Audio Classifier Recycle Bin

Recycle Bin that sorts rubbish based on the sound of collision using Edge AI audio classification.

IntermediateFull instructions provided18 hours9,313
AI Audio Classifier Recycle Bin

Things used in this project

Hardware components

550mm 2020 aluminium profile
×4
290mm 2020 aluminium profile
×12
90º angle bracket
×25
M5 T-nut
×69
M5 8mm bolt
×69
M4 10mm bolt
×4
M4 4mm bolt
×8
M3 10mm bolt
×13
M4 threaded insert
×8
M3 threaded insert
×12
688zz ball bearing
×5
28T GT2 pulley gear 5mm bore
×1
300mm GT2 timing belt
×1
small neodymium magnet
×1
Nano 33 BLE Sense
Arduino Nano 33 BLE Sense
×1
dc-dc 5V boost charger
×1
Li-ion 1s 250mAh battery
×1
TMC2208 stepper driver
×1
A3144 hall sensor
×1
3s LiPo battery
×1
1kg spool PLA+
×1
Spool of wires
×1

Software apps and online services

Edge Impulse Studio
Edge Impulse Studio

Hand tools and fabrication machines

3D Printer (generic)
3D Printer (generic)
Laser cutter (generic)
Laser cutter (generic)

Story

Read more

Custom parts and enclosures

big_pulley_1Rw4lK9fJg.stl

bumper_FVxT5g3Kx5.stl

container_uKTwXHL2JZ.stl

main_trapdoor_1UIdkfbDtD.stl

follower_trapdoor_u0veQ6ihod.stl

hall_sensor_holder_OfE9NlggfY.stl

mcu_batt_case_wkCCdo9YvO.stl

middle_separator_LWZuId6Wne.stl

rotating_base_holder_boBdLAxhPl.stl

shoe_8X0CvSKDDR.stl

stepper_driver_holder_WnkBwT27wR.stl

top_bearing_holder_SiRODOwxpf.stl

top_sheet_adapter_mVuRewoKiZ.stl

trapdoor_holder_Usneq3XdOP.stl

Schematics

Wiring schematic

Code

nano_ble33_sense_microphone_BinDemo.ino

Arduino
/* AI Audio Classifier Recycle Bin
 * License: Apache-2.0
 * Modification by: Samuel Alexander
 * Based on the nano_ble33_sense_microphone example
 * Below is the license statement from the base software
/*

/* Edge Impulse ingestion SDK
 * Copyright (c) 2022 EdgeImpulse Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

// If your target is limited in memory remove this macro to save 10K RAM
#define EIDSP_QUANTIZE_FILTERBANK   0

/*
 ** NOTE: If you run into TFLite arena allocation issue.
 **
 ** This may be due to may dynamic memory fragmentation.
 ** Try defining "-DEI_CLASSIFIER_ALLOCATION_STATIC" in boards.local.txt (create
 ** if it doesn't exist) and copy this file to
 ** `<ARDUINO_CORE_INSTALL_PATH>/arduino/hardware/<mbed_core>/<core_version>/`.
 **
 ** See
 ** (https://support.arduino.cc/hc/en-us/articles/360012076960-Where-are-the-installed-cores-located-)
 ** to find where Arduino installs cores on your machine.
 **
 ** If the problem persists then there's not enough memory for this model and application.
 */

/* Includes ---------------------------------------------------------------- */
#include <PDM.h>
#include <Edge_AI_Recycle_Bin_inferencing.h>
#include <AccelStepper.h>
#include <Servo.h>

/** Audio buffers, pointers and selectors */
typedef struct {
    int16_t *buffer;
    uint8_t buf_ready;
    uint32_t buf_count;
    uint32_t n_samples;
} inference_t;

static inference_t inference;
static signed short sampleBuffer[2048];
static bool debug_nn = false; // Set this to true to see e.g. features generated from the raw signal

/**
 * @brief      Arduino setup function
 */

AccelStepper stepper1(1, 2, 3); // (Typeof driver: with 2 pins, STEP, DIR)

int compartment[] = {925, 2775, 0, 4625, 6475}; //based on our EI model (Bottle, Can, Noise, Paper, Pong)
const int HALL = 5;

Servo trapdoor;
int pos = 0;
float threshold = 0.8;
int selected = 2; //default as Noise

void setup()
{
    // put your setup code here, to run once:
    Serial.begin(115200);
    pinMode(HALL, INPUT);
    trapdoor.attach(A5);
    
    // stepper motor calibration
    stepper1.setMaxSpeed(2000); // Set maximum speed value for the stepper
    stepper1.setAcceleration(1000); // Set acceleration value for the stepper
    while(digitalRead(HALL)){
      stepper1.setSpeed(-400); // Set the current position to 0 steps
      stepper1.runSpeed();
      Serial.println("homing...");
    }
    delay(100);
    stepper1.setCurrentPosition(0);
    //end of stepper motor calibration


    // servo motor calibration
  for (pos = 11; pos <= 90; pos += 1) { // goes from 0 degrees to 180 degrees
    // in steps of 1 degree
    trapdoor.write(pos);              // tell servo to go to position in variable 'pos'
    delay(20);                       // waits 15 ms for the servo to reach the position
  }
  for (pos = 90; pos >= 11; pos -= 1) { // goes from 180 degrees to 0 degrees
    trapdoor.write(pos);              // tell servo to go to position in variable 'pos'
    delay(20);                       // waits 15 ms for the servo to reach the position
  }
    // end of servo motor calibration
    delay(500); //Delay so the sound of servo is not captured



    

    // comment out the below line to cancel the wait for USB connection (needed for native USB)
    //while (!Serial);
    Serial.println("Edge Impulse Inferencing Demo");

    // summary of inferencing settings (from model_metadata.h)
    ei_printf("Inferencing settings:\n");
    ei_printf("\tInterval: %.2f ms.\n", (float)EI_CLASSIFIER_INTERVAL_MS);
    ei_printf("\tFrame size: %d\n", EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE);
    ei_printf("\tSample length: %d ms.\n", EI_CLASSIFIER_RAW_SAMPLE_COUNT / 16);
    ei_printf("\tNo. of classes: %d\n", sizeof(ei_classifier_inferencing_categories) / sizeof(ei_classifier_inferencing_categories[0]));

    if (microphone_inference_start(EI_CLASSIFIER_RAW_SAMPLE_COUNT) == false) {
        ei_printf("ERR: Could not allocate audio buffer (size %d), this could be due to the window length of your model\r\n", EI_CLASSIFIER_RAW_SAMPLE_COUNT);
        return;
    }
}

/**
 * @brief      Arduino main function. Runs the inferencing loop.
 */
void loop()
{
    //ei_printf("Starting inferencing in 2 seconds...\n");

    //delay(2000);

    ei_printf("Recording...\n");

    bool m = microphone_inference_record();
    if (!m) {
        ei_printf("ERR: Failed to record audio...\n");
        return;
    }

    ei_printf("Recording done\n");

    signal_t signal;
    signal.total_length = EI_CLASSIFIER_RAW_SAMPLE_COUNT;
    signal.get_data = &microphone_audio_signal_get_data;
    ei_impulse_result_t result = { 0 };

    EI_IMPULSE_ERROR r = run_classifier(&signal, &result, debug_nn);
    if (r != EI_IMPULSE_OK) {
        ei_printf("ERR: Failed to run classifier (%d)\n", r);
        return;
    }

    // print the predictions
    ei_printf("Predictions ");
    ei_printf("(DSP: %d ms., Classification: %d ms., Anomaly: %d ms.)",
        result.timing.dsp, result.timing.classification, result.timing.anomaly);
    ei_printf(": \n");
    for (size_t ix = 0; ix < EI_CLASSIFIER_LABEL_COUNT; ix++) {
        ei_printf("    %s: %.5f\n", result.classification[ix].label, result.classification[ix].value);
        if (result.classification[ix].value > threshold){
          ei_printf("\nYOUR TRASH IS: %s\n", result.classification[ix].label);
          selected = ix;
        }
    }
    // The order is Bottle, Can, Noise, Paper, Ping-pong
    // if BOTTLE selected
    if (selected == 0){
      stepper1.moveTo(compartment[selected]);
      stepper1.runToPosition();
      for (pos = 11; pos <= 90; pos += 1) { // goes from 0 degrees to 180 degrees
        // in steps of 1 degree
        trapdoor.write(pos);              // tell servo to go to position in variable 'pos'
        delay(20);                       // waits 15 ms for the servo to reach the position
      }
      for (pos = 90; pos >= 11; pos -= 1) { // goes from 180 degrees to 0 degrees
        trapdoor.write(pos);              // tell servo to go to position in variable 'pos'
        delay(20);                       // waits 15 ms for the servo to reach the position
      }
      delay(500);
    }
    // if CAN selected
    else if (selected == 1){
      stepper1.moveTo(compartment[selected]);
      stepper1.runToPosition();
      for (pos = 11; pos <= 90; pos += 1) { // goes from 0 degrees to 180 degrees
        // in steps of 1 degree
        trapdoor.write(pos);              // tell servo to go to position in variable 'pos'
        delay(20);                       // waits 15 ms for the servo to reach the position
      }
      for (pos = 90; pos >= 11; pos -= 1) { // goes from 180 degrees to 0 degrees
        trapdoor.write(pos);              // tell servo to go to position in variable 'pos'
        delay(20);                       // waits 15 ms for the servo to reach the position
      }
      delay(500);
    }
    // if PAPER selected
    else if (selected == 3){
      stepper1.moveTo(compartment[selected]);
      stepper1.runToPosition();
      for (pos = 11; pos <= 90; pos += 1) { // goes from 0 degrees to 180 degrees
        // in steps of 1 degree
        trapdoor.write(pos);              // tell servo to go to position in variable 'pos'
        delay(20);                       // waits 15 ms for the servo to reach the position
      }
      for (pos = 90; pos >= 11; pos -= 1) { // goes from 180 degrees to 0 degrees
        trapdoor.write(pos);              // tell servo to go to position in variable 'pos'
        delay(20);                       // waits 15 ms for the servo to reach the position
      }
      delay(500);
    }
    // if Ping Pong selected
    else if (selected == 4){
      stepper1.moveTo(compartment[selected]);
      stepper1.runToPosition();
      for (pos = 11; pos <= 90; pos += 1) { // goes from 0 degrees to 180 degrees
        // in steps of 1 degree
        trapdoor.write(pos);              // tell servo to go to position in variable 'pos'
        delay(20);                       // waits 15 ms for the servo to reach the position
      }
      for (pos = 90; pos >= 11; pos -= 1) { // goes from 180 degrees to 0 degrees
        trapdoor.write(pos);              // tell servo to go to position in variable 'pos'
        delay(20);                       // waits 15 ms for the servo to reach the position
      }
      delay(500);
    }
    // if NOISE selected
    else {
      //no statement
    }



#if EI_CLASSIFIER_HAS_ANOMALY == 1
    ei_printf("    anomaly score: %.3f\n", result.anomaly);
#endif
}

/**
 * @brief      PDM buffer full callback
 *             Get data and call audio thread callback
 */
static void pdm_data_ready_inference_callback(void)
{
    int bytesAvailable = PDM.available();

    // read into the sample buffer
    int bytesRead = PDM.read((char *)&sampleBuffer[0], bytesAvailable);

    if (inference.buf_ready == 0) {
        for(int i = 0; i < bytesRead>>1; i++) {
            inference.buffer[inference.buf_count++] = sampleBuffer[i];

            if(inference.buf_count >= inference.n_samples) {
                inference.buf_count = 0;
                inference.buf_ready = 1;
                break;
            }
        }
    }
}

/**
 * @brief      Init inferencing struct and setup/start PDM
 *
 * @param[in]  n_samples  The n samples
 *
 * @return     { description_of_the_return_value }
 */
static bool microphone_inference_start(uint32_t n_samples)
{
    inference.buffer = (int16_t *)malloc(n_samples * sizeof(int16_t));

    if(inference.buffer == NULL) {
        return false;
    }

    inference.buf_count  = 0;
    inference.n_samples  = n_samples;
    inference.buf_ready  = 0;

    // configure the data receive callback
    PDM.onReceive(&pdm_data_ready_inference_callback);

    PDM.setBufferSize(4096);

    // initialize PDM with:
    // - one channel (mono mode)
    // - a 16 kHz sample rate
    if (!PDM.begin(1, EI_CLASSIFIER_FREQUENCY)) {
        ei_printf("Failed to start PDM!");
        microphone_inference_end();

        return false;
    }

    // set the gain, defaults to 20
    PDM.setGain(80);

    return true;
}

/**
 * @brief      Wait on new data
 *
 * @return     True when finished
 */
static bool microphone_inference_record(void)
{
    inference.buf_ready = 0;
    inference.buf_count = 0;

    while(inference.buf_ready == 0) {
        delay(10);
    }

    return true;
}

/**
 * Get raw audio signal data
 */
static int microphone_audio_signal_get_data(size_t offset, size_t length, float *out_ptr)
{
    numpy::int16_to_float(&inference.buffer[offset], out_ptr, length);

    return 0;
}

/**
 * @brief      Stop PDM and release buffers
 */
static void microphone_inference_end(void)
{
    PDM.end();
    free(inference.buffer);
}

#if !defined(EI_CLASSIFIER_SENSOR) || EI_CLASSIFIER_SENSOR != EI_CLASSIFIER_SENSOR_MICROPHONE
#error "Invalid model for current sensor."
#endif

Credits

Samuel Alexander

Samuel Alexander

5 projects • 29 followers

Comments