Forum: Compiler & IDEs lokale Variable nimmt Übegabewert nicht an


von Hegy (Gast)


Lesenswert?

Hallo,

in meiner Funktion nimmt eine lokale Variable nicht den Wert eines 
Übergabeparameters an. Sieht so aus:
1
unsigned char crc16(volatile unsigned char *buf, unsigned char inout)
2
{
3
    unsigned char length;
4
  
5
>>  length = buf[1]; // lokale Var. soll den Wert von buf[1] annehmen, funxoniert überhaupt nicht!
6
    outstr0(length); // 1. Gegencheck, Wertausgabe über serielle Schnitte
7
    outstr0(buf[1]); // 2. Gegencheck, Wertausgabe über serielle Schnitte
8
  ...
9
}

Eingabewert *buf in die Fkt. crc16, Zahlen in dezimal:
buf[0] =   1
buf[1] =   3
buf[2] =  71
buf[3] = 194
buf[4] =  96

Der Gegencheck bringt folgendes Ergebnis :
length =   0
buf[1] =   3
obwohl in der 5. Zeile (>>) der Wert kopiert werden sollte.

Warum geht das beim 1. Mal nie, fragt

 Hegy

von Christoph _. (chris)


Lesenswert?

Der Fehler liegt nicht in dem geposteten Code. Wenn du eine Zuweisung 
schreibst, wird diese auch stets (semantisch) stattfinden, denn etwas so 
simples wird kein C-Compiler fehlerhaft hinbekommen.

Hast du die Moeglichkeit das Programm zeilenweise zu debuggen?

von Curtis (Gast)


Lesenswert?

Wie+Wo legst Du denn buf an?

von Gast (Gast)


Lesenswert?

buf muss global sein.

von Hegy (Gast)


Lesenswert?

Die Var. *buf, 1. Parameter der crc16()-Fkt., ist doch nur ein Zeiger 
auf die Variable, von wo die Fkt. aufgerufen wurde. Im ganzen sieht das 
dann so aus.

Datei main.c
1
static volatile unsigned char pcbuf[MAXMSGLEN]; // globaler geht's nicht
2
3
int main(void)
4
{
5
  ....
6
  ret = crc16(pcbuf, IN); //  #define IN  2
7
  ....
8
}

Datei crc16.c
1
unsigned char crc16(volatile unsigned char *buf, unsigned char inout)
2
{
3
    unsigned char length;
4
  
5
>>  length = buf[1]; // lokale Var. soll den Wert von buf[1] annehmen, funxoniert überhaupt nicht!
6
    outstr0(length); // 1. Gegencheck, Wertausgabe über serielle Schnitte
7
    outstr0(buf[1]); // 2. Gegencheck, Wertausgabe über serielle Schnitte
8
  ...
9
}

Ich vermute mal, irgendwo ist irgendwas falsch definiert/deklariert.

von Karl H. (kbuchegg)


Lesenswert?

Hegy wrote:

> Ich vermute mal, irgendwo ist irgendwas falsch definiert/deklariert.

Mag sein. Aber nicht in den von dir dir geposteten Ausschnitten.
Die sind in Ordnung.

Kannst du nicht mal ein vollständiges, kompilierbares,
möglichst kurzes Beispiel zusammenstellen, welches den Fehler
immer noch enthält? Dann könnten mehr Leute das durch ihre
Compiler jagen und mal untersuchen, was da abgeht.

von Hegy (Gast)


Lesenswert?

Bau ich gerade >mal eben schnell< zusammen, mal kukken ob der Fehler 
dann auch noch da ist. Zuvor aber noch mal einen Auszug aus dem Listing, 
dem .lss-File, wo der Gegencheck der beiden Var. erfolgt. Auch sieht es 
m. M. nach richtig aus.
1
  ; hier wird die Fkt. crc16 aufgerufen
2
 e3a:   ldi  r22, 0x02  ; 2
3
 e3c:   ldi  r24, 0xAC  ; 172
4
 e3e:   ldi  r25, 0x02  ; 2
5
 e40:   call  0x131a <crc16>
6
 ....
7
8
 0000131a <crc16>:
9
 ;....push push push....
10
 133c:  ldd  r16, Y+1  ; 0x01
11
 133e:  mov  r24, r16
12
 1340:  call  0x5b6 <outstr0>
13
 1344:  ldd  r24, Y+1  ; 0x01
14
 1346:  call  0x5b6 <outstr0>
Wenn ich es richtig verstehe:
133c: lade direkt Reg. 16 mit dem Wert, wo Zeiger Y+1 draufzeigt
133e: kopiere (move) Inhalt von Reg. 16 nach Reg. 24
1340: Fkt. oustr0 ausführen, da wird auch das Reg. 24 gelesen & 
ausgegeben
1344: lade direkt Reg. 24 mit dem Wert, woder Zeiger Y+1 draufzeigt, 
also genau dasselbe wie in 133c, nur nicht den Wert in Reg. 16 laden 
sondern Reg. 24.
1346: und raus damit über UART0

und 2x verschiedene Werte....komich.....sehr sehr komich....

Aber jetzt bau ich mal ein Kurzproggi, >mal eben schnell<

von Hegy (Gast)


Angehängte Dateien:

Lesenswert?

Mit dem Kurzproggi >mal eben schnell machen< wird so keiner. Macht mehr 
Arbeit als alles andere. Also poste ich doch mal den gesamten Quellcode, 
es geht ja nur um einen Funktionsaufruf und die Parameterübergabe, das 
kann ich ja hier erklären, wo was zu finden ist.

Es geht nur um 3 oder 4 Zeilen in 2 Dateien, main.c und crc16.c

In main.c, Zeile 289 bis 292 steht:
1
289 // Hier geht es los. Wenn der Datensatz vollständig vom PC angekommen ist
2
290 // wird der CRC-Check gemacht. In pcbuf[] sind die Daten richtig enthalten
3
291 ret = crc16(pcbuf, IN); // crc16(adr of beginning buffer, direction);
4
292 // hier ist das Problem ENDE
Es wird also in Zeile 291 die Funktion mit richtigen Parametern 
aufgerufen. Diese Parameter sind:
buf[0] bis buf[4] = 1, 3, 71, 194, 96 (alles dez.)
IN ist per #define festgelegt (2)
Die Daten von buf[] bedeuten folgendes:
buf[0] = SW-Adresse (1)
buf[1] = Anzahl der noch folgenden Bytes incl. CRC16 (3)
buf[2] = Kommando ('G')
buf[3 & 4] = CRC16-Checksumme (194, 96)

Diesen String erwarte ich natürlich auch in der crc16()-Fkt., da nur der 
Zeiger auf den Datensatz übergeben wird. Und die Daten kommen auch an, 
nur klemmt es beim kopieren der Daten in eine lokale Variable.
Hier die Stelle im File crc16.c, Zeile 37 bis 40
1
37 // hier ist das ganz große Problem
2
38 length = buf[1]; // buf[1] (Datenlänge) nach length kopieren
3
39 outstr0(length); // auf UART0 ausgeben, am PC kukken, ob's richtig ist...
4
40 outstr0(buf[1]); // beim 1. Mal war length immer 0, buf[1] = 3, was richtig wäre

Der Rest, wie Variablendefinition usw. steht ja alles in den Header- 
bzw. im den C-Dateien.

Den Datensatz habe ich ohne Fehler durch den gcc gekricht, also 
funktioniert. Es gibt nur eine Warnung, wenn es eine gibt.

How2Probier aus:
ATmega162 mit 7,3728MHz, an UART0 eine Verbindung zum PC herstellen, 
Baudrate 57600.
Nachdem der ATmega einen Reset hatte, folgenden String über die serielle 
Schnitte zum ATmega schicken: Dezimal: 1, 3, 71, 194, 96 oder Hex: $01, 
$03, $47, $C2, $60.
Innerhalb von 0,nix Sekunden gibt der ATmega 12 Bytes auf der UART0 an 
den PC zurück. Eingelesen ist das:
1
000 003 001 003 071 194 096 000 255 255 050 003

