C Timervariable verhält sich nicht wie gewünscht

fanatiXalpha

Fleet Admiral
Registriert
Aug. 2011
Beiträge
13.558
Hab ne Timervariable die nach 10 Sekunden von 0 auf 1 springt.
Wenn ich das in der Main ausführe, egal ob über if oder while, die Dauer sind diese 10 Sekunden.
Jetzt will ich diese Variable an einer anderen Stelle nutzen und plötzlich ist die Dauer nur noch 4 oder 5 Sekunden und ich hab bisher nicht rausfinden können wieso.
Es funktioniert ja in der Main....

Ich setze die Variable direkt vor der while-Schleife auf 0 und die Variable ist als volatile gekennzeichnet damit der Compiler da nichts dran optimiert (ne Endlosschleife oder sowas zu machen).

Programmiert wird das auf einem AT90, und die Timervariable wird dann durch eine ISR bedient.
Ich bin halt einfach total verwirrt weil es halt in der Main funktioniert....
Hat jemand Anhaltspunkte?
 
Eventuell wenn du uns Code zeigst.
 
So sieht das in der anderen c-Datei aus:
Code:
if(RS232_data == 'c')
		{
			serial_println_RS232("return state");	
			uc1Min = 0;		
			while(uc1Min == 0);
			LED_1_Toggeln();
Ist natürlich nur ein Teil, er wartet halt darauf was über UART zu bekommen und dann soll er erstmal 10 Sekunden warten.
Eigentlich ne Minute, aber das ist zum Testen blöd.

Und in der ISR sieht es so aus:
Code:
	if(ucSec == 1)
	{
		ucSec = 0;
		uc2Sec++;
		if(uc2Sec == 10)
		{
			uc2Sec = 0;
			uc1Min = 1;
		}
		
	}
 
Und was ist ucSec?
 
das ist die Variable für die Sekunde
der Timer an sich ist so eingestellt, dass die ISR jede Millisekunde auslöst
gibt diverse Variablen für verschiedene Zeiten (10ms, 50ms, 100ms) und halt eben auch eine Sekunde
und damit zähle ich dann entsprechend hoch
wie gesagt, eigentlich sollte es eine Minute sein, aber zum testen sind 10 Sekunden wesentlich angenehmer
 
Du nutzt also mehrere eigene Zähler um darüber jeweils andere Zähler hochzuzählen?

Warum nicht ein einzigen Zähler nehmen und dann etwas wie
Code:
if (seconds % 10 == 0)
um ein "Ereignis" für alle 10 Sekunden zu haben.


Ansonsten Poste mal dein gesamten (relevanten) Code.
 
bis zu 1 Sekunde mach ich es auch mit Modulo
aber hinten raus hab ich es zunächst nicht gemacht weil ich da viel am rumwurschteln war
das dürfte ja aber nicht der Grund für die Probleme sein, sonst würde es in der Main ja auch nicht funktionieren, oder?

Naja, relevanter Code ist eigentlich nicht viel bis dahin.
Main:
Code:
*Hier ist der Aufruf der Initialisierungsroutinen*
while (1)
{
GIO_UART_input();

*anderer Kram der Momentan nicht benutzt wird*

}

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

#include "Timer.h"

unsigned char uc1Ms;
unsigned char uc1Ms_2;
unsigned char uc10Ms;
unsigned char uc50Ms;
unsigned char uc100Ms;
unsigned char ucSec;
unsigned char uc2Sec;
volatile char uc1Min;
unsigned int  ui10MsCount;
unsigned int  ui1MsCount;
unsigned char uc1Micros;
unsigned char uc2Micros;
unsigned char uc3Micros;
unsigned int  ui1MicrosCount;

/*
* Timer INIT
*/
void init_Timer(void)
{
	uc1Ms = 0;
	uc1Ms_2 = 0;
	uc10Ms = 0;
	uc50Ms = 0;
	uc100Ms = 0;
	ucSec = 0;
	uc2Sec = 0;
	uc1Min = 0;
	ui10MsCount = 0;
	ui1MsCount = 0;
	// Timer 3 16-Bit eingestellt auf eine Millisekunde
	TCCR3B |= (1<<WGM32);													// Waveform Generation Mode 4 CTC
	TCCR3B |= (1<<CS31) ;													// Clock Select: prescaling 8 -> 16Mhz -> 2000000Hz
	TCCR3A |= (1<<COM3A1);													// Clear OC3A on Compare Match (Set output to low level)
	TIMSK3 |= (1<<OCIE3A);													// Output Compare Match A Interrupt Enable
	OCR3A = 2000;															// Alle 1 Ms
}

/**
* @ brief Interrupt Service Routine, Vektortabelle: 'TIMER3_COMPA_vect' wird jede Millisekunde ausgelöst
*/
ISR (TIMER3_COMPA_vect)
{
	ui1MsCount++;
	uc1Ms = 1;
	
	if((ui1MsCount%1) == 0)
	{
		uc1Ms_2 = 1;
	}
	
	if ((ui1MsCount%10) == 0)
	{
		uc10Ms = 1;
	}

	if ((ui1MsCount%50) == 0)					// ui10MsCount modulo 50 ohne Rest -> 50ms erreicht
	{
		uc50Ms = 1;
	}

	if((ui1MsCount%100) == 0)					// ui10MsCount modulo 100 ohne Rest -> 100ms erreicht
	{
		uc100Ms = 1;								
	}

	if(ui1MsCount == 1000)						// ui10MsCount nach 1sec zurücksetzen
	{
		ui10MsCount = 0;	
		ui1MsCount = 0;							
		ucSec = 1;
	}
	
	if(ucSec == 1)
	{
		ucSec = 0;
		uc2Sec++;
		if(uc2Sec == 10)
		{
			uc2Sec = 0;
			uc1Min = 1;
		}
		
	}
	
	
}

und dann noch die c-Datei die Probleme macht, GIO:
Code:
void GIO_UART_input(void)
{
	//beide UART-Ports werden geprüft wobei RS232-Schnittstelle priorisiert ist
	if(RS232_data_flag == 1 && Automatic_Mode_Flag == 0)
	{
		RS232_data_flag = 0;
		
		if(RS232_data == 'c')
		{
			serial_println_RS232("return state");	
 			uc1Min = 0;		
 			while(uc1Min == 0);

*anderer/weiterer Code der dann kommt*

An der Stelle nach "return state" soll er nachher entweder auf eine Eingabe warten oder eben nach einer Minute einen Timeout geben und der "Antrag" muss erneut gestellt werden.
 
Das wäre meine Lösung:

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

#define SECOND 1000

volatile uint32_t time;

void init_Timer(void)
{
	time = 0;
  
	/*dein anderer code*/
}
 
/**
* @ brief Interrupt Service Routine, Vektortabelle: 'TIMER3_COMPA_vect' wird jede Millisekunde ausgelöst
*/
ISR (TIMER3_COMPA_vect)
{
  time++;
}

void GIO_UART_input(void)
{
   //beide UART-Ports werden geprüft wobei RS232-Schnittstelle priorisiert ist
   if(RS232_data_flag == 1 && Automatic_Mode_Flag == 0)
   {
      RS232_data_flag = 0;
		
      if(RS232_data == 'c')
      {
         serial_println_RS232("return state");	
      
         delay(10 * SECOND);

         //do whatever you want
      }
   }
}

void delay(uint32_t ms) {
  uint32_t end = time + ms;      
  while(time < end);
}
 
Zuletzt bearbeitet:
Alleine die Grundidee für die 10 Sekunden Verzögerung ist murks: Du kannst nicht 1000x mit 1ms Timer eine ISR aufrufen und denken, dass dies dann genau 1 Sekunde dauert. Die Ausführung der ISR kostet auch Zeit! Erst recht wenn du so viel Mathe darin machst.
Wenn du willst, dass 'eine Zeitlang' gewartet wird, dann nimm doch dafür wieder einen Timer! Das hast du doch bereits für die 1ms Periode zum laufen gebracht.
Außerdem müssen generell Variablen, die zwischen ISR und anderen Funktionen geteilt verwerden werden volatile sein. Das trifft dann auch auf RS232_data zu. Ist GIO_UART_input auch eine ISR nur eben für rs232?

edit: Bagbags Lösung sieht schon 100x sauberer aus - wobei ich Polling auf Verzögerung komisch finde, wenn man schon Timer hat.
Dort muss time aber auch unbedingt volatile sein.
Wieso nicht einfach beim receive von 'c' einen bereits vorbereiteten Timer starten, der dann genau 1 Sekunde läuft?
 
Zuletzt bearbeitet:
Noch ein Vorteil von einer Lösung per Timer: Während dem warten könnte noch anderer Code ausgeführt werden.

Hab volatile hinzugefügt, danke.
 
Wenn die ISR von einem Hardware-Timer aufgerufen wird (davon ist auszugehen), dann wird sie sehr wohl im gleichen Rhythmus aufgerufen. Dem Timer ist es egal, wie lange die ISR dauert (solange deren Verarbeitung <1ms dauert).

Edit: Wäre es nicht einfacher eine 64Bit Variable zu nehmen, die du in der ISR hochzählst. Das Berechnen der 10ms, 100ms usw. machst du dann in der Main? Dadurch wird die ISR extrem kurz und du kannst dir sicher sein keine anderen Interrupts zu verpassen.
 
Zuletzt bearbeitet:
danke schon mal für die Antworten :)
aber ich selbst werde erstmal morgen antworten :D

nur eins weil es mir grad im Kopf rumschwirrt:
Das Berechnen der 10ms, 100ms usw. machst du dann in der Main?
Die ISR sollte immer so kurz wie möglich gehalten werden, das stimmt.
Aber ich halte mich halt nicht immer in der Main auf, deswegen weiß ich nicht ob das Berechnen der Zeitwerte dann am Ende noch passt.
Ich müsste immer wieder die Methode aufrufen mit welcher ich die Berechnung mache...
 
kuddlmuddl schrieb:
Alleine die Grundidee für die 10 Sekunden Verzögerung ist murks: Du kannst nicht 1000x mit 1ms Timer eine ISR aufrufen und denken, dass dies dann genau 1 Sekunde dauert. Die Ausführung der ISR kostet auch Zeit! Erst recht wenn du so viel Mathe darin machst.
Wenn du willst, dass 'eine Zeitlang' gewartet wird, dann nimm doch dafür wieder einen Timer! Das hast du doch bereits für die 1ms Periode zum laufen gebracht.
Außerdem müssen generell Variablen, die zwischen ISR und anderen Funktionen geteilt verwerden werden volatile sein. Das trifft dann auch auf RS232_data zu. Ist GIO_UART_input auch eine ISR nur eben für rs232?

edit: Bagbags Lösung sieht schon 100x sauberer aus - wobei ich Polling auf Verzögerung komisch finde, wenn man schon Timer hat.
Dort muss time aber auch unbedingt volatile sein.
Wieso nicht einfach beim receive von 'c' einen bereits vorbereiteten Timer starten, der dann genau 1 Sekunde läuft?

So ein "SystemTimer" nach Bagbags Stil ist durchaus üblich. Die Ausführung der ISR kostet zwar Zeit, aber der IRQ wird immer im gleichen Zeitraster ausgelöst (die Methode ist damit recht genau). Der Nachteil, einen Timer nur für dieses eine Warten zu verwenden, ist der, dass damit dieser Timer während des Wartens nicht für andere Zwecke genutzt werden kann (der SystemTimer dagegen kann auch noch gleichzeitig an anderer Stelle verwendet werden).

Bagbag schrieb:
Noch ein Vorteil von einer Lösung per Timer: Während dem warten könnte noch anderer Code ausgeführt werden.

Hab volatile hinzugefügt, danke.

Man könnte auch leicht eine Delay-Funktion schreiben, die nicht die Ausführung blockiert, sondern sofort mit "Zeit wurde nicht überschritten" oder "Zeit wurde überschritten" zurückkehrt, was es ermöglicht, auch noch anderen Code auszuführen.
Ergänzung ()

fanatiXalpha schrieb:
bis zu 1 Sekunde mach ich es auch mit Modulo
aber hinten raus hab ich es zunächst nicht gemacht weil ich da viel am rumwurschteln war
das dürfte ja aber nicht der Grund für die Probleme sein, sonst würde es in der Main ja auch nicht funktionieren, oder?

Naja, relevanter Code ist eigentlich nicht viel bis dahin.
Main:
Code:
*Hier ist der Aufruf der Initialisierungsroutinen*
while (1)
{
GIO_UART_input();

*anderer Kram der Momentan nicht benutzt wird*

}

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

#include "Timer.h"

unsigned char uc1Ms;
unsigned char uc1Ms_2;
unsigned char uc10Ms;
unsigned char uc50Ms;
unsigned char uc100Ms;
unsigned char ucSec;
unsigned char uc2Sec;
volatile char uc1Min;
unsigned int  ui10MsCount;
unsigned int  ui1MsCount;
unsigned char uc1Micros;
unsigned char uc2Micros;
unsigned char uc3Micros;
unsigned int  ui1MicrosCount;

/*
* Timer INIT
*/
void init_Timer(void)
{
	uc1Ms = 0;
	uc1Ms_2 = 0;
	uc10Ms = 0;
	uc50Ms = 0;
	uc100Ms = 0;
	ucSec = 0;
	uc2Sec = 0;
	uc1Min = 0;
	ui10MsCount = 0;
	ui1MsCount = 0;
	// Timer 3 16-Bit eingestellt auf eine Millisekunde
	TCCR3B |= (1<<WGM32);													// Waveform Generation Mode 4 CTC
	TCCR3B |= (1<<CS31) ;													// Clock Select: prescaling 8 -> 16Mhz -> 2000000Hz
	TCCR3A |= (1<<COM3A1);													// Clear OC3A on Compare Match (Set output to low level)
	TIMSK3 |= (1<<OCIE3A);													// Output Compare Match A Interrupt Enable
	OCR3A = 2000;															// Alle 1 Ms
}

/**
* @ brief Interrupt Service Routine, Vektortabelle: 'TIMER3_COMPA_vect' wird jede Millisekunde ausgelöst
*/
ISR (TIMER3_COMPA_vect)
{
	ui1MsCount++;
	uc1Ms = 1;
	
	if((ui1MsCount%1) == 0)
	{
		uc1Ms_2 = 1;
	}
	
	if ((ui1MsCount%10) == 0)
	{
		uc10Ms = 1;
	}

	if ((ui1MsCount%50) == 0)					// ui10MsCount modulo 50 ohne Rest -> 50ms erreicht
	{
		uc50Ms = 1;
	}

	if((ui1MsCount%100) == 0)					// ui10MsCount modulo 100 ohne Rest -> 100ms erreicht
	{
		uc100Ms = 1;								
	}

	if(ui1MsCount == 1000)						// ui10MsCount nach 1sec zurücksetzen
	{
		ui10MsCount = 0;	
		ui1MsCount = 0;							
		ucSec = 1;
	}
	
	if(ucSec == 1)
	{
		ucSec = 0;
		uc2Sec++;
		if(uc2Sec == 10)
		{
			uc2Sec = 0;
			uc1Min = 1;
		}
		
	}
	
	
}

und dann noch die c-Datei die Probleme macht, GIO:
Code:
void GIO_UART_input(void)
{
	//beide UART-Ports werden geprüft wobei RS232-Schnittstelle priorisiert ist
	if(RS232_data_flag == 1 && Automatic_Mode_Flag == 0)
	{
		RS232_data_flag = 0;
		
		if(RS232_data == 'c')
		{
			serial_println_RS232("return state");	
 			uc1Min = 0;		
 			while(uc1Min == 0);

*anderer/weiterer Code der dann kommt*

An der Stelle nach "return state" soll er nachher entweder auf eine Eingabe warten oder eben nach einer Minute einen Timeout geben und der "Antrag" muss erneut gestellt werden.

Kein Wunder, dass die Zeiten nicht stimmen. Du setzt zu einem "beliebigen" Zeitpunkt uc1Min auf 0 und weißt gleichzeitig nicht, wann uc1Min das nächste mal inkrementiert wird (u2Sec kann hier irgendeinen Wert haben von 0 bis 10!!!)
Dass die Zeiten in der Main stimmen, liegt wohl daran, dass uc2Sec an der Stelle noch bei 0 liegt, da die Ausführung des Programms ja gerade erst begonnen hat.
 
Zuletzt bearbeitet:
Ich habs zu negativ ausgedrückt:
der fehler durch unterschiedliche abläufe in der isr verursacht natürlich keinen effekt auf den timer wie ihr schon sagt dh der 1000'te aufruf findet nach 1000ms also 1 sek statt aaber:
Ich hab bei meinen mikrocontroller projekten sehr strenge laufzeitanforderungwn gehabt und das problem ist dann:
Die 1001te isr ist nicht genau 1 ms später als die 1000te 1ms nach der 999ten
Dh wenn die zahl der ms zb die belichtungszeit einer lampe ist dann hat man im fall von 1000ms nicht die doppelte lichtmenge wie bei 500ms weil eben evtl noch einige modulos zusätzlich mehr stattfinden
 
also solche strengen Anforderungen hab ich nicht :D

ich hab mir eure Rückmeldungen nochmal angeschaut und über die Zeit die ich hatte etwas hirnen lassen.
Alle "Timer" in der ISR laufen über Modulo und nach 1min wird der Counter zurückgesetzt.

Und für die Wartezeit die ich manchmal brauche bzw. die man eventuell noch für andere Dinge brauch werde ich einfach eine Funktion schreiben, die zum Startzeitpunkt den Wert des ui1MsCount kopiert und dann immer mit dem aktuellen ui1MsCount vergleicht.
Bzw. eine Rechnung macht, zum Beispiel ui1MsCount-Kopie und wenn dann die Differenz einen bestimmten Wert erreicht hat (z. B. 60.000), dann ist ja 1 Minute um und gibt eben was entsprechendes zurück.

Das ist doch vernünftig, oder?
Oder hab ich das unverständlich ausgedrückt? :D


PS:
sorry das ich so lange gebraucht hab mich zurückzumelden, aber ging nicht anders :(
 
Zuletzt bearbeitet:
Warum nach einer Minute zurücksetzten? Damit kannst du dann keinen nicht Ganzzahlig durch 60 Teilbaren Timer machen.

Und ja, ist umständlich ausgedrückt :D


PS: Deine (Variablen-)Namensgebung gefällt mir überhaupt nicht. Wofür steht überhaupt das ui in den ganzen Variablen?

Edit: Ah, unsigned int :D
 
Zuletzt bearbeitet:
Weiß auch nicht...
ich mein, ich könnte ihn einfach durchlaufen lassen, dann läuft er halt nach 65.535 über
aber dann stimmt es doch hinten raus wieder nicht, oder?
die Variable für 1 Minute sollte ja nach 60.000 auf 1 gesetzt werden, aber wenn ich es überlaufen lasse dann hab ich ja die 60.000 + 535 und bin damit dann bei 1 Minute und 5+ Sekunden für den zweiten Durchlauf

Und ja, ist umständlich ausgedrückt
Ich kann mich nie so gut erklären^^
 
Ich habe dir doch die lösung gezeigt:

uint32_t - das würde für knapp 50 Tage reichen
oder
uint64_t - das reicht über das Ende der Menschheit hinaus
 
Man kann die Zählvariable auch überlaufen lassen, ohne den Überlauf behandeln zu müssen.

Beispiel (anhand eines uint8):
DIFF_CNT = JETZT_CNT - START_CNT
5 - 253 = 8

und 8 entspricht auch der tatsächlichen zeitlichen Differenz.
 
Das funktioniert dann aber nicht wenn der Timer größer sein soll als das Maximum von int.
 
Zurück
Oben