heyo,
gerade bin ich bei einem Projekt, in dem ein Tiny24 Blinkfolgen ausgeben
soll - auf zwei PORTS verteilt. Die zu schreibenden Bytes werden
nacheinander generiert - die ersten 6 Bits gehen an PORTA:0-5, die
höchsten beiden an PORTB:0-1.
1
while(1) {
2
if (..) {
3
byteA = calculate_byte(message);
4
PORTB = byteA>>6;
5
PORTA = byteA;
6
bits.set = 1;
7
}
8
9
if (..) {
10
PORTA = 0;
11
PORTB = 0;
12
}
13
}
Wenn ich den Code compiliere und übertrage, funktioniert das nur ohne
Optimierung (-O0) wie gewünscht.
Bei -Os schaltet er grundsätzlich alle LEDs an und gibt Müll aus.
Wenn ich nur jeweils einen PORT beschreibe mit dem gewünschten Byte, und
den anderen mit einem festem Wert, geht das allerdings.
Sobald ich beide PORTs mit dem gleichen Byte beschreibe, geht es nicht.
Schreibe ich auf den ersten PORT ein Byte, hole mir das nächste und
schreibe die höchsten beiden Bits auf den anderen PORT - geht das.
Der Assembler-Code macht eigentlich auch das richtige:
1
218: 9c df rcall .-200 ; 0x152 <calculate_byte>
2
21a: 80 93 60 00 sts 0x0060, r24
3
21e: 80 91 60 00 lds r24, 0x0060
4
222: 82 95 swap r24
5
224: 86 95 lsr r24
6
226: 86 95 lsr r24
7
228: 83 70 andi r24, 0x03 ; 3
8
22a: 88 bb out 0x18, r24 ; 24
9
22c: 80 91 60 00 lds r24, 0x0060
10
230: 8b bb out 0x1b, r24 ; 27
0x18 und 0x1b sind die Portregister.
Ich habe noch einen Hardware PWM auf PB2 - aber der sollte ja unabhängig
davon sein, oder?
1
TCCR0A = (1<<COM0A1) | (1<<WGM01) | (1<<WGM00);
2
// prescaler = 8 --> CLK 1Mhz; 255us
3
TCCR0B = (1<<CS01);
Interrupts sind keine aktiv.
Ich hab echt keine Ahnung woran das liegen könnte - und würde mich
freuen über Ideen und Vorschläge.
Kann gern auch noch mehr Code posten - bin aber irgendwie sicher, dass
es an der Optimierung liegt.
Vielen Dank schonmal
Benni
pschyrum schrieb:> Kann gern auch noch mehr Code posten - bin aber irgendwie sicher, dass> es an der Optimierung liegt.
Immer ein compilerbares Minimalprogramm, das den Fehler enthält! Fast
immer liegt der Fehler woanders.
pschyrum schrieb:> Der Assembler-Code macht eigentlich auch das richtige:
Tja, in dem Fall ist dein Tiny kaputt. Was anderes kommt ja nicht in
Frage...
Oliver
pschyrum schrieb:> Ich hab echt keine Ahnung woran das liegen könnte - und würde mich> freuen über Ideen und Vorschläge.> Kann gern auch noch mehr Code posten - bin aber irgendwie sicher, dass> es an der Optimierung liegt.
Das kann schon sein. Aber nicht an dieser Stelle. Wie du ja selber
siehst, hat der Compiler exakt das umgesetzt, was du programmiert hast.
Du solltest dir im Übrigen solche Rundumschlag-Portzugriffe abgewöhnen.
Wenn du nur die Bits 0 bis 5 beeinflussen willst, dann tu auch genau
das. Wir wissen nicht, was sonst noch so alles an den restlichen
Port-Pins hängt, und was daher noch alles ungewollt umgestellt wird.
PORTB = ( PORTB & ~0x03 ) | ((byteA>>6) & 0x03);
PORTA = ( PORTA & ~0x3F ) | (byteA & 0x3F );
so ist es sauber. Es werden nur die tatsächlich benutzten Bits
verändert. Alle anderen bleiben gleich. Und wenn du auch noch Interrupts
im Spiel hast, dann sollte das ganze dann auch noch unter Interrupt
Sperre laufen (ok, hast du nicht).
Und byteA sollte besser ein unsigned char sein oder ein uint8_t.
woah! Stark, das ging ja extrem schnell.
Besten Dank für die Antworten!
Kritik am Code ist immer erwünscht :), habe das mal soweit angepasst und
alles unwesentliche entfernt.
// clear OC0A on Compare match (COM0A1) and FAST PWM (WGM01 WGM00)
78
TCCR0A = (1<<COM0A1) | (1<<WGM01) | (1<<WGM00);
79
// prescaler = 8 --> CLK 1Mhz; 255us
80
TCCR0B = (1<<CS01);
81
// set Timer0 value to 0
82
TCNT0 = 0;
83
84
uint8_t message[MAX_CHARS];
85
uint16_t counter = 0;
86
uint8_t byteA;
87
singleBits bits;
88
bits.set = 0;
89
90
// get message
91
read_message(message);
92
93
while(1) {
94
95
// switch on leds
96
if ((counter == DISTANCE*4) & (bits.set == 0)) {
97
byteA = calculate_byte(message);
98
// 2 highest bits to PORTB
99
PORTB = (PORTB & ~0x03) | ((byteA>>6) & (0x03));
100
// 6 remaining bits to PORTA
101
PORTA = (PORTA & ~0x3F) | (byteA & 0x3F);
102
bits.set = 1;
103
}
104
105
// switch of leds
106
if (counter == 4*REFRESH_RATE + DISTANCE*4) {
107
PORTA = (PORTA & ~0x3F);
108
PORTB = (PORTB & ~0x03);
109
counter = 0;
110
bits.set = 0;
111
}
112
113
if (TIFR0 & (1<<TOV0)) {
114
// set overflow flag
115
TIFR0 |= (1<<TOV0);
116
counter += 1;
117
}
118
}
119
}
Die Beschaltung ist eigentlich super einfach - die LEDS werden mit der
Anoden an jew. einen Pin, mit der Katode alle zusammen an' Kollektor vom
Transistor, der von PB2 angesteuert wird.
Bin schon gespannt was da falsch sein könnte ..
Besten Dank nochmal!
mfg
Benni
Zwei kleine Anmerkungen, auch wenn sie auf die Funktion des Programms
keinen Einfluss haben dürften:
1
if((counter==DISTANCE*4)&(bits.set==0))
Das wird zwar funktionieren, weil durch die '==' entweder 0 oder 1 links
und rechts des &-Operators steht, aber die logische Verknüpfung sollte
dennoch mit && gemacht werden.
1
// set overflow flag
2
TIFR0|=(1<<TOV0);
Du willst das Flag hier sicher löschen, gesetzt wird es ja durch den
Timer Overflow von selbst. Um es zu löschen, sollte die Anweisung
1
// clear overflow flag
2
TIFR0=(1<<TOV0);
lauten, also ohne die "Veroderung". Spielt in deinem speziellen Fall
keine große Rolle, da du keine anderen Flags aus dem TIFR0-Register
betrachtest, durch die Veroderung würden die aber - sofern sie gesetzt
sind - durch deinen Befehl gelöscht werden.
Felix Pflaum schrieb:> Zwei kleine Anmerkungen, auch wenn sie auf die Funktion des Programms> keinen Einfluss haben dürften:>>
1
if((counter==DISTANCE*4)&(bits.set==0))
> Das wird zwar funktionieren, weil durch die '==' entweder 0 oder 1 links> und rechts des &-Operators steht, aber die logische Verknüpfung sollte> dennoch mit && gemacht werden.>>>
1
// set overflow flag
2
>TIFR0|=(1<<TOV0);
> Du willst das Flag hier sicher löschen, gesetzt wird es ja durch den> Timer Overflow von selbst. Um es zu löschen, sollte die Anweisung>
1
// clear overflow flag
2
>TIFR0=(1<<TOV0);
> lauten, also ohne die "Veroderung". Spielt in deinem speziellen Fall> keine große Rolle, da du keine anderen Flags aus dem TIFR0-Register> betrachtest, durch die Veroderung würden die aber - sofern sie gesetzt> sind - durch deinen Befehl gelöscht werden.
Ich fürchte das hast Du Dich vertan.
Es ist genau umgekehrt. Die ursprüngliche Anweisung
1
// set overflow flag
2
TIFR0|=(1<<TOV0);
würde andere Bits al TOV0, sofern sie gesetzt sind auch gesetzt lassen
und sofern sie gelöscht sind, sie auch gelöscht lassen.
Deine neue Anweisung würde aber Bits löschen die gesetzt sind.
Abgesehen davon ist der ursprüngliche (also nicht von Dir stammende)
Kommentar falsch. Mit dem schreiben einer 1 wird das TOV0-Flag gelöscht
und nicht gesetzt.
Hmm schrieb:> Ich fürchte das hast Du Dich vertan.> Es ist genau umgekehrt. Die ursprüngliche Anweisung> // set overflow flag> TIFR0 |= (1<<TOV0);> würde andere Bits al TOV0, sofern sie gesetzt sind auch gesetzt lassen> und sofern sie gelöscht sind, sie auch gelöscht lassen.>> Deine neue Anweisung würde aber Bits löschen die gesetzt sind.>> Abgesehen davon ist der ursprüngliche (also nicht von Dir stammende)> Kommentar falsch. Mit dem schreiben einer 1 wird das TOV0-Flag gelöscht> und nicht gesetzt.
Nein, habe ich nicht. Interrupt-Flags löscht man tatsächlich ohne die
Oder-Anweisung. Nachzulesen ist das u.a. hier:
http://www.mikrocontroller.net/articles/AVR_Checkliste#Flag_richtig_gel.C3.B6scht.3F
Hintergrund: Man löscht das Flag durch Schreiben einer 1 ins Register.
Nun ist die Anweisung
1
TIFR0|=(1<<TOV0);
gleichbedeutend mit:
1
TIFR0=TIFR0|(1<<TOV0);
Man schreibt damit eine 1 an jede Stelle, an der vorher schon eine 1
stand und zusätzlich auch an die durch TOV0 bezeichnete Stelle, löscht
also alle gesetzten Flags.
Schreibt man hingegen eine 0 ins Register, wo vorher eine 1 stand,
bleibt die 1 stehen. Das Flag-Register verhält sich hier einfach anders
als die PORT- oder DDR-Register.
das stimmt wohl nur bei ausgeschalteter Optimierung.
Mit Optimierung wird doch
1
TIFR0|=(1<<TOV0);
durch einen Bit-Setz-Befehl (sbi) ersetzt, wenn das möglich ist.
In diesem Fall würde sogar das erwartete Ergebnis eintreten und das
Programm korrekt arbeiten; böse Falle.
hey,
danke schonma' für die Antworten.
Oops, dann ohne das '|', wieder was gelernt!
Das 'clear flag' ist natürlich der richtige Kommentar ;) - ebenso müsste
es 'brightness'heißen nicht 'clarity' :P
Irgendwelche Ideen, was falsch sein könnte?
Gerade habe ich noch etwas rumprobiert: Wenn ich
1
message[0] = 1;
setze, dann geht gar nichts.
Setze ich
1
message[0] = 0;
geht die erste Blinkfolge, die folgenden aber nicht.
Setze ich nur PORTA oder nur PORTB gehen alle Blinkfolgen richtig, egal
was in message[0] steht.
Hö?
Faszinierend wie mich der avrgcc austrickst ;)
Noch irgendwelche Hinweise, Ideen? Würde mich sehr freuen!
mfg
Benni
Josef D. schrieb:> In diesem Fall würde sogar das erwartete Ergebnis eintreten und das> Programm korrekt arbeiten; böse Falle.
Ist im Datenblatt nicht explizit ausgewiesen. SBI verhält sich in
den allermeisten Fällen schon genauso wie PORT = PORT | bit, also
als komplettes read-modify-write, einschließlich der entsprechenden
Seiteneffekte.
Ob das genau hier auch so ist, müsste man im Zweifelsfalle
ausprobieren.
Das scheint sogar je nach CPU unterschiedlich zu sein
(Eine Fußnote aus dem Datenblatt zum AtMega328, doc827-rev1209, S.535):
"3. Some of the Status Flags are cleared by writing a logical one to
them. Note that, unlike most other AVRs, the CBI and SBI
instructions will only operate on the specified bit, and can therefore
be used on registers containing such Status Flags. The
CBI and SBI instructions work with registers 0x00 to 0x1F only."
und hängt vielleicht hiermit zusammen (S. 78):
"13.2.2 Toggling the Pin
Writing a logic one to PINxn toggles the value of PORTxn, independent on
the value of DDRxn.
Note that the SBI instruction can be used to toggle one single bit in a
port."
pschyrum schrieb:> Noch irgendwelche Hinweise, Ideen? Würde mich sehr freuen!
Was genau passiert in der Funktion calculateByte(...)? Kommt da das
heraus, was herauskommen soll?
Wenn ich "message" mit {1, 0, 1, 255} initialisiere und die Funktion
calculateByte(message) 16-mal aufrufe, erhalte ich als Ergebnis: 255 145
110 31 104 136 104 31 255 145 110 0 255 145 110 31.
hey,
nun, eigentlich kommt schon das raus was soll - vorausgesetzt ich
schreibe das Ergebnis nur auf einen Port.
calculate_byte funktioniert super einfach - die Adresse von message wird
übergeben - für jeden Eintrag wird das entsprechende Array aus Bytes
durchgegangen und pro Aufruf ein Byte übergeben.
Ich habs mal sehr ausführlich dokumentiert.
1
// Arrays, die die jeweiligen Muster repräsentieren.
2
// Der erste Wert ist jeweils die Breite des Muster bzw. die Länge
pschyrum schrieb:> Könnte das auch an der avrgcc Version liegen? Oder am (noch) nicht> optimalen __flash ?
Ich weiß nicht, wie gut __flash implementiert ist und ob da noch Fehler
drinnen sind. Dein zugriff geht ja auch einmal indirekt übers Flash.
Probiers halt aus. Nimm das __flash raus und sieh nach, ob sich was
ändert.
ich hab mir grad die von Felix geposteten Dezimalzahlen ins Binäre
konvertiert und angeschaut - entspricht genau den richtigen Bitmuster
-.-
Zwischendurch habe ich noch etwas weiterprogrammiert, an einer anderen
Stelle. Nach dem Übertragen hat nicht nur diese Funktion (analog -
digital Konvertierung die die Blinkfrequenz anpasst) funktioniert - nein
es ging auch plötzlich der Rest.
Der neu hinzugekommene Teil nimmt allerdings in keinster Weise Einfluss
auf das vorhandene.
Da ich dann vollkommen verwirrt war, habe ich den hier von mir
geposteten Code wieder kompiliert und übertragen. Er ging. HMPF.
avrgcc müsste eigentlich den gleichen Assemblercode erzeugt haben - da
sich nichts geändert hat. Trotzdem geht es. Die zusätzliche Beschaltung
habe ich rückgängig gemacht.
Ich hätte ja gesagt - Tiny Programmiergerät Schaltung selbst kaputt.
Aber ohne Optimierung ging es zuvor immer.
Jetzt hab ich grad mal meine avrgcc Version gecheckt - bin etwas
erstaunt über das prerelease..
"gcc-Version 4.8.0 20130502 (prerelease) (GCC)"
Fehler dort zu suchen?
Danke in jedem Fall für all die Mühe!
pschyrum schrieb:> Fehler dort zu suchen?
Sehr wahrscheinlich nicht. Inzwischen ist 4.8.0 allerdings auch
freigegeben worden, kannst du also auch upgraden, wenn du willst.
Josef D. schrieb:> Note that, unlike most other AVRs, the CBI and SBI> instructions will only operate on the specified bit, and can therefore> be used on registers containing such Status Flags.
Mir ist noch kein AVR untergekommen, bei dem das nicht so wäre.
Oliver
Oliver schrieb:> Mir ist noch kein AVR untergekommen, bei dem das nicht so wäre.
Das war anfangs anders: "Some of the status flags are cleared by writing
a logical one to them. Note that the CBI and SBI instructions will
operate on all bits in the I/O register, writing a one back into any
flag read as set, thus clearing the flag."
Josef D. schrieb:> Das scheint sogar je nach CPU unterschiedlich zu sein
Streng genommen darf bei CPUs mit der neuen Eigenschaft von SBI/SBI ein
C Compiler eine |= Operation bei volatilem linken Operanden nicht mehr
mit SBI implementieren. Da hat man wohl einen 8051-Kämpen ran gelassen,
die sind diese Form klammheimlicher Standardverletzung gewohnt.
A. K. schrieb:> Da hat man wohl einen 8051-Kämpen ran gelassen,> die sind diese Form klammheimlicher Standardverletzung gewohnt.
Was hast du denn heute geraucht? ;-)
Der AVR-GCC hat einfach keine Kennung davon, dass sich da
irgendwelche CPUs subtil anders verhalten. Schließlich erzählt
Atmel das ja auch niemandem von denen, die da am GCC basteln.
Das Einzige, bei dem dann klar war, dass es deutliche(re)
Unterschiede gibt, ist die Xmega-Serie. Alle vorangegangenen
CPUs implementieren im GCC mehr oder minder das gleiche CPU-Modell
(modulo der verschiedenen Speichergrößen und davon abhängigen
Befehlsvielfalt bzw. der CPUs, die MUL und word-Befehle enthalten).
Jörg Wunsch schrieb:> Was hast du denn heute geraucht? ;-)
Das Kraut heisst "Pedant". Keine Sorge, das vergeht wieder. ;-)
> Der AVR-GCC hat einfach keine Kennung davon, dass sich da> irgendwelche CPUs subtil anders verhalten.
Keine blasse Ahnung von der Maschine haben ist für einen Compilerbauer
eine äusserst schwache Entschuldigung. ;-)
Bei den Compilern für 51er ist das ausserdem Vorsatz. Da wars nie
anders: R-M-W Befehle lesen das Ausgaberegister (vgl PORTx),
aufgedröselt wird jedoch den Portzustand (vgl PINx) gelesen. Bei (fast)
Open Drain Ports ist der Unterschied recht bedeutsam.
A. K. schrieb:> Keine blasse Ahnung von der Maschine haben ist für einen Compilerbauer> eine äusserst schwache Entschuldigung.
"Keine blasse Ahnung" ist aber auch 'ne mächtige Übertreibung
dafür, dass offenbar einzelne CPUs (und dann teilweise auch noch
nur auf einzelnen Ports) anders implementiert worden sind als
andere.