Forum: Mikrocontroller und Digitale Elektronik Ereignisse zählen und mit Vorgabe pro Minute vergleichen.


von kopfkratzer (Gast)


Lesenswert?

Hallo,
ich stehe gerade selber auf dem Schlauch und finde den Baum vor lauter 
Wald nicht m(
Ich möchte mit einem Tiny25 Ereignisse zählen (z.B. via Taster/Reed 
o.ä.) und die Anzahl davon mit einem Vorgabewert vergleichen.
Timer1 läuft und gibt mir Sekunden und Minuten an.
Über einen Poti soll nun eingestellt werden wieviele Ereignisse pro 
Minute stattfinden müssen um einen Schaltvorgang auszulösen.
Meine erste Idee ist nun im Timer neben den Sekunden noch den 
Taster/Eingang abzufragen und zu entprellen um dann ein Flag zu setzen.
So und nun steh ich auf dem Schlauch und bekomme es aktuell nicht 
gebacken wie ich nun das pro Minute realisiere ?
Zwischenzeit nehmen oder extra Sekundenzähler lokal im IRQ ?
Das ganze ist nicht Zeitkritisch, maximal sollen 60 Ereignisse pro 
Minute vorkommen können.
Wo ist mein Denkfehler ?

von Karl H. (kbuchegg)


Lesenswert?

kopfkratzer schrieb:

> Zwischenzeit nehmen oder extra Sekundenzähler lokal im IRQ ?
> Das ganze ist nicht Zeitkritisch, maximal sollen 60 Ereignisse pro
> Minute vorkommen können.

Die Frage ist eher:
Wann beginnt eigentlich die Minute zu laufen?


Du kannst natürlich in deiner Timer ISR bei der Implementierung der Uhr, 
an der Stelle an der die Minuten weitergeschaltet werden einen 
Ereigniszähler auf 0 setzen. In der Hautpschleife werden die 
Tastendrücke ausgewertet und der Zähler entsprechend erhöht.
Vor dem 0-setzen des Zählers prüfst du noch, ob du unter dem Limit warst 
und reagierst entsprechend.
1
uint16_t Count;
2
uint16_t CountMax = 50;   // per ADC änderbar
3
4
ISR( ... Timer ... )
5
{
6
  ...
7
8
  Second++;
9
  if( Second == 60 )
10
  {
11
    if( Count > CountMax )
12
      LED ein
13
    else
14
      LED aus
15
16
    Count = 0;
17
  }
18
}
19
20
int main()
21
{
22
  ...
23
24
  while( 1 )
25
  {
26
    if( get_keypress( 1 << KEY ) )
27
      Count++;
28
29
    ...
30
  }
31
}

Aber das wird nur dann funktionieren, wenn es da einen kontinuierlichen 
Tastendruck-Strom gibt.

Hast du 30 Tastedrücke in den letzten 10 Sekunden einer Minute und 30 
Tastendrücke in den ersten 10 Sekunden der nächsten Minute, dann ist 
zwar jede einzelne Minute für sich unter dem Limit von 50. Aber 
betrachtet man die 'Minute, die bei einem Sekundenstand von 30 begonnen 
hätte und auch wieder endet, dann wäre diese Minute mit einem Count von 
60 über dem Limit.


D.h. so einfach ist die Sache gar nicht, weil nicht klar ist, was 'in 
einer Minute' eigentlich bedeuten soll. Ist das die Minute die mit dem 
Sekundenzeiger auf 0 (oder irgendeiner anderen Zahl) beginnt, oder sind 
das 'die letzten 60 Sekunden' (egal zu welchem Zeitpunkt sich diese 
Frage stellt)

Ich würde eventuell so eine Art Sliding Window Ansatz ins Kalkül ziehen. 
D.h. ich weiß von allen Tastendrücken der letzten 60 Sekunden, wann 
genau sie in diesen 60 Sekunden aufgetreten sind. Mit jeder Sekunde 
fallen hinten aus dieser Liste von Ereignissen diejenigen raus, die über 
60 sind. Bei einem Tastendruck wird am anderen Ende der Liste zusammen 
mit einem Timestamp angefügt. Die Länge der benutzten Liste darf nie 
größer als das Limit sein.
Implementieren kann man das als eine Variation eines Ringbuffers.

> Das ganze ist nicht Zeitkritisch, maximal sollen 60 Ereignisse pro Minute 
vorkommen können.
Also: überschaubare Zahlen, so das man auch ein wenig urassen darf. :-)

