Aus RN-Wissen.de
Version vom 24. April 2006, 23:10 Uhr von SprinterSB_alt (Diskussion | Beiträge) (Aufsummieren in einer Schleife)

Wechseln zu: Navigation, Suche
Laderegler Test Tueftler Seite

Ein wichtiges Merkmal eines Compilers ist die Güte des erzeugten Codes. Immerhin will man seine Hardware optimal nutzen, und die geschriebenen Programme sollen möglichst wenig Laufzeit brauchen und möglichst wenig Speicher – also RAM und Flash – belegen.

Ein Vergleich der erzeugten Codes ist jedoch nicht einfach, denn ein Problem kann bereits innerhalb ein und der selben Programmiersprache auf sehr unterschiedliche Art und Weisen formuliert oder gelöst werden.

Dieser Artikel versucht ansatzweise einen Codevergleich weit verbreiteter AVR-Compiler anhand sehr einfacher Aufgaben, die "geradeaus" und ohne Umschweife programmiert wurden.

Ein Vergleich der Programmierung von Hardware-Komponenten und Peripherie wie Timer-, UART- oder I2C-Module scheint dabei weniger interessant, denn obwohl die Codes zum Steuern dieser Komponente in unterschiedlichen Sprachen recht verschieden aussehen, werden sie doch auf die selben Maschinen-Codes abgebildet, die sich im wesentlichen auf das Setzen und Lesen von Registern (SFRs) reduzieren. Neben diesen für jedes Programm essenziellen Abschnitten, besteht ein Programm aber zum großen Teil aus hardwareunabhängigen Aufgaben wie Registerverwaltung, Funktionsaufrufen, Schleifen, Abfragen, Zuweisungen, Parameterübergaben, Abfragen, Arithmetik, etc.

Interessanter erscheint ein Vergleich einfacher Aufgaben, die erkennen lassen, wie gut ein Compiler in der Lage ist, die Ressourcen eines Mikrocontrollers zu nutzen bzw. zu schonen.

Rahmenbedingungen

avr-gcc
Die Assembler-Ausgaben wurden mit der Optimierungsstufe "auf Größe optimieren" (-Os) für einen ATmega8 erstellt. GCC-Version war 3.4.x. Andere Optimierungsstufen haben wenig bis keinen Einfluss auf den erzeugtenCode. Codes für andere Controller der ATmega-Familie unterscheiden sich praktisch nicht vom ATmega8-Code.
Bascom

Summe der ersten n Zahlen

Berechnet wird die Summe der ersten n Zahlen:

[math] \operatorname{sum}(n) \,=\, \sum_{k=1}^n k \,=\, 1 + 2 + \ldots + n [/math]

Die Zahl n wird als 16-Bit Zahl angegeben und das Ergebnis als 16-Bit-Zahl berechnet. Ein eventueller Überlauf wird nicht beachtet.

Der Code wird jeweils als eigene Funktion implementiert, um Abhängigkeiten vom umliegenden Code zu vermeiden.

Für diese Berechnung gibt es mehrere Möglichkeiten.

Aufsummieren in einer Schleife

Quellcodes:

avr-gcc
unsigned int 
sum_n_loop (unsigned int n)
{
   unsigned int sum = 0;
   unsigned int i;

   for (i=n; i > 0; i--)
      sum += i;
	
   return sum;	
}
 
BASCOM

Declare Function Sum_n_loop(byval N As Integer) As Integer

Dim X As Integer

'Start
   X = Sum_n_loop(10)
End

' -----

Function Sum_n_loop(byval N As Integer) As Integer
   Sum_n_loop = 0
   While N > 0
      Sum_n_loop = Sum_n_loop + N
      Decr N
   Wend
End Function

Compilat:

avr-gcc
00000000 <sum_n_loop>:

unsigned int 
sum_n_loop (unsigned int n)
{
   unsigned int sum = 0;
   0:	ldi	r18, 0x00
   2:	ldi	r19, 0x00
   unsigned int i;

   for (i=n; i > 0; i--)
   4:	sbiw	r24, 0x00
   6:	breq	.+8   ; 0x10
      sum += i;
   8:	add	r18, r24
   a:	adc	r19, r25
   c:	sbiw	r24, 0x01
   e:	rjmp	.-12  ; 0x4
	
   return sum;	
}
  10:	movw	r24, r18
  12:	ret
 
BASCOM


