Loading a program to the AVR microcontroller manually using Arduino as interface – Part 2

In part 1 we discussed some of the parts of the Attiny13A and got to a point where we would be ready to loading a program in to the microcontroller. (LINK TO PART 1)

In this part we will look at how to create a program.

If you are not really interested in how we got the binary code, I would suggest to jump to part 3 where we will actually load this code in to the Attiny13A. (ADD LINK TO PART X)

The scope of this code, will simply be the often used blink example, where we are tuning an LED on and off.

If we programmed this in the Arduino IDE, it could look something like this:

arduinocode_2arduinocode_3With ‘pinMode’ we set pin 3 as an output. With the void loop(), we have a never endong loop that keeps running the code below.

‘digitalWrite set’s the pin high, so pin 3 goes high, then delay for x miliseconds and then another ‘digitalWrite’ to set the pin low again.

So a few line of code would make this possible, now if we are to do the same in binary, we need to break this down and find the appropriate instructions on the microcontroller.

I have decided that I will first create the logic using assembler, as this is so close to the binary form we need for the eventual load and much easier to read (at least relative to binary code). The assembler instructions available for the AVR – can all be found here: http://www.atmel.com/images/Atmel-0856-AVR-Instruction-Set-Manual.pdf.

We will first look at how to program some of the more physical aspects of the microcontroller, like values for pin’s and using interrupts. After that we will look at the logic aspects of our program.

Here I will go through the following 4 type of tasks:

  • Set a pin as output
  • Set value on selected pin to ‘1’ or ‘0’
  • Make the program wait
  • Creating a loop where we keep running the program

Setting a pin as output

Setting the pin as output is controlled by the Data Direction Register, in this case for Port B (DDRB). In order for us to control this, we need to:

  1. Find the address of the Data Direction Register for Port B
  2. Find what bit in this register that controls if the pin is input or output

In the datasheet we find the following information for register DDRB:

Register AddressNameFurther details
0x17DDRBPage 56

The address os the DDRB register is 0x17 and we can find further details on page 56 – please see below:

Port B Data Direction Register

Bit76543210
Pin name--DDB5DDB4DDB3DDB2DDB1DDB0
Initial value00000000
Physical pin132765

We can see what bits control each of the pins, and we can also see what the initial value of the bits are.

So if we want to make pin 3 output, then we need set this to ‘1’, and since we are writing an entire byte, the byte we need to write here is: ‘0000 1000’.

So our task for the program is:

  • Write ‘0000 1000’ to DDRB

The assembler instruction for this is: OUT DDRB, register X  (register with value we want to load)

We only need to set this once, as we need this to be enabled for output for the entire time.

Set the value of the pin high ‘1’ and low ‘0’

The is controlled from the Port x Data Register, as we are working with Port B, it is the PORTB register – the address is 0x18 (you can check this in the datasheet – LINK TO DATASHEET) and we can find further details on PORTB on page 56 again.

From the datasheet we see that bit 4 is controlling PRTB4, so to set this high ‘1’, we need to write the byte ‘0001 0000’ to this address, when we want the LED to light up, and ‘0000 0000’ when we want to turn the LED off.

So here the programming task is:

  • First write ‘0001 0000’ to PORTB

  • after wait

  • Write ‘0000 0000’ to PORTB

The assembler instruction for this is: OUT PORTB, (register with – 0b00010000) for high, and

OUT PORTB, (register with – 0b00000000) for low.

In between these two commands, we have the wait statement.

Let the program wait

If we look at the possible instructions available for us in the Attiny13A, there is no direct wait x seconds or cycles instruction.

In order to perform a wait for a microcontroller there is basically 2 options;

  • Make the microcontroller perform a number of instructions that really do nothing but wasting time. Since we know how many clock cycles an instruction takes and we know how many clock cycles the microcontroler runs at, we can make some calculations of how many instructions would we need to run in order to ‘waste’ 1 second

  • The other option is to have something external that would indicate that a certain amount of time has passed – interrupts.

