Hallo zusammen,
ich habe hier ein kleines Problem, das mich total verwirrt:
In einigen meiner Projekte verwende ich (in nur leicht angepasster Form)
die USI-UART Routine aus AN AVR307.
Funktioniert normalerweise gut, nur hier nicht.
Ein Tiny 861A "empfängt" nach jedem korrekt empfangenem Byte ein 0xFF,
das nie gesendet wurde,
- wenn man vorher irgendein Bit in DIDR0 oder DIDR1 gesetzt hat
(ausdrücklich NICHT für die Pins der USI),
- UND mit Optimierung -Os kompiliert,
- ODER die Funktion USI_UART_start() als "static inline" deklariert
(dann tritt der Fehler auch mit Optimierung -O0 auf).
Sobald man in DIDR0 oder DIDR1 wieder alle Bits zurück setzt,
funktioniert die Kommunikation wieder.
Was optimiert sich denn der avr-gcc (Version 4.8.1) da zusammen?
Oder ist mein µC defekt?
DIDR0 hat doch gar nichts mit der USI zu tun. Ich schalte ja nicht den
Input Buffer für die USI-Pins ab.
Ich füge den Quelltext bei, der den Fehler zeigt. Es stammt aus einem
größeren Projekt, aber ich habe alles entfernt, was nicht zum Fehler
beiträgt bis auf die Atmel USI-UART Routine und
das Hauptprogramm, dem man per serieller Verbindung einfache 'Befehle'
senden kann:
- 'B' setzt DIDR0 auf 0xd0,
- 'C' setzt DIDR0 auf 0,
- 'D' wartet auf die nächsten 3 Zeichen, sendet diese zurück, einmal als
ASCII und einmal nach atoi().
Sendet man 'D056', so sollte man erhalten: Input = 056 Data = 8.
Im Fehlerfall erhält man jedoch Input = ?0? (0xff 0x30 0xff).
Was läuft hier schief?
Danke für Eure Hilfe!
Oliver
Wenn du schon avr-gcc 4.8 verwenden willst, dann nimm mindestens 4.8.4.
Durch den Code wühl ich mich jestzt mal nicht durch. Würdest du konkret
aufzeigen, wo der Compiler falschen Code erzeugt hat?
Johann L. schrieb:> Wenn du schon avr-gcc 4.8 verwenden willst, dann nimm mindestens> 4.8.4.
Hm, ich probier' mal, ob ich auf 4.9.2 upgraden kann...
> Durch den Code wühl ich mich jestzt mal nicht durch. Würdest du konkret> aufzeigen, wo der Compiler falschen Code erzeugt hat?
Ja, das wenn i wüsst ;-)
Ich nehme an, dass es im Bereich der USI_UART_start hakt.
Aber wie die dann vom Setzen von DIDR0 beeinflusst werden kann?
Hier lege ich noch die disassemblierten *.lss Dateien bei, einmal mit
Optimierung, einmal ohne.
Daraus die Abschnitte USI_UART_start:
1
mit Optimierung:
2
000000d2 <USI_UART_start>:
3
d2: f8 df rcall .-16 ; 0xc4 <timer_init>
4
d4: 80 91 74 00 lds r24, 0x0074
5
d8: 88 60 ori r24, 0x08 ; 8
6
da: 80 93 74 00 sts 0x0074, r24
7
de: 89 b7 in r24, 0x39 ; 57
8
e0: 84 60 ori r24, 0x04 ; 4
9
e2: 89 bf out 0x39, r24 ; 57
10
e4: 81 e0 ldi r24, 0x01 ; 1
11
e6: 81 bb out 0x11, r24 ; 17
12
e8: 82 e5 ldi r24, 0x52 ; 82
13
ea: 8d b9 out 0x0d, r24 ; 13
14
ec: 08 95 ret
1
ohne Optimierung:
2
0000014a <USI_UART_start>:
3
14a: cf 93 push r28
4
14c: df 93 push r29
5
14e: cd b7 in r28, 0x3d ; 61
6
150: de b7 in r29, 0x3e ; 62
7
152: e4 df rcall .-56 ; 0x11c <timer_init>
8
154: 80 91 74 00 lds r24, 0x0074
9
158: 88 60 ori r24, 0x08 ; 8
10
15a: 80 93 74 00 sts 0x0074, r24
11
15e: 89 e5 ldi r24, 0x59 ; 89
12
160: 90 e0 ldi r25, 0x00 ; 0
13
162: 29 e5 ldi r18, 0x59 ; 89
14
164: 30 e0 ldi r19, 0x00 ; 0
15
166: f9 01 movw r30, r18
16
168: 20 81 ld r18, Z
17
16a: 24 60 ori r18, 0x04 ; 4
18
16c: fc 01 movw r30, r24
19
16e: 20 83 st Z, r18
20
170: 81 e3 ldi r24, 0x31 ; 49
21
172: 90 e0 ldi r25, 0x00 ; 0
22
174: 21 e0 ldi r18, 0x01 ; 1
23
176: fc 01 movw r30, r24
24
178: 20 83 st Z, r18
25
17a: 8d e2 ldi r24, 0x2D ; 45
26
17c: 90 e0 ldi r25, 0x00 ; 0
27
17e: 22 e5 ldi r18, 0x52 ; 82
28
180: fc 01 movw r30, r24
29
182: 20 83 st Z, r18
30
184: 00 00 nop
31
186: df 91 pop r29
32
188: cf 91 pop r28
33
18a: 08 95 ret
Leider bin ich etwas schwach in Assembler...
Wo DIDR0 gesetzt wird und wie das die USI_UART_start "abschießen" kann,
habe ich noch nicht gefunden.
So, ich fange nun an, anhand des Befehlssatzes im Datenblatt etwas
Assembler zu lernen. ;-)
Meine Ergebnisse so weit:
1. M.E. stimmt das obige Listing für USI_UART_start mit Optimierung
genau mit dem C Quelltext überein, Zeile für Zeile.
Das ohne Optimierung dagegen... ist etwas verwirrend. Da müssen wohl
noch ein paar andere Programmteile verbacken sein.
2. Im *.lss mit Optimierung finde ich die Stellen, an denen DIDR0 (0x01)
gesetzt wird, nämlich in diesen Zeilen:
37e - 380
und
392 - 3aa
Das sieht eigentlich auch nicht verkehrt aus.
In der *.lss ohne Optimierung finde ich jedoch nicht einmal die
Schreibzugriffe auf 0x01. Wie wird denn das hier gemacht?
Ich habe nun gcc-avr 4.9.2 installiert. Mit Optimierung bekomme ich
dasselbe Fehlerbild.
Und ohne -Os ist es völlig hoffnungslos den beabsichtigten Code im Tiny
861A unterzubringen...
oliver schrieb:> - ODER die Funktion USI_UART_start() als "static inline" deklariert> (dann tritt der Fehler auch mit Optimierung -O0 auf).
Scheint ein Laufzeitproblem zu sein. Bei -Os werden einmalig aufgerufene
Funktionen geinlined, daher gehts dann schneller. Kannst ja mal ein
_dely_us() davor einfügen.
oliver schrieb:> Sobald man in DIDR0 oder DIDR1 wieder alle Bits zurück setzt,> funktioniert die Kommunikation wieder.
Klingt merkwürdig.
Es hat aber schon seltsame Bugs gegeben, wo sich eigentlich getrennte
HW-Einheiten beeinflußt haben. Z.B. darf beim ATtiny1634 PB3 kein Input
sein, wenn der Watchdog aus ist.
Vielleicht gibt es auch Seiteneffekte zwischen RX und TX, das USI ist ja
nicht Full-Duplex fähig. Man könnte daher jeweils bei der Umschaltung
von RX nach TX und umgekehrt Delays probieren (2 Bitzeiten).
Ich fand die UART mit USI viel zu kompliziert und habe sie daher mit
Timern gemacht, ist dann auch Full-Duplex:
Beitrag "Software UART"
Hallo Peter,
Peter D. schrieb:> Scheint ein Laufzeitproblem zu sein.
Ja, so sieht es aus. Ich habe mal großzügig _delay_us(100) jeweils am
Anfang der USI_UART_Initialise_Transmitter() und der
USI_UART_Initialise_Receiver() eingefügt.
Damit läuft es.
Für die Anwendung ist das natürlich fatal, denn Atmel verwendet beide
Funktionen in ISRs. :-(
Wenn ich ja wüsste, was genau da zu schnell ist und wie die 0xff in den
Ringpuffer kommen... dann könnte man vielleicht durch einfaches
Umstellen der Reihenfolge das Problem beheben.
Vielleicht wird in der USI_UART_Initialise_Transmitter() USIDR zu früh
geschrieben? (USIDR = 0xFF;)
Peter D. schrieb:> Klingt merkwürdig.> Es hat aber schon seltsame Bugs gegeben, wo sich eigentlich getrennte> HW-Einheiten beeinflußt haben. Z.B. darf beim ATtiny1634 PB3 kein Input> sein, wenn der Watchdog aus ist.
Interessant - noch ein Kapitel aus dem Buch "µC-Esoterik", das ich noch
nicht gelesen habe. ;-)
Wie der Inhalt von DIDR0 oder DIDR1 Auswirkungen auf die Laufzeit haben
kann, ist mir ein Rätsel. Und auf einem Tiny 85 habe ich das Problem
nicht.
Nein, an der USI_UART_Initialise_Transmitter() lag's nicht.
Schade - die wird nämlich nicht aus einer ISR aufgerufen.
Ich brauche am Anfang der USI_UART_Initialise_Receiver() mindestens 6µs
delay (F_CPU = 8MHz). Drunter geht es nicht.
Das war jetzt reine Empirie - ich versteh's immer noch nicht.
Wieso braucht er gerade hier eine "Denkpause"?
Oder wo genau?
Woher bekommt er die 0xff?
Und was hat DIDR0 damit zu tun?
Peter D. schrieb:> Ich fand die UART mit USI viel zu kompliziert und habe sie daher mit> Timern gemacht,
Dein Soft-UART hatte ich gesehen. Bisher habe ich allerdings gedacht:
"Was ich in Hardware machen kann, mache ich nicht in Software."
Inzwischen zweifele ich aber an dem Mehrwert des USI...
Johann L. schrieb:> Ich würd jetzt auf ein fehlendes volatile oder ein Glitch durch> nicht-atomaren Zugriff in main tippen.
Das würde mich freuen, denn das wäre einfach zu beheben.
Allerdings wäre ein fehlendes volatile ein logischer Fehler und somit
zeitunabhängig. Egal wie schnell oder langsam - es wäre immer ein
Fehler. Auch egal ob DIDR0 beschrieben wird oder nicht.
Und atomarer Zugriff würde doch erfordern, dass es irgendwo eine 16bit
Variable gäbe, auf die atomar zugegriffen werden müsste. Alles ist 8bit.
Auch der Timer läuft im 8bit Modus.
Inzwischen ist es mir gelungen weiter einzugrenzen, wo der Delay sein
muss:
1
ISR(PCINT_vect)
2
{
3
4
if(!(PINA&(1<<DI_RX)))// If the USI DI pin is low, then it is likely that it
5
{// was this pin that generated the pin change interrupt.
6
_delay_us(8);
7
TCNT1=INITIAL_TIMER1_SEED+INTERRUPT_STARTUP_DELAY;// Plant initial TIMER1 seed for first bit to match baudrate (incl interrupt start up time.).
8
9
USI_UART_start();
10
USISR=0xF0|// Clear all USI interrupt flags.
11
USI_COUNTER_SEED_RECEIVE;// Preload the USI counter to generate interrupt.
12
13
GIMSK&=~(1<<PCIE1);// Disable pin change interrupt for PA7:0.
Genau dort, vor dem seed des TCNT1 - und nicht danach.
Nun bin ich auf den Gedanken gekommen, dass, wenn es nur darum ginge das
Setzen des Seed und damit den Timer Overflow zu verzögern, man denselben
Effekt ja genauso gut durch Ändern des Seeds erreichen könnte.
Geht aber nicht.
Oder ich habe mich verrechnet. F_CPU 8MHz, Timer Prescaler 4 - da
sollten 8µs doch Seed - 16 sein...
Ach, es ist schon spät und mir brummt der Kopf.
Ich verstehe noch immer nicht, was das mit DIDR0 zu tun hat.
oliver schrieb:> Genau dort, vor dem seed des TCNT1 - und nicht danach.
Welcher Wert wird denn da konkret geladen?
Ich würde danach noch das Overflow-Flag löschen (auf 1 setzen).
oliver schrieb:> Ich verstehe noch immer nicht, was das mit DIDR0 zu tun hat.
Gibt es da ein bestimmtes Bit, was das Fehlerbild bewirkt?
Kannst Du zukünftig statt .tar.gz besser .zip verwenden, das läßt sich
leichter öffnen.
Beim ADC ist mir auch was komisches aufgefallen. Wenn man den ADC mit
Timer triggert, muß man trozdem den Timerinterrupt ausführen, sonst wird
das Overflow-Bit nicht gelöscht. Man spart also nicht wirklich
Interruptlast ein. Scheint wohl beim USI auch nötig zu sein.
Guten Morgen, Peter,
Peter D. schrieb:> Welcher Wert wird denn da konkret geladen?
206, sagt der Präprozessor.
Peter D. schrieb:> Ich würde danach noch das Overflow-Flag löschen (auf 1 setzen).
Das geschieht gleich am Anfang der USI_UART_start():
(0<<USIWM1)|(1<<USIWM0)|// Select Three Wire mode.
9
10
(0<<USICS1)|(0<<USICS0)|(1<<USICLK)|// use software clock strobe
11
(0<<USITC);
12
// Note that enabling the USI will also disable the pin change interrupt.
13
return;
14
}
Peter D. schrieb:> Gibt es da ein bestimmtes Bit, was das Fehlerbild bewirkt?
Nein. Der Fehler tritt sogar auf, wenn ich in DIDR1 Bits setze, also für
PortB, obwohl die USI-UART PortA nutzt und PortB in dieser abgespeckten
Programmversion leer ist.
Peter D. schrieb:> Scheint wohl beim USI auch nötig zu sein.
Ja, genau. Da ich in der kompletten Anwendung die USI auf PortB für SPI
nutze, trigger ich die USI clock in der ISR:
1
ISR(TIMER1_OVF_vect)
2
{
3
if(spiX_status.modeUART==1)
4
USICR|=(1<<USICLK);
5
else
6
USICR|=(1<<USITC);// Toggle clock output pin.
7
return;
8
}
Das setzt natürlich auch den Timer OVF Interrupt zurück. In der
abgespeckten Version wird SPI nicht genutzt, d.h. modeUART ist immer 1
und PortB ist leer.
oliver schrieb:> Johann L. schrieb:>> Ich würd jetzt auf ein fehlendes volatile oder ein Glitch durch>> nicht-atomaren Zugriff in main tippen.>> Und atomarer Zugriff würde doch erfordern, dass es irgendwo eine 16bit> Variable gäbe, auf die atomar zugegriffen werden müsste. Alles ist 8bit.> Auch der Timer läuft im 8bit Modus.
Das stimmt in dieser Allgeminheit nicht. Erstens hast du auch bitfelder
im Code welche nicht atomar gesetzt werden können da immer mindestens
ein 8-Bit Wert gelesen und wieder gespreichert werden muss. Analoges
gilt für 8-Bit Werte. Zwar kann atomar gelesen und gespeichert werden,
aber wenn eine Opertaion atomar sein muss dann hilft es auch nicht,
wenn es ein 8-Bit wert ist.
Wenn es z.B. einen 8-Bit Wert gibt, der 8 Flags enthält und wo ein Flag
gesetzt wird, kann folgendes passieren:
1
uint8_tvolatileflags;
2
3
voidset_flag0(void)
4
{
5
flags|=1;
6
}
1
set_flag0:
2
lds r24,flags
3
;; Falls hier oder...
4
ori r24,lo8(1)
5
;; hier eine ISR ausgeführt wird, die abanfalls eines der flag-
6
;; Bits verändert, werden all diese Änderungen durch folgendes
7
;; STS überschrieben.
8
sts flags,r24
9
ret
Aber das ist ja alles bereits im ober verlinkten Wiki-Artikel dargelegt,
und solche Situationen treten in deinem Code wohl niemals auf.
Hallo Johann,
Johann L. schrieb:> Das stimmt in dieser Allgeminheit nicht. Erstens hast du auch bitfelder> im Code welche nicht atomar gesetzt werden können
Ja, da hast Du recht.
Bei read-modify-write Operationen könnte es ein Problem geben.
Die sehe ich hier aber nicht. Auch die Ringpuffer, auf die ja auch aus
den ISR zugegriffen wird, werden ja an dem einen Ende immer nur
geschrieben und an dem anderen immer nur gelesen, und auf den
dazugehörigen Index wird entweder nur in der ISR oder nur außerhalb
schreibend zugegriffen.