Video
# Lab: Button State with Timestamp
## Objective
Learn to track button state changes with precise timestamps using the `millis()` function. This lab builds upon basic button reading to add time-based monitoring and state change detection.
## Learning Outcomes
- Understand state change detection principles
- Master the `millis()` function for timing
- Learn to track button press and release events with timestamps
- Implement efficient state change monitoring without blocking delays
## Required Components

1. **Lonely Binary UNO R3** - Main Arduino board
2. **TinkerBlock UNO R3 Shield** - Expansion shield that plugs onto the UNO R3
3. **TinkerBlock TK04 - Push Button** - Push button module with 4-pin connector
## Theory
### State Change Detection
- **State Change**: Occurs when a button transitions from one state to another (LOW to HIGH or HIGH to LOW)
- **Edge Detection**: Detecting the moment when a signal changes from one level to another
- **Rising Edge**: Transition from LOW to HIGH (button press)
- **Falling Edge**: Transition from HIGH to LOW (button release)
### The `millis()` Function
- **Purpose**: Returns the number of milliseconds since the Arduino board began running the current program
- **Range**: 0 to 4,294,967,295 (approximately 49.7 days)
- **Overflow**: Automatically resets to 0 after reaching maximum value
- **Non-blocking**: Unlike `delay()`, `millis()` doesn't pause program execution
- **Precision**: Provides millisecond-level timing accuracy
### State Tracking Variables
- **`lastButtonState`**: Stores the previous button state for comparison
- **Purpose**: Enables detection of state changes by comparing current vs. previous state
- **Initialization**: Should be set to the initial button state (usually LOW)
### Key Functions Used
- `digitalRead(pin)`: Reads the state of a digital pin (HIGH or LOW)
- `millis()`: Returns milliseconds since program start
- `Serial.begin(baud)`: Initializes serial communication
- `Serial.print()` and `Serial.println()`: Outputs data to Serial Monitor
## Circuit Diagram

## Connection Instructions
1. **Assemble the Hardware:**
- Place the TinkerBlock UNO R3 Shield onto the Lonely Binary UNO R3
- Ensure all pins are properly aligned and seated
- The shield should fit snugly on top of the Arduino board
2. **Connect the Push Button Module:**
- Take the TinkerBlock TK04 - Push Button module
- Align the 4-pin connector with any available 4-pin slot on the shield
- Gently push the module into the slot until it clicks into place
- The module will automatically connect:
- **GND** pin to ground
- **VCC** pin to 5V power
- **NC** pin remains unconnected
- **Signal** pin to digital pin D5
3. **Important Notes:**
- No breadboard or jumper wires required
- The TK04 push button has a built-in 10kΩ pull-down resistor
- The button's built-in LED will light when pressed
## Code
```cpp
// Lab 6: Button State with Timestamp
// Push button connected to digital pin D5
// Tracks button state changes with precise timestamps
// Define the button pin
const int buttonPin = 5; // Button input pin
// Variable to store the last button state
int lastButtonState = LOW;
void setup() {
// Configure button pin as input
pinMode(buttonPin, INPUT);
// Initialize serial communication for debugging
Serial.begin(9600);
Serial.println("Button State with Timestamp Lab Started");
Serial.println("Press and release the button to see timestamps");
Serial.println("----------------------------------------");
}
void loop() {
// Read the current button state
int buttonState = digitalRead(buttonPin);
// Check if button state has changed
if (buttonState != lastButtonState) {
// Get current time in milliseconds
unsigned long currentTime = millis();
// Determine the type of state change
if (buttonState == HIGH) {
// Button was just pressed (rising edge)
Serial.print("Button PRESSED at: ");
Serial.print(currentTime);
Serial.println(" ms");
} else {
// Button was just released (falling edge)
Serial.print("Button RELEASED at: ");
Serial.print(currentTime);
Serial.println(" ms");
}
// Update the last button state
lastButtonState = buttonState;
}
// Small delay to prevent too rapid reading and reduce noise
delay(10);
}
```
## Code Explanation
### Setup Function
```cpp
void setup() {
pinMode(buttonPin, INPUT);
Serial.begin(9600);
Serial.println("Button State with Timestamp Lab Started");
}
```
- Configures pin D5 as input for button reading
- Initializes serial communication for debugging
- Prints startup messages to Serial Monitor
### Global Variables
```cpp
int lastButtonState = LOW;
```
- **Purpose**: Stores the previous button state for comparison
- **Initial Value**: Set to LOW to match the initial button state (not pressed)
- **Usage**: Compared with current button state to detect changes
### Loop Function
```cpp
void loop() {
int buttonState = digitalRead(buttonPin);
if (buttonState != lastButtonState) {
// State change detected - process it
}
}
```
**State Change Detection:**
- `buttonState != lastButtonState`: Compares current and previous states
- **True**: Button state has changed (either pressed or released)
- **False**: Button state remains the same
**Timestamp Recording:**
- `unsigned long currentTime = millis()`: Captures precise timing
- Records the exact moment when state change occurs
- Provides millisecond-level accuracy
**State Change Processing:**
```cpp
if (buttonState == HIGH) {
// Button press detected
Serial.print("Button PRESSED at: ");
Serial.print(currentTime);
Serial.println(" ms");
} else {
// Button release detected
Serial.print("Button RELEASED at: ");
Serial.print(currentTime);
Serial.println(" ms");
}
```
**State Update:**
- `lastButtonState = buttonState`: Updates the stored previous state
- Ensures next comparison will be accurate
- Prevents repeated detection of the same state change
### Timing Considerations
- `delay(10)`: Small delay to prevent excessive reading
- Reduces noise and improves stability
- Allows other processes to run
- 10ms provides good responsiveness while maintaining efficiency
## Testing and Verification
1. **Upload the code to Lonely Binary UNO R3**
2. **Open Serial Monitor** in Arduino IDE (Tools → Serial Monitor)
3. **Observe the behavior:**
- Initial message: "Button State with Timestamp Lab Started"
- When button is pressed: Shows "Button PRESSED at: [time] ms"
- When button is released: Shows "Button RELEASED at: [time] ms"
- The button's built-in LED should light when pressed
4. **Verify timing accuracy:**
- Press and hold the button for different durations
- Note the time difference between press and release events
- Verify that timestamps increase monotonically
## Expected Output Example
```
Button State with Timestamp Lab Started
Press and release the button to see timestamps
----------------------------------------
Button PRESSED at: 1250 ms
Button RELEASED at: 1850 ms
Button PRESSED at: 3200 ms
Button RELEASED at: 3450 ms
Button PRESSED at: 5200 ms
Button RELEASED at: 5800 ms
```
## Troubleshooting
### No state change detection:
- Check if the TinkerBlock shield is properly seated on the Arduino
- Ensure the TK04 module is firmly connected to the shield
- Verify the module is connected to a slot that uses D5 pin
- Check if `lastButtonState` is properly initialized to LOW
- Open Serial Monitor to see if any output appears
### Repeated state change messages:
- Check for loose connections between shield and Arduino
- Verify the button module is securely connected
- Try reconnecting the TK04 module to a different 4-pin slot
- Check for electrical noise or interference
### Incorrect timestamps:
- Verify that `millis()` is being called correctly
- Check that Serial Monitor is set to 9600 baud rate
- Ensure no other timing functions are interfering
- Verify the Arduino has been running continuously
### Button's built-in LED doesn't light:
- Check if the TK04 module is properly powered (VCC and GND connections)
- Verify the module is firmly seated in the shield
- Try reconnecting the module to a different 4-pin slot
## Experiment Variations
### 1. Button Hold Duration
Calculate and display how long the button was held:
```cpp
int lastButtonState = LOW;
unsigned long pressTime = 0;
void loop() {
int buttonState = digitalRead(buttonPin);
if (buttonState != lastButtonState) {
if (buttonState == HIGH) {
// Button pressed - record start time
pressTime = millis();
Serial.print("Button PRESSED at: ");
Serial.println(pressTime);
} else {
// Button released - calculate hold duration
unsigned long holdDuration = millis() - pressTime;
Serial.print("Button RELEASED at: ");
Serial.print(millis());
Serial.print(" ms - Held for: ");
Serial.print(holdDuration);
Serial.println(" ms");
}
lastButtonState = buttonState;
}
delay(10);
}
```
### 2. Multiple Button Press Counter
Count button presses and track timing:
```cpp
int lastButtonState = LOW;
int pressCount = 0;
unsigned long lastPressTime = 0;
void loop() {
int buttonState = digitalRead(buttonPin);
if (buttonState != lastButtonState) {
if (buttonState == HIGH) {
pressCount++;
unsigned long currentTime = millis();
Serial.print("Press #");
Serial.print(pressCount);
Serial.print(" at: ");
Serial.print(currentTime);
Serial.println(" ms");
if (pressCount > 1) {
unsigned long timeSinceLastPress = currentTime - lastPressTime;
Serial.print("Time since last press: ");
Serial.print(timeSinceLastPress);
Serial.println(" ms");
}
lastPressTime = currentTime;
}
lastButtonState = buttonState;
}
delay(10);
}
```
### 3. Button Activity Monitor
Track button activity patterns:
```cpp
int lastButtonState = LOW;
unsigned long sessionStartTime = millis();
int totalPresses = 0;
int totalReleases = 0;
void loop() {
int buttonState = digitalRead(buttonPin);
if (buttonState != lastButtonState) {
unsigned long currentTime = millis();
unsigned long sessionTime = currentTime - sessionStartTime;
if (buttonState == HIGH) {
totalPresses++;
Serial.print("Press #");
Serial.print(totalPresses);
Serial.print(" at ");
Serial.print(currentTime);
Serial.print(" ms (Session: ");
Serial.print(sessionTime);
Serial.println(" ms)");
} else {
totalReleases++;
Serial.print("Release #");
Serial.print(totalReleases);
Serial.print(" at ");
Serial.print(currentTime);
Serial.println(" ms");
}
lastButtonState = buttonState;
}
delay(10);
}
```
## Questions for Understanding
1. What is the difference between `millis()` and `delay()`?
2. Why do we need to store the `lastButtonState`?
3. What is a "state change" in the context of button input?
4. How does the `millis()` function help with timing measurements?
5. What would happen if we didn't update `lastButtonState`?
6. Why do we use `delay(10)` instead of a longer delay?
7. How can you calculate the duration between button press and release?
8. What is the advantage of using timestamps over simple state reporting?
## Next Steps
- Experiment with different timing intervals and patterns
- Try combining timestamp tracking with other sensors
- Learn about interrupt-based button handling for more precise timing
- Explore creating a button activity logger with timestamps
- Try implementing button gesture recognition using timing patterns
- Learn about debouncing techniques for more reliable state detection
## Safety Notes
- Always disconnect power before connecting or disconnecting modules
- Handle the TinkerBlock shield and modules carefully to avoid bending pins
- Ensure proper alignment when connecting modules to prevent damage
- The built-in resistor in the TK04 module prevents component damage
- Keep the Lonely Binary UNO R3 and shield in a stable position during operation
- Monitor Serial output to ensure proper operation and detect any issues