128 Bytes SRAM beim Tiny25 ist natürlich nicht viel, aber mit einem 
maximal erwarteten Limit von 60 noch beherrschbar.

: Bearbeitet durch User
von Paul B. (Gast)


Lesenswert?

Ich denke, man muss hier jede Minute einzeln betrachten. Das läuft auf 
besagten Ringpuffer hinaus, der permanent aufsummiert werden muss, um 
die in der letzten Minute insgesamt gedrückten Tasten zu erkennen. Dies 
wäre soweit noch einfach, allerdings  kommt die Aufgabe ja irgendwo her 
und ich vernute hier wenigstens noch die Themen ->Totzeit und -> 
Entprellung. Die erstere Thematik wäre statistisch zu lösen, wie beim 
Geigerzähler, die zweite braucht eine Analyse, was denn nun 2 Tasten 
waren und was eine, die prellt.

von Route_66 H. (route_66)


Lesenswert?

Hallo!
Soll es etwas wie ein Schlagzahlmesser beim Kanu oder beim Spinning 
werden: wenn Sollwert erreicht, geht die grüne Lampe an?

Dafür zählt man nicht über eine Minute, sondern misst die Zeit zwischen 
einzelnen Impulsen, mittelt eventuell gleitend über mehrere Abstände und 
entscheidet dann, Sollwert erreicht oder nicht.

von Conny G. (conny_g)


Lesenswert?

Route 66 schrieb:
> Hallo!
> Soll es etwas wie ein Schlagzahlmesser beim Kanu oder beim Spinning
> werden: wenn Sollwert erreicht, geht die grüne Lampe an?
>
> Dafür zählt man nicht über eine Minute, sondern misst die Zeit zwischen
> einzelnen Impulsen, mittelt eventuell gleitend über mehrere Abstände und
> entscheidet dann, Sollwert erreicht oder nicht.

Wenn die Impulse durch eine Rotation oder einen Rhythmus gleichverteilt 
sind.
Dann kann man sozusagen die "Runrate" bestimmen durch den Abstand von 
zwei Impulsen.
Wenn die Impulse aber zufällig kommen, dann muss ich für 1 Minute zählen 
(und mir von "jetzt" die letzten 60 Sekunden ansehen) und entscheiden.

von Conny G. (conny_g)


Lesenswert?

Karl Heinz schrieb:
> Ich würde eventuell so eine Art Sliding Window Ansatz ins Kalkül ziehen.
> D.h. ich weiß von allen Tastendrücken der letzten 60 Sekunden, wann
> genau sie in diesen 60 Sekunden aufgetreten sind. Mit jeder Sekunde
> fallen hinten aus dieser Liste von Ereignissen diejenigen raus, die über
> 60 sind. Bei einem Tastendruck wird am anderen Ende der Liste zusammen
> mit einem Timestamp angefügt. Die Länge der benutzten Liste darf nie
> größer als das Limit sein.
> Implementieren kann man das als eine Variation eines Ringbuffers.
>
>> Das ganze ist nicht Zeitkritisch, maximal sollen 60 Ereignisse pro Minute
> vorkommen können.
> Also: überschaubare Zahlen, so das man auch ein wenig urassen darf. :-)
>
> 128 Bytes SRAM beim Tiny25 ist natürlich nicht viel, aber mit einem
> maximal erwarteten Limit von 60 noch beherrschbar.

Das könnte man noch variieren und eine Bitmap als Speicher wählen:
ich habe 60 Bits = 8 Bytes als Speicher für die Ereignisse.
Jedes Bit repräsentiert 1 Sekunde und es gibt einen Zeiger für Start und 
Ende des Ringpuffers.
Also habe ich immer einen Überblick über 1 Minute, kompakt in 8 Bytes.
Dann ist die Auflösung natürlich nur 1 Sekunde, aber genau das war ja 
gefragt.

von Falk B. (falk)


Lesenswert?

@ Conny G. (konrad_g)

>Dann kann man sozusagen die "Runrate" bestimmen durch den Abstand von
>zwei Impulsen.

Fürher (tm) hieß das einfach Periodendauer.

>Wenn die Impulse aber zufällig kommen, dann muss ich für 1 Minute zählen
>(und mir von "jetzt" die letzten 60 Sekunden ansehen) und entscheiden.

Nennt sich Mittelung oder Tiefpassfilter.

von Karl H. (kbuchegg)


Lesenswert?

Conny G. schrieb:

