Forum: Mikrocontroller und Digitale Elektronik Stoppuhr Code Optimierung


von Franny (Gast)


Lesenswert?

Hallo alle miteinander!

Ich habe für meinen ATMega8 ein kleines Programm für eine Stoppuhr 
geschrieben. Hierbei wird mit einem Taster die Stoppuhr gestartet und 
auf vier 7-Segment Anzeigen ausgegeben. Bei erneutem Drücken des Tasters 
wird die Uhr gestoppt. Drückt man jetzt erneut auf den Taster wird die 
Stoppuhr zurückgesetzt und erneut gestartet.
Das Programm funktioniert auch recht gut.
Allerdings bin ich noch recht neu in der Programmierung von uC und 
dementsprechend sieht auch mein Code aus.
Habt ihr vielleicht Tipps, ob ich hier ein paar Sachen zusammenfassen 
bzw. effizienter schreiben könnte?
Und wenn ja, wie?

Schonmal vielen Dank im Voraus!
Franny
1
#define F_CPU 8000000
2
#include <avr/io.h>
3
#include <avr/interrupt.h>
4
#include <util/delay.h>
5
6
uint16_t damn [10] = {(0x44), (0x7D), (0xA4), (0x34), (0x19), (0x16), (0x06), (0x7C), (0x04), (0x14)};
7
uint8_t counts[4];
8
9
volatile uint16_t var;
10
volatile uint16_t value;
11
volatile uint16_t digit;
12
volatile uint16_t x;
13
volatile uint16_t x1;
14
volatile uint16_t x2;
15
volatile uint16_t x3;
16
17
int main(void)
18
{
19
  DDRB |= (1<<PB0);    //Datenrichtungsregister
20
  DDRC |= 0x1E;
21
  DDRD |= 0xFB;
22
  
23
  MCUCR |= (1<<ISC01);  //externer Interrupt
24
  GICR |= (1<<INT0);
25
  
26
  TCCR0 |= (1<<CS00) | (1<<CS01);    //Timer 0
27
  TIMSK |= (1<<TOIE0);
28
  TCCR1B |= (1<<CS10);  //Timer 1
29
  TIMSK |= (1<<TOIE1);
30
  
31
  sei();    //Freigabe der Interrupts
32
  
33
  PORTC |= 0x1C;
34
  PORTD |= ~0xFB;
35
  
36
  while (1)
37
  {
38
  }
39
}
40
41
ISR(TIMER1_OVF_vect)
42
{  
43
  if (x2 == 1)
44
  {
45
    x1++;
46
  }
47
  if (x2 == 0)
48
  {
49
    x1 = 0;
50
  }
51
  
52
  if (x1 == 15/6)        //Für annähernden Sekundentakt
53
  {
54
    x3++;
55
    x1=0;
56
  }
57
  
58
  if (x3>9999)
59
  {
60
    x3=0;
61
  }
62
  
63
  value = x3;
64
    
65
  counts [3] = 0;          //Alles wieder auf 0 setzen
66
  counts [2] = 0;
67
  counts [1] = 0;
68
  counts [0] = 0;
69
    
70
  while(value >= 1000)      //Wertzerlegung
71
  {
72
    counts[0]++;
73
    value = value - 1000;
74
  }
75
    
76
  while(value >= 100)
77
  {
78
    counts[1]++;
79
    value = value - 100;
80
  }
81
    
82
  while(value >= 10)
83
  {
84
    counts[2]++;
85
    value = value - 10;
86
  }
87
    
88
  counts[3] = value;
89
}
90
91
ISR(TIMER0_OVF_vect)        //Weitergabe über die Transistoren
92
{
93
  var++;
94
  
95
  if (var > 1)
96
  {
97
    var = 0;
98
    digit++;
99
    
100
    if (digit > 4)
101
    {
102
      digit = 1;
103
    }
104
  }
105
  
106
  PORTC |= 0x1E;
107
  PORTC &= ~(1 << digit);
108
  PORTD = damn[counts[digit - 1]];
109
}
110
111
ISR(INT0_vect)
112
{
113
  PORTB ^= (1<<PB0);    //Debug LED
114
  x2++;
115
  if (x2 == 2)
116
  {
117
    x2 = 0;
118
  }
119
  if (x2 == 1)
120
  {
121
    x3=0;
122
  }
123
}

von Walter T. (nicolas)


Lesenswert?

Franny schrieb:
> Habt ihr vielleicht Tipps, ob ich hier ein paar Sachen zusammenfassen
> bzw. effizienter schreiben könnte?

Was stört Dich denn an Deinem Quelltext? Ich sehe jetzt keine schlimmen 
Antipattern für so ein kurzes Programm, außer dass die 
Programmbeschreibung (Kommentar) am Anfang fehlt.

Normalerweise pausiert man bei einer Stoppuhr mit einem kurzen 
Tastendruck. Mit einem weiteren kurzen Tastendruck kann weiterlaufen 
gelassen werden.

Zurückgesetzt wird mit einem langen Tastendruck oder mit einer extra 
Rücksetztaste.

von Sebastian R. (sebastian_r569)


Lesenswert?

Eigentlich sollte in den Interrupts so wenig wie möglich passieren, 
dafür mehr in der Main-Loop. Aber gut.

Für deine Wertezerlegung: Schau dir mal an, wie der Modulo-Operator (%) 
funktioniert. Der könnte sehr nützlich für dich sein.
Und bei Integern gilt: 7821/1000 = 7

Damit solltest du die While-Schleifen wegoptimieren können und das ganze 
schneller und übersichtlicher hinbekommen.

Noch ein Tipp zur Dokumentation:
Schreibe noch dazu, wie die einzelnen Timer konfiguriert sind und was 
sie machen.
1
  // Timer 2 konfigurieren
2
  GTCCR |= (1 << TSM) | (1 << PSRASY);  //Timer anhalten, Prescaler Reset
3
  ASSR |= (1 << AS2);                   //Asynchron Mode einschalten
4
  TCCR2A = (1 << WGM21);                //CTC Modus
5
  TCCR2B |= (1 << CS22) | (1 << CS21);  //Prescaler 256
6
  // 32768 / 256 / 1 = 128                Intervall = 1s
7
  OCR2A = 128 - 1;
8
  TIMSK2 |= (1<<OCIE2A);                //Enable Compare Interrupt
9
  GTCCR &= ~(1 << TSM);                 //Timer starten
10
  sei();

Hierbei kann man sofort sehen, was man sich bei der Konfiguration der 
Timer gedacht hat und vor allem, dass das Interrrupt jede Sekunde 
aufgerufen wird.

Nach einem halben Jahr wirst du bei deinem Code nicht mehr wissen, wann 
welches Timerinterrupt aufgerufen wird.

von Walter T. (nicolas)


Lesenswert?

Nachtrag: Und ich verstehe nicht, warum zwei ISRs nötig sind. Was die 
ISRs jeweils machen, sollte in einem Kommentar verewigt werden. Was 
eigentlich für alle Funktionen gilt.

von Teo (Gast)


Lesenswert?

Franny schrieb:
> Das Programm funktioniert auch recht gut.
> Allerdings bin ich noch recht neu in der Programmierung von uC und
> dementsprechend sieht auch mein Code aus.

Ah, dir sind also bereits ein paar Baustellen bekannt!
Da gibts nix zu optimieren, fange einfach noch mal von vorne an... und 
such dir geeignete Literatur!

von Walter T. (nicolas)


Lesenswert?

Teo schrieb:
> und such dir geeignete Literatur!

Achja. Die klassischen Nullaussagen.

von MaWin (Gast)


Lesenswert?

Das ist schon mal besser als das was man oft hier sieht.
Nur die Tastenauswertung per Interrupt wird durch prellende Tasten 
gestört. Schreib die um.

https://dse-faq.elektronik-kompendium.de/dse-faq.htm#F.29.1

Ich selbst würde weniger in die Interruptroutinen tun  und dafür den 
Stoppzeitzähler und Digitaufbereitungscode in die while(1) 
Hauptschleife, im Interrupt nur die Timerticks zählen, und käme mit 1 
Timer für multiplexing und tick zählen aus.

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Franny schrieb:
> Bei erneutem Drücken des Tasters wird die Uhr gestoppt.
Du hast den Taster im Ernst einfach so an einen Interruptpin 
angeschlossen? Dann solltest du dir die Sache mit dem "Prellen" von 
mechanischen Schaltern mal genauer anschauen.

> Habt ihr vielleicht Tipps, ob ich hier ein paar Sachen zusammenfassen
> bzw. effizienter schreiben könnte?
Ich würde 90% des ganzen Klimbims in der Mainloop machen. Und in der 
Timer-ISR nur einen ms-Timer (oder 10ms oder 50ms) hochzählen und darauf 
reagieren. Im selben Timer könnte dann auch die Multiplexerei 
abgehandelt werden. Und auch das Entprellen des Tasters.

: Bearbeitet durch Moderator
von Stefan F. (Gast)


Lesenswert?

Mir ist der Dokumentations-Stil besonders aufgefallen.

Ich empfehle sprechende Namen für Variablen und Funktionen. Der Name 
soll anzeigen, was die Variable bedeutet. Du hast viel Platz, niemand 
wird sich für Abkürzungen bedanken.

Was ist "damn"? Das heißt auf deutsch "verdammt"! Was ist verdammt?

Ganz oben sollte stehen, um welchen µC es geht.
1
// Für einen ATMega8 mit 8MHz Quarz

