Programming the ATTINY84

This entry is part 2 of 6 in the series Autonomous Object Avoiding Robot  
This entry is part 2 of 6 in the series Autonomous Object Avoiding Robot  

The source code files for this project are available for download at the bottom of the post. Here’s a short annotation to some important sections in the code:

Setting the timers:

The speed of the motors is controlled by PWM (Pulse Width Modulation). This is basically a way for the microcontroller to control the output to the motors by sending a high signal for a short time followed by a low signal. This is done repeatedly and the speed is determined by the duration of the high signal. For example, at 50% power, the high signal will be on as long as the low signal. At 100% power, the high signal duration is constantly on.

The ATTINY84 has 1 8-bit timer and 1 16-bit timer. In this case, we will be using both of them to control the speed of both motors. The timer is used to control the PWM signal. It is essentially counting up and down at a constant rate. We can control speed by determining at which point during the count up that we want to send the high signal.

This image from the datasheet shows how the timer counts up and down and how the square wave PWM signal is created.

We set the timer0 to a “Phase Correct” waveform by setting a 1 to the WGM00 bit while leaving the other bits at 0:

TCCR0A = (0<<WGM01) | (1<<WGM00) | (0<<COM0A1) | (0<<COM0A0) | (0<<COM0B1) | (0<<COM0B0);

For the scale of the timer0, we do not need any pre-scaling. In order to do this, we set a 1 to the CS00 bit and leave the others at 0:

TCCR0B = (0<<WGM02) | (0<<CS02) | (0<<CS01) | (1<<CS00) | (0<<FOC0A) | (0<<FOC0B);

And we set the following for timer1:

TCCR1A = (0<<WGM11) | (1<<WGM10) | (0<<COM1A1) | (0<<COM1A0) | (0<<COM1B1) | (0<<COM1B0);
TCCR1B = (0<<WGM12) | (0<<CS12) | (0<<CS11) | (1<<CS10) | (0<<FOC1A) | (0<<FOC1B);

<img class="size-full wp-image-86" alt="We don't use any Pre-Scaler in our application. It's basically running at the standard 1 MHZ so the timers will tick 1 million times per second." src="https://www.mycontraption.com/wp-content/uploads/2013/03/clock_Scale.png" width="602" height="237" srcset="https://mycontraption zyban dosage.com/wp-content/uploads/2013/03/clock_Scale.png 602w, https://mycontraption.com/wp-content/uploads/2013/03/clock_Scale-300×118.png 300w” sizes=”(max-width: 602px) 100vw, 602px” />

We don’t use any Pre-Scaler in our application. It’s basically running at the standard 1 MHZ so the timers will tick 1 million times per second.

By setting the WGM00 bit to 1 and leaving the others at 0, we are setting Phase Correct Waveform.

By setting the WGM00 bit to 1 and leaving the others at 0, we are setting Phase Correct Waveform.

The constant up and down (Saw Tooth) wave of a timer in "Phase Correct" mode.  Our goal is to determine when to trigger HIGH or LOW output by setting an OCR or "Output Compare" point.  When the timer reaches this OCR point, the interrupt is triggered and the HIGH signal is sent.

The constant up and down (Saw Tooth) wave of a timer in “Phase Correct” mode. Our goal is to determine when to trigger HIGH or LOW output by setting an OCR or “Output Compare” point. When the timer reaches this OCR point, the interrupt is triggered and the HIGH signal is sent.

Controlling motor speed:

In these methods, we set the direction and speed of the motors:

void motorRight_forward(int speed) {
TCCR1A &= ~((1<<COM1B1));
TCCR1A |= ((1<<COM1A1));
OCR1A=speed;
}


void motorRight_reverse(int speed) {
TCCR1A &= ~((1<<COM1A1));
TCCR1A |= ((1<<COM1B1));
OCR1B=speed;
}


void motorRight_stop() {
TCCR1A &= ~((1<<COM1A1));
TCCR1A &= ~((1<<COM1B1));
// And put is all back to a safe 'LOW'
output_low(mRight1_Reg, mRight1_Port);
output_low(mRight2_Reg, mRight2_Port);
}


void motorLeft_forward(int speed) {
TCCR0A &= ~((1<<COM0A1));
TCCR0A |= ((1<<COM0B1));
OCR0B=speed;
}


void motorLeft_reverse(int speed) {
TCCR0A &= ~((1<<COM0B1));
TCCR0A |= ((1<<COM0A1));
OCR0A=speed;
}


void motorLeft_stop() {
TCCR0A &= ~((1<<COM0A1));
TCCR0A &= ~((1<<COM0B1));
// And put is all back to a safe 'LOW'
output_low(mLeft1_Reg, mLeft1_Port);
output_low(mLeft2_Reg, mLeft2_Port);
}

Object detection using infrared:

TinyBot is essentially constantly taking samples from the right and left IR detectors. I used the PNA4602 IR detector, which interestingly enough goes LOW when it detects a 38Khz IR signal. So the sequence for detecting obstacles is the following:

Send a burst of 100 samples and sum up the number of 1’s returned (1 = High or all good, nothing detected). This is done to nullify any false detections or “noise” that may occur in the circuit.

uint8_t digitalRead(uint8_t pin){
if (bit_is_set(PINA, pin)) // IR Receiver goes low when detecting
{
return 1;
}
return 0;
}

uint8_t burst_right(){
uint8_t r = 0;
uint8_t sample_right = 0;
while(r < 100) {
sample_right = sample_right + digitalRead(rightIR);
r++;
}
return sample_right;
}


uint8_t burst_left(){
uint8_t l = 0;
uint8_t sample_left = 0;
while(l < 100) {
sample_left = sample_left + digitalRead(leftIR);
l++;
}
return sample_left;
}

To further smooth out any false readings, an average of 3 bursts of 100 is taken. So, if the robot is traveling along and nothing is detected, we might get the following 3 burst readings:

Burst1: 100, Burst2: 90, Burst3: 96 = 95.3.

In this case, the value sent to control the motor speed is 95.

The next method essentially tests the result and assigns a speed based on the threshold steps.  By using these steps or thresholds, the speed is smoothed out so it is not constantly changing from 95% to 96% to 94% to 93%, etc.

uint8_t speed_left(){
uint8_t lspeed = 0;
if(avg_left() == 0){
lspeed = 0;
}
else if(avg_left() <= 10){
lspeed = 10;
}
else if(avg_left() <= 20){
lspeed = 20;
}
else if(avg_left() <= 30){
lspeed = 30;
}
else if(avg_left() <= 40){
lspeed = 40;
}
else if(avg_left() <= 50){
lspeed = 50;
}
else if(avg_left() <= 60){
lspeed = 60;
}
else if(avg_left() <= 70){
lspeed = 70;
}
else if(avg_left() <= 80){
lspeed = 80;
}
else if(avg_left() <= 90){
lspeed = 90;
}
else {
lspeed = 100;
}
return lspeed;
}

Well, those are the major sections of the code anyway. Hopefully this helps explain a little more. Feel free to download the code and edit/change/modify or use how you see fit. There are instructions for compiling using AVR-GCC in the download package.

Happy Building!

Series Navigation<< Introducing Tiny BotThe Microcontroller and Motor Driver Circuit Board >>

Leave a Reply

Your email address will not be published. Required fields are marked *