LED auf Tastendruck

Nochmal hallo,

@LotadaC: Wow, danke für deine Antwort, ist ja richtig viel geworden :)

Nochmal zu meiner Frage mit dem Delay und den Tastern. Mein ATmega8 läuft ja jetzt mit den Werkseinstellungen, d.h. mit dem internen Takt von 1MHz. D.h. die Abarbeitung des Programms geschieht einmal pro us (stimmt das so?). Da ich aber meinen Taster nicht so schnell drücken kann, d.h. er z.B. 100ms betätigt ist, arbeitet sich das Programm innerhalb dieser Zeit 1000mal ab. Um das zu vermeiden, habe ich das Delay eingebaut, damit ich immer nur einen Zustand weiterschalte und nicht mit einmal drücken alle Zustände durchlaufe.
Die Frage ist, wird das Programmtechnisch wirklich so gemacht? oder gibt es da andere Methoden, schließlich ist der MC, wie LotadaC das schon ein paar Beiträgen früher geschrieben hat, für diese Zeit "lahm" gelegt.

Ich füchte mal, da wird noch ein weiterer Gedankenfehler sein :rolleyes:

LotadaC hat ja schon ein wenig geschrieben. Ich schreib noch was dazu.

Wenn du vorher nur auf PCs programmiert hast ... Vergiß das meißte :p Du mußt erstmal ein wenig umlernen.

Wenn du ein Programm für einen Mikrocontroller schreibst, dann schreibst du damit quasi auch ein BIOS, Betriebssystem, Userinterface, ... , ja und auch ein wenig deine Applikation :D

Im PC ist dein Programm ins Betriebssystem eingebettet und wird da gestartet und am Ende deines Programms steht dann wieder das Betriebssystem.

Beim Atmel läuft dein gesamtes programmiertes Zeugs in einer Endlosschleife. Wenn nicht, dann schmiert das alles ab. Dein BIOS/Betriebssystem/Programm wird gestartet wenn du den Strom anstellst und dann läuft es und läuft und läuft und ... Es hat kein Ende. Auf dem Controller ist nichts vorhanden was dein Programm jede µs einmal ausführt oder startet oder was auch immer.

Soviel erstmal um eventuell vorhandene Unklarheiten zu beseitigen.

Gruß
Dino
 
Ok, als nächstes werde ich mal versuchen den Timer0 in gang zu bekommen. Wenn ich das geschafft habe, dann werde ich mit Interrupts anfangen, um z.B. den Timer0Overflow als Interrupt zu benutzen um meine LEDs zum Blinken zu bringen.

@LotadaC:

Wie ist das gemeint? Habe ich vorhin richtig rausgehört, dass du mit Assambler programmierst? Also ich würde C benutzen, weil wir das im nächsten Semester als Modul haben und weil wir das auch im 3. Semester höchstwahrscheinlich benutzten werden, aber im Prinzip bin ich nicht wirklich festgenagelt. Hast du da vielleicht Tipps?
 
Ich konnte nicht schlafen, deshalb hab ich mich rangesetz und habe versucht zu verstehen, wie man den Timer/Counter0 benutzt. Dazu habe ich gleich noch die Interrupts aktiviert, aber beim Compilen tritt ein Fehler beim Aufruf der ISR-Routine mit ISR(TIMER0_OVF_vect) auf. Hier der Code bisher:

Code:
/*
 * Timer_driven_LED.c
 */

#include <avr/io.h>
#include <avr/interrupt.h>

#define F_CPU 1000000UL

