FitTracker: Build Your Own BMI Calculator With Arduino

by _Stemonkey in Circuits > Arduino

232 Views, 3 Favorites, 0 Comments

FitTracker: Build Your Own BMI Calculator With Arduino

Screenshot 2024-10-13 172024.png
Screenshot 2024-10-13 171951.png

Introduction

Welcome to this exciting project where we’ll build a fully functional Body Mass Index (BMI) calculator using an Arduino Uno, a 4x4 matrix keypad, and an I2C LCD. Whether you’re a beginner or a seasoned Arduino enthusiast, this project will guide you through creating a device that calculates BMI based on user inputs. The device also provides personalized health messages based on the calculated BMI, making it fun and educational.

Supplies

Gather Your Materials

Before we start, make sure you have all the necessary components:

Hardware Components

  1. Arduino Uno: The brain of our project.
  2. 4x4 Matrix Keypad: For user input (numbers, decimals, and special commands).
  3. I2C 16x2 LCD Display: To display prompts, inputs, and results.
  4. Buzzer: (Optional) For audio feedback on keypresses and errors.
  5. Connecting Wires: To connect everything on a breadboard.
  6. Breadboard: For assembling the circuit. (additional)

Wiring Connections



  1. Keypad:
  2. Row 1-4: Connect to pins 2-5.
  3. Column 1-4: Connect to pins 6-9.
  4. LCD Display:
  5. SDA: Connect to A4 of Arduino Uno.
  6. SCL: Connect to A5 of Arduino Uno.
  7. Optional: Connecting the Buzzer
  8. Connect the positive lead of the buzzer to a digital pin D11 on the Arduino.
  9. Connect the negative lead to the ground.



Code

Software Libraries

  1. Wire.h: For I2C communication.
  2. LiquidCrystal_I2C.h: To control the I2C LCD.
  3. Keypad.h: For interfacing with the keypad.

Install the necessary libraries in the Arduino IDE for the code to work properly.


#include <Wire.h>
#include <Keypad.h>
#include <LiquidCrystal_I2C.h>
#include <Adafruit_LiquidCrystal.h>

// Initialize the I2C LCD with the correct address
LiquidCrystal_I2C lcd(0x27, 16, 2);

const byte ROWS = 4; // Number of rows in the keypad
const byte COLS = 4; // Number of columns in the keypad

// Define the key map for the 4x4 keypad
char hexaKeys[ROWS][COLS] = {
{'1', '2', '3', 'A'},
{'4', '5', '6', 'B'},
{'7', '8', '9', 'C'},
{'*', '0', '#', 'D'}
};

// Pin connections for the rows and columns
byte rowPins[ROWS] = {9, 8, 7, 6};
byte colPins[COLS] = {5, 4, 3, 2};

// Create the Keypad object with the defined keymap and pin configuration
Keypad customKeypad = Keypad(makeKeymap(hexaKeys), rowPins, colPins, ROWS, COLS);

String ageInput = "";
String heightInput = ""; // Variable to store the height input
String weightInput = ""; // Variable to store the weight input
char genderInput = 'M'; // Default gender is Male
boolean isEnteringAge = true; // Flag to track if entering age
boolean isEnteringGender = false; // Flag to track if entering gender
boolean isEnteringHeight = false; // Flag to track if entering height
boolean isEnteringWeight = false; // Flag to track if entering weight
bool isMetric = true; // Flag to track the unit system (Metric or Imperial)
bool buzzerEnabled = true; // Flag to toggle the buzzer on/off
unsigned long keyPressTime = 0; // Time when the key was pressed
bool longPressDetected = false; // Flag to detect long press
const int buzzerPin = 11; // Pin connected to the buzzer
int heightLimit = 4; // Default limit for height input

void setup() {
Serial.begin(9600); // Initialize Serial Monitor
lcd.init(); // Initialize the LCD
lcd.backlight(); // Turn on the LCD backlight
pinMode(buzzerPin, OUTPUT); // Configure the buzzer pin

// Welcome Screen
lcd.setCursor(0, 0);
lcd.print("BMI Calculator");
delay(3000); // Display the welcome screen for 3 seconds
promptForAge();
}

