Smart Medication System - shalan/CSCE4301-WiKi GitHub Wiki
| Name | GitHub |
|---|---|
| Nadia Dessouky | nadiadessouky24 |
| Nour Selim | nourselim |
Github Repo: Smart-Medical-Dispenser-Github-Link
Proposal Presentation: https://canva.link/9kqljnv628obovf
Milestone 3 Presentation: https://canva.link/gsxf2pqk3uec3s4
Final Presentation: https://canva.link/olqzg2743kiidno
Medication non-adherence is a serious healthcare problem, particularly among elderly patients who often miss doses due to forgetfulness or lack of supervision. The Smart Medication Dispenser was proposed to fill that gap with an affordable, microcontroller-driven device that automatically dispenses pills, alerts the patient, and confirms the dose was actually taken.
The device is built around the STM32L432KC and uses a DS3231 RTC module to track time and trigger scheduled alarms. When a dose is due, an SG90 servo motor rotates a four-compartment pill wheel until the correct compartment aligns with a fixed slot, containing the medicine. A buzzer alerts the patient. An IR sensor (TCRT5000) detects whether the pill was removed from the tray, and the patient presses an acknowledgment button to confirm. If no pill removal is detected within 30 seconds, the system logs the event as a missed dose with a timestamp on a 16x2 LCD over UART.
To keep the system practical and self-contained, patients or caregivers can configure up to four daily dose times directly on the device using their phone via a bluetooth module. This whole system will be powered by a 5V power source.
Objectives:
-
Automate pill dispensing — Dispense the correct compartment at up to four scheduled times daily using the STM32L432KC, with no patient intervention.
-
Verify and log doses — Confirm pill removal via an IR sensor and acknowledgment button. Log each dose as "taken" or "missed" with a timestamp over UART.
-
Ensure standalone reliability — Allow schedule configuration via a keypad and LCD. Persist data in EEPROM to survive power cycles.
Minimal Viable Product:
- Dispense the correct pill compartment within ±5 seconds of the scheduled time using the DS3231 RTC and SG90 servo motor.
- Alert the patient with a buzzer and RGB LED when a dose is dispensed.
- Detect pill removal using the TCRT5000 IR sensor and confirm via an acknowledgment button.
- Allow configuration of up to 4 daily dose times via the 4×3 membrane keypad.
- Display status messages and menus on a 16×2 LCD (communicating over UART).
- Persist the dose schedule across power cycles using EEPROM.
- To be able to log each dose as "taken" or "missed" with a timestamp via bluetooth module on patient and/or caregiver phone. (Stretch Goal)
Proposed Components:
| Controller (STM32L432KC) | DS3231 RTC Module | Bluetooth Module |
|---|---|---|
![]() |
![]() |
![]() |
| 16×2 LCD | IR Sensor | Micro Servo Motor |
![]() |
![]() |
![]() |
| Buzzer | Switch | |
![]() |
![]() |
.png)
The STM32L432KC MCU acts as the central controller, coordinating all subsystems based on timing from the DS3231 RTC module over I2C. The user sends scheduled dose times to the system wirelessly through the Bluetooth module (UART), which the MCU stores and tracks against the RTC. When a scheduled dose arrives, the MCU sends a PWM signal to the SG90 servo motor to rotate the dispensing mechanism. The TCRT5000 IR sensor provides analog feedback via ADC to confirm that a dose was actually dispensed. The 16×2 LCD displays the current time, system status, and next dose time over I2C, while the buzzer emits a PWM-driven alert tone to notify the user. The user acknowledges dose intake through a push-button switch (GPIO); if no acknowledgment is received within a 30-second notice period, the buzzer continues sounding and the LCD displays "Dose Missed," otherwise it shows "Dose Taken." All modules feed into or are driven by the MCU, creating a closed loop from scheduling through dispensing, verification, and user acknowledgment.
- Controller (STM32L432KC): Chosen for its 32-bit ARM Cortex-M4 core running at 80MHz, built-in FPU for faster timing calculations, and ultra-low-power modes that suit an always-on medical device.
- Real-Time Clock (DS3231): Chosen for its built-in temperature-compensated crystal oscillator (TCXO), providing ±2 ppm accuracy (~1 minute drift per year), which is critical when missing a dose time has medical consequences.
- Pill Dispensing Motor (SG90 Servo): Chosen for its precise angular positioning via PWM, simple 3-wire interface, and sufficient torque (1.8 kg·cm) to rotate a lightweight pill wheel without needing a driver board.
- IR Sensor (TCRT5000): Chosen for short-range pill detection because its reflective optical design works reliably at <10mm distances, has no minimum dead zone, and provides a clean digital output when paired with a comparator.
- Display (16×2 LCD over UART): Chosen to free up GPIO pins on the STM32L432KC (which has limited I/O) and simplify wiring, since UART requires only TX/RX lines and the LCD's onboard controller handles character rendering.
- Alerting (Passive Buzzer + RGB LED): Chosen as dual-modality alerts to accommodate hearing-impaired users. The RGB LED provides visual cues (green = taken, red = missed, blue = upcoming) while the passive buzzer allows tone variation for different alert types.
- Acknowledgment Button: Chosen to shut off the active dose timer once pressed, confirming the patient has taken their medication and preventing the system from logging the event as a missed dose.

