I have been trying to organize some of my code as I enjoy writing in assembler. So many times I have made timer loops in various projects I decided to make some common routines I use.
I originally started with parallax basic stamp and the Basic stamp editor. Te common commands I would use to start with are Pause and High and LOW. Here is how I implemented them in AVR assembler.
To implement a command I used a macro to enable passing of variables. A Macro simply inserts code into your program like a shortcut key. If the macro has three lines of code for example, you will have three lines of code for every instance you use the macro. Macros allow you to load setup information into various registers before a subroutine call. It is a way of passing variables akin to the C language where you enclose a variable in brackets separated by commas.
Instead of writing for example
We can put the code inside a macro and name the macro like this:
In addition we can use the @ sign to allow variable data to be inserted by the macro assembler.
Now we can simply use the command
And this will insert the following code
What if temp is being used in your main program? If we want to have many small routines like this we don’t want to use up our finite amount of registers for variables. We can store the variable on the stack before we use it in the routine and then restore the variable when we have finished using it.
The commands PUSH and POP allow us to do this. The stack must be setup in the main program before using it. Typically it is set to the end of memory and as you put variables in it it points to the next lower memory location. Taking them off simply moves the pointer back up the stack.
Subroutine calls such as “rcall delay” also use the same stack. When a subroutine is called the return address is placed on the stack and the stack pointer is decremented to point to the next location that is unused. If you PUSH something on the stack you must POP it back off before executing the RET statement or your data will become the next instruction location instead of the address pushed their by the rcall statement.
The code here is for the TINY13. Many other processors can use the same code and some can’t like the tiny11, it only has a three level deep stack and would crash. Your processor speed as well as the clock DIV fuse plays an important part in setting up the correct timing. I am using the internal oscillator at 9.6 MHZ . I am using the timer in CTC mode. Clear Timer on Compare. When an interrupt is triggered the timer is cleared, i.e. TCNT0 is loaded with zero by the hardware so you don’t need to load it with an initial 0. The OCR0A is the compare value, this is set to a value that when equal to TCNT0 will trigger the interrupt. Remember TCNT0 is being incremented by the HARDWARE not anything in your program, it’s automatic once the correct setup is in place.
Reading the datasheets has always been fun. I am going to have you jump around with me from page to page to figure out what needs to be done to set up the timer in CTC mode.
Lets start with looking at the sidebar bookmarks in the full datasheet PDF file of 175 pages you can download from ATMEL or avrfreaks. Looking down the list we see 8-bit Timer/Counter0 with PWM
We have an overview starting on page 57 and going all the way down to page 73 for the last of the information regarding this timer.
Lets jump to page 72 Timer/Counter Register – TCNT0
Here we see at the top a continuation of the bit descriptions of the previous page. Next is the description of four different registers (memory BYTES within the processor) they are:
TCNT0 The counter that hardware increments
OCR0A One of the compare registers
OCR0B The second compare register
TIMSK0 The interrupt mask that tells the timer to start working.
These NAMES are located in the TINY13DEF.INC file we include at the top of our .ASM file.
We will be putting a value in OCR0A and changing just one BIT to 1 in TIMSK0. every time we use the timer. But first we need to set up the timer the way we want to use it.
Skip back up to page 68. We see TCR0A timer control register. It has 6 different bits we can set to do many different things. We don’t want to do any waveforms or output anything on our pins so we skip table 26 compare output mode and table 27 a different kind of output mode and look at the next page. Again skip table 28 and 29 and 30. Now we are on page70, Drilling down we read in table 32 modes of operation and see of the 8 modes to choose from the CTC mode in number 2 and we need to set bit WGM01 (skip back and look at page 68 again to see the BYTE description if TCCR0A and see that BIT 1 is the WGM01 we need to set.
Ok how do we set it?
We load a register with the number that has that bit set and use the OUT command.
Using the BIT names like this will set the correct bits.
ldi temp, (1<<wgm01)|(0<<wgm00) ;Select CTC mode by setting WGM01 to 1
Whew. Now we need to set up our prescaler. Table 33 shows us the bits to set. CS01, CS01 and CS00
There are eight different settings here. Six of them for setting a prescale and two for setting an external clock source. Lets do the math to find out what we need to do.
The processor speed:
At 9.6mhz we have 9,600,000 clock cycles in one second.
We want to interrupt only 1000 times per second so lets divide 9,600,000 by 1000
9,600,000 / 1000 = 9,600
So if we let 9,600 clock cycles go by we will have one millisecond of time.
With an 8 bit memory variable we can only count from 0 up to 255 so we need to slow down the timer hardware so it only increments the timer after a larger number of clock cycles instead of every clock cycle. Because 0 is a number we have actually 256 numbers in a byte.
Let’s divide 9,600 by 256 to see how much we need to slow it down
9,600/ 256 = 37.5
If we could use a prescale of 37.5 we would be able to trigger an interrupt every time the timer ran over 255 to give us our 1 millisecond interrupt. But we cant so we have to do something else.
We need to select the next larger prescale number than 37.5 and that would be 64. So we look at the table and see bits CS01 and CS00 need to be set to set a prescale of 64.
We then look at our numbers again and take 9,600 and divide by 64, we get:
9,600 / 64 = 150
So that fits nicely within the 0 to 255 boundry we have on our 8 bit timer counter. Now all we have to do is trigger the interrupt every time it counts up to 150 and that will be one millisecond.
Let’s set the bits for the prescale like this:
ldi temp,(0<<cs02) | (1<<cs01) | (1<<cs00) ;Select Prescale CS01 and CS02 bits are set to 1.
Ok that’s done. Now we need to set the global interrupts on and then let our subroutines turn on and off the timer interrupt bit located on page 73. Here we want to set the OCF0A bit to turn on the timer so it starts counting. To prevent any other interrupts between the load instruction and the out instruction I turn off global interrupts. This may not be needed but I am not sure, I am sure someone will point it out though and we will edit this page.
Macros HIGH and LOW
I decided to just make four macros to turn on and off any of the four port pins I typically use, eight actually on and off commands instead of passing a variable of the port number because it is a simple one line set or reset a bit.
So to set PB3 high you just type HIGH3 to set it low use LOW3
To use the subroutines in your program just include the macros.inc file with the command .include "macros.inc" (put the file in your project file, when you do a build it will show up under Included files)
Copy and paste the subroutines Delay1ms and delayxms and put the 6 lines of setup code in your interrupt setup area of your .ASM program.
Remember the PAUSE command will only use constant data. You can not use PAUSE Variable
You could set up a macro to handle that using the mov command instead of the LDI command but you would need to specify two 8 bit register variables. In that case you could just do this:
If you have two more variables as a high and low combination to make a 16 bit number called HighV and LowV you could:
rcall delayxms ;call delay directly
If you want to use that, and save both temp and temp1 from being overwritten you could add push and pops to save them.
You can also call delay1ms directly without any setup to delay 1ms
That's it for today. Hope the non technical way I have tried to explain it works out. If I have any major gaffs here please point them out.