void loop() {
char key = customKeypad.getKey(); // Get the key pressed on the keypad

if (key) {
playTone(1000, 100); // Normal key press tone

if (key == '*') {
keyPressTime = millis(); // Record the time when '*' was pressed
longPressDetected = false;
}

switch (key) {
case '#':
if (isEnteringAge) {
if (isNumeric(ageInput)) {
isEnteringAge = false;
isEnteringGender = true;
promptForGender();
playTone(1500, 150); // Confirmation tone
} else {
displayError("Invalid Age!");
}
} else if (isEnteringGender) {
isEnteringGender = false;
isEnteringHeight = true;
promptForHeight();
playTone(1500, 150); // Confirmation tone
} else if (isEnteringHeight) {
if (isNumeric(heightInput)) {
isEnteringHeight = false;
isEnteringWeight = true;
promptForWeight();
playTone(1500, 150); // Confirmation tone
} else {
displayError("Invalid Height!");
}
} else if (isEnteringWeight) {
if (isNumeric(weightInput)) {
calculateBMI(); // Calculate and display BMI
playTone(1500, 150); // Confirmation tone
delay(7000); // Display BMI for 7 seconds
promptForAge(); // Reset inputs for next calculation
} else {
displayError("Invalid Weight!");
}
}
break;
case '*':
if (millis() - keyPressTime > 2000 && !longPressDetected) { // If '*' is held for more than 2 seconds
playTone(500, 300); // Reset tone
promptForAge(); // Clear inputs and start over
longPressDetected = true;
} else if (!longPressDetected) { // Short press: clear current input
clearCurrentInput(); // Clear the current input field
}
break;
case 'C':
if (isEnteringGender) {
toggleGender(); // Toggle between Male and Female
} else {
switchUnits(); // Toggle between metric and imperial units without resetting inputs
}
break;
case 'D':
handleDecimalInput(); // Add decimal point
break;
case 'A':
showKeyFunctions(); // Display functions of other keys
break;
case 'B':
toggleBuzzer(); // Toggle buzzer on/off
break;
default:
if (isDigit(key)) {
if (isEnteringAge) {
if (ageInput.length() < 3) { // Limit age input to 3 characters
ageInput += key;
lcd.setCursor(0, 1);
lcd.print(ageInput);
} else {
displayError("Limit: 3 digits");
ageInput = ""; // Clear the input so the user can re-enter valid data
}
} else if (isEnteringHeight) {
if (heightInput.length() < heightLimit) { // Adjust limit based on decimal
heightInput += key;
lcd.setCursor(0, 1);
lcd.print(heightInput);
} else {
displayError("Limit: " + String(heightLimit) + " digits");
heightInput = ""; // Clear the input so the user can re-enter valid data
}
} else if (isEnteringWeight) {
// ** Change made here: Handle weight input length based on decimal point **
if (weightInput.indexOf('.') == -1) { // If no decimal point
if (weightInput.length() < 3) { // Limit to 3 characters
weightInput += key;
lcd.setCursor(0, 1);
lcd.print(weightInput);
} else {
displayError("Limit: 3 digits");
weightInput = ""; // Clear the input
}
} else { // If a decimal point exists
if (weightInput.length() < 5) { // Limit to 5 characters
weightInput += key;
lcd.setCursor(0, 1);
lcd.print(weightInput);
} else {
displayError("Limit: 5 digits");
weightInput = ""; // Clear the input
}
}
}
}
break;
}
}
}

void promptForAge() {
ageInput = "";
genderInput = 'M'; // Default gender is Male
heightInput = "";
weightInput = "";
isEnteringAge = true;
isEnteringGender = false;
isEnteringHeight = false;
isEnteringWeight = false;
heightLimit = 4; // Reset height limit
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Enter Age:");
}

void promptForGender() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Use C to change");
lcd.setCursor(0, 1);
lcd.print("Gender: Male"); // Default is Male
}

void promptForHeight() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(isMetric ? "Height (cm):" : "Height (in):");
lcd.setCursor(0, 1);
}

void promptForWeight() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(isMetric ? "Weight (kg):" : "Weight (lbs):");
lcd.setCursor(0, 1);
}

void showKeyFunctions() {
lcd.clear();
const char* keyExplanations[] = {
"B: Sound On/Off",
"C: Toggle Units",
"D: Add Decimal",
"*: Clear/Reset",
"#: Confirm",
"BMI Range",
"U: Under weight",
"H: Healthy",
"O: Over Weight",
"OB: Obesity",
};

for (int i = 0; i < 10; i++) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(keyExplanations[i]);
delay(2000); // Display each explanation for 2 seconds
}
// After showing the key functions, return to the current input stage
if (isEnteringAge) {
promptForAge();
} else if (isEnteringGender) {
promptForGender();
} else if (isEnteringHeight) {
promptForHeight();
} else if (isEnteringWeight) {
promptForWeight();
}
}

