PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Riesen Problem mit Int0, Timer1 und Impulszählung



m@rkus33
19.01.2006, 00:37
Hallo zusammen,

ich habe ein riesieges Problem.
Ich habe einen Code bei dem über den Int0 die Impulse eines Durchflussmessers kontinuierlich gezählt werden. Wichtig ist das kein Impuls verloren geht. Gleichzeitig soll aber der Gesamtwert der Impulszählung über RS232 jede Sekunde ausgegeben werden.

Das Problem das ich jetzt habe ist, das zwar die Impulse gezählt werden aber bei höherer Frequenz die Daten nicht gesendet werden, da ja ständig der Interrupt des Impulszählers "aktiv" ist. Erst wenn keine Impulse mehr kommen bzw. die Impulsrate wieder sehr niedrig ist werden die Daten ausgegeben.

Die max. Impulszahl die kommen kann ist je nach Durchflussmengenmesser um die 30.000 Impulse/Min.

Wie kann ich das Problem lösen?

Hier der Code:




$regfile = "m8def.dat"
$crystal = 3686400
$baud = 9600

Dim Eu As Word
Dim Dfmimpulse As Long
Dim Tv As Integer
Dim Ti As Byte
Dim Tb As Byte
Dim Tank As Integer


Config Timer1 = Timer , Prescale = 64
Timer1 = 7936

Config Int0 = Falling
Config Pind.2 = Input 'Int0 bei Mega8
Portd.2 = 1


Config Adc = Single , Prescaler = Auto

Config Pinc.0 = Input 'ADC0 ein
Config Pinc.1 = Output 'Status LED Setup
Config Pinc.2 = Output 'Status LED Datensenden
Config Pinc.3 = Output 'Status LED impulse

Declare Sub Irq0
Declare Sub Datensenden

On Timer1 Datensenden
Enable Timer1

On Int0 Irq0
Enable Int0

Enable Interrupts

'-------------------
'Main
'-------------------

Portc.1 = 1
Portc.2 = 0
Portc.3 = 1

Dfmimpulse = 0
Tv = 5000
Ti = 0
Tb = 20
Tank = 0

Do
Portc.2 = 1
Portc.3 = 1

Start Adc 'Messung Akkuspannung
Eu = Getadc(0)
Stop Adc

'***Hier stehen noch ein paar interne Berechnungen

Portc.2 = 1

Loop

'-------------------
'Sub IrQ
'-------------------

Irq0:
Portc.3 = 0
Decr Tv 'für weitere interne Berechnungen
Incr Ti 'für weitere interne Berechnungen

If Ti = Tb Then 'für weitere interne Berechnungen
Ti = 0 'für weitere interne Berechnungen
Incr Tank 'Für weitere interne Berechnungen
End If 'für weitere interne Berechnungen


Incr Dfmimpulse 'Der ist wichtig und muss über RS232 raus
Return

'--------------------
'Sub RS232
'--------------------

Datensenden:

Portc.2 = 0

Print Dfmimpulse

Return




Und nu???????? Datensenden nicht über Timer? Oder geht es irgendwie das über den Timer die Daten gesendet werden und die Zählung der Impulse trotzdem weiterläuft?

Wie gesagt zwei Sachen sind wichtig: 1. Kein einziger Impuls darf verloren (nicht gezählt) werden. 2. Die Daten müssen alle 1 Sekunde gesandt werden.

Stehe jetzt voll auf dem Schlauch

Gruß
Markus

xanadu
19.01.2006, 09:54
Ohne mir den Code angesehen zu haben: wenn es tatsächlich so ist, dass die Interrupt-Routine für den Zähler so häufig angestoßen wird, dass der Prozessor zu nichts anderem mehr kommt, fallen mir zwei Lösungsansätze aus dem Stegreif ein:

1. Schnellere Taktung: Laut $crystal hats du einen 3.7 MHz Quartz im Einsatz. Tausche ihn gegen etwas deutlich schnelleres aus. Wenn du nur sekündlich ein paar Bytes überträgst und dazwischen immer wieder Sendepausen auf der RS232 herrschen, ist der Timingfehler zu verbachlässigen, insodern kommt durchaus ein 16MHz Quartz in Betracht, also immerhin die vierfache Geschwindigkeit.

2. Softwareoptimierung: Ich selber arbeite nicht mit Basic, kann also nichts dazu sagen, wie gut der erzeugte Code optimiert ist. Bei AVR-GCC bemerke ich deutliche Unterschiede zwischen den veschiedenen Optimirungsstufen. Zur Not muss man halt selbst zeitkritische Teile in Assembler proggen, was aber sicherlich ein hartes Los für einen Hochsprachler ist.

Gruß,
Chris

Guy
19.01.2006, 10:12
Du muß einen Timer als Counter benutzen und damit die Impulse zählen, und den andren Timer als Timer um jede Sekunde den Wert vom Counter über die RS232 auszugeben.

Edit:
Sicher kann er den Controller schneller Takten, irgendwann wird es dann klappen. Das ist heute so die Regel. Man schreibt ein schlechtes Programm und ändert die Hartware damit es läuft. Als die Amerikaner zum Mond flogen hatten die keine Controller mit 3 Mhz, und heute können wir nicht mal ein paar Impulse mit 3 Mhz zählen.

xanadu
19.01.2006, 10:53
Für die Impulszählung ist kein Timer nötig, das geht über externe Interrupts einwandfrei, schliesslich soll nur die Anzahl der Impulse gezählt werden, aber nicht ihre Frequenz (so hab ichs zumindest verstanden).

Timer1 (10bit) kann dann als Sekundentimer genutzt werden (CTC Mode).

Was die Geschichte mit der Optimierung durch schnellere Hardware angeht: für mich ein klares Dingen. Wenn ich Software optimieren möchte, weils mir ein gutes Gefühl gibt, dann optimiere ich Software. Wenn ich schnell zum Ziel kommen muss oder möchte, dann greife ich auch zum schnellen Mittel.

Guy
19.01.2006, 11:16
Für die Impulszählung ist kein Timer nötig, das geht über externe Interrupts einwandfrei, schliesslich soll nur die Anzahl der Impulse gezählt werden, aber nicht ihre Frequenz (so hab ichs zumindest verstanden).

Genau das ist ja der Fehler den er auch gemacht hat. Wenn du mit einem externe Interrupts Impulse Zählst dann macht der Controller sonnst nichts als dauernd die Unterroutine des Interrupts auszuführen, und es bleibt keine Zeit für andres.

Warum glaubst du den wozu das gut ist, dass man den Timer als Counter benutzen kann? Genau, dann werden die Impulse von der Hardware gezählt und belasten das laufende Programm nicht. Nur bei einem Überlauf, oder bei einem Interrupts durch eine Timer wird das Programm Unterbrochen. Also bei Ihm hier jede Sekunde.

xanadu
19.01.2006, 11:53
Ah jetzt ja. Jetzt hab ich verstanden. Okay, den Ansatz lasse ich gelten ;-)

m@rkus33
19.01.2006, 12:26
Danke Jungs,

also habe ich das richtig verstanden:

Die Impulszählung nicht vom Interrupt sondern vom Timer zählen lassen.

Der Timer lässt das Programm laufen und zählt sozusagen im "Hintergrund" obwohl das Programm gerade mit anderen Dingen wie das Senden der Daten beschäftigt ist.

Wogegen der Interrrupt ganz brutal alles blockt wenn er "aktiv" ist.

So richtig?

Dann werd ich mal die beiden Timer bemühen.

Gruß
Markus

Guy
19.01.2006, 13:28
Ja genau. Du kannst dir das so vorstellen als hättest du einen externen Zähler den du jede Sekunde ausliest.

