avr-gcc, Eigenartiges Schleifenverhalten

Gast #6191202
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
Gast #6191240
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
Gast #6191250
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?
Moderator Persönliche Seite #6191255
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.
Gast #6191299
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?).
Gast #6192134
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
Moderator Persönliche Seite #6192145
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
Gast #6192158
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!
#6192247
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.)
Gast #6192280
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
#6192283
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.
Moderator Persönliche Seite #6192440
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.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren