Community project

Automated Medication Dispenser

ESP32
Photo of Automated Medication Dispenser
Generated with AI

Arif Hermawan

Published October 24, 2026

This automated medication dispenser uses an ESP32 microcontroller to manage four independent medication tubes with servo-controlled gates and weight verification. The system combines infrared sensors to detect medication passage, a load cell scale to verify dispensed quantities, and audio-visual feedback to confirm operations. Builders will receive a complete wiring diagram, parts list, and firmware code to assemble a bench prototype that demonstrates gated dispensing with weight validation.

The guide covers connecting the servo motors with proper power isolation, positioning the TCRT5000 infrared sensors on each tube, integrating the HX711 load cell amplifier for accurate weight measurement, and using the 74AHCT125 level shifter to safely interface 5V servo signals with the ESP32. Assembly steps walk through prototype construction, sensor calibration, and testing each medication gate independently before full system operation.

Wiring diagram

Wiring diagram for Automated Medication Dispenser

Gather all the parts

QtyComponent
1

Regulated isolated 5 V 4 A DC supply

Regulated isolated 5 V 4 A DC supply

1

SG90 Servo

Micro servo motor (SG90)

1

SG90 Servo

Micro servo motor (SG90)

1

SG90 Servo

Micro servo motor (SG90)

1

SG90 Servo

Micro servo motor (SG90)

1

LED

Standard 3mm/5mm through-hole LED. A current-limiting series resistor is added automatically.

1

330 ohm LED resistor

330 ohm LED resistor

1

LED

Standard 3mm/5mm through-hole LED. A current-limiting series resistor is added automatically.

1

330 ohm LED resistor

330 ohm LED resistor

1

LED

Standard 3mm/5mm through-hole LED. A current-limiting series resistor is added automatically.

1

330 ohm LED resistor

330 ohm LED resistor

1

LED

Standard 3mm/5mm through-hole LED. A current-limiting series resistor is added automatically.

1

330 ohm LED resistor

330 ohm LED resistor

1

LED

Standard 3mm/5mm through-hole LED. A current-limiting series resistor is added automatically.

1

330 ohm LED resistor

330 ohm LED resistor

1

HX711 Load Cell Amplifier

24-bit ADC front-end for strain-gauge load cells and weigh scales. DT/DOUT and SCK are MCU-facing pins; E+/E-/A+/A- connect to the load cell bridge.

1

Load Cell

Four-wire strain-gauge straight-bar load cell, common 5 kg hobby scale sensor. It produces a small bridge signal and should be read through a load-cell ADC/amplifier such as HX711 or NAU7802 rather than directly by a microcontroller GPIO.

1

Piezo Buzzer

Small passive piezo buzzer (TDK PS1240) for beeps, tones and alerts. It has no built-in oscillator, so drive one lead with a 3-30 V square wave from a microcontroller pin using tone() or PWM and connect the other lead to ground. It is loudest around 4 kHz and works from about 2 to 10 kHz; driving both leads in antiphase roughly doubles the volume.

1

1000 uF 10 V electrolytic capacitor

1000 uF 10 V electrolytic capacitor

1

74AHCT125 Quad Buffer / Level Shifter

Quad non-inverting buffer/line driver with 3-state outputs and active-low output-enable pins. In 5 V AHCT/HCT designs, 3.3 V MCU outputs are high enough for the TTL-level inputs, making it a common one-way 3.3 V to 5 V level shifter for WS2812/NeoPixel data and other fast digital lines.

1

100 nF ceramic capacitor

Buffer supply bypass capacitor

1

TCRT5000 module (3.3 V operation)

Catalog module rated 3.3–5 V, operated at 3.3 V; DO is comparator output.

1

TCRT5000 module (3.3 V operation)

Catalog module rated 3.3–5 V, operated at 3.3 V; DO is comparator output.

1

TCRT5000 module (3.3 V operation)

Catalog module rated 3.3–5 V, operated at 3.3 V; DO is comparator output.

1

TCRT5000 module (3.3 V operation)

Catalog module rated 3.3–5 V, operated at 3.3 V; DO is comparator output.

1

TCRT5000 module (3.3 V operation)

Catalog module rated 3.3–5 V, operated at 3.3 V; DO is comparator output.