Du mußte jetzt nur noch schauen ob du den Counter bei jeder Ausgabe wieder auf null setzt, oder den Counter durchlaufen läset und die Differenz rechnest. Wenn du den Counter auf Null setzt, dann solltest du erst den Wehrt in eine Variable setzten, und dann auf Null setzen. Erst dann die Ausgabe auf die Schnittstelle senden.

Noch mal zu deinem Prog mit dem Interrrupt. Selbst wenn das Programm in die (Datensenden) Routine springen, hättest du immer das Problem, dass während der Ausführung dieser Routine keine weitere Interrrupt ausgeführt werden können weil die Interrrupts blockiert sind, und du so Impulse verlieren kannst. Beim AVR kannst du keine zwei Interrrupt gleichzeitig ausführen, oder aus einem Interrrupt in einen andren springen.

m@rkus33
19.01.2006, 13:51
Hallo Guy,

wäre es schlau die Timervorgabe so zu wählen das bei jedem Impuls der Timer überläuft und in meine alte Interruptroutine spring? Müsste dann weniger in meinem Code ändern. Oder ist dann das gleiche Problem wie vorher?

Guy
19.01.2006, 14:28
Dann hast du das selbe Problem. Für den Counter braust du keine Interruptroutine, der wird ja nie überlaufen Bei 30.000 Impulse/Min. sind ja nur 500 Impulse/sec.

An deinem Programm braust du ja nicht viel zu ändern. Braust nur den Interrupt raus zuschmeißen und den Zweiten Timer als Counter einstellen. Dann in der Ausgabe die ja durch den andren Timer jede Sekunde angesprungen wird den Counter auslesen. Du mußt nur den Impuls an den richtigen Pin vom Timer-Counter legen.

Ok, habe jetzt mal dein Programm geschaut, du machst noch in der Interruptroutine Berechnungen. Das solltest du sowieso vermeiden. Eine Interruptroutine sollte immer so schnell wie möglich verlassen werden.

Es gibt noch eine andre Möglichkeit. Du läßt das Programm in einer DO Schleife laufen. wartest immer auf steigende oder fallende Flanke und Zählst dann jedesmal eine Variable hohe, und machst dann deine Brechungen. Die Berechnungen dürfen aber auch nicht zulange dauern weil sonst auch Impulse verlorengehen können. Mit dem Timer Springst du dann jede Sekunde zur Ausgabe. Aber auch hier besteht das Risiko das währen der abarbeiteung dieser Routine Impulse verloren gehen.

Wenn keine Impulse verloren gehen dürfen ist es mit einem Timer als Counter am sichersten.

xanadu
19.01.2006, 14:59
Beim AVR kannst du keine zwei Interrrupt gleichzeitig ausführen, oder aus einem Interrrupt in einen andren springen.

Bist Du sicher? Im WinAVR gibt es zwei Macros (SIGNAL und INTERRUPT), sie unterscheiden sich darin, dass bei SIGNAL Inteterrupts nicht unterbrochen werden können, bei INTERRUPT wohl.

Problematisch wirds doch dann erst, wenn ein Interrupt sich selbst unterbricht, was bei einem einfachen Zähler-Inkrement in einem Counter Overflow wohl kaum su erwarten ist.

Allerdings würde dadurch auch während der Abarbeitung der Sekunden-Routine kein Zählimpuls verloren gehen.

m@rkus33
19.01.2006, 15:26
...jetzt kenn ich mich gar net mer aus...

Ich habe bisher nur begrenzte Erfahrung mit der Proggerei.



Es gibt noch eine andre Möglichkeit. Du läßt das Programm in einer DO Schleife laufen. wartest immer auf steigende oder fallende Flanke und Zählst dann jedesmal eine Variable hohe, und machst dann deine Brechungen. Die Berechnungen dürfen aber auch nicht zulange dauern weil sonst auch Impulse verlorengehen können. Mit dem Timer Springst du dann jede Sekunde zur Ausgabe. Aber auch hier besteht das Risiko das währen der abarbeiteung dieser Routine Impulse verloren gehen.


