The Verdict: Which Peripheral Wins Your Next Design?
If you are measuring elapsed time, generating PWM signals, or scheduling RTOS tasks, the timer is the undisputed winner. If you are tallying external asynchronous events, measuring signal frequency, or decoding rotary encoders, the counter takes the prize. In modern microcontroller design, the underlying silicon is often identical, but the routing of the clock source dictates the application. You cannot reliably use a software-polled timer to count high-speed external pulses without dropping data, nor can you use an external counter to generate a precise internal system tick without an external oscillator. Choose the tool based entirely on the origin of the clock signal.
Choose a Timer when:
- You need to generate precise delays, timeouts, or watchdog resets.
- You are generating PWM waveforms for motor control or LED dimming (e.g., using the ESP32 LEDC peripheral).
- The system requires a deterministic, internally sourced timebase derived from a crystal oscillator.
Choose a Counter when:
- You need to tally external events like flow meter pulses, button presses, or limit switch triggers.
- You are decoding quadrature encoder signals to track motor shaft position.
- The signal source is asynchronous to your MCU's internal clock and operates at frequencies that would overwhelm CPU interrupts.
The Single Physical Difference That Drives Everything
The entire functional difference between a counter and a timer boils down to a single hardware multiplexer (MUX) at the clock input of the flip-flop chain. A timer counts internally generated, highly stable clock pulses. A counter counts external, asynchronous events arriving on a GPIO pin.
In discrete logic, this physical separation is obvious. If you buy an NE555 timer IC (around $0.25 in bulk), it contains internal comparators and an RC discharge network to generate its own timing intervals based on external passive components. If you buy a 74HC4040 14-stage ripple counter (around $0.45), it has no internal timebase; it simply advances its binary state every time the external clock pin sees a falling edge. You can read more about the internal block diagram of the classic 555 in the Texas Instruments LM555 Datasheet.
However, in modern microcontrollers (like the STM32 or ESP32 families), the distinction blurs because the hardware peripheral is the same. A general-purpose 16-bit timer/counter peripheral consists of a prescaler, an up/down counting register, and compare/capture modules. The 'difference' is purely a configuration bit in the peripheral's control register. When you configure an STM32 TIMx peripheral to use TIM_CLOCKSOURCE_INTERNAL, it acts as a timer. When you configure it for TIM_CLOCKSOURCE_ETRMODE2 (External Trigger), the MUX switches the flip-flop clock input to an external GPIO pin, and it acts as a counter.
Because external signals are asynchronous to the MCU's internal system clock, hardware counters include a critical physical component that timers do not need: metastability synchronizers. These are chains of two or three D-type flip-flops that sample the external GPIO pin into the internal clock domain to prevent setup/hold time violations from crashing the peripheral state machine.
Hardware Showdown: Timer vs Counter Specifications
Whether you are selecting discrete ICs for a breadboard prototype or configuring MCU peripherals for a production PCB, the operational parameters differ significantly based on the clock source.
| Criteria | Timer Configuration | Counter Configuration |
|---|---|---|
| Clock Source | Internal system clock (APB/AHB bus) | External GPIO pin (asynchronous) |
| Max Operating Frequency | Up to MCU core speed (e.g., 170 MHz on STM32G4) | Typically limited to 1/2 to 1/4 of core speed due to synchronizer latency |
| Jitter Sensitivity | Extremely low (bound by crystal PPM) | High (subject to external signal noise and bounce) |
| Primary Output Action | PWM generation, RTOS tick, DAC trigger | Event tally, frequency measurement, encoder position |
| Discrete IC Example | NE555, TLC555 ($0.25 - $0.50) | 74HC4040, 74HC161 ($0.40 - $0.75) |
Where They Are Absolutely NOT Interchangeable
The most common mistake hobbyists and junior firmware engineers make is attempting to use a timer-based software interrupt to count external events. This fails catastrophically in real-world applications due to interrupt latency and CPU overhead.
Suppose you are building a flow meter using an ESP32. The sensor outputs a 50 kHz pulse train at maximum flow. If you attach a GPIO interrupt to count these pulses (essentially using the CPU as a software counter driven by a timer tick), the ESP32 will spend nearly 100% of its cycles entering and exiting the Interrupt Service Routine (ISR). You will experience massive pulse dropout, and your main application loop will starve.
This is where you must use a dedicated hardware counter peripheral. The ESP32 features a dedicated Pulse Counter (PCNT) module. The PCNT is a hardware counter that tallies edges autonomously in silicon, completely independent of the CPU. The CPU only needs to read the accumulated register value once every few milliseconds. A timer peripheral cannot do this if its clock MUX is set to internal.
Conversely, you cannot use an external counter peripheral to generate a reliable RTOS system tick. An RTOS requires a deterministic, uninterrupted timebase (usually 1 kHz or 100 Hz) to manage task scheduling and timeouts. If you attempt to drive this tick using an external counter, any noise, missed pulses, or jitter on the external signal will cause the RTOS scheduler to drift, leading to watchdog resets and erratic task execution.
Frequently Asked Questions
Can I use a microcontroller timer to count external pulses?
Yes, but only if you reconfigure the timer's clock multiplexer to accept an external trigger, effectively turning it into a hardware counter. For example, on an STM32, you can route an external GPIO pin to the TIMx_ETR (External Trigger) pin and configure the timer to increment on each edge. However, if you leave the timer in its default internal-clock mode and try to count pulses via software interrupts, you will miss high-frequency events due to CPU interrupt latency.
Why does my Arduino interrupt miss counts at high speeds?
Standard Arduino attachInterrupt() relies on the CPU executing an Interrupt Service Routine (ISR) for every single edge. At speeds above 10 kHz to 20 kHz, the overhead of pushing registers to the stack, executing your C++ code, and popping registers takes longer than the period between pulses. The hardware buffer overflows, and pulses are ignored. To fix this, you must use a hardware counter peripheral (like Timer1 in counter mode on the ATmega328P) or switch to a microcontroller with a dedicated pulse-counting silicon module like the ESP32 PCNT.
What is the difference between a counter and a timer in PLC programming?
In Programmable Logic Controllers (PLCs), the distinction is strictly functional and abstracted from the hardware. A PLC Timer (TON, TOF) measures elapsed time based on the PLC's internal scan clock and preset timebases (e.g., 10ms, 100ms). A PLC Counter (CTU, CTD) increments its accumulator by exactly one each time the rung logic transitions from false to true (a rising edge). While the underlying PLC CPU uses hardware timers to track the scan time, the PLC 'Counter' instruction is actually evaluated sequentially during the software scan loop, making it unsuitable for counting high-speed physical pulses without dedicated high-speed counter (HSC) hardware modules.