> Das könnte man noch variieren und eine Bitmap als Speicher wählen:
> ich habe 60 Bits = 8 Bytes als Speicher für die Ereignisse.
> Jedes Bit repräsentiert 1 Sekunde und es gibt einen Zeiger für Start und
> Ende des Ringpuffers.

gute Idee.
Ich hätte das viel banaler gemacht. Aber so gehts natürlich auch, wenn 
es nicht mehr als 1 Ereignis pro Sekunde geben kann.

Ich hätt ganz einfach bei Auftreten eines Ereignisses eine 0 in den 
Ringpuffer eingeschoben. Die 0 ist der Zeitstempel.
Jede Sekunde läuft die ISR und klappert den Ringpuffer ab und erhöht 
alle Zeitstempel um 1. Alle Zeitstempel über 60 fliegen raus aus dem 
Ringpuffer.

Mir ist nämlich ums verrecken nicht in 2 Minuten eingefallen, wie ich 
das mit der aktuellen Sekundennummer im Ringpuffer hinkriegen könnte, so 
dass alle Zeitstempel mit einer Differenz größer als 60 Sekunden zur 
aktuellen Sekundennummer erkennbar sind. :-) Daher der pragmatische 
Ansatz: jede Sekunde werden alle Einträge im Ringpuffer um 1 Sekunde 
älter und die ältesten fliegen raus. Bei seinen Zeitvorgaben mach ich 
mir da keine Sorgen, dass das zu langsam wird, aber es wurmt mich, dass 
das natürlich nicht sehr elegant ist :-)

: Bearbeitet durch User
von Conny G. (conny_g)


Lesenswert?

Falk Brunner schrieb:
> @ Conny G. (konrad_g)
>
>>Dann kann man sozusagen die "Runrate" bestimmen durch den Abstand von
>>zwei Impulsen.
>
> Fürher (tm) hieß das einfach Periodendauer.

Mja, stimmt :-)

>>Wenn die Impulse aber zufällig kommen, dann muss ich für 1 Minute zählen
>>(und mir von "jetzt" die letzten 60 Sekunden ansehen) und entscheiden.
>
> Nennt sich Mittelung oder Tiefpassfilter.

Ja....! Das ist auch noch ein Ansatz: bei jedem Impuls die Zeitabstände 
einem gleitenden Mittelwert hinzufügen.
Das hat eine gewisse Unschärfe, aber vielleicht ist die ja ok.

von kopfkratzer (Gast)


Lesenswert?

Conny G. schrieb:
> Das könnte man noch variieren und eine Bitmap als Speicher wählen:
> ich habe 60 Bits = 8 Bytes als Speicher für die Ereignisse.
> Jedes Bit repräsentiert 1 Sekunde und es gibt einen Zeiger für Start und
> Ende des Ringpuffers.
> Also habe ich immer einen Überblick über 1 Minute, kompakt in 8 Bytes.
> Dann ist die Auflösung natürlich nur 1 Sekunde, aber genau das war ja
> gefragt.

Danke,
genau das ist die Lösung die mir nicht eingefallen ist ;-)
Jetzt stellt sich mir nur noch die Frage wie ich das am sinnvollsten 
realisiere ?
Geht das auf dem Tiny25:
1
.
2
.
3
.
4
//Buffer allozieren
5
volatile uint8_t *pEvent = malloc(8*sizeof(uint8_t));
6
7
ISR(..timer...)
8
{
9
...
10
//Taster/Ereignis abfragen inclusive Entprellung
11
event = getkey();
12
...
13
uSecond++%60;
14
//Event hineinschreiben
15
pEvent[uSecond]=event;
16
...
17
}
18
.
19
.
20
.
Oder zerschieße ich mir damit irgendwas ?
Bzw. reicht der Tiny25 dann noch aus, wegen malloc & Co. ?
Wobei mir ja gerade auffällt das keine Bitmanipulation ist o_O
kopfkratz
OK kann mir bitte mal jemand ein passendes Tutorial bzw. Beispielcode 
nennen ?
Oder muß ich jedes uint8_t einzeln mit einer Bitmaske bearbeiten und 
dann eines weiter gehen, sobald ich das letzte Bit benutzt habe ?
Dann wäre ein einfaches Array sinnvoller ?
Danke nochmals ;-)

von PittyJ (Gast)


Lesenswert?

Ein Grundkurs C Programmierung wäre nicht schlecht.
Der Beispielcode kann nicht vernünftig gehen. Man sollte Bits, Bytes und 
malloc() schon ein bisschen kennen.