Assemble it in 6 steps

1. Prepare a safe bench prototype

Use non-medical test pieces only. Make each tube's gate mechanically closed when unpowered. An SG90 is not a reliable medicine safety gate; the mechanism must release only one piece per stroke. Mount horns with power off and check travel without pieces first.

  • Startup servo motion can release contents. Do not load medication.

2. Connect separate servo power

Use an enclosed regulated 5 V 4 A supply. Connect its positive output only to the four servo VCC wires. Join supply ground, servo grounds and ESP32 ground. Power the ESP32 separately through USB; never join the external positive wire to its 5V or 3V3 pin. Put the capacitor across the servo supply, plus to 5 V and minus to ground.

  • Reversing the capacitor may rupture it. Do not carry servo current through thin breadboard power rails.

3. Position the infrared sensors

Power all five modules from 3V3, never 5 V. Connect DO for tubes 1–4 to GPIO34, 35, 36, 39; counter DO to GPIO32. Leave AO unused. Place tube sensors at the refill level. Guide falling test pieces close to sensor 5, one at a time. Adjust the trim knobs and verify PRESENT polarity in STATUS; change PRESENT in firmware if required. These sensors detect a threshold, not remaining quantity.

  • Reflective infrared sensors can miss dark pieces or count overlapping pieces as one. Shield them from room light and test every piece shape.

4. Connect indicators and gates

Servo signals 1–4 go to GPIO13, 14, 16, 17. Tube LED outputs go through their own 330 ohm resistor from GPIO18, 19, 21, 22 to the long LED leg; short legs go to ground. Alarm LED uses GPIO23 and its resistor. Piezo SIG goes to GPIO26 and its other leg to ground.

5. Mount and connect the collection scale

Fix one end of the load-cell bar to a rigid base and the other to the collection tray, with clearance beneath the bending section. Connect E+, E-, A+, A- by function to HX711, not guessed wire colours. HX711 VCC goes to 3V3, GND to ground, DT to GPIO33 and SCK to GPIO25. Keep motor wires away from scale wires.

  • A 5 kg hobby load cell may not resolve lightweight tablets adequately. It does not verify drug identity. Do not rely on it for dosing.

6. Add the servo signal buffer

Route GPIO13, 14, 16, 17 to buffer inputs 1A, 2A, 3A, 4A instead of directly to servos. Outputs 1Y–4Y go to matching servo SIGNAL wires. Tie all four OE pins to ground. Buffer VCC uses the external 5 V rail, GND uses common ground. Put the 100 nF capacitor beside VCC and GND. Use the chip datasheet pin labels when locating its physical legs.

Review all connections

1. Connections between "supply" and "ESP32"

FunctionsupplyESP32
powerOUT5V
groundGNDGND

2. Connections between "buffer" and "ESP32"

FunctionbufferESP32
digital1AGPIO 13
digital1Y → SG90 Servo SIGNALEXT
ground1OEGND
digital2AGPIO 14
digital2Y → SG90 Servo SIGNALEXT
ground2OEGND
digital3AGPIO 16
digital3Y → SG90 Servo SIGNALEXT
ground3OEGND
digital4AGPIO 17
digital4Y → SG90 Servo SIGNALEXT
ground4OEGND
powerVCC5V
groundGNDGND

3. Connections between "servo1" and "ESP32"

Functionservo1ESP32
powerVCC5V
groundGNDGND

4. Connections between "servo2" and "ESP32"

Functionservo2ESP32
powerVCC5V
groundGNDGND

5. Connections between "servo3" and "ESP32"

Functionservo3ESP32
powerVCC5V
groundGNDGND

6. Connections between "servo4" and "ESP32"

Functionservo4ESP32
powerVCC5V
groundGNDGND

7. Connections between "sensor1" and "ESP32"

Functionsensor1ESP32
adcDOGPIO 34
powerVCC3V3
groundGNDGND

8. Connections between "sensor2" and "ESP32"

Functionsensor2ESP32
adcDOGPIO 35
powerVCC3V3
groundGNDGND

9. Connections between "sensor3" and "ESP32"

Functionsensor3ESP32
adcDOGPIO 36
powerVCC3V3
groundGNDGND

10. Connections between "sensor4" and "ESP32"