Deine Kommentare sollten nicht erklären was du tust (denn das ist 
offensichtlich, sondern warum. DDR sagt mir schon, dass du die 
Datenrichtung einstellt. Aber ich kann nicht erkennen, welchem Zweck die 
Pins dienen. Wenn sie etwas an/aus schalten, dann gebe an was da
dran hängt und, welcher Pegel an oder aus ist. Manchmal bedeutet ja auch 
LOW=an.
1
DDRC |= 0x1E; // Transistoren für die digits der 7-Segment Anzeige, HIGH=an

Für die Interrupt-Konfiguration:
1
// Interrupt bei fallender Flanke an INT0 (= PD2, Start-Taster, LOW=gedrückt)
2
MCUCR |= (1<<ISC01);
3
GICR |= (1<<INT0);

Für den Timer:
1
// Timer 0 dient dem Multiplexing der digits von der 7-Segment Anzeige
2
TCCR0 |= (1<<CS00) | (1<<CS01);  // Prescaler 64 = 125 kHz Takt
3
TIMSK |= (1<<TOIE0);             // Erlaube Timer Interrupts
4
// Der Timer 0 teilt die Takte durch 256 -> ruft die ISR mit ca. 488 Hz auf

Gleiches gilt für die anderen Timer.

Ich habe keine Ahnung, ob meine Kommentare richtig sind. Das ist alles 
nur geraten. Genau dieses Raten sollte nicht nötig sein.

> if (x1 == 15/6)      // Für annähernden Sekundentakt

Wenn du Integer Zahlen dividierst, kommt eine Integer Zahl ohne 
Nachkommastellen heraus. Das ergibt in deinem Fall einen großen 
Rundungsfehler. 15/6 ist 2 (nicht 2.5).

> // Wertzerlegung

Welcher Wert wird in was zerlegt und wofür ist das gut? Schreibe es hin!

Ich sehe viele unnötige Leerzeilen, die den Lesefluss behindern. Füge 
Leerzeilen nur zwischen Blöcken ein, die zusammen gehören. Aber nicht 
zwischen einzelnen Zeilen und Klammern.

von fop (Gast)


Lesenswert?

Sieht doch schon ganz gut aus für den Anfang.

Franny schrieb:
>
1
if (x1 == 15/6)        //Für annähernden Sekundentakt

Hier lauert nur ein Haken. Die Rechnung wird komplett 
nachkommastellenfrei ausgeführt. X1 wird also mit 2 verglichen. Macht 
-20% Abweichung.

von fop (Gast)


Lesenswert?

Und zum Beginn des Programms kannst Du ruhig alle Bits der 
Datenrichtungsregister überschreiben, anstatt nur die von Dir benötigten 
zu ändern. Ist für den µC sogar minimal weniger Arbeit.

von H.Joachim S. (crazyhorse)


Lesenswert?

Franny schrieb:
> uint16_t damn [10] = {(0x44), (0x7D), (0xA4), (0x34), (0x19), (0x16),
> (0x06), (0x7C), (0x04), (0x14)};

Das soll wohl der Siebensegmentcode sein? Warum 16bit? Und warum 
überhaupt ne Variable? Hol sie direkt aus dem flash, denn da liegen sie 
sowieso und müssen nicht im RAM sein. Und lesbarer schreiben.

Warum läuft digit von 1..4? Der Rest der Welt nimmt normalerweise 0..3.
Aber für den Anfang gar nicht so schlecht.

von Wolfgang (Gast)


Lesenswert?

Stefan ⛄ F. schrieb:
> Ich sehe viele unnötige Leerzeilen, die den Lesefluss behindern. Füge
> Leerzeilen nur zwischen Blöcken ein, die zusammen gehören. Aber nicht
> zwischen einzelnen Zeilen und Klammern.

Das sieht mehr nach einem Problem der Forensoftware aus, sobald die 
eingefügte Zeilennummer 3-stellig wird.

Ob

Franny schrieb:
1
 if (x2 == 0)
2
   {
3
     x1 = 0;
4
   }
nicht übersichtlicher als
1
if (x2 == 0) x1 = 0;
oder
1
if (x2 == 0)
2
  x1 = 0;
geschrieben werden kann, steht auf einem anderen Blatt.

von HildeK (Gast)


Lesenswert?

Wolfgang schrieb:
> Das sieht mehr nach einem Problem der Forensoftware aus, sobald die
> eingefügte Zeilennummer 3-stellig wird.

Scheint so zu sein. Glücklicherweise nicht, wenn man den File anhängt 
und ihn über die Codeansicht betrachtet. Das wäre bei einer 
dreistelligen Zeilenzahl auch anzuraten. Da werden dann nur die 
Zeilennummern auf zwei Stellen abgeschnitten. Was ist schon perfekt? 😉

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.