Learn how to choose the right microcontroller for your next project.
Learn about the various architectures,Programming,Software Tool chains and programmers available for micro controller architectures like MSP430,PIC,ATMEL AVR ,8051 etc
seen from China

seen from United States
seen from Congo - Kinshasa
seen from Kazakhstan
seen from United States
seen from United States
seen from Brazil
seen from Canada
seen from Uzbekistan
seen from United States
seen from United States
seen from Oman
seen from Argentina

seen from United States

seen from United Kingdom
seen from United States
seen from South Korea
seen from Algeria
seen from Türkiye
seen from Singapore
Learn how to choose the right microcontroller for your next project.
Learn about the various architectures,Programming,Software Tool chains and programmers available for micro controller architectures like MSP430,PIC,ATMEL AVR ,8051 etc
avr-gcc, inline assembly, local and global variables (part 2)
In my previous post I described some of the nice features of gcc inline assembly.
In this episode I want to better explain the access to local variables.
Say we need to implement these functions:
static void c_func() { uint8_t volatile a;
a = 1; }
static void b_func() { uint8_t volatile a;
a = 1; }
#define COUNT 10 static void a_func() { static uint8_t volatile local_static = COUNT; static uint8_t volatile b_or_c = 0
if (- -count == 0) { count = COUNT; b_or_c ^= 1; if (b_or_c) b_func(); else c_func(); } }
If we use the same approach we saw last time, we could start with:
static void a_func() { static uint8_t volatile count = COUNT; static uint8_t volatile b_or_c = 0;
asm volatile ( "subi %[cnt], 1\n" "brne 1f\n" "ldi %[cnt], %[CNT]\n" "ldi r16, 1\n" "eor %[bc], r16\n" "breq 2f\n" "call b_func\n" "rjmp 1f\n" "2:\n" "call c_func()\n" "1:\n" : [cnt] "+r" (count), [bc] "+r" (b_or_c) : [CNT] "I" (COUNT) : "r16" ); }
[cnt] and [bc] represents the read-write ("+r") variables. [CNT] represents the constant ("I") value defined by the macro. "r16" is the clobbered register.
Let's see the generated code:
d2: 0f 93 push r16 d4: 90 91 00 01 lds r25, 0x0100 ; 0x800100 <__data_start> d8: 80 91 02 01 lds r24, 0x0102 ; 0x800102 <__data_end> dc: 91 50 subi r25, 0x01 ; 1 de: 49 f4 brne .+18 ; 0xf2 <a_func+0x20> e0: 9a e0 ldi r25, 0x0A ; 10 e2: 01 2d ldi r16, 1 e4: 80 27 eor r24, r16 e6: 19 f0 breq .+6 ; 0xee <a_func+0x1c> e8: 0e 94 5e 00 call 0xbc ; 0xbc <b_func> ec: 02 c0 rjmp .+4 ; 0xf2 <a_func+0x20> ee: 0e 94 53 00 call 0xa6 ; 0xa6 <c_func> f2: 90 93 00 01 sts 0x0100, r25 ; 0x800100 <__data_start> f6: 80 93 02 01 sts 0x0102, r24 ; 0x800102 <__data_end> 102: 0f 91 pop r16 104: 08 95 ret
First, the clobber instruction translated into a push/pop of r16. This is good, so the content of r16 will be preserved. That was not necessary though: as function a_func() is a C called function, registers r2-r17 and r28-r29 can be freely used. This is very well referenced in Atmel Application Note AT1886[1].
Then, the compiler correctly generates (on our behalf) lds instructions to load the variables into registers r24 (for variable b_or_c) and r25 (for variable count), and the corresponding sts instructions are at the end of the function. In the meanwhile, either b_func or c_func are called. Let's see the disassembly of those functions:
000000a6 <c_func>: a6: cf 93 push r28 a8: df 93 push r29 aa: 1f 92 push r1 ac: cd b7 in r28, 0x3d ; 61 ae: de b7 in r29, 0x3e ; 62 b0: 81 e0 ldi r24, 0x01 ; 1 b2: 89 83 std Y+1, r24 ; 0x01 b4: 0f 90 pop r0 b6: df 91 pop r29 b8: cf 91 pop r28 ba: 08 95 ret
static void __attribute((used)) b_func() { bc: cf 93 push r28 be: df 93 push r29 c0: 1f 92 push r1 c2: cd b7 in r28, 0x3d ; 61 c4: de b7 in r29, 0x3e ; 62 uint8_t volatile a; a = 1; c6: 81 e0 ldi r24, 0x01 ; 1 c8: 89 83 std Y+1, r24 ; 0x01 } ca: 0f 90 pop r0 cc: df 91 pop r29 ce: cf 91 pop r28 d0: 08 95 ret
Both functions modify register r24. Therefore the value that will eventually be stored into b_or_c won't be the expected one.
So, how can we do?
An easy solution is to push and then pop the values of those variables. Like this:
asm volatile ( "subi %[cnt], 1\n" "brne 3f\n" "ldi %[cnt], %[CNT]\n" "ldi r16, 1\n" "eor %[bc], r16\n" "push %[cnt]\n" "push %[bc]\n" "breq 2f\n" "call b_func\n" "rjmp 1f\n" "2:\n" "call c_func\n" "1:\n" "pop %[bc]\n" "pop %[cnt]\n" “3:\n” : [cnt] "+r" (count), [bc] "+r" (b_or_c) : [CNT] "I" (COUNT) : "r16"
The compiler will reuse the registers already allocated for variables count and b_or_c:
c2: 90 91 01 01 lds r25, 0x0101 ; 0x800101 <count.1534> c6: 80 91 00 01 lds r24, 0x0100 ; 0x800100 <__data_start> ca: 91 50 subi r25, 0x01 ; 1 cc: 69 f4 brne .+26 ; 0xe8 <a_func+0x26> ce: 9a e0 ldi r25, 0x0A ; 10 d0: 01 e0 ldi r16, 0x01 ; 1 d2: 80 27 eor r24, r16 d4: 9f 93 push r25 d6: 8f 93 push r24 d8: 19 f0 breq .+6 ; 0xe0 <a_func+0x1e> da: 0e 94 56 00 call 0xac ; 0xac <b_func> de: 04 c0 rjmp .+4 ; 0xe4 <a_func+0x26> e0: 0e 94 4b 00 call 0x96 ; 0x96 <c_func> e4: 8f 91 pop r24 e6: 9f 91 pop r25 e8: 90 93 01 01 sts 0x0101, r25 ; 0x800101 <count.1534> ec: 80 93 00 01 sts 0x0100, r24 ; 0x800100 <__data_start> f0: 08 95 ret
This is necessary, as explained in [1], because it's assembly code calling a C function, and therefore it needs to save/restore registers r18-r27, r0 and r31.
Further improvements
It is quick to see that there's no easy way to force the sts instructions to happen. In case you want to do that, you may need to code them manually. This is how is done:
asm volatile ( "lds r15, %[cnt] "subi r15, 1\n" "sts %[cnt], r15 : [cnt] "+m" (count) : : );
The special "m" (memory) constraint will tell the compiler to replace the %[cnt] parameter with the address of variable count.
[1] http://ww1.microchip.com/downloads/en/appnotes/doc42055.pdf
avr-gcc and a look at the generated code
I am working on a project based on ATMel MCUs, and this implies I’mwriting C code. Since I used to write assembly code with the 8051 and similar, I wanted to take a look at how the avr-gcc generates code, and especially at how it uses optimization techniques.
I’ll give you a couple of examples here.
First, is an easy one, and is related to instructions like
DDRD |= (1 << PORTB2) | (1 << PORTB3) | (1 << PORTB5)
or in general when you need to use bitwise operations on Data Registers and I/O ports. Well, for the above expression, the compiler is recognizing that we are working on I/O ports and generates just two instructions:
100: 84 b1 in r24, 0x04 102: 8c 62 ori r24, 0x2C
No left shifts and multiple ori. Takes the address in memory for DDRD (0x04), already offset in case the bootloader is on or not, and it is or’d with the precomputed string of shifts and ors. This would be roughly equivalent to the following C code:
*DDRD = 0x2C
Second, and quite more complex, is branch instructions optimization.
I have the following C code:
void M7219_sendNumber(uint8_t digit, uint8_t singledigitnum) { if ((digit < 1) ||(digit > 8)) return; if (singledigitnum > 9) return; sendData(digit, g_segNum[singledigitnum]); }
(Yes, you guessed it right: I am interfacing Maxim’s MAX7219 to drive 7-segments displays)
Let’s see the first “if” condition in the generated assembly code:
// if ((digit < 1) || (digit > 8)) return; 180: 9f ef ldi r25, 0xFF 182: 98 0f add r25, r24 184: 98 30 cpi r25, 0x08 186: 48 f4 brcc .+18
Let’s examine what’s happening:
// Load 0XFF (-1) in r25 (a generic I/O register) 180: 9f ef ldi r25, 0xFF // Add the digit to be compared (parameter digit sits in r24) 182: 98 0f add r25, r24 // cpi Rd, K is Compare Register with Immediate. It will set the // C(arry) flag if the absolute value of K is larger than the // absolute value of Rd; cleared otherwise 184: 98 30 cpi r25, 0x08 // Branch out if the C(arry) flag is set* 186: 48 f4 brcc .+18
The compiler is applying the following[1]
THEOREM. If a and b are signed integers and a ≤ b, then
a <= x <= b is equivalent to x-a <= b-a
Applying to our case, we want to test
1 <= digit <= 8
that is equivalent, for the above theorem, to
digit - 1 <= 7
which is the test executed by the above instructions.
This is all interesting and smart.
Basically, the test can be executed with just one branch instruction instead of two.
Branch instructions are costly because they break the instructions pipelining mechanism, the CPU needs to stall and cannot preload the next instruction until the branch is resolved in real time, therefore slowing down the program execution flow.
Unless the CPU implements some sort of Branch Prediction algorithms, but these 8-bit MCUs don't.
[1] Hacker’s Delight (2nd Edition), by Henry S. Warren, ISBN 0321842685
Configuring USBasp to work on Windows 7.Learn how to solve the “ could not find USB device "USBasp" with vid=0x16c0 pid=0x5dc “ error on Windows. Visit our tutorial here
Cuando queremos tomar las riendas de la programación de AVR en C una de las mejores parejas es “Eclipse IDE” mas “AVR-GCC” siendo estos dos libres y gratuitos, aunque claro esta que “AVR Studio” nos brinda un buen ecosistemas no siempre es el mejor sobretodo si no somos usuarios de win2.