int main(void)
{

//	Timer/Counter initialisieren	


	TIMSK = (1<<TOIE0);						
	TCCR0 = (1<<CS02) | (1<<CS00);					
	sei();							             



//	Register initialisieren

	DDRB = 0xFF;
	PORTB = 0xFF;	//    Zuerst alle LEDs ausschalten



	int i = 0;   	//    Zustandszähler: 0 = LED ein; 1 = LED aus




//	Hauptprogramm

    while(1)
    {
		i = i;	// Sinnlos :D
    }



//	ISR Routine für TIMER0_OVF_vect

	ISR(TIMER0_OVF_vect)
	{
		if (i==0)		//    Wenn i==0, LED einschalten
		{
			PORTB = 0x00;
                                          i=1;
		}
		if (i==1)		//    Wenn i==1, LED ausschalten
		{
			PORTB = 0xFF;
                                          i=0;
		}
	}
}

Hier ist der Fehler, der mir ausgegeben wird:

Error.JPG
 
Hallo mr.twister!

Die ISR musst du ausserhalb main() platzieren. Danach definierst du deine gemeinsame Variablen, welche im Hauptprogramm und der ISR verwendet werden global.

In deinem Fall also vor main:

volatile uint8_t i;

int ist sicher nicht notwendig, besser ist hier eventuell uint8_t.

"volatile" ist wichtig, damit der Compiler "weiß", dass i ggf. auch ausserhalb main() verändert werden kann.


Dein Programm müsste sich nun compilieren lassen.


Allgemein zu ISR:
Hier immer nur zeitkritischen Programmteile ausführen, nie zu viel Code verwenden. _delay_ ist hier tabu.

Dirk :ciao:
 
Hey,

also ich habe das

Code:
int i=0;

in

Code:
volatile uint8_t i=0;

umgeändert, aber der Fehler bleibt das gleiche. Ich glaube, der Compiler hat ein Problem mit dem

Code:
ISR (TIMER0_OVF_vect) { }

Hmmm...
 
umgeändert, aber der Fehler bleibt das gleiche. Ich glaube, der Compiler hat ein Problem mit dem

Code:
ISR (TIMER0_OVF_vect) { }

Hmmm...

Hi mr.twister.

Hast du die ISR auch ausserhalb main() gesetzt, wie ich es bereits geschrieben hatte? Wenn nicht, ist es klar, dass der Compiler einen Fehler meldet.

Dirk :ciao:
 
Entschuldige bitte :eek:. Ich war fest davon überzeugt, das die ISR außerhalb stand... Hattest recht, da war ganz unten noch die Main-Klammer :rolleyes: Jetzt funktionierts, mal schaun wie es an meinem Board funktionieren wird :)

Globale Variablen werden außerhalb der Main deklariert? Wie war das nochmal ???
 
Hmmm.... Habe das Programm jetzt auf mein MC geflasht und am Anfang leuchten die LEDs einmal auf, aber dann nicht wieder.... komische Sache, im Simulator lief es...

EDIT: Habe den Fehler gefunden, jetzt leuchten die LEDs auf jeden Fall durch ;) Mal schaun ob ich auch blinken hinbekomme...

EDIT2: Blinken funktioniert auch, aber leider Blinken die LEDs in einer Art Puls.... Ich wollte eigentlich, dass die immer in gleichen Zeitabständen blinken, wie macht man das?

Ist es normal, das der Timer während der ISR schon wieder anfängt zu zählen?
 
Hallo mr.twister,

der Timer wird durch die ISR nicht beeinflusst!



Ich weiß nicht genau, was du erreichen möchtest, bzw. deinen letzten Programmstand habe ich nicht.


Die TimerISR wird ja periodisch aufgerufen. Du nutzt im Moment den Overflow Interrupt des Timer. Das heißt, der Timer läuft irgendwann über, es wird ein Overflow Interrupt ausgelöst, der Timer fängt wieder bei 0 an. Wie schon geschrieben, wird der Timer nicht durch die ISR beeinflusst. Die Periodendauer des Timer Interrupt kannst du verringern, in dem du in der TimerISR den Timer auf einen bestimmten Wert initialisiertst. Ab diesem Wert läuft er los, bis er wieder überläuft und die TimerISR wieder aufgerufen wird.

