38. System for Measurement Unit Configuration

The task is to create a unit preference management system using a microcontroller, where the device allows users to select their preferred measurement units for various parameters such as Temperature, Distance, Weight, and Volume through a serial terminal (e.g., PuTTY or Arduino IDE).

Requirements

  • The system should display the currently saved preferences on startup (e.g., Temperature – Celsius, Distance - Kilometers).
  • It should prompt the user to set or change unit preferences via the serial terminal.
  • The selected preferences must be saved in EEPROM, ensuring they are retained even after power loss or reset.
  • The user should also have the option to restore factory default settings when desired.

Why Use EEPROM?

EEPROM (Electrically Erasable Programmable Read-Only Memory) is a form of non-volatile memory that retains stored data even when power is turned off.
It is ideal for this application as it allows the system to store and recall user preferences reliably across restarts, ensuring a consistent user experience.

This task can be implemented on the following microcontrollers

  • ESP32
  • Arduino UNO

We are using the ESP32 DevKitC v4 development board and programming it using the Arduino IDE.

  • Before uploading, make sure to select “ESP32 Dev Module” as the board to ensure correct settings and compatibility.

ESP32 does not have a real EEPROM chip inside. Instead, part of its Flash memory is used to simulate EEPROM behavior.

Hardware Connection

Connect the ESP32  development board to your computer using a USB cable.

Code

#include <EEPROM.h>

//Constants for Menu Options
const char* tempUnits[] = { "Celsius", "Fahrenheit" };
const char* distUnits[] = { "Kilometers", "Miles" };
const char* weightUnits[] = { "Kilograms", "Pounds" };
const char* volumeUnits[] = { "Liters", "Gallons" };

//Struct to hold user preferences
struct Preferences {
  uint8_t tempUnit;
  uint8_t distUnit;
  uint8_t weightUnit;
  uint8_t volumeUnit;
};

Preferences userPreferences;

//EEPROM Configuration
const int eepromAddress = 0;
#define EEPROM_SIZE sizeof(Preferences)

void setup() {
  delay(2000);
  Serial.begin(115200);

  EEPROM.begin(EEPROM_SIZE);
  EEPROM.get(eepromAddress, userPreferences);

  // Validate stored preferences
  if (userPreferences.tempUnit > 1 || userPreferences.distUnit > 1 || userPreferences.weightUnit > 1 || userPreferences.volumeUnit > 1) {
    resetPreferences();
  }

  displayCurrentPreferences();
  displayMenu();
}

void loop() {
  if (Serial.available()) {
    handleMenuInput();
  }
}

//Display Current Preferences
void displayCurrentPreferences() {
  Serial.println("\nCurrent Preferences:");
  Serial.print("1. Temperature: ");
  Serial.println(tempUnits[userPreferences.tempUnit]);
  Serial.print("2. Distance: ");
  Serial.println(distUnits[userPreferences.distUnit]);
  Serial.print("3. Weight: ");
  Serial.println(weightUnits[userPreferences.weightUnit]);
  Serial.print("4. Volume: ");
  Serial.println(volumeUnits[userPreferences.volumeUnit]);
  Serial.println();
}

//Display Menu
void displayMenu() {
  Serial.println("\nMenu Options:");
  Serial.println("1: Set Temperature Unit");
  Serial.println("2: Set Distance Unit");
  Serial.println("3: Set Weight Unit");
  Serial.println("4: Set Volume Unit");
  Serial.println("5: Reset to Default Preferences");
  Serial.println("Enter your choice (1–5): ");
}

//Handle User Input
void handleMenuInput() {
  static int option = -1;

  String input = Serial.readStringUntil('\n');
  input.trim();

  // Step 1: Get the menu option
  if (option == -1) {
    option = input.toInt();
    if (option < 1 || option > 5) {
      Serial.println("Invalid option! Please enter 1–5.");
      option = -1;
      displayMenu();
      return;
    }

    if (option == 5) {
      resetPreferences();
      displayCurrentPreferences();
      option = -1;
      displayMenu();
      return;
    }

    // Ask for the preference value based on selected option
    switch (option) {
      case 1:
        Serial.println("Enter Temperature Unit (0: Celsius, 1: Fahrenheit): ");
        break;
      case 2:
        Serial.println("Enter Distance Unit (0: Kilometers, 1: Miles): ");
        break;
      case 3:
        Serial.println("Enter Weight Unit (0: Kilograms, 1: Pounds): ");
        break;
      case 4:
        Serial.println("Enter Volume Unit (0: Liters, 1: Gallons): ");
        break;
    }
    return;
  }

  // Step 2: Get and validate preference value
  int value = input.toInt();
  if (value < 0 || value > 1) {
    Serial.println("Invalid preference! Must be 0 or 1.");
    option = -1;
    displayMenu();
    return;
  }

  // Step 3: Apply based on selected option
  switch (option) {
    case 1:
      userPreferences.tempUnit = value;
      break;
    case 2:
      userPreferences.distUnit = value;
      break;
    case 3:
      userPreferences.weightUnit = value;
      break;
    case 4:
      userPreferences.volumeUnit = value;
      break;
  }

  savePreferences();
  Serial.println("Preference updated successfully!");
  displayCurrentPreferences();

  // Reset for next input
  option = -1;
  displayMenu();
}

