civil-and-structural-engineering
Creating a Digital Stopwatch with User-friendly Interface on Pic Microcontrollers
Table of Contents
Introduction to PIC-Based Digital Stopwatch Design
Developing a digital stopwatch using PIC microcontrollers combines fundamental embedded systems concepts with practical hardware-software integration. This project is ideal for students and hobbyists who want hands-on experience with timer peripherals, interrupt-driven programming, and user interface design. By the end of this guide, you will have a working stopwatch that responds precisely to start, stop, reset, and lap commands while displaying elapsed time on an LCD. The architecture can be adapted to other timing applications such as countdown timers or event loggers.
Selecting the Right PIC Microcontroller
The PIC16F877A is a popular choice for this project due to its built-in timers, multiple I/O ports, and sufficient program memory. However, other PIC families like the PIC18F series offer more advanced features such as hardware multiplier and larger flash memory. Consider factors such as operating voltage (usually 5V), clock speed (up to 20 MHz), and the number of available interrupt sources when selecting a device. For the stopwatch, a microcontroller with at least one hardware timer and an external interrupt pin is sufficient. External oscillators (crystal or ceramic resonator) provide better timing accuracy, though internal RC oscillators can be used for basic functionality.
Hardware Components and Circuit Design
Essential Parts List
- PIC microcontroller (e.g., PIC16F877A or PIC18F4520)
- 16×2 character LCD with HD44780 controller
- Three push buttons (start/stop, reset, lap) – use normally open, momentary switches
- 10 kΩ potentiometer for LCD contrast adjustment
- Four 10 kΩ resistors as pull-up resistors for buttons
- 20 MHz crystal oscillator with two 22 pF ceramic capacitors
- 100 µF and 0.1 µF capacitors for power supply decoupling
- Breadboard and jumper wires for prototyping
- 5V regulated power supply (battery or USB adapter)
Circuit Schematic Overview
Connect the PIC microcontroller’s VDD and VSS pins to the 5V supply and ground respectively. The crystal oscillator connects to OSC1 and OSC2 with the two capacitors tied to ground. The LCD operates in 4-bit mode to save I/O pins – connect data lines D4–D7 to PORTB bits 0–3, and control lines RS, RW, and EN to PORTB bits 4–6. The potentiometer’s wiper connects to the LCD’s Vee pin. Each push button should connect one pin to ground and the other to an input pin on the microcontroller (e.g., PORTD bits 0–2) with a 10 kΩ pull-up resistor to VDD. The reset button connects to the MCLR pin with a pull-up resistor and a capacitor to ground for noise filtering.
Button Debouncing
Mechanical switches produce bounces that can cause multiple false triggers. A reliable approach is to implement a software debounce routine that samples the button state every 10–20 ms and confirms a stable change. Alternatively, a hardware debounce using an RC filter (e.g., 10 kΩ resistor and 100 nF capacitor) followed by a Schmitt trigger works well. The stopwatch’s responsiveness depends on clean edge detection; without debouncing, the timer could start and stop erratically on a single press.
Software Architecture and Development Environment
Programming is typically done using MPLAB X IDE with the XC8 compiler. The code should be structured into modules: timer initialization, LCD driver, button handler, and stopwatch logic. Use a state-machine approach to manage the stopwatch’s states: IDLE, RUNNING, PAUSED, and LAP. This makes the code easier to debug and extend.
Timer Configuration for Accurate Timebase
The PIC’s Timer1 is ideal for precise timekeeping because it is a 16-bit timer with an optional external crystal or internal clock. Configure Timer1 to generate an interrupt every 1 millisecond. For a 20 MHz system clock, set the prescaler to 8 and the period register to 250 (20 MHz / 4 = 5 MHz instruction clock; with prescaler of 8, timer clock = 625 kHz; interrupt every 250 ticks gives 2.5 µs? Let's recalculate: instruction clock = Fosc/4 = 5 MHz. Timer1 with prescaler 8 gives 5 MHz/8 = 625 kHz. For a 1 ms interrupt, period = 625 – 1 = 624? Actually count up from 0 to 624 gives 625 ticks -> 625 / 625 kHz = 1 ms. So set PR1 = 624 or use compare mode. Alternatively, use Timer0 with an 8-bit prescaler and count 250 ticks at 1:32 prescaler for 20 MHz? Simpler: use the CCP module in compare mode to toggle an output and generate an interrupt. Many tutorials use Timer1 with the low-power 32.768 kHz crystal as secondary oscillator for low-power timekeeping, but for a stopwatch, 1 ms resolution is adequate.
Note: Always verify the timer’s interrupt period mathematically. A 1 ms interrupt provides 0.001 second resolution – sufficient for displaying hundredths of a second. If higher precision is needed, use a faster interrupt (e.g., 100 µs) and accumulate counts.
LCD Interfacing in 4-bit Mode
The HD44780-based LCD requires a specific initialization sequence: wait 15 ms after power-up, send command 0x30 three times, then set 4-bit mode (0x28), display on (0x0C), clear display (0x01), and entry mode (0x06). Write data and commands using a nibble-by-nibble approach. For time-critical displays, background updates can be triggered from the main loop rather than inside interrupts to avoid race conditions.
State Machine Implementation
Define four states:
- IDLE – displays 00:00:00, waits for start button
- RUN – increments a counter each millisecond, updates display every 100 ms to avoid flickering
- PAUSE – timer stops but displays the elapsed time
- LAP – stores current time in a buffer and signals with a flag; the LCD alternates between live time and lap time on a button press
Each state transition is triggered by a button press event. Debounced button states feed into the state machine through a global variable or queue. Avoid blocking delays – use non-blocking debounce with timer flags.
Writing the Stopwatch Logic
The elapsed time is calculated from a 32-bit variable that counts the number of timer interrupt ticks (each tick = 1 ms). Convert this value to hours, minutes, seconds, and hundredths. For example: hundredths = ticks % 100; seconds = (ticks / 100) % 60; minutes = (ticks / 6000) % 60; hours = (ticks / 360000) % 24. Store the lap time in a separate 32-bit variable when the lap button is pressed. To display both live and lap times, use a toggle flag or a short press on the lap button to switch the LCD between the two values. A long press on the lap button could exit lap display mode and return to live time.
Handling Lap Time Display
When the stopwatch is in RUN state and the lap button is pressed for less than 500 ms (short press), copy the current ticks into a lap_ticks variable and set a lap_available flag. The LCD will show the live time normally. If the user presses the lap button again within RUN mode, a long press ( > 500 ms) enters LAP_VIEW mode where the LCD continuously shows the stored lap time. Another short press returns to live time. In PAUSE state, the lap button has no effect or could be used to display the last lap.
Testing and Validation
After programming the PIC, power the circuit and observe the LCD. The first line should show “STOPWATCH” and the second line “00:00:00”. Press the start button – the timer should increment every hundredth of a second smoothly. Verify that the stop button freezes the count and the reset button returns to zero. Test lap functionality by pressing the lap button while running and then checking the alternate display. Use a known accurate stopwatch (e.g., smartphone) to compare timing over 10 minutes. If the PIC stopwatch drifts more than 2 seconds, adjust the timer prescaler or use a more stable external crystal. Common problems include:
- Display corruption – check timing delays in LCD initialization (use
__delay_ms()from XC8). - Erratic button response – improve debounce filter or increase sampling interval.
- Timer overflows causing time jumps – use a 32-bit counter; reset the timer register on interrupt if needed.
- Power supply noise – add bypass capacitors near the PIC and LCD.
Enhancing the Project
Once the basic stopwatch works, consider these improvements:
- Add an EEPROM to store lap times across power cycles (internal EEPROM on many PICs).
- Use a real-time clock (RTC) module like the DS1307 for long-duration timing without relying on interrupt counts.
- Implement a countdown timer mode with programmable set points.
- Integrate an alarm output (buzzer or LED) that activates at zero.
- Switch to a graphical LCD (128×64) to display both live and lap times simultaneously.
- Use low-power sleep modes to extend battery life in portable applications.
External Resources and References
For deeper understanding of timer peripherals, refer to the PIC16F877A datasheet for timer register maps. A comprehensive HD44780 LCD tutorial explains 4-bit initialization and command set. For button debouncing theory, Jack Ganssle’s “A Guide to Debouncing” is an authoritative reference. The MPLAB X IDE download page provides the free development environment.
Conclusion
Building a digital stopwatch on a PIC microcontroller delivers a tangible grasp of timer interrupts, state machines, and user-interface design. By carefully selecting components, debouncing inputs, and implementing a well-structured timer loop, you create a reliable device that can be extended for more complex timing systems. This project serves as a stepping stone to real-world embedded applications like event timers, sports scoreboards, or industrial process monitors. With the code and circuit described, you now have a production-ready foundation for your own timekeeping projects.