Du könntest zum Beispiel auch den Compare Interrupt verwenden, dies ist etwas eleganter als beim Overflow Interrupt. Schau dir hierzu vielleicht einmal die Erklärungen in dem Thema AVR-Timer-Calculator an. Lass dich aber jetzt nicht irritieren!

Eine Anwendung wäre zum Beispiel, sich für das Hauptprogramm ein Zeitsignal erzeugen zu lassen (Man kann für so eine Aufgabe im Hauptprogramm direkt das Interruptanforderungsflag prüfen, aber das soll hier erst mal egal sein).
In der TimerISR könntest du ein Signal für das Hauptprogramm setzen. Zum Beispiel einfach eine globale volatile Variable auf 1 setzen.
Im Hauptprogramm prüfst du diese Variable auf den Inhalt 1. Wenn diese Bedingung wahr ist, dann "löscht" du das Signal, indem du den Ihnalt der Variable auf 0 setzt (zuvor Interrupts deaktivieren, danach wieder aktivieren, damit nicht ggf. die ISR dazwischenfunkt) und führst dann deinen Code aus.

Dein Code wird somit periodisch ausgeführt. Das könnte zum Beispiel alle 10ms eine Tastaturabfrage sein. Inerhalb der while(1) Schleife deines Hauptprogramms, wird diese Tastaturabfrage das System nicht sehr belasten, so dass noch andere Programmbereiche abgearbeitet werden können.

Dirk :ciao:
 
Also hier nochmal der aktuelle Stand meines

Code:
#include <avr/io.h>
#include <avr/interrupt.h>


//	Globale Variablen

volatile uint8_t i = 0;								//	Zustandszähler: 0 = LED ein; 1 = LED aus
volatile uint8_t x = 0;
unsigned char first_start = 1;						        //	Verhindert, dass sich die Register bei jedem Zyklus neu initialisieren

int main(void)
{

//	Timer/Counter initialisieren	

	TIMSK = (1<<TOIE0);								//	Timer/Counter Interrupt Mask Register: TOIE0=1 aktiviert Interrupt
	TCCR0 = (1<<CS02) | (1<<CS00);					        //	Timer/Counter0 Control Register: Prescaler/Vorteiler - F = F_CPU/1024
	
	sei();											//	Globale Interrupts einschalten;	SREG 1-Bit


//	Register initialisieren

	if (first_start)
	{
		DDRB = 0xFF;
		PORTB = 0xFF;								// Zuerst alle LEDs ausschalten
		first_start = 0;							
	}	



//	Hauptprogramm

    while(1)
    {
		i = i;
    }
}


//	ISR Routine für TIMER0_OVF_vect

ISR(TIMER0_OVF_vect)
{
	if (i==0)									//	Wenn i==0, LED einschalten
	{
		PORTB = 0x00;
		x=1;
	}
	if (i==1)									//	Wenn i==1, LED ausschalten
	{
		PORTB = 0xFF;
		x=0;
	}
	
	if (x==1)
	{
		i=1;
	} 
	else
	{
		i=0;
	}
}

Deine Idee mit der Tastaturabfrage klingt ganz interessant. Bedeutet das, dass ich irgendwann meine while-Schleife abschaffen kann, wenn ich nur noch mit Interrupts arbeite?
 
Öhm, überleg doch selbst. Was soll Deine MCU denn tun, wenn im Moment keine Interrupts fliegen?

Ein AtMega ist relativ langsam, ein ARM hat einen Takt von über 1GHz, sprich zwischen den Interrupts hat er massig Zeit. Was soll er da tun?
Richtig, er wird die Main() verlassen und dann abstürzen.
 
Bedeutet das, dass ich irgendwann meine while-Schleife abschaffen kann, wenn ich nur noch mit Interrupts arbeite?

Nein, die while(1) Schleife bleibt, hier läuft ja dein Hauptprogramm, welches dann ab un zu mal durch Aufruf einer ISR unterbrochen wird. Übrigends wird der Code VOR while(1) nur einmal nach Reset ausgeführt. Hier führst du Initialisierungen aus.