void toggleGender() {
genderInput = (genderInput == 'M') ? 'F' : 'M'; // Toggle between Male and Female
lcd.setCursor(0, 1);
lcd.print("Gender: ");
lcd.print(genderInput == 'M' ? "Male " : "Female");
}

void toggleBuzzer() {
buzzerEnabled = !buzzerEnabled;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(buzzerEnabled ? "Buzzer: ON" : "Buzzer: OFF");
delay(1000); // Display the status for 1 second
// Return to the current input stage after toggling
if (isEnteringAge) {
promptForAge();
} else if (isEnteringGender) {
promptForGender();
} else if (isEnteringHeight) {
promptForHeight();
} else if (isEnteringWeight) {
promptForWeight();
}
}

bool isNumeric(String str) {
bool decimalPoint = false;
for (byte i = 0; i < str.length(); i++) {
if (str.charAt(i) == '.') {
if (decimalPoint) return false; // If more than one decimal point, invalid
decimalPoint = true;
} else if (!isDigit(str.charAt(i))) {
return false;
}
}
return true;
}

void switchUnits() {
isMetric = !isMetric;
if (isEnteringHeight) {
promptForHeight(); // Update prompt without resetting inputs
} else if (isEnteringWeight) {
promptForWeight(); // Update prompt without resetting inputs
}
}

void handleDecimalInput() {
if (isEnteringHeight && heightInput.indexOf('.') == -1) {
heightInput += '.';
heightLimit = 5; // Adjust height limit for decimal
lcd.setCursor(0, 1);
lcd.print(heightInput);
} else if (isEnteringWeight && weightInput.indexOf('.') == -1) {
weightInput += '.';
lcd.setCursor(0, 1);
lcd.print(weightInput);
}
}

void calculateBMI() {
float height = heightInput.toFloat(); // Convert height to float
if (isMetric) {
height = height / 100.0; // Convert height from cm to meters
}
float weight = weightInput.toFloat(); // Convert weight to float
float bmi;

if (isMetric) {
bmi = weight / (height * height); // kg/m^2
} else {
bmi = (weight / (height * height)) * 703; // lbs/in^2
}

lcd.clear();
lcd.setCursor(0, 0);
lcd.print("BMI: ");
lcd.print(bmi, 2); // Display BMI with two decimal places
lcd.setCursor(0, 1);

String advice;
if (bmi < 18.5) {
advice = "U: Eat more!";
} else if (bmi < 24.9) {
advice = (genderInput == 'M') ? "H: Looking good!": "H: You rock!";
} else if (bmi < 29.9) {
advice = "O: Cut the junk!";
} else {
advice = "OB: Time to GYM!";
}
lcd.print(advice);
}

void displayError(String message) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(message);
playTone(400, 500); // Error tone
delay(2000); // Display error message for 2 seconds
// Return to the current input stage after error
clearCurrentInput();
}

void clearCurrentInput() {
if (isEnteringAge) {
ageInput = "";
promptForAge();
} else if (isEnteringHeight) {
heightInput = "";
promptForHeight();
} else if (isEnteringWeight) {
weightInput = "";
promptForWeight();
}
}

void playTone(int frequency, int duration) {
if (buzzerEnabled) {
tone(buzzerPin, frequency, duration);
}
}

Key Functions


Define how each key on the keypad will behave:

  1. # (Confirm): Moves to the next input stage.
  2. * (Clear/Reset):
  3. Short Press: Clears the current input.
  4. Long Press (More than 2 seconds): Resets the entire input process.
  5. C (Toggle): Switches between Male/Female or metric/imperial units.
  6. D (Decimal Point): Adds a decimal point to height/weight.
  7. A (Help): Displays key functions.

Conclusion

You’ve now built a fully functional BMI calculator using Arduino! This project has taught you how to use various input methods, display output on an LCD, and handle errors gracefully. Feel free to customize the project further—perhaps by adding more features or refining the design.

Enjoy your new Arduino-based BMI calculator, and happy tinkering!