Forum: Compiler & IDEs LCD aus GCC Tutorial läuft nicht mit AVR Studio


von Igor M. (bastel-wastel)


Lesenswert?

Guten Tag miteinander,

ich habe ein Problem mit der LCD Ansteuerung aus dem AVR-GCC Tutorial.
-> 
http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial#Programmierung

Ich habe den Quellcode in zwei Dateien kopiert und den Dateinamen mit 
lcd-routines.h bzw. lcd-routines.c versehen. In meinem Hauptprogramm 
verwende ich nur den Testcode aus dem Tutorial. Das ganze Projekt läuft 
unter AVR Studio 4.14 mit der neuesten Version von Win-AVR.
Leider compiliert das Ganze nicht und ich kann mit dem Fehlercode vom 
Compiler nicht wirklich was anfangen. Vielleicht hat einer von euch ne 
Idee:
PS: Ich verwende kein externes Makefile, sondern stelle in AVR Studio 
unter  "Configuration Option" mein Device (Atmega8) und meine 
Taktfrequenz (8MHz) ein.

Fehlercode:
1
 
2
Build started 12.5.2008 at 14:26:17
3
avr-gcc.exe  -mmcu=atmega8 -Wall -gdwarf-2 -std=gnu99    -DF_CPU=8000000UL -Os -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums -MD -MP -MT AVR_LCD.o -MF dep/AVR_LCD.o.d  -c  ../AVR_LCD.c
4
In file included from ../lcd-routines.c:7,
5
                 from ../AVR_LCD.c:7:
6
../lcd-routines.h:15:1: warning: "F_CPU" redefined
7
<command-line>: warning: this is the location of the previous definition
8
../lcd-routines.c: In function 'set_cursor':
9
../lcd-routines.c:126: warning: 'tmp' may be used uninitialized in this function
10
avr-gcc.exe -mmcu=atmega8 -Wl,-Map=AVR_LCD.map AVR_LCD.o lcd-routines.o     -o AVR_LCD.elf
11
lcd-routines.o: In function `lcd_enable':
12
D:\Elektronik\AVR\AVR_LCD\default/../lcd-routines.c:56: multiple definition of `lcd_enable'
13
AVR_LCD.o:D:\Elektronik\AVR\AVR_LCD\default/../lcd-routines.c:56: first defined here
14
lcd-routines.o: In function `lcd_command':
15
D:\Elektronik\AVR\AVR_LCD\default/../lcd-routines.c:35: multiple definition of `lcd_command'
16
AVR_LCD.o:D:\Elektronik\AVR\AVR_LCD\default/../lcd-routines.c:35: first defined here
17
lcd-routines.o: In function `lcd_data':
18
D:\Elektronik\AVR\AVR_LCD\default/../lcd-routines.c:13: multiple definition of `lcd_data'
19
AVR_LCD.o:D:\Elektronik\AVR\AVR_LCD\default/../lcd-routines.c:13: first defined here
20
lcd-routines.o: In function `lcd_clear':
21
D:\Elektronik\AVR\AVR_LCD\default/../lcd-routines.c:109: multiple definition of `lcd_clear'
22
AVR_LCD.o:D:\Elektronik\AVR\AVR_LCD\default/../lcd-routines.c:109: first defined here
23
lcd-routines.o: In function `lcd_home':
24
D:\Elektronik\AVR\AVR_LCD\default/../lcd-routines.c:117: multiple definition of `lcd_home'
25
AVR_LCD.o:D:\Elektronik\AVR\AVR_LCD\default/../lcd-routines.c:117: first defined here
26
lcd-routines.o: In function `set_cursor':
27
D:\Elektronik\AVR\AVR_LCD\default/../lcd-routines.c:125: multiple definition of `set_cursor'
28
AVR_LCD.o:D:\Elektronik\AVR\AVR_LCD\default/../lcd-routines.c:125: first defined here
29
lcd-routines.o: In function `lcd_string':
30
D:\Elektronik\AVR\AVR_LCD\default/../lcd-routines.c:140: multiple definition of `lcd_string'
31
AVR_LCD.o:D:\Elektronik\AVR\AVR_LCD\default/../lcd-routines.c:140: first defined here
32
lcd-routines.o: In function `lcd_init':
33
D:\Elektronik\AVR\AVR_LCD\default/../lcd-routines.c:70: multiple definition of `lcd_init'
34
AVR_LCD.o:D:\Elektronik\AVR\AVR_LCD\default/../lcd-routines.c:70: first defined here
35
make: *** [AVR_LCD.elf] Error 1
36
Build succeeded with 3 Warnings...

Wo liegt der Fehler?

Mit freundlichen Grüßen
bastl

von Oliver (Gast)


Lesenswert?

Von oben nach unten:

>../lcd-routines.h:15:1: warning: "F_CPU" redefined
><command-line>: warning: this is the location of the previous definition

AVRStudio definiert F_CPU schon in der Kommandoszeile zum Compiler, 
daher ist die erneute Definition im headerfile unnötig.

