Forum: Compiler & IDEs avr-gcc, Eigenartiges Schleifenverhalten


Announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
von mrleop (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Hallo,
ich benütze Debian (10/buster) mit avr-gcc um meinen ATMEGA16 am STK500 
zu programmieren.
Soweit so gut, meine Programme compilieren und werden richtig 
programmiert und ausgefuehrt.
Jeoch habe ich jetzt ein Problem mit einer eigentlich simplen Schleife
void loopFunction(void)                                                                                                                                               
{
    char *mystring = "ABCDEF";
    int i = 0;
    for (i = 0; i<3; i++) {
        LCD_Write(mystring[i]);
    }
}

Diese funktioniert, und (schlussendlich wird ABC am LCD Display 
angezeigt). Wenn ich jedoch die Abbruchbedingung erhoehe:
void loopFunction(void)                                                                                                                                               
{
    char *mystring = "ABCDEF";
    int i = 0;
    for (i = 0; i<4; i++) {
        LCD_Write(mystring[i]);
    }
}

werden am LCD Display zwar 4 Zeichen ausgegeben, aber nur Mist, bzw. das 
Symbol zum Wert 0xFF.
Da der Code mMn eigentlich richtig aussieht, hab ich ins generierte 
Assembler reingeschaut, via
avr-gcc -mmcu=atmega16 -Wall --save-temps -Os -c lcd.c

Fuer 3 Buchstaben, wird die Schleife einfach weg-optimiert, was auch 
sinnvoll aussieht:
loopFunction:
/* prologue: function */
/* frame size = 0 */
/* stack size = 0 */
.L__stack_usage = 0 
    ldi r24,lo8(65)
    call LCD_Write
    ldi r24,lo8(66)
    call LCD_Write
    ldi r24,lo8(67)
    jmp LCD_Write
    .size   loopFunction, .-loopFunction  

Aber schraeg wird es dann bei 4 Buchstaben, da scheint, als ob die Werte 
gar nicht mehr geladen werden:
.LC0:
    .string "ABCDEF"
    .text

loopFunction:
    push r28
    push r29
/* prologue: function */
/* frame size = 0 */
/* stack size = 2 */
.L__stack_usage = 2
    ldi r28,lo8(.LC0)
    ldi r29,hi8(.LC0)
.L12:
    ld r24,Y+
    call LCD_Write
    ldi r24,hi8(.LC0+4)
    cpi r28,lo8(.LC0+4)
    cpc r29,r24
    brne .L12
/* epilogue start */                                                                                                                                                      
    pop r29
    pop r28
    ret

Irgendeine Idee, was ich falsch mache?
Zu meinem System:
Debian 10 mit Buster repo:
avr-gcc version 5.4.0
avr-libc 2.0.0+Atmel3.6.1-2

MfG, Danke

von Philipp K. (philipp_k59)


Bewertung
0 lesenswert
nicht lesenswert
Versuch mal:
 LCD_Write(mystring);
 mystring++;

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Bewertung
2 lesenswert
nicht lesenswert
mrleop schrieb:
> da scheint, als ob die Werte gar nicht mehr geladen werden:

Aber nur, weil du offensichtlich keine wirkliche Idee vom AVR-Assembler 
hast.

Selbstverständlich werden die Werte geladen. Bis zu drei Zeichen hat der 
Compiler nur die Schleife entrollt, weil das nach seiner Berechnung die 
sparsamere Variante war.

von mrleop (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Danke für die schnelle Antwort, dann muss ich aber immer das erste 
Element aus dem String nehmen:
void loopFunction(void)
{
    char *mystring = "ABCDEF";
    int i = 0;
    for (i = 0; i<4; i++) {
        LCD_Write(mystring[0]);
        mystring++;
    }                                                                                                                                                                     
}


Es ist besser :D damit schaff ich 4 Zeichen richtig, ab 5 wieder das 
selbe Ergebnis.
loopFunction:
/* prologue: function */
/* frame size = 0 */
/* stack size = 0 */
.L__stack_usage = 0 
    ldi r24,lo8(65)
    call LCD_Write
    ldi r24,lo8(66)
    call LCD_Write
    ldi r24,lo8(67)
    call LCD_Write
    ldi r24,lo8(68)
    jmp LCD_Write
    .size   loopFunction, .-loopFunction 

Mit 5:
loopFunction:                                                                                                                                                             
    push r28 
    push r29 
/* prologue: function */
/* frame size = 0 */
/* stack size = 2 */
.L__stack_usage = 2 
    ldi r28,lo8(.LC0)
    ldi r29,hi8(.LC0)
.L12:
    ld r24,Y+
    call LCD_Write
    ldi r24,hi8(.LC0+5)
    cpi r28,lo8(.LC0+5)
    cpc r29,r24
    brne .L12
/* epilogue start */
    pop r29 
    pop r28 
    ret 
    .size   loopFunction, .-loopFunction

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Bewertung
0 lesenswert
nicht lesenswert
Allerdings wäre es wohl "netter", wenn du nicht nur einen Zeiger auf ein 
anonymes Array baust, sondern lieber das Array selbst referenzierst:
   char mystring[6] = "ABCDEF";

Noch besser wäre es am Ende, das auch noch in den Flash zu packen:
   char __flash mystring[6] = "ABCDEF";

von mrleop (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Jörg W. schrieb:
> Allerdings wäre es wohl "netter", wenn du nicht nur einen Zeiger
> auf ein
> anonymes Array baust, sondern lieber das Array selbst referenzierst:
>    char mystring[6] = "ABCDEF";

Damit habe ich leider das selbe problem.

> Noch besser wäre es am Ende, das auch noch in den Flash zu packen:
>    char __flash mystring[6] = "ABCDEF";

Dazu muss ich das array aber noch statisch und const definieren, oder? 
Anosten bekomme ich compiler errors.
void loopFunction(void)
{
    const static char __flash mystring[17] = "ABCDEFGH";
    for (int i = 0; mystring[i] != 0; i++) {
        LCD_Write(mystring[i]);
    }
}    

Aber wo ist dann der Fehler wenn ich einfach
char mystring[6] = "ABCDEF";
verwende?

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Bewertung
0 lesenswert
nicht lesenswert
mrleop schrieb:

> Dazu muss ich das array aber noch statisch und const definieren, oder?

Ja. Ist ja irgendwie logisch: Flash ist halt dauerhaft (static) und 
konstant (nicht zur Laufzeit änderbar, zumindest nicht direkt).

> Aber wo ist dann der Fehler wenn ich einfach
>
> char mystring[6] = "ABCDEF";
> 
> verwende?

Weiß ich nicht – jedenfalls liegt er nicht in den paar Zeilen 
Assemblercode, die du oben gepostet hast. Die sind OK. Es wird halt ein 
Indirektzugriff über das Y-Register benutzt statt der Direktoperanden, 
die die entrollte Schleife benutzt hat.

Irgendwas wird wohl mit deiner Linkerei nicht hinhauen, sodass die Daten 
deines Arrays nicht da gelandet sind, wo sie eigentlich sein müssten.

Um das aber debuggen zu können, müsstest du ein komplettes 
(einschließlich Makefile oder Linkerscripts etc.) minimales Beispiel 
posten statt der paar Zeilen Code.

von 2^5 (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Hm... mystring liegt ja im RAM. Für mich sieht es so aus, als ob bei 
Startup der Code fehlt oder fehlerhaft ist, der String Literale aus dem 
Flash ins RAM kopiert.

von foobar (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Der Kode in Ausgangsposting ist schon korrekt und sollte funktionieren. 
Folgendes fällt mir ein, warum es schief gehen könnte:

1) LCD_Write ist handgestrickter Assemblercode, der das 
Y-Register(R28/R29) vermanscht.

2) Interrupt-Routinen vermanschen das Y-Register.

3) Der Stack ist zu klein und läuft in den RAM-Bereich rein, in dem der 
String steht.

4) Das Datensegment ist zu groß und keiner hat's gemerkt.

5) Der Linker erzeugt Mist (falsches Script? Selbst gebastelt?).

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Bewertung
0 lesenswert
nicht lesenswert
2^5 schrieb:
> Für mich sieht es so aus, als ob bei Startup der Code fehlt oder
> fehlerhaft ist, der String Literale aus dem Flash ins RAM kopiert.

Das wäre jetzt auch meine erste Vermutung, daher die Frage nach einem 
komplette Minimalprojekt.

von mrleop (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Hi,
vielen Dank für die Antworten!
Ich hab mal versucht, mein Programm sehr minimal zu machen, um weitere 
Einfluesse zu eliminieren. Dazu hab ich auch das ganze LCD ansteuern 
rausgeschmissen, und lass stattdessen meine LEDs blinken.
#define F_CPU 1000000UL /* Clock Frequency = 1Mhz */
#include <util/delay.h>
#include <avr/io.h>
                                                                                                                                                                          
int main() {
    // portB is output, turn all LEDs off
    DDRB = 0xFF;
    PORTB = 0xF0;

    const static char __flash flashmystring[17] = "ABCDEFGH";
    char mystring[17] = "ABCDEFGH";
    for (int i = 0; flashmystring[i] != 0; i++) {
        if (mystring[i] == flashmystring[i]) {
            PORTB = ~PORTB;
            _delay_ms(200);
        }   
    }   
    while(1);
}

Das ganze steuere ich mit folgendem Makefile:
CC=avr-gcc
OBJCOPY=avr-objcopy
CFLAGS= -mmcu=atmega16 -Wall -O3 --save-temps

PROGRAMMER = avrdude
BOARD = stk500
MCU = atmega16
PORT = /dev/ttyUSB0

PFLAGS = -p $(MCU) -c $(BOARD) -P $(PORT)

rom.hex : lcd.out
    $(OBJCOPY) -j .text -O ihex lcd.out rom.hex
lcd.out : lcd.o
    $(CC) $(CFLAGS) -o lcd.out -Wl,-Map,lcd.map lcd.o
lcd.o : lcd.c
    $(CC) $(CFLAGS) -Os -c lcd.c
clean:
    rm -f *.o *.out *.map *.hex *.s *.i
program:
    $(PROGRAMMER) $(PFLAGS) -e
    $(PROGRAMMER) $(PFLAGS) -U flash:w:rom.hex     

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Bewertung
0 lesenswert
nicht lesenswert
mrleop schrieb:

> rom.hex : lcd.out
>     $(OBJCOPY) -j .text -O ihex lcd.out rom.hex

Da liegt der Hase im Pfeffer: du kopierst nur .text, damit gehen dir die 
Initialwerte für das Datensegment verloren, die stehen nämlich in .data.

Richtig wäre also -j .text -j .data.

Aber Empfehlung: lass den ganzen Zirkus mit der Hexdatei weg, und flashe 
direkt die ELF-Datei (die bei dir lcd.out heißt).

>     $(PROGRAMMER) $(PFLAGS) -e

Würde ich ganz weglassen, vorheriges Löschen ist beim Schreiben des 
Flashs implizit.

>     $(PROGRAMMER) $(PFLAGS) -U flash:w:rom.hex

Ersetze das beides durch:
   $(PROGRAMMER) $(PFLAGS) -U lcd.out

von mrleop (Gast)


Bewertung
1 lesenswert
nicht lesenswert
Jörg W. schrieb:
> Da liegt der Hase im Pfeffer: du kopierst nur .text, damit gehen dir die
> Initialwerte für das Datensegment verloren, die stehen nämlich in .data.
>
> Richtig wäre also -j .text -j .data.

Mhm, also ein klassisches Layer8 Problem.

> Ersetze das beides durch:
>    $(PROGRAMMER) $(PFLAGS) -U lcd.out

Damit funktioniert es einwandfrei :) Vielen Dank an alle die mir 
geholfen haben!