;---------------------------------------------------------
;   Function Sum_n_loop(byval N As Integer) As Integer
;---------------------------------------------------------
L_0x00B4:

;---------------------------------------------------------
;   Sum_n_loop = 0
;---------------------------------------------------------

	LDI	r24,0x00    ; clear
	LDI	r25,0x00    ; 
	LDD	XL,Y + 2    ; return value addr (softstack)
	LDD	XH,Y + 3
	ST	X+,r24      ;  
	ST	X,r25

L_0x00C0:

;---------------------------------------------------------
;   While N > 0
;---------------------------------------------------------
	LDD	XL,Y + 0  ; argument addr (softstack)
	LDD	XH,Y + 1
	LD	r16,X+    ; load r16:r17
	LD	r17,X
	CPI	r16,0x00   ; LOW  <> 0 ?
	LDI	r21,0x00   
	CPC	r17,r21    ; HIGH <> 0 ? 
	BRLT	L_0x00D6   ; branch lower  ->function exit
	BREQ	L_0x00D6   ; branch equal  ->function exit
	JMP	L_0x00DA   ; continue
L_0x00D6:
	JMP	L_0x0102   ; jmp function exit

L_0x00DA:
;---------------------------------------------------------
;      Sum_n_loop = Sum_n_loop + N
;---------------------------------------------------------
	LDD	XL,Y + 2   ; summ_n_loop 
	LDD	XH,Y + 3
	LD	r16,X+     ; into r16:r17
	LD	r17,X
	LDD	XL,Y + 0   ; N
	LDD	XH,Y + 1
	LD	r20,X+     ; into r20:r21
	LD	r21,X
	ADD	r16,r20    ; add      r16, r20
	ADC	r17,r21    ; add+cy   r17, r21
	LDD	XL,Y + 2   ; 
	LDD	XH,Y + 3
	ST	X+,r16     ; store sum
	ST	X,r17
;---------------------------------------------------------
;      Decr N
;---------------------------------------------------------
	LDD	XL,Y + 0
	LDD	XH,Y + 1
	CALL	L_0x0114
;---------------------------------------------------------
;   Wend
;---------------------------------------------------------
	JMP	L_0x00C0 ; reenter loop
;---------------------------------------------------------
L_0x0102:
	RET      ; end function
;---------------------------------------------------------
; library function : decrement integer
;---------------------------------------------------------
L_0x0114:
	LD	ZL,X+
	LD	ZH,X
	SBIW	ZL,0x0001
	ST	X,ZH
	ST	-X,ZL
	RET

Remark: Das reine Register-rechnen (GCC) macht Bascom nicht. Er arbeitet immer im SRAM , brav mit Load & Store. (PicNick)

Berechnung mit rekursiver Funktion

Quellcodes:

avr-gcc
unsigned int 
sum_n_rekursiv (unsigned int n)
{
   if (n == 0)
      return 0;

   return n + sum_n_rekursiv (n-1);	
}
 
BASCOM

???

Compilat:

avr-gcc
sum_n_rekursiv:
   push r28
   push r29
   movw r28,r24
   sbiw r24,0
   breq .L13
   sbiw r24,1
   rcall sum_n_rekursiv
   add r24,r28
   adc r25,r29
.L13:
   pop r29
   pop r28
   ret
 
BASCOM

???

Berechnung durch Formel

Quellcodes:

avr-gcc
unsigned int 
sum_n_formel (unsigned int n)
{
   return n*(n+1) / 2;
}
 
BASCOM

???

Compilat:

avr-gcc
sum_n_formel:
   mul r24,r24
   movw r18,r0
   mul r24,r25
   add r19,r0
   mul r25,r24
   add r19,r0
   clr r1
   add r18,r24
   adc r19,r25
   movw r24,r18
   lsr r25
   ror r24
   ret
 
BASCOM

???

Interrupt-Routinen

Auch diese Beispiele machen nicht viel. Das erste zählt nur eine 16-Bit Variable hoch, das zweite macht nichts weiter, als ein Funktionsaufruf.

Eine Variable hochzählen

Quellcodes:

avr-gcc
#include <avr/io.h>
#include <avr/signal.h>

int volatile count;

SIGNAL (SIG_OVERFLOW0)
{
   count++;
}
 
BASCOM


Compilat:

avr-gcc
__vector_9:
   push __zero_reg__
   push __tmp_reg__
   in __tmp_reg__,__SREG__
   push __tmp_reg__
   clr __zero_reg__
   push r24
   push r25

   lds r24,count
   lds r25,(count)+1
   adiw r24,1
   sts (count)+1,r25
   sts count,r24

   pop r25
   pop r24
   pop __tmp_reg__
   out __SREG__,__tmp_reg__
   pop __tmp_reg__
   pop __zero_reg__
   reti
 
BASCOM


Eine Funktion aufrufen

Quellcodes:

avr-gcc
#include <avr/io.h>
#include <avr/signal.h>

extern void foo();

SIGNAL (SIG_OVERFLOW1)
{
   foo();
}
 
BASCOM



Compilat:

avr-gcc
__vector_8:
   push __zero_reg__
   push __tmp_reg__
   in __tmp_reg__,__SREG__
   push __tmp_reg__
   clr __zero_reg__
   push r18
   push r19
   push r20
   push r21
   push r22
   push r23
   push r24
   push r25
   push r26
   push r27
   push r30
   push r31

   rcall foo

   pop r31
   pop r30
   pop r27
   pop r26
   pop r25
   pop r24
   pop r23
   pop r22
   pop r21
   pop r20
   pop r19
   pop r18
   pop __tmp_reg__
   out __SREG__,__tmp_reg__
   pop __tmp_reg__
   pop __zero_reg__
   reti
 
BASCOM


Sortieren mit Bubble-Sort

Zum Abschluss noch ein komplexeres Beispiel: Ein Array mit 8-Bit-Werten soll mit Bubble-Sort der Größe nach sortiert werden.

Im Array wird nach dem größten Wert gesucht und dieser ans Ende getauscht. Danach macht man den Teilbereich, in dem man das Maximum sucht, um 1 kleiner, bis man fertig ist. Die größten Zahlen wandern wie Blasen nach oben, daher der Name Bubble-Sort für diesen Sortier-Algorithmus.

Variablen:

  • n Array-Größe
  • a das Array
  • i Größe der Teilbereichs
  • j Laufvariable durch den Teilbereich

Quellcodes:

avr-gcc
#include <limits.h>
#include <inttypes.h>

void 
bubble_sort (uint8_t n, char * a)
{
   uint8_t i;

   // Bereich schrumpft von n auf 2
   for (i = n; i >= 2; i--)
   {
      char max = CHAR_MIN;
      uint8_t j, j_max;

      // im Bereich nach MAX suchen
      for (j = 0; j < i; j++)
      {
         if (a[j] >= max)
         {
            // MAX und Pos. merken
            j_max = j;
            max   = a[j_max];
         }
      }

      // MAX ans Ende tauschen
      a[j_max] = a[i-1];
      a[i-1]   = max;
   }
}
 
BASCOM


Compilat:

avr-gcc
bubble_sort:
   cpi  r24,2    ; i >= 2
   brlo .L12
   movw r26,r22  ; $6 = a
   add  r26,r24  ; $6 += i
   adc  r27,__zero_reg__
   sbiw r26,1    ; $6--
.L10:		 ; {  i
   ldi  r20,-128 ;  max = -128
   ldi  r19,0    ;  j = 0
   cp   r19,r24  ;  j < i
   brsh .L14
   movw r30,r22  ;  $3 = a
.L9:		 ;  {  j
   ld   r18,Z+   ;   $4 = * $3++
   cp   r18,r20  ;   $4 >= max
   brlt .L7	     {
   mov  r21,r19  ;    j_max = j
   mov  r20,r18  ;    max = $4
.L7:		     }
   subi r19,-1   ;   j++
   cp   r19,r24  ;   j < i
   brlo .L9
.L14:		 ;  }  j
   movw r30,r22  ;  $5 = a
   add  r30,r21  ;  $5 += j_max
   adc  r31,__zero_reg__
   ld   r18,X    ;  $7 = * $6
   st   Z,r18    ;  * $5 = $7
   st   X,r20    ;  * $6 = max
   subi r24,1    ;  i--
   sbiw r26,1    ;  $6--
   cpi  r24,2    ;  i >= 2
   brsh .L10
.L12:		 ; }  i
   ret
 
BASCOM


Die Codes wurden etwas nachkommentiert und -formatiert, um besser den Zusammenhang mit der Quelle durchblicken zu können.

Siehe auch


LiFePO4 Speicher Test