Forum: Compiler & IDEs Mein erstes Projekt


von Michael R. (mray)


Angehängte Dateien:

Lesenswert?

Ich habe die Programierung meines ersten Projektes(fürs Erste) 
abgeschlossen. Kurz zum Projekt: Es ist ein Logger für unsere 
Katzenklappe, der aufzeichnet, wann die Katze das Haus verlässt bzw. 
betritt. Die Betätigung der Klappe wird von einem Reed-Kontakt 
registriert.
Des weiteren läuft eine Uhr mit und ich habe zwei Taster für das Setup 
(Einstellung der Uhrzeit uns Status der Katze (drin oder draußen)) oder 
für den Abruf der geloggten Ereignisse. Zur Zeit werden 20 Ereignisse in 
einem Array gespeichert, das werde ich noch auf eine Liste umstellen.
Ich habe die Schaltung auf einem Steckbrett aufgebaut und es 
funktioniert alles, jetzt beginne ich mit dem Löten auf eine Platine
Ich habe den Quellcode ( außer lcd-rotines.c und lcd-routines.h) 
beigefügt und bitte einige alte Hasen, mal über den Code zu schauen, und 
mir Anregungen für Verbesserungen zu geben

Tia

MRay

von Wastl F. (rescuetigerchen)


Lesenswert?

Hi,
ist die Katzenklappe an einen Transponder gekoppelt? Sonst ist "Zicke on 
tour" und du hast aber trotzdem Besuch im Haus. Was ist mit Wind? Kann 
der die Klappe aufmachen? Und wie ist es mit "Katze guckt raus, will 
dann aber doch lieber an deiner Lieblingspflanze drinnen knabbern"? 
Ansonsten eine feine Sache, wenn das Mistvieh gerade wieder mal nicht 
auffindbar ist. Dann weiß man wenigstens, daß man nicht suchen braucht, 
wenn "Zicke on tour" ist.
Gruß,
wastl

von Code (Gast)


Lesenswert?

Michael Rochel schrieb:
> mal über den Code zu schauen,
1
volatile unsigned int millisekunden;
2
volatile unsigned int sekunde;
3
volatile unsigned int minute;
4
volatile unsigned int stunde;
5
volatile unsigned int AktLog;
6
volatile unsigned int Data[20][4];
7
volatile unsigned int SecsFromStart;
8
volatile unsigned int LastAction;
9
volatile unsigned int LastLog;
10
volatile unsigned int CatState;

Mit Vorgabewerten initialisieren...

von Karl H. (kbuchegg)


Lesenswert?

Datentypen!

Auf einem µC willst du fast nie einfach nur 'unsigned int', sondern du 
willst ein bischen bessere Kontrolle über die Bitanzahl.

Gerade ein AVR tut sich bei 8-Bit Arithmetik um einiges leichter, als 
wie wenn du ihn auf 16 Bit oder gar noch höherern Bitzahlen fuhrwerkeln 
lässt.

Das ist jetzt in deiner Anwendung sicherlich nicht das große Problem, 
aber es geht ums Prinzip.

Sekunden, Minuten, Stunden, das alles kann problemlos in 8 Bit 
untergebracht werden. Ein uint8_t reicht daher locker aus und es muss 
kein unsigned int sein.

von Michael R. (mray)


Lesenswert?

Hallo,

nein an einen Transponder ist die Klappe nicht gekoppelt. Es ist 
natürlich eine Fehlermöglichkeit, bisher hatten wir aber nie eine fremde 
Katze im Haus.
Der Wind schafft es nicht, die Klappe zu öffnen.
Wenn die Katze nur rausguckt ist das ein Problem. Ich habe auch schon 
darüber nachgedacht, mir ist noch keine Lösung eingefallen. Ich habe an 
zwei ReedkontaktE gedacht, dann kann ich registrieren, ob die Klappe 
weit geöffnet wurde, aber das Kann natürlich auch beim Rausgucken 
passieren. Für Anregungen bin ich hier dankbar.
Erwähnen muss ich noch, dass ich auch elektronisch ein Anfänger bin, 
also elektrojnisch komplizierte Lösungen werde ich nicht umsetzen 
können.

