Hallo zusammen,
ich möchte mit C auf dem ATtiny2313 ein IR-Signal (NEC) erzeugen.
Folgenden Ansatz hab ich mir überlegt (bzw. aus diversen Forenbeiträgen
etc. zusammengesucht):
- Frequenz von 38 kHz würde ich über PWM generieren. Muss/soll ich einen
externen Quarz benutzen, um die notwendige Genauigkeit zu erreichen?
Welcher ist zu empfehlen?
- Puls- und Pausephasen würde ich über einen Counter mit Prescaler 256
terminieren. Bei Erreichen des Compare-Wertes dann PWM aktivieren (Puls)
bzw. deaktivieren (Pause). Dazu sehe ich zwei Möglichkeiten:
--> Interrupt bei Erreichen Compare-Wert: Kann ich da einfach den
Compare-Wert setzen, den Timer starten und dann in den Sleep-Modus, um
das Programm "anzuhalten"? Oder entstehen bei Beginn sleep() und
Unterbrechung durch Interrupt zu lange oder "ungenaue" Verzögerungen?
--> Warteschleife: Timer auf 0, Timer starten und dann Schleife, bis
Counter = Anzahl Counts der entsprechenden Puls-/Pausephase
Die erste Möglichkeit fände ich etwas eleganter - aber funzt das? Was
würdet Ihr empfehlen? Oder sind beide Ansätze "Quatsch" und es gibt
einen besseren Weg?
VG Mike
@ Mike (Gast)
>- Frequenz von 38 kHz würde ich über PWM generieren.
genau.
> Muss/soll ich einen>externen Quarz benutzen, um die notwendige Genauigkeit zu erreichen?
Nein.
>- Puls- und Pausephasen würde ich über einen Counter mit Prescaler 256>terminieren.
Geht viel einfacher. Über den OIverflow Interrupt des Vounter, der auch
die PWM macht. Dann kann man sogar exakte Pulseanzahlen generieren.
>--> Interrupt bei Erreichen Compare-Wert: Kann ich da einfach den>Compare-Wert setzen, den Timer starten und dann in den Sleep-Modus, um>das Programm "anzuhalten"?
Warum?
> Oder entstehen bei Beginn sleep() und>Unterbrechung durch Interrupt zu lange oder "ungenaue" Verzögerungen?
???
>--> Warteschleife: Timer auf 0, Timer starten und dann Schleife, bis>Counter = Anzahl Counts der entsprechenden Puls-/Pausephase
Nein. Siehe Interrupt und Multitasking.
@Falk: Danke! Damit ich es richtig verstanden habe:
Also ich nutze einen einzelnen Timer abwechselnd im PWM-Modus (Puls) und
im Normal-Zähl-Modus (Pause) und setze den Compare auf die jeweilige
Anzahl der Flanken (nennt man das so?)? Also z. B. bei Startbit von 9000
us setze ich den Compare auf 342 = 9.000 * 38.000 / 1.000.000?
Nach Setzen und Starten des Timers mach ich dann sleep(), damit das
Programm auf den Interrupt wartet, und setze nach Rückkehr aus Interrupt
den Timer (Modus und Compare-Wert) für das nächste Halbbit (Puls bzw.
Pause)... usw.
Müsste gehen, oder?
VG Mike
@ Mike (Gast)
>Also ich nutze einen einzelnen Timer abwechselnd im PWM-Modus (Puls) und>im Normal-Zähl-Modus (Pause)
Nein, immer PWM. Die kann man ja zu Pin hin abschalten, mit den COMxy
Bits. Der Zähler läuft immer durch, dadurch hat man eine Zeitbasis.
> und setze den Compare auf die jeweilige>Anzahl der Flanken (nennt man das so?)?
Nein. Du zählst in der ISR die Anzahl Pulse, die gesendet werden sollen
bzw. Pause sein soll.
>Müsste gehen, oder?
Naja, etwas aufgeräumter. Lies die Artikel und denk drüber nach.
Danke, verstehe es (hoffentlich) langsam - Artikel hab ich gelesen und
nochmal drüber nachgedacht :-) als Einsteiger fällt mir nur die Anwenung
der Theorie auf's eigene Problem manchmal etwas schwer - also würde es
dann so angehen:
- Phasenkorrekter PWM (damit An/Aus der IR LED gleich lange sind) mit
Presclaler und Compare-Wert so, dass 38 kHz erzeugt werden -
automatischer Toogle des Pin und Interrupt bei Compare Match geht beides
"parallel", oder muss ich Pin in der ISR "manuell" umschalten?
- Hauptprogramm: Schleife über 32 Datenbits des NEC Befehls, pro
Darenbit dann:
--> PWM an LED Pin für Pulsphase, an unbenutzten Pin für Pausephase
--> Variable für gewünschte Gesamtzahl der Pulse setzen
--> in ISR Anzahl der Pulse hochzählen
--> In Hauptprogramm warten, bis Anzahl Pulse = gewünchte Gesamtzahl
--> nächstes (Halb)Bit senden
Anmerkung: Während Senden müssen keine sonstigen Programmaktivitäten
durchgeführt werden - dann ist die hohe Interruptfrequenz kein Problem,
oder?
VG Mike
Mike schrieb:> - Phasenkorrekter PWM (damit An/Aus der IR LED gleich lange sind) mit> Presclaler und Compare-Wert so, dass 38 kHz erzeugt werden -> automatischer Toogle des Pin und Interrupt bei Compare Match geht beides> "parallel", oder muss ich Pin in der ISR "manuell" umschalten?
Was habt ihr nur immer mit der Phasen korrekten PWM. Ist die sowas wie
selig machend? Oder klingt die nur einfach gut? So von wegen "correct
ist immer gut"
Fast-PWM mit Vorgabe des Top-Wertes
Den Compare Wert der PWM wird auf die Hälfte eingestellt.
Zusätzlich wird an diesen Compare Wert eine ISR geklemmt,
In der ISR wird ein SOftware-Zähler runtergezählt. Ist der bei 0
angelengt, dann koppelt er mittels COM Bits die LED vom Timer weg und
schaltet sie aus.
Hauptprogramm
Für jedes Bit
ist es 1 (LED soll also leuchten)
Mittels COM Bits die LED an den Timer koppeln
LED einschalten
Verzögerungszeit in den Softwarezähler schreiben
warten bis der Softwarezähler auf 0 gefallen ist
> Anmerkung: Während Senden müssen keine sonstigen Programmaktivitäten> durchgeführt werden - dann ist die hohe Interruptfrequenz kein Problem,> oder?
Die ist sowieso kein Problem. IR-Übertragung ist doch keine
Uhrmacher-Arbeit. Da gibt es Toleranzen. Die 38kHz sollten einigermassen
genau passen (wegen dem optischen Filter vom Empfänger) aber in den
Pulsen bzw. Pausen hast du genügend Toleranzen.
Mike schrieb:> - Phasenkorrekter PWM (damit An/Aus der IR LED gleich lange sind) mit> Presclaler und Compare-Wert so, dass 38 kHz erzeugt werden -> automatischer Toogle des Pin und Interrupt bei Compare Match geht beides> "parallel", oder muss ich Pin in der ISR "manuell" umschalten?
Wie Karl Heinz schon bemerkte, ist das nicht notwendig. Im Gegenteil,
ich denke der Empfänger wird ein TSOP sein und die erlauben
Tastverhältnisse von unter 20%. Das hat den Vorteil, das man die IR
Diode mit einem höheren Strom senden lassen kann, was die Reichweite
deutlich verbessert.
Gruß Willi
Karl Heinz Buchegger schrieb:> Die 38kHz sollten einigermassen genau passen (wegen dem optischen Filter> vom Empfänger)
Was haben denn die 38kHz mit dem optischen Filter zu tun?
Zu dem optischen Filter muss die Wellenlänge der IR-LED passen. Bei
einem TSOP31238 hat der Filter eine FWHM-Breite von etwa 180nm (im
Datenblatt Fig. 10 - Sensitivity vs. Ambient Temperature).
Die 38kHz der IR-LED müssen zu der Mittenfrequenz des elektrischen
Bandpassfilters im Empfänger passen. Die Bandbreite beträgt beim
TSOP31238 z.B. Δf(3dB)=f0/10 (Fig. 5 - Frequency Dependence of
Responsivity)
Michael schrieb:> Karl Heinz Buchegger schrieb:>> Die 38kHz sollten einigermassen genau passen (wegen dem optischen Filter>> vom Empfänger)>> Was haben denn die 38kHz mit dem optischen Filter zu tun?>> Zu dem optischen Filter muss die Wellenlänge der IR-LED passen.
Autsch.
Ja, hast recht. Da hab ich nicht gut genug nachgedacht.
Ich brauch einen Kaffee.
Ich habe einen ersten Testaufbau gemacht (siehe Foto). Links ist der
Sender, rechts der Empfänger - wenn das IR-Signal blockiert wird, geht
die blaue LED an (also eine Art Lichtschranke).
Hier der Code des Senders:
<c>int main(void)
{
// PB0 und PB1 als Ausgang
DDRB = (1<<PB0);
// OC0A --> PIN PB0
OCR0A = 8; // Dauer der Low-/High-Phase
// PWM Modus konfigurieren
TCCR0A = (1<<COM0A0) | (1<<WGM00);
// T/C-Vorteiler = Clock
TCCR0B = (1<<WGM02) | (1<<CS00);
while(1)
{}
}</c>
Und hier der Code des Empfängers:
<c>int main(void)
{
// PB0 als Ausgang (blaue LED)
DDRB = (1<<PB0);
// PB1 als Eingang (IR LED)
PORTB = (1<<PB1);
while(1)
{
if (!(PINB & (1<<PB1))) // IR Signal empfangen
{
// blaue LED aus
PORTB = (1<<PB1) | (1<<PB0);
}
else
{
// blaue LED an
PORTB = (1<<PB1);
}
}
}</c>
Als erster Test funktioniert das soweit, aber ein paar Verstädnisfragen:
- Aktuell nutze ich phasenkorrekten PWM mit Vorteiler = 0 und Compare
Wert = 8. Prozessortakt sollte 9,6 MHz sein, wobei bei den Fuses noch
ein Flag "Divide clock by 8 internally" gesetzt ist. Es funktioniert,
aber wie/warum komme ich auf die (annähernd) richtige Frequenz von 40kHz
des IR-Empfängers?? Eine Messung ergibt übrigens 68kHz? Es geht, aber
ich verstehs nicht....
- Wie mach ich das über Fast PWM genau?
Karl Heinz Buchegger schrieb:> Fast-PWM mit Vorgabe des Top-Wertes>> Den Compare Wert der PWM wird auf die Hälfte eingestellt.>> Zusätzlich wird an diesen Compare Wert eine ISR geklemmt,>> In der ISR wird ein SOftware-Zähler runtergezählt.
Der TOP-Wert kommt doch in OCR0A (oder eben alternativ 0xFF). Aber ist
das nicht auch der Compare-Wert? Mein Verständnisproblem ist wohl, wie
ich den Top-Wert und den Compare-Wert getrennt setzen kann (Compare-Wert
= 0,5 Top-Wert).
Den Rest über die ISR hab ich soweit verstanden, hoffe ich. Werde mal
ans Programmieren gehen...
Karl Heinz Buchegger schrieb:> Die 38kHz sollten einigermassen>> genau passen (wegen dem optischen Filter vom Empfänger) aber in den>> Pulsen bzw. Pausen hast du genügend Toleranzen.
Da ich den Empfänger auch selbst programmieren möchte, habe ich die
Toleranzen eh in der Hand, oder? Anbei ein allererster, ungetesteter
Code-Entwurf für die Entschlüsselung des NEC-Signals (habe da 20%
Toleranz für die Phasenlängen versucht einzubauen) --> Interrupt bei
Änderung am Eingangsport, dann Counter hochzählen und die einzelnen
Status des Signals (Startbit, Datenbit, Endebit) durchlaufen - so
zumindest die Theorie...
Karl Heinz Buchegger schrieb:> Für jedes Bit>> ist es 1 (LED soll also leuchten)>> Mittels COM Bits die LED an den Timer koppeln>> LED einschalten
Beim NEC-Signal sind m. W. Pulslänge bei 0- und 1-Bit gleich. Auf den
Puls folgt bei 0-Bit eine kurze Pause, bei 1-Bit eine lange Pause - aber
ändert ja nichts am Prinzip. "mittels COM Bits LED an Timer koppeln"
heißt, TCCR0A = (1<<COM0A0), oder? Und zum Entkoppeln dieses Bit
löschen, richtig?
Nochmal vielen Dank an alle! Ich geh jetzt an die Arbeit, könnte also im
Laufe der Nacht noch die ein oder andere Frage kommen :-)
VG Mike
Mike schrieb:> Da ich den Empfänger auch selbst programmieren möchte, habe ich die> Toleranzen eh in der Hand, oder?
Wenn du als Detektor auf der Empfängerseite irgendetwas in Richtung
TSOP31238 verwendest, was einem wegen der Umgebungslichtunterdrückung
das Leben sehr vereinfacht, hat der µC mit den 38kHz gar nichts mehr zu
tun. Da kommen das Signal schon ohne die Trägerfrequenz raus.
@ Michael: Ja, ich verwende einen TSOPxxx mit 40 kHz. Aber auf der
Senderseite müsste ich ja annähernd auch auf die 40kHz kommen - komme
ich aber weder rechnerisch (9,6 MHz 8 (2*8)??)
--> 8 wegen "Divide clock by 8 internally"
--> 2*8 wegen Rauf- und Runterzählen bis Compare-Wert = 8
--> da komme ich auf 75 kHz
noch gemäß Test mit Messgerät (~ 68 kHz)
Wenn ich Compare-Wert erhöhe (um Frequenz zu verringern), reagiert die
LED (bei Unterbrechung der "Lichtschranke") nicht mehr. Und das versteh
ich noch nicht ganz....
Empfängerseite ist mir klar, dass der TSOP die "Frequenz" bereits im
Bauch hat. Ich hatte was gelesen, dass der TSOP ein invertiertes Signal
ausgibt --> heißt das, wenn IR empfangen wird, ist Eingang am Pin (TSOP
OUT) low - und umgekehrt?
Ich habe versucht, den von Euch vorgeschlagenen Ansatz (Zähler in ISR
hochzählen, in Hauptprogramm LED an PIN koppeln bzw. entkoppeln bei
Erreichen Schwellenwert) in ein kleines Programm umzusetzen:
<c>#include <avr/io.h>
#include <avr/delay.h>
#include <avr/interrupt.h>
volatile long int interrupt_count;
int main(void)
{
// Flag zum Toogeln der IR LED
unsigned char led_an_aus;
// PB0 als Ausgang --> Anschluss IR LED
DDRB = (1<<PB0);
// OC0A --> PIN PB0 mit PWM
OCR0A = 8; // Dauer der Low-/High-Phase
// PWM Modus konfigurieren
TCCR0A = (1<<COM0A0) | (1<<WGM00);
// T/C-Vorteiler = Clock
TCCR0B = (1<<WGM02) | (1<<CS00);
// Interrupt bei T/C Compare Match
TIMSK0 = (1<<OCIE0A);
sei();
while(1)
{
// IR LED zwischen Puls und Pause umschalten
if (interrupt_count > 50000)
{
interrupt_count = 0;
if (led_an_aus == 0)
{
TCCR0A = (1<<COM0A0) | (1<<WGM00); // LED an Timer koppeln
led_an_aus = 1;
}
else
{
PORTB = (1<<PB0); // LED aus
TCCR0A = (1<<WGM00); // LED vom Timer entkoppeln
led_an_aus = 0;
}
}
}
}
ISR(TIM0_COMPA_vect)
{
interrupt_count++; // Interrupts zählen
}</c>
Allerdings sendet die IR LED ununterbrochen weiter... ich hätte
erwartet, dass es zu einem Umschalten etwa jede Sekunde käme...
Habt jemand eine Idee, wo der Fehler ist?
HIIILLFEE --- i werd verrückt... jetzt check ich's gar nicht mehr.
Habe das Programm leicht modifiziert:
- Umstellung auf Fast PWM
- initial IR LED nicht an Timer hängen
Erwartetes Verhalten:
- Wechsel IR-Signal an/aus etwa im Sekundentakt
Tatsächliches Verhalten:
- IR-Signal zunächst ca. 1-2 Sekunden aus
- dann IR-Signal an --> aber bleibt dann auch "für immer" an, d. h. wird
nicht mehr um-/ausgeschaltet
Es sieht so aus, als würde die Kopplung der IR LED an PWM über
(1<<COM0A0) das Interruptverhalten verändern bzw. der Interrupt wird
nicht mehr ausgeführt(???)
Ihr seid sicher, dass das Umschalten des PIN über PWM und Auslösen des
Interrupts parallel gehen, oder?! Wo ist dann bloß der Fehler
(wahrscheinlich ne dumme Sache, aber ich find's einfch nicht)... stöhn
Hier der aktualisierte Code:
<c>#include <avr/io.h>
#include <avr/delay.h>
#include <avr/interrupt.h>
volatile long int interrupt_count;
int main(void)
{
// Flag zum Toogeln der IR LED
unsigned char led_an_aus;
// PB0 als Ausgang --> Anschluss IR LED
DDRB = (1<<PB0);
// Compare-Wert setzen (Dauer der Low-/High-Phase)
OCR0A = 14;
// PWM Modus konfigurieren (mit LED am Timer)
TCCR0A = (1<<WGM00) | (1<<WGM01);
TCCR0B = (1<<WGM02) | (1<<CS00);
// Interrupt bei T/C Compare Match
TIMSK0 = (1<<OCIE0A);
sei();
while(1)
{
// IR LED zwischen Puls und Pause umschalten
if (interrupt_count > 30000)
{
cli();
interrupt_count = 0;
if (led_an_aus == 0)
{
// PWM Modus konfigurieren (ohne LED am Timer)
TCCR0A = (1<<WGM00) | (1<<WGM01);
TCCR0B = (1<<WGM02) | (1<<CS00);
PORTB = (1<<PB0); // LED aus
led_an_aus = 1;
}
else
{
// PWM Modus konfigurieren (mit LED am Timer)
TCCR0A = (1<<WGM00) | (1<<WGM01) | (1<<COM0A0);
TCCR0B = (1<<WGM02) | (1<<CS00);
led_an_aus = 0;
}
sei();
}
}
}
ISR(TIM0_COMPA_vect)
{
interrupt_count++; // Interrupts zählen
}</c>
Eine kleine Unschönheit - nicht mal ein echter Fehler ist noch im
Programm - der sollte aber nicht den beschriebenen Effekt haben:
Die abfrage
"if (interrupt_count > 30000)"
ist nicht davor geschützt das der Wert gerade in der ISR geändert wird.
Da könnte es also passieren das High und Low Wert nicht zusammenpassen.
Der Fehler sollte aber hier nur eine eventuell minimal falsche Zeit
sein.
Später kann dann der Interruptcount wohl auf den Typ Char geändert
werden, weil die Impulse eher so im Bereich 30 Zyklen des 40 kHz Signals
sind. Dann hat man das Problem nicht mehr.
Das Problem wird sein, dass die ISR zum hochzählen einfach zu lange
bracht. So wie es konfiguriert ist, kommt alle 14 Zyklen ein Aufruf der
ISR. Das ist einfach zu schnell, selbst wenn nur ein Bytewert
hochgezählt werden soll.
Man kann die Zahl der Interrupts noch auf die Hälfe reduzieren, indem
man den Timer bis 28 Zählen läßt, und das Signal als echtes PWM Signal
am Ausgang OCR0B zu erzeugt. Man hat denn ggf. auch die Wahl das ein/aus
Verhältnis für die LED anders (kürzer) zu wählen. Auch dann wird es mit
nur 28 Zyklen sehr knapp und bräuchte wohl ASM für die ISR, oder halt
einen höheren Takt von z.B. 2 MHz.
Wenn man mag, könnte man den Zähler vom USI-Teil nutzen um die Überläufe
von Timer0 zu zählen, aber der hat nur 4 Bit und wird eher auch nicht
ganz ausreichen. Erst mit Zählen der USI Überläufe ginge es dann.
Die einfachere Lösung wäre da wohl wirklich den Timer1 für die Länge der
Pulse und Pausen zu nutzen. Schließlich gibt es für ungenutzte HW kein
Geld zurück.
Es geht jetzt :-) "Empfangs-LED" (zeigt an, ob IR sendet oder nicht)
blinkt jetzt im Sekundentakt - was auch mit dem Interrupt-Counter
zusammen passt.
Grund für das "unerklärliche" Verhalten war wohl - ich trau mich's gar
nicht zu sagen - der fehlende Stützkondensator (allmählich kapier auch
ich, dass der wohl kein "nice-to-have" ist...) mann oh mann, wieder viel
Lehrgeld bezahlt
Hoffe mal, dass es mit dem eigentlichen Ziel (Codierung/Decodierung NEC
Signal) dann weniger schmerzvoll geht :-)
Danke für Euren Support & GUTE NACHT
Mike
Hallo zusammen,
ich habe nun ein kleines Programm geschrieben (siehe Anhang), das eine
4-Byte-Sequenz über IR sendet. Das Ganze funktioniert auch soweit (die
Dauer von Puls und Pause sind extra lange gewählt, damit ich auf der
Empfängerseite "mit dem Auge" sehe, ob es prinzipiell funktioniert).
Nur ein komisches Phänomen habe ich:
Wenn ich das Stopbit kurz einstelle (z. B. 500 Counts Puls / 500 Counts
Pause), sendet die IR LED danach ununterbrochen weiter - das letzte
"Abhängen" der LED über COM0A0=0 scheint also nicht zu funktionieren.
Wenn ich eine längere Dauer wähle (z. B. 50.000 Puls / 50.000 Pause)
geht es einwandfrei, und die Diode sendet dann am Schluss nicht mehr.
Hat jemand eine Idee, was das Problem sein könnte? Irgendwie scheint
sich das Programm zu "verhaspeln"?
VG Mike
PS: Auch sonstige Kommentare/Verbesserungsvorschläge zum Programm sind
natürlich willkommen...
PS: Noch komischer:
Wenn ich sendStopBit() im Hauptprogramm weglasse, und das letzte
Datenbit sehr kurz (z. B. 500 Puls/500 Pause) geht es, d. h. nach Senden
der Sequenz ist die LED "entkoppelt".
Wenn ich sendStupBit() mit der gleichen Länge (z. B. 500 Puls/500 Pause)
am Schluss drin lasse, wird die LED nicht "entkoppelt" --> sendet munter
mit 40 kHz "ewig" weiter
???
Um das Problem einzugrenzen und die Frage zu konkretisieren, habe ich
das Programm von allem "überflüssigen" Balast befreit. Verhalten ist
jetzt so:
sendBit(laengePulse, laengePause)
--> für laengePulese/laenge Pause > 40.000 funktioniert es - IR-Sender
ist abwechselnd an und aus; die Empfangs-LED blinkt im Sekundentakt
--> für laengePulese/laenge Pause < 30.000 funktioniert es NICHT -
IR-Sender ist ständig an; die Empfangs-LED entsprechend auch
Ich habe keinerlei Ahnung oder Ansatzpunkt, woran das liegen könnte...
hat jemand eine Idee? Sonst verlier ich komplett den Glauben ;-)
Danke Mike
entweder du machst das atomar (d.h. interrupt_count unter Interrupt
Sperre auslesen) oder du benutzt einen uint8_t zusätzlich, den du
problemlos atomar auslesen kannst.
interrupt_count--;// Interrupts (= IR Pulse) zaehlen
21
if(interrupt_count==0)
22
time_done=1;
23
}
24
}
Beachte: Das spart dir auch das dauernde Timer ein/ausschalten. Denn
solange interrupt_count auf 0 ist, macht die ISR de facto nichts.
Runterzählen von einem Vorgabewert bis auf 0 ist oft einfacher als
raufzählen und gibt eine bessere Logik. Aber hier dürfte konkret das
nicht atomare Abfragen von interrupt_count in main() das Problem sein.
Der Prozessor kann einen long int nicht in einem Aufwasch bearbeiten,
sondern muss das in mehrere 8-Bit Häppchen zerlegen. Wenn da
zwischendurch die ISR zum Zug kommt und dir den long int während der
Abfrage verändert, dann kann da beim Vergleich alles mögliche passieren.
Es ist wie wenn du eine Geschichte liest und während du aufs Klo gehst,
tauscht dir jemand das Buch aus. Plötzlich stimmt in deiner Geschichte
hinten und vorne nichts mehr.
Wieso eigentlich ein long? Können deine Wartezeiten auch negativ sein?
Hallo Karl Heinz,
erstmal vielen herzlichen Dank, dass Du Dir das Problem angeschaut hast!
Die Erklärung hört sich logisch an, wäre ich nicht drauf gekommen. Ich
habe das jetzt so wie beschrieben umgesetzt (denke ich), aber immer noch
das gleiche fehlerhafte Verhalten (bei Counts < ~40.000 bleibt die LED
im Dauersende-Modus). Unten nochmal das vollständige aktualisierte
Programm (zur Sicherheit, dass ich Deinen Ansatz auch richtig übernommen
habe).
Hast Du noch eine Idee? Sicherheitshalber habe ich die Anweisungen in
der ISR auch zwischen cli() und sei() gesetzt, damit während der ISR
kein Interrupt auftritt... aber hilft auch nix... seufz :-(
Karl Heinz Buchegger schrieb:> Wieso eigentlich ein long? Können deine Wartezeiten auch negativ sein?
nein, das ist eigentlich unnötig - zum Test benutze ich relativ große
(aber stets positive) Wartezeiten - später müssten die Puls- und
Pausephasen (gemäß NEC) so 560 us (= 22 Pulse/Interrupts) sein.
Hat zwar mit dem Problem (wahrscheinlich) nichts zu tun, aber
> Hast Du noch eine Idee? Sicherheitshalber habe ich die Anweisungen> in der ISR auch zwischen cli() und sei() gesetzt
Das machst du gleich wieder weg.
Das ist nicht 'sicherheitshalber'.
Denn mit dem Eintritt in die ISR werden die Interrupts sowieso gesperrt
und beim Verlassen der ISR werden sie wieder aktiviert. Der cli() macht
daher gar nichts, während dir der sei() einen Fehler einbrocken kann,
weil die Interrupts zu früh wieder freigegeben werden.
So, jetzt studier ich mal das Programm.
TCCR0A&=~(1<<COM0A0);// LED nicht an PWM gekoppelt
sicherheitshalber die LED ab.
Nur weil du den Pin vom Timer wieder entkoppelst, heißt das nicht, dass
der Pin danach 0 bzw. 1 ist. Du weißt nicht wie er steht, also sorg für
gesicherte Verhältnisse
1
// Pulse Phase
2
cli();
3
TCCR0A|=(1<<COM0A0);// LED an PWM gekoppelt
4
time_done=0;
5
interrupt_count=countPulse;
6
sei();
7
while(!time_done)
8
;
9
10
// Pause Phase
11
cli();
12
TCCR0A&=~(1<<COM0A0);// LED nicht an PWM gekoppelt
13
PORTB|=(1<<PB0);// und LED ausschalten
ob du jetzt den Pin auf 0 oder 1 setzen musst, um die LED auszuschalten,
weiß ich nicht. Hängt von deiner Verschaltung ab.
der Timer immer noch eine PWM erzeugt, dann würde ich auch mal ins Auge
fassen, dass der µC da zwischendurch abstürzt und das Programm wieder
von vorne anfängt.
Du hast doch am µC 100nF Blockkondensatoren verbaut, oder nicht?
Karl Heinz Buchegger schrieb:> Nur weil du den Pin vom Timer wieder entkoppelst, heißt das nicht, dass>> der Pin danach 0 bzw. 1 ist. Du weißt nicht wie er steht, also sorg für>> gesicherte Verhältnisse
ok, hab ich eingebaut. Wobei die IR LED ja komischerweise mit 40 kHz
weiterflackert, so dass es vermutlich nicht so wichtig wäre, ob sie
dauernd an oder aus wäre - aber sicher ist sicher - schalte ich aus.
Karl Heinz Buchegger schrieb:> Denn mit dem Eintritt in die ISR werden die Interrupts sowieso gesperrt>> und beim Verlassen der ISR werden sie wieder aktiviert. Der cli() macht>> daher gar nichts, während dir der sei() einen Fehler einbrocken kann,>> weil die Interrupts zu früh wieder freigegeben werden.
danke für die Erklärung - hab ich auch rausgenommen
Karl Heinz Buchegger schrieb:> Wo ist eigentlich deine Hauptschleife in main()?
Hatte ich keine - ich wollte nur senden und dann Schluss. Aber hast
Recht, brauch natürlich eine Schleife - ich hab jetzt nach dem Senden
ein while(1) ergänzt.
Karl Heinz Buchegger schrieb:> Du hast doch am µC 100nF Blockkondensatoren verbaut, oder nicht?
Ja, nachdem mich dieser dumme Fehler Samstag Nacht ca. 3 h gekostet hat,
werd ich es nicht mehr vergessen.
Karl Heinz Buchegger schrieb:> Seh ich das richtig, dass dein IR-LED keinen Vorwiderstand hat?
Ja, die LED verträgt (angeblich) 5 V - werde ich nochmal prüfen - aber
ohne Vorwiderstand sollte das Ding doch eher kaputt gehen, als korrekt
40 kHz zu senden, obwohl es Ruhe geben sollte
Die 40 kHz habe ich übrigens auch mit dem Voltmeter nachgemessen.
Karl Heinz Buchegger schrieb:> Wenn nach diesem hier // alles ausschalten>> TCCR0A = 0;>> TCCR0B = 0;>> TIMSK0 = 0;>> der Timer immer noch eine PWM erzeugt, dann würde ich auch mal ins Auge>> fassen, dass der µC da zwischendurch abstürzt und das Programm wieder>> von vorne anfängt.
Ja, vermutlich kommt das Programm nie an diese Stelle. Aber wo/warum
kann es vorher abstürzen? Das Programm ist ja nun ziemlich
"überschaubar", was kann da schief laufen?
Anbei ein Foto der aktuellen Schaltung und hier der aktuelle Code (immer
noch der gleiche Fehler, Mist)... bin echt am Verzweifeln :-(
sehr komisch finde ich auch, dass es mit Pulse/Pause > 50000 geht, und
bei kleineren Werten nicht... wofür könnte das ein Anhaltspunkt sein?
#include <avr/io.h>
#include <avr/delay.h>
#include <avr/interrupt.h>
// Zaehler fuer Anzahl Interrupts (= Anzahl LED-Impulse)
volatile long int interrupt_count;
volatile uint8_t time_done;
void main(void)
{
// Test
unsigned char i;
// PB0 als Ausgang (Toogle IR LED)
DDRB = (1<<PB0);
// Konfiguration Timer
// Phasenkorrekter PWM Vorteiler 1 Compare-Wert = 55
TCCR0A=(1<<WGM00);
TCCR0B=(1<<WGM02)|(1<<CS00);
OCR0A=55;
// Interrupt definieren
TIMSK0 = (1<<OCIE0A);
for (i=1;i<10;i++)
{
sendBit(50000,50000);
}
// alles ausschalten
TCCR0A = 0;
TCCR0B = 0;
TIMSK0 = 0;
PORTB |= (1<<PB0);
while(1)
{
}
}
void sendBit(long int countPulse,long int countPause)
{
// Pulse Phase
cli();
TCCR0A|=(1<<COM0A0); // LED an PWM gekoppelt
time_done = 0;
interrupt_count = countPulse;
sei();
while (!time_done);
// Pause Phase
cli();
TCCR0A&=~(1<<COM0A0); // LED nicht an PWM gekoppelt
PORTB |= (1<<PB0);
time_done = 0;
interrupt_count = countPause;
sei();
while (!time_done);
}
ISR(TIM0_COMPA_vect)
{
if(interrupt_count > 0)
{
interrupt_count--;
if (interrupt_count == 0)
time_done = 1;
}
}
PS: Noch ein Hinweis, um dem Problem (vielleicht) auf die Schliche zu
kommen:
In dem Ursprungsprogramm (Senden Startbit, Senden 32 Datenbits, Senden
Stopbit) hat es funktioniert (d. h. IR Signal nach Senden auch wirklich
beendet), wenn ich das Senden des Stopbits weggelassen habe - und das
obwohl das letzte Datenbit auch eine sehr kurze Pulse/Pause Dauer hat (<
5000) - für mich bedeutet dieses Programmverhalten eigentlich nur noch
mehr Verwirrung, aber vielleicht kannst Du damit was anfangen? ... danke
...
Mike schrieb:>> Seh ich das richtig, dass dein IR-LED keinen Vorwiderstand hat?>> Ja, die LED verträgt (angeblich) 5 V - werde ich nochmal prüfen - aber> ohne Vorwiderstand sollte das Ding doch eher kaputt gehen
Na, na, na. So schnell schießen die Preussen nicht!
Da gehören immer 2 dazu.
Der eine, der Verbraucher, der eine bestimmte Strommenge beziehen will.
Und der andere, die Quelle, die diese Strommenge liefern können muss.
An einem idealen Netzgerät brennt deine LED ohne Vorwiderstand durch,
weil zuviel Strom durchjagt. Ein ideales Netzgerät kann unendlich viel
Strom liefern. Aber dein Tiny ist kein ideales Netzgerät. Der kann nur
eine bestimmte Strommenge abgeben. Ist er an dieser Grenze angelangt,
dann bricht die Ausgangsspannung ein. Irgendetwas wird überlastet. Und
eine einbrechende Spannung ist prinzipiell nicht gut.
Du kannst ja auch Gegenstände hochheben, kannst also mit deinem Arm eine
Kraft aufbringen. Kein Problem. Die Mineralwasserflasche kannst du
problemlos um 20 Zentimeter anheben. Ist der Gegenstand aber zu schwer
(ein PKW), dann wird das nichts mit dem Hochheben um 20 Zentimeter,
sondern statt dessen bricht deine Hubhöhe auf 0 ein. Der PKW verlangt
dir eine bestimmte Kraft zum Hochheben ab, du kannst aber nicht
'liefern'.
> Ja, vermutlich kommt das Programm nie an diese Stelle. Aber wo/warum> kann es vorher abstürzen? Das Programm ist ja nun ziemlich> "überschaubar", was kann da schief laufen?
Spannungseinbruch in der Versorgungsspannung.
Lässt sich aber leicht feststellen. Verkabel eine 2.te LED (rot, grün,
gelb. Aber bitte mit Vorwiderstand) an einen Portin und lass dir diese
am Anfang in main() eine zeitlang einschalten. (kannst ruhig _delay_ms
dafür nehmen).
( PortPin - LED - 330 Ohm - Vcc )
1
intmain()
2
{
3
...
4
5
DDRB|=(1<<PB1);
6
PORTB&=~(1<<PB1);
7
_delay_ms(500);
8
PORTB|=(1<<PB1);
9
_delay_ms(500);
10
11
....
Diese LED darf nur ein einziges mal nach dem Flashen für kurze Zeit
leuchten. Leuchtet sie auch zwischendurch irgendwann mal, dann wurde der
µC resettet. Aus welchem Grund auch immer. Um den Grund kümmert man
sich, wenn man weiß, dass es etwas gibt worum man sich kümmern muss.
Erst mal muss man feststellen, ob ....
Auf Deutsch:
Lass den µC dir helfen, zu verstehen was er eigentlich macht.
Man sollte nie unterschätzen, was man mit dem sinnvollen und überlegten
Einsatz von ein paar zusätzlichen LED an Informationen gewinnen kann :-)
Karl Heinz Buchegger schrieb:> Diese LED darf nur ein einziges mal nach dem Flashen für kurze Zeit>> leuchten. Leuchtet sie auch zwischendurch irgendwann mal, dann wurde der>> µC resettet. Aus welchem Grund auch immer. Um den Grund kümmert man>> sich, wenn man weiß, dass es etwas gibt worum man sich kümmern muss.>> Erst mal muss man feststellen, ob ....
Habe die "Test-LED" in die Schaltung und ins Programm eingebaut: Sie
blinkt tatsächlich nur am Anfang einmal kurz - und das bei einem
Pulse/Pause-Wert von 50.000 (LED hört am Ende auf zu senden) und bei
einem Pulse/Pause-Wert von 10.000 (LED hört am Ende nicht auf zu
senden). Kann man daraus folgern, dass das Programm nicht abstürzt (da
es nicht neu startet)?
Als zusätlichen Test habe ich das Blinken der "Test-LED" in die Schleife
hinter sendBit() eingebaut
Mike schrieb:> for (i=1;i<10;i++)>> {>> sendBit(50000,50000);>> }
Da ist es nun so, dass bei 50.000 die LED 10 mal blinkt (1 mal am
Anfang, 9 mal innerhalb Schleife) - und bei 10.000 nur 1 mal am Anfang.
Das würde heißen, dass das Programm aus dem ersten Aufruf von
sendBit(10000,10000) schon nicht mehr zurückkommt. "Nicht mehr
zurückkommen" würde wohl bedeuten, dass time_done nie 1 wird. Und das
wiederum, dass interrupt_count nie 0 wird. Und das wiederum, dass
1) die ISR nicht (häufig genug) angesprungen wird oder
2) das Runterzählen von interrupt_count die 0 "überspringt" (sozusagen
von 1 direkt auf -1, z. B.) ????
Fall 1) Würde bedeuten, dass Timer (warum auch immer) nicht mehr
weiterläuft oder der Interrupt bei Erreichen des Compare-Wertes nicht
mehr ausgelöst wird bzw. ISR nicht mehr aufgerufen wird
Fall 2) Könnte es sein, dass interrupt_count--; zweimal (oder mehrmals)
ausgeführt wird, ohne dass es zur Prüfung interrupt_count == 0 kommt?
Aber wenn während ISR keine Interrupts möglich sind, sollte das
ausgeschlossen sein.
Hast Du einen Erklärungsansatz?
Karl Heinz Buchegger schrieb:> LED ohne Vorwiderstand
Als LED nutze ich "IR-Sendediode SFH 409 Osram Components SFH 409
Gehäuseart T 1 Wellen-Länge 950 nm" - im Datenblatt steht etwas von
Sperrspannung 5 V - ich betreibe die Schaltung aktuell an 4,5 V -
brauche ich eventuell trotzdem Vorwiderstand oder sonstiges Bauteil?
@ Mike (Gast)
>Als LED nutze ich "IR-Sendediode SFH 409 Osram Components SFH 409>Gehäuseart T 1 Wellen-Länge 950 nm" - im Datenblatt steht etwas von>Sperrspannung 5 V - ich betreibe die Schaltung aktuell an 4,5 V -
Ja und? Deine LED soll leuchten, nicht sperren.
>brauche ich eventuell trotzdem Vorwiderstand oder sonstiges Bauteil?
JA! Siehe LED!
Ähm.
Mir fällt da gerade was anderes an deinem Code auf :-)
Du ignorierst die ganze Zeit eine Compiler-Warnung!
Hier
1
sendBit(50000,50000);
müsste dir dein Compiler eine Warnung über eine implizit Deklaration
geben. Wenn dich der Compiler nicht warnt, dann dreh die
Compiler-Warnungen hoch auf -Wall. Du willst jede Hilfe haben, die du
kriegen kannst.
Und wenn du die Warnung dann hast, dann 'entschärfst' du sie, in dem du
für die Funktion einen Protoypen vor deine main() stellst oder die
Funktion vor die main() ziehst.
Und dann schätze ich mal, wird dein Programm sogar funktionieren, weil
du den Compiler nicht mehr angelogen hast bzw. ihm vorenthalten hast,
dass die Funktion 2 long Werte nimmt (und bitte: spar dir das int in
long int. long bzw. unsigned long oder noch besser uint32_t bzw. int32_t
reicht) und er sich aus den Zahlenwerten 10000 zusammenreimt, dass die
Funktion 2 int Werte nimmt.
Und wenn etwas nicht negativ sein kann, dann mach es, verdammt noch mal,
auch unsigned. Du willst auf keinen Fall haben, dass irgendein
Bitmuster, welches sich möglicherweise sogar auf momentan nicht
verstandenen Wegen da einschmuggelt (zb aufgrund fehlerhafter
Parameterlisten und/oder fehlendem Funktionsprotoyp), als negative Zahl
aufgefasst wird, so dass so ein Vergleich
if(interrupt_count > 0)
niemals wahr werden kann.
Karl Heinz Buchegger schrieb:> bitte: spar dir das int in>> long int. long bzw. unsigned long oder noch besser uint32_t bzw. int32_t>> reicht
YES, es klappt :-)) das war der entscheidende Hinweis!! Werde mich mit
den C Datentypen nochmal genauer auseinandersetzen müssen... jetzt bin
ich erstmal super froh, dass das Problem gelöst ist (und einiges dabei
gelernt hab ich auch noch) - VIELEN DANK!
Karl Heinz Buchegger schrieb:> Wenn dich der Compiler nicht warnt, dann dreh die>> Compiler-Warnungen hoch auf -Wall.
Hmm, Compiler warnt mich da (trotz -Wall) nicht - aber werde zukünftig
auf solch "dummen" Datentyp-Verwendungen achten
Falk Brunner schrieb:>>brauche ich eventuell trotzdem Vorwiderstand oder sonstiges Bauteil?>>> JA! Siehe LED!
ebenfalls danke. Baue ich ein (da habe ich den netten Herrn bei Conrad
wohl missverstanden...)
VG Mike
Mike schrieb:> Karl Heinz Buchegger schrieb:>> bitte: spar dir das int in>>>> long int. long bzw. unsigned long oder noch besser uint32_t bzw. int32_t>>>> reicht>> YES, es klappt :-)) das war der entscheidende Hinweis!!
Nö.
Das war nicht der entscheidende Hinweis.
Der entscheidende Hinweis ist der fehlende Funktionsprotoyp!
Und zwar auch nur dann, wenn du
1
sendBit(10000,10000);
schreibst.
Denn jetzt muss der Compiler an dieser Stelle im Code (der Compiler
liest den COde nur EINMAL von oben nach unten) davon ausgehen, nachdem
er vorher noch nie irgendwas von einer Funktion sendBit im Code gesehen
hat, dass diese Funktion so
1
intsendBit(intwert1,intwert2)
2
{
3
...
4
}
aussieht.
Schreibst du
1
sendBit(50000,50000);
dann muss sich der Compiler aufgrund der C-Regeln, die Funktion so
annehmen
1
intsendBit(longwert1,longwert2)
2
{
3
...
4
}
und das deckt sich, bis auf den falschen Returntyp (der sich hier aber
nicht auswirkt), mit deiner tatsächlichen Funktion
1
voidsendBit(longintcountPulse,longintcountPause)
im letzten Fall hat der COmpiler daher richtig 'geraten', bzw. haben ihn
die C-Regeln zum richtigen Ergebnis geführt. Im ersten Fall (mit den
10000) aber nicht!
Und das ist dein eigentlicher Fehler.
Karl Heinz Buchegger schrieb:> Nö.>> Das war nicht der entscheidende Hinweis.>>>> Der entscheidende Hinweis ist der fehlende Funktionsprotoyp!
Oh Mann, habe ich nicht mal das richtig "erkannt"... Aber jetzt versteh
ich's (hoffentlich) --> ich muss entweder die gesamte Unterroutine vor
das Hauptpgrogramm bringen oder den Funktionsprototyp (das ist
Funktionsname inkl. Signatur/Parameter-Liste, richtig?)
Das hatte mir echt eine schlaflose Nacht bereitet und war ziemlich
"verzweifelt" - aber im Nachhinein muss ich sagen, dass ich durch die
Probleme/Fehler wieder einiges dazu gelernt habe...
Das Programm zum Senden der 32-Bit-Sequenz hab ich entsprechend
überarbeitet - funktioniert jetzt (natürlich) auch. Heute Abend geh ich
dann - mit gestärktem Wissen und Motivation ;-) an den entsprechenden
Empfänger...
Also danke für die Hilfe und Hilfe zur Selbsthilfe!