Aufgedröselt nach Byte 1...12:
1
000 stammt aus Zeile 39, crc16.c, outstr0(length),
2
003 stammt aus Zeile 40, crc16.c, outstr0(buf[1]),
3
001 1. Byte des Befehlsstrings vom PC, SW-Adresse, Z. 57, crc16.c
4
003 2. Byte, Anzahl der noch kommenden Bytes, Z. 58, crc16.c
5
071 3. Byte, Befehl 'G', Z. 59, crc16.c
6
194 4. Byte, CRC16, Z. 60, crc16.c
7
096 5. Byte, CRC16, Z. 61, crc16.c
8
000 wieder das Längenbyte, immernoch 0, Zeile 62, crc16.c
9
255 da das Lägenbyte nicht ankommt, Fehler, Zeile 73/80, crc16.c
10
255 da das Lägenbyte nicht ankommt, Fehler, Zeile 74/81, crc16.c
11
050 Fehlercode an PC: EBADCRC (defs.h), Zeile 295, main.c
12
003 nochmal das Längenbyte aus dem String vom PC, Zeile 296, main.c

Wie gesagt, das Problem tritt immer nur beim 1. Mal auf. Also entweder 
bei einem x-beliebigen Befehl nach Reset oder wenn ein neuer Befehl 
kommt.

von Hermann-Josef (Gast)


Lesenswert?

Hallo Hegy,

wenn ich das richtig verstehe, dann ist pcbuf[] der RX-Empfangspuffer, 
daher volatile und die ISR(SIG_USART0_RECV) schreibt da bei jedem 
Interrupt rein.

Wenn der gerade zwischen der Zuweisung und dem ersten outstring() kommt, 
das ja naturgemäss etwas länger dauert, hat sich das Byte geändert, oder 
sehe ich da was nicht ?

Gruß
Hermann-Josef

von Hermann-Josef (Gast)


Lesenswert?

... sorry, meinte natürlich das 2. outstr0(), bis dahin darf es sich 
geändert haben.

HJ

von Hegy (Gast)


Lesenswert?

Deine Annahme ist fast richtig.
pcbuf[] ist der Empfangsspeicher für Zeichen vom PC, die über UART0 
eintrudeln. Bei jedem Zeichen wird die ISR(SIG_USART0_RECV) ausgelöst. 
Darin wird das gerade empfangene Zeichen in pcbuf[i] gespeichert, und i 
wird um eins erhöht, es wird also mitgezählt, wieviele Zeichen da sind 
und es wird verglichen, ob alle Zeichen da sind. Sind alle Zeichen da, 
wird das Flag uart0rcvcmpl = 1 (uart0rcvcmpl = UART0 receive complete) 
gesetzt. In main() wird zyklisch abgefragt, ob uart0rcvcmpl = 1 ist. 
Erst wenn das so ist, wird die crc16-Fkt. aufgerufen, von daher kann 
nichts zwischen den beiden outstr0() den Wert verändern. Es wäre dann 
auch ein zufälliger Fehler, aber der Fehler ist definitiv 
reproduzierbar.

Ich verstehe das immernoch nicht und habe schon soooo viel rumgeändert.

von Hegy (Gast)


Lesenswert?

Eine Möglichkeit wäre, ich hab's gerade versucht, das File crc16.c aus 
dem Makefile zu kikken und dafür die beiden darin enthaltenen Funktionen 
in das File main.c mit reinzunehmen. Jetzt geht es, aber das ist keine 
Problemlösung, es ist eine Behelfslösung.

Wegen der Verlagerung vermute ich jetzt, daß der Fehler nicht im Code, 
sondern eher im Makefile liegt bzw. bei den Compiler-/Linkeroptionen. 
Nur ne Vermutung! Vllt. ist ja doch im Code was faul.

von Peter (Gast)


Lesenswert?

>static volatile unsigned char pcbuf[MAXMSGLEN]; // globaler geht's nicht

Doch es geht noch globaler, wenn Du "static" weglässt! Mit "static" ist 
der Buffer nur in der Compile-Einheit "main.c" sichtbar. Ich denke aber 
nicht, dass dies das Problem ist, da Du den Pointer übergibst. Aber 
ausprobieren schadet ja nicht.