von Karl H. (kbuchegg)


Lesenswert?

Da du höchst wahrscheinlich genug Flash hast, so dass du dir ein bischen 
Komfort gönnen kannst.

Deine ShowTime Funktion lässt sich so schreiben
1
void ShowTime(unsigned int showstunde, unsigned int showminute, unsigned int showsekunde, unsigned int ShowState)
2
{
3
  char OutString[30]; 
4
5
  lcd_clear();
6
7
  if (ShowState == 0)
8
    lcd_string( CAT_IN );
9
  else
10
    lcd_string( CAT_OUT );
11
12
  sprintf( OutString, "%02u:%02u:02u", showstunde, showminute, showsekunde );
13
14
  lcd_setcursor( 0, 2 );
15
  lcd_string( OutString );
16
}

(Dein { - } Klammerungsschema find ich unübersichtlich. Aber man muss 
dir zu Gute halten, dass du es konsequent durchgezogen hast)

von Karl H. (kbuchegg)


Lesenswert?

Karl Heinz Buchegger schrieb:

> (Dein { - } Klammerungsschema find ich unübersichtlich. Aber man muss
> dir zu Gute halten, dass du es konsequent durchgezogen hast)


Ooops. Ich nehms zurück. Also die Sache mit dem konsequent. Das sind 
einige Patzer in der Einrückung, die sich auch mit der ewigen 
Tabulator-Leerzeichen Problematik nicht erklären lassen.

von Karl H. (kbuchegg)


Lesenswert?

Seh ich das richtig, dass du keine Tastenentprellung eingebaut hast?
Da wirst du in ein paar Monaten dann so richtig deine Freude an den 
Tasten haben :-)

von Michael R. (mray)


Lesenswert?

Hallo Karl Heinz,

mit der sprintf-Funktion habe ich mich noch nicht beschäftigt, Deinen 
Vorschlag werde ich jedoch umsetzen, er macht das ganze deutlich 
übersichtlicher.
Klammerungsschema: Meinst Du so:

if(...)
{
  command1;
  command2;
}

statt
if(...)
  {
  command1;
  command2;
  }

von Karl H. (kbuchegg)


Lesenswert?

Konsequent sein!
1
  while(PINC & 1<<3)
2
    {
3
    }
4
    while (!(PINC & 1<<3));

Wieso hat das erste while eine völlig andere Form als das 2-te?

Das ist jetzt zugegebenermassen auch ein wenig Ansichtsache, aber ist 
dir bewusst, dass in
1
    while (!(PINC & 1<<3));
der ; technisch gesehen die vom while abhängige Anweisung darstellt? Es 
handelt sich im die leere Anweisung, die nur aus einem ; besteht. Alle 
Compound-Statements, zu denen auch ein while gehört, werden NICHT durch 
ein ; abgeschlossen!

Wenn man daher konsequent ist und konsequent durchzieht, dass die 
abhängige Anweisung bei einem while (oder bei einem if) immer in der 
nächsten Zeile steht, dann lautet die sich daraus ergebende Schreibweise
1
    while (!(PINC & 1<<3))
2
      ;
sie unterscheidet sich im Grund in der Schreibweise durch nichts von
1
    while (!(PINC & 1<<3))
2
      i++;
nur eben, dass es sich um eine leere Anweisung anstelle von i++ handelt.

Ich ziehe diese Schreibweise auch aus dem Grund vor, weil hier absolut 
klar ist, dass ich bewusst eine leere Anweisung benutze und der ; am 
Zeilenende nicht einfach nur ein Tippfehler war.

von Karl H. (kbuchegg)


Lesenswert?

Michael Rochel schrieb:
> Hallo Karl Heinz,
>
> mit der sprintf-Funktion habe ich mich noch nicht beschäftigt,

dann solltest du das tun. Mit den Formatierungszeichen kann man sich 
sehr oft das Leben enorm erleichtern. sprintf kostet zwar etwas an Code 
bze- Laufzeit, wenn das aber keine Rolle spielt muss ich mich ja nicht 
unnötig plagen.

> Klammerungsschema: Meinst Du so:
>
> if(...)
> {
>   command1;
>   command2;
> }
>
> statt
> if(...)
>   {
>   command1;
>   command2;
>   }

Find ich übersichtlicher, weil die Klammern da besser rausstehen. Ich 
kenne zwar deine KLammereinrückung aus der Literatur, ich hab aber noch 
nie einen Programmierer getroffen, der sie auch tatsächlich verwendet.

von Michael R. (mray)


Lesenswert?

@ Karl-Heinz

nein Tastenentprellung habe ich nicht drin. Ich hatte gehofft, da ich 
nach jedem Tastendruck warte, bis er gelöst ist, würde das reichen. Auf 
meinem Steckbrett hat es soweit funktioniert. Ist es so, dass die 
Probleme erst nach einiger Zeit, wenn die Taster älter sind, auftauchen?

von Karl H. (kbuchegg)


Lesenswert?

Michael Rochel schrieb:
> @ Karl-Heinz
>
> nein Tastenentprellung habe ich nicht drin. Ich hatte gehofft, da ich
> nach jedem Tastendruck warte, bis er gelöst ist, würde das reichen.

Tastenprellen spielt sich im Millisekundenbereich ab. Deine Schleifen 
können das so nicht ausgleichen. Die werden viel zu schnell 
abgearbeitet.

> Auf
> meinem Steckbrett hat es soweit funktioniert. Ist es so, dass die
> Probleme erst nach einiger Zeit, wenn die Taster älter sind, auftauchen?

Genau. Tastenprellen entsteht, wenn die Kontakte die aufeinander 
gedrückt werden, abgenutzt sind bzw. die einstmals strammen 
Kontaktfedern anfangen auszuleiern.

von Karl H. (kbuchegg)


Lesenswert?

1
      if (CatState == 0)
2
          {
3
          CatState = 1;
4
          }
5
    else
6
          {
7
      CatState = 0;
8
      }

kleiner Trick.
Der Sinn dieser Sequenz ist es CatState von 0 auf 1 bzw. umgekehrt zu 
schalten.

1
   CatState = 1 - CatState;
macht genau das gleiche.

von Karl H. (kbuchegg)


Lesenswert?

1
      if (minute == 60) {minute = 0;}

Autsch. Böses Faul gegen die Formatierrichtlinien.

von Karl H. (kbuchegg)


Lesenswert?

Das ist jetzt "Jammern auf hohem Niveau"
1
ISR (TIMER2_COMP_vect)
2
{
3
  millisekunden++;
4
  
5
  if(millisekunden == 1000)
6
  {
7
    sekunde++;
8
  SecsFromStart++;
9
    millisekunden = 0;
10
  
11
    if(sekunde == 60)
12
    {
13
      minute++;
14
      sekunde = 0;
15
    }
16
    if(minute == 60)
17
    {
18
      stunde++;
19
      minute = 0;
20
    }
21
    if(stunde == 24)
22
    {
23
      stunde = 0;
24
    }
25
  }
26
}

Ob die Minuten 60 erreicht haben oder nicht, brauchst du nur testen, 
wenn sich die Minuten auch tatsächlich erhöht haben
1
ISR (TIMER2_COMP_vect)
2
{
3
  millisekunden++;
4
  
5
  if(millisekunden == 1000)
6
  {
7
    millisekunden = 0;
8
    SecsFromStart++;
9
    sekunde++;
10
11
    if(sekunde == 60)
12
    {
13
      sekunde = 0;
14
      minute++;
15
16
      if(minute == 60)
17
      {
18
        minute = 0;
19
        stunde++;
20
21
        if(stunde == 24)
22
          stunde = 0;
23
      }
24
    }
25
  }
26
}

(Interessant, dass du hier ein anderes { } Einrückungsschema benutzt 
hast :-))

von Code (Gast)


Lesenswert?

Karl Heinz Buchegger schrieb:
> sekunde++;
> SecsFromStart++;
> millisekunden = 0;

Dann weisen wir doch auch gleich noch auf die inkonsequenten Wahl der 
Bezeichnernamen hin. ;)

von Karl H. (kbuchegg)


Lesenswert?

1
    LastLog = SecsFromStart;
2
    Data[AktLog][0] = stunde;
3
    Data[AktLog][1] = minute;
4
    Data[AktLog][2] = sekunde;
5
  Data[AktLog][3] = CatState;
6
    AktLog++;
7
    }

Hoppla.
Wer prüft, ob das Array droht überzulaufen?
Das ist kein Schönheitsfehler mehr, über den man diskutieren kann. Das 
ist ein echter Showstopper.

von Karl H. (kbuchegg)


Lesenswert?

OK.
Möglicherweise bist du noch nicht soweit. Aber ein paar Strukturen 
hätten dem Programm gut getan.

zum einen gibt es da Zeiten in Form von Stunden Minuten Sekunden
1
struct Zeit
2
{
3
  uint8_t Stunde;
4
  uint8_t Minute;
5
  uint8_t Sekunde;
6
};

zum anderen gibt es das Ereigniss, charakterisiert durch die Art des 
Ereignisses (Katze geht/kommt) und den zugehörigen Zeitpunkt
1
#define CAT_NO_DATA 2
2
#define CAT_IS_OUT  1
3
#define CAT_IS_IN   0
4
5
struct Aktion
6
{
7
  uint8_t     Typ;    // CAT_NO_DATA, CAT_IS_OUT oder CAT_IS_IN
8
  struct Zeit Wann;
9
};

und es gibt ein Array in dem Ereignisse gespeichert werden
1
#define NR_LOG_DATA  20
2
struct Aktion LogData[NR_LOG_DATA];


alles zusammen:
1
const char *CAT_IN = "Zicke ist drin";
2
const char *CAT_OUT = "Zicke auf Tour";
3
4
struct Zeit
5
{
6
  uint8_t Stunde;
7
  uint8_t Minute;
8
  uint8_t Sekunde;
9
};
10
11
#define CAT_IS_OUT  1
12
#define CAT_IS_IN   0
13
14
struct Aktion
15
{
16
  uint8_t     Typ;    // CAT_OUT oder CAT_IN
17
  struct Zeit Wann;
18
};
19
20
#define NR_LOG_DATA  20
21
struct Aktion LogData[NR_LOG_DATA];
22
uint8_t       actLogNr;
23
24
volatile unsigned int millisekunden;
25
volatile struct Zeit actZeit;
26
27
void ShowTime( struct Zeit* zeit )
28
{
29
  char OutString[10]; 
30
31
  sprintf( OutString, "%02u:%02u:02u", zeit->Stunde, zeit->Minute, zeit->Sekunde );
32
  lcd_string( OutString );
33
}
34
35
void ShowAktion( struct Aktion* aktion )
36
{
37
  char OutString[30]; 
38
39
  lcd_clear();
40
41
  if (ShowState == CAT_IS_IN)
42
    lcd_string( CAT_IN );
43
  else
44
    lcd_string( CAT_OUT );
45
46
  lcd_setcursor( 0, 2 );
47
  ShowZeit( &(aktion->Wann) );
48
}
49
...

Das ist zwar hier am Anfang des Programms erst mal etwas mehr Aufwand, 
dafür wird dann aber ab ungefähr dieser Stelle der restliche Code 
einfacher, besser zu lesen und auch oft kürzer. zb

die Klappe wurde betätigt, ein neuer Log-Eintrag wird angelegt
1
   ...
2
3
     LogData[actLogNr].Typ = CatState;
4
     LogData[actLogNr].Wann = actZeit;
5
6
     actLogNr++;
7
     if( actLogNr == NR_LOG_DATA )
8
       actLogNr = 0;


Das Log soll gelöscht werden:
1
  // LogArray löschen
2
  for( i = 0; i < NR_LOG_DATA; i++ )
3
    LogData[i].Typ = CAT_NO_DATA;
4
  actLogNr = 0;


es wird ausgegeben:
1
  for( i = 0; i < NR_LOG_DATA, i++ ) {
2
    if( LogData[i].Typ != CAT_NO_DATA )
3
      ShowAktion( &LogData[i] );
4
  }

von Michael R. (mray)


Lesenswert?

@ Karl-Heinz

Vielen Dank für dDein umfassendes Feedback. Im Detail muss ich es mir 
heute Abend noch ansehen.

Die inkonsistente Klammerung und Variablenbezeichnungen sind enthalten, 
weil ich teilweise den Quelltext aus den Tutorien hier übernommen habe 
(z. B. die Interrupt-Routine).
Das Problem mit dem Array-Überlauf ist mit klar, ich stell das noch auf 
eine Liste um.
Mit den Strukturen ist natürlich auch richtig, aber ich habe seit ca. 20 
Jahren nicht mehr inb C programmiert, hier habe ich noch Nachholbedarf.

von Karl H. (kbuchegg)


Lesenswert?

Michael Rochel schrieb:

> Das Problem mit dem Array-Überlauf ist mit klar, ich stell das noch auf
> eine Liste um.

Was versprichst du dir davon?
Der zur Verfügung stehende Speicher wird dadurch auch nicht mehr. 
Eigentlich wird er weniger, weil du mehr Verwaltungsdaten hast.

von Michael R. (mray)


Lesenswert?

Dafür aber ohne Beschränkung der Anzahl der Logs (außer dem Speicher). 
Zusätzlich benötige ich doch nur einen Pointer pro Log und einige 
Funktionen zu Listenverwaltung. Ich bin da eher ein Fan von dynamischen 
Strukturen, meinst Du, bei Mikroprozessoren ist es besser ein Array zu 
nehmen und die Logs zu beschränken? (Dann natürlich mit Prüfung auf 
Überlauf)

von Karl H. (kbuchegg)


Lesenswert?

Michael Rochel schrieb:
> Dafür aber ohne Beschränkung der Anzahl der Logs (außer dem Speicher).

Ähm. Das ist ein Scheinargument.
Denn du kennst deine Speichergröße. Du weißt auch wieviel Speicher du 
für statische Variablen benötigst und den Stackverbrauch bzw. Verbrauch 
für lokale Variablen kann man abschätzen.

Daraus kannst du errechnen wie groß dein Array werden darf. Mehr geht 
nicht. Denn mehr passt ganz einfach nicht in den Speicher.

> Zusätzlich benötige ich doch nur einen Pointer pro Log und einige
> Funktionen zu Listenverwaltung. Ich bin da eher ein Fan von dynamischen
> Strukturen,

Oh, ich auch.
Auf einem PC.

Auf einem kleinen µC macht es keinen Sinn von den vorhandenen 1K SRAM 
auch nur 5% für Verwaltungsdaten wie zb Verpointerungen von Listenknoten 
zu verbrauchen.
Zumindest solange, solange man nicht den Speicher dynamisch zur Laufzeit 
auf unterschiedliche Datentypen verteilen muss. Den Fall hast du aber 
nicht: Du hast nur einen Typ von Daten. Alle Log-Einträge sind gleich 
aufgebaut. Und du weißt wieviel SRAM dir zur Verfügung steht. Mehr 
Speicher hast du nicht.

