Let's Make Robots!

AVR C is hard!

Well, last week I got several packages of parts with lots of sets of ten. I got ten 8pin sockets, ten CR2032 batteries and ten battery holders, and most importantly ten attiny85 MCUs. I figure I can use this set of parts for a bunch of small projects, but my first project is to make a tiny bot! I've already got some tiny pager motors, and some ir emitters I can use as light sensors.

Of course my first problem was figuring out how to program these little things. I've been using Arduino relatives up until now, so I don't have a proper chip programmer. I'd read in a few places though that an Arduino could be used to program other 8bit AVR chips. It turns out most of the posts I found about this are out of date though. Luckly there is an example 'sketch' included with the Arduino environment that lets the Arduino act as an ISP, and the comments at the top of the file had all the information I needed to wire it up!

With that sorted my second problem was: figuring out how to program it! Programming a raw AVR chip isn't simple like the Arduino, there is no hand holding. I'm not that comfortable with C, so I at least wanted an IDE. I'm on a new Macbook pro though, so I couldn't even use the standard (Windows only) IDE. It wasn't hard to install the AVR crosspack for OSX and get Eclipse setup with the AVR plugin. From there I found a blinking LED tutorial somewhere, and read some tutorials in the AVR-Freaks forum. It is kind of a wierd way to work, but I'm starting to understand how to talk to these things. (BTW I had to specify a bitrate for AVRdude before it would work, but setting the bitrate with the avr plugin seemed buggy)

So after getting a light to blink I started working on the most basic thing I would need to get a bot working: using PWM to run a motor. This is where things got really tricky. All the tutorials I could find were doing the pulsing manually by playing with timers and interrupts, and the datasheet was just a bunch of nonsense acronyms and no clear example of the steps to get working PWM... Well, after hours of searching and reading on the internet, and reading the datasheet backward and forward I finally figured out the magic incantations used to run hardware PWM!

Here are some pictures of my setup:

breadboardbreadboard2

Red and Black for power, Orange/Blue for ISP programming, White for ISP signal LEDs.

I got the motor driver "design" from the project here, which is incidentally about the same as what I want to make. It is just a transistor acting as a switch driven by a data pin, and a snubber diode. This only allows motor control in one direction, but my design still has one free data pin, so if I were clever I could probably find a way to switch the motor connections?

Here is my code:

#include <avr/io.h>
#include <util/delay.h>

// I haven't decided about these yet...
#define bit_get(p,m) ((p) & (m))
#define bit_set(p,m) ((p) |= (m))
#define bit_clear(p,m) ((p) &= ~(m))
#define bit_flip(p,m) ((p) ^= (m))
#define BIT(x) (0x01 << (x))

#define MOTOR PB4

void delay_ms(uint16_t ms){
    // _delay_ms is limited, and requires a const arg for optimal code generation
    // The maximal possible delay is 262.14 ms / F_CPU in MHz. (approx. 32 at 8MHz)
    int i;
    if (ms <= 32){
        _delay_ms(ms);
        return;
    }
    ms = ms/32; // note rounding error!
    for (i=0;i<32;i++){
        _delay_ms(ms);
    }
}

void setup_pwm(void){
    // Do all the bit twiddling magic to activate hardware PWM (only using pwmb for this test)
    int i;
    // First setup the pll (Fast Peripheral Clock)
    PLLCSR = BIT(PLLE);
    _delay_ms(0.1); // Datasheet
    for (i=0;i<255;i++) { // timeout at 256 trys
        if (~bit_get(PLLCSR, 0x01)) { // Wait for the PLOCK bit to be set
            break;
        }
    }
    PLLCSR = BIT(PCKE);
    // Set clock rate prescaler for 250kHz (From datasheet)
    TCCR1 = 0x01;
    // Setup timer1b for PWM on pb4
    GTCCR = BIT(PWM1B) | BIT(COM1B1);
    // Set the Output Compare Register for 50% duty cycle
    OCR1B = BIT(7);
}