>unsigned char crc16(volatile unsigned char *buf, unsigned char inout)

Hierbei hab ich schon eher Zweifel: Ich fürchte Du deklarierst den 
Pointer als "volatile" nicht aber den Inhalt des Buffers. Versuche mal 
die "volatiles" wegzulassen und schau ob's dann tut. Arrays liegen 
nämmlich ohnehin stets im RAM und nicht in Registern, daher bringt das 
volatile für Arrays ansich nichts...

Hast Du verschidene Optimierungsstufen ausprobiert? Geht es ohne 
Optimieriung bzw mit -O0?

MfG Peter

von Hegy (Gast)


Lesenswert?

Das Weglassen von 'static' oder 'volatile' habe ich alles schon 
durchexerziert, alles keine Lösung. Manchmal ändert sich die 
Programmgröße, wenn ich irgendwo an einer Variable ein 'volatile' oder 
'static' zufüge (wird größer) aber in dem konkreten Fall war das nicht 
so.

Mit Optimierungsstufen habe ich noch nix gemacht, Standard war bisher 
immer -Os, s. Makefile.

von Karl H. (kbuchegg)


Lesenswert?

Das hat jetzt nichts mit deinem eigentlichen Problem zu tun,
aber lass um Gottes Willen das SREG in einer ISR in Ruhe.
Du brauchst, nein, du sollst in einer ISR nicht selbst cli
aufrufen und du sollst auch nicht selbst das SREG sichern.
Das alles macht der Compiler für dich.

Weiter gehts mit Code durchsehen ....

von Karl H. (kbuchegg)


Lesenswert?

Hmm. Im AVR-Simulator ist nichts zu sehen.
In crc16 haben alle Variablen den Wert den sie haben sollen.

von Hermann-Josef (Gast)


Lesenswert?

Vielleicht sollte man den USART0_RECV Interrupt während der Bearbeitung 
des pcbuf[] abschalten, ich habe immer noch ein ungutes Gefühl, dass das 
noch was ankommen kann.

Noch 2 Anmerkungen zum Code:
- Kommentare der Form
1
//****
2
//****
3
//****
  werden von einigen kontextsensitiven Editor (z.B. nedit interpretiert 
das als C-code, es ist aber C89 bzw. C++) als Beginn eines Kommentares 
gesehen, das Ende fehlt dann, der Compiler sieht das anders (= Codegröße 
bleibt gleich), es ist halt nur etwas verwirrend
- ich würde z.B. EnableSerial0Receiver so definieren und einsetzen:
1
#define EnableSerial0Receiver() { UCSR0B |= (1<<RXEN0) }
2
3
/* und einsetzen ... */
4
EnableSerial0Receiver();
Dann sieht das wie ein normales Sprachelement von C aus, d.h. diese 
Makros sind Funktionsmacros werden aber dann inline durch den Code 
ersetzt.

von Hermann-Josef (Gast)


Lesenswert?

War ein bisschen zu schnell, so muss es sein:
1
#define EnableSerial0Receiver() { UCSR0B |= (1<<RXEN0); }

Hermann-Josef

von Hegy (Gast)


Lesenswert?

Ob
1
#define EnableSerial0Receiver() { UCSR0B |= (1<<RXEN0); }
oder
1
#define EnableSerial0Receiver  UCSR0B |= (1<<RXEN0);
ist doch eigentlich egal. Der Präprozessor ersetzt jedes 
EnableSerial0Receiver mit UCSR0B |= (1<<RXEN0);
Erst wenn der Präprozessor die Syntx verstanden und umgebaut hat, kommt 
der Compiler zum Einsatz.

Die UART0 während der Bearbeitung von pcbuf[] abzuschalten bringt IMHO 
nix, weil nur ein relativ kurzer String von weniger als 30 Byte da 
reingeschossen wird. ISt der String raus, wartet die Anwendung für 0,5 
Sekunden max. auf eine Antwort (ACK/NAK & Fehlercode), die ebenfalls nur 
ein paar Byte lang ist. Ander beim dem Befehl, den ich oben schonmal 
erwähnt hatte. Da bekomme ich 145 Bytes an gesammelten Daten aus dem 
internen SRAM.