| Component | Part Number | Quantity | Cost (EGP) | Datasheet |
|---|---|---|---|---|
| Microcontroller | STM32L432KC (Nucleo-32) | 1 | 750 | Datasheet |
| RTC Module | DS3231 | 1 | 150 | Datasheet |
| Bluetooth Module | HC-05 | 1 | 200 | Datasheet |
| LCD Display | 16×2 (HD44780) | 1 | 70 | Datasheet |
| IR Sensor | TCRT5000 | 1 | 65 | Datasheet |
| Servo Motor | SG90 | 1 | 150 | Datasheet |
| Buzzer | Active Buzzer | 1 | 5 | Datasheet |
| Switch | Tactile Switch | 1 | 5 | Datasheet |
| Total | 1,395 EGP |
| Component | Voltage | Peak Current | Supply |
|---|---|---|---|
| SG90 Servo | 5V | 650 mA | External 5V |
| STM32L432KC | 3.3V | 10 mA | Main 5V → LDO |
| DS3231 RTC | 3.3V | 0.2 mA | Main 5V → LDO |
| TCRT5000 IR Sensor | 5V | 60 mA | Main 5V |
| 16×2 LCD (UART) | 5V | 30 mA | Main 5V |
| Passive Buzzer | 3.3V | 30 mA | Main 5V → LDO |
| RGB LED | 3.3V | 60 mA | Main 5V → LDO |
| Acknowledgment Button | 3.3V | 1 mA | Main 5V → LDO |
-
External 5V supply (motor only): 650 mA
-
Main 5V supply (logic + sensors): ~191 mA
-
3.3V rail (via STM32 LDO): ~101 mA
-
Motor: Dedicated 5V / 1A supply isolates servo stall currents and inrush spikes from the logic rail, preventing brownouts on the STM32 during dispensing.
-
Logic: 5V / 500 mA USB source comfortably powers all sensors and the LCD.
-
Common ground: Both supplies share a common GND to ensure PWM signal integrity between the STM32 and servo.
The firmware uses FreeRTOS running on the STM32L432KC. A single task, StartDefaultTask, handles Bluetooth input, reads the DS3231 RTC every 5 seconds, and triggers the dose-dispensing state machine when a scheduled time matches. All peripheral access is done through blocking HAL calls with osDelay() yielding the CPU to the scheduler. SysTick drives the kernel tick.
graph TD;
%% ISRs
subgraph ISRs
SysTick[SysTick IRQ<br/>Drives Scheduler]
HALTick[HAL Tick]
end
SysTick --> Kernel;
HALTick --> Kernel;
%% Boot
Start([Power On]) --> Init[Init Peripherals];
Init --> Load[Load Schedule from Flash];
Load --> Kernel[FreeRTOS Scheduler];
Kernel --> Loop[Default Task Loop];
%% Main logic
Loop --> BT[Poll Bluetooth UART];
BT --> RTC[Read RTC Time];
RTC --> Check{Dose Time?};
Check -- No --> Loop;
Check -- Yes --> Idle;
%% State machine
Idle[State: Idle] --> Alerting[State: Alerting<br/>Servo + Buzzer + LCD];
Alerting -- Pill Removed --> Taken[State: Taken];
Alerting -- Switch Pressed --> Ack[State: Acknowledged];
Alerting -- 30s Timeout --> Missed[State: Missed];
Ack -- Pill Removed --> Taken;
Ack -- 30s Timeout --> Missed;
Taken --> Advance[Advance Dose + Save Flash];
Missed --> Advance;
Advance --> Loop;
- Two-phase dose acknowledgement: Phase 1 waits 30s for pill removal or a switch press; pressing the switch silences the buzzer and grants a second 30s window in Phase 2.
- IR threshold detection: raw ADC value compared against a calibrated threshold (2000) instead of treating the sensor as binary.
-
Flash persistence with header validation: the schedule is validated by a
0xDEADBEEFsignature on boot before being trusted, with each dose packed into a 64-bit word. - Servo calibration: PWM pulse range narrowed to 500-1050 us (instead of the standard 500-2500 us) to map four compartments onto this specific SG90 unit.
We are using Keil µVision 5 and STM32CubeIDE as our development environments. STM32CubeIDE is used for peripheral configuration and HAL code generation through CubeMX, while Keil µVision 5 is used for writing, compiling, and debugging the firmware on the STM32L432KC.
-
I2C bus: an
I2C_Scan()routine sweeps addresses 0x01-0x7F at boot and prints every responding device, confirming the LCD (0x27) and DS3231 RTC (0x68) are reachable before any driver is used. - LCD: initialised independently and tested by printing fixed strings to both rows, verifying 4-bit mode, cursor positioning, and backlight control.
-
DS3231 RTC: validated by calling
RTC_SetTime()once to seed a known value, then reading back throughRTC_GetTime()over multiple boots to confirm BCD conversion and battery-backed persistence. -
IR sensor: the raw ADC value is printed on boot (
IR on boot: %d) so the threshold could be tuned empirically by placing and removing pills and observing the logged value. - Servo: tested with a homing routine that drives the servo to compartment 0 at startup, then by cycling through all four calibrated pulse widths (500, 683, 866, 1050 us) to verify mechanical alignment.
-
Buzzer and switch: tested as simple GPIO toggles. The switch state is logged on boot (
Switch is HIGH/LOW) to confirm pull-up wiring before the dose cycle relies on it. -
Bluetooth (HC-05 on UART1): validated by sending
SET HH:MMcommands from the Arduino Bluetooth app and confirming the parsed schedule echoes back on UART2.
- End-to-end dose cycle: a schedule was sent over Bluetooth with a dose time set one to two minutes ahead of the current RTC time. The system was then observed through a full cycle: LCD updating each minute, servo rotating to the next compartment at the scheduled time, buzzer sounding, IR sensor detecting pill removal, and the schedule advancing to the next entry.
- Acknowledgement paths: all four outcomes of the two-phase state machine were tested individually, namely pill removed in Phase 1, switch pressed then pill removed in Phase 2, 30-second timeout in Phase 1, and 30-second timeout in Phase 2. Each path was confirmed through the UART2 log and the LCD status message.
- Multi-dose scheduling: the maximum of four doses was set with closely spaced times to confirm correct sequencing, servo advancement through all four compartments, and proper shifting of the schedule array after each dose.
- Power-cycle recovery: the board was reset mid-schedule to verify that the saved schedule reloaded from flash, the servo returned to compartment 0, and the next dose alarm still fired at the correct time.
- Stress and edge cases: rapid Bluetooth commands, empty schedules, dose times set to the current minute, and IR sensor occlusion were tested to confirm the firmware handles boundary conditions without locking up or triggering false alarms.
-
Worn servo gear teeth — The SG90's internal gear teeth had worn down, preventing reliable rotation to commanded positions. Resolved by replacing the servo.
-
Servo undervoltage under load — Powering the SG90 from the Nucleo's 3.3 V rail caused voltage sag during the 250–400 mA inrush current associated with movement, leading to erratic positioning. Resolved by migrating the servo to a dedicated 5 V supply with a shared ground reference to the STM32.
-
UART receive overrun during dose execution — Blocking calls within
HandleDoseCycle()prevented timely servicing of incoming HC-05 traffic, resulting in RX register overflow and assertion of the ORE flag, which locked further reception. Resolved by migrating Bluetooth reception to interrupt-driven mode viaHAL_UART_Receive_IT(), with parsing deferred to a ring buffer processed in the main loop. -
Mechanical switch contact bounce — Transient contact oscillation on the SPDT acknowledgment switch (1–10 ms) was being interpreted as a valid user input, causing premature termination of the dose cycle. Resolved by introducing a 20 ms confirmation interval with state re-verification before accepting the input as a registered press.