>../lcd-routines.c:126: warning: 'tmp' may be used uninitialized in this function

tmp wird nach Ansicht des Compilers vor seiner ersten Verwendung nicht 
initialisiert. Hier irrt der gcc allerdings öfter mal.

Bisher waren das alles Warnungen.

>D:\Elektronik\AVR\AVR_LCD\default/../lcd-routines.c:56: multiple definition of 
>`lcd_enable'
>AVR_LCD.o:D:\Elektronik\AVR\AVR_LCD\default/../lcd-routines.c:56: first >defined 
>here

und alle folgenden:

Der Linker findet alle Funktionen aus lcd-routines.c zweimal. Hat du 
vielleicht sowas wie "#include lcd-routines.c" in AVR_LCD.c stehen? Oder 
ist lcd-routines.c zweimal im source-Ordner angegeben?

Oliver

von Igor M. (bastel-wastel)


Lesenswert?

Vielen Dank für die Hilfe.
Hab den Fehler dadurch gefunden:

Ich war der Annahme, dass ich die include-files im "AVR GCC" Baum, der 
standardmäßig auf der Linken Seite in AVR Studio platziert ist einfügen 
muss. Habe dort die "lcd-routines.c" in den "Source-file" Ordner 
geschoben.

Nachdem ich diese wieder gelöscht hatte geht es nun. Blöder Fehler 
meinerseits.

Nochmals vielen Dank für deine Hilfe !!

von Karl H. (kbuchegg)


Lesenswert?

Igor Metwet wrote:
> Vielen Dank für die Hilfe.
> Hab den Fehler dadurch gefunden:
>
> Ich war der Annahme, dass ich die include-files im "AVR GCC" Baum, der
> standardmäßig auf der Linken Seite in AVR Studio platziert ist einfügen
> muss. Habe dort die "lcd-routines.c" in den "Source-file" Ordner
> geschoben.
>
> Nachdem ich diese wieder gelöscht hatte geht es nun. Blöder Fehler
> meinerseits.

Das allerdings war die falsche Korrektur.

Dieses File in den Baum mit aufzunehmen war schon richtig.
Was du entfernen hättest sollen ist der

#include "lcd-routines.c"

bzw. diesen durch ein

#include "lcd-routines.h"

ersetzen.

von Igor M. (bastel-wastel)


Lesenswert?

#include lcd-routines.h wird in lcd-routines.c aufgerufen
 lcd-routines.h habe ich aber auch noch im Baum stehen. Dies stört den 
Compiler dann anscheinend nicht, wenn dieses Doppelt aufgerufen wird.

Könntet Ihr mir noch Eines erklären:
Warum ist es besser wenn man die include-Dateien im Baum einfügt und 
nicht über den C-Text.

Danke schonmal

von Karl H. (kbuchegg)


Lesenswert?

Igor Metwet wrote:
> #include lcd-routines.h wird in lcd-routines.c aufgerufen

Ja, das ist auch korrekt so.

Was aber nicht korrekt ist, ist dass du in deinem Programm
einen
#include "lcd-routines.c"
                      ***
stehen hast.

*.c Files werden nicht inkludiert! Immer nur die Header Files *.h

>  lcd-routines.h habe ich aber auch noch im Baum stehen. Dies stört den
> Compiler dann anscheinend nicht, wenn dieses Doppelt aufgerufen wird.

lcd-routines.h wird nicht compiliert (zumindest nicht einzeln)!
Immer nur im Zusammenhang mit einem *.c File, welches das *.h File
includiert.

> Könntet Ihr mir noch Eines erklären:
> Warum ist es besser wenn man die include-Dateien im Baum einfügt und
> nicht über den C-Text.

Jedes *.c File wird getrennt von allen anderen übersetzt. So ist
die Sprache C ausgelegt und so funktioniert das seit über 30 Jahren.
Die einzelnen *.c Files werden getrennt voneinander compiliert
und erst deren Ergebnisse (die Objekt Files) werden dann zum
vollständigen Programm zusammengebunden.

Bei 2 Dateien, wie bei dir, macht das noch keinen grossen Unterschied.
Aber wenn du 2000 Source Code Dateien (*.c) hast, dann bist du froh
darüber, dass jedes einzelne *.c einzeln compiliert werden kann.
Erst so ist es möglich die Compilierzeiten für ein grosses Projekt
in vertretbarem Rahmen zu halten. Warum sollen 200000 Lines of Code
compiliert werden, wenn es völlig ausreicht das eine *.c File, in
dem eine Änderung notwendig war und das vielleicht 500 Lines of Code
besitzt, neu zu compilieren und das Programm neu zu bauen.

von Igor M. (bastel-wastel)


Lesenswert?

Ahh, jetzt wirds klar. Deine Erklärungen sind sehr anschaulich.
Das erklärt warum ich in dieser Hinsicht schonmal Probleme hatte...

Danke sehr !!

von Thor M. (metulski)


Angehängte Dateien:

Lesenswert?

Hallo Leute,

ich als µC Anfänger bin am verzweifeln. Ich möchte das Projekt von 
www.mikrocontrollerspielwiese.de "Analog Spannung messen" compilieren 
erhalte aber immer mehrfach im Build-Fenster von AVR Studio die 
"multiple definition of ***" Fehlermeldung?

Habt ihr vielleicht einen guten Tipp, wo das Problem in der 
lcd-routines.c sein soll? Irgendein #include ist unklar?

Vielen Dank und Grüße aus Dresden!
Metulski

von Karl H. (kbuchegg)


Lesenswert?

Torsten Müller schrieb:
> Hallo Leute,
>
> ich als µC Anfänger bin am verzweifeln. Ich möchte das Projekt von
> www.mikrocontrollerspielwiese.de "Analog Spannung messen" compilieren
> erhalte aber immer mehrfach im Build-Fenster von AVR Studio die
> "multiple definition of ***" Fehlermeldung?
>
> Habt ihr vielleicht einen guten Tipp, wo das Problem in der
> lcd-routines.c sein soll? Irgendein #include ist unklar?

Auszug aus test1.c:
1
#include <avr/io.h>
2
#include <inttypes.h>
3
#include "messen.h"
4
#include "lcd-routines.c" //17.01. von.c auf .h

Welchen Teil des Satzes
'*.c Files werden nicht inkludiert! Immer nur die Header Files *.h'
hast du nicht verstanden?

Beitrag "Re: LCD aus GCC Tutorial läuft nicht mit AVR Studio"

von Stefan E. (sternst)


Lesenswert?

Oder etwas anders ausgedrückt:

Sich an einen alten fremden Thread zu hängen, ist nicht gut.

Obendrein diesen alten Thread nicht mal vorher richtig gelesen zu haben, 
ist richtig böse.

von Oliver (Gast)


Lesenswert?

1
#include "lcd-routines.c" //17.01. von.c auf .h

Und sich nicht einmal an die eigenen Kommentare zu halten, ist 
ultra-böse.

Oliver

von metulski (Gast)


Lesenswert?

... Tschuldigung, das grausam Kommentierte stammt von mir - da ich 
probehalber mal auf die "lcd-routines.h" verwiesen habe -> da wird aber 
kein ordentlicher Wert angezeigt, da kein string.
Aber original lautet der Quellcode:
#include "lcd-routines.c"

Somit fehlt mir immer noch die Lösung ...
Hat es vielleicht mal jemand im AVR Studio compiliert?

Grüße

von Karl H. (kbuchegg)


Lesenswert?

metulski schrieb:
> ... Tschuldigung, das grausam Kommentierte stammt von mir - da ich
> probehalber mal auf die "lcd-routines.h" verwiesen habe -> da wird aber
> kein ordentlicher Wert angezeigt, da kein string.
> Aber original lautet der Quellcode:
> #include "lcd-routines.c"
>
> Somit fehlt mir immer noch die Lösung ...


die Lösung lautet immer noch, dass

#include "lcd-routines.h"

richtig ist.

> verwiesen habe -> da wird aber
> kein ordentlicher Wert angezeigt, da kein string.

Was immer das auch heissen mag.

von metulski (Gast)


Lesenswert?

metulski schrieb:
> #include "lcd-routines.c"

www.mikrocontrollerspielwiese.de inkludiert im test1.c immer 
"lcd-routines.c", denn die verweist dann auf die header 
"lcd-routines.h"?

Seltsam...

von Karl H. (kbuchegg)


Lesenswert?

metulski schrieb:
> metulski schrieb:
>> #include "lcd-routines.c"
>
> www.mikrocontrollerspielwiese.de inkludiert im test1.c immer
> "lcd-routines.c", denn die verweist dann auf die header
> "lcd-routines.h"?
>
> Seltsam...

Nicht "seltsam"
"Planlos" trifft es eher (von dem was der Autor da in messen.h 
aufgeführt hat, wollen wir erst gar nicht reden)


Edit: Ich hätte auch "Ahnungslos" schreiben können. Aber da kenne ich 
den Autor zu wenig, um ihm das zu unterstellen und anhand der paar 
Codezeilen will ich keine derart schwerwiegende Anschuldigung 
vorbringen.

von metulski (Gast)


Lesenswert?

Ok mag sein, aber für den allerersten Einstieg ist das eine heilwegs 
verständliche Seite.

Vielleicht kannst Du mir einen besseren Tipp für eine Seite (Bsp. 
Analogmessung) geben?

Viele Grüße

von Karl H. (kbuchegg)


Lesenswert?

metulski schrieb:

> Vielleicht kannst Du mir einen besseren Tipp für eine Seite (Bsp.
> Analogmessung) geben?

Darum gehst doch gar nicht.
Es geht darum, wie man sinnvoll C-Projekte aufbaut, die aus mehr als 
einem *.c File bestehen.

Und die Lösung dafür besteht eben nicht darin, alle einzelnen *.c 
Dateien in einem Haupt-C-File zu includen sondern: Jedes einzelne *.c 
dem AVR-Studio als einzelnes File in der Kategorie 'Source-Files' 
bekannt zu machen (was du getan hast, sonst hättest du die Fehlermeldung 
nicht) und immer nur die Header Files zu inkludieren.

Funktionsimplementierungen haben in Header Files in C nichts verloren! 
Es sei denn, sie sind als inline-Code explizit markiert.

D.h. so wie du da gemacht hast, ist das schon richtig.

Noch was zum Lesen:
http://www.mikrocontroller.net/articles/FAQ#Ich_hab_da_mehrere_.2A.c_und_.2A.h_Dateien._Was_mache_ich_damit.3F

Die Frage ist jetzt, was du mit
> verwiesen habe -> da wird aber
> kein ordentlicher Wert angezeigt, da kein string.
meinst.
Da steckt also noch irgendein Problem im Code, das raus möchte.
Aber dieses Problem hat wiederrum nichts damit zu tun, dass man 
Header-Files includet und keine Source Files.

Als Vorschlag würde ich hier
1
int main()
2
{
3
  int x;
4
  char messwertstring[5];
als allererstes einmal das Array von 5 auf mindestens 20 Zeichen 
anheben. Auch wenn der int x theoretisch niemals größer als 1024 sein 
kann, ist es nicht besonders schlau, solche Arrays auf Knirsch zu 
dimensionieren.
Und dann würde ich auch gleich die extern Deklaration von sprintf 
rauswerfen und dafür den Weg gehen, der eigentlich dafür vorgesehen ist:
#include <stdio.h>

von P. S. (Gast)


Lesenswert?

metulski schrieb:
> Ok mag sein, aber für den allerersten Einstieg ist das eine heilwegs
> verständliche Seite.

Offensichtlich nicht, sonst kaeme ja nicht so ein Unsinn heraus. C lernt 
man nicht mit Beispielen via Trial & Error, sondern mit einem guten 
Buch.

von Steffen M. (steffenmaq)


Lesenswert?

Karl heinz Buchegger schrieb:

> Und die Lösung dafür besteht eben nicht darin, alle einzelnen *.c
> Dateien in einem Haupt-C-File zu includen sondern: Jedes einzelne *.c
> dem AVR-Studio als einzelnes File in der Kategorie 'Source-Files'
> bekannt zu machen (was du getan hast, sonst hättest du die Fehlermeldung
> nicht) und immer nur die Header Files zu inkludieren.

Hallo, ich hab den Code auf der mikrocontrollerspielwiese geschrieben 
und hiermit verbessert. Ich würde mich tatsächlich noch als "im Stadium 
eines Anfängers mit einwenig Erfahrung" beschreiben.

Mit dem in der Mikrocontrollerspielwiese beschriebenen Equipment traten 
die  hier diskutierten Fehler nicht auf. Und gesagt hat's mir auch 
keiner.

Lieber Karl Heinz Buchegger,
vielleicht könntest Du mal einen kurzen Blick auf die geänderte Version 
werfen. Da wäre ich Dir sehr dankbar.

http://www.sachsendreier.com/msw/projekte/analog_lcd/analog_lcd.html



>
> Als Vorschlag würde ich hier
>
>
1
> int main()
2
> {
3
>   int x;
4
>   char messwertstring[5];
5
>
> als allererstes einmal das Array von 5 auf mindestens 20 Zeichen
> anheben. Auch wenn der int x theoretisch niemals größer als 1024 sein
> kann, ist es nicht besonders schlau, solche Arrays auf Knirsch zu
> dimensionieren.

Das mit dem "Knirsch" ist mir leider nicht ganz klar geworden.
Könntest Du das bitte noch mal erläutern?

Ich Danke Dir!

von Karl H. (kbuchegg)


Lesenswert?

Steffen M. schrieb:

> vielleicht könntest Du mal einen kurzen Blick auf die geänderte Version
> werfen. Da wäre ich Dir sehr dankbar.

Kein Problem. Gleich mehr dazu

>
> Das mit dem "Knirsch" ist mir leider nicht ganz klar geworden.
> Könntest Du das bitte noch mal erläutern?

:-)
Ich weiß nicht, ob man diesen Ausdruck in Deutschland kennt. In 
Österreich ist er gebräuchlich.
Mit "auf Knirsch dimensionieren" meint man: genau passend, nicht zu groß 
und auch nicht zu klein.
Wenn du dein Auto 'auf Knirsch' parkst, dann passt in der Parklücke kein 
Blatt Papier mehr zwischen die Stossstangen. Und zwar vorne wie hinten. 
Und da knirscht es dann beim Einparken tatsächlich :-)

So wie das dimensioniert ist, mit 5 chars, ist das gerade ausreichend. 
Der theoretische Maximalwert 1024 passt da rein.
Aber: da darf nichts schief gehen!
Wenn dein Programmierer auf die Idee kommt, den sprintf zb so abzuändern
1
   sprintf(messwertstring, "V: %dmv", x);
dann kracht es, weil das 5 Elemte Array überlaufen wird.
Wenn in der Testphase jemand die Mittelwertbildung aushebelt und die 
abschliessende Division rausnimmt, und da kommt als Ergebnis 12876 raus, 
dann kracht es, weil die 5 Zeichen nicht ausreichen.

Genau das ist mit 'auf Knirsch dimensionieren' gemeint. Man hat 
keinerlei Spielraum. Alles muss glatt gehen und bei Änderungen muss man 
höllisch aufpassen.

Dabei ist das aber gar nicht notwendig. Du kriegst von Atmel kein Geld 
zurück für Speicher, der zwar da ist, den du aber nicht verbrauchst. 
Benutze doch den Speicher und bau dir eine kleine Reserve in solche 
Arrays ein. Dann endet eine kleine Änderung in der Ausgabe oder in der 
Messwertberechnung nicht gleich in einem Fiasko. So etwas nennt man 
'defensives Programmieren'. Ein wenig Spielraum für unbeabsichtigte 
'Fehler' (sind ja eigentlich keine Fehler, nur arten sie in einem Fehler 
aus wenn der Programmierer auf das Array vergisst) einplanen.

von Karl H. (kbuchegg)


Lesenswert?

Karl heinz Buchegger schrieb:
> Steffen M. schrieb:
>
>> vielleicht könntest Du mal einen kurzen Blick auf die geänderte Version
>> werfen. Da wäre ich Dir sehr dankbar.
>
> Kein Problem. Gleich mehr dazu

Nicht böse sein. Ich sehs mir morgen früh an.
Da gibt es mehrere Punkte, über die man ein paar Worte verlieren sollte.

von Christian (Gast)


Lesenswert?

Hätte da auch eine Frage:

Warum steht in der lcd-routines.h vom Tutorial #define F_CPU 8000000
und nicht #define F_CPU 8000000UL ?

Habe im WinAVR dies mit UL gesehen.

von Stefan B. (PCBSD) (Gast)


Lesenswert?

Die Definition an dieser Stelle in dieser Form ist IMHO unglücklich. Das 
sollte aus der lcdroutines.h komplett raus.

Die Definition sollte in AVR Studio, AVR Eclipse oder im Makefile 
gemacht werden und wenn eine Source ohne dort gesetztes F_CPU übersetzt 
wird, soll eine Warnung+Defaultwert kommen. So wie es in util/delay.h 
gemacht wird.

Oder besser noch ein Fehler, damit man diese Stelle in Ruhe nachsehen 
kann und F_CPU bewusst auf den richtigen Wert setzt.

Jedenfalls finde ich ein pauschales Setzen des Werts in einem lokalen 
Library-Includefile ohne Abfrage eines ggf. bereits gesetzten Werts 
nicht so toll.

Zum Thema mit UL oder ohne...

In lcdroutines.h ist F_CPU überhaupt nur drin, weil die _delay_xx 
Funktionen benutzt werden und die ein definiertes F_CPU brauchen. Diesen 
Funktionen aus der avr-libc ist es egal, ob F_CPU mit oder ohne UL (oder 
L) definiert ist. Der Wert wird zur Compilezeit als Gleitkommawert 
interpretiert.

Kritisch wird es, wenn zusätzlich zu den LCD-Routinen auch UART-Routinen 
zum Einsatz kommen und dabei die Baudratenmakros aus dem 
AVR-GCC-Tutorial benutzt werden. Dort ist lt. Doku im Artikel die UL 
Kennzeichnung notwendig.

Lange Rede kurzer Sinn: UL im Tutorial an die Definition anhängen, wenn 
man die Definition dort stehen lassen will. Oder komplett dort 
rausschmeissen und Warnung einfügen, wenn F_CPU nicht definiert ist.

von Karl H. (kbuchegg)


Lesenswert?

OK.
Hier die Kritik.

Zunächst mal:
Du hast deine 'Zulieferroutinen', die LCD Funktionen und die ADC 
Abfrage, in Header Files verpackt. Das macht man so nicht.

Einer der wesentlichen Punkte in C ist es, dass einzelne Code-Teile 
unabhängig voneinander compiliert werden können.
Das bedeutet: Code kommt immer in *.c Files, die einzeln compiliert 
werden. In Header Files kommt alles, was ein Benutzer der Funktionen 
wissen muss. Das sind die Funktionsprototypen, können irgendwelche 
Konstanten sein oder Strukturdefinitionen etc. Aber kein Code!

Sinn der Sache ist es, einzelne Codeteile möglichst voneinander zu 
entkoppeln, so dass sie tatsächlich einzeln compilierbar sind.
Bei dir ist es so, dass du im Grunde nur eine Source-Code Datei hast, 
die du auf mehrere physikalische Dateien aufgeteilt hast. Das ist nicht 
Sinn der Sache! Gut. Bei den relativ kleinen Programmen, die auf einem 
AVR entstehen, spielt das keine große Rolle. Aber warum den Mechanismus 
nicht so umsetzen wie er gedacht ist. Stell dir vor, dein Programm 
besteht tatsächlich aus 2 Millionen Lines of Code (das hört sich jetzt 
groß an, aber so groß ist das gar nicht). Bei jeder kleinsten Änderung 
muss ALLLES neu compiliert werden, weil es ja im Grunde nur eine 
logische *.c Datei gibt, die compilliert wird. Das muss nicht sein. Wenn 
deine LCD Funktionen in einer lcd-routines.c Datei gesammelt sind, muss 
auch nur diese Datei compiliert werden, wenn du etwas an der 
Implementierung der LCD Funktionen änderst. Warum soll der ADC Code 
deswegen neu compiliert werden? Der hat doch nichts damit zu tun.

Der Weg den C (und viele andere Hochsprachen) gehen, ist hier kurz 
skizziert
http://www.mikrocontroller.net/articles/FAQ#Ich_hab_da_mehrere_.2A.c_und_.2A.h_Dateien._Was_mache_ich_damit.3F

Also: Einzelne *.c Dateien werden unabhängig coneinander compiliert. Die 
daraus resultierenden Object-Files werden zum eigentlichen Programm 
zusammengelinkt. Wird eine C Datei geändert, dann wird nur diese Datei 
neu compiliert und mit den noch vorhandenen Object Files der anderen 
'Module' erneut zu einem neuen Programm zusammengelinkt.
Auf diese Art sind dann auch Programme mit ein paar Millionen Lines of 
Code handhabbar.

In der ADC Funktion:
Es gibt eine Konvention, an die sich so gut wie alle C Programmierer 
halten. Und zwar: Namen die komplett in Grossbuchstaben gehalten sind, 
sind ausschliesslich für Makros reserviert. Sehe ich also in einem Code 
so etwas
1
   k = MAX( i, j );
dann weiß ich, dass MAX als Makro implementiert ist.
Und dieses Wissen kann wichtig sein!

Nehmen wir zb genau dieses MAX Makro. Wie implementierst du es. 
Naheliegend ist zb
1
#define MAX(x,y)    ( (x) > (y) ? (x) : (y) )

Bei diesem Makro gibt es jetzt aber ein Problem. Benutze ich es zb so
1
   int i = 5;
2
   int j = 6;
3
   int k;
4
5
   k = MAX(i++, j++);
welche Werte haben i und j nach der Bestimmung des Maximums. Wenn du 
jetzt sagst: i ist dann 6 und j gleich 7, dann mag man das zwar anhand 
der Verwendung so denken, aber es ist falsch! i ist 6 aber j ist danach 
8!
Dies deshalb, weil das Makro ja nur eine Textersetzung ist. Für den 
Compiler steht da in Wirklichkeit
1
  k = ( (i++) > (j++) ? (i++) : (j++) );
und im Zuge dieser Auswertung wird j zweimal inkrementiert. Es kann 
allerdings auch i zweimal inkrementiert werden, abhängig von den 
tatsächlichen Werten von i und j.

Von daher ist das etwas völlig anderes als
1
int max( int x, int y )
2
{
3
  return x > y ? x : y;
4
}
5
6
....
7
8
  k = max( i++, j++ );
das funktioniert genau so wie erwartet. Von daher kann es eminent 
wichtig sein, dass man an der Stelle der Verwendung mit einem Blick 
erkennen kann, was eine echte Funktion ist, und was ein Makro ist.

Mit dieser Konvention geht so etwas
1
int MESSWERT (int kanal)

daher überhaupt nicht. Das ist eine echte Funktion und kein Makro. Ein 
Name mit nur Grossbuchstaben ist daher nicht angebracht.

Auch ist der Funktionsname 'Messwert' nicht allzu glücklich gewählt. 
Funktionen führen eine Aktion aus, sie tun etwas. Funktionsnamen werden 
dadher gerne so gewählt, dass sie ein Verb (ein Zeitwort) enthalten: 
Gehe..., Hole..., Setze..., Lese...
Stell dir bei der Wahl von Funktionsnamen am besten einfach die Zeile 
beim Aufrufer vor. Wenn man sich da ein paar unbedeutende Füllwörter 
reindenkt und sich einfach nur durch Lesen der Wörter ein sinnvoller 
Satz ergibt, dann hast du einen guten Namen gewählt.

   i = GetADC( PotiChannel );

oder in Langform (mit den dazugedachten Füllwörtern):
i erhält den Wert, der sich beim Lesen des ADC vom Kanal an dem das Poti 
hängt, ergibt.
Perfekt. Die Codezeile ist ihr eigener Kommentar. Da muss man nichts 
dazu schreiben.

  i = GetVoltage( PotiChannel );
wäre auch nicht schlecht, allerdings etwas misveständlich. Denn von der 
Funktion bekommt man ja nicht die Spannung direkt als Zahlenwert (also 5 
für 5 Volt), sondern in einer Codierung.

  i = GetFilteredADC( PotiChannel );
wäre auch nicht schlecht. Dann sieht man nämlich beim Aufrufer, dass da 
nicht einfach nur der ADC einmal abgefragt wird, sondern dass da eine 
Filterung passiert (in deinem Fall eine Mittelwertbildung)

von Karl H. (kbuchegg)


Lesenswert?

Weiter in der Funktion MESSERT

Wenn etwas eine for Schleife ist, dann schreib es auch als for-Schleife 
und betreibe nicht die Taktik der maximalen Konfusion indem du 
Schleifenkonstruktionen benutzt die man so sicher nicht erwarten würde.

Was spricht gegen:
1
int GetADC( unsigned char kanal ) {
2
3
  int i, j;
4
  long int analogwert = 0, analogwert2=0 ;
5
  
6
  for( j = 0; j < 512; j++ ) {
7
8
    analogwert = 0;
9
    for( i = 0; i < 512; i++ ) {
10
      
11
      ADCSRA = 0x80;     // ADC eingeschaltet, kein Prescale 
12
      ADMUX = kanal;      // ADC Ref auf Avcc, PC0 gewaehlt, normale Formatierung
13
      ADCSRA |=_BV(ADSC);   // single conversion mode ein
14
15
      while (ADCSRA & (1<<ADSC)) {
16
        ;
17
      }  // auf Abschluss der Konvertierung warten 
18
19
      analogwert += ADCW;
20
    }
21
22
    analogwert2 += analogwert/512;
23
  }
24
  
25
  return analogwert2/512;
26
}

Jeder C Programmierer, der sein Handwerk länger als 3 Tage macht, 
erkennt sofort eine Schleife
  for( j = 0; j < 512; j++ ) {
und weiß, dass diese Schleife 512 mal ausgeführt wird. Bei deiner while 
Konsturuktion fragt man sich als allererstes sofort: Warum? Was wird 
spezielles mit dem j gemacht, dass man nicht eine stink normale 
Zählschleife einsetzen kann. Wie ist das? Wenn i in jedem Durchlauf um 1 
erniedrigt wird und bei 512 startet, wie oft wird dann die Schleife 
durchlaufen?

Da muss man viel zu viel darüber nachdenken. Obige for-Schleife hingegen 
ist das Standardkonstrukt für eine Schleife, die so oft ausgeführt wird, 
wie die Zahl in der Vergleichsbedingung angibt.

By the way. Ist dir eigentlich aufgefallen, dass du in deinem Code 
analogwert nie auf 0 zurücksetzt, aber munter immer weiter ADC Werte 
darin aufsummierst? Selbst dann, wenn in analogwert eigentlich die 
nächste Summe der nächsten 512 ADC Werte entstehen soll? Wie gut, dass 
in main() defensiv programmiert wurde :-)

beachte auch, wie ich dir
1
      while (ADCSRA & (1<<ADSC)) {
2
        ;
3
      }  // auf Abschluss der Konvertierung warten
umgebrochen habe. Sei bei Code-Formatierungen konsequent!
Deine Formatierung sagt: Die öffnende { kommt als letztes Zeichen in die 
Zeile, der Blockinhalt kommt eingerückt in die nächste Zeile und die 
Block-schliessende } kommt wieder auf eine eigene Zeile, aber wieder 
ausgerückt. genau das habe ich durchgezogen. Das dein Block nur aus 
einer einsamen, leeren Anweisung, bestehend aus dem ; besteht, ist kein 
Grund das Formatierschema über den Haufen zu werfen.

Der nächste Punkt ist zugegebenermassen etwas Geschmackssache.
1
  return (analogwert);

return ist kein Funktionsaufruf. Also lass ihn auch nicht wie einen 
aussehen! Die Klammern sind nicht notwendig und werden, anders als bei 
for und if, von der Sprache nicht gefordert. Sie schaden allerdings auch 
nicht, so wie sie bei
1
  analogwert=(analogwert2/512);
auch nicht schaden. Allerdings bringen sie auch nichts. Gerade letzteres 
würde allerdings durch das Weglassen der Klammern und hinzufügen von 2 
Leerzeichen gewinnen
1
  analogwert = analogwert2/512;
gewöhn dir wenigstens an, links und rechts eines = ein Leerzeichen zu 
machen. Seit deiner Volksschulzeit hast du dein Gehirn darauf trainiert, 
dass es einzelne Wörter in einem Text anhand von Leerräumen schnell als 
eigenständiges Wort identifizieren kann. Nutze doch dieses Training um 
dir selbst das Lesen des Quelltextes einfacher zu machen. Nichts ist so 
schlimm wie eine Zeile Quelltext, in der man erst mal 10 Sekunden mit 
den Augen links/rechts die Zeile absuchen muss, um die einzelnen 
Bestandteile der Anweisung zu identifizieren. 
OderfindestdudiesenTexthieretwaleichterzulesen als diese Fortsetzung?

von Karl H. (kbuchegg)


Angehängte Dateien:

Lesenswert?

So würde ich den Code aufbauen. Da könnte man sicherlich noch eine Menge 
anderer Dinge am Code tun. Aber fürs erste solls das sein.

Makefile anpassen nicht vergessen!

Die *.c Files werden einzeln compiliert und die entstehenden *.o Files 
zusammengelinktr

von Karl H. (kbuchegg)


Angehängte Dateien:

Lesenswert?

Hier noch das App-Studio File, für diejenigen die wissen wollen, wie man 
mehrere Source/Header Files im App Studio benutzt.
Einfach in den "Source File" / "Header File" Abschnitt einfügen.
Beim Build mal darauf achten. Wird irgendetwas verändert, so wird auch 
nur das absolut Notwendige nachcompiliert und neu gelinkt.

von Steffen M. (steffenmaq)


Lesenswert?

Lieber Karl Heinz Buchegger,

da hast Du Dir ja ein ganz schönes Stückchen Arbeit gemacht.
Ich danke Dir dafür recht herzlich!
Und ich werde mich auch gleich mal daran setzen, einen großen Teil 
Deiner Vorschläge umzusetzen.

Bei den .h und .c Dateien dachte ich einfach: man lagert aus, was man 
immer wieder braucht. Was wohl richtig ist aber nicht so, wie ich es 
letztendlich umsetzte.

Das mit der Groß-/Kleinschreibung von Makros und Funktionen hatte ich 
noch gar nicht gewußt.

Die Hinweise zu mnemotechnisch gewählten Funktionsnamen und homogener 
Struktur incl. strikt verwendeter Leerzeichen werden mich hoffentlich 
einwenig mehr an das Ziel bringen, Code zu schreiben, der Klarheit 
ausstrahlt.

Die for-next-Schleife funktionierte in den ersten Versionen des gcc-avr 
unter Linux nicht richtig und etliche meiner Bekannten benutzen noch 
immer die alte Linux-Live-CD von Guido Socher (tuxgraphics.org), die 
seinerzeit (und noch heute) einen genial schnellen Einstieg in die 
Mikrocontrollerprogrammierung bietet: Live CD booten, Controller 
anstöpseln, in ein Projektverzeichnis wechseln, make load eingeben - 
fertig. Nix installieren und funktioniert sogar in der Schule, wo man 
dann auch niemanden um irgendwelche Erlaubnis fragen muß. Und die läuft 
wirklich auf jedem alten Schrottrechner.

Danke nochmal!

von Jürgen D. (Gast)


Lesenswert?

Hallo

Hätte da auch eine Frage bezüglich LCD Tutorial.
In diesem Tutorial gibt es die Funktion lcd_clear, um ein Display zu 
löschen.
1
void lcd_clear(void)
2
{
3
   lcd_command(CLEAR_DISPLAY);
4
   _delay_ms(5);
5
}

Wie kann ich aber nur die zweite Zeile löschen?

von Karl H. (kbuchegg)


Lesenswert?

Jürgen D. schrieb:
> Hallo
>
> Hätte da auch eine Frage bezüglich LCD Tutorial.
> In diesem Tutorial gibt es die Funktion lcd_clear, um ein Display zu
> löschen.
>
1
> void lcd_clear(void)
2
> {
3
>    lcd_command(CLEAR_DISPLAY);
4
>    _delay_ms(5);
5
> }
6
>
>
> Wie kann ich aber nur die zweite Zeile löschen?

Lauter Leerzeichen in die Zeile schreiben

von Karl H. (kbuchegg)


Lesenswert?

Steffen M. schrieb:

> Bei den .h und .c Dateien dachte ich einfach: man lagert aus, was man
> immer wieder braucht. Was wohl richtig ist

Ja. Gegen die Grundidee ist absolut nichts einzuwenden. Ganz im 
Gegenteil

> aber nicht so, wie ich es
> letztendlich umsetzte.

Siehs einfach so.
Im Header File steht alles drinnen, was derjenige benötigt, der deine 
Funktion benutzen will. Ich C File ist alles enthalten, was die 
tatsächliche Implementierung dieser Funktionalität braucht. Im Grunde 
das, was der Verwender gar nicht wissen muss, er sich aber ansehen kann, 
wenn es ihn interessiert.

Sieh dir auch an, wie ich die lcd-routines zerteilt habe. Insbesondere, 
was ich mit den Makros gemacht habe. Ein paar der Makros sind im Header 
File, einige andere im C File. Je nachdem wer sie kennen muss. Die 
Makros, die einzelnen Bytes, die als Kommandos an das LCD geschickt 
werden, in Namen verpacken, muss jemand der die LCD Funktionen benutzt, 
nicht kennen. Ergo haben sie im Header File nichts verloren. Sie werden 
nur im C File gebraucht, also sind sie auch dort drinnen.

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.