//Save Preferences to EEPROM
void savePreferences() {
  EEPROM.put(eepromAddress, userPreferences);
  if (EEPROM.commit()) {
    Serial.println("Preferences Saved!");
  } else {
    Serial.println("Error saving preferences!");
  }
}

//Reset to Default
void resetPreferences() {
  userPreferences = { 0, 0, 0, 0 };  // Default values
  savePreferences();
  Serial.println("Preferences Reset to Default!");
}

Code Explanation

  • struct Preferences
    • Contains four 1-byte fields —> tempUnitdistUnit, weightUnitvolumeUnit — each representing a binary choice (0 or 1).
  • setup()
    • Starts Serial at 115200 baud.
    • Initializes EEPROM with EEPROM.begin(EEPROM_SIZE) and loads saved data using EEPROM.get().
    • Validates that every field ≤ 1; otherwise calls resetPreferences() to restore defaults.
    • Displays current settings and the selection menu.
  • loop()
    • Continuously checks for Serial input and calls handleMenuInput() whenever data is available.
  • displayCurrentPreferences()
    • Prints the four current selections by indexing the corresponding string arrays (e.g., tempUnits[userPreferences.tempUnit]).
  • displayMenu()
    • Lists the five menu options and prompts for an option number (1–5).
  • handleMenuInput()
    • Uses a two-step input process.
      • Step 1: Reads the menu option (1–5).
      • Step 2: If option 1–4, asks for the new unit value (0 or 1); if option 5, resets to defaults.
    • Validates both entries, updates the selected field, and calls savePreferences().
    • For ESP32, each write to EEPROM must end with EEPROM.commit() to make data permanent.
  • savePreferences()
    • Stores the Preferences struct to EEPROM with EEPROM.put() and commits the change.
  • resetPreferences()
    • Restores defaults {0,0,0,0} → Celsius, Kilometers, Kilograms, Liters; saves to EEPROM and prints confirmation.

Precautions when Allocating Flash to EEPROM

  1. Don’t exceed Flash partition size
    • The emulated EEPROM uses the “app data” area of Flash and is safe up to about 4096 bytes. Allocating more (e.g., EEPROM.begin(8000)) can corrupt other Flash data or cause runtime errors.
  2. Write infrequently
    • Each Flash cell supports a limited number of writes (~100k).
      • Avoid continuous writing in loop().
      • Use EEPROM.commit() only when values actually change.
  3. Call EEPROM.commit() after writing
    • Without this, your changes stay only in RAM and disappear after a restart.
  4. Avoid overlapping with other Flash uses

We are using the Arduino UNO development board and programming it using the Arduino IDE.

  • Before uploading, make sure to select “Arduino UNO” as the board to ensure correct settings and compatibility.

Hardware Connection

Connect the Arduino UNO  development board to your computer using a USB cable.

Firmware Implementation

We are going to use EEPROM.h library to implement this task. In Arduino UNO, there is a separate 1KB EEPROM chip available.

Code

#include <EEPROM.h>

// Constants for Menu Options
const char* tempUnits[] = { "Celsius", "Fahrenheit" };
const char* distUnits[] = { "Kilometers", "Miles" };
const char* weightUnits[] = { "Kilograms", "Pounds" };
const char* volumeUnits[] = { "Liters", "Gallons" };

//Struct to hold user preferences
struct Preferences {
  uint8_t tempUnit;    // 0: Celsius, 1: Fahrenheit
  uint8_t distUnit;    // 0: Kilometers, 1: Miles
  uint8_t weightUnit;  // 0: Kilograms, 1: Pounds
  uint8_t volumeUnit;  // 0: Liters, 1: Gallons
};

Preferences userPreferences;

// EEPROM address for preferences
const int eepromAddress = 0;

//Global Variables
int currentOption = -1;  // Tracks selected option

void setup() {
  Serial.begin(9600);
  delay(1000);

  // Load preferences from EEPROM
  EEPROM.get(eepromAddress, userPreferences);

  // Validate loaded preferences
  if (userPreferences.tempUnit > 1 || userPreferences.distUnit > 1 || userPreferences.weightUnit > 1 || userPreferences.volumeUnit > 1) {
    resetPreferences();
  }

  displayCurrentPreferences();
  displayMenu();
}

void loop() {
  if (Serial.available()) {
    handleMenuInput();
  }
}

//Display Current Preferences
void displayCurrentPreferences() {
  Serial.println("\nCurrent Preferences:");
  Serial.print("1. Temperature: ");
  Serial.println(tempUnits[userPreferences.tempUnit]);
  Serial.print("2. Distance: ");
  Serial.println(distUnits[userPreferences.distUnit]);
  Serial.print("3. Weight: ");
  Serial.println(weightUnits[userPreferences.weightUnit]);
  Serial.print("4. Volume: ");
  Serial.println(volumeUnits[userPreferences.volumeUnit]);
  Serial.println();
}