Code:
if (first_start)
{
    DDRB = 0xFF;
    PORTB = 0xFF;    // Zuerst alle LEDs ausschalten
    first_start = 0;                            
}

Dies wird immer ausgeführt, first_start ist 1 wenn main() ausgeführt wird. Danach kommt while(1), und darin bleibst du. Die obige if-Abfrage hat also keinen Sinn.

Dirk :ciao:


EDIT:
So ganz verstehe ich dein Programm noch nicht. Es spielt sich eigentlich alles in der ISR ab, innerhalb while(1) von main() passiert nichts. In der ISR nutzt du praktisch den periodischen Aufruf. Setze in der ISR ein Signal für das Hauptprogramm, verlege den Code von der ISR ins Hauptprogramm und führe den Code aus, wenn das Signal durch die ISR gesetzt ist. So wie ich es zuvor mal beschrieben hatte.
 
So hier, nochmal überarbeitet, aber diesmal so wie ihr meintet, in der ISR wird nur ein Wert einer Variablen verändert, die dann im Hauptprogramm weiterverarbeitet wird. Und siehe da, auf einmal blinkt die LED in gleichmäßigen Abständen. Danke :)

Code:
#include <avr/io.h>
#include <avr/interrupt.h>


//	Globale Variablen

volatile uint8_t i = 0;								//	Zustandszähler: 0 = LED ein; 1 = LED aus

int main(void)
{

//	Timer/Counter initialisieren	

	TIMSK = (1<<TOIE0);								//	Timer/Counter Interrupt Mask Register: TOIE0=1 aktiviert Interrupt
	TCCR0 = (1<<CS02) | (1<<CS00);					//	Timer/Counter0 Control Register: Prescaler/Vorteiler - F = F_CPU/1024
	
	sei();											//	Globale Interrupts einschalten;	SREG 1-Bit


//	Register initialisieren

	DDRB = 0xFF;
	PORTB = 0xFF;									// Zuerst alle LEDs ausschalten


//	Hauptprogramm

    while(1)
    {
		if (i==0)									//	Wenn i==0, LED einschalten
		{
			PORTB = 0x00;
		}
		if (i==1)									//	Wenn i==1, LED ausschalten
		{
			PORTB = 0xFF;
		}
    }
}


//	ISR Routine für TIMER0_OVF_vect

ISR(TIMER0_OVF_vect)
{
	if (i==0)
	{
		i=1;
	} else
	{
		i=0;
	}
}
 
Eine Empfehlung von mir: mach zuerst alle Initialisierungen und Einstellungen und schalte erst dann die Interrupts ein (mit dem sei(); Aufruf). Dann ist es strukturierter.

Vom Ablauf dann sowas:
  • Variablen definieren
  • Einstellungen vornehmen (Ein- und Ausgänge, ....)
  • alles notwendige Initialisieren
  • sei();
  • ab in die Hauptschleife
 
Danke für den Hinweis, werde ich gleich ändern :)

TIFR0 |= (1<<OCF0A); // Interrupt Request loeschen

Was bedeutet das?

Das TIFR0 - Timer/Counter Interrupt Flag Register 0 enthält alle Interrupt Flags.

OCF0A (Output Compare Flag 0/A?) - Ist das einfach nur das Flag was gesetzt wird, wenn der Vergleich zweir Register TRUE ist?
 
TIFR0 |= (1<<OCF0A); // Interrupt Request loeschen

Was bedeutet das?

Das TIFR0 - Timer/Counter Interrupt Flag Register 0 enthält alle Interrupt Flags.

Jeder Interrupt hat sein Interruptanforderungsflag, dieses wird durch die Hardware gesetzt, wenn das Ereignis für den Interrupt auftritt. Beim Timer zum Beispiel TimerOverflow oder Compare. Ist der Interrupt freigegeben und global Interrupts freigegeben (sei) dann wird die entsprechende ISR ausgeführt. Beim Rücksprung (Assembler: reti) von der ISR setzt die Hardware das zugehörige Interruptanforderungsflag wirder auf 0.

