Basics Programming ATtiny13
When you want to program a microcontroller like the ATtiny13 there are some basic things you need to know. I will discuss them here. If I forgot something, please post in the comments. This page is a tutorial slash reference page.
Please make sure you have downloaded the datasheet of the ATtiny13. You can download it from the Atmel website: download first link (176 pages)
The ATtiny13 has three types of memory. Flash memory, SRAM and EEPROM. The Flash memory is where you put the code for the microcontroller (like the HDD in a PC) and has a maximum of 1K Bytes. The ATtiny45 has 4K of memory. SRAM and EEPROM are like little boxes where the microcontroller works from and with, also called registers. You could compare those with the RAM in a PC. If you turn the microcontroller off the data in SRAM is gone, but Flash and EEPROM not. Why different types of memory? This is because of the differences in maximum rewrites, costs, speed and power consumption. Flash for example cannot handle that much rewrites as SRAM or EEPROM. More info on wiki.
A microcontroller works on 0101101001, called binary, and because this is hard to remember we make use of a programming language. For microcontrollers there are two main languages you can pick from; 'C' and 'Assembly'. There are some important differences between these two. The biggest difference is that C is more 'general', this means you can also run it, often with little adjustment, on other microcontrollers. Assembly will really only work on the specific microcontroller. The disadvantage of this more general approach of C is that the code is often unnecessary big(ger) than Assembly. Microcontrollers don't have that much memory so that could cause problems.
Which language is better is hard to say, some people prefer one above the other. I would suggest to know them both, this is because on the web you will be looking for example code and sometimes that is in Assembly and other times in C. In this tutorial I will first use Assembly (later on C), because I do think it will give a better understanding of how the microcontroller works.
Before we begin, it is important to know that the ATtiny works with 8-bit. This means that the zero's and ones that the microcontroller uses are stored in 8 numbers of only an 0 or a 1. Like so: 00000000 or 11111111 or any combination in between. This also means that the maximum real number/decimal is 256 (2 to the power 8). You could sometimes use two 8 bit registers in the memory for a 16 bit number (max 65536), but you often just work in the 0 to 256 range. Note that I say to and not including 256. You see, zero als counts for something. So to say it correctly, you can use the decimal numbers from 0 up to and including 255. Also for the microcontroller 255+1 = 0. Huh? Yeah, after you reached/got to 256 the microcontroller will start at 0 again, because it can't go higher. This starting over at 0 can be very useful for counters in delays, I will discuss that later on.
The 8-bit is also used to enable or disable certain parts of the microcontroller. Let's get the datasheet, go to page 56 and I'll show you what I mean. On this page you will see at the bottom this:
Often your first program will be flashing a LED and to let this work you need to make sure the microcontroller knows which pins will be used for sending the pulse/flash. To do this you need to change the DDRB register in the register of the chip, which *happens* to be an 8-bit register. It says 0 to 7, that is 8 in total of course. So when you make DDRB into 00000001, only DDB0 will be able to send the pulse. Often you write this as 0b00000001, where 0b stands for binary. What is DDB0? That is your PORTB pin, also in short that is PB. You can find your PORTB pin on your microcontroller in the datasheet on page 2 (or look here below). In this case it is PB0 because it is DDB0 and that is pin 5. This pin can also do other things as you can see, like PCINT0, I will discuss this another time.
To make a LED flash we are not done yet, we only told the microcontroller which pin will be used for sending, we are not sending anything yet. To do this we need to change another register in the microcontroller, the PORT B Data register. You can find this also on page 56. When you set this bit to 0b00000001, you will generate a signal/voltage across PB0, which is pin 5. If you change it back to 0b00000000 there will be no signal, so it is OFF. This is a start for a flash/blink program with a LED.
Okay, I'll post some Assembly code here and than explain what it does. (Please note that this code won't let your LED flash, because the delay time is too short.)
.include "tn13def.inc"
.def mp = r16
rjmp main
main:
ldi mp,0b00001000 ;this is PB3
out DDRB,mp
loop:
LDI mp,0x00
OUT PORTB,mp
nop
LDI mp,0xFF
OUT PORTB,mp
nop ; this is a comment
rjmp loop
The include line is always there, it links to a file where the compiler (the one that changes Assembly to 011001, binary) gets information to translate everything correctly. In this case it's "tn13def.inc", which is for the ATtiny13.
The microcontroller has 32 registers in the memory (if you don't believe me see page 1 in datasheet) and these can be used to store numbers (remember max is 255). These registers are actually called general purpose registers, because they are quickly accessible for the microcontroller. These 32 registers also each have a name, like R6 or R20, but because this isn't very clear when making a program, you can define a different name. That is where the ".def mp = r16" stands for. It says, mp is register 16. This part of the code does nothing yet, it is only for the compiler again. Why r16 and not r1 you might ask. This is because r1 up to and including r15 are different type of registers. Later on I'll tell what the difference is.
rjmp main, ah yes, our first real command to the microcontroller. These things are actually called instructions and the microcontroller knows around 120 of them. There is .pdf or an online page from Atmel where you can find them all, here, I suggest to bookmark this page. One thing you need to remember right away is that an instruction takes up a 'cycle'. This cycle is like a 'tick' or 'tock' from the microcontroller and takes time to perform. How much time? This depends on the speed of the microcontroller.
The factory default is 9.6 Mhz for the ATtiny13 (ATtiny2313 is 8Mhz). You can change this speed. Hz means how often something happens in a second. So 1 Hz means, for example, 1 tick in a second. 3 Hz means 3 ticks in second. This also means that: 1 second / 3 ticks = 0.333... seconds for each tick. So now get our 9.6 Mhz (where M stands for Million), we get 1 / 9600000 = 1.04e-7 seconds for one cycle. That is very fast, to be precise, 0.10416 µs (micro seconds, more info) for a cycle. Too bad this is not correct. The microcontroller also has a divider, which is default on 8. So 9.6Mhz / 8 = 1.2 Mhz. Which is 1 / 1.2 = 0.83333... µs for one cycle.
So this 'rjmp main' instruction takes 0.83 µs to perform, right? No sorry, if you look at the online instructions page you can find that it takes 2 cycles to perform, which mean 1.67 µs. These timings might look unimportant now, but when you for example want to make an infrared remote, they need to be very precise, which you luckily now can calculate.
On the online instruction page you might also have found what the instruction 'rjmp main' does, it is very simple. It jumps to the label 'main', which is a bit further down in the program code and looks like "main:". Oh wait, it is right below it... haha. That's no really useful, because the code when run by the microcontroller goes from top to bottom, so it automatically gets to label main. You can safely remove rjmp main. However you will see later on that rjmp is a very useful instruction.
"main:" is just a label where the microcontroller can jump to when running the code. You can name these labels how you like, but only use normal words. In this main label we will find the line "ldi mp, 0b00001000". You might by now recognize the 8 bit here with the 0b (for binary) in front of it. The ldi means to store the value after the comma (,) to the register mp (which is actually r16). So after this line is run by the microcontroller (which takes one cycle, 0.83 µs) your little microcontroller memory register has a new value of 00001000. Yeah!
The next line is where we meet our DDRB again. "out DDRB, mp" means put the value mp (00001000) in DDRB register. As you notice, instruction for ATtiny go from right to left. The right value or register (after the comma ,) goes to the left register. Why not directly put 00001000 into DDRB? Like "ldi DDRB, 00001000"?, because it just cannot work. Your little microcontroller first needs to 'know' this value before it can use it. 00001000 means DDB3, which is PB3, just check page 56 in the datasheet.
We arrived at the next label "loop". In this label we meet a new friend the ldi. However this time it's written as LDI, this is no problem for the compiler, you can use lower or higher case. What this line does is giving the register mp (r16) a new value of 0x00. What is that? The 0x means hex and stands for hexadecimal notation. Which is a bit too much to explain here. However I do want to mention that we now have had two ways of giving a value to a register. With binary and with hex. There is however a third one, simply with a number from 0 - 255. To convert a value from hex to decimal or the other way around, you can use the Windows calculator. Make sure that scientific option is turned on in the menu and after you entered a value (like 95) you press hex. Which gives you 5F. Cool, I just found out that it can do binary also. It is called bin on the calculator. Try it! Also try to change 255 and 256 (decimal) to binary and see what happens.
The next line "out portb, mp" makes the line before it more clearly. We are going to write the value of register mp (in binary, hex, decimal - 0b0000000, 0x00, 0) to PORTB. This means actually zero or that nothing will happen at all, haha. What a *great* instruction you might think, but this is required to make the LED blink. Turn on and off, this is the off part. If you see the next line "nop" and after that the other two instructions you will see that the PORTB will be put ON (high value). 0xff is 255.
The "nop" instruction is like what it sounds like, 'nope', 'nothing'. The microcontroller does nothing for a whole cycle. You wasted 0.83 µs of the microcontrollers time, al thought in timers it can sometimes be useful. Later on I will discuss a way to make a delay here instead of the nop and make the LED really blink.
The last nop has "; this is a comment" behind it. Whenever you use this character (;) everything behind it on the same line will be skipped by the compiler. Please use this often, if you don't comment your code and look at it again a month later on it won't make sense any more.
"rjmp loop" is the same as the "rjmp main" but now it is more useful, it will actually put the microcontroller in a loop by jumping to label loop, as you can see.
Readers that are paying good attention will notice that giving PORTB a value of 0xff (0b11111111 or 255) is a bit unnecessary. Only 0b00001000 is necessary, because this responds to port 3, which we also set in DDRB register. Well done!
Now let's replace those nop delays with a real delay that will give you a clear blink. To do this, we need to let the microcontroller do something else, for few (mili)seconds, when the LED is turned on or off to get a nice blink. We could place thousands of nop's in the code, but that is not really smart and takes way too much Flash memory. We will create another loop, but this time the microcontroller will just count. You need to place this code at the top, but under .def. Also you need to replace the nop's with rcall delay. Which brings me to quickly explaining the rcall instruction. What it does is the same as rjump, but it will return to where it jumped from after reading ret. However it won't work good, because now the microcontroller will start at delay, you need to add the line rjmp main back in the code. I think you can figure out where it needs to be put.
delay:
clr t1
clr t2
ldi temp, 100
delaywalk:
;dec t2 ;
;brne delaywalk ; uncomment these for longer delay
dec t1 ; 256 x 100 x 3 = 76800 cycles
brne delaywalk ; standard ATtiny13 clock is 9.6Mhz
dec temp ; 9.6Mhz/8 (divider) = 1.2Mhz a cycle
brne delaywalk ; total delay time = +/- 6 ms (with temp on 10)
ret ; go back to where you came from
clr is short for clear and all it does is clear the register to zero. But wait, what is t1 or t2? That's just a name I made up for a register, but I haven't defined it yet. So what you need to do is add the following line at the top of your code, under the other .def.
.def t1 = r17
.def t2 = r18
.def temp = r19
You can search the other instruction if you don't understand them, but I do want to tell you what happens in the delay code. First the registers are cleared and register temp gets a value of 100, next we go to label delaywalk and there we decrease the register t1 with one (not t2, because it's commented out). The register was cleared, which means it is zero and if you subtract one of zero you get 255. brne means 'branch if not equal' and that means if the previous instruction is not equal (or zero) it branches. Branches means it goes to the label defined after brne and that is delaywalk. The instruction dec takes 1 cycle and brne when true (not a zero as result) it will take 2 cycles, so a total of 3 cycles. This means in total 256 x 100 x 3 = 76800 cycles. With 0.83 µs per cycle that is 63.744 µs, that is 64 ms. Of course this is not completely correct, because ldi, out and rcall also take up cycles, but not that much time.
You can get the total Assembly code here, The Code
C
Work in progress...
January 1st, 2010 - 20:28
Wow! Thanks for good and easily understandable article. I’m looking forward to understand why the “rjmp main” is necessary
January 11th, 2010 - 09:15
First a question:
Why put delay: above main:? Is there an advantage to place the functions above the primary loop?
Second a correction:
This character “,” is a comma. This character “;” is a semicolon. References above referred to the comma as a semicolon and the reference to “this character” called a comma a semicolon.
Thank you for the lesson. My reading thus far is making the use of assembly language easier than that of C.
January 11th, 2010 - 16:33
Thanks for the reply.
I tried putting the delay under the main label and that did not work, so that is why I put it above it. Theoretically it shouldn’t bother where you put it, I guess.
January 22nd, 2010 - 23:37
Nice.. helps somewhat.. but im still a long way from being able to write the code I want. I know how to change some of the data sets in an exisiting prg I use, but want to write code to have my ATTINY 2313 do something similar but different than what it does now. I am currently running the SLM (brain Machine) from Mitch Altman(got from makezine.com).
Lots of fun and pretty cool. But what I need now is to change out the (tables?) with different data. I want to be able to set up specific led flash frequencies.. I dont know If this will all fit on one chip so may have to span the tables across several controllers and select the output”ranges” via a selector switch which would select differrent controllers for different ranges of flash rates.
I need to get my leds to flash at rates from 1hz – 600hz.
Probably would have one 2313 programmed with tables for say frequencies from 1-25hz or whatever fits and second chip from 26-50hz and so on. building and fabricating everything for me is rather a simple matter since at present I only need to flash two large leds. And adding a multipole selector switch to switch between controllers is easy too… Where I fall flat is in knowing how to program the chip /’s to do what I want..
if I can see the actual programming and where the data tables go etc for the first controller I might be able to manage subsequent controllers on my own.. Can anyone help me with this?
Thanks
January 23rd, 2010 - 18:48
Did you try the avrfreaks forums?
http://www.avrfreaks.net/index.php?name=PNphpBB2&file=index
February 10th, 2010 - 10:18
Great post! Make another one