Greetings everyone, and welcome back!
Meet Medic Mini, a handheld self-diagnostic tool designed to guide users through basic symptom checks with just a few button presses. Whether you're feeling under the weather or just want a quick health triage, Medic Mini offers a fast, intuitive way to assess common symptoms without needing an app, internet, or medical jargon.
At its core, Medic Mini runs on an ESP32-C6 paired with a 1.47" Waveshare display, housed inside a custom PCB and a 3D-printed enclosure I designed from scratch. The interface is clean and purposeful: users are prompted with one symptom at a time and respond using three tactile buttons—Yes, No, or Not Sure.
I designed Medic Mini to serve as a simple, standalone tool for diagnosing basic medical conditions like fever, viral infections, and fatigue. The current logic is based on common symptoms and straightforward decision rules, but this is just the beginning. I plan to expand its diagnostic database by collecting more symptom data and working with medical professionals to refine the logic.
Based on the pattern of responses, the device offers a basic diagnostic suggestion, making health checks feel as simple as flipping a switch.
This Article covers the whole build process of this project, so let's get started with the build.
Materials RequiredThese were the materials we used in this project:
- Custom PCB (provided by PCBWAY)
- Waveshare ESP32 C6 DISPLAY Board
- IP5306
- 10 uF Capacitors
- Push Buttons 4x4 Size
- Push Buttons 6x6 Size
- ESP32-C6 Devkit
- ILI9341
- Jumper Wires
- Breadboard
- 3D-Printed Parts
- 1uH SMD Inductor
- 10K Resistors
- 3.7V 600mAh LiPo Cell
- M2 Screws
The concept behind Medic Mini was born out of a simple frustration: most health checkers are either too complex, too expensive, or too slow.
I wanted something instant, intuitive, and portable, a device that could walk someone through basic symptoms without needing an app or the internet.
The idea came from observing how people respond to simple yes/no questions when describing how they feel. That’s where the logic started:
• Ask one symptom at a time
• Let the user respond with a button press
• Track the pattern of responses
• Offer a basic result or suggestion
It’s not meant to replace a doctor; it’s meant to give clarity when you’re unsure. Whether it’s for kids, travelers, or just quick reassurance, the goal was to make a health checkup feel like flipping a switch.
The spark really came after watching my own parents. One day, my mom had a headache that seemed minor but by the next day, it turned out to be viral. That made me realize how many symptoms are actually straightforward to track and interpret.
So I thought, why not build a diagnostic device that can guide people through that process?
Right now, the data I’ve collected is limited, but the potential is huge. With input from doctors and medical professionals, we could expand the database, refine the logic, and even integrate sensors to read pulse, temperature, and other vitals.
Medic Mini is just the start; the goal is to make accessible, intelligent health tools that anyone can use.
Basic Setup- ESP32 C6 Devkit Breadboard EditionTo get started with the electronics, I built a simple breadboard setup using the ESP32-C6 DevKit connected to a 320×240 ILI9341 display.
For user interaction, I added three tactile buttons—Yes, No, and Not Sure—mapped to GPIO pins. This setup served as our base model for testing the core logic and UI flow.
Once everything is working reliably, the plan is to shrink the entire system into a compact, handheld device using a custom PCB and a 3D-printed enclosure.
We started by placing the ESP32-C6 DevKit on a breadboard alongside a 320×240 ILI9341 display and three push buttons for user input.
Using the provided wiring diagram, we connected the display to the ESP32 Devkit in the following order.
- DISPLAY's MOSI to GPIO6
- SCK to GPIO7
- Chip Select to GPIO10
- Reset to GPIO11
- DC to GPIO12
- LED Pin of Display goes to 3V3 of the DevKit.
- VCC goes to 5V
- GND to GND
This setup gave us a clean, testable base to test the UI and logic before moving on to a custom PCB and enclosure.
CODE for Breadboard EditionFor our Breadboard setup, we use the following code and let's have a quick breakdown of how it works.
#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
#include <SPI.h>
// Display pins
#define TFT_CS 10
#define TFT_DC 12
#define TFT_RST 11
// Button pins
#define BTN_YES 15
#define BTN_NO 23
#define BTN_UNSURE 22
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST);
// Symptom list
const char* symptoms[] = {
"Headache", "Fever", "Cough", "Fatigue", "Nausea"
};
const int symptomCount = sizeof(symptoms) / sizeof(symptoms[0]);
int responses[symptomCount]; // -1 = not answered, 0 = No, 1 = Yes, 2 = Not Sure
int currentSymptom = -1;
bool symptomDrawn = false;
enum ScreenState {
SCREEN_STARTUP,
SCREEN_SYMPTOM,
SCREEN_RESULT
};
ScreenState currentScreen = SCREEN_STARTUP;
ScreenState lastScreen = SCREEN_STARTUP;
bool lastYes = false;
bool lastNo = false;
bool lastUnsure = false;
unsigned long lastInputTime = 0;
const unsigned long inputLockout = 300;
bool isStablePress(int pin, bool& lastState) {
bool current = digitalRead(pin) == LOW;
bool pressed = current && !lastState;
lastState = current;
return pressed;
}
void drawCenteredText(const char* text, int y, int textSize, uint16_t color) {
tft.setTextSize(textSize);
tft.setTextColor(color);
int16_t x1, y1;
uint16_t w, h;
tft.getTextBounds(text, 0, y, &x1, &y1, &w, &h);
int x = (320 - w) / 2;
tft.setCursor(x, y);
tft.println(text);
}
void drawStartup() {
tft.fillScreen(ILI9341_BLACK);
drawCenteredText("Medic", 40, 3, ILI9341_CYAN);
drawCenteredText("Mini", 80, 3, ILI9341_CYAN);
drawCenteredText("Symptom", 130, 2, ILI9341_WHITE);
drawCenteredText("Checker", 160, 2, ILI9341_WHITE);
// Red medical cross centered at bottom
int crossCenterX = 160;
int crossCenterY = 210;
// Vertical bar
tft.fillRect(crossCenterX - 10, crossCenterY - 30, 20, 60, ILI9341_RED);
// Horizontal bar
tft.fillRect(crossCenterX - 30, crossCenterY - 10, 60, 20, ILI9341_RED);
}
void drawSymptomPrompt(const char* symptom, int response) {
tft.fillScreen(ILI9341_BLACK);
drawCenteredText("Do you have", 80, 2, ILI9341_WHITE);
char buffer[32];
snprintf(buffer, sizeof(buffer), "%s?", symptom);
drawCenteredText(buffer, 110, 2, ILI9341_WHITE);
if (response == 1) {
drawCenteredText("Yes", 180, 3, ILI9341_GREEN);
} else if (response == 0) {
drawCenteredText("No", 180, 3, ILI9341_RED);
} else if (response == 2) {
drawCenteredText("Not Sure", 180, 3, ILI9341_YELLOW);
}
}
void drawResult() {
tft.fillScreen(ILI9341_BLACK);
drawCenteredText("Processing...", 60, 2, ILI9341_GREEN);
delay(1000);
int yesCount = 0;
for (int i = 0; i < symptomCount; i++) {
if (responses[i] == 1) yesCount++;
}
tft.fillScreen(ILI9341_BLACK);
if (yesCount >= 3) {
drawCenteredText("Possible", 100, 2, ILI9341_WHITE);
drawCenteredText("match:", 170, 2, ILI9341_WHITE);
drawCenteredText("Flu or Viral", 210, 2, ILI9341_YELLOW);
} else if (yesCount == 2) {
drawCenteredText("Mild symptoms", 100, 2, ILI9341_WHITE);
drawCenteredText("Monitor &", 140, 2, ILI9341_WHITE);
drawCenteredText("rest", 170, 2, ILI9341_WHITE);
} else {
drawCenteredText("No major", 100, 2, ILI9341_WHITE);
drawCenteredText("match found", 140, 2, ILI9341_WHITE);
}
drawCenteredText("Press YES to restart", 200, 1, ILI9341_CYAN);
}
void setup() {
Serial.begin(115200);
SPI.begin(7, -1, 6); // SCK=7, MISO not used, MOSI=6
tft.begin();
tft.setRotation(3); // Landscape
pinMode(BTN_YES, INPUT_PULLUP);
pinMode(BTN_NO, INPUT_PULLUP);
pinMode(BTN_UNSURE, INPUT_PULLUP);
for (int i = 0; i < symptomCount; i++) {
responses[i] = -1;
}
drawStartup();
}
void loop() {
unsigned long now = millis();
if (currentScreen != lastScreen) {
if (currentScreen == SCREEN_STARTUP) {
drawStartup();
} else if (currentScreen == SCREEN_RESULT) {
drawResult();
}
lastScreen = currentScreen;
}
if (currentScreen == SCREEN_SYMPTOM &&
currentSymptom < symptomCount &&
!symptomDrawn) {
drawSymptomPrompt(symptoms[currentSymptom], responses[currentSymptom]);
symptomDrawn = true;
}
if (currentScreen == SCREEN_STARTUP &&
(isStablePress(BTN_YES, lastYes) || isStablePress(BTN_NO, lastNo) || isStablePress(BTN_UNSURE, lastUnsure))) {
currentSymptom = 0;
currentScreen = SCREEN_SYMPTOM;
symptomDrawn = false;
delay(300);
}
if (currentScreen == SCREEN_SYMPTOM && currentSymptom < symptomCount) {
if (now - lastInputTime > inputLockout) {
if (isStablePress(BTN_YES, lastYes)) {
responses[currentSymptom] = 1;
drawSymptomPrompt(symptoms[currentSymptom], 1);
delay(500);
currentSymptom++;
symptomDrawn = false;
lastInputTime = now;
} else if (isStablePress(BTN_NO, lastNo)) {
responses[currentSymptom] = 0;
drawSymptomPrompt(symptoms[currentSymptom], 0);
delay(500);
currentSymptom++;
symptomDrawn = false;
lastInputTime = now;
} else if (isStablePress(BTN_UNSURE, lastUnsure)) {
responses[currentSymptom] = 2;
drawSymptomPrompt(symptoms[currentSymptom], 2);
delay(500);
currentSymptom++;
symptomDrawn = false;
lastInputTime = now;
}
if (currentSymptom >= symptomCount) {
currentScreen = SCREEN_RESULT;
}
}
}
if (currentScreen == SCREEN_RESULT && isStablePress(BTN_YES, lastYes)) {
for (int i = 0; i < symptomCount; i++) {
responses[i] = -1;
}
currentSymptom = 0;
currentScreen = SCREEN_SYMPTOM;
symptomDrawn = false;
delay(300);
}
}We use the Adafruit ILI9341 library to control our 320×240 TFT display. The SPI pin and input button configuration are defined as follows.
#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
#include <SPI.h>
#define TFT_CS 10
#define TFT_DC 12
#define TFT_RST 11
#define BTN_YES 15
#define BTN_NO 23
#define BTN_UNSURE 22This Section contains Symptom Data, here we added a fixed list of symptoms. response[] stores the user's input and currentSymptom tracks which symptom is currently being asked.
const char* symptoms[] = { "Headache", "Fever", "Cough", "Fatigue", "Nausea" };
int responses[symptomCount]; // -1 = not answered, 0 = No, 1 = Yes, 2 = Not Sure
int currentSymptom = -1;We used an enum to control which screen is active, our setup works in three parts basically: STARTUP, which is the welcome screen; SYMPTOM, which prompts the symptom loop; and RESULT that displays the final diagnosis.
enum ScreenState { SCREEN_STARTUP, SCREEN_SYMPTOM, SCREEN_RESULT };
ScreenState currentScreen = SCREEN_STARTUP;
ScreenState lastScreen = SCREEN_STARTUP;This function detects button presses and avoids false triggers from bouncing or holding. Also, this tracks the previous state of each button for debounce logic.
bool isStablePress(int pin, bool& lastState)
bool lastYes = false;
bool lastNo = false;
bool lastUnsure = false;Here's what happens in the Symptom loop: Medic Mini displays a question like “Do you have a headache?”
We press one of the three responses: YES, NO or NOT SURE.
After a short delay, it moves to the next symptom. Once all are answered, the sketch counts how many “YES” responses and shows a basic result.
HANDHELD DEVICE DESIGNFor the design, the goal was to create a handheld device and naturally, the first thing that came to mind was a TV remote.
So I went with a similar form factor: long, slim, and with buttons positioned near the thumb for easy access.
To begin, I imported the ESP32-C6 board model into Fusion 360, along with models for the Type-C port and push buttons. Using these references, I laid out a long PCB: the display sits at the top, the buttons are centered, and the USB Type-C port is placed at the bottom.
The enclosure was designed around this PCB layout. I added three button actuators that align directly above the push buttons; pressing the actuator triggers the switch underneath. The enclosure consists of two interlocking halves, secured together with screws.
We printed both halves using white PLA, while the button actuators were printed in grey PLA to give a clean contrast and tactile feel.
ESP32-C6-LCD-1.47For the handheld version of Medic Mini, I switched to a more integrated solution: the ESP32-C6 LCD 1.47" Display Board by Waveshare.
The name might be long, but the board itself is surprisingly compact and it packs a lot of power into a small footprint.
The board features an ESP32-C6 microcontroller with a 32-bit RISC-V core running at up to 160 MHz, along with Wi-Fi 6 (802.11 b/g/n) and Bluetooth 5 (LE) support. It includes 4MB of Flash, 512KB of SRAM, and 320KB of ROM, and comes equipped with a 1.47" ST7789 LCD display offering a 172×320 resolution and 262K colors. For interaction and expansion, it also provides an onboard RGB LED for visual feedback, a TF card slot, and a GPIO header for connecting additional peripherals.
The built-in ST7789 display is SPI-based and works great with libraries like Adafruit GFX or LVGL. It’s crisp, colorful, and perfect for rendering the Medic Mini UI in a tight space.
This board let me ditch the breadboard and external display entirely; everything is now packed into a single, compact unit that fits inside a custom 3D-printed enclosure.
You can check out more about this display from its wiki page.
https://www.waveshare.com/wiki/ESP32-C6-LCD-1.47
Also, for sourcing this display, we got it from PCBWAY's Giftshop.
PCB DESIGNThe PCB design for Medic Mini is split into two main sections. First, we built the schematic around the Waveshare ESP32-C6 Dev Board, which connects to three tactile buttons for user input. These buttons are wired to GPIO9, GPIO18, and GPIO19, with each switch also tied to GND. When a button is pressed, the corresponding GPIO pin is pulled low, registering a valid input.
The second section handles power delivery. We use the IP5306 power management IC, which boosts the 3.7V from a lithium-ion cell to a stable 5V at 2A, enough to reliably power the ESP32 board and display. The module also includes a charging status LED; it blinks while charging and stays solid once the battery is full. Built-in features like overcharge protection, low battery cutoff, and full charge cutoff help extend battery life and prevent damage from unsafe voltage levels.
Using the dimensions from the CAD model, we prepared the PCB outline and then placed the buttons in their mounting positions as specified in the design. We did the same for the Waveshare ESP32 board, the Type-C port, and the mounting holes. The rest of the components were placed wherever we found adequate space, and then we connected the tracks and finalized the board.
After completing the PCB design, we exported the Gerber data and shared it with a PCB manufacturer to get samples made.
PCBWAY SERVICEOnce the board design was finalized, I opted for a purple solder mask with white silkscreen and uploaded the Gerber files to PCBWay’s quote page for fabrication.
While I typically go with a white or black solder mask for most of my builds, this time I decided to try out PCBWay’s Purple option just for a change. The order was placed smoothly, and the PCBs arrived within a week.
The quality was excellent—clean finish, sharp silkscreen, and everything matched the design perfectly.
Over the past ten years, PCBWay has distinguished themselves by providing outstanding PCB manufacturing and assembly services, becoming a trusted partner for countless engineers and designers worldwide.
Also, PCBWay is organizing the PCBWay 8th Project Design Contest, a global event that invites makers, engineers, and innovators to showcase their most creative builds. With categories in Electronics, Mechanical, and AIoT, it’s a great opportunity to share your work, connect with the community, and compete for exciting prizes.
You guys can check out PCBWAY if you want great PCB service at an affordable rate.
PCB ASSEMBLY PROCESS- We start the PCB assembly process by applying solder paste to each SMD component pad using a solder paste syringe. We’re using 63-37 SnPb paste, which has a melting temperature of 200°C.
- Next, we begin the pick-and-place process, which involves picking up SMD components using ESD tweezers and placing them in their designated locations.
- Once placed, we lift the circuit and set it on our SMD reflow hotplate. The hotplate heats the PCB from below until it reaches the solder paste’s melting temperature. As soon as the PCB hits 200°C, the solder paste melts and secures the components to the board.
- After that, we place a 6×6 tactile switch on the bottom side of the PCB and solder it in place using a soldering iron.
- The rest of the through-hole components are added from the top side, including three 4×4 tactile switches and one Type-C port.
- Finally, we place the Waveshare ESP32 board in its position and solder it using a soldering iron. We simply align it over the mounting pads and use excess solder on each pad to join the ESP32 board to the PCB.
For the power source of this project, we are utilizing a 3.7V 600mAh Li-Po cell, which can provide a total runtime of more than 10 hours, which is pretty decent, considering we won’t be using it continuously. After diagnosing, we can turn the device OFF, which gives us a backup of 2–3 days depending on usage.
We solder the positive and negative terminals of the lithium cell to the battery connectors provided on the circuit.
By pressing the 6×6 tactile switch added on the backside of the circuit, the device turns ON. Double-tapping the switch turns the device OFF.
We can also charge this setup by plugging in a 5V supply via the provided Type-C port. During charging, the indicator LED will blink, and it turns stable once the lithium cell is fully charged.
CODE for Waveshare ESP32 C6 1.47 DisplayThis is the finalized code running on the Medic Mini device. It differs slightly from our earlier demo sketch due to changes in the display and I/O configuration. The demo version used an ILI9341 display with the ESP32-C6 DevKit, which required a different set of GPIO assignments and library setup.
In contrast, the final build uses the Waveshare ESP32-C6 1.47" display module.
The below Breakdown highlights all the key differences introduced in this updated sketch.
#include <Arduino_GFX_Library.h>
// LCD pin map for ESP32-C6
#define LCD_MOSI 6
#define LCD_SCLK 7
#define LCD_CS 14
#define LCD_DC 15
#define LCD_RST 21
#define LCD_BL 22
// Button pins
#define BTN_YES 9
#define BTN_NO 18
#define BTN_UNSURE 19
const char* symptoms[] = {
"Headache",
"Fever",
"Cough",
"Fatigue",
"Nausea"
};
const int symptomCount = sizeof(symptoms) / sizeof(symptoms[0]);
int responses[symptomCount]; // -1 = not answered, 0 = No, 1 = Yes, 2 = Not Sure
int currentSymptom = -1;
bool symptomDrawn = false;
enum ScreenState {
SCREEN_STARTUP,
SCREEN_SYMPTOM,
SCREEN_RESULT
};
ScreenState currentScreen = SCREEN_STARTUP;
ScreenState lastScreen = SCREEN_STARTUP;
// Button states
bool lastYes = false;
bool lastNo = false;
bool lastUnsure = false;
unsigned long lastInputTime = 0;
const unsigned long inputLockout = 300;
Arduino_DataBus *bus = new Arduino_ESP32SPI(
LCD_DC, LCD_CS, LCD_SCLK, LCD_MOSI, GFX_NOT_DEFINED
);
Arduino_GFX *gfx = new Arduino_ST7789(
bus,
LCD_RST,
2,
true,
172,
320,
34, 0,
34, 0
);
// Stable press detection
bool isStablePress(int pin, bool& lastState) {
bool current = digitalRead(pin) == LOW;
bool pressed = current && !lastState;
lastState = current;
return pressed;
}
void drawStartup() {
gfx->fillScreen(BLACK);
// Title split: Medic / Mini
gfx->setTextSize(3);
gfx->setTextColor(CYAN);
gfx->setCursor(20, 40);
gfx->println("Medic");
gfx->setCursor(20, 80);
gfx->println("Mini");
// Subtitle split: Symptom / Checker
gfx->setTextSize(2);
gfx->setTextColor(WHITE);
gfx->setCursor(20, 130);
gfx->println("Symptom");
gfx->setCursor(20, 160);
gfx->println("Checker");
// Red medical plus sign
gfx->fillRect(80, 220, 20, 60, RED); // vertical bar
gfx->fillRect(60, 240, 60, 20, RED); // horizontal bar
}
void drawSymptomPrompt(const char* symptom, int response) {
gfx->fillScreen(BLACK);
gfx->setTextSize(2);
gfx->setTextColor(WHITE);
gfx->setCursor(10, 80);
gfx->println("Do you have");
gfx->setCursor(10, 110);
gfx->print(symptom);
gfx->println("?");
// Centered response label
gfx->setTextSize(3);
int centerX = 86;
if (response == 1) {
gfx->setTextColor(GREEN);
gfx->setCursor(centerX - 30, 180);
gfx->println("Yes");
} else if (response == 0) {
gfx->setTextColor(RED);
gfx->setCursor(centerX - 30, 180);
gfx->println("No");
} else if (response == 2) {
gfx->setTextColor(YELLOW);
gfx->setCursor(centerX - 70, 180);
gfx->println("Not Sure");
}
}
void drawResult() {
gfx->fillScreen(BLACK);
gfx->setCursor(10, 60);
gfx->setTextSize(2);
gfx->setTextColor(GREEN);
gfx->println("Processing...");
delay(1000);
int yesCount = 0;
for (int i = 0; i < symptomCount; i++) {
if (responses[i] == 1) yesCount++;
}
gfx->fillScreen(BLACK);
gfx->setTextSize(2);
gfx->setTextColor(WHITE);
if (yesCount >= 3) {
gfx->setCursor(10, 100);
gfx->println("Possible");
gfx->setCursor(10, 170);
gfx->println("match:");
gfx->setCursor(10, 210);
gfx->setTextColor(YELLOW);
gfx->println("Flu or Viral");
} else if (yesCount == 2) {
gfx->setCursor(10, 100);
gfx->println("Mild symptoms");
gfx->setCursor(10, 140);
gfx->println("Monitor &");
gfx->setCursor(10, 170);
gfx->println("rest");
} else {
gfx->setCursor(10, 100);
gfx->println("No major");
gfx->setCursor(10, 140);
gfx->println("match found");
}
gfx->setCursor(10, 200);
gfx->setTextSize(1);
gfx->setTextColor(CYAN);
gfx->println("Press YES to restart");
}
void setup() {
pinMode(LCD_BL, OUTPUT);
digitalWrite(LCD_BL, HIGH);
pinMode(BTN_YES, INPUT_PULLUP);
pinMode(BTN_NO, INPUT_PULLUP);
pinMode(BTN_UNSURE, INPUT_PULLUP);
for (int i = 0; i < symptomCount; i++) {
responses[i] = -1;
}
gfx->begin();
drawStartup();
}
void loop() {
unsigned long now = millis();
if (currentScreen != lastScreen) {
if (currentScreen == SCREEN_STARTUP) {
drawStartup();
} else if (currentScreen == SCREEN_RESULT) {
drawResult();
}
lastScreen = currentScreen;
}
if (currentScreen == SCREEN_SYMPTOM &&
currentSymptom < symptomCount &&
!symptomDrawn) {
drawSymptomPrompt(symptoms[currentSymptom], responses[currentSymptom]);
symptomDrawn = true;
}
if (currentScreen == SCREEN_STARTUP &&
(isStablePress(BTN_YES, lastYes) || isStablePress(BTN_NO, lastNo) || isStablePress(BTN_UNSURE, lastUnsure))) {
currentSymptom = 0;
currentScreen = SCREEN_SYMPTOM;
symptomDrawn = false;
delay(300);
}
if (currentScreen == SCREEN_SYMPTOM && currentSymptom < symptomCount) {
if (now - lastInputTime > inputLockout) {
if (isStablePress(BTN_YES, lastYes)) {
responses[currentSymptom] = 1;
drawSymptomPrompt(symptoms[currentSymptom], 1);
delay(500);
currentSymptom++;
symptomDrawn = false;
lastInputTime = now;
} else if (isStablePress(BTN_NO, lastNo)) {
responses[currentSymptom] = 0;
drawSymptomPrompt(symptoms[currentSymptom], 0);
delay(500);
currentSymptom++;
symptomDrawn = false;
lastInputTime = now;
} else if (isStablePress(BTN_UNSURE, lastUnsure)) {
responses[currentSymptom] = 2;
drawSymptomPrompt(symptoms[currentSymptom], 2);
delay(500);
currentSymptom++;
symptomDrawn = false;
lastInputTime = now;
}
if (currentSymptom >= symptomCount) {
currentScreen = SCREEN_RESULT;
}
}
}
if (currentScreen == SCREEN_RESULT && isStablePress(BTN_YES, lastYes)) {
for (int i = 0; i < symptomCount; i++) {
responses[i] = -1;
}
currentSymptom = 0;
currentScreen = SCREEN_SYMPTOM;
symptomDrawn = false;
delay(300);
}
}In our main sketch, we use the Arduino GFX Library to drive the ST7789 display.
The following configuration sets up the screen dimensions, rotation, and offset parameters tailored to the Waveshare ESP32-C6 1.47" module.
Arduino_GFX *gfx = new Arduino_ST7789(
bus,
LCD_RST,
2, // rotation
true, // IPS
172, // width
320, // height
34, 0, // X offset
34, 0 // Y offset
);We configured the pin mapping to match the default wiring of the Waveshare ESP32-C6 1.47" display. These assignments were based on the official Waveshare Wiki documentation.
#define LCD_MOSI 6
#define LCD_SCLK 7
#define LCD_CS 14
#define LCD_DC 15
#define LCD_RST 21
#define LCD_BL 22Next comes the button input.
#define BTN_YES 9
#define BTN_NO 18
#define BTN_UNSURE 19The rest of the code logic remains unchanged from the previous demo sketch. The only modifications made here are the updated display configuration and revised input button mappings to match the final hardware.
We uploaded the code into our ESP32 board and moved onto the next step, which was the enclosure assembly. From next, we cannot upload code into ESP32 because its USB will be inaccessible.
ENCLOSURE ASSEMBLY- The enclosure assembly begins by placing the switch actuators into the front half of the body. Next, we position the circuit board over the screw bosses and secure it using two M2 screws.
- A strip of double-sided tape is applied to the back of the LiPo cell, which is then pressed firmly onto the circuit to hold it in place.
- After that, we align the back half of the enclosure with the front and fasten them together using four M2 screws, completing the full assembly of the device.
After completing the build, I tested Medic Mini by running through the symptom checker and sure enough, I was greeted with a “Fever” message based on my inputs.
The logic works as intended, and the device responds quickly and accurately to button presses. It’s a solid proof of concept for a portable, interactive diagnostic tool.
That said, this is just the beginning. To truly elevate Medic Mini to a practical, real-world level, we’ll need to consult with medical professionals and gather validated symptom data. This will help refine the logic and ensure the device aligns with actual diagnostic standards.
Looking ahead, I plan to expand the project by integrating additional medical sensors in future revisions, starting with an infrared temperature sensor (MLX90614) and eventually adding support for heart rate, SpO₂, and other vitals. The goal is to turn Medic Mini into a compact, multi-sensor health assistant that’s both accessible and reliable.
VERSION 2For version 2 of this project, I’m planning to integrate sensors to collect real-time health data. The first upgrade will be the MLX90614 infrared temperature sensor, which will let us measure body temperature without contact. That data can then be fed directly into the symptom checker logic.
Overall, this setup was finished, and I would like to make revisions to this project in the future to further add sensors, helping expand Medic Mini’s diagnostic capabilities beyond just button-based input.
Thanks for reaching this far, and I will be back with a new project pretty soon.
Peace.






_t9PF3orMPd.png?auto=compress%2Cformat&w=40&h=40&fit=fillmax&bg=fff&dpr=2)








Comments