Catto / Topic Map · Digital Electronics III Year 6
Digital Electronics III · 144 h · Topic 3 of 7

Peripherals

Peripherals are hardware that works on its own, in parallel with the CPU. A timer counts while the program does something else; a PWM module holds an output without anyone tending to it. Understanding them changes the way you write programs: you stop waiting.

Microcontrollers Timers Interrupts PWM A/D converter

01Stop waiting

Everyone’s first program blinks an LED with a delay. The problem appears with the second requirement: while it waits, the program can’t do anything else. Not read a pushbutton, not service the serial port, not measure a temperature.

// NOT like this: while the delay lasts, the micro is stuck
while (1) {
    led_on();  _delay_ms(500);
    led_off(); _delay_ms(500);      // a button pressed here goes undetected
}

// Like THIS: the timer counts on its own and signals; the loop stays free
while (1) {
    if (bandera_500ms) { bandera_500ms = 0; led_toggle(); }
    atender_pulsadores();
    atender_puerto_serie();
}

The second scheme —a main loop that never blocks, plus flags set by interrupts— is the structure of almost every serious embedded program.

PeripheralWhat it does on its ownTypical example
Timer / counterCounts clock pulses or external eventsTimebase, measuring frequency, generating delays
PWMHolds a square wave with an adjustable duty cycleLED brightness, motor speed, servo
Capture (CCP)Saves the counter value when an edge arrivesMeasuring a pulse width, a period, an ultrasonic sensor
CompareTriggers an event when the counter reaches a valueExact periodic interrupt, generating frequencies
A/D converterConverts a voltage into a numberSensors, potentiometers, battery measurement
USART, SPI, I²CBuilds and decodes framesCommunication module
WatchdogWatches that the program doesn’t hangSafety in unattended equipment

02Timers

A timer is a binary counter —the same thing covered in programmable counters— fed by the system clock through a prescaler, which is simply a divider.

OCR = 249 · compare overflow (255) output counter value time In CTC mode the counter resets on reaching OCR: the period stays fixed and exact.
Figure 1. The counter and its two events, animated. When it reaches the value loaded in the compare register an event occurs and —if it is in CTC mode— the counter resets. If nothing stops it, it keeps going until overflow and returns to zero by itself.
ttick=NfoscOCR=fosc·tN−1 N is the prescaler (1, 8, 64, 256, 1024). The “−1” is there because the counter starts at zero: if you want 250 counts, the compare value is 249.
Worked example · One interrupt every millisecond

16 MHz crystal, 8-bit timer in CTC mode:

  1. With a prescaler of 64, each count lasts 64/16,000,000 = 4 µs.
  2. For 1 ms you need 1000/4 = 250 counts.
  3. Load OCR = 250 − 1 = 249. Since it fits in 8 bits (maximum 255), it works.
  4. Check: (249 + 1) × 4 µs = exactly 1000 µs. No error, because 16 MHz is a multiple of 64 × 1000.

With a prescaler of 8 you would need 2000 counts: it doesn’t fit in 8 bits and you have to use the 16-bit timer. That is the complete reasoning: choose the smallest prescaler whose result fits in the counter, because the smaller the prescaler, the better the resolution.

The modes you need to know
  • Normal: counts up to the maximum and overflows. Simple, but the period is fixed by the prescaler.
  • CTC (clear timer on compare): counts up to OCR and resets. It is the mode for exact timebases.
  • Fast PWM and phase-correct PWM: the output changes on its own according to the compare value. See the PWM section.
  • External event counter: instead of the internal clock it counts edges on a pin. It is a frequency counter in two lines of code.

03Interrupts

An interrupt is an automatic detour: when the peripheral raises its flag, the CPU finishes the current instruction, saves where it was and jumps to a special routine. When it is done, it returns to exactly the same place. The main program doesn’t even notice.

What you gain
  • Immediate response to an event, without having to keep asking.
  • The main loop stays free.
  • It lets you put the micro to sleep and wake it only when something happens.
The rules of an ISR
  • Very short. Set a flag, read a piece of data, and get out. No delays, long calculations or sending characters over the serial port.
  • The variables it shares with the main loop are declared volatile, or the compiler optimizes them and the program stops seeing the changes.
  • If the shared variable is more than one byte, it has to be read with interrupts disabled: otherwise you can read the old half and the new half.
  • The flag clears itself or has to be cleared by hand depending on the peripheral: if it isn’t cleared, the interrupt fires forever.
// 1 ms timebase with the timer in CTC mode
volatile uint16_t ms = 0;      // volatile: modified by an interrupt

ISR(TIMER0_COMPA_vect) {          // runs every 1 ms
    ms++;                         // and nothing else: the ISR ends here
}

int main(void) {
    TCCR0A = (1 << WGM01);                    // CTC mode
    TCCR0B = (1 << CS01) | (1 << CS00);      // prescaler 64
    OCR0A  = 249;                            // 250 counts of 4 µs = 1 ms
    TIMSK0 = (1 << OCIE0A);                   // enable the interrupt
    sei();                                     // enable globally
    while (1) { /* the loop stays free for everything else */ }
}
Latency and priorities

