Forum: Compiler & IDEs avr-gcc, Eigenartiges Schleifenverhalten


von mrleop (Gast)


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
1
void loopFunction(void)                                                                                                                                               
2
{
3
    char *mystring = "ABCDEF";
4
    int i = 0;
5
    for (i = 0; i<3; i++) {
6
        LCD_Write(mystring[i]);
7
    }
8
}

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

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
1
avr-gcc -mmcu=atmega16 -Wall --save-temps -Os -c lcd.c

Fuer 3 Buchstaben, wird die Schleife einfach weg-optimiert, was auch 
sinnvoll aussieht:
1
loopFunction:
2
/* prologue: function */
3
/* frame size = 0 */
4
/* stack size = 0 */
5
.L__stack_usage = 0 
6
    ldi r24,lo8(65)
7
    call LCD_Write
8
    ldi r24,lo8(66)
9
    call LCD_Write
10
    ldi r24,lo8(67)
11
    jmp LCD_Write
12
    .size   loopFunction, .-loopFunction

Aber schraeg wird es dann bei 4 Buchstaben, da scheint, als ob die Werte 
gar nicht mehr geladen werden:
1
.LC0:
2
    .string "ABCDEF"
3
    .text
4
5
loopFunction:
6
    push r28
7
    push r29
8
/* prologue: function */
9
/* frame size = 0 */
10
/* stack size = 2 */
11
.L__stack_usage = 2
12
    ldi r28,lo8(.LC0)
13
    ldi r29,hi8(.LC0)
14
.L12:
15
    ld r24,Y+
16
    call LCD_Write
17
    ldi r24,hi8(.LC0+4)
18
    cpi r28,lo8(.LC0+4)
19
    cpc r29,r24
20
    brne .L12
21
/* epilogue start */                                                                                                                                                      
22
    pop r29
23
    pop r28
24
    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)


Lesenswert?

Versuch mal:
 LCD_Write(mystring);
 mystring++;

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


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)


Lesenswert?

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

Es ist besser :D damit schaff ich 4 Zeichen richtig, ab 5 wieder das 
selbe Ergebnis.
1
loopFunction:
2
/* prologue: function */
3
/* frame size = 0 */
4
/* stack size = 0 */
5
.L__stack_usage = 0 
6
    ldi r24,lo8(65)
7
    call LCD_Write
8
    ldi r24,lo8(66)
9
    call LCD_Write
10
    ldi r24,lo8(67)
11
    call LCD_Write
12
    ldi r24,lo8(68)
13
    jmp LCD_Write
14
    .size   loopFunction, .-loopFunction

Mit 5:
1
loopFunction:                                                                                                                                                             
2
    push r28 
3
    push r29 
4
/* prologue: function */
5
/* frame size = 0 */
6
/* stack size = 2 */
7
.L__stack_usage = 2 
8
    ldi r28,lo8(.LC0)
9
    ldi r29,hi8(.LC0)
10
.L12:
11
    ld r24,Y+
12
    call LCD_Write
13
    ldi r24,hi8(.LC0+5)
14
    cpi r28,lo8(.LC0+5)
15
    cpc r29,r24
16
    brne .L12
17
/* epilogue start */
18
    pop r29 
19
    pop r28 
20
    ret 
21
    .size   loopFunction, .-loopFunction

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


Lesenswert?

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

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

von mrleop (Gast)


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.
1
void loopFunction(void)
2
{
3
    const static char __flash mystring[17] = "ABCDEFGH";
4
    for (int i = 0; mystring[i] != 0; i++) {
5
        LCD_Write(mystring[i]);
6
    }
7
}

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

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


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
>
1
> char mystring[6] = "ABCDEF";
2
>
> 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)


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)


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


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)


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.
1
#define F_CPU 1000000UL /* Clock Frequency = 1Mhz */
2
#include <util/delay.h>
3
#include <avr/io.h>
4
                                                                                                                                                                          
5
int main() {
6
    // portB is output, turn all LEDs off
7
    DDRB = 0xFF;
8
    PORTB = 0xF0;
9
10
    const static char __flash flashmystring[17] = "ABCDEFGH";
11
    char mystring[17] = "ABCDEFGH";
12
    for (int i = 0; flashmystring[i] != 0; i++) {
13
        if (mystring[i] == flashmystring[i]) {
14
            PORTB = ~PORTB;
15
            _delay_ms(200);
16
        }   
17
    }   
18
    while(1);
19
}

Das ganze steuere ich mit folgendem Makefile:
1
CC=avr-gcc
2
OBJCOPY=avr-objcopy
3
CFLAGS= -mmcu=atmega16 -Wall -O3 --save-temps
4
5
PROGRAMMER = avrdude
6
BOARD = stk500
7
MCU = atmega16
8
PORT = /dev/ttyUSB0
9
10
PFLAGS = -p $(MCU) -c $(BOARD) -P $(PORT)
11
12
rom.hex : lcd.out
13
    $(OBJCOPY) -j .text -O ihex lcd.out rom.hex
14
lcd.out : lcd.o
15
    $(CC) $(CFLAGS) -o lcd.out -Wl,-Map,lcd.map lcd.o
16
lcd.o : lcd.c
17
    $(CC) $(CFLAGS) -Os -c lcd.c
18
clean:
19
    rm -f *.o *.out *.map *.hex *.s *.i
20
program:
21
    $(PROGRAMMER) $(PFLAGS) -e
22
    $(PROGRAMMER) $(PFLAGS) -U flash:w:rom.hex

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


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:
1
   $(PROGRAMMER) $(PFLAGS) -U lcd.out

von mrleop (Gast)


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)


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


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
1
char mystring[] = "ABCDEF";

von Walter T. (nicolas)


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.
1
    char mystring1[6] = "ABCDEF";
2
    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)


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:
1
char mystring1[6]
 initialisierst.
1
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)


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.
>
1
>     char mystring1[6] = "ABCDEF";
2
>     char mystring2[6] = {'A','B','C','D','E','F','\0'};
3
>
> 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


Lesenswert?

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

Weil beides explizit durch den Standard gedeckt ist.
1
6.7.8 Initialization
2
3
[…]
4
5
Semantics
6
[…]
7
  14 An array of character type may be initialized by a character string literal, optionally
8
     enclosed in braces. Successive characters of the character string literal (including the
9
     terminating null character if there is room or if the array is of unknown size) initialize the
10
     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)


Lesenswert?

Jörg W. schrieb:
> 6.7.8 Initialization

Danke fürs finden!

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.