int main(void){
    int i;
    // Set clock full speed
    CLKPR = BIT(CLKPCE);
    CLKPR = BIT(CLKPS0);
    // Set port b to output
    DDRB = BIT(MOTOR);
    setup_pwm();
    for (;;){
        for (i=0;i<255;i++){
            // Change the PWM duty cycle
            OCR1B = i;
            // delay for a while
            delay_ms(5);
        }
        for (i=255;i>0;i--){
            // Change the PWM duty cycle
            OCR1B = i;
            // delay for a while
            delay_ms(5);
        }
        // pause for a moment
        DDRB = 0x00;
        delay_ms(500);
        DDRB = BIT(MOTOR);
    }

    return (1);    // should never happen
}

It may not be the best code around, but like I said - I couldn't find any decent examples!

Next I'll try to get some ADC readings with my irLEDs!

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

Nice to see another AVR developer! I've been doing kind of the same thing as you with the AVR. I'm using the ATtiny26 and have made a blinking LED plus motor control using timer + interrupt for the LED and pwm for motor control. You can check out my code example (as an attachment) from this page

My development setup is Vim editor + avr-gcc compiler + avrdude programmer where the last two are packed into a makefile that does the hard work. It works really well I think.

Yeah, I'm liking the AVR so far, and I'm finally going back and filling all the gaps in my c knowledge.

attiny26 huh? I've been looking around at other AVR models this week, but I'm thinking I'll stick with the 85. I want to get a bunch of them in SOIC form factor (25 for approx. $30 on digikey). First I need to figure out if I can make them talk to each other, then whether I can solder to SOIC!

Nice setup by the way! Full dual h-bridge with mosfets? I would love to get full directional control, but I only had 8-3=5 pins! I also didn't want to add so many components. Last week I picked up a couple dual h-bridge chips though, and some little BPJ ics. Those things are cool - a NPN and PNP transistor, and some resistors, all in one package. They are super small though!

Thanks for posting your source too! My current project is giving me doubts about my PWM setup, so I'll be going through your code very carefully!

I tried to make VIM my main editor a while back, but we broke up over something...

I found this a useful read for intercom between attinys. I have yet to play around with it due to time, but this was the method I was planning on using. It should work for the attinyX5 and attinyX4 series plus whatever else uses spi.

 

Yeah, I should try the built-in. Because of my lack of pins I was thinking more along the lines of this here. I2C gives multiple slaves on one bus.

BTW: I found this while playing with LED 'simultaneous' light sensing and emission: Link to main page

eh the AVR is hard yes, but don't forget you have #define x y, where x will be for exemple PINA and y will be 13 etc etc :) like in Arduino...

 

Second thing... If I burn Arduino bootloader on AVR, and put another microcontroller in Duemilanove, it will work programming it? Any chance of burning the arduino board?

You should be able to burn the Arduino bootloader on different AVR chips, but don't expect anything to work right if the pinout and specs aren't identical to the atmega*8 though.

If you are going to go to the bother of burning chips with the ISP I wouldn't get too hung-up on the Arduino framework. Either go all Arduino, or all Independent. Once you jump to a different chip the only thing you'll miss from the Arduino PCB is the power source.

About a week after getting my first arduino I jumped to programming it in straight C without the Arduino library in Eclipse with the plugin... I guess I wanted to be able to use any chip in the future without being limited to the arduino so I thought why not start learning now... and being a programmer I can pick things up pretty quickly...

If you really do want to code in the arduino language you could always burn the bootloader onto the chip then start programming with the familiar arduino environment you're used to ( and I absolutely hate! lol )

Well, using the ADC hardware was fairly easy after dealing with the PWM. There are all kinds of options and settings for the ADC, but for my sensors a really simple setup was all I needed:


#include <avr/io.h>
#include <util/delay.h>

