Voltmeter Clock Arduino Code
Complete Arduino source code for the Voltmeter Clock project with detailed comments
Voltmeter Clock - Arduino Code Documentation
This page contains the complete Arduino source code for the Voltmeter Clock project. The code is fully commented and organized for easy understanding and modification.
Source Code
#include "Arduino.h" // Arduino stuff
#include "uRTCLib.h" // Real time clock stuff
#include <Adafruit_Sensor.h> // Pressure sensor stuff
#include <Adafruit_BMP085_U.h> // Pressure sensor stuff
#include <DHT.h> // Humidity sensor stuff
// Create an instance of the BMP180 sensor
Adafruit_BMP085_Unified bmp = Adafruit_BMP085_Unified(10180); // BMP180 sensor object for pressure and temperature
uRTCLib rtc(0x68); // RTC object for handling real-time clock operations
// Pin assignments
const int hrPin = 9; // PWM pin for hour display
const int minPin = 5; // PWM pin for minute display
const int secPin = 6; // PWM pin for second display
const int buttonPin = 4; // Button pin for user interaction
const int dhtPin = 7; // Pin connected to the DHT sensor
const int hrLedPin = 8; // LED pin for hour adjustment indicator
const int minLedPin = 10; // LED pin for minute adjustment indicator
const int secLedPin = 11; // LED pin for second adjustment indicator
const int pthLedsPin = 12; // LED pin for PTH (Pressure, Temperature, Humidity) display indicator
// DHT sensor setup
#define DHTPIN dhtPin
#define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);
// Limit switch (rotary encoder) pin definitions
const int pinA = 2; // Limit switch A (interrupt pin)
const int pinB = 3; // Limit switch B (interrupt pin)
// Rotary encoder variables
int counter = 0; // Counter for rotary encoder steps
volatile int A_prev = 0; // Previous state of signal A
volatile int B_prev = 0; // Previous state of signal B
// PWM output variables
int hrVal = 0; // Hour PWM value
int minVal = 0; // Minute PWM value
int secVal = 0; // Second PWM value
int displayMode = 0; // Current display mode (0 = time, 1 = PTH)
// Rotary encoder limits
const int minCounter = 0; // Minimum value for counter
volatile int maxCounter; // Maximum value for counter
// Quarter-second variables
float quarterSecVal = 0; // Quarter-second PWM adjustment value
int currentSec = 0; // Current second
int startMillis = 0; // Start time for quarter-second calculations
int dMillis = 0; // Delta time for quarter-second logic
// BMP180 sensor data
float pressure = 0; // Pressure reading in hPa
float temperature = 0; // Temperature reading in °C
// DHT sensor data
float humidity = 0.0; // Humidity reading in %
float dhtTemperature = 0; // Temperature reading from DHT sensor in °F
// Debugging flag
bool debug_println = false; // Enable or disable debug messages
// Timing variables for blinking LEDs
unsigned long previousMillisBlink = 0; // Last time LED on pin D11 blinked
const long blinkInterval = 500; // Interval for blinking (in ms)
bool ledState = LOW; // Current state of the LED
// Serial communication timing
unsigned long previousMillisSerial = 0; // Last time serial data was printed
// Button press handling
const unsigned long longPressThreshold = 500; // Threshold for long press (in ms)
unsigned long buttonPressStart = 0; // Time when button press started
bool buttonHeld = false; // Flag for button hold state
bool isAdjustingTime = false; // Flag for time adjustment mode
int currentAdjustmentStep = 3; // Current adjustment step (Hour -> Minute -> Second -> Brightness (not used))
// Constants defining the expected range of sensor values for the voltmeter
const int minPressureVal = 950; // Minimum pressure in hPa (covers extreme low-pressure weather)
const int maxPressureVal = 1030; // Maximum pressure in hPa (covers high-pressure weather conditions)
const int minTempVal = 40; // Minimum temperature in °F
const int maxTempVal = 100; // Maximum temperature in °F
const int minRHVal = 0; // Minimum relative humidity in %
const int maxRHVal = 100; // Maximum relative humidity in %
void setup() {
Serial.begin(9600);
URTCLIB_WIRE.begin(); // Ensure the RTC module is properly initialized
// Set output pins to output mode (PWM pins)
pinMode(hrPin, OUTPUT);
pinMode(minPin, OUTPUT);
pinMode(secPin, OUTPUT);
pinMode(buttonPin, INPUT_PULLUP);
// Enable internal pull-up resistors and set the pins for input
pinMode(pinA, INPUT_PULLUP);
pinMode(pinB, INPUT_PULLUP);
// Attach interrupts for pinA and pinB
attachInterrupt(digitalPinToInterrupt(pinA), updateEncoderA, CHANGE);
attachInterrupt(digitalPinToInterrupt(pinB), updateEncoderB, CHANGE);
// Initialize previous states
A_prev = digitalRead(pinA);
B_prev = digitalRead(pinB);
// Initialize BMP180 sensor
if (!bmp.begin()) {
Serial.println("Could not find a valid BMP180 sensor, check wiring!");
while (1);
}
dht.begin();
}
void loop() {
static bool lastButtonState = HIGH;
bool buttonState = digitalRead(buttonPin);
// Detect button press (short or long press)
if (buttonState == LOW && lastButtonState == HIGH) { // Button pressed
if (debug_println)
Serial.println("Button pressed");
buttonPressStart = millis(); // Start measuring the press time
buttonHeld = true;
} else if (buttonState == HIGH && lastButtonState == LOW) { // Button released
if (debug_println)
Serial.println("Button released");
if (buttonHeld) {
unsigned long pressDuration = millis() - buttonPressStart;
if (pressDuration >= longPressThreshold) {
if (debug_println)
Serial.println("Started adjusting time");
// Long press detected, enable time adjustment mode
isAdjustingTime = true;
currentAdjustmentStep = 0; // Start with hour adjustment
counter = int(rtc.hour() % 12); // Initialize counter to current hour
} else {
if (isAdjustingTime == false) {
// Short press detected, toggle between display modes
toggleDisplayMode(); // Toggle between time display and PTH display modes
}
}
}
buttonHeld = false; // Reset buttonHeld state
}
lastButtonState = buttonState;
// Adjust time if in adjustment mode
if (isAdjustingTime) {
adjustTime(); // Adjust time based on rotary encoder input and button presses
}
// Read sensor data and update displays if not adjusting time
if (!isAdjustingTime) {
readHumidity(); // Read humidity and temperature data from the DHT sensor
readPressure(); // Read pressure and temperature data from the BMP180 sensor
blinkDisplaySerial(); // Blink serial output for debugging or display purposes
// Update displays based on the current mode
if (displayMode == 0) {
updateTimeDisplay(); // Update the PWM displays for hours, minutes, and seconds
}
if (displayMode == 1) {
updatePTH(); // Update the PWM displays for pressure, temperature, and humidity
}
}
controlLEDsBasedOnDisplayMode(); // Control the LEDs to indicate the current mode
rtc.refresh(); // Refresh RTC data to ensure accurate time readings
// Set rotary encoder limits based on the current adjustment step
if (currentAdjustmentStep == 0) {
maxCounter = 11; // Limit counter to 11 for hours (12-hour format)
} else if (currentAdjustmentStep == 1) {
maxCounter = 59; // Limit counter to 59 for minutes
} else if (currentAdjustmentStep == 2) {
maxCounter = 59; // Limit counter to 59 for seconds
} else if (currentAdjustmentStep == 3) {
maxCounter = 20; // Example: Set maximum brightness adjustment to 20 (NOT USED FEATURE YET)
}
}
void updateTimeDisplay() {
if (debug_println)
Serial.println("Time is displaying");
hrVal = (rtc.hour() % 12 + rtc.minute() / 60.0) * 255.0 / 12;
minVal = (rtc.minute() + rtc.second() / 60.0) * 255.0 / 60;
// Handle quarter-second logic
if (rtc.second() == currentSec) {
handleQuarterSec();
} else {
currentSec = rtc.second();
startMillis = millis();
quarterSecVal = 0;
}
secVal = map(rtc.second(), 0, 60, 0, 255) + quarterSecVal;
analogWrite(hrPin, hrVal);
analogWrite(minPin, minVal);
analogWrite(secPin, secVal);
}
// Function to handle quarter-second timing
void handleQuarterSec() {
dMillis = millis() - startMillis;
if (dMillis < 250) {
quarterSecVal = 0;
} else if (dMillis < 500) {
quarterSecVal = 4.25 * 1 / 4;
} else if (dMillis < 750) {
quarterSecVal = 4.25 * 2 / 4;
} else if (dMillis < 1000) {
quarterSecVal = 4.25 * 3 / 4;
} else {
startMillis = millis();
quarterSecVal = 0;
}
}
void adjustTime() {
if (debug_println)
Serial.println("Time is being adjusted");
static bool lastButtonState = HIGH;
bool buttonState = digitalRead(buttonPin);
if (buttonState == LOW && lastButtonState == HIGH) { // Button pressed to confirm adjustment
// Toggle between adjustment steps (Hour -> Minute -> Second -> Done)
if (currentAdjustmentStep == 0) {
rtc.set(0, 0, counter, 0, 0, 0, 0); // Set hour
currentAdjustmentStep = 1; // Move to minute adjustment
Serial.print("Hour set to: ");
if (debug_println)
Serial.println(counter);
} else if (currentAdjustmentStep == 1) {
rtc.set(0, counter, rtc.hour(), 0, 0, 0, 0); // Set minute
currentAdjustmentStep = 2; // Move to second adjustment
Serial.print("Minute set to: ");
if (debug_println)
Serial.println(counter);
} else if (currentAdjustmentStep == 2) {
rtc.set(counter, rtc.minute(), rtc.hour(), 0, 0, 0, 0); // Set second
isAdjustingTime = false; // Finish time adjustment
currentAdjustmentStep = 3; // Reset to bulb brightness adjustment
Serial.print("Second set to: ");
if (debug_println)
Serial.println(counter);
displayMode = 1; // so it can toggle back to 0 after time setting
}
}
lastButtonState = buttonState;
// Show the current adjustment step on the display
displayAdjustment();
}
void displayAdjustment() {
if (debug_println)
Serial.println("Displaying adjusted time in adjustment mode");
// Display the current value of the counter (adjustment in progress)
Serial.print("Adjusting: ");
if (currentAdjustmentStep == 0) {
Serial.print("Hour: ");
analogWrite(hrPin, map(counter, 0, 12, 0, 255));
analogWrite(minPin, 0);
analogWrite(secPin, 0);
} else if (currentAdjustmentStep == 1) {
Serial.print("Minute: ");
analogWrite(hrPin, 0);
analogWrite(minPin, map(counter, 0, 60, 0, 255));
analogWrite(secPin, 0);
} else {
Serial.print("Second: ");
analogWrite(hrPin, 0);
analogWrite(minPin, 0);
analogWrite(secPin, map(counter, 0, 60, 0, 255));
}
Serial.println(counter);
}
void updateEncoderA() {
int A = digitalRead(pinA);
int B = digitalRead(pinB);
if (A != A_prev) { // A has changed
if (A == LOW) { // Falling edge of A
if (B == HIGH) {
counter++; // Clockwise
} else {
counter--; // Counter-clockwise
}
} else { // Rising edge of A
if (B == LOW) {
if (counter != minCounter) counter++; // Clockwise
} else {
if (counter != maxCounter) counter--; // Counter-clockwise
}
}
A_prev = A;
}
counter = constrain(counter, minCounter, maxCounter);
if (debug_println)
Serial.println(counter);
}
void updateEncoderB() {
int A = digitalRead(pinA);
int B = digitalRead(pinB);
if (B != B_prev) { // B has changed
if (B == LOW) { // Falling edge of B
if (A == LOW) {
counter++; // Clockwise
} else {
counter--; // Counter-clockwise
}
} else { // Rising edge of B
if (A == HIGH) {
if (counter != minCounter) counter++; // Clockwise with bump correction
} else {
if (counter != maxCounter) counter--; // Counter-clockwise
}
}
B_prev = B;
}
counter = constrain(counter, minCounter, maxCounter);
if (debug_println)
Serial.println(counter);
}
// Function to read pressure and temperature
void readPressure() {
if (debug_println)
Serial.println("Pressure data got");
sensors_event_t event;
bmp.getEvent(&event);
if (event.pressure) {
pressure = event.pressure; // Save pressure in hPa
bmp.getTemperature(&temperature); // Save temperature in °C
} else {
pressure = -1; // Indicate failure to read pressure
temperature = -1; // Indicate failure to read temperature
}
}
void readHumidity() {
if (debug_println)
Serial.println("Humidity data got");
humidity = dht.readHumidity();
dhtTemperature = dht.readTemperature(true);//true=farenheight
if (isnan(humidity) || isnan(dhtTemperature)) {
Serial.println("Failed to read from DHT sensor!");
humidity = -1; // Error handling
dhtTemperature = -1; // Error handling
}
}
void toggleDisplayMode() {
if (debug_println)
Serial.println("Toggling display mode");
displayMode = (displayMode == 0) ? 1 : 0;
if (debug_println)
Serial.print("Display mode toggled to: ");
if (debug_println)
Serial.println(displayMode);
}
// Function to update the Pressure, Temperature, Humidity (PTH) display
void updatePTH() {
if (debug_println)
Serial.println("PTH displaying");
// Map pressure, temperature, and humidity readings to a PWM range (0-255)
hrVal = map(pressure, minPressureVal, maxPressureVal, 0, 255); // Map pressure in hPa to LED brightness
minVal = map(dhtTemperature, minTempVal, maxTempVal, 0, 255); // Map temperature in °F to LED brightness
secVal = map(humidity, minRHVal, maxRHVal, 0, 255); // Map humidity in % to LED brightness
// Constrain mapped values to the valid PWM range (0-255)
hrVal = constrain(hrVal, 0, 255); // Ensure pressure value is within the valid range
minVal = constrain(minVal, 0, 255); // Ensure temperature value is within the valid range
secVal = constrain(secVal, 0, 255); // Ensure humidity value is within the valid range
// Output PWM signals to the respective pins
analogWrite(hrPin, hrVal); // Display pressure on the hour voltmeter
analogWrite(minPin, minVal); // Display temperature on the minute voltmeter
analogWrite(secPin, secVal); // Display humidity on the second voltmeter
}
void controlLEDsBasedOnDisplayMode() {
if (!isAdjustingTime){
if (displayMode == 0) {
digitalWrite(hrLedPin, HIGH);
digitalWrite(minLedPin, HIGH);
digitalWrite(secLedPin, HIGH);
digitalWrite(pthLedsPin, LOW);
} else if (displayMode == 1) {
digitalWrite(hrLedPin, LOW);
digitalWrite(minLedPin, LOW);
digitalWrite(secLedPin, LOW);
digitalWrite(pthLedsPin, HIGH);
}
}
else if (isAdjustingTime){
if (currentAdjustmentStep == 0){
blinkPin(hrLedPin);
digitalWrite(minLedPin, LOW);
digitalWrite(secLedPin, LOW);
digitalWrite(pthLedsPin, LOW);
} else if(currentAdjustmentStep == 1){
digitalWrite(hrLedPin, LOW);
blinkPin(minLedPin);
digitalWrite(secLedPin, LOW);
digitalWrite(pthLedsPin, LOW);
} else if(currentAdjustmentStep == 2){
digitalWrite(hrLedPin, LOW);
digitalWrite(minLedPin, LOW);
blinkPin(secLedPin);
digitalWrite(pthLedsPin, LOW);
}
}
}
void blinkPin(int pin) {
unsigned long currentMillis = millis();
// Check if blinkInterval ms have passed
if (currentMillis - previousMillisBlink >= blinkInterval) {
previousMillisBlink = currentMillis; // Save the current time
if (debug_println)
Serial.println("Blink!");
// Toggle the LED state
ledState = !ledState; // Toggle the LED state
digitalWrite(pin, ledState); // Set the LED to the new state
}
}
void blinkDisplaySerial(){
unsigned long currentMillis = millis();
// Check if blinkInterval ms have passed
if (currentMillis - previousMillisSerial>= blinkInterval) {
previousMillisSerial = currentMillis; // Save the current time
if (displayMode==0){
Serial.print(rtc.hour());
Serial.print(":");
Serial.print(rtc.minute());
Serial.print(":");
Serial.println(rtc.second());
}
if (displayMode==1){
Serial.print(pressure);
Serial.print(" millibar, ");
Serial.print(dhtTemperature);
Serial.print(" degF, ");
Serial.print(humidity);
Serial.println(" %RH");
}
}
}
// Function to display debug information
void displayDebugInfo() {
Serial.print("displaymode = ");
Serial.print(displayMode);
Serial.print("; AHr = ");
Serial.print(hrVal);
Serial.print("; AMin = ");
Serial.print(minVal);
Serial.print("; ASec = ");
Serial.print(secVal);
Serial.print("; A(.25)Sec = ");
Serial.print(quarterSecVal);
Serial.print("; dmillis = ");
Serial.print(dMillis);
Serial.print("; RTC.second = ");
Serial.print(rtc.second());
Serial.print("; Pressure = ");
Serial.print(pressure);
Serial.print(" hPa; DHT Humidity = ");
Serial.print(humidity);
Serial.print(" %; DHT Temperature = ");
Serial.print(dhtTemperature);
Serial.print(" °F; counter= ");
Serial.println(dhtTemperature);
}
Code Structure Overview
Main Components
- Libraries: Includes necessary libraries for RTC, sensors, and Arduino core functionality
- Pin Definitions: All pin assignments for voltmeters, LEDs, sensors, and inputs
- Global Variables: State variables for display modes, sensor data, and timing
- Setup Function: Initializes all hardware components and interrupts
- Loop Function: Main program loop handling user input and display updates
Key Functions
-
updateTimeDisplay(): Calculates and displays time on voltmeters -
updatePTH(): Displays pressure, temperature, and humidity -
adjustTime(): Handles time adjustment mode -
updateEncoderA()/updateEncoderB(): Interrupt handlers for rotary encoder -
readPressure()/readHumidity(): Sensor reading functions -
handleQuarterSec(): Implements smooth second hand movement
Download
You can download the source code files from the project repository:
- Arduino Clock Code v1 - Original Arduino source code (v1)
- Arduino Clock Code v2 - Enhanced version with play mode (v2)
Additional Resources
- Complete Documentation - Full project documentation
- Operation Guide - How to use the clock
- Technical Details - In-depth technical information