Und da sind jetzt die Probleme, die durch Fragmentierung entstehen noch 
gar nicht mit eingerechnet. Denn es ist ein Trugschluss, wenn du denkst, 
dass du durch dynamische Allokierung auch noch das letzte Byte aus dem 
SRAM ausnutzen kannst.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Karl Heinz Buchegger schrieb:
> Und da sind jetzt die Probleme, die durch Fragmentierung entstehen noch
> gar nicht mit eingerechnet.

Bei einer Liste mit nur einem Datentyp gibt es (zumindest statistisch)
keine Fragmentierungsprobleme, da die alten, verworfenen Einträge ja
garantiert immer wieder "recyclet" werden können.

Der wunde Punkt beider Lösungen ist die Abschätzung des Stackverbrauchs.
Bei der statischen Variante muss man die komplett selbst machen, bei
der dynamischen unterstützt einen malloc() zumindest rudimentär, indem
es die Anforderung ablehnt, wenn der Heap bereits bis "dicht unter"
dem Stack angelangt ist.  Trotzdem kann man natürlich auch hier
pathologische Fälle finden, in denen dieser Test nicht ausreichen
würde (aus ebendiesem Grunde ist das Maß für "dicht unter" auch zur
Laufzeit konfigurierbar).

von Karl H. (kbuchegg)


Lesenswert?

Jörg Wunsch schrieb:

> würde (aus ebendiesem Grunde ist das Maß für "dicht unter" auch zur
> Laufzeit konfigurierbar).

Apropos:
Das hab ich mich nämlich auch gerade gefragt.
Wie konfiguriert man das? Weißt du das zufällig auswendig?

von Karl H. (kbuchegg)


Lesenswert?

Michael Rochel schrieb:

> Zusätzlich benötige ich doch nur einen Pointer pro Log

Es ist mehr.
Denn malloc muss ja auch noch Buch führen, welchen Speicher es schon 
rausgerückt hat.

von Karl H. (kbuchegg)


Lesenswert?

Karl Heinz Buchegger schrieb:
> Jörg Wunsch schrieb:
>
>> würde (aus ebendiesem Grunde ist das Maß für "dicht unter" auch zur
>> Laufzeit konfigurierbar).
>
> Apropos:
> Das hab ich mich nämlich auch gerade gefragt.
> Wie konfiguriert man das? Weißt du das zufällig auswendig?

Habs gefunden.
globale Variablen __malloc_heap_start, __malloc_heap_end

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Karl Heinz Buchegger schrieb:
> Wie konfiguriert man das? Weißt du das zufällig auswendig?

Nö, ich muss da auch in der Doku nachgucken, obwohl ich sie selbst
geschrieben habe. ;-)

http://www.nongnu.org/avr-libc/user-manual/malloc.html

``The default value of __malloc_margin is set to 32.''

Karl Heinz Buchegger schrieb:
> Denn malloc muss ja auch noch Buch führen, welchen Speicher es schon
> rausgerückt hat.

Dafür braucht es pro Block weitere 2 Byte, die dem zum Nutzer
rausgerückten Block vorangestellt werden.  Darin wird die Länge des
Blocks aufgezeichnet.  Außerdem gibt es noch global einen Zeiger, der
das aktuelle obere Ende des Heaps kennzeichnet und einen, der den
Anfang der "freelist" markiert.  Welche Blöcke gerade vergeben sind,
merkt sich malloc nicht selbst, das muss sich schon die Applikation
merken. ;-)  Wenn man mit free etwas zurückgibt, wird die Verkettung
für die freelist in dem Bereich hinterlegt, der vorher dem Nutzer zur
Verfügung gestellt worden war.  (Wenn man also auf einen bereits
freigegebenen Block nachträglich zugreift, liest man nicht mehr die
gewünschten Daten, und wenn man drauf schreibt, zerrammelt man die
freelist.)

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.