/*
* STM32-Based Smart Waste Segregation and Monitoring System
* Wokwi simulation firmware - STM32F103C8 (Blue Pill), Arduino framework
* Course: 23CSE304 Embedded Systems, Amrita Vishwa Vidyapeetham, Amritapuri
*
* Simulation substitutions (see README.md):
* Capacitive moisture sensor -> potentiometer on PA0 (high = wet)
* IR reflectance (TCRT5000) -> potentiometer on PA1 (high = glossy)
* IR presence (digital) -> slide switch on PB0 (active LOW = item present)
* Inductive metal sensor -> slide switch on PB1 (active LOW = metal)
* HC-SR04 x2 -> real Wokwi parts (drag the distance slider)
* Tray servo -> Wokwi servo (90 = neutral, 60 = bio, 120 = non-bio)
* 16x2 LCD -> LCD1602 over I2C (PCF8574 backpack, 0x27)
*
* States: IDLE -> DETECT -> SENSE -> CLASSIFY -> ACTUATE -> IDLE
* Classification priority: metal -> moisture -> reflectance -> dry default.
*/
#include <Arduino.h>
#include <Wire.h>
#include <Servo.h>
// ----------------------------- Pin map -------------------------------------
#define PIN_MOISTURE PA0 // ADC1 ch0
#define PIN_IR_REFL PA1 // ADC1 ch1
#define PIN_ITEM PB0 // presence, active LOW
#define PIN_METAL PB1 // metal, active LOW
#define PIN_SERVO PA8 // TIM1_CH1 PWM
#define PIN_TRIG_A PB12 // bin A (biodegradable)
#define PIN_ECHO_A PB13
#define PIN_TRIG_B PB14 // bin B (non-biodegradable)
#define PIN_ECHO_B PB15
#define PIN_LED_BIO PB8
#define PIN_LED_NONBIO PB9
#define PIN_LED_ALERT PA4
#define PIN_BUZZER PA5
// I2C1 default pins on Blue Pill: SCL = PB6, SDA = PB7
// ----------------------------- Parameters ----------------------------------
const uint8_t ANGLE_NEUTRAL = 90;
const uint8_t TILT_DEG = 30;
const uint8_t ANGLE_BIO = ANGLE_NEUTRAL - TILT_DEG; // 60
const uint8_t ANGLE_NONBIO = ANGLE_NEUTRAL + TILT_DEG; // 120
const uint32_t ITEM_CONFIRM_MS = 200; // IR must stay low this long
const uint32_t SETTLE_MS = 1500; // item settling delay
const uint32_t HOLD_MS = 1500; // tray hold time
const uint32_t RETURN_MS = 500; // time for tray to return
const uint32_t FILL_PERIOD_MS = 2000; // fill refresh period
const uint8_t ADC_SAMPLES = 16; // averaging
const int WET_OFFSET = 900; // counts above baseline => wet
const int REFLECT_OFFSET = 1200; // counts above baseline => glossy
const float EMPTY_DIST_CM = 30.0f; // sensor-to-floor, bin empty (0 %)
const float FULL_DIST_CM = 5.0f; // sensor-to-waste, bin full (100 %)
const uint8_t FULL_ALERT_PCT = 80;
#define LCD_ADDR 0x27
// ----------------------------- Globals -------------------------------------
enum State { ST_IDLE, ST_DETECT, ST_SENSE, ST_CLASSIFY, ST_ACTUATE };
enum Class { CLS_BIO, CLS_NONBIO };
enum ActPhase { ACT_START, ACT_HOLD, ACT_RETURN };
Servo tray;
State state = ST_IDLE;
ActPhase actPhase = ACT_START;
int baseMoist = 0, baseIR = 0;
int moistVal = 0, irVal = 0;
bool metalVal = false;
Class result = CLS_BIO;
const char *reason = "";
uint32_t lowSince = 0; // when presence first went low (0 = not low)
uint32_t tDetect = 0; // cycle start
uint32_t tPhase = 0; // generic phase timer
uint32_t lastFill = 0;
bool needRelease = false; // item switch must be released before re-arm
int fillA = 0, fillB = 0;
bool binFull = false;
char lastTag[4] = "---";
// ----------------------------- LCD (PCF8574) -------------------------------
static uint8_t lcdBL = 0x08;
static void pcfWrite(uint8_t d) {
Wire.beginTransmission(LCD_ADDR);
Wire.write((uint8_t)(d | lcdBL));
Wire.endTransmission();
}
static void lcdNibble(uint8_t nib, uint8_t rs) {
uint8_t d = (uint8_t)((nib << 4) | rs); // P4-7 = D4-D7, P0 = RS
pcfWrite(d | 0x04); // EN high
delayMicroseconds(2);
pcfWrite((uint8_t)(d & ~0x04)); // EN low
delayMicroseconds(60);
}
static void lcdSend(uint8_t v, uint8_t rs) {
lcdNibble(v >> 4, rs);
lcdNibble(v & 0x0F, rs);
}
static void lcdInit() {
delay(50);
lcdNibble(0x03, 0); delay(5);
lcdNibble(0x03, 0); delayMicroseconds(150);
lcdNibble(0x03, 0);
lcdNibble(0x02, 0); // 4-bit mode
lcdSend(0x28, 0); // 2 lines, 5x8
lcdSend(0x0C, 0); // display on
lcdSend(0x06, 0); // entry mode
lcdSend(0x01, 0); delay(3); // clear
}
static void lcdLine(uint8_t row, const char *s) {
lcdSend(0x80 | (row ? 0x40 : 0x00), 0);
uint8_t i = 0;
for (; i < 16 && s[i]; i++) lcdSend((uint8_t)s[i], 1);
for (; i < 16; i++) lcdSend(' ', 1);
}
// ----------------------------- Helpers -------------------------------------
static int readAvg(uint8_t pin) {
long sum = 0;
for (uint8_t i = 0; i < ADC_SAMPLES; i++) { sum += analogRead(pin); delayMicroseconds(100); }
return (int)(sum / ADC_SAMPLES);
}
static float readDistanceCm(uint8_t trig, uint8_t echo) {
digitalWrite(trig, LOW); delayMicroseconds(2);
digitalWrite(trig, HIGH); delayMicroseconds(10);
digitalWrite(trig, LOW);
unsigned long us = pulseIn(echo, HIGH, 30000UL);
if (us == 0) return 400.0f; // no echo -> treat as empty
return us * 0.0343f / 2.0f;
}
static int distToFillPct(float d) {
float p = (EMPTY_DIST_CM - d) / (EMPTY_DIST_CM - FULL_DIST_CM) * 100.0f;
if (p < 0) p = 0;
if (p > 100) p = 100;
return (int)(p + 0.5f);
}
static void beep(uint16_t ms) { // bit-banged ~2 kHz, no timer needed
uint32_t end = millis() + ms;
while (millis() < end) {
digitalWrite(PIN_BUZZER, HIGH); delayMicroseconds(250);
digitalWrite(PIN_BUZZER, LOW); delayMicroseconds(250);
}
}
static void showFill() {
char buf[17];
snprintf(buf, sizeof(buf), "A:%3d%% B:%3d%% %c", fillA, fillB, binFull ? '!' : ' ');
lcdLine(1, buf);
}
static void updateFill() {
fillA = distToFillPct(readDistanceCm(PIN_TRIG_A, PIN_ECHO_A));
fillB = distToFillPct(readDistanceCm(PIN_TRIG_B, PIN_ECHO_B));
binFull = (fillA >= FULL_ALERT_PCT) || (fillB >= FULL_ALERT_PCT);
digitalWrite(PIN_LED_ALERT, binFull ? HIGH : LOW);
showFill();
Serial.print("FILL,A,"); Serial.print(fillA);
Serial.print(",B,"); Serial.println(fillB);
}
static void showReady() {
char buf[17];
snprintf(buf, sizeof(buf), "Ready last:%s", lastTag);
lcdLine(0, buf);
}
static void calibrate() {
lcdLine(0, "Calibrating...");
lcdLine(1, "Keep tray empty");
uint32_t t0 = millis();
long sm = 0, si = 0; int n = 0;
while (millis() - t0 < 500) {
sm += analogRead(PIN_MOISTURE);
si += analogRead(PIN_IR_REFL);
n++; delay(5);
}
baseMoist = (int)(sm / n);
baseIR = (int)(si / n);
Serial.print("CAL,moist_base,"); Serial.print(baseMoist);
Serial.print(",ir_base,"); Serial.println(baseIR);
}
// ----------------------------- Setup ---------------------------------------
void setup() {
Serial.begin(115200);
analogReadResolution(12);
pinMode(PIN_ITEM, INPUT_PULLUP);
pinMode(PIN_METAL, INPUT_PULLUP);
pinMode(PIN_TRIG_A, OUTPUT); pinMode(PIN_ECHO_A, INPUT);
pinMode(PIN_TRIG_B, OUTPUT); pinMode(PIN_ECHO_B, INPUT);
pinMode(PIN_LED_BIO, OUTPUT);
pinMode(PIN_LED_NONBIO, OUTPUT);
pinMode(PIN_LED_ALERT, OUTPUT);
pinMode(PIN_BUZZER, OUTPUT);
Wire.begin();
lcdInit();
tray.attach(PIN_SERVO);
tray.write(ANGLE_NEUTRAL);
lcdLine(0, "Waste Segregator");
lcdLine(1, "STM32 Blue Pill");
delay(1200);
calibrate();
updateFill();
lastFill = millis();
showReady();
needRelease = (digitalRead(PIN_ITEM) == LOW); // ignore switch left closed
Serial.println("EVENT,cycle,moisture,ir,metal,class,reason,cycle_ms");
}
// ----------------------------- Main loop -----------------------------------
void loop() {
uint32_t now = millis();
// Background fill-level refresh every 2 s (all states)
if (now - lastFill >= FILL_PERIOD_MS) {
lastFill = now;
updateFill();
}
bool itemPresent = (digitalRead(PIN_ITEM) == LOW);
switch (state) {
case ST_IDLE:
if (needRelease) { // wait for item to leave the tray
if (!itemPresent) needRelease = false;
lowSince = 0;
break;
}
if (itemPresent) {
if (lowSince == 0) lowSince = now;
if (now - lowSince > ITEM_CONFIRM_MS) { // confirmed item, not a hand
tDetect = lowSince;
tPhase = now;
lcdLine(0, "Item detected...");
state = ST_DETECT;
}
} else {
lowSince = 0;
}
break;
case ST_DETECT: // settling delay
if (!itemPresent) { // item removed -> abort
showReady();
state = ST_IDLE; lowSince = 0;
break;
}
if (now - tPhase >= SETTLE_MS) {
lcdLine(0, "Sensing...");
state = ST_SENSE;
}
break;
case ST_SENSE:
moistVal = readAvg(PIN_MOISTURE);
irVal = readAvg(PIN_IR_REFL);
metalVal = (digitalRead(PIN_METAL) == LOW);
state = ST_CLASSIFY;
break;
case ST_CLASSIFY:
if (metalVal) { // 1. metal overrides all
result = CLS_NONBIO; reason = "metal";
} else if (moistVal > baseMoist + WET_OFFSET) { // 2. wet organic
result = CLS_BIO; reason = "moisture";
} else if (irVal > baseIR + REFLECT_OFFSET) { // 3. glossy plastic/foil
result = CLS_NONBIO; reason = "reflect";
} else { // 4. dry paper / leaves
result = CLS_BIO; reason = "dry-organic";
}
actPhase = ACT_START;
state = ST_ACTUATE;
break;
case ST_ACTUATE:
switch (actPhase) {
case ACT_START:
if (result == CLS_BIO) {
tray.write(ANGLE_BIO);
digitalWrite(PIN_LED_BIO, HIGH);
lcdLine(0, "BIODEGRADABLE");
} else {
tray.write(ANGLE_NONBIO);
digitalWrite(PIN_LED_NONBIO, HIGH);
lcdLine(0, "NON-BIODEGRAD.");
}
tPhase = now;
actPhase = ACT_HOLD;
break;
case ACT_HOLD:
if (now - tPhase >= HOLD_MS) {
tray.write(ANGLE_NEUTRAL);
tPhase = now;
actPhase = ACT_RETURN;
}
break;
case ACT_RETURN:
if (now - tPhase >= RETURN_MS) {
digitalWrite(PIN_LED_BIO, LOW);
digitalWrite(PIN_LED_NONBIO, LOW);
uint32_t cycleMs = now - tDetect;
static uint16_t cycleNo = 0;
cycleNo++;
strcpy(lastTag, result == CLS_BIO ? "BIO" : "NON");
Serial.print("EVENT,"); Serial.print(cycleNo);
Serial.print(","); Serial.print(moistVal);
Serial.print(","); Serial.print(irVal);
Serial.print(","); Serial.print(metalVal ? 1 : 0);
Serial.print(","); Serial.print(lastTag);
Serial.print(","); Serial.print(reason);
Serial.print(","); Serial.println(cycleMs);
// Fresh fill reading, then bin-full warning before returning to idle
updateFill();
lastFill = millis();
if (binFull) {
lcdLine(0, "** BIN FULL **");
beep(200); delay(100); beep(200); delay(100); beep(200);
delay(800);
}
showReady();
needRelease = true; // item must leave before next cycle
lowSince = 0;
state = ST_IDLE;
}
break;
}
break;
}
}
Loading
stm32-bluepill
stm32-bluepill