@Karl heinz
Besten Dank für deine Bemühungen. Hilft es, wenn ich mal das ELF, HEX 
oder was auch immer Output mal poste? Evtl. ist meine GCC-Karre ja im 
Eimer oder irgendwas drumherum. Ich benutze übrigens avr-gcc (GCC) 
4.1.0.

Die *.lss-Datei ist doch das in Assembler übersetzte C-Programm. Wenn 
ich mir den 7. Beitrag (oder 6. Antwort, 
Beitrag "Re: lokale Variable nimmt Übegabewert nicht an") ansehe, den Teil mit 
dem Assemblerlistung (Zeilen 133c bis 1346), so muß das eigentlich 
funktionieren, zumindest sollte der Simulator/Debugger auch sowas 
anzeigen.

Vllt. sollte ich die lokale Variable einfach mal global definieren, 
könnte helfen, aber ist auch nur eine Behelfslösung.

von Hermann-Josef (Gast)


Lesenswert?

Hallo Hegy,

bzgl. der Benutzung des Preprocessors:
Ja es ist hinsichtlich des Ergebnisses für den Compiler egal, ich habe 
aber schon C-Kurse erlebt bei denen mit diesem Hilfsmittel solange 
umdefiniert wurde bis es nach FORTRAN aussah, und sowas ist sicher nicht 
im Sinne der Erfinder. Alles was man mit dem Preprocessor einführt, 
sollte wieder nach C aussehen, um es eben für den geneigten Leser 
leichter verständlich zu machen. So ist
1
int var1;
2
3
/* viel Code dazwischen ... */
4
5
var1;
legal, erzeugt maximal eine Warnung ('statement without effect'), aber 
wer würde so etwas schreiben?

OK ist vielleicht Ansichtssache, zu dem eigentlichen Problem:
Tatsache ist, dass sich eine Varable an einer Stelle ändert, wo man es 
nicht erwartet. Das heißt, jemand/etwas schreibt da rein. Entweder durch
- einen Interrupt (race condition)
- durch einen Stackoverflow
- oder durch einen Schreibzugriff nach UDR0 während der Ausgabe.
Den Compiler würde ich als letztes verdächtigen.

Es schadet sicher nichts, mal testweise alle Interrupts in crc16() 
auszuschalten, um den 1. Fall sicher auszuschliessen.
Da das Programm ca. 52 % des RAMs belegt, würde ich das 2. recht sicher 
ausschliessen.
Zu outstr0() hätte ich noch den Vorschlag zu machen, erst dann ein Byte 
nach UDR0 zu schreiben, wenn der USART das auch erlaubt, sprich UDRE0 im 
UCSR0A auszuwerten (hoffe das Register heißt so).

Viel Erfolg
Hermann-Josef

von Hegy (Gast)


Lesenswert?

Viele Möglichkeiten habe ich jetzt mal durchgechekkt. Außerdem habe ich 
noch eine 3. Variable zum testen dazugenommen, welche in main.c als 
globale uchar8 definiert ist und mit 55 initialisiert wird. In crc16.c 
steht die Variable nochmal global als extern uchar8 drin. Um 
sicherzugehen, daß die UART0 mich nicht bescheißt, sende ich noch über 
UART0 einen Buchstaben vor und nach den Variablen raus, sieht alles so 
aus:
1
extern unsigned char lllll;
2
3
unsigned char crc16(unsigned char *buf, unsigned char inout)
4
{
5
  unsigned char i, length;
6
  union crcbytes crc; // crc.all = 16 Byte = crc.lo + crc.hi
7
8
  length = buf[1];
9
  lllll = buf[1];
10
  crc.all = 0xFFFF;
11
12
  if(UDRE0)
13
  {
14
    outstr0('a');    // = 97 dez.
15
    outstr0(buf[1]); // = 3
16
    outstr0(length); 
17
    outstr0(lllll);
18
    outstr0('e');    // = 101 dez.
19
  }
20
  ....
21
}