After reading up on both approaches I decided to try to use the interrupt approach. So we need to look at a few things on interrupts first.

Interrupts

Interrupts are external to the CPU and can be external to the microcontroller or we can use internal interrupts. For our purpose we will use the timer within the microcontroller to create the interrupt.

The timer in our microcontroller is just a counter, that counts (it will count 1 for every clock cycle), and as we are working with an 8-bit, we can only count from 0 – 255, before starting from zero again.

If we are running the microcontroller at 1 MHz (that is 1 million instruction, or counts, per second), then this will only takes us 256 nano seconds, to count from 0 to 255 – we would need to do this 1,000,000 / 256 = 3906 times to give us approxamately one 1 second.

Prescaling

To make this easier we are going to use an option for the AVR microcontroller’s to make the timer run slower than the CPU – this feature is called prescaling. So while the microcontroller still runs at 1 MHz, we can force the timer to run at a fraction of this speed.

If we look at the datasheet for the ATtiny13A (table 11-9) we see the following prescaling options:

Bit 2Bit 1Bit 0Description
001clock IO
010clock IO / 8
011clock IO / 64
100clock IO / 256
101clock IO / 1024

From the above we see that we have 4 options for making the timer run slower – the 4 options are:

  • 1/8 of the clock speed → 8 clock cycles will make the timer count 1 time

  • 1/64 of the clock speed → 64 clock cycles will make the timer count 1 time

  • 1/256 of the clock speed → 256 clock cycles will make the timer count 1 time

  • 1/1024 of the clock speed → 1024 clock cycles will make the timer count 1 time

If we use the 1/1024 option, that would mean that the counter only count 1 up, for every 1024 clock cycles.

So using this math:

  • 1 Mhz processor = 1 million clock cycles per second

  • Timer only count 1 for every 1024 clock cycles

  • For the timer to count from 0 to 0 again (255 + 1 count) that would now take:

    • 256 counts * 1024 clock cycles = 262,144 clock cycles

  • 262,144 clock cyles approxamately ¼ of a second

From the above that gives us that we would use up approximately ¼ of a second to count from 0 – 255. If we do this 4 times, that would give us close to the 1 second delay we are looking for.

The register where we configure this is TCCR0B at address 0x33:

Bit 2 1 0
Name CS02 CS01 CS00
Initial value 0 0 0

Bit 3 to 7 are not relevant for our purpose.

The assembler code: LDI, TCCR0B, 0b0000 0101

That gives us the next task – how do we keep track of how many times the counter have counted from 0 to 255?

Overflow flag and interrupts

When the timer hit’s the 255, it will throw an overflow flag – so if we count how many times we get this overthrow flag, we know that after 4 times, we have 1 seconds. One option would be to always poll for this overflow flag, but the other option, the one used here, us to have the overflow flag create an interrupt request – and this is when our program will add one to how many times we have seen the overflow flag.

When the CPU receives our interrupt request, the running program will finish it’s current instruction and then perform a jump to the address where the code associated with the interrupt exists.

So our task is to create the configuration for the interrupt triggered by the timer. This has 2 steps:

  1. Make the timer send an interrupt request

  2. Let the microcontroller know what to do when the interrupt happens

Enabling the timer to send the overflow flag is done by setting bit 1 (‘0000 0010’) in the TIMSKO (Timer/Counter Interrupt Mask Register), see in the datasheet in section 11.9.6.

The assembler code for this: LDI TIMSKO, 0b00000010.

Know when the timer overflows (that is at the point where the counter goes from 255 to 0), the AVR will look in the table of interrupt vectors (table 9-1) if any address is present – if yes jump to this address.

So we need to place a jump to our code that reacts to the overflow in address 0x0003 (binary 0000 0011′) – IN THE PROGRAM CODE. One interesting feature is that if we let the overflow flag trigger this interrupt, it will automatically be reset. As a result we do NOT need to write any code that reset this overflow indicator.

