Servos gotta move… That’s how this robot is gonna walk… This entails sending them a Pulse Width Modulated (PWM) signal… Essentially this means sending sending a pulse train to the servos for which the signal is high for a certain fraction of the time and doing so with a certain frequency, thus effecting a certain duty cycle…
It’s possible to take the reins and do this yourself, writing code that flips the value of an output port back and forth repeatedly to bring about the desired effect…
#include <avr/io.h>
#define F_CPU 8000000
#include <util/delay.h>
int neutral() {
int i = 0;
for (i = 0; i < 100; ++i)
_delay_us(15);
return 1;
}
int skew() {
int i = 0;
for (i = 0; i < 100; ++i)
_delay_us(20);
return 1;
}
int main(void)
{
int i, j;
// Set Port B pins as all outputs
DDRB = 0xff;
while (1) {
for (i = 0; i < 60; ++i) {
// Set all Port B pins as LOW
PORTB = 0;
for (j = 0; j < 10; ++j)
neutral();
// Set all Port B pins as HIGH
PORTB = 0xff;
neutral();
}
for (i = 0; i < 60; ++i) {
// Set all Port B pins as LOW
PORTB = 0;
for (j = 0; j < 10; ++j)
skew();
// Set all Port B pins as HIGH
PORTB = 0xff;
skew();
}
}
return 0;
}
Hook a servo up to this signal and it will swing back and forth periodically. That said, it’s tedious so have your main body of code encumbered thusly, as there are likely other things you want your robot’s micro-controller to be pondering.
Enter the micro-controller’s timer faculties… The ATtiny2313 supports two 16-bit timers and two 8-bit timers. In this context, a timer is a register that gets its value incremented after a particular unit of time. This increment operation can occur either every tick of the clock, or after every N ticks, N defining a so-called “pre-scaler”. These timers also support Pulse Width Modulation by virtue (when operating in certain modes) of having the timer not roll over to zero when it reaches its maximum value (at which point it invokes an interrupt routine) but rather start counting down, and upon reaching zero start counting up, and at the endpoints inverting the value being sent out on a configured port.
The key components of configuring such a setup are setting the “TOP” value for the counter, setting a pre-scaler value for it, and telling the micro-controller to operate the given timer in a specific PWM mode. With some help from an AVR Freaks forum entry and the ATtiny2313 data sheet I was able to puzzle through creating a basic program to test out these faculties, and by wiring up an LED to a breadboard get visual confirmation that things were working as expected.
#include <avr/io.h>
#define F_CPU 8000000
#include <util/delay.h>
int sleep() {
int i = 0;
for (i = 0; i < 10; ++i)
_delay_ms(10);
return 1;
}
int main(void)
{
int i = 0;
// configure port PB4 as an output port
DDRB = (1 << PB4);
// place the desired TOP value in a register whence it will be sourced
ICR1 = 10000;
// set the initial width of the pulse
OCR1B = 0;
// tell the micro-controller the wave generation mode of interest,
// the pre-scaler, and how to output the signal
TCCR1A = (1<<COM1A1)|(1<<COM1B1)|(1<<WGM11);
TCCR1B = (1<<WGM13)|(1<<WGM12)|(1<<CS11);
// and then gradually dial the duty cycle up and down...
while (1) {
for (i = 0; i <= 10; ++i) {
OCR1B = 1000 * i;
sleep();
}
for (i = 20; i >= 0; --i) {
OCR1B = 1000 * i;
sleep();
}
}
}
If you hook up an LED to the PB4 port, you will see it fade in and out in response to the duty cycle being dialed up and down. Such behavior is a stepping stone to driving a servo. In the case of the servo, a desired frequency of 50Hz means a period of 20ms, which means setting “TOP” to 20000 (given an 8MHz processor speed and a pre-scaler value of 8), and then we can make the servo deviate from the neutral position (with a 1.5ms pulse width) by ranging the value in OCR1B between 1000 and 2000.
At least that’s the theory… We’ll see how it goes when we switch from an LED to a servo. I’m also a little worried that although the ATtiny2313 micro-controller boasts 4 PWM channels it seems that two of them are underpinned by a 16-bit timer but the other two are underpinned by an 8-bit timer. The robot has three servos, meaning that they can’t all be running on 16-bit timer driven PWM outputs, and so I am hoping that somehow the right pre-scaler value applied to an 8-bit timer will give a good enough resolution. I haven’t yet thought this all the way through…