Also aus dem Bauch raus gefällt mir die Lösung. Da komm ich auch Kopfmäßig noch mit ;-)
Das kommt meiner Bisherigen Routine am nächsten. Da ich mit dem hochzählen einer Variablen besser zurecht komme.

Ich hab allerdings sowas mit den Timern als Counter noch nie gemacht. Wie konfiguriere ich dann den Timer1 das bei jeder Flankenänderung (ob steigend oder fallend) eine Variable hochgezählt wird? Kurzes Beispiel wäre super.

Gruß
Markus

m@rkus33
19.01.2006, 23:31
Hallo Guy

jetzt hab ich aber das nächste Problem. Ist mir gerade eingefallen:

Den 16bit Timer1 brauche ich als counter aber mit dem Timer0 bekomme ich ja gar keine Sekunde hin. Dann würde die Ausgabe ja längsten 15mal in der Sekunde kommen. Das müllt mir ja die RS232 zu.

Und nu??? Hast Du eine Idee? Ich bin hier voll am Ende.

Gruß
Markus

Bluesmash
19.01.2006, 23:44
hallo markus

du kannst ja im timer0 interrupt einfach ne variabel raufzählen und wen sie auf 15 ist, variabel auf null setzen und dein wert ausgeben...

gruss bluesmash

Guy
20.01.2006, 09:49
So könntest du das machen. Timer1 zählt die Impulse. Mit Timer0 springst du 15 mal in der Sekunde in die Timer_irq Routine. Dort machst du einen Zähler. Wenn der Zähler 15 erreicht hat = 1 Sekunde, dann gibst du den Wehrte aus.


Dim Zahler_timer As Integer

Config Timer1 = Counter , Edge = Falling
'oder Edge = Rising

$regfile = "m16def.dat"
$crystal = 3686400
$baud = 9600

Config Timer0 = Timer , Prescale = 1024
On Timer0 Timer_irq
Const Timervorgabe = 16

Enable Timer0
Enable Timer1

Enable Interrupts

Do
Loop
End

Timer_irq:
Timer0 = Timervorgabe
Zahler_timer = Zahler_timer + 1
If Zahler_timer => 15 Then
Print Timer1
Timer1 = 0
Zahler_timer = 0
End If
Return

Eine andre Möglichkeit ist den Timer0 zum zählen der Impulse benutzen. Dann bei jedem Überlauf in eine Unterroutine springen und jedes mal 255 zu einer Variable rechnen. Bei 1 Sekunde diese Variable + den Timer0 ausgeben.

Am besten währe es wenn du einen Controller mit zwei 16 Bit Timern nimmst.

Um dir mal den Unterschied zwischen Timer und Counter zu erklären. Im Prinzip ist es sehr einfach. Jeder Timer ist nur ein Counter. Mit Config Timer1 = Counter oder Config Timer1 = Timer entscheidest du nur was der Counter Zählt. Interner Takt oder externer Takt.


@xanadu
Ob das jetzt an Bascom oder am AVR liegt kann ich auch nicht hundertprozentig sagen. Nur bei Bascom ist es mal so. Aber wenn das möglich ist aus einem INTERRUPT in einen andren zuspringen, dann muß man auch Prioritäten setzten können. Es ist ja nicht immer erwünscht wegen einen unwichtigen INTERRUPT einen wichtigen zu unterbrechen.

m@rkus33
20.01.2006, 10:04
Hallo Bluesmash,

...mensch logisch! ](*,) Hab da gerade voll die Hirnblockade #-o gehabt.

Jetzt geht es.


Aber mal was anderes. Wie bekomme ich es hin, das eine Long-Variable die über RS232 vom Impulsmesser gesand wird auf einem LCD beim Empfänger auch richtig angezeigt wird.

Es ist so, das ich im Terminalprogramm den Zahlenwert wunderbar sehe so wie er tatsächlich ist, auf dem LCD aber nicht.

Konkret:

Mein Impulsmesser sendet jede Sekunde über RS232 mit "Pirint Impulswert" den Zahlenwert. (von 0 - kann bis 100.000 hochlaufen) diese Variable "Impulswert" ist beim Sender eine Long-Variable. Schließe ich an die RS232 das Terminalprogramm an sehe ich wunderbar die Zahlen hochlaufen.

Schließe ich aber einen anderen Mega8 mit LCD an kommt anstelle der Zahl nur undefinierbarer Müll auf dem Display.

Display geht, da alle anderen Sachen wunderbar angezeigt werden.

Code beim Empfänger:




$regfile = "m8def.dat"
$crystal = 3686400
$baud = 19200

Dim Impulswert As Long

Config Lcd = 20 * 4
Config Lcdpin = Pin , E = Portd.3 , Rs = Portd.2 , Db7 = Portd.7 , Db6 = Portd.6 , Db5 = Portd.5 , Db4 = Portd.4


CLS
Lcd "Impulswert:"

Do

Impulswert = Inkey()

Locate 1, 13
Lcd Dfmimpulse

Loop



Ich hab es schon mit "Input", "Inputbin", "waitkey()" usw. probiert. Jedesmal kommt was anderes undefinierbares.

Eine Idee?

Gruß
Markus

Guy
20.01.2006, 10:17
Also wenn ich das richtig verstehe sendest du von einem Controller zum andren über die RS232. Wenn ich jetzt deine zwei Programme sehe die du hier rein gestellt hast, dann hast du im Sender $baud = 9600 eingestellt, und im Empfänger $baud = 19200. Das würde natürlich den Müll erklären.

m@rkus33
20.01.2006, 10:33
Hallo Guy,

uhps.. Schreibfehler. Beide laufen auf der gleichen Baudrate von 9600.

Sorry.

Gruß
Markus

Guy
20.01.2006, 10:48
OK, Inkey gibt dir den ASCII code zurück. Das heißt du muß den wieder umwandeln mit Chr(xy)

xanadu
20.01.2006, 10:54
Aber wenn das möglich ist aus einem INTERRUPT in einen andren zuspringen, dann muß man auch Prioritäten setzten können. Es ist ja nicht immer erwünscht wegen einen unwichtigen INTERRUPT einen wichtigen zu unterbrechen.

Aus dem Datenblatt vom Atmega 32:

"The interrupts have priority in accordance with their
interrupt vector position. The lower the interrupt vector address, the higher the priority."

Die höchste Prio haben die externen Interrupts, danach kommen die Timer.

Guy
20.01.2006, 11:01
Die höchste Prio haben die externen Interrupts, danach kommen die Timer.
Ok, ich hatte das Problem noch nicht, aber Danke gut zu wiesen.

m@rkus33
20.01.2006, 11:44
@Guy

wg. der Anzeige:
ich habe es gerade mit

Variable = Inkey()
Lcd Chr (variable)

versucht.

Das Display zeigt mir jetzt nur irgendwelche Zeichen und Symbole an.

...hmm

Liegt es daran das die gesendete und hier empfangene Variable eine Long Variable ist und nur das erste Byte von den vier Bytes ausgelesen wird?

Gruß
Markus

Guy
20.01.2006, 12:12
Ja INKEY empfangt immer nur ein Zeichen als ASCII. Also mußt du das wieder um wandeln mit CHR und zusammen setzen.

Mit Long Variable geht das wahrscheinlich nicht, das ist ja ein Zeichen und keinen numerischen Wert. Nimm einfach mal eine String Variable.

Am besten du versuchst es mal mit einem Zeichen. Sende zum Beispiel erstmals nur ein "A" zum testen.