Forum: Mikrocontroller und Digitale Elektronik C-Code auf AVR effizient schreiben


von Matthias M. (Gast)


Lesenswert?

Hallo zusammen,

ich sitze aktuell an einem kleinen Projekt mit einem Atmel Mega16 wo ich 
einfach keine Struktur in den Code reinbringe.

Der kleine Käfer soll folgendes machen:

-> Ansteuerung eines LCD's (HD44780, LIB hier aus dem Forum).
-> Taster abfragen (alle 10mS mit Timer, LIB hier aus dem Forum).
-> hin und wieder eine AD Wandlung durchführen und I2C Werte einlesen 
(völlig zeitunkritisch).
-> hin und wieder Werte über I2C übertragen die zuvor über Tasten und 
LCD eingestellt wurden.
-> möglichst wenig Stromverbrauch da Batteriebetrieb.

Gleich mal vorweg, ich hab das schon fertig geschrieben. Leider halt 
schlecht dokumentiert und immer wieder etwas geändert -> ein ziemliches 
durcheinander aber es läuft.

Ich würde es jetzt gerne nochmals neu machen und dieses mal mit 
Struktur. Wie geht man an sowas am besten heran? Ich denke der einzig 
fixe Punkt in dieser Sache ist der 10mS Timer für die Tastenentprellung. 
Dann könnte man doch einen PowerDown Mode wählen wo dieser Timer an 
bleibt, oder?

Wie realisiert man dann dass die restlichen Dinge sauber ablaufen und 
z.b. nicht den Timer blockieren? (das hatte ich nämlich auch schon, 
wollte die AD Wandlung mit in den Timer packen dann reagierten die 
Tasten nicht mehr sauber).

Desweiteren interessiert mich ob z.b. die LCD Routinen mit in die main.c 
müssen oder ob man die auch auslagern kann? Leider macht das den Code 
etwas unübersichtlich aber alle Versuche bisher scheiterten weil mein 
Compiler die Funktionen dann nicht mehr findet.

Wäre um ein paar Denkanstöße sehr dankbar.

von Peter II (Gast)


Lesenswert?

Matthias M. schrieb:
> Leider macht das den Code
> etwas unübersichtlich aber alle Versuche bisher scheiterten weil mein
> Compiler die Funktionen dann nicht mehr findet.

dann müsste wir mal sehen wie du es gemacht hast. Die anzeige sollte in 
der Main-schleife erfolgen, aber nicht direkt in der Main stehen. Sie 
sollte in einer extra Prozedur liegen.

von Mike (Gast)


Lesenswert?

Matthias M. schrieb:
> Wie realisiert man dann dass die restlichen Dinge sauber ablaufen und
> z.b. nicht den Timer blockieren?

Ohne delay() und mit kurzen Interruptroutinen

von Matthias M. (Gast)


Angehängte Dateien:

Lesenswert?

Peter II schrieb:
> Matthias M. schrieb:
>> Leider macht das den Code
>> etwas unübersichtlich aber alle Versuche bisher scheiterten weil mein
>> Compiler die Funktionen dann nicht mehr findet.
>
> dann müsste wir mal sehen wie du es gemacht hast. Die anzeige sollte in
> der Main-schleife erfolgen, aber nicht direkt in der Main stehen. Sie
> sollte in einer extra Prozedur liegen.

der Code ist leider seeeehr unübersichtlich, ich weiß nicht ob es eine 
gut Idee ist ihn hier komplett zu posten. Für alle hartgesonnenen -> 
Anhang ;-)

von Matthias M. (Gast)


Lesenswert?

Peter II schrieb:
> Die anzeige sollte in
> der Main-schleife erfolgen, aber nicht direkt in der Main stehen. Sie
> sollte in einer extra Prozedur liegen.


Das ist schon mein nächstes Problem. Die Ausgabe auf dem LCD ist sehr 
zerrupft. Sprich es ist bei jedem Menü ein Kraus erstmal das Display zu 
leeren weil noch irgendwelche Alten Dinge angezeigt werden.

Sollte ich das besser mit einer einzigen Funktion machen die ich von 
verschiedenen Positionen im Programm aufrufe?

Sprich so?:

Funktion AusgabeLCD (Zeile1, Zeile2) {

Ausgabe Zeile1;
Ausgabe Zeile2;

}

.
.
.
.
.

main {

// Schreibe Willkomemsmeldung:

Ausgabe LCD("Willkommen","");
.
.
.

// Schreibe Status ASRC:

Ausgabe LCD("Status ASRC1:","downsampling");

}

Also natürlich sehr vereinfacht, aber ist der Denkansatz richitg?

von greg (Gast)


Lesenswert?

Du solltest einfach etwas strukturierter programmieren. Was mir auf den 
ersten Blick auffällt:

* Haufenweise globale Variablen: das solltest du vermeiden. Deklariere 
Variablen in dem Scope, in dem du sie brauchst, und tausche 
Informationen nur über Parameter und Rückgabewerte von Funktionen aus. 
Globale Variablen brauchst du dann sehr selten. Du hast dann auch 
weniger Seiteneffekte und der Code sollte besser verständlich sein.

* Mangelhafte Kapselung: Teile die Aufgaben sinnvoll auf die Funktionen 
auf. Deine state machine in main() z.B. sollte wirklich nur eine state 
machine sein, sonst wird das zu unübersichtlich. Extrahiere die 
Funktionalität der einzelnen States immer in Funktionen.

* Teile eventuell den Sourcecode in mehrere Dateien auf, du kannst 
beispielsweise alles was mit dem LCD oder mit I2C zu tun hat jeweils in 
eine separate Datei auslagern. Das sorgt auch dafür, dass du dir 
Gedanken über saubere Interfaces machen musst.

von Stefanus (Gast)