von Walter T. (nicolas)


Bewertung
0 lesenswert
nicht lesenswert
mrleop schrieb:
> Aber wo ist dann der Fehler wenn ich einfachchar mystring[6] = "ABCDEF";
> verwende?

Kein Platz für den Terminator.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Bewertung
0 lesenswert
nicht lesenswert
Walter T. schrieb:
> Kein Platz für den Terminator.

Da er die Schleife anderweitig terminieren lassen will, ist das egal.

Wenn man den Terminator haben will, schreibt man einfach
char mystring[] = "ABCDEF";

von Walter T. (nicolas)


Bewertung
0 lesenswert
nicht lesenswert
Jörg W. schrieb:
> Da er die Schleife anderweitig terminieren lassen will, ist das egal.

Hoppla. Tatsache. Ich hätte eine Compiler-Warnung erwartet, wenn ein 
6-Zeichen-Array mit einem 7-Zeichen Literal initialisiert wird.
    char mystring1[6] = "ABCDEF";
    char mystring2[6] = {'A','B','C','D','E','F','\0'};
Warum ist "ABCDEF" manchmal ein Literal mit 6 oder 7 Zeichen?
Nach welcher Regel ist ersteres nicht mit dem zweiten identisch (und 
wird vom Compiler mit "excess element" bemängelt) ?