#define MOTOR PB4
// Port B3 is ADC3
#define SENSOR PB3
// I want to use the ADC internal 1.1v reference and left adjusted 8bit results
#define ADC_SETTINGS (1 << REFS1 | 1 << ADLAR)

void delay_ms(uint16_t ms){
    // _delay_ms is limited, and requires a const arg for optimal code generation
    // The maximal possible delay is 262.14 ms / F_CPU in MHz. (approx. 32 at 8MHz)
    int i;
    if (ms <= 32){
        _delay_ms(ms);
        return;
    }
    ms = ms/32; // note rounding error!
    for (i=0;i<32;i++){
        _delay_ms(ms);
    }
}

void setup_pwm(void){
    // Do all the bit twiddling magic to activate hardware PWM (only using pwmb for this test)
    // First setup the pll (Fast Peripheral Clock)
    PLLCSR = 1 << PLLE | 1 << PCKE; // I have to set PCKE here because...
    _delay_ms(0.1); // Datasheet
    //loop_until_bit_is_set(PLLCSR, PLOCK); // The lock bit is never set!?!
    //PLLCSR = 1 << PCKE;
    // Set clock rate prescaler for 250kHz (From datasheet)
    TCCR1 = 0x01;
    // Setup timer1b for PWM on pb4
    GTCCR = 1 << PWM1B | 1 << COM1B1;
    // Set the Output Compare Register for 50% duty cycle
    OCR1B = 0x80;
}

uint8_t get_adc(uint8_t channel) {
    // ADC setup, call with the adc port to be read

  // select channel
  ADMUX = ADC_SETTINGS | channel;

  // warm up the ADC, discard the first conversion
  ADCSRA |= (1 << ADSC);
  while (ADCSRA & (1 << ADSC)); //Analog Digital Start Convert is cleared after conversion

  ADCSRA |= (1 << ADSC);           // start single conversion
  while (ADCSRA & (1 << ADSC));    // wait until conversion is done

  return ADCH; // Only the high byte is used
}

int main(void){
    // Set clock full speed
    CLKPR = 1 << CLKPCE;
    CLKPR = 1 << CLKPS0;
    // Set port b to output
    DDRB = 1 << MOTOR;
    // enable ADC and set prescaler to 32 (approx 250kHz)
    ADCSRA = (1 << ADEN) |
             (1 << ADPS2) | (1 << ADPS0);
    // Disable analog ports
    DIDR0 = 1 << ADC1D | 1 << ADC3D;
    setup_pwm();
    for (;;){
        OCR1B = get_adc(SENSOR) << 1;
        // delay for a while
        delay_ms(5);
    }

    return (1);
}

Sorry if that's too much code (didn't Fritsl say something about not posting long code listings?).

It turns out the light sensor I have is a photodiode, not an LED. My multimeter tells me it reads up to about 300mV in dim sunlight. I figure 8bits ought to be enough when measured against 1.1V, and at that resolution going over the 200kHz spec in the datasheet is probably ok. I don't know if I need a current limiting resistor though?

Anyway, it works great so far, and hasn't let the smoke out yet!

Yeah, two problems:

  • I don't know how applicable c++ code written for the atmega328 will be to writing C on an attiny85 (though I do plan to go through the libraries eventually). 
  • I'm on MacOS and don't feel like paying ~$200 to just to run and IDE. Besides Eclipse is pretty cool so far. Unfortunately I'll need Windows eventually since all my CAD software is Windows only...

For the macros, I just meant that I liked the plain bit arithmetic a little better. I guess using 0b00000000 is bad style though?

Aside from hardware on the two different chips, you should have basic similarities that translate over from one proc to the other. I think you'll find more c source vs cpp for the attiny, but thats just what I've noticed.

I assume you're refering to Window$ as the cost. You could always use Wine and I've heard it works great under snowleopard though I haven't tried it myself. I just use a Windows vm or my old laptop with windows on it.

As for the macros, I think it just makes it easier to read and keeps the code a bit more readable...but thats just my opinion.

I prefer 0xff  :D