Between the event occurring and the ISR starting, a few microseconds go by: the CPU finishes the current instruction and saves the context. If another interrupt is also running, you have to wait for it. That is why long ISRs ruin the response of the whole system: one that takes 2 ms makes all the others lose pulses. On small micros the priorities are fixed and are listed in the vector table of the datasheet.

04PWM: pulse-width modulation

An output that switches quickly between 0 and VCC, with an adjustable high time. The average value is proportional to that time, and with it power is regulated without dissipating: the transistor is saturated or cut off, never in between.

0 V 5 V average = 0.50 V duty cycle = 10% average = 1.75 V duty cycle = 35% average = 3.25 V duty cycle = 65% average = 4.50 V duty cycle = 90% brightness The frequency doesn’t change: what changes is how long the output stays high. The transistor is saturated or cut off: it dissipates nothing, so power is regulated without heating.
Figure 2. Duty cycle, animated. The frequency doesn’t change; what changes is how long the output stays high. The dashed line is the average value, which is what a lamp, a motor or an R-C filter sees.
D=thighT·100Vavg=D·VCCfPWM=foscN·2n With 16 MHz, a prescaler of 64 and 8 bits of resolution: f = 16,000,000/(64 × 256) = 976.6 Hz. Raising the resolution lowers the frequency, and vice versa: it is the central trade-off of PWM.
ApplicationSuitable frequencyWhy
LED brightness> 100 HzBelow that the flicker is visible, especially when moving your eyes.
DC motor1 to 20 kHzBelow 20 kHz the whine is audible; above it, switching losses grow.
Heater or thermal load< 10 HzThermal inertia averages by itself: there is no need to switch fast.
Servo motorExactly 50 HzIt doesn’t regulate by average value: the angle is set by the pulse width, between 1 and 2 ms.
Analog output (with R-C filter)High, with a slow filterThe higher the frequency, the smaller the remaining ripple.
Example · A servo with the 16-bit timer

16 MHz crystal, prescaler 8 → each count lasts 0.5 µs.

  • A 20 ms period (50 Hz) = 20,000 µs / 0.5 = 40,000 counts: it just fits in 16 bits.
  • A 1 ms pulse (0°) = 2000 counts. A 2 ms pulse (180°) = 4000 counts.
  • Each degree is (4000 − 2000)/180 = 11.1 counts: there is resolution to spare.

With the 8-bit timer this can’t be done: there aren’t enough counts for 20 ms with useful resolution. Choosing the right timer is part of the problem.

05Capture and compare

Capture

When an edge arrives at a pin, the module copies the counter value into a register and signals. The CPU doesn’t have to be watching: the instant is recorded with the precision of the timer.

With two captures —rising and falling edge— you measure the width of a pulse. It is the right way to read an HC-SR04 ultrasonic sensor, a remote-control receiver or the signal from a flow meter.

Compare

The other way around: when the counter reaches a loaded value, it triggers an event —an interrupt, a pin change, a counter reset—. It is what lies underneath CTC mode and PWM.

Example · Measuring distance with ultrasound

The sensor returns a pulse whose width is proportional to the round-trip time of the sound.

  • With a prescaler of 8 at 16 MHz, each count is 0.5 µs.
  • The two edges are captured and subtracted: if the difference is 2320 counts, the pulse lasted 2320 × 0.5 = 1160 µs.
  • Sound travels at 343 m/s: it covered 0.001160 × 343 = 0.398 m, round trip. The distance is half: 19.9 cm.

If you measure the same thing with a busy-wait loop, any interrupt that occurs in the middle ruins the measurement. With capture, it doesn’t.

06The internal A/D converter

It is almost always a successive-approximation type, 10 or 12 bits, with a multiplexer that lets you choose among several pins. All the concepts are in A/D and D/A converters; here what matters are the four parameters you have to configure.

What you chooseDetail
ChannelWhich pin is measured. There is only one multiplexer: one channel is measured at a time, and you have to wait between changes.
ReferenceThe supply, an internal reference (1.1 V typical) or an external one through AREF. It defines the full scale and with it the resolution.
Converter clockWith its own prescaler: it should fall between 50 and 200 kHz for maximum accuracy. At 16 MHz with a prescaler of 128 it gives 125 kHz, and one conversion takes about 104 µs.
Alignment and triggerWhether the result is left-aligned (useful for reading only 8 bits) and whether the conversion starts by software or by a timer event.
The three mistakes that ruin an analog measurement
  • Noisy reference: if VCC is used as the reference and the supply has ripple, the reading fluctuates even though the input is still. Use an internal or a filtered external reference.
  • High-impedance source: the sample & hold capacitor doesn’t get a chance to charge. The datasheet asks for less than 10 kΩ; with more, you need a follower with an op amp.
  • Digital noise: the micro’s own switching coupled into the input. You average, separate the grounds and, on micros that have it, use the noise reduction mode that puts the CPU to sleep during the conversion.

07In the lab

Lab 1 · An exact timebase

Configure the timer in CTC mode for an interrupt every 1 ms and toggle a pin on each interrupt. Measure with the oscilloscope: you should get exactly 500 Hz. Then change the prescaler without changing OCR and see how the frequency shifts. That makes the formula clear.

Lab 2 · Non-blocking blinking

Write the same blink in two ways: with _delay_ms() and with the interrupt-driven timebase. In both, add a pushbutton that turns on another LED. With the delay, the pushbutton responds late or not at all; with the interrupt, it always responds. It is the lab that justifies this whole topic.

Lab 3 · PWM and average value

Generate PWM at 1 kHz and vary the duty cycle with a potentiometer read by the A/D. Measure the pulse width with the oscilloscope and, with the multimeter on DC, the average value. Verify that Vavg = D × VCC. Then add an R-C filter and compare the filtered output with the raw one.

Lab 4 · Servo

With the 16-bit timer, move a servo from 0° to 180° according to the potentiometer. Measure the pulse width at the extremes and verify that they are 1 and 2 ms. Note what happens if the frequency is not 50 Hz.

Lab 5 · Measuring a pulse with capture

Generate a pulse of known width with a 555 and measure it with the capture module. Compare with the oscilloscope measurement. Repeat after adding an interrupt that consumes time: the capture measurement doesn’t change, and that is the point.

08Common mistakes

SymptomUsual cause
The interrupt fires non-stopThe peripheral’s flag wasn’t cleared, or was cleared incorrectly.
The main program doesn’t see a variable changevolatile is missing: the compiler kept it in a register and doesn’t re-read it.
A 16-bit counter gives absurd values now and thenIt was read while the interrupt was modifying it. It has to be read with interrupts disabled.
The system responds slowly or loses pulsesAn ISR that is too long. Move everything that isn’t essential out to the main loop.
The timer’s timing doesn’t match the calculationThe “−1” in the compare register was forgotten, or the actual clock isn’t the assumed one (fuses, divide-by-8 enabled).
The PWM-driven LED visibly flickersFrequency too low: raise it above 100 Hz.
The PWM-driven motor squealsFrequency within the audible band. Raise it above 18 to 20 kHz.
The servo jitters or doesn’t reach the extremesA period other than 20 ms, or widths outside the model’s range. Each servo has its own.
The A/D reading jumps by several countsReference with ripple, high-impedance source or noise. Average and check all three causes.
After changing channels, the first reading is wrongThe acquisition time after moving the multiplexer wasn’t waited for. The first conversion is discarded.

09Self-assessment

What concrete advantage does a timer have over a delay loop?

It counts on its own, in parallel with the CPU. The program doesn’t get blocked and can handle other things; besides, the timing doesn’t depend on what the program is doing.

With 8 MHz and a prescaler of 256, how long does one count last, and how long does an 8-bit overflow take?

Each count lasts 256/8,000,000 = 32 µs; the overflow occurs every 256 × 32 = 8.192 ms.

You want an interrupt every 2 ms with 16 MHz and a prescaler of 64. What compare value?

Each count is 4 µs; 2000/4 = 500 counts → OCR = 499. Since it doesn’t fit in 8 bits, you have to use the 16-bit timer (or a prescaler of 256, with OCR = 124).

Why is a variable shared with an ISR declared volatile?

Because the compiler, seeing nobody else modify it, may keep it in a register and never re-read it from memory. volatile forbids that optimization and forces it to be read every time.

What should an interrupt service routine do —and not do?

It should: read the data or set a flag and get out. It should not: use delays, do long calculations, send over the serial port or refresh the watchdog.

With 8-bit PWM, a prescaler of 8 and 16 MHz, what is the frequency?

f = 16,000,000/(8 × 256) = 7812.5 Hz. It works for a motor —it is below the annoyingly audible range yet switches fast— and is more than enough for an LED.

Why isn’t a servo controlled by average value?

Because its internal electronics measure the pulse width, not the average: between 1 and 2 ms within a 20 ms period. Two signals with the same average value and different width give different angles.

What is the capture module for?

To record the exact instant an edge arrives, copying the counter value without CPU intervention. With two captures you measure the width of a pulse or a period without other tasks affecting the measurement.

You are measuring a voltage that barely varies and the A/D reading jumps ±3 counts. What do you check?

The reference (supply ripple), the source impedance (too high for the sample & hold) and the coupled digital noise. It also helps to average several readings.

Why is it a good idea to discard the first conversion after changing channels?

Because the hold capacitor still has the charge from the previous channel and needs an acquisition time to take on the new value. The first reading is contaminated by the previous measurement.

Development of the topic “Peripherals” of Digital Electronics III (Year 6), based on the “Curriculum Proposal – Second Cycle of the Technical-Vocational Track, Secondary Education – Electronics,” Ministry of Education of the Province of Córdoba, DGETyFP. Back to the Topic Map · catto.ar