Programming an ATtiny85, Part 1: Using C with a USBtinyISP (Shallow Thoughts)

Akkana's Musings on Open Source Computing and Technology, Science, and Nature.

Thu, 02 Nov 2017

Programming an ATtiny85, Part 1: Using C with a USBtinyISP

[ATtiny85 and USBtinyISP programmer] Arduinos are great for prototyping, but for a small, low-power, cheap and simple design, an ATtiny chip seems like just the ticket. For just a few dollars you can do most of what you could with an Arduino and use a lot of the same code, as long as you can make do with a little less memory and fewer pins.

I've been wanting to try them, and recently I ordered a few ATtiny85 chips. There are quite a few ways to program them. You can buy programmers specifically intended for an ATtiny, but I already had a USBtinyISP, a chip used to program Arduino bootloaders, so that's what I'll discuss here.

Wiring to the USBtinyISP

[ATtiny85 and USBtinyISP wiring] The best reference I found on wiring was Using USBTinyISP to program ATTiny45 and ATTiny85. That's pretty clear, but I made my own Fritzing diagram, with colors, so it'll be easy to reconstruct it next time I need it. The colors I used:
MISO yellow VCC red
SCK white MOSI green
RESET orange
or red/black
GND black

Programming the ATtiny in C

I found a couple of blink examples at electronut.in, Getting Started with ATtiny AVR programming, and in a Stack Exchange thread, How to program an AVR chip in Linux Here's some basic blink code:

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

int main (void)
{
    // Set Data Direction to output on port B, pins 2 and 3:
    DDRB = 0b00001000;
    while (1) {
        // set PB3 high
        PORTB = 0b00001000;
        _delay_ms(500);
        // set PB3 low
        PORTB = 0b00000000;
        _delay_ms(500);
    }

    return 1;
}

Then you need a Makefile. I started with the one linked from the electronut page above. Modify it if you're using a programmer other than a USBtinyISP. make builds the program, and make install loads it to the ATtiny. And, incredibly, my light started blinking, the first time!

[ATtiny85 pinout] Encouraged, I added another LED to make sure I understood. The ATtiny85 has six pins you can use (the other two are power and ground). The pin numbers correspond to the bits in DDRB and PORTB: my LED was on PB3. I added another LED on PB2 and made it alternate with the first one:

    DDRB = 0b00001100;
[ ... ]
        // set PB3 high, PB2 low
        PORTB = 0b00001000;
        _delay_ms(500);
        // set PB3 low, PB2 high
        PORTB = 0b00000100;
        _delay_ms(500);

Timing Woes

But wait -- not everything was rosy. I was calling _delay_ms(500), but it was waiting a lot longer than half a second between flashes. What was wrong?

For some reason, a lot of ATtiny sample code on the web assumes the chip is running at 8MHz. The chip's internal oscillator is indeed 8MHz (though you can also run it with an external crystal at various speeds) -- but its default mode uses that oscillator in "divide by eight" mode, meaning its actual clock rate is 1MHz. But Makefiles you'll find on the web don't take that into account (maybe because they're all copied from the same original source). So, for instance, the Makefile I got from electronut has

CLOCK = 8000000
If I changed that to
CLOCK = 1000000
now my delays were proper milliseconds, as I'd specified. Here's my working attiny85 blink Makefile.

In case you're curious about clock rate, it's specified by what are called fuses, which sound permanent but aren't: they hold their values when the chip loses power, but you can set them over and over. You can read the current fuse settings like this:

avrdude -c usbtiny -p attiny85 -U lfuse:r:-:i -v
which should print something like this:
avrdude: safemode: hfuse reads as DF
avrdude: safemode: efuse reads as FF
avrdude: safemode: Fuses OK (E:FF, H:DF, L:62)

To figure out what that means, go to the Fuse calculator, scroll down to Current settings and enter the three values you got from avrdude (E, H and L correspond to Extended, High and Low). Then scroll up to Feature configuration to see what the fuse settings correspond to. In my case it was Int. RC Osc. 8 Mhz; Start-up time PWRDWN/RESET; 6CK/14CK+ 64ms; [CKSEL=1011 SUT=10]; default value and Divide clock by 8 internally; [CKDIV8=0] was checked.

More on ports and pins

There's more info on ATtiny ports in ATTiny Port Manipulation (Part 1): PinMode() and DigitalWrite()

Nobody seems to have written much about AVR/ATTINY programming in general. Symbols like PORTB and functions like _delay_ms() come from files in /usr/lib/avr/include/, at least on my Debian system. There's not much there, so if you want library functions to handle nontrivial hardware, you'll have to write them or find them somewhere else.

As for understanding pins, you're supposed to go to the datasheet and read it through, all 234 pages. Hint: for understanding basics of reading from and writing to ports, speed forward to section 10, I/O Ports. A short excerpt from that section:

Three I/O memory address locations are allocated for each port, one each for the Data Register - PORTx, Data Direction Register - DDRx, and the Port Input Pins - PINx. The Port Input Pins I/O location is read only, while the Data Register and the Data Direction Register are read/write. However, writing a logic one to a bit in the PINx Register, (comma sic) will result in a toggle in the corresponding Data Register. In addition, the Pull-up Disable - PUD bit in MCUCR disables the pull-up function for all pins in all ports when set.

There's also some interesting information there about built-in pull-up resistors and how to activate or deactivate them.

That's helpful, but here's the part I wish they'd said:

PORTB (along with DDRB and PINB) represents all six pins. (Why B? Is there a PORTA? Not as far as I can tell; at least, no PORTA is mentioned in the datasheet.) There are six output pins, corresponding to the six pins on the chip that are not power or ground. Set the bits in DDRB and PORTB to correspond to the pins you want to set. So if you want to use pins 0 through 3 for output, do this:

    DDRB = 0b00001111;

If you want to set logical pins 1 and 3 (corresponding to pins 6 and 2 on the chip) high, and the rest of the pins low, do this:

    PORTB = 0b00001010;

To read from pins, use PINB.

In addition to basic functionality, all the pins have specialized uses, like timers, SPI, ADC and even temperature measurement (see the diagram above). The datasheet goes into more detail about how to get into some of those specialized modes.

But a lot of those specialties are easier to deal with using libraries. And there are a lot more libraries available for the Arduino C++ environment than there are for a bare ATtiny using C. So the next step is to program the ATtiny using Arduino ... which deserves its own article.

Tags: , ,
[ 18:01 Nov 02, 2017    More hardware | permalink to this entry | ]

Comments via Disqus:

blog comments powered by Disqus