Please note, that here we are asked to set a value in the programming address space, not an register. That creates the necessity to avoid writing any of our program code in this specific address.

The assembler code: LDI 0b00000011, xxxx.

In summary we have so far:

  • Found the register where we need to set the pin we plan to use (pin 3) as an output pin.

  • Found the register where we can control the value of this pin (high/low)

  • Found the register where we can make the timer run at a slower speed than the CPU

  • Found the register where we can enable the timer

  • Found the address where we can insert the address of the program to run when an interrupt happens

Now it is time to look at some of the logic we want to create in the program.

Since we know that in order for us to measure 1 second, we need to track that the overflow flag from the timer has done this 4 times, we need logic to keep track of the number of interrupts.

The program will have to do something similar to this:

  1. set a variable to 4

  2. subtract one for each interrupt

  3. if variable is 0, then change the output pin to other state (if pin is ‘1’ then change to ‘0’ and visa versa)

  4. If not, then go back to sleep

  5. Then go back to step 1

Set a variable
Setting a variable in assembler can be done with the LDI command, and we will use register 16 (R16) to hold this variable:

The assembler code: LDI R16, 4 /* we load 4 in to register R16

Subtracting from variable

The command DEC wil subract 1 from the value given.

The assembler code: DEC R16 /* subtract 1 from current value in register R16

If then test

The assembler code: CPI R16, 1 /* Compare if R16 = 1

The assembler code: BRLT (address) /* If R16 lower than 1, then jump to address

For this we use branching instruction, BRLT, Branch Lower Than. This test is done again the result after the execution of the CPI instruction.

We need to supply this command with the actual address to jump to, and here we can not use the logical names that would otherwise be available in assembler.

For our program we will have the make the address calculation manually.

Main program

The main program is going to be simple;

The assembler code: SLEEP /* This will make the CPU stop processing and wait for the interrupt.

The assembler code: JMP address /* This will be the instruction we return to after the subroutine called by the interrupt.

Using the SLEEP command will also save power, but is not the main concern here of course.

Putting all of this together in to one program

We have 3 main parts:

  1. Set selected registers with needed values

  2. Main program

  3. Rutine called for every interrupt that controls the light

Set selected registers with needed values

setup:

ldi r16, 8 ; Setting bit 3 high

ldi r17, 20 ; load 4 for count down to register R17

ldi r18, 0 ; used for setting pin 3 low

ldi r19, 8 ; used for setting pin 3 high

out DDRB, r16 ; loading R16 to PORTB – set pin 3 as output

ldi r16, 0 ; set register R16 to 0

out PORTB, r16 ; set to pin 3 low

ldi r16, 5 ; set R16 to 000 0101

out TCCR0B, r16 ; load R16 to timer, prescaling = 1024

ldi r16, 2

out TIMSK0, r16 ; Enable timer overflow interrupt

sei ; enable interrupts glbally

Main program

In our case the only thing we want the main program to do is sleep

when not interrupted by the timer.

main:

sleep ; makes the microcontroller sleep

rjmp main ; jumps to main

SLEEP /* Will put the CPU in the microcontroller to sleep and wait for interrupt

Rutine called for interrupt

This where we control if the LED should be switched on or off as well as performing this switch.

count: ; When interrupt, call this rutine

dec r17 ; subtract 1 one Register 17 (starts at 4)

cpi r17, 1 ; Test if Register 17 is equal to 1

brlt change ; If Register 17 lower than 1, from test above jump to change

reti ; if not, then just return to where the interrupt was called from

change:

; First test what current value for output is

in r16, PORTB ; move current port status to R16

cpi r16, 4 ; test if pin 3 high

breq chglow ; if above test tru, then jump to change to low

out PORTB, r19 ; change to high

ldi r17, 12 ; set counter again, this time for 3 seconds

reti

I created the above code in Atmel Studio, and tested it in the simulater. The next step will be to convert the above program to binary code. This we will do in part 3.