A close look at the ATtiny10, the smallest microcontroller produced by Atmel, and how makers can use it to make compact projects.
Bigger isn't always better. Sometimes, we don't need all the bells and whistles of a particular gadget to get what we need to be done. Just like you don’t need a Lamborghini for shopping trips (unless that’s your type of thing), you don’t need a gigantic Arduino Uno for doing the really basic stuff.
Maybe an Arduino Nano will do the trick? What about an ATtiny85? These two options are pretty popular for compact DIY electronics, so they pop up a lot in discussions online and in the projects we publish.
But, both of those solutions are monster trucks compared to this little guy. The ATtiny10 is the smallest maker-friendly microcontroller that we’ve seen available, so we just had to get our hands on one to see what it could do. Let’s dig in! ››
First, a quick note on sourcing these elusive components. At the time of writing, the ATtiny10 and its associated SOT-23 breakout board isn’t available from Jaycar, Altronics or Pakronics. Element14 do list them on their site, however, at the time, didn’t have stock. We ended up ordering them from AliExpress.
The Broad Overview
First things first: It’s tiny. Really tiny. Like loose-it-in-5-seconds tiny. Here it is compared to a grain of rice.
The body of the microcontroller squares up at 1.6mm by 2.9mm in a SOT-23 form factor, which is a common package for transistors and small regulators on PCBs.
On the six pins available, two are for power (1.8V-5V and Ground) and the other four are all GPIO pins! Four doesn’t sound like much, but for applications intended for use with this microcontroller, it's a perfect amount. Note that the fourth GPIO pin (Pin 6 on the chip) acts as a reset switch so High Voltage Programming is needed if it is desired to be used as a digital input or output. That way, only 12V can reset the chip for programming, and 5V is used for regular digital logic.
All pins can be used as analogue inputs thanks to the 8-bit ADC! There’s only one Analog-to-Digital converter, but the ATtiny10 can switch the pin using it whenever necessary. This makes the ATtiny10 a great little unit for creating complex digital functionality from simple analogue devices like potentiometers.
It has a 10MHz internal clock, plenty of ‘Omph’ for basic computations. If 2MHz was enough to get Apollo 11 to the Moon, then believe us when we say 10MHz can do much more than blink an LED!
In the RAM department, we’ve got a whopping 32 bytes of it. That's about 500 million times less than a desktop computer. But don’t fret, 32 bytes is sufficient for 90% of the applications used with this microcontroller. You may have to get creative with how data is stored though! We’ll look at this later.
It has 1KB of programmable flash memory, which really isn’t far off the 32KB featured by the astronomically bigger ATmega328 used in the Arduino Uno. Again, because of what the ATtiny10 was designed for, this is usually perfectly fine. However, flashing libraries, unnecessary data (e.g. storing a ‘long’ variable where a ‘byte’ data type would be sufficient) will wipe out this memory pretty quickly.
There are a number of projects online featuring the ATtiny10, however, we've noticed, in particular, many don't pay attention to the rather complex nature of register programming and logic - a process that's required to use the ATtiny10. To break it all down, we've built a few projects to show you how to get started and understand a different paradigm in Arduino programming. Let's get started!
Project 1: Programming Shield
Parts Required: | Jaycar | ||
---|---|---|---|
1 x ATtiny10 | - | ||
1 x Double-sided Perfboard | - | ||
2 x 5 pin Single-row Male Header ^ | HM3250 | ||
1 x 10 pin Dual-row Male Header ^ | HM3211 | ||
2 x 5 pin Single-row Female Header ^ | HM3230 | ||
1 x SOT23 Breakout Board | - |
* Hook-up wire is also required. ^ Longer strips can be cut down to size.
We can’t exactly plug the ATtiny10 straight into a USB port and program it like most other Arduino microcontrollers. It’d probably get lost inside the port! Because the ATtiny doesn’t have an inbuilt USB interface, we need to use an external USBasp programmer to put programs onto it. Once the program is uploaded, we’re free to remove it and install it into the place where compact functionality is required. ››
First up though, we need to 'break out' the pins on the ATtiny10. Because they’re too small to be soldered onto regular breadboard-spaced pins directly, we need to use a breakout board compatible with it. We’re using a simple SOT-23 breakout board, which splits up to 10 pins into a breadboard-friendly layout.
If you don’t want to solder the ATtiny10 because you wish to transfer it to a different circuit after programming, a pair of tweezers or a small weight can be used to provide non-permanent contact between it and the breakout board.
To make things easier, we put together a programming shield that helps eliminate the hassle of connecting the header wires all the time when new code needs to be uploaded. You can, of course, omit this and connect it directly to a breadboard if you don’t care for the organisation.
Programming Shield
First up, it’s important that a dual-sided perfboard is used since we’re soldering headers to both the top and bottom sides. We soldered two rows of five-long female headers to the perfboard and male headers to the underside. This way, the pinout on the breadboard, once we’ve inserted the ATtiny10, will be the same as normal.
We also popped in another pair of 5-long male headers on the top side to connect to the USBasp programming header.
We soldered in the 5V and Ground wires for power to the ATtiny10. Orientation is important, and unlike other microcontrollers like the ATtiny85, the power pins are in the centre-most positions on the chip.
Next, we need to connect three wires to support programming data transfer between the programmer and ATtiny10 - MOSI, SCK and RESET. You can see which wires are connected to what in the wiring diagram.
After checking there were no shorts on the programming shield, we popped in the ATtiny10 with its breakout board and connected the USBasp programmer.
Unfortunately, the drivers aren’t plug-and-play on Windows 10. There’s an extra setup required to get the USBasp programming working properly. You can confirm that the drivers aren’t installed by heading to Device Manager (right-click on the Windows button and select Device Manager from the list). If the USBasp appears with a warning or question mark, you’ll need to install the drivers. Head to the project resources to download a program called Zadig, which should handle installing the libusbK libraries to get the USBasp working properly.
At this point, we can set up the ATtiny10 with the Arduino IDE. Note that many programmers seem to use the Atmel Studio software, however, thanks to Technoblogy’s GitHub repository, we can just add programming functionality to the Arduino IDE by heading into the preferences and adding this line of code to the ‘Additional Board Manager URL’ box:
http://www.technoblogy.com/package_technoblogy_index.json
After that, just head into the Boards Manager and search for ATtiny10. After hitting Install, it can be treated like any other Arduino board. As long as the Board type is selected in the Tools menu, it should be good to upload code. Note that you shouldn’t need to select a COM port.
Of course, we need something to upload onto the ATtiny10, so let’s fix that and build the next project.
Project 2: LED Dimmer Controller
Additional Parts Required: | Jaycar | ||
---|---|---|---|
1 x LED Strip* | ZD0570 | ||
1 x IRF540 MOSFET | ZT2466 | ||
1 x 10kΩ Potentiometer | RP8510 | ||
1 x 1kΩ Resistor* | RR0572 | ||
1 x 10kΩ Resistor* | RR0596 |
* Quantity required, may only be sold in packs. A breadboard and prototyping hardware is also required.
Now that we’ve built our programming shield, we’re going to build a PWM generator. PWM (Pulse-Width Modulation) can be used to dim LEDs and other resistive loads efficiently, so it’s a good task for a compact microcontroller like this.
Handily enough, this project can be made into a waveform generator by changing just one line of code. Leaving it in PWM mode creates a fixed-frequency, variable duty cycle signal, while switching it to the waveform mode inverts this behaviour. In other words, a square wave signal with variable frequency. This code can be seen more in detail in the next project.
You’ll need the programming shield (or a similar breakout board) for this project, which we left in place from the last build. We added ground wires to both sides, but connect the 5V wire to ONLY the bottom. Since we’re adding 12V to the top red line for the LEDs, we definitely do not want this anywhere near our microcontroller power lines. We also added headers in the top left corner of the breadboard to be connected to our 12V power supply.
To control our PWM output, we need a control knob which we used a 10kΩ potentiometer for. You can use any potentiometer or resistor divider for this, so long as it generates a voltage between 0V and 5V on the output.
We connected the wiper (middle pin) to Pin 4 (PB2) on the ATtiny10, and the other pins to Ground and 5V.
Since the 250mA or so required to drive the LEDs is far too much for our ATtiny, we need to use a MOSFET or transistor to switch the current. In this case, we’re using a IRF540 N-channel MOSFET. This will be configured as a low-side switch, so on our breadboard we need to connect the upper red 12V line to the positive side of the LED strip. The negative side connects to the Drain of the MOSFET, and the Source connects to ground.
To switch on and off the MOSFET, we need a digital signal from the ATtiny. We connected Pin 1 (PB0) to the MOSFET though a 100Ω resistor to limit the inrush current. To ensure the signal is in a known state, we added a 10kΩ resistor on the gate too. And ta-da! Our electronics are done. Let’s hook up the USB programmer and get to coding.
Because we’re dealing with such a barebones microcontroller, most of the code we’ll be writing will use register logic rather than high-level calls - you may be used to functions like “digitalWrite” or “pinMode”. We’re going to step through this code one line at a time because it’s pretty overwhelming without an explanation.
We aren’t just plucking all this stuff out of thin air. There are a number of guides that helped us to figure out how to get the ATtiny10 running properly. Check out the excellent Technoblogy blog and the ATtiny10 datasheet, which you can find at the end of the article.
#define MIN_VAL 5
void setup () {
DDRB = 0b0001;
We only have one definition in this program, “MIN_VAL”, which is the minimum potentiometer value that can trigger a response. Since analog signals typically have trouble reaching the high and low ranges, we can add a “deadzone” so the output actually fully turns on and off. We'll check this out more in the main loop.
DDRB = 0b0001 is our first register assignment. We’re simply writing '1' into the last bit of the DDRB register, which stands for the 'Data Direction Register' on Port B. This is the register equivalent of the ‘pinMode’ function. Since the ATtiny10 has four GPIO pins, we only want the first pin to be an output (to drive our LEDs), while all others are inputs.
TCCR0A = 2<<COM0A0 | 1<<WGM00;
TCCR0B = 1<<WGM02 | 1<<CS00;
This is where we start really getting into register programming! Buckle up. The first register we’re dealing with here is the TCCR (Timer/Counter Compare Register) that can be manipulated to produce different results with the inbuilt timers. If you’ve never experimented with bitwise operators before, this code will probably not be clear in what it is accomplishing. We won’t go too far into it, but for the purposes of this guide, 2 << COM0A0 means write '2' in decimal or '10' in binary to the COM0A0 part of the target register. Here’s the table from the ATtiny10 datasheet showing the functionality of the COM0A0 register.
We can also write to other parts of the register by using the bitwise OR operator: ‘|’. Therefore, the first line is writing 2 to COM0A0 and 1 to WGM00 within TCCR0A. Of course, without explaining these names, they don’t mean much! The COM0A0 register essentially allows us to override the default functionality of the pin it’s connected to, in this case, PB0. Since we’re running it as a timer, the output will be connected directly to that pin.
The WGM00 register allows us to select how the timer will run. In mode 1, it is a simple 8-bit PWM source with adjustable duty cycle. In mode 2, it runs as a 9-bit timer instead. There is a multitude of other options, such as CTC (Clear Timer on Compare) mode, which we’ll be using to generate a variable frequency for our next project.
The 1 << CS00 stands for ‘Clock Select’ and lets us pick a prescaler. Essentially, we’re choosing our PWM frequency. We're using the fastest frequency available, which doesn’t have the prescaler applied. If were to use 2 << CS00, we would get a frequency 8 times slower, all the way up to 5 << CS00 for 1024 times slower! This would work great for much bigger mechanical devices like driving a heater or garden irrigation pumps.
In practice, the un-scaled PWM clock oscillates around 3kHz, so a 1024-scaled clock would result in a 3Hz PWM signal.
ADMUX = 2<<MUX0;
ADCSRA = 1<<ADEN | 3<<ADPS0;
}
This code is responsible for setting up our Potentiometer input. Normally, for simplicity’s sake on an Arduino Uno, we use the “pinMode()” and “analogRead()” functions to set up the analog signal reading functionality. The ATtiny10 requires that we manually initialise and handle running the ADC (Analog-to-Digital Converter). The first line of the code above selects which port we wish to run the ADC on, in our case PB2 or Pin 2. ADMUX stands for Analog-to-Digital Multiplexer, which selects a specific port to be routed to the inbuilt ADC.
The next line enables the ADC (1 << ADEN) and then sets an ADC sampling frequency, similarly to the Clock Select (CS00) in the above setup code. We want a target frequency of around 125KHz for the most accurate ADC sampling according to the datasheet.
void loop () {
ADCSRA = ADCSRA | 1<<ADSC;
while (ADCSRA & 1<<ADSC);
OCR0A = ADCL;
The great thing about register programming is that it’s usually possible to accomplish what you’re looking for with some clever register manipulation. Virtually all of the functionality we want is accomplished in these three lines of code. Since it’s accomplished in hardware, there is also very low overhead and runs very fast.
The first line of code tells the ADC to start reading a value from the input. ii.e. writing a ‘1’ to the ADSC. However, it does not complete instantly. It takes 13 clock cycles to complete an ADC read, so we can’t just keep running our code. While it’s still converting, ADSC will remain at one, then return to zero when it’s done. That’s the purpose of the very compact while loop - it waits until the ADSC register returns to 0 and then continues.
The last line is the most crucial. It’s writing the read value of the ADC to the OCR0A register. As the datasheet says, “The Output Compare Registers contain a 16-bit value that is continuously compared with the counter value (TCNT0)”. This timer value is constantly being incremented from the timer we set up during the setup function.
The practical behaviour of this code is perhaps explained best visually!
You can see that as the timer increments, we’re constantly comparing the potentiometer signal and the current timer value. In this instance, we’re slowly turning up the potentiometer to generate an analogue signal.
Since the timer value acts as a sawtooth signal, when comparing these values we end up with a variable duty-cycle wave. Note that we’re running the timer register in 8-bit mode instead of 16-bit mode, so the 8-bit input from the ADC can easily be compared.
if(ADCL > MIN_VAL) {
TCCR0A = 2<<COM0A0 | 1<<WGM00;
} else {
TCCR0A = 2<<COM0A0 | 0<<WGM00;
}
}
This chunk of code is optional, but it essentially prevents small potentiometer values from activating the PWM output. This way, we can get a clean ON-OFF behaviour instead of very small signals temporarily activating PWM. The MIN_VAL value is the threshold for this, which we usually leave at 5 or so.
And done! Just hit the upload button like any other Arduino sketch, and with any luck, it should upload just fine.
Project 3: Wireless Music Box
Additional Parts Required: | Jaycar | ||
---|---|---|---|
1 x Arduino Compatible Dual Ultrasonic Sensor Module | XC4442 | ||
1 x Passive Buzzer | - | ||
1 x BC547 Transistor* | ZT2152 | ||
1 x 1kΩ Resistor | RR0596 |
* Quantity required, may only be sold in packs. A breadboard and prototyping hardware is also required.
If you’ve ever wanted to make award-winning music without lifting a finger, then you might be out of luck. But if you can be bothered to wave about a hand, we’ve got you covered (maybe without the award-winning part though).
This project uses an Ultrasonic sensor and an analog buzzer to generate sound frequencies based on the distance the sensor detects. The closer an object is, the higher pitch it is, and the further away it is, the lower pitch it is. It’s awesome that we can pull this off with such a tiny little microcontroller!
First up, we added the power wires to the breadboard and programming shield. This needs a 5V and Ground wire inserted to both sides, bridged by jumper wires. You could use Dupont or solid-core wires for this.
Different piezo-buzzers draw different amounts of current, but we think it’s better erring on the side of caution and not stressing out the ATtiny by driving it. We’re using a standard NPN transistor to drive the buzzer, so all the ATtiny has to do is turn on the transistor. Since our frequency output is on Pin 1 (PB0), we hooked up the gate of the BC547 to the pin through a simple 1kΩ resistor. Don’t forget to hook up the collector of the transistor to 5V.
After that, it’s simply a matter of dropping in our buzzer. It’s important that a passive buzzer is used, which are typically shorter than active ones and don’t have a resin-coated underside. A passive buzzer won’t generate its own frequency, unlike an active one. We want to generate our own frequency to make different sound pitches! To connect it, just hook the positive side to the emitter and the negative side to Ground.
Nearly there! We inserted our ultrasonic module facing horizontally outwards. Pretty much all the standard ultrasonic modules we’ve seen will work with this project, so just pop it into the breadboard and connect its 5V and Ground wires.
Both the Trigger and Echo pins need to be connected to the ATtiny on pins 3 and 4 respectively. There are no extra pullups or dividers necessary for these data lines as they should be 5V compatible. Awesome, let's jump into the code!
The Code
We can now write the code necessary for controlling our cute musical instrument, affectionately dubbed the DIYODE-monica.
#include <util/delay.h>
uint32_t countTimer0 = 0;
void setup() {
DDRB = 1 << PB0 | 1 << PB1 | 0 << PB2;
First up, we’re importing the ‘delay’ library which will give us access to the “_delay_ms()” and “_delay_us()” functions. We always need to be careful when importing libraries on the ATtiny10, we’re going to run out of space pretty quickly! The other thing that will use a lot of memory is a bunch of variables defined in large data types like ‘uint32_t’. This variable alone uses an eighth of the available RAM, even though it’s only four bytes in size.
Next, in our setup function, we’re setting up three pins for external access of electronic components. The first pin (1 << PB0) is an output for the Buzzer, the second (1 << PB1) is an output for the ultrasonic trigger pulse, and the third is (0 << PB2) is an input to listen for the echo pulse.
Speaking of which, how is that going to work? Let’s ignore the buzzer and register technicalities for one moment and discuss how these ultrasonic modules common across Arduino kits work. To calculate the distance between it and the nearest object directly in front of it, it transmits an ultrasonic wave when it receives a trigger signal and times how long it takes to return. It returns a pulse on the echo line, with a duration equivalent to the measured time.
Since we don’t have a lot of resources to use external libraries on our ATtiny10, we need to make our own system for interfacing with the ultrasonic sensor.
TCCR0A = 1 << COM0A0 | 3 << WGM00;
TCCR0B = 3 << WGM02 | 2 << CS00;
}
Like our previous project, we’re using inbuilt Timer0 to facilitate creating a frequency on the buzzer. In this case, we’re using a prescaler for the Clock Select register of 2, which is a 64 clock division. This gives us a range of around 200Hz to 3KHz, which is good enough for experimenting with musical notes.
void loop() {
PORTB &= ~(1 << PB1);
_delay_us(20);
PORTB |= (1 << PB1);
_delay_us(12);
PORTB &= ~(1 << PB1);
_delay_us(20);
In our main loop, we’re first creating a pulse sequence to send to the ultrasonic sensor’s Trigger pin. This code might look overly complicated, but really, we’re just sending a digital pulse that matches our Ultrasonic timing diagram. PORTB &= ~(1 << PB1) turns off Pin 1 and PORTB |= (1 << PB1) turns it back on. So, if you read through this code, we should have a digital pulse that lasts exactly (or close to) 12 microseconds.
while (!(PINB & (1 << PB2)));
countTimer0 = 0;
while (PINB & (1 << PB2)) {
countTimer0++;
}
OCR0A = countTimer0;
_delay_ms(60);
}
This is where the real business happens. Since there is a short space in time when the ultrasonic sensor is polling data, we need to wait until it sends back its data. That’s the purpose of the one-line while loop - just keep looping as long as Pin 2 (the echo pin) reads low.
Once it does go high, we know the Ultrasonic sensor has started sending the distance signal so we need to listen to how long it is. The ‘countTimer0’ variable starts at zero and continues to increment so long as the echo pulse is high.
For the keen-eyed code-readers out there, you may notice we’ve missed out on a chunk of code in this while loop - a delay! Without it, ‘countTimer0’ goes up ridiculously fast, right? That’s totally correct, but our ATtiny10 spends enough time-consuming processing cycles reading and writing to the ‘countTimer0’ variable that it doesn’t end up going that high. It also shouldn’t have any problems with overflowing, since the variable is 32-bit. Doing this on a fast Arduino or Raspberry Pi might produce erroneous results though.
Once the echo signal finally goes low, we end up with a ‘countTimer0’ variable containing a value proportional to the object distance. After that, we just write to the OCR0A timer the value we got. The buzzer now will receive a PWM signal thanks to the hardware timer.
One quick quirk with this code is, currently, we are only dealing with proportional distances rather than any real units. If you want to actually calculate the distance an object is away, bear in mind that sound travels in air at 343m/s = 0.343m/µs. Using this info, just get the number of microseconds that the ultrasonic module replied with, and convert it to a distance. (You’ll also need to divide it by two since the sound has to make a return trip from the object!)
Since we’re fairly new ourselves to fancy register programming, please let us know if there is a more efficient way of using the inbuilt timer and interrupts to accomplish this functionality. We’d love to see how optimised this code can get.
Our ultrasonic music box works well. Waving a hand in front of the sensor can rapidly change the pitch of the buzzer, however, due to the noise of the Ultrasonic sensor, the notes jump about a bit. Using a flatter object like a sheet of paper helps smooth out the response.
One possible improvement with this project is to purposely round the distance values to real note frequencies. It would take some experimentation to squeeze frequencies of real notes onto the ATtiny10, but we’re sure it can be done. This would make the musical box sound much more pleasant.
Troubleshooting
If you’re used to experimenting with regular-sized Arduino boards, the ATtiny10 can get up to more mischief than usual! Here are a couple of common issues with this board.
Many of the problems related to working with this board arise when dealing with uploading the code. Because there are so few GPIO pins available, the ICSP (In-Circuit Serial Programming) pins directly interfere with the pins we use for our inputs and outputs. The first thing you should try is to remove as much unnecessary hardware as possible. For example, we had an issue with uploading while a potentiometer was connected to Pin 3. Since this pin also functions as the SCK line while programming, the potentiometer was obviously pulling up or down the SCK signal.
Since the ATtiny10 doesn’t have hardware Serial and would be difficult to emulate over software, ATtiny10’s are annoying to debug because most logic is done on the register level. This is where the datasheet comes in handy, which is very well-written. If you run into programming or logic issues, it’s indispensable for this chip.
Unfortunately, it’s not impossible to brick these ATtiny10s. An accidental misconfiguration of the upload fuses may prevent it from listening to any further programming commands, or even booting at all. A high-voltage programmer may help to revive it, but considering the cost (or lack thereof) of these microcontrollers, it may be worth just swapping it out for another.
Where To From Here?
By far the most difficult part of working with the cute little processing factory that is the ATtiny10 is working with the registers. Just because flash memory is so limited, it’s not practical to use the fully-featured Arduino functions like “digitalWrite”, so you need to get creative with how you use your processing resources.
If you do some light Sunday afternoon reading and dig into the ATtiny10’s datasheet, there is enough information to get your head around manipulating the registers to your desire. We’ve seen multiple ATtiny10 projects on the internet, including charplexing up to 10 LEDs, creating ridiculously tiny pairs of LED earrings, and even an electronic dice that’s smaller than an actual die.
But, we’re betting you’ve already got some ideas to make with the ATtiny10. So grab a USBasp programmer, build our programming shield, and get to making some seriously tiny maker gadgets!