Wenn du nun einen Timer und dessen Interrupt initialisiertst, kannst du manuell ein zuvor möglicherweise gesetztes Interruptanforderungsflag löschen, indem du eine "1" in diese Bitposition schreibst. Das musst du nicht grundsätzlich machen, nach einen Reset sind alle Anforderungsflags gelöscht.

Manchmal ist es sinnvoll oder einfacher einen Interrupt explizit gar nicht freizugeben und manuell im Hauptprogramm das Interruptanforderungsflag permanent abzufragen (zu pollen). Dann ist es auf jeden Fall notwendig, das Flag manuell zurückzusetzen, da ja dies die ISR nicht erledigt.

OCF0A (Output Compare Flag 0/A?) - Ist das einfach nur das Flag was gesetzt wird, wenn der Vergleich zweir Register TRUE ist?

Ja genau, das ist das Interruptanforderungsflag für den CompareAInterrupt des Timer0.

Aber Achtung: nicht alle AVRs haben bei Timer/Counter0 das Compare Feature. Hier musst du mal in das Datenblatt des Mikrocontrollers schauen. Wenn du dieses Feature nutzen möchtest, kannst du aber ggf. auch einen Timer/Counter 1 nutzen.


Dirk :ciao:
 
Guten morgen Dirk,

also ich habe jetzt einfach mal angefangen die Register TIMSK, TIFR, TCCRn, TCNTn mit den jeweiligen Bits und den Bedeutungen aufzuschreiben (ATmega8), wahrscheinlich gibt es hier im Forum schon vergleichbares, ich habe bereits gesehen, dass dino3 einen Beitrag zu den ganzen FußeBits geschrieben hat.

Ich würde jetzt die anderen beiden Timer/Counter im Datenblatt durchlesen und bei Bedarf dann eine kleine Zusammenfassung hier reinstellen.
 
Also bei den 16-bit Registern hab ich eine Frage und zwar zu C und Assembler. Ich weiß nicht ob ich es richitg verstanden habe, notfalls bitte korrigieren.

Zitat aus dem Atmel ATmega8 Handbuch, S. 78:

"To do a 16-bit write, the High byte must be written before the Low byte. For a 16-bit read, the
Low byte must be read before the High byte.
"

Daraus folgt für Assembler:

Code:
TCNT1_read:

in r18, SREG        ; Globales Interrupt Flag in r18 speichern
cli                     ; gleicher Befehl wie in C, das Global Interrupt Flag löschen
in r16, TCNT1L    ; Low-Byte in r16
in r17, TCNT1H    ;High-Byte in r17

out SEREG, r18   ; Inhalt von r18 in SREG

Das gleiche Beispiel in C:

Code:
unsigned int TIM16_ReadTCNT1( void )
{
unsigned char sreg;
unsigned int i;
/* Gobal Interrupt Flag sichern */
sreg = SREG;
/* Interrupts deaktivieren */
_CLI();
/* Inhalt von TCNT1 in i schreiben ??? Low-Byte und High-Byte werden einfach selbstständig verwaltet? */
[B]i = TCNT1;[/B]
/* Global Interrupt Flag wiederherstellen */
SREG = sreg;
return i;
}

Beide stehen so auch im Handbuch auf S.76.

Was passiert denn mit dem High-, Low-Byte in C? Schreibt der Compiler das um?
 
