/*
1/3/26 CM
Thoughts: This works but does not work as well as a 16 bit timer.
With a 16 bit timer there is no need to count overflow events and
use them with an ISR. With an 8 bit timer you cannot achieve a
20 ms pulse regardless of prescaller / clock division and must count
overflow events.
⚙️ How Standard Hobby Servos Work
1. Expect a 50 Hz signal (one pulse every 20 ms).
2. The pulse width (high time) determines the angle:
a. 1.0 ms = 0° (full left)
b. 1.5 ms = 90° (center)
c. 2.0 ms = 180° (full right)
3. So: we need a PWM signal with:
a. Period = 20 ms
b. High time = 1–2 ms
🧪 Can Timer0 Fast PWM Do This?
Not directly — Fast PWM runs too fast and can’t give 20 ms periods easily.
Instead, we’ll use:
🕒 Option: Timer0 in CTC Mode + Manual Pulse Generation
1. Toggle the servo pin HIGH,
2. Delay 1–2 ms (based on desired angle),
3. Toggle LOW,
4. Wait the rest of the 20 ms period.
🔁 Summary: Servo Behavior in Your Code
Time What Happens
0 µs (tick 0) ISR sets nothing yet
20 µs (tick 1) Pin goes HIGH (start of servo pulse)
20 µs × pulse_width_ticks Pin goes LOW (end of pulse)
After 20 ms (tick 200) tick_count resets, new frame begins
If pulse_width_ticks changed Servo moves to new angle
*/
#define F_CPU 8000000UL
#include <avr/io.h>
#include <avr/interrupt.h>
#define SERVO_PIN PB1 // Output pin for the servo signal
// Pulse width duration in 0.02ms (20µs) units. Example:
// 50 = 1.0 ms (minimum servo angle)
// 75 = 1.5 ms (center position)
// 100 = 2.0 ms (maximum angle)
volatile uint8_t pulse_width_ticks = 0;
// Counts how many 100µs ticks have passed in the current 20ms frame
volatile uint8_t tick_count = 0;
// Timer0 Compare Match A Interrupt Service Routine
// each tick takes 100 us or 0.1 ms, so it takes 10 overflow ticks to = 1 ms
ISR(TIMER0_COMPA_vect) {
tick_count++; // Increment the 100µs tick counter
if (tick_count == 1) {
// Start of new 20ms frame: set servo pin HIGH
PORTB |= (1 << SERVO_PIN);
}
if (tick_count == (pulse_width_ticks + 1)) {
// End of pulse: set servo pin LOW
PORTB &= ~(1 << SERVO_PIN);
}
if (tick_count >= 1000) {
// 1000 × 20µs = 20 ms → End of frame, reset tick counter
tick_count = 0;
}
}
// Setup Timer0 in CTC mode to generate 100µs interrupts
void timer0_ctc_init(void) {
// Set Timer0 to CTC mode (Clear Timer on Compare Match)
TCCR0A = (1 << WGM01); // (WGM01 = 1, WGM00 = 0) (CTC Mode)
TCCR0B = (1 << CS01); // Prescaler = 8
// At 8 MHz with /8 prescaler:
// Timer clock = 1 MHz → 1 tick = 1 µs = 0.000001 sec
// To get 20 µs interrupt: OCR0A = 20 - 1 = 19, because counter starts at 0
// 100 us = 0.1 ms, so it takes 10 overflows to = 1 ms
// 20000 / 20 = 1000 interupts per frame of 20 ms
OCR0A = 19;
// Enable Compare Match A interrupt
TIMSK |= (1 << OCIE0A);
}
int main(void) {
DDRB |= (1 << SERVO_PIN); // Set SERVO_PIN as output
// Initialize timer and enable global interrupts
timer0_ctc_init();
sei();
// Main loop: adjust pulse width every second to sweep servo
while (1) {
//*
pulse_width_ticks = 27;
_delay_ms(1000);
pulse_width_ticks = 50;
_delay_ms(1000);
pulse_width_ticks = 73;
_delay_ms(1000);
pulse_width_ticks = 93;
_delay_ms(1000);
pulse_width_ticks = 120;
_delay_ms(1000);
pulse_width_ticks = 27;
_delay_ms(1000);
// Sweep from 1.0 ms (27) to 2.0 ms (120)
for (pulse_width_ticks = 27; pulse_width_ticks <= 120; pulse_width_ticks++) {
for (uint8_t i = 0; i < 10; i++) {
// Hold each position for 50 × 20ms = 1000ms or 1s
// Hole each position for 10 x 5ms = 50ms
_delay_ms(5);
}
}
// Sweep back down from 2.0 ms to 1.0 ms
for (pulse_width_ticks = 120; pulse_width_ticks >= 27; pulse_width_ticks--) {
for (uint8_t i = 0; i < 10; i++) {
_delay_ms(5);
}
}
}
}