//Display Menu
void displayMenu() {
  Serial.println("\nMenu Options:");
  Serial.println("1: Set Temperature Unit ");
  Serial.println("2: Set Distance Unit ");
  Serial.println("3: Set Weight Unit ");
  Serial.println("4: Set Volume Unit ");
  Serial.println("5: Reset to Default Preferences");
  Serial.println("Enter your choice (1–5): ");
}

//Handle Menu Input
void handleMenuInput() {
  String input = Serial.readStringUntil('\n');
  input.trim();

  // Step 1: If no option selected, expect menu choice
  if (currentOption == -1) {
    int option = input.toInt();

    if (option < 1 || option > 5) {
      Serial.println("Invalid option! Please enter a number between 1–5.");
      displayMenu();
      return;
    }

    if (option == 5) {
      resetPreferences();
      displayCurrentPreferences();
      displayMenu();
      return;
    }

    currentOption = option;

    // Ask for preference value based on option
    switch (option) {
      case 1:
        Serial.println("Enter Temperature Unit (0: Celsius, 1: Fahrenheit): ");
        break;
      case 2:
        Serial.println("Enter Distance Unit (0: Kilometers, 1: Miles): ");
        break;
      case 3:
        Serial.println("Enter Weight Unit (0: Kilograms, 1: Pounds): ");
        break;
      case 4:
        Serial.println("Enter Volume Unit (0: Liters, 1: Gallons): ");
        break;
    }
    return;
  }

  // Step 2: If option already selected, expect preference value
  int value = input.toInt();
  if (value < 0 || value > 1) {
    Serial.println("Invalid value! Must be 0 or 1.");
    currentOption = -1;
    displayMenu();
    return;
  }

  // Update based on option
  switch (currentOption) {
    case 1:
      userPreferences.tempUnit = value;
      Serial.print("Temperature Unit set to: ");
      Serial.println(tempUnits[value]);
      break;
    case 2:
      userPreferences.distUnit = value;
      Serial.print("Distance Unit set to: ");
      Serial.println(distUnits[value]);
      break;
    case 3:
      userPreferences.weightUnit = value;
      Serial.print("Weight Unit set to: ");
      Serial.println(weightUnits[value]);
      break;
    case 4:
      userPreferences.volumeUnit = value;
      Serial.print("Volume Unit set to: ");
      Serial.println(volumeUnits[value]);
      break;
  }

  savePreferences();

  // Reset and show updates
  currentOption = -1;
  displayCurrentPreferences();
  displayMenu();
}

//Save Preferences to EEPROM
void savePreferences() {
  EEPROM.put(eepromAddress, userPreferences);
  Serial.println("Preferences Saved!");
}

//Reset to Default
void resetPreferences() {
  userPreferences = { 0, 0, 0, 0 };  // Default: Celsius, Kilometers, Kilograms, Liters
  savePreferences();
  Serial.println("Preferences Reset to Default!");
}

Code Explanation

  • struct Preferences
    • Stores four 1-byte user settings:
      • tempUnit: 0 = Celsius, 1 = Fahrenheit
      • distUnit: 0 = Kilometers, 1 = Miles
      • weightUnit: 0 = Kilograms, 1 = Pounds
      • volumeUnit: 0 = Liters, 1 = Gallons
    • Each field represents a user’s preferred measurement unit.
  • setup()
    • Starts Serial at 9600 baud for user input.
    • Loads saved preferences from EEPROM using EEPROM.get().
    • Checks if all values are valid (0 or 1). If not, resets to defaults.
    • Displays current preferences and shows the menu.
  • loop()
    • Continuously checks for Serial input.
    • If data is received, calls handleMenuInput() to process the user’s command.
  • displayCurrentPreferences()
    • Shows the current saved preferences (Temperature, Distance, Weight, Volume) with readable unit names from the label arrays.
  • displayMenu()
    • Prints the main menu with five options:
      • Set Temperature Unit
      • Set Distance Unit
      • Set Weight Unit
      • Set Volume Unit
      • Reset to Default Preferences
    • Then asks the user to “Enter your choice (1–5).”
  • handleMenuInput()
    • Handles user input in two steps:
    • Step 1 – Option: Waits for the user to choose an option (1–5).
      • If 5 → resets all preferences.
      • If 1–4 → asks for a new value (0 or 1).
    • Step 2 – Value: Checks if entered value is valid (0 or 1).
    • Updates the selected field and saves it to EEPROM.
    • Shows updated preferences and menu again.
  • savePreferences()
    • Writes all preferences to EEPROM using EEPROM.put().
       Confirms with “Preferences Saved!”
  • resetPreferences()
    • Restores defaults {0, 0, 0, 0} → Celsius, Kilometers, Kilograms, Liters.
    • Saves them to EEPROM and shows “Preferences Reset to Default!”

Output Video