06 - Language Millis

# Arduino Language Learning: millis() Function ## **What is millis()?** The **millis()** function is a built-in Arduino function that returns the number of milliseconds since the Arduino board began running the current program. It's like a stopwatch that starts when you upload your code and keeps running until you reset the board. ### **Basic Concept** Think of `millis()` as a digital clock that: - **Starts at 0** when your program begins - **Counts up** continuously in milliseconds - **Never stops** unless you reset the Arduino - **Overflows** after about 49.7 days (4,294,967,295 milliseconds) ### **Why Use millis() Instead of delay()?** - **delay()**: Pauses the entire program (blocking) - **millis()**: Non-blocking timing (program continues running) - **Better Performance**: Allows multiple tasks to run simultaneously - **Responsive**: Keeps your Arduino responsive to inputs ## **Basic millis() Usage** ### **Syntax** ```cpp unsigned long currentTime = millis(); ``` ### **Return Type** - **Type**: `unsigned long` - **Range**: 0 to 4,294,967,295 (about 49.7 days) - **Unit**: Milliseconds (1/1000th of a second) ### **Simple Example** ```cpp void setup() { Serial.begin(9600); Serial.println("Program started!"); } void loop() { unsigned long currentTime = millis(); Serial.print("Time since start: "); Serial.print(currentTime); Serial.println(" milliseconds"); delay(1000); // Wait 1 second } ``` **Serial Monitor Output:** ``` Program started! Time since start: 0 milliseconds Time since start: 1000 milliseconds Time since start: 2000 milliseconds Time since start: 3000 milliseconds ``` ## **Converting millis() to Different Units** ### **Seconds** ```cpp unsigned long seconds = millis() / 1000; ``` ### **Minutes** ```cpp unsigned long minutes = millis() / 60000; // 60,000 ms = 1 minute ``` ### **Hours** ```cpp unsigned long hours = millis() / 3600000; // 3,600,000 ms = 1 hour ``` ### **Complete Time Display** ```cpp void setup() { Serial.begin(9600); } void loop() { unsigned long currentMillis = millis(); unsigned long seconds = currentMillis / 1000; unsigned long minutes = seconds / 60; unsigned long hours = minutes / 60; // Remove completed units seconds = seconds % 60; minutes = minutes % 60; Serial.print("Uptime: "); Serial.print(hours); Serial.print("h "); Serial.print(minutes); Serial.print("m "); Serial.print(seconds); Serial.println("s"); delay(1000); } ``` **Serial Monitor Output:** ``` Uptime: 0h 0m 1s Uptime: 0h 0m 2s Uptime: 0h 0m 3s ... Uptime: 0h 1m 0s Uptime: 0h 1m 1s ``` ## **Non-blocking Timing with millis()** ### **The Problem with delay()** ```cpp void loop() { // Task 1 digitalWrite(13, HIGH); delay(1000); // Program stops here for 1 second // Task 2 digitalWrite(13, LOW); delay(1000); // Program stops here for 1 second // Task 3 - This won't run until 2 seconds later! Serial.println("Hello"); } ``` ### **The Solution with millis()** ```cpp unsigned long previousMillis = 0; const long interval = 1000; // 1 second void loop() { unsigned long currentMillis = millis(); if (currentMillis - previousMillis >= interval) { previousMillis = currentMillis; // Task 1: Toggle LED static boolean ledState = false; ledState = !ledState; digitalWrite(13, ledState); // Task 2: Print message Serial.println("Hello"); } // Other tasks can run here without delay! } ``` ## **Timing Patterns with millis()** ### **Pattern 1: Simple Interval** ```cpp unsigned long previousMillis = 0; const long interval = 2000; // 2 seconds void loop() { unsigned long currentMillis = millis(); if (currentMillis - previousMillis >= interval) { previousMillis = currentMillis; // Code to run every 2 seconds Serial.println("2 seconds have passed!"); } } ``` ### **Pattern 2: Multiple Intervals** ```cpp unsigned long previousMillis1 = 0; unsigned long previousMillis2 = 0; const long interval1 = 1000; // 1 second const long interval2 = 3000; // 3 seconds void loop() { unsigned long currentMillis = millis(); // Task 1: Every 1 second if (currentMillis - previousMillis1 >= interval1) { previousMillis1 = currentMillis; Serial.println("Task 1: Every second"); } // Task 2: Every 3 seconds if (currentMillis - previousMillis2 >= interval2) { previousMillis2 = currentMillis; Serial.println("Task 2: Every 3 seconds"); } } ``` ### **Pattern 3: One-shot Timer** ```cpp unsigned long startTime = 0; const long duration = 5000; // 5 seconds boolean timerActive = false; void loop() { unsigned long currentMillis = millis(); // Start timer if (!timerActive) { startTime = currentMillis; timerActive = true; Serial.println("Timer started!"); } // Check if timer finished if (timerActive && (currentMillis - startTime >= duration)) { timerActive = false; Serial.println("Timer finished! 5 seconds have passed."); } } ``` ### **Pattern 4: Countdown Timer** ```cpp unsigned long startTime = 0; const long countdownDuration = 10000; // 10 seconds boolean countdownActive = false; void loop() { unsigned long currentMillis = millis(); // Start countdown if (!countdownActive) { startTime = currentMillis; countdownActive = true; Serial.println("Countdown started!"); } // Show remaining time if (countdownActive) { unsigned long elapsed = currentMillis - startTime; unsigned long remaining = countdownDuration - elapsed; if (remaining > 0) { Serial.print("Time remaining: "); Serial.print(remaining / 1000); Serial.println(" seconds"); } else { countdownActive = false; Serial.println("Countdown finished!"); } } delay(1000); // Update every second } ``` ## **Timing Statistics with millis()** ### **Average Time Calculation** ```cpp unsigned long startTime = 0; unsigned long totalTime = 0; int loopCount = 0; void loop() { unsigned long loopStart = millis(); // Simulate some work delay(random(100, 500)); // Random delay 100-500ms // Calculate loop time unsigned long loopTime = millis() - loopStart; totalTime += loopTime; loopCount++; // Calculate and display average unsigned long averageTime = totalTime / loopCount; Serial.print("Loop #"); Serial.print(loopCount); Serial.print(" took "); Serial.print(loopTime); Serial.print("ms, Average: "); Serial.print(averageTime); Serial.println("ms"); } ``` ### **Performance Monitoring** ```cpp unsigned long lastReportTime = 0; const long reportInterval = 5000; // Report every 5 seconds int operationsCount = 0; void loop() { // Simulate operations for (int i = 0; i < 100; i++) { // Some work here operationsCount++; } // Report performance every 5 seconds unsigned long currentMillis = millis(); if (currentMillis - lastReportTime >= reportInterval) { float operationsPerSecond = (float)operationsCount / 5.0; Serial.print("Performance: "); Serial.print(operationsPerSecond); Serial.println(" operations/second"); operationsCount = 0; lastReportTime = currentMillis; } } ``` ## **Best Practices** ### **1. Use Constants for Intervals** ```cpp // Good const long BLINK_INTERVAL = 1000; const long REPORT_INTERVAL = 5000; // Avoid unsigned long previousMillis = 0; // ... later in code ... if (currentMillis - previousMillis >= 1000) { ``` ### **2. Handle Overflow Safely** ```cpp // Good - Safe overflow handling if ((unsigned long)(currentMillis - previousMillis) >= interval) { previousMillis = currentMillis; } // Avoid - Can fail after overflow if (currentMillis - previousMillis >= interval) { previousMillis = currentMillis; } ``` ### **3. Use Descriptive Variable Names** ```cpp // Good unsigned long lastBlinkTime = 0; unsigned long lastReportTime = 0; // Avoid unsigned long time1 = 0; unsigned long time2 = 0; ``` ### **4. Group Related Timing Variables** ```cpp // Good - Grouped timing variables unsigned long lastBlinkTime = 0; unsigned long lastReportTime = 0; const long BLINK_INTERVAL = 500; const long REPORT_INTERVAL = 2000; // Avoid - Scattered variables unsigned long time1 = 0; const long interval1 = 500; unsigned long time2 = 0; const long interval2 = 2000; ``` ### **5. Use millis() for Non-blocking Delays** ```cpp // Good - Non-blocking unsigned long previousMillis = 0; const long interval = 1000; void loop() { unsigned long currentMillis = millis(); if (currentMillis - previousMillis >= interval) { previousMillis = currentMillis; // Do something } // Other code can run here } // Avoid - Blocking void loop() { // Do something delay(1000); // Blocks everything // Other code waits } ``` ## **Common Use Cases** ### **1. Non-blocking Delays** - Replace `delay()` with `millis()` for responsive programs - Allow multiple tasks to run simultaneously - Keep Arduino responsive to inputs ### **2. Timing Events** - Schedule tasks at specific intervals - Create time-based sequences - Implement countdown timers ### **3. Performance Monitoring** - Measure execution time - Calculate averages and statistics - Monitor system performance ### **4. State Machines** - Time-based state transitions - Create complex timing patterns - Implement animations and sequences ### **5. Data Logging** - Timestamp events and data - Create time-based data records - Implement periodic reporting ## **Summary** ### **Key Points:** 1. **millis()** returns milliseconds since program start 2. **Non-blocking**: Unlike `delay()`, doesn't pause the program 3. **Overflow**: Occurs after ~49.7 days (4,294,967,295 ms) 4. **Safe Handling**: Use `(unsigned long)` casting for overflow safety 5. **Multiple Tasks**: Enable simultaneous timing of different events ### **When to Use millis():** - **Non-blocking Delays**: Replace `delay()` for responsive programs - **Multiple Timers**: Run several timed events simultaneously - **State Machines**: Time-based state transitions - **Performance Monitoring**: Measure execution times - **Data Logging**: Add timestamps to events ### **Next Steps:** - **Practice**: Try the timing patterns with your Arduino - **Combine**: Use millis() with other Arduino functions - **Experiment**: Create your own timing-based projects - **Explore**: Learn about micros() for microsecond timing **millis() is essential for creating responsive, multi-tasking Arduino programs. Master it, and you'll be able to create sophisticated timing-based projects!**