(Wenn das hier zu Off-Topic ist, stelle ich die Frage an anderer Stelle 
nochmal.)

: Bearbeitet durch User
von Stephan (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Walter T. schrieb:
> Warum ist "ABCDEF" manchmal ein Literal mit 6 oder 7 Zeichen?

Das ist immer ein Array aus 6 Zeichen (char) wenn Du es so:
char mystring1[6]
 initialisierst.
char mystring2[6] = {'A','B','C','D','E','F','\0'};
ist immer ein Fall für die Syntaxprüfung ("too many initializer values")
VG, Stephan

von Carl D. (jcw2)


Bewertung
0 lesenswert
nicht lesenswert
Walter T. schrieb:
> Jörg W. schrieb:
>> Da er die Schleife anderweitig terminieren lassen will, ist das egal.
>
> Hoppla. Tatsache. Ich hätte eine Compiler-Warnung erwartet, wenn ein
> 6-Zeichen-Array mit einem 7-Zeichen Literal initialisiert wird.
>
>     char mystring1[6] = "ABCDEF";
>     char mystring2[6] = {'A','B','C','D','E','F','\0'};
> 
> Warum ist "ABCDEF" manchmal ein Literal mit 6 oder 7 Zeichen?
> Nach welcher Regel ist ersteres nicht mit dem zweiten identisch (und
> wird vom Compiler mit "excess element" bemängelt) ?
>
> (Wenn das hier zu Off-Topic ist, stelle ich die Frage an anderer Stelle
> nochmal.)

C-Compiler denken einfach: hätte der eine String gewollt, hätte er keine 
Länge angegeben.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Bewertung
0 lesenswert
nicht lesenswert
Walter T. schrieb:
> Warum ist "ABCDEF" manchmal ein Literal mit 6 oder 7 Zeichen?

Weil beides explizit durch den Standard gedeckt ist.
6.7.8 Initialization

[…]

Semantics
[…]
  14 An array of character type may be initialized by a character string literal, optionally
     enclosed in braces. Successive characters of the character string literal (including the
     terminating null character if there is room or if the array is of unknown size) initialize the
     elements of the array.

"if there is room of if the array is of unknown size"

"unknown size" heißt, dass das Array als "array[]" geschrieben worden 
ist.

: Bearbeitet durch Moderator
von Walter T. (nicolas)


Bewertung
0 lesenswert
nicht lesenswert
Jörg W. schrieb:
> 6.7.8 Initialization

Danke fürs finden!

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.