Milestone 3 Demo: https://drive.google.com/file/d/1z7dHnxBu5OYP-HgLB1DswZt31e5LYsPW/view?usp=sharing
Final Demos:
Test Case #1 2 Doses Scheduled, Dose is Taken Both Times: https://drive.google.com/file/d/1dsN542W62pzrkOd2GxBwiRHfnSsPAOUA/view?usp=sharing
Test Case #2 Missing a Dose, Only Acknowledging: https://drive.google.com/file/d/1mLqRG5BqPnS8tjoSn4WUKRID6Ou_6wFW/view?usp=sharing
Test Case #3 Watching the Servo Go to Next Compartment: https://drive.google.com/file/d/1b53s8RHW9uRkd6c1nuwBuT1PgjfNlWQz/view?usp=sharing
- Pill detection reliability: 100% true-positive rate across 10 test doses with TCRT5000, exceeding the 95% target.
- Compartment alignment: All 4 compartments reached within ±2° of commanded position after servo replacement (target: ±5°).
Milestone 1 and Checkpoint A:
It was a collaborative effort. We brain stormed ideas together to find the one we will use. In addition to that, we worked on editing the wiki and creating the presentation together.
Milestone 3:
Nour: RTC integration — Set up the DS3231 over I2C to track real time and trigger dose events at the scheduled time Buzzer alert system — Implemented the buzzer alert that sounds when a dose is due Missed dose detection — Built the 30-second notice period logic that displays "Dose Missed" and keeps the buzzer sounding if the user doesn't acknowledge
Nadia: Bluetooth dose scheduling — Set up the Bluetooth module (UART) so the user can wirelessly send dose times to the system LCD time & status display — Programmed the 16×2 LCD to show the current time and dose status messages Switch acknowledgment logic — Wired the push-button and implemented the "Dose Taken" confirmation flow
Both of us: System integration — Connecting all the modules (Bluetooth, RTC, LCD, buzzer, switch) into one working flow took both of us debugging side by side End-to-end testing — Verified the full sequence (send dose time → display → alert → acknowledge / miss) by testing together
Milestone 4:
The servo motor integration and full system debugging were carried out collaboratively as a team, with both us working side by side through hardware wiring, PWM calibration, compartment alignment testing, and iterative resolution of issues encountered during bring-up and validation.







