Hallo zusammen,
mich macht gerade das einfache byteweise Auslesen eines 16 Bit Messwerts
über i2c wahnsinnig, C- und PIC18-Versierte mögen doch bitte mal
darüberschauen und mir sagen, auf welchem Schlauch ich stehe.
Ich betreibe div. Sensoren via I2C an einem PIC18 Controller, welchen
ich mit MPLAB-X und XC8 programmiere.
Das funktionierte anfangs alles recht problemlos, mit steigender
Komplexität schlichen sich einige sehr seltsame Effekte ein, bereits
funktionierender Code lieferte plötzlich wirre Daten.
Einen solchen Effekt konnte ich einkreisen & wäre für eine Erklärung
dankbar:
Ein 16bit Messwert wird über I2c byteweise ausgelesen, und soll nun zu
einem 16 bit int werden, welcher dann auf einem Display (4 char Hex)
angezeigt wird. Die von i2c gelesenen Daten liegen in einem unsigned
char Array, Indexposition 4 (lo) und 5 (hi).
Der Testcode soll nun das hi-Byte aus dem Array in eine int Variable
kopieren und 8 bit nach links shiften. Die int Variable wird dann auf
einem Display ausgegeben. Das lo Byte vergessen wir erst mal. Das
Hi-Byte des realen Messwerts toggelt zwischen 0x44 und 0x45.
************
1. Beispiel. So sollte es funktionieren...
1
unsignedcharbb=I2C_Recv[5];
2
intz=bb<<8;
3
showint(z);
Ergebnis: Die Anzeige zeigt für das hi-Byte wirr alle möglichen Werte
an, lo ist immer "00":
************
2. Beispiel. Ich spare mir zu Debugzwecken das shiften:
1
unsignedcharbb=I2C_Recv[5];
2
intz=bb;
3
showint(z);
Ergebnis: Die Anzeige toggelt zwischen "0044" und "0045". passt.
************
3. Beispiel. Ich shifte nur 4 Bit:
1
unsignedcharbb=I2C_Recv[5];
2
intz=bb<<4;
3
showint(z);
Ergebnis: Die Anzeige toggelt zwischen "0440" und "0450". passt.
************
4. Beispiel. Ich shifte komplett, verwende aber ein hardcode
Messergebnis:
1
unsignedcharbb=0x44;
2
intz=bb<<8;
3
showint(z);
Ergebnis: Die Anzeige zeigt stabil "4400".
************
Tatsächlich kann ich mein Messergebnis erfolgreich um 5 bit nach links
shiften, dann toggelt die Anzeige zwischen "0880" und "08A0", das passt.
Sobald ich 6 bit oder mehr shifte, zeigt mein Display nur noch wirre
Werte.
Die Messwerte werden vor den Codebeispielen vom Sensor gelesen, kein
asynchrones Auslesen.
Interrupts werden lediglich für die LED-Anzeige benutzt.
Hat irgendjemand eine Idee, was da passiert?
ciao
Alberich
Der Compiler weiß wohl, dass wenn Du ein char 8 bits nach links oder
rechts schiebst, immer 0 rauskommt. Standardkonform ist das nicht, er
hätte den Operanden erst nach int wandeln müssen. Vim Schieben um 4
unterbleibt diese Optimierung.
fchk
Frank K. schrieb:> Standardkonform ist das nicht, er> hätte den Operanden erst nach int wandeln müssen
Frank, bist Du da sicher ?
Ich habe da im Hinterkopf, dass die RHS (also bb << 8) insgesamt auf das
Format der LHS gecastet wird. Den Ausdruck bb << 8 macht der Compiler
vor der Zuweisung, dann aber mit dem Datentyp von bb, also unsigned
char.
Über sollche Details "stolpert" man ja immer gerne mal (und ich
besonders ;-)
Grüße
Andreas
Ich habe das auch schon Schrittweise versucht, char->int und dann int
shiften, gleiches Ergebnis. Ebenso mit 4 bit shiften und dann noch mal 4
bit, keine Änderung. Beispiel 4 dürfte dann ja auch nicht funktionieren.
Aber von meiner Warte aus schließen sich die 4 Beispiele eh gegenseitig
aus. Irgendeine Optimierung könnte Schuld sein, glaube ich aber
eigentlich nicht.
Der Code hat ja mal funktioniert. Dann habe ich Gimmicks wie dezimale
Ausgabe hinzugefügt & dann fing das an.
Ich hatte zwischendurch beispielsweise folgenden Effekt: Eine Methode
1
voidNonsenseCalc(){
2
intxy=0x3482;
3
xy=xy/23;
4
}
macht eigentlich nichts. Wenn ich sie während der Messwertumrechnung
aufgerufen habe, lieferte die Messwertumrechnung wirre Ergebnisse.
Ich hatte das dann auf irgendeinen Stacküberlauf o.ä. geschoben und die
Zahl der Funktionsaufrufe minimiert, das half erst mal. Jetzt habe ich
allerdings für die Fehlersuche den Code für einzelne Sensoren komplett
entfernt, ohne dass es geholfen hätte.
Holger Schneider schrieb:> Ich habe das auch schon Schrittweise versucht, char->int und dann int> shiften, gleiches Ergebnis. Ebenso mit 4 bit shiften und dann noch mal 4> bit, keine Änderung. Beispiel 4 dürfte dann ja auch nicht funktionieren.
Bei Beispiel 4 kann der Compiler das Ergebnis bereits zur Compilezeit
berechnen. Da dürftest Du dann nur eine Konstante im Binary finden.
Lass Dir den erzeugten Assemblercode ausgeben. Dann siehst Du, was der
Compiler daraus macht.
fchk
> Bei Beispiel 4 kann der Compiler das Ergebnis bereits zur Compilezeit> berechnen. Da dürftest Du dann nur eine Konstante im Binary finden.
Es geht hier um den XC8 ;).
Wie sieht der ASM aus, wenn es mit Funktionsaufruf ist?
6 shifts sehe ich da auch nicht, eher 8.
Was passiert, wenn du z = bb machst und erst danach << 8?
Kann es so wie es momentan ist nicht so sein, dass er bb nach links
shiftet, einen Überlauf hat und dann erst die Zuweisung macht?
Holger Schneider schrieb:> Ein 16bit Messwert wird über I2c byteweise ausgelesen, und soll nun zu> einem 16 bit int werden, welcher dann auf einem Display (4 char Hex)> angezeigt wird. Die von i2c gelesenen Daten liegen in einem unsigned> char Array, Indexposition 4 (lo) und 5 (hi).
Aha. Aber dann brauchst du den 16 Bit Zwischenwert doch gar nicht?
1
voidhexout(uint8_tx)
2
{
3
uint8_tnibble=x>>4;
4
putc(nibble>9?'0'+nibble:'A'-10+nibble);
5
nibble=x&0x0F;
6
putc(nibble>9?'0'+nibble:'A'-10+nibble);
7
}
8
9
hexout(I2C_Recv[5]);
10
hexout(I2C_Recv[4]);
und nur für den Fall, daß du den 16 Bit int auch noch für etwas anderes
als die Ausgabe brauchst:
> 811 0017 3006 movlw 6> 812 0018 u15:> 813 0018 35F3 lslf ??_main,f> 814 0019 0DF4 rlf ??_main+1,f> 815 001A 0B89 decfsz 9,f> 816 001B 2818 goto u15
Da geht was kräftig schief.
Die Laufvariable für die shift-Schleife wird in das w-Register geladen
aber in der Schleife wird plötzlich die Adresse 9 als Zähler verwendet.
Laut DB liegt auf 9 je nach Bank das PORTE oder TRISE Register (falls
vorhanden).
Keine Ahnung warum der Compiler da Mist macht.
chris schrieb:> bei 16f ja, bei 18f nein, da sind die Ports anders gemappt.
Grmpf, da fehlt mir offenbar der aktuelle Bezug.
Ich mach schon seit ein paar Jahren nix mehr mit PIC16 und 18, ist nur
noch Schubladen-Wissen.
chris schrieb:> bei 16f ja, bei 18f nein, da sind die Ports anders gemappt.
Beim PIC16 sind SFR am Anfang jeder Bank, bei P18 sind die meisten am
Ende der letzten Bank.
Mein XC8 löst das so:
1
21:bb=I2C_Recv[5];
2
7FCCC008MOVFF0x8,0xd
3
7FCEF00DNOP
4
22:z=bb<<6;//+I2C_Recv[4];
5
7FD0500DMOVF0xd,W,ACCESS
6
7FD26E01MOVWF0x1,ACCESS
7
7FD46A02CLRF0x2,ACCESS
8
7FD60E06MOVLW0x6
9
7FD890D8BCF0xfd8,0,ACCESS
10
7FDA3601RLCF0x1,F,ACCESS
11
7FDC3602RLCF0x2,F,ACCESS
12
7FDE2EE8DECFSZ0xfe8,F,ACCESS
13
7FE0D7FBBRA0x7fd8
14
7FE2C001MOVFF0x1,0xb
15
7FE4F00BNOP
16
7FE6C002MOVFF0x2,0xc
17
7FE8F00CNOP
Ich habe jetzt nicht nachgeschaut, aber 0xfe8 müsste die Adresse des
WREG sein.
Ich habe zwei Varianten compiliert & den Code gegenübergestellt. Ein mal
der funktionierende Shift um 4 Bit, dann der nicht funktionierende Shift
um 8 Bit:
************ 4-Bit shift:
Offensichtlich wird da optimiert. Eigentlich wird nirgendwo geshiftet,
das höchste der Gefühle ist ein swapf... Die "movlb 0 ; () banked"
verstehe ich allerdings nicht.
Holger Schneider schrieb:> Offensichtlich wird da optimiert. Eigentlich wird nirgendwo geshiftet,> das höchste der Gefühle ist ein swapf
Auf µC deren ALU keinen Barrelshifter hat, ist die Verwendung von swap
(high- und low-Nibble in einem Byte tauschen) eine gute Optimierung für
die Implementierung eines Shifts um 4 Bits. Ich würde die
Nichtverwendung von Schiebe-Opcodes als positives Zeichen sehen.
XL
- Welchen PIC verwendest du?
- Bist du sicher, dass die Zahl korrekt in einen String umgewandelt und
auf dem Display angezeigt wird?
- Verwendest du die aktuellste Version vom XC8 (V1.30)? Der Compiler ist
noch jung und Microchip muss noch viele Fehler korrigieren...
Im Simulator funktioniert das Schieben (MPLAB X V1.95, XC8 V1.30):
1
unsignedchara=1;
2
intx=(int)a<<6;
1
1FE2 5005 MOVF a, W, ACCESS
2
1FE4 6E01 MOVWF 0x1, ACCESS
3
1FE6 6A02 CLRF 0x2, ACCESS
4
1FE8 0E06 MOVLW 0x6
5
1FEA 90D8 BCF STATUS, 0, ACCESS
6
1FEC 3601 RLCF 0x1, F, ACCESS
7
1FEE 3602 RLCF 0x2, F, ACCESS
8
1FF0 2EE8 DECFSZ WREG, F, ACCESS
9
1FF2 D7FB BRA 0x1FEA
10
1FF4 C001 MOVFF 0x1, x
11
1FF6 F003 NOP
12
1FF8 C002 MOVFF 0x2, 0x4
13
1FFA F004 NOP
Nachtrag:
Interessant ist auch, dass der XC8 das nicht in eine Multiplikation mit
64 umwandelt, schliesslich besitzen die PIC18 einen
Hardwaremultiplizierer und der erzeugte Code ist kürzer...
Gut, dass das hier hinpasst.
Toll, jetzt muss man solche piss (AVR) Fanboys auch noch ertragen, wenns
um ein PIC Compiler Problem/Frage geht... Die kommen bestimmt auch noch
im Off-Topic Bereich an..
Holger Schneider schrieb:> ************ 8-Bit shift:unsigned char bb = I2C_Recv[5];> int z = ((int)bb) << 8;> Assemblercode: 911 ;gyro.c: 77: unsigned> char bb = I2C_Recv[5];> 912 0003C6 C07E F091 movff _I2C_Recv+5,Gyroscope@bb> 913> 914 ; BSR set to: 0> 915 ;gyro.c: 78: z = ((int)bb) << 8;> 916 0003CA 0100 movlb 0 ; () banked> 917 0003CC 0100 movlb 0 ; () banked> 918 0003CE 5191 movf Gyroscope@bb& (0+255),w,b> 919 0003D0 0100 movlb 0 ; () banked> 920 0003D2 0100 movlb 0 ; () banked> 921 0003D4 6F93 movwf (Gyroscope@z+1)& (0+255),b> 922 0003D6 0100 movlb 0 ; () banked> 923 0003D8 6B92 clrf Gyroscope@z& (0+255),b>> Offensichtlich wird da optimiert. Eigentlich wird nirgendwo geshiftet,> das höchste der Gefühle ist ein swapf... Die "movlb 0 ; () banked"> verstehe ich allerdings nicht.
So wie ich das sehe, weist er dem Highbyte von z die Zahl zu (921) und
löscht das Lowbyte (923). Da braucht er nicht shiften.
Stefan schrieb:> ohne Worte>> XC8:
[24 Instruktionen]
> avr-gcc:
[3 Instruktionen]
LOL. "Treffer, versenkt" würde ich sagen :D
Andererseits paßt es doch. Compilertechnologie von vor 30 Jahren für
eine Mikroarchitektur von vor 30 Jahren. Muß man sich schön saufen ;)
PROST!
Und es taugt auch prima als Erklärung, warum die Compiler von MCP
closed source sind. Nix von wegen "was nix kostet, taugt nix" -
die schämen sich einfach. Und zu Recht!
XL
Also erst mal muss ich sagen, dass ich hocherfreut war, hier im Forum
nicht die typischen Grabenkämpfe wiederzufinden. Das legt sich gerade
wieder ein bisschen.
Aber zum Thema: Ich habe mir trotz der kruden Assemblersyntax der PICs
den compilierten Code angeschaut, konnte so recht keinen Fehler finden &
hab mich deshalb noch mal ins Debugging gestürzt. Ergebnis: Ein Fehler
im I2C-Code hat dazu geführt, dass beim übermitteln der zu lesenden
Registeradresse der Sensoren zu viele Bytes geschrieben wurden, weshalb
diese zu Recht auch mal mit Blödsinn geantwortet haben. Beim lesen der
Device-IDs kamen sie nicht durcheinander, beim Lesen der
Messwertregister waren sie offensichtlich zimperlicher.
Warum ich, in zig Versuchen nachvollziehbar, das Verhalten mit der
Anzahl der Shift-Bits steuern konnte, das bleibt wohl erst mal ein
Geheimnis. Vielleicht wurde das Array der zu sendenden Bytes
unterschiedlich initialisiert, je nach dem, was man im Code sonst noch
so treibt. Und sei es nur, das es an einer anderen Stelle im Speicher
landete. Wenn ich mich von dem Chaos erholt habe, dann schaue ich mir
das vielleicht noch mal an ;-)
Fazit: XC8 war doch unschuldig und es war auch keine Magie am Werk,
sondern ein ganz normaler Programmierfehler.
Vielen Dank an alle, die geholfen haben. Alleine wäre ich der falschen
Fährte sicherlich noch weiter gefolgt - und sorry für diese falsche
Fährte.
p.S.: Aber mit einem [hier anderen Prozessor einsetzen] wäre das
sicherlich nicht passiert :-)
Stefan schrieb:> avr-gcc:> 474:main.c **** unsigned char a=1;> 475:main.c **** int x=(int)a<<6;> 476:main.c **** puts(x);> 924 .LM120:> 925 00b8 80E4 ldi r24,lo8(64)> 926 00ba 90E0 ldi r25,0> 927 00bc 00D0 rcall puts
Irgendwie sehe ich da was nicht. Wo ist da die Zeile 475, also der
LShift um 6 Stellen, als ASM Code ?
Grüße
Andreas
constant propaganation.
Da a immer 1 ist, wird X gleich mit 64 geladen, also 1<<64.
Auch der C18 Compiler da gcc macht dasselbe. Ob X8 es mit nicht
abgeschalteter Optimierung auch macht, mòglicherweise.