In Assembler arbeitest Du (fast) immer nur mit bytes bzw deren bits. Größere Konstrukte mußt Du selbst verwalten/implementieren. In C ist das bereits im C-Compiler eigebaut, TCNT1 ist eine 16-Bit-Zahl, i als unsigned int sicher auch. Wenn du also mit i=TCNT1 den Inhalt von TCNT1 nach i schreiben läßt, wird das vom Compiler in denselben Code überführt, den man auch in Assembler schreiben würde (also die Opcodes hinter den ASM-Mnemonics.
Erst wird TCNT1L aus dem I/O-Space in ein Rechenregister geladen, und von dort in das SRAM geschrieben (Adresse von low(i))
Dann wird TCNT1H aus dem I/O-Space in ein Rechenregister geladen, und ins SRAM (Adresse high(i)) geschrieben.

Hintergrund: Wie Du bereits erkannt hast, läuft der Timer (ggf) im Hintergrund weiter. Auch wenn Du den Timer (TCNT) lesen oder schreiben willst. Da dieses lesen/schreiben nur durch Übertragung zweier bytes (low und high eben) erfolgen kann, erfordert es auch mindestens 2 Takte, in denen der Teimer also bereits weitergezählt haben kann. Möglicherweise erfolgte dabei sogar ein Überlauf vom low auf das high-Byte, oder sogar ein kompletter Überlauf des ganzen Timers - um das sicher zu umgehen, wird jede Interaktion mit dem high-Byte in Wirklichkeit über einen Puffer ausgeführt. Die Interaktion zwischen Puffer und high-Byte wird dabei durch den Zugriff auf das low-Byte ausgelöst. Also:
-liest man das low-Byte, wird gleichzeitig das high Byte gelesen, und in den Puffer geschrieben, aus dem man es danach also auslesen kann
-beschreibt man das low-Byte, wird gleichzeitig der Inhalt des Puffers ins High-Byte geschrieben, wo folglich vorher ein entsprechender Inhalt hinterlegt worden sein sollte.
Aber Vorsicht: Alle 16-bit-Register des Timers verwenden dasselbe Pufferregister, liest man also zuerst TCNT1L (TCNT1H landet im Puffer), und danach zB OCR1AL wird der Puffer mit OCR1AH überschrieben -TCNT1H ist dann weg (also der Wert)...

Zusätzlich gibt es bei einigen 16-Bit-Registern uU noch eine weitere Pufferung, bei einigen PWM-Modi wird das beschreiben der Output-Compare-Register erst nach dem nächsten Überlauf wirksam.
Das ist aber auch alles im Datenblatt beschrieben (scheinst Du ja schon zu lesen;))

Und, wie oben angedeutet, nimmt Dir C als Hochsprache da vieles ab.
...Wie ist das gemeint? Habe ich vorhin richtig rausgehört, dass du mit Assambler programmierst?...
Ja, Dino (und einige andere) natürlich auch. Unter C kann Dir hemi kräftig unter die Arme greifen, unter Bascom fällt mir als erstes Cassio ein - hat halt jeder so seine Vorlieben. Eigentlich ist die verwendete Sprache dann weitgehend egal, Du darfst eben nur nicht vergessen, daß der Controller kein PC ist.
 

Über uns

  • Makerconnect ist ein Forum, welches wir ausschließlich für einen Gedankenaustausch und als Diskussionsplattform für Interessierte bereitstellen, welche sich privat, durch das Studium oder beruflich mit Mikrocontroller- und Kleinstrechnersystemen beschäftigen wollen oder müssen ;-)
  • Dirk
  • Du bist noch kein Mitglied in unserer freundlichen Community? Werde Teil von uns und registriere dich in unserem Forum.
  •  Registriere dich

User Menu

 Kaffeezeit

  • Wir arbeiten hart daran sicherzustellen, dass unser Forum permanent online und schnell erreichbar ist, unsere Forensoftware auf dem aktuellsten Stand ist und der Server regelmäßig gewartet wird. Auch die Themen Datensicherheit und Datenschutz sind uns wichtig und hier sind wir auch ständig aktiv. Alles in allem, sorgen wir uns darum, dass alles Drumherum stimmt :-)

    Dir gefällt das Forum und unsere Arbeit und du möchtest uns unterstützen? Unterstütze uns durch deine Premium-Mitgliedschaft!
    Wir freuen uns auch über eine Spende für unsere Kaffeekasse :-)
    Vielen Dank! :ciao:


     Spende uns! (Paypal)