Der ++% Operator ist schon eine Klasse für sich... Macht wohl nur nicht 
das, was gedacht wurde.

von Conny G. (conny_g)


Lesenswert?

Eher irgendwie so:
1
uint8_t eventBuffer[8];
2
uint8_t start, end;
3
uint8_t second
4
5
void addBit( uint8_t bitValue )
6
{
7
  // end eins hochzählen und ggf im Ringpuffer "umbrechen"
8
  end = (end++ % 60);
9
10
  // byte und bit-Adresse berechnen
11
  uint8_t byteOfs = second / 8;
12
  uint8_t bitOfs = second % 8;
13
14
  if ( bitValue )
15
  {
16
    eventBuffer[byteOfs] |= (1 << bitOfs);
17
  }
18
  else
19
  {
20
    eventBuffer[byteOfs] &= ~(1 << bitOfs);
21
}
22
23
/* jede Sekunde */
24
ISR(..timer...)
25
{
26
  if ( getKey() )
27
  {
28
    addBit( getKey() );
29
  }
30
}

Hier stimmt jetzt die Ringpufferverwaltung noch nicht, denn man müsste 
auch das "start" noch weitersetzen, wenn der Puffer voll ist sodass 
Elemente im Puffer immer = 60.

von Conny G. (conny_g)


Lesenswert?

Fehler drin (Second im addBit ist falsch), neu:
1
uint8_t eventBuffer[8];
2
uint8_t start, end;
3
uint8_t second
4
5
void addBit( uint8_t bitValue )
6
{
7
  // end eins hochzählen und ggf im Ringpuffer "umbrechen"
8
  end = (end++ % 60);
9
10
  // byte und bit-Adresse berechnen
11
  uint8_t byteOfs = end / 8;
12
  uint8_t bitOfs = end % 8;
13
14
  if ( bitValue )
15
  {
16
    eventBuffer[byteOfs] |= (1 << bitOfs);
17
  }
18
  else
19
  {
20
    eventBuffer[byteOfs] &= ~(1 << bitOfs);
21
}
22
23
/* jede Sekunde */
24
ISR(..timer...)
25
{
26
  if ( getKey() )
27
  {
28
    addBit( getKey() );
29
  }
30
}

von Karl H. (kbuchegg)


Lesenswert?

Conny G. schrieb:

> Hier stimmt jetzt die Ringpufferverwaltung


Hmm.
Wenn ich mich nicht verhaut habe, braucht man doch den Ringpuffer im 
Grunde gar nicht mehr.
Du hast 60 Bit, jedes hält fest, ob in dieser Sekunde ein Ereignis 
eingetreten ist oder nicht. Das reicht doch schon völlig, genau so wie 
du das gemacht hast. Mit der Sekundennummer (nenn 'end' einfach um in 
'sekunde') wird ein Bit von diesen 60 gesetzt oder gelöscht. Alles was 
man noch braucht, ist die Anzahl der gesetzten Bit von diesen 60. Das 
ist aber einfach zu realisiern. Man braucht nur mitzählen.


(aus Übersichtsgründen nenne ich ein paar Dinge um. Am Prinzip hat sich 
aber nicht viel geändert)
1
uint8_t eventBuffer[8];
2
uint8_t nrEvents = 0;
3
uint8_t second = 0;
4
5
void registerEvent( uint8_t second, uint8_t bitValue )
6
{
7
  // byte und bit-Adresse berechnen
8
  uint8_t byteOfs = second / 8;
9
  uint8_t bitOfs = second % 8;
10
11
  // den vorhergehenden 'Status' für diese Sekunde austragen
12
  if ( eventBuffer[byteOfs] & ( 1 << bitOfs ) )
13
    nrEvents--;
14
15
  // den neuen eintragen
16
  if ( bitValue )
17
  {
18
    eventBuffer[byteOfs] |= (1 << bitOfs);
19
  }
20
  else
21
  {
22
    eventBuffer[byteOfs] &= ~(1 << bitOfs);
23
  }
24
25
  // und gegebenenfalls zählen
26
  if ( bitValue )
27
    nrEvents++;
28
}
29
30
/* jede Sekunde */
31
ISR(..timer...)
32
{
33
  second++;
34
  if( second == 60 )
35
    second = 0;
36
37
  uint8_t event = getKey();
38
  registerEvent( second, event );
39
}

nrEvents müsste hier eigentlich ständig die gesuchte Zahl enthalten (die 
Anzahl an Ereignissen in den letzten 60 Sekunden).
Voraussetzung: nicht mehr als 1 Event pro Sekunde, obwohl man das auch 
noch aufweichen könnte, wenn das zu restriktiv ist. Das die ISR im 
Sekundentakt die Eingänge prüft, ist ja recht willkürlich einfach so 
festgelegt. Die könnte das ja auch im halbe Sekunde-Takt machen und in 
120 Bits den Status festhalten.

: Bearbeitet durch User
von Conny G. (conny_g)


Lesenswert?

Mmmh, jetzt brauchen wir den Ringpuffer nur noch um eins zu 
dekrementieren, wenn das letzte Bit 1 war.
Bekommen wir ihn noch ganz weg? :-)

von Karl H. (kbuchegg)


Lesenswert?

Conny G. schrieb:
> Mmmh, jetzt brauchen wir den Ringpuffer nur noch um eins zu
> dekrementieren, wenn das letzte Bit 1 war.
> Bekommen wir ihn noch ganz weg? :-)