Bevor was über UART0 rausgefunkt wird, wird das Flag UDRE0 gecheckt, ob 
es 1 ist. Wenn, dann den Wert der einzelnen Variablen rausfunken, 
interruptgesteuert. Ergebnis:
1
'a'  buf[1] length lllll  'e'
2
097   003    000    000   101

Der 1., 2. und 5. Wert ist richtig. Zumindest bei der Var. lllll, also 
das 4. Byte, da hätte ich 55 erwartet, aber auch hier 0.

Ich habe es auch schon mit strncpy(), auch keine Lösung.
Oder ein _delay_loop_2(5), natürlich auch nix.

Und bevor die crc16()-Fkt. aufgerufen wird, habe ich ein cli und danach 
ein sei, eingebaut, hilft auch nicht.

von Hegy (Gast)


Lesenswert?

Noch eine Erkenntnis:
wenn ich versuche, die Werte von pcbuf[] in eine andere Var. zu 
kopieren, geht das nur mit dem 1. Byte beim 1. Mal. Die Funktionen 
memcpy(tmpstr, pcbuf, 5) und strncpy(tmpstr, pcbuf, 5) kopieren auch nur 
das 1. Byte, der Rest von tmpstr[] ist nach dem ersten lesen 0. Auch 
hier habe ich wieder Dummy-ASCII-Zeichen nach jeder Zeichenausgabe von 
pcbuf[] bzw. tmpstr[] ausgegeben, um sicherzustellen, daß die UART mich 
nicht bescheißt.

von Hegy (Gast)


Lesenswert?

problem seems to be fixed :))))

Ist schon suboptimal, wenn man sein eigenes Protokoll nicht kennt.....

Ursache/Lösung im File main.c, Zeile 189:
1
178  ISR(SIG_USART0_RECV)
2
179  {
3
183     pcbuf[uart0incnt] = UDR0;
4
185     if(pcbuf[0] == OWNADR)
5
186     {
6
187       uart0incnt++;
7
188
8
189 >>>   if(uart0incnt == pcbuf[1]+1)
9
193         uart0rcvcmpl = 1;
10
194     }
11
198  }

Der Zähler uart0incnt zählt die eingehenden Zeichen, die über UART0 
reinkommen. Kein Zeichen da, uart0cnt = 0. In dem String, der reinkommt, 
steht an 2. Stelle die Anzahl der noch folgenden Bytes inkl. CRC16 (2 
Bytes). Im dem Fall von mir war immer die Rede von folgendem String: 01 
03 71 194 96 (alles dez.). 1. Byte SW-Adresse, 2. Byte = 03 = 3 noch 
folgende Bytes (71 194 96). wenn das 2. Byte angekommen ist, also die 
ISR zum 2. Mal aufgerufen wird, wird erst der Zähler hochgezählt, steht 
also richtigerweise auf zwei und nicht wie von mir angenommen auf eins. 
uart0incnt steht nach dem 1. Byte schon auf eins, und wenn ich die Länge 
auswerte (Zeile 189), dann steht uart0incnt auf zwei. Dieses +1 bei 
pcbuf[1]+1 war die Ursache, so wie es aussieht. Deswegen kamen auch erst 
die Daten entsprechend rüber, wenn ich den String 2x abgefeuert habe, 
weil nach dem 2. Mal abfeuern das Flag uartrcvcmpl = 1 gesetzt wird.

Ich bau den Code mal wieder zurück, so wie es ursprünglich war und werde 
das ausgiebig testen. Dann das hoffentlich finale Ergebnis.

von Hegy (Gast)


Lesenswert?

So, feddich meinich.
Es geht!

Und dann war da noch ein Fehler in der ISR(SIG_USART0_RECV) mit der 
Bytezählerei, und pcbuf[] wurde beim Start nicht initialisiert. hat der 
gcc nicht mitbekommen, weil nur das erste Element von pcbuf[] 
initialisiert wird und der Rest geht leer aus. Es sind also mehrere 
SW-Fehler gewesen.

Danke auch an Hermann-Josef, für die Anteilnahme!

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.