Functionsensor4ESP32
adcDOGPIO 39
powerVCC3V3
groundGNDGND

11. Connections between "sensor5" and "ESP32"

Functionsensor5ESP32
adcDOGPIO 32
powerVCC3V3
groundGNDGND

12. Connections between "r1" and "ESP32"

Functionr1ESP32
data1GPIO 18
data2 → LED ANODEEXT

13. Connections between "led1" and "ESP32"

Functionled1ESP32
groundGNDGND

14. Connections between "r2" and "ESP32"

Functionr2ESP32
data1GPIO 19
data2 → LED ANODEEXT

15. Connections between "led2" and "ESP32"

Functionled2ESP32
groundGNDGND

16. Connections between "r3" and "ESP32"

Functionr3ESP32
data1GPIO 21
data2 → LED ANODEEXT

17. Connections between "led3" and "ESP32"

Functionled3ESP32
groundGNDGND

18. Connections between "r4" and "ESP32"

Functionr4ESP32
data1GPIO 22
data2 → LED ANODEEXT

19. Connections between "led4" and "ESP32"

Functionled4ESP32
groundGNDGND

20. Connections between "r5" and "ESP32"

Functionr5ESP32
data1GPIO 23
data2 → LED ANODEEXT

21. Connections between "led5" and "ESP32"

Functionled5ESP32
groundGNDGND

22. Connections between "hx" and "ESP32"

FunctionhxESP32
powerVCC3V3
groundGNDGND
digitalDTGPIO 33
digitalSCKGPIO 25
powerE+ → Load Cell E+EXT
groundE- → Load Cell E-EXT
analogA+ → Load Cell A+EXT
analogA- → Load Cell A-EXT

23. Connections between "alarm" and "ESP32"

FunctionalarmESP32
pwmSIGGPIO 26
groundGNDGND

24. Connections between "cap" and "ESP32"

FunctioncapESP32
power+5V
ground-GND

25. Connections between "decouple" and "ESP32"

FunctiondecoupleESP32
power15V
ground2GND

Deploy the firmware

#include <Arduino.h>
#include <ESP32Servo.h>
#include <HX711.h>
// BENCH PROTOTYPE ONLY. No schedule or automatic retries. Power loss loses state.
const int servoPins[]={13,14,16,17}, tubePins[]={34,35,36,39}, ledPins[]={18,19,21,22};
const int counterPin=32, alarmLed=23, buzzerPin=26;
// Verify each module's polarity with STATUS before using test pieces.
const int PRESENT=LOW;
const int CLOSED=0, OPEN=60;
Servo gates[4]; HX711 scale;
enum State {IDLE, OPENING, SETTLING, READY, FAULT};
State state=IDLE;
bool calibrated=false,tared=false; float factor=0,weight=0,baseline=0,expected=0,tolerance=0;
long raw=0; unsigned long sampleAt=0,stageAt=0,stableAt=0,edgeAt=0;
int tube=0,count=0; bool lastBeam=false,beam=false; unsigned long beamAt=0;
String line;
void closeAll(){for(auto &s:gates)s.write(CLOSED);}
void fail(const char*why){closeAll();state=FAULT;Serial.print("FAULT: ");Serial.println(why);}
void setup(){
 Serial.begin(115200); line.reserve(100);
 for(int i=0;i<4;i++){pinMode(tubePins[i],INPUT);pinMode(ledPins[i],OUTPUT);gates[i].setPeriodHertz(50);gates[i].attach(servoPins[i],500,2400);gates[i].write(CLOSED);}
 pinMode(counterPin,INPUT);pinMode(alarmLed,OUTPUT);pinMode(buzzerPin,OUTPUT);scale.begin(33,25);
 Serial.println("BENCH ONLY. Commands: STATUS, TARE, CAL grams, DO tube grams tolerance, RESET. Use non-medical test pieces.");
}
void command(String s){
 s.trim();s.toUpperCase();
 if(s=="STATUS"){Serial.printf("state=%d raw=%ld weight=%.3fg count=%d calibrated=%d tared=%d beam=%d tubes:",state,raw,weight,count,calibrated,tared,digitalRead(counterPin));for(int p:tubePins)Serial.printf(" %d",digitalRead(p));Serial.println();return;}
 if(s=="RESET"){closeAll();state=IDLE;tared=false;Serial.println("Inspect and empty tray; TARE required. No automatic retry.");return;}
 if(state!=IDLE){Serial.println("Busy/fault. Inspect first, then RESET.");return;}
 if(millis()-sampleAt>500){Serial.println("Scale unavailable");return;}
 if(s=="TARE"){scale.set_offset(raw);weight=0;tared=true;Serial.println("Empty tray zero recorded; keep tray installed.");return;}
 float grams=0,tol=0;int t=0;
 if(sscanf(s.c_str(),"CAL %f",&grams)==1 && grams>0 && tared){factor=float(raw-scale.get_offset())/grams;if(fabsf(factor)<1){Serial.println("Calibration rejected");return;}scale.set_scale(factor);calibrated=true;Serial.println("Calibration stored in RAM only. Remove reference mass; TARE empty tray.");tared=false;return;}
 if(sscanf(s.c_str(),"DO %d %f %f",&t,&grams,&tol)==3){
 if(!calibrated||!tared||t<1||t>4||grams<=0||tol<=0||tol>=grams/2){Serial.println("Need calibration, tare, tube 1..4, expected grams, tolerance < half mass.");return;}
 if(fabsf(weight)>tol||digitalRead(counterPin)==PRESENT||digitalRead(tubePins[t-1])!=PRESENT){Serial.println("Tray not empty, counter blocked, or tube low.");return;}
 tube=t-1;expected=grams;tolerance=tol;baseline=weight;count=0;lastBeam=false;beam=false;beamAt=millis();stageAt=millis();state=OPENING;gates[tube].write(OPEN);return;
 }
 Serial.println("Unknown command");
}
void loop(){
 unsigned long now=millis();
 if(scale.is_ready()){raw=scale.read();sampleAt=now;weight=calibrated?float(raw-scale.get_offset())/factor:0;}
 for(int i=0;i<4;i++)digitalWrite(ledPins[i],digitalRead(tubePins[i])!=PRESENT);
 bool observed=digitalRead(counterPin)==PRESENT;
 if(observed!=beam){beam=observed;beamAt=now;}
 if(now-beamAt>=5 && beam!=lastBeam){lastBeam=beam;if(beam&&(state==OPENING||state==SETTLING||state==READY)){count++;edgeAt=now;if(count>1)fail("More than one counter event");}}
 if(state==OPENING||state==SETTLING||state==READY){
 if(now-sampleAt>700)fail("Scale stopped responding");
 else if(beam&&now-beamAt>1200)fail("Counter blocked");
 }
 if(state==OPENING && (count>=1 || now-stageAt>=350)){gates[tube].write(CLOSED);state=SETTLING;stageAt=now;stableAt=now;}
 if(state==SETTLING){
 float delta=weight-baseline;
 if(fabsf(delta-expected)>tolerance)stableAt=now;
 if(now-stageAt>1000 && count==1 && now-stableAt>=1000 && !beam){state=READY;stageAt=now;stableAt=now;Serial.printf("READY: count=1 mass=%.3fg. Collect test piece; leave tray fitted.\n",delta);}
 else if(now-stageAt>6000)fail("Count or settled weight mismatch; inspect manually");
 }
 if(state==READY){
 if(fabsf(weight-baseline)>tolerance)stableAt=now;
 if(now-stableAt>=1500){state=IDLE;tared=false;Serial.println("Tray appears empty. Inspect, then TARE before next test.");}
 }
 bool alarm=(state==READY||state==FAULT)&&((now/300)%2==0);
 digitalWrite(alarmLed,alarm);if(alarm)tone(buzzerPin,4000);else noTone(buzzerPin);
 while(Serial.available()){char c=Serial.read();if(c=='\n'){command(line);line="";}else if(c!='\r'&&line.length()<99)line+=c;}
 delay(1);
}

// Wiring verified: tube inputs use input-only GPIOs; servo outputs pass through AHCT buffer.

Remix this project

Make it yours in one click

Open a full copy of this project in your own Schematik workspace — diagram, code, parts, and assembly steps included. Swap the sensor, add features, or redesign the whole thing with AI. The author's original stays untouched.

Open in Schematik