Lesenswert?

Zerlege das Programm in Tasks, also die Aufgaben, die Du ganz oben 
genannt hast. Jeder Task ist eine funktion.

Starte einen 10ms Timer, der den Controller aufwachen lässt.

Das Hauptprogramm ist eine Schleife, welche die Tasks nacheinander 
ausführt und dann als letzten den Controller schlafen legt.

So werden alle Tasks im 10ms Rythmus ausgeführt - vorausgesetzt alle 
Tasks zusammen dauern nicht länger als 10ms.

Die Displayausgabe könnte zu lange dauern. Lege Dir einen Puffer an 
(char array) und übertrage bei jedem Schleifendurchlau nur ein (oder ein 
par) Zeichen. Wenn die 10ms pro Schleifendurchlauf nicht unbedingt 
eingehalten werden müssen, kannst Du Dir den Aufwand sparen.
1
int main(...) {
2
  while (1) {
3
    task_tasten_abfragen();
4
    task_a_d_wandlung();
5
    task_i2c_uebertragen();
6
    task_display_ausgabe(); // falls nicht in den anderen Funktionen eingebettet
7
    sleep_cpu();
8
  }
9
}
Der timer-interrupt wäre dabei im einfachsten Fall eine leere Funktion. 
Du könntest da auch einen Zeitmesser unterbringen, der bei jeden 
Imterrupt eine unsinged int (oder long int) Variable um 1 erhöht.
1
volatile unsigned int systemtimer;
2
ISR(TIMER0_OVF_vect) 
3
{
4
  systemtimer++;
5
}
Der Timer wird z.B. nützlich, wenn ein Task z.B. ein mal pro Sekunde 
ausgeführt werden soll.
1
function task_sekunden_blinker() {
2
  if (systemtimer%100==0) {
3
    // toggle LED
4
    PORTB ^= ( 1 << PB0 );
5
  }
6
}
Du kannst den Timer auch nutzen, um Zeiten zu messen (im 10ms Raster):
1
if (taste_gedrueckt) {
2
  start_time=systemtimer;
3
}
4
else {
5
  unsinged int duration=systemtimer-start_time;
6
}
Das funktioniert auch, wenn der Timer zwischenzeitlich einmal (aber 
nicht mehrmals) überlauft.

von Matthias M. (Gast)


Lesenswert?

Wow danke für die hilfreichen Tipps :) Werd mich gleich ransetzen und 
sehen wie ich das umsetzen kann.

von Matthias M. (Gast)


Lesenswert?

greg schrieb:
> Teile eventuell den Sourcecode in mehrere Dateien auf, du kannst
> beispielsweise alles was mit dem LCD oder mit I2C zu tun hat jeweils in
> eine separate Datei auslagern. Das sorgt auch dafür, dass du dir
> Gedanken über saubere Interfaces machen musst.

Wie mache ich das? Ich hatte das schon versucht, also eine eigene Datei 
z.b. lcd_functions.c zu erstellen wo alle LCD Funktionen drin lagen. 
Diese Datei hab ich dann gleich zu Anfang des Programmes mit include 
"lcd_functions.c"; eingebunden. Der Compiler meckert dann allerdings 
dass er die Funktionen nicht findet.

von Fred (Gast)


Lesenswert?

Matthias M. schrieb:
> Wie mache ich das?

Indem du dich informierst, was eine Header-Datei ist, dann diese 
schreibst und in deiner Hauptdatei einbindest.

Und dann kompilierst du auch die neue Datei lcd_function.c und linkst 
die mit dazu.

von Falk B. (falk)


Lesenswert?


von Falk B. (falk)


Lesenswert?

Hmmm, es gibt Optimierungspotential ;-)

typedef struct {
  uint8_t reg_off;
  uint8_t reg_val_D24_8;uint8_t reg_val_D14_8;uint8_t 
reg_val_D08_8;uint8_t reg_val_D00_8;
  uint8_t reg_val_D24_7;uint8_t reg_val_D14_7;uint8_t 
reg_val_D08_7;uint8_t reg_val_D00_7;
  uint8_t reg_val_D24_6;uint8_t reg_val_D14_6;uint8_t 
reg_val_D08_6;uint8_t reg_val_D00_6;
  uint8_t reg_val_D24_5;uint8_t reg_val_D14_5;uint8_t 
reg_val_D08_5;uint8_t reg_val_D00_5;
  uint8_t reg_val_D24_4;uint8_t reg_val_D14_4;uint8_t 
reg_val_D08_4;uint8_t reg_val_D00_4;
  uint8_t reg_val_D24_3;uint8_t reg_val_D14_3;uint8_t 
reg_val_D08_3;uint8_t reg_val_D00_3;
  uint8_t reg_val_D24_2;uint8_t reg_val_D14_2;uint8_t 
reg_val_D08_2;uint8_t reg_val_D00_2;
  uint8_t reg_val_D24_1;uint8_t reg_val_D14_1;uint8_t 
reg_val_D08_1;uint8_t reg_val_D00_1;
  uint8_t reg_val_D24_0;uint8_t reg_val_D14_0;uint8_t 
reg_val_D08_0;uint8_t reg_val_D00_0;
} reg_value256;

Das schreit nach einem Array!

Die LCD Sachen gehören in eine separate .c Datei mit dazugehörger .h. 
datei, so wie es im Forum/Wiki schon drin steht.

Gleiches gilt für TWI

Ebenso die ganzen Menusachen.

Deine Hauptschleife rennt mit maximaler Prozessorgeschwindigkeit, das 
ist werder nötig noch sinnvoll. Dort sollte eine "Bremse" rein, sprich, 
nur alle ???ms ein Durchlauf erfolgen. Das kann man prima per Timer und 
Interrupt steuern.

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.