/*
⚙️ 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
100 µs (tick 1) Pin goes HIGH (start of servo pulse)
100 µ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.1ms (100µs) units. Example:
// 10 = 1.0 ms (minimum servo angle)
// 15 = 1.5 ms (center position)
// 20 = 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 >= 200) {
// 200 × 100µ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 100 µs interrupt: OCR0A = 100 - 1 = 99, because counter starts at 0
// 100 us = 0.1 ms, so it takes 10 overflows to = 1 ms
OCR0A = 99;
// 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 = 10;
_delay_ms(1000);
pulse_width_ticks = 15;
_delay_ms(1000);
pulse_width_ticks = 20;
_delay_ms(1000);
/*
// Sweep from 1.0 ms (10) to 2.0 ms (20)
for (pulse_width_ticks = 10; pulse_width_ticks <= 20; pulse_width_ticks++) {
for (uint8_t i = 0; i < 50; i++) {
// Hold each position for 50 × 20ms = 1s
_delay_ms(20);
}
}
// Sweep back down from 2.0 ms to 1.0 ms
for (pulse_width_ticks = 20; pulse_width_ticks >= 10; pulse_width_ticks--) {
for (uint8_t i = 0; i < 50; i++) {
_delay_ms(20);
}
}
*/
}
}