Der Ringbuffer existiert so überhaupt nicht mehr.

Das Array ist einfach nur ein Flag-Array in dem zu jeder Sekundennummer 
vermerkt ist, ob ein Ereignis aufgetreten ist oder nicht. Da die 
Sekunden sequentiell in der ISR abgearbeitet werden, überschreibt nach 
60 Sekunden die jeweils dann vorliegende Ereignis-Information 
automatisch die Information von 1 Minute früher.
Alles was ich noch ergänzt habe, ist das mitzählen der 1-Bits in diesem 
Array. Dann braucht man da nicht aufwändig die 1-Bits zählen. Die 
Information ergibt sich ganz zwangsläufig aus dem 'alten' Status von vor 
1 Minute und dem jeweils neu einzuschreibenden Status von jetzt.

Stell dir eine Analoguhr vor, bei dem es zu jeder Sekunde 1 Fach gibt 
(so wie bei einem Roulett-Kessel). Im Fach kann eine Murmel liegen oder 
nicht.
Nach 1 Sekunde schiebt sich der Sekundenzeiger um 1 weiter.
Man sieht im Fach, auf das der Sekundenzeiger zeigt, nach: liegt da eine 
Murmel drinnen, dann gab es 1 Minute vorher ein Ereignis, daher wird von 
der Gesamtzahl der Ereignisse 1 abgezogen, weil dieses Ereignis ja 
aufgrund der vergangenen Zeit aus der Statistik rausfliegt. Damit ist 
die Buchhaltung für 'alte Ereignisse' erledigt. Hat man ein neues 
Ereignis, welches zu registrieren ist, dann wird wieder eine Murmel 
reingelegt und der Zähler um 1 erhöht. Hat man kein Ereignis, dann 
bleibt das Fach leer.
Und so geht das weiter. Sekunde für Sekunde. Der Zustand von vor 1 
Minute wird jedesmal aufgehoben (das Fach geleert und die Summe 
gegebenenfalls korrigiert) und je nach aufgetretenem Zustand wird das 
Fach befüllt oder auch nicht (und der Zähler entsprechend hochgezählt 
oder auch nicht).

: Bearbeitet durch User
von Conny G. (conny_g)


Lesenswert?

Karl Heinz schrieb:
> Der Ringbuffer existiert so überhaupt nicht mehr.

Ist klar, ich hab ihn unkorrekterweise nur nochmal so genannt.
Auch klar, was jetzt daraus geworden ist.
Nach ein bisschen nachdenken kam ich auch zu dem Schluss, dass man sich 
den Puffer nicht sparen kann.
Denn ich muss irgendwie an das Event vor 60 Sekunden herankommen und das 
geht nur über die ganze Historie.

Coole Lösung geworden, ganz simpel und nur 8 Bytes Speicher.

von Conny G. (conny_g)


Lesenswert?

Karl Heinz schrieb:
> Das Array ist einfach nur ein Flag-Array in dem zu jeder Sekundennummer
> vermerkt ist, ob ein Ereignis aufgetreten ist oder nicht. Da die
> Sekunden sequentiell in der ISR abgearbeitet werden, überschreibt nach
> 60 Sekunden die jeweils dann vorliegende Ereignis-Information
> automatisch die Information von 1 Minute früher.

Ah, jetzt. Habe beim Überfliegen eins nicht erkannt: jede Sekunde hat 
jetzt ein festes Bit!
Das ist eine coole Vereinfachung, ja!

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.