/*
⚙️ 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.
*/
#define F_CPU 8000000UL
#include <avr/io.h>
#include <util/delay.h>
#define SERVO_PIN PB1
void send_servo_pulse(uint16_t pulse_width_us) {
PORTB |= (1 << SERVO_PIN); // Set pin high
// Delay for desired pulse width (1000–2000 µs)
/*
This counts down from 0 to 99 if pulse_width_us is 1000,
or 0 to 199 if pulse_width_us is 2000.
0 to 99 is 100 steps, add a delay of 10us (microseconds) between steps
and you get 100 steps x 10 = 1000us. 1000us = 1.0ms (milisecond)
It uses a loop of _delay_us(10) because _delay_us() with very small values
can be inaccurate or optimized away, so chunking it is a good workaround.
*/
for (uint16_t i = 0; i < pulse_width_us / 10; i++) {
_delay_us(10);
}
PORTB &= ~(1 << SERVO_PIN); // Set pin low
}
int main(void) {
DDRB |= (1 << SERVO_PIN); // Set SERVO_PIN as output
while (1) {
// Sweep from 0° to 180°
/*
// this works for Wokwi
send_servo_pulse(410); // 0
_delay_ms(1000);
send_servo_pulse(1350); // 90
_delay_ms(1000);
send_servo_pulse(2299); // 180
_delay_ms(1000);
*/
for (uint16_t pulse = 1000; pulse <= 2000; pulse += 50) {
send_servo_pulse(pulse);
_delay_ms(20); // 20 ms total period
}
// Sweep back
for (uint16_t pulse = 2000; pulse >= 1000; pulse -= 50) {
send_servo_pulse(pulse);
_delay_ms(20); // 20 ms total period
}
}
}