PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : [ERLEDIGT] Empfängersignal mit ATtiny13A erkennen



Lichti01
16.06.2017, 20:53
Hallo zusammen!

Bin neu hier in diesem Forum und auch Neuling auf dem Gebiet der AVR-Programmierung.

Hoffe das ich hier mit meinem Anliegen richtig bin.

Habe die suche schon benutzt aber keine Antwort auf meine Frage gefunden mit der ich etwas anfangen konnte.

So nun zu meinem Anliegen.

Ich möchte das von einem RC-Empfänger ausgegebene PWM-Signal mit einem ATtiny13A (später auch mit dem ATtiny2313A) auswerten und bei entsprechendem Wert >= LED einschalten bzw. wenn der Wert einen bestimmten Schwellenwert wieder unterschreitet ausschalten. Das ganze möchte ich im AVR-Studio in C Programmieren.

Wenn ich alles, was ich bisher finden konnte, richtig verstanden habe muss ich den Timer mit einem Prescaler von 8 laufen lassen und die steigende flanke messen, die fallende messen und dann den Wert der fallenden Flanke minus den Wert der steigenden Flanke rechnen. Soweit habe ich das auch verstanden, nur an der Umsetzung hapert es dann. Bitte berichtigt mich wenn das nicht stimmt!

Über eure Hilfe bin ich sehr dankbar.

Gruß Lichti01

021aet04
16.06.2017, 22:46
Willkommen im Forum,
Ich würde einen PCINT (Pin Change Interrupt) verwenden. In der ISR prüfst du ob der Pegel high oder low ist und je nachdem setzt du den timer auf 0 oder ließt den Zählerstand aus.

Als Pseudocode:


ISR (PCINT)
{
Wenn PIN == high
{
TCNT = 0
}
Sonst
{
Zähler = TCNT
}

Im main wertest du die Variable Zähler aus und reagierst dementsprechend. Zum Thema Timer, je nach Genauigkeit, die du haben willst, nimmst du einen 8 oder 16 bit Timer, der 8 bit zählt von 0 bis 255 und fängt wieder bei 0 an, der 16 bit Timer zählt bis 65535. Wie er zählt hangt von der Taktquelle und dem Prescaler ab. Die Frequenz mit der der Zähler um 1 weitererzählt ist Takt/Prescaler. Bei 8MHz und Prescaler von 1024 ergibt sich eine Frequenz f von ca. 7813Hz, mit der der Zähler weitererzählt (ca. Alle 128us). Du musst jetzt beachten das der Timer nicht überläuft bzw darauf achten und reagieren, aber auch das ein sinnvoller Zähler wert herauskommt

Mfg Hannes

Lichti01
17.06.2017, 08:31
Hi Hannes!

Vielen Dank für deine schnelle Antwort!

Werde versuchen das ganze umzusetzen und mich dann nochmal melden!

MfG
Lichti01

Lichti01
17.06.2017, 20:01
So habe nun den ganzen Tag getüftelt hänge aber immer noch fest.

Hier mal mein bisheriger Code:


#define F_CPU 1200000UL //Taktfrequenz CPU 1,2MHz
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <stdio.h>

//LED-Ports
#define LED1 1
#define LED2 2
#define LED3 3
#define LED4 4

#define C 0 //Variable für Zählerstand

int main(void)
{

DDRB = 0b00011110; //Eingänge und Ausgänge definieren
PORTB = 0b00000001; //Pullups setzen

//Einstellungen für PCINT
MCUCR = (1<<ISC00); //Jede logische Änderung an INT0 erzeugt eine Interrupt-Anforderung
GIMSK = (1<<PCIE); //PCINT aktivieren
PCMSK = (1<<PCINT0); //PCINT0 Eingang wird genutzt

//Einstellungen Timer / PWM
TCCR0A = ((1<<WGM00) | (1<<WGM01)); //FastPWM (Top=0xFF Update of OCRx at = TOP TOV Flag Set on = MAX)
TCCR0B = ((1<<CS00) | (1<<CS01)); //Prescaler clk/64 (zählt in ca. 0,002s bis 219)


sei(); //globale Interrupts aktivieren


while(1)
{

if(C >= 100)
{
PORTB |= ((1<<LED1) | (1<<LED2) | (1<<LED3) | (1<<LED4));
}
else if (C <=100)
{
PORTB &= ~((1<<LED1) | (1<<LED2) | (1<<LED3) | (1<<LED4));
}

}

}

ISR(PCINT0_vect)

{
if(PINB & (1<<PB0)) //erkennung steigender Flanke
{
TCNT0 = 0;
}
else //sonst
{
C = TCNT0; //Zählerstand auslesen und in variable schreiben
}
}

Leider bekomme ich bei der Zeile "//Zählerstand auslesen und in Variable schreiben" noch den Fehler angezeigt: "lvalue required as left operand of assignment".

Über Hilfe wäre ich wieder dankbar.
Was natürlich auch gut zu wissen wäre ist, ob der Code ansonsten für mein Vorhaben richtig umgesetzt ist.

LG
Lichti01

021aet04
17.06.2017, 21:38
Du hast glaube ich C nicht richtig verstanden. Ein define ist dafür da bestimmte Werte einem Namen zu geben, als Beispiel z.b. Pi, das ist immer 3,1415. Jetzt kannst du in dem Programm jedes mal 3,1415 hinschreiben oder zuerst mit define definieren und anschließend nur mehr mit dem definierten Label (Pi) arbeiten. Bei den Leds hast du es richtig verwendet, bei der Zählervariable allerdings falsch, dort musst du schreiben wie groß der wert sein kann (signed bzw unsigned, char, int float,...). https://www.mikrocontroller.net/articles/AVR-GCC-Tutorial dort bei ganzzahlige datentypen (integer). Zuerst musst du wissen wie weit der TCNT zählen kann (beim längsten Impuls, also 2ms) und dementsprechend den Typ wählen (vermutlich unsigned char bzw unsigned int).

MfG Hannes

Lichti01
18.06.2017, 14:11
Ja da habe ich wohl etwas falsch verstanden!

Habe nun meinen Code geändert. Im Testlauf habe ich mit einem Zweiten µC die PWM vom Empfänger simuliert und es funktioniert auch soweit.
Die Tage geht es dann an den Test mit einem RC-Empfänger. Hoffe das das dann auch so reibungslos funktioniert wie mit dem zweiten µC.

An dieser Stelle möchte ich mich bei dir auch noch für deine Hilfe bedanken! Finde es klasse, dass du mir nicht einfach den Code präsentiert hast. Somit habe ich doch einiges gelernt!

Gruß
Lichti01

Lichti01
19.06.2017, 20:09
so nun habe ich den code mit verschiedenen Empfängern erfolglos getestet. :(

Was ist falsch?



#define F_CPU 1200000UL //Taktfrequenz CPU 1,2MHz
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <stdio.h>

//LED-Ports
#define LED1 1
#define LED2 2
#define LED3 3
#define LED4 4

uint16_t C; //Integer einfügen und Variable für Zählerstand festlegen

int main(void)
{

DDRB = 0b00011110; //Eingänge und Ausgänge definieren
PORTB = 0b00000000; //Pullups setzen

//Einstellungen für PCINT
MCUCR = (1<<ISC00); //Jede logische Änderung an INT0 erzeugt eine Interrupt-Anforderung
GIMSK = (1<<PCIE); //PCINT aktivieren
PCMSK = (1<<PCINT0); //PCINT0 Eingang wird genutzt

//Einstellungen Timer / PWM
TCCR0A = ((1<<WGM00) | (1<<WGM01)); //FastPWM (Top=0xFF Update of OCRx at = TOP TOV Flag Set on = MAX)
TCCR0B = (1<<CS01); //Prescaler clk/8


sei(); //globale Interrupts aktivieren


while(1)
{

if (C < 80)
{
PORTB &= ~((1<<LED1) | (1<<LED2) | (1<<LED3) | (1<<LED4));
sei();
}


else if((C > 80) & (C < 190))
{
PORTB &= ~((1<<LED2) | (1<<LED4));
PORTB |= (1<<LED1);
PORTB &= ~(1<<LED3);
_delay_ms(500);
PORTB |= (1<<LED3);
PORTB &= ~(1<<LED1);
sei();
_delay_ms(500);
}


else if (C > 190)

{
PORTB |= ((1<<LED2) | (1<<LED4));
PORTB |= (1<<LED1);
PORTB &= ~(1<<LED3);
_delay_ms(500);
PORTB |= (1<<LED3);
PORTB &= ~(1<<LED1);
sei();
_delay_ms(500);
}


}

}

ISR(PCINT0_vect)

{
if(PINB & (1<<PB0)) //erkennung steigender Flanke
{
TCNT0 = 0;
}
else //ansonsten
{
C = TCNT0; //Zählerstand auslesen und in variable schreiben
cli();
}
}


Habe schon mit den Einstellungen am Sender gespielt hab verschiedene Prescaler / Integer ausprobiert. Was kann ich noch machen?

Gruß
Lichti01

vohopri
20.06.2017, 08:01
Servus,

du hast die PWM simuliert, und es hat funktioniert. Da würde ich das Simulierte Signal und das Signal vom RC Empfänger vergleichen. Entweder mit einem Oszilloskop, oder als ich das noch nicht hatte, ging das mit einem Freeware Softwareoszi für den PC sehr gut.

IMHO ist es sinnlos, im Code herum zu rühren, wenn man nicht weiss, was die Hardware macht. Auf Dauer ist das fürchterlich ineffizient.

021aet04
20.06.2017, 08:07
Warum schreibst du PORTB = 0b00000000; ? Portb ist beim einschalten automatisch 0x00 (steht im DB).

Warum konfigurierst du 2 Interrupts, das ist vermutlich auch der Fehler. Du konfigurierst einmal INT0 und PCINT0, INT0 ist PB1 und PCINT0 ist PB0. Du reagierst aber nur auf den PCINT, ich vermute das du den Empfänger auf PB1 (INT0) angeschlossen hast. Eigentlich sollte es aber Warnungen geben wenn du es kompilierst. Warum schaltest du eigentlich die Interruptfreigabe aus?

MfG Hannes

Ceos
20.06.2017, 08:58
okay ich werf fragen hinterher, wozu einen WaveForm Modus?


//Einstellungen Timer / PWM
TCCR0A = ((1<<WGM00) | (1<<WGM01)); //FastPWM (Top=0xFF Update of OCRx at = TOP TOV Flag Set on = MAX)
TCCR0B = (1<<CS01); //Prescaler clk/8


lass TCCR0A einfach auf initial Zustand! Normaler Zählmodus, kein PWM das ist nutzlos!

1.2MHz / 8 = 150kHz -> 8bit = 265 counts -> 1/150kHz * 256 = 1.70666 mS
sollte alos für den maximal möglichen 1.5mS Puls reichen

du initialisierst den Timer OHNE clockselect, du lässt CS einfach komtplett weg! Damit steht dein Timer!

in der ISR, bei steigender Flanke, startest du den timer indem du die CS01 setzt

bei fallender Flanke stoppst du den timer, indem du die CS Bits weider auf 0 setzt, prüfst du zuerst das TIFR Register ob das Overflow Bit gesetzt ist, löschst es (eine 1 auf das bit schreiben, keine 0) und verwirfst die Messung (Die High Phase war offensichtlich länger als 1.7mS)
wenn das OVF Flag nicht gesetzt ist schreibst du den TCNT wert auf und verarbeitest die gemessenen zeit und setzt TCNT wieder auf 0

so sparst du sogar in den low phasen noch strom weil dein timer nciht die ganze zeit sinnlos mitrennt

Lichti01
22.06.2017, 21:53
HI

Habe nun alles mit Hilfe eurer Antworten nochmal überarbeitet. Jetzt funktioniert es!
SUPER! Danke für eure Hilfe

Gruß
Lichti01

021aet04
23.06.2017, 08:16
Was war der Fehler?

MfG Hannes

Lichti01
23.06.2017, 15:58
HI

Ich denke, dass der ausschlaggebende Punkt war, dass ich den Timer nicht gestartet habe.
Außerdem habe ich den INT0 rausgenommen sowie den Timer auf initial Zustand gelassen.

021aet04
23.06.2017, 22:33
Der Timer war schon gestartet. Den hast du gestartet indem du CS00 und CS01 gesetzt hast. Ich vermute das sich das Programm ins Nirvana verabschiedet hat weil du den Int0 Interrupt konfiguriert hast, aber nicht in die ISR gesprungen bist.

MfG Hannes

wkrug
24.06.2017, 07:19
Ich mach die Servosignal Auswertung auch gerne mal mit dem INT0.
Der Trick dabei ist, im Interrupt das Sensing von steigende Flanke auf fallende Flanke um zu konfigurieren.
Und in einem zweiten Schritt wieder umgekehrt.

Du initialisierst also erstmal den Interupt 0 auf steigende Flanke und startest dann die Hauptschleife.
Ein Timer zum messen der Impulslänge muss auch noch gestartet werden, braucht aber prinzipiell keinen Interrupt - Ich nehm da meistens einen 16 Bit Timer mit einem Prescaler von 8.

Tritt nun der steigende Interrupt auf wird in der Interrupt Routine das TCNT Register des Timers ausgelesen und in einer volatile Variable abgespeichert.
Jetzt wird dann auch das Sensing auf fallende Flanke umgestellt.

Kommt nun die fallende Flanke wird der INT0 wieder aufgerufen.
Es wird wieder der TCNT ausgelesen und der vorher abgespeicherte Wert der Variable abgezogen.
Das Ergebnis wird nun in einer weiteren volatile Variable abgespeichert und gibt das Maß der Impulslänge an.
( Bei 8MHZ und Prescaler 8 in µs, Bei 16MHz µs*2, also halbe µs Schritte ).
Nun das sensing wieder auf steigende Flanke umstellen für den nächsten Impuls.
Man kann auch noch eine weitere Variable als FLAG setzen, damit das Hauptprogramm weiß, das ein neuer Impuls eingetroffen ist.
Man muß lediglich darauf aufpassen, das bei einer maximalen Impulslänge der Timer nicht überläuft - Das kann aber eigentlich nur passieren, wenn man die Pausen auswertet.

Ich weiß - Das Ganze geht auch mit Pin Change.
Das INT0 Verfahren funktioniert aber auch mit Controllern die keinen Pin Change Interrupt haben.

Eine weitere Möglichkeit wäre noch der Input Capture Interrupt eines Timers, wenn man noch zusätzliche Eingänge braucht.

Das Verfahren ist aber prinzipiell immer gleich, mal die steigende, dann die fallende Flanke des Signals auszuwerten.