Hallo Community!
Mir geht es darum ein 7-Segment Display mit einem ATmega8 anzusteuern.
Das alles möchte ich in C (GCC) machen. Einzelne Zahlen darzustellen ist
ja nicht das Problem, jedoch scheitere ich daran Variablen auszugeben.
Also dem Controller beizubringen, welche Ziffer zu welchem Binärwert für
PortD (da hängt das Display dran) gehört. Macht man sowas über einen
#define-Block, mit einem Array oder noch anders? Wenn möglich sollte es
nicht den SRAM belasten.
Weiter habe ich Probleme beim Multiplexen (Segmentweise). Wie bekomme
ich es hin, dass er bei z.B. einer 21 die 2 "isoliert" und an erster
Stelle anzeigt und das gleiche mit der 1 an zweiter Stelle macht?
Ich danke schon mal!
Edgar
Gut, hatte ich mir schon gedacht, wollte nur sicher gehen.
Dann wäre nur noch die Frage des Multiplexens. Das einzige was mir
einfällt wären jetzt lustige Konstruktionen mit Strings, aber ich glaube
irgendwie nicht, das das so richtig ist...
Edgar
Michael L. schrieb:> Das Array ist ja "const", kann also nicht mehr verändert werden. Wiso> sollte es der GCC dann ins RAM stecken?
Weil das für den Compiler einfacher ist.
Wenn man wirklich haben will daß er das Array im Flash läßt braucht man
entsprechende "progmem" attribute.
So groß sind die Arrays nicht, dass man da unbedingt vermeiden muss die
ins RAM zu packen. Das sind nur 10 Bytes für die Umrechnung von 0-9 in
die Segmente. Den Speicher für die aktuelle Anzeige muss man ja ohnehin
ins RAM ablegen, und das sind auch nur 1 Byte je Stelle.
Wenn das Array im RAM steht, wird der Zugriff in der Regel schneller und
einfacher. Damit die Daten nicht ins Flash kommen müsste man schon
PGM_space nutzen und dann einen umständlicheren Zugriff, weil das Flash
einen anderen Adressbereich nutzt. Entsprechend landen bei GCC-AVR auch
konstante Arrays normalerweise im RAM.
p.s. für ungenutztes RAM gibt es kein Geld zurück.
Gut, dann ins RAM, das bisschen interessiert nun auch nicht...
Ich als Anfäger bin nur etwas vorsichtiger, man liest ja auch überall,
das man ressourcensparend programmieren soll.
Die Frage mit dem Multiplexen wäre da noch, oder habe ich dort einen
vollkommen falschen Ansatz?
Mein Wert kommt vom ADC und wird ein bisschen umgerechnet, am Ende
bleibt eine zweistellige Zahl.
Meine Idee gerade: Bei einem Integer wird bei einer Division immer
abgerundet(richtig?). Also einfach den Wert durch 10 und gut ist. Also
21 / 10 = 2,1 -> 2 wird ausgegeben und dann angezeigt.
Für die 1 dann 21 % 10 müsste doch 1 ergeben
Könnte das funktionieren?
Michael L. schrieb:> Das Array ist ja "const", kann also nicht mehr verändert werden. Wiso> sollte es der GCC dann ins RAM stecken?
weil der prozessor das flash in der avr-architektur nicht einfach so
adressieren kann. siehe datenblatt, suchfunktion, compiler-doku, etc pp.
Michael H. schrieb:>> weil der prozessor das flash in der avr-architektur nicht einfach so> adressieren kann.
Klar kann er das, kostet (jedenfalls bis 64k Flash) nur genau einen Takt
mehr pro Zugriff.
c-hater schrieb:> Klar kann er das, kostet (jedenfalls bis 64k Flash) nur genau einen Takt> mehr pro Zugriff.
nein, kann er nicht.
direkte adressierung ist nicht möglich. und ein compiler wird den teufel
tun und die adressierung einfach so einbauen.
das ist bei harvard-architekturen prinzipbedingt so.
aber wenn dus nicht glauben willst, kann mir das egal sein. ein drittes
mal schreiben werd ichs nicht.
lies es nach oder stirb dumm - deine sache.
edgar 339 schrieb:> Ich als Anfäger bin nur etwas vorsichtiger, man liest ja auch überall,> das man ressourcensparend programmieren soll.
Höre nicht auf die Angstmacher! Das sind vielfach Ratschläge (heute sind
es Vorurteile), die im letzten Jahrhundert entstanden sind und nicht
mehr aus den Köpfen wollen.
Als Anfänger bietet Dir ein ATmega8 unendlich viel Platz. Kümmere Dich
zunächst um die Lösung Deines Problems. Mit zunehmender Erfahrung
programiert man automatisch effizienter.
Zur Orientierung noch ein Beispiel, wie man ADC-Werte auf ein Display
bekommen kann. Beitrag "7-Segm. LCD-Ansteuerung, 4-stelliges Einbaumodul, Attiny45"AVR-GCC erzeugt 2122 Byte Code und es werden 75 Byte Ram gebraucht -
ohne irgendwelche platzsparenden Tricksereien.
Für eine 2-stellige Anzeige würde ich kein Multiplex veranstalten. Zwei
Schieberegister CD4094 (die CMOS-Version) direkt an die Segmente
angeschlossen reichen aus. Statischer Betrieb: keine Treiber, keine
Widerstände und kein Ausfall der Anzeige, falls der µC hängen bleibt,
keine 'Geistersegmente', kein Ripple in der Versorgungsspannung, keine
Störstrahlung (um mal selber Angst zu schüren :-), ...
Aber mache, wie Du willst.
Danke für deine Vorschläge!
Das mit dem Angst machen ist mir auch schon aufgefallen. Bisher aber nur
in den ganzen Schaltungen. Mindestbeschaltung der Controller
beispielsweise. Abblockkondensatoren kann ich ja noch verstehen, aber
Start-Up-Kondensator, oder nen externen Brown-Out-Detektor?
Ich wollte das Multiplexen, damit ich erstens lerne, wie das geht, und
da dort noch weitere Digits dazukommen(derzeit läuft es mit 4, sollen 6
werden).
Ein Problem fällt jetzt schon auf: die Berechnungen für die Ziffern
dauern unterschiedlich lange, deshalb sind auch die Digits
unterschiedlich hell. Ich werde das jetzt mit Timerinterrups machen,
dann sollte das ja gehen.
Edgar
edgar 339 schrieb:> Ich wollte das Multiplexen, damit ich erstens lerne, wie das geht, und
Das ist gut, mach es!
Selber hatte ich im Laufe der Zeit einige Großanzeigen (25cm
Ziffernhöhe) gebaut, wo Multiplexen nicht mehr sinnvoll war; aktuell
habe ich eine Schaltung vor mir, die noch mit <3V arbeitet. Auch hier
ist die statische Ansteuerung einfacher. Deshalb mein Rat zum statischen
Betrieb.
> unterschiedlich hell. Ich werde das jetzt mit Timerinterrups machen,> dann sollte das ja gehen.
Muliplexen sollte man grundsätzlich per Timerinterrupt machen. Achte auf
ein bißchen Totzeit zwischen dem Abschalten des letzten Digits und dem
Einschalten des nächsten. Die Zeit dazwischen kann man für weitere
Befehle nutzen, die auch im Interrupt ausgeführt werden müssen.
Andernfalls kommt es zu den 'Geistersegmenten'. Aber das wirst Du selber
sehen.
...und ich dachte, die Leds scheinen durch die Wände zwischen den
Segmenten :D
Ich bin fast fertig, benutze aber diesmal einen mega48 und bin erst mal
nicht da...
Edgar
Da hast Du ja schon mal ein Erfolgserlebnis. Das braucht man unbedingt.
Wenn Du kurze Kommentare in den Code einfügst, kann man ihn später
einmal besser verstehen, insbesondere wenn es nicht mehr 50 sondern 5000
Zeilen sind.
Eine Sache ist aber ganz wichtig: deklariere die Variablen i und y, die
nur in der ISR von T0 gebraucht werden auch dort.
ISR (TIMER0_OVF_vect)
{
static uint8_t i; // Schleifenzähler
uint16_t y; // Hilfsvariable und Index
....
Ferner sollte erst PORTD geschrieben werden und dann PORTB, auch wenn es
aktuell nichts auszumachen scheint.
Mit den Berechnungen in der T0-ISR hast Du ja allen Optimierungsgurus
zunächst die kalte Schulter gezeigt :-)
Irgendwann wirst Du ein 'ausgabe_register[MAX_DIGITS]' verwenden und im
Interrupt nur noch die betreffeden Digits indiziert ausgeben. Spätestens
dann, wenn noch ein '-' Vorzeichen angezeigt oder die Vornullen
unterdrückt werden sollen.
Die Kommentare habe ich nur weg gelassen, damit es schnell geht :D
Das die immer dazu gehören habe ich schon am eigenen Leibe erfahren
müssen...
Globale Variablen sind böse, ich weiß...
i muss aber global sein, es soll ja von Interrupt zu Interrupt erhöht
werden, ansonsten wird es jedes mal auf 0 gesetzt, oder?
Zur Leistungsoptimierung hatte ich mir gedacht: Für jede Ziffer eigene
Variable, und die Berechnung nur durchführen, wenn sich die anzuzeigende
Zahl auch wirklich geändert hat.
Außerdem wollte ich noch die Berechnung außerhalb der ISR durchführen.
Danke noch für das mit den Ports, da muss man nicht lange nachdenken, um
zu sehen das das sinnvoller ist ...aber man muss selbst erstmal drauf
kommen...
>ausgabe_register[MAX_DIGITS]
Ich verstehe zwar noch nicht, was das wird oder soll, aber wenn ich
soweit bin, kann ich ja wieder hier vorbeischauen :)
Ich danke dir(und natürlich auch den anderen hier) das ihr mir helfen
wollt und hoffe, das ich das irgendwann wieder gut machen kann(wie war
das mit Maus und Löwe?...)
Edgar
edgar 339 schrieb:> Globale Variablen sind böse, ich weiß...
Naja, ganz sooo ist es dann auch wieder nicht - es ist nur einfach
sinnvoll, die Variablen in der kleinstmöglichen Sichtbarkeitsebene zu
deklarieren - d. h. in einer Funktion, wenn sie nur ein der Funktion
gebraucht werden, oder in der Datei, wenn sie nur in der Datei gebraucht
werden.
> i muss aber global sein, es soll ja von Interrupt zu Interrupt erhöht> werden, ansonsten wird es jedes mal auf 0 gesetzt, oder?
Nicht, wenn Du es mit dem Schlüsselwort "static" versiehst.
>>ausgabe_register[MAX_DIGITS]> Ich verstehe zwar noch nicht, was das wird oder soll, aber wenn ich> soweit bin, kann ich ja wieder hier vorbeischauen :)
Der Hintergedanke ist, dass die Berechnung in der main()-Loop oder wo
auch immer vorgenommen und in der Timer-ISR nur das Ergebnis ausgegeben
wird. Als Variable zur Übergabe dient dann sinnvollerweise ein
(globales) Array, das den anzuzeigenden Wert jedes Digits enthält.
>Berechnung in der main()-Loop oder wo>auch immer vorgenommen und in der Timer-ISR nur das Ergebnis ausgegeben>wird
Dann habe ich ja schon aus Versehen richtig gedacht :D
Ah, jetzt ist es klar, mich hatte "Register" nur verwirrt. Hatte
überhaupt nicht an ein Array gedacht... Danke!
M. N. schrieb:> Andernfalls kommt es zu den 'Geistersegmenten'.
Aber auch nur, wenn man Treibertransistoren im Emitterschaltung benutzt.
Die Kollektorschaltung ist schnell genug und spart obendrein den
Bassiswiderstand.
Peter Dannegger schrieb:> Die Kollektorschaltung ist schnell genug und spart obendrein den> Bassiswiderstand.
Das ist ja nicht verkehrt, aber wer macht es denn so? :-)
Allerdings ist der Anwendungsbereich eingeschränkt, da der sinnvolle
Versorgungsspannungsbereich bei rund 4-5V liegt. Ein 3V-Design geht
nicht mehr, da der Spannungsabfall (Segment ca. 1,8-2,2V + 2 x Vbe ca.
1,4V) zu groß wird. Die obere Grenze wird durch VCC des Prozessors
begrenzt. Größere Anzeigen mit mehreren LEDs in Reihenschaltung (>=2)
funktionieren somit auch nicht.
Auf der anderen Seite sind ca. 5µs Totzeit bei einer Multiplexfrequenz
von 0,4 - 1kHz (>= 1ms) doch kein Thema. Die Helligkeit wird um max.
0,5% rduziert, was überhaupt nicht negativ auffällt.
Wenn wir schon bei der Helligkeit sind: bei statischer Ansteuerung kann
man diese bequem über ein PWM-Signal an output-enable steuern.
Ein Pluspunkt, den ich oben noch vergessen hatte :-)
Noch was verbesserbar? :D
Das einzige was mir einfallen würde, währe, nur die letzte(n) Ziffern
neu zu berechnen, nachdem hochgezählt wurde, aber es soll ja später für
Werte vom ADC sein, die sich willkürlich ändern.
Edgar
dir ist klar, dass du dir damit den Rest vom Port B unbenutzbar machst?
1
ISR(TIMER1_OVF_vect)
2
{
3
n++;//Auszugebene Zahl erhöhen
4
Ausgabe_Ziffern[0]=n/1000;//Berechnungen
5
Ausgabe_Ziffern[1]=(n%1000)/100;
6
Ausgabe_Ziffern[2]=(n%100)/10;
7
Ausgabe_Ziffern[3]=n%10;
8
}
Die Aufsplittung eines uint16_t in die einzelnen Ziffern (und das
bestimmen des dafür anzuzeigenden Musters) hätte sich aber wirklich eine
eigene Funktion verdient. Sowas wird man ja schliesslich öfter brauchen.
> Werte vom ADC sein, die sich willkürlich ändern.
Eben. Du willst Werte vom ADC ausgeben. Und dafür hättest du gerne eine
Funktion
void disp_number( uint16_t value )
{
...
}
welche sich um das 'schmutzige' Geschäft kümmert, alles für die
Multiplexroutine herzurichten, so dass diese dann auch tatsächlich diese
Zahl anzeigt.
Lass diese Umkodierung von der numerischen Ziffer in das von der Anzeige
anzuzeigende Muster
1
PORTD=Ziffer[Ausgabe_Ziffern[0]];
auch gleich die Funktion disp_number machen.
Zum einen muss das nicht ständig in jedem Interrupt gemacht werden, zum
anderen soll sich die Multiplex-Funktion gar nicht darum kümmern, was
das was du anzeigst, eigentlich darstellen soll. Wenn du in deinem
Programm entscheidest, dass die 7-Seg Anzeige in allen 4 Stellen lauter
'-' anzeigen soll, dann sollte das der Multiplex-Funktion aber sowas von
egal sein. Dein Programm schreibt die entsprechenden Musterbytes in das
Array und der Multiplex-Code in der ISR zeigt sie an.
Ok, Danke!
PortB brauche ich nicht, PortB |= 1 << [0/1/was auch immer] wäre
natürlich besser.
Eine Funktion ist bestimmt sinnvoller, als den Code in andere Projekte
zu kopieren...
Edgar
edgar 339 schrieb:> Ok, Danke!>> PortB brauche ich nicht,
:-)
kennst du die Kategorie: Berühmte letzte Worte?
> PortB |= 1 << [0/1/was auch immer] wäre> natürlich besser.
Nicht nur "wäre". Es ist in Summe auch einfacher und universeller.
Solche Basis-Sachen richtet man sich einmalig ordentlich her, so dass
man sie die nächsten 10 Jahre in x Projekten benutzen kann, ohne dauernd
das Rad neu erfinden zu müssen.
Zu Deiner T0-ISR:
Lösche bitte ganz zu Anfang alle Segmente und den zuletzt aktiven
Digit-Treiber (hatte Karl-Heinz ja schon gezeigt). Danach mach mit
i++; .... weiter.
Dadurch erhälst Du automatisch eine Totzeit zwischen den Digits.
Als nächstes könntest Du probieren, die Vornullen zu unterdrücken, dass
anstatt "0000" nun " 0", oder statt "0123" dann " 123" angezeigt wird.
Wenn Du ein bißchen mutiger bist, setze den CPU-Takt ruhig auf 8MHz :-)
Gut, dann habe ich ja was, worüber ich nachdenklen kann, ich muss mich
aber jetzt erst mal um die Schule kümmern... Naja, hat ja alles seinen
Sinn.
Edgar
So, langes Wochenende :3
So, ich habe die Zeit sinnvoll genutzt und den Code umgeschrieben. Die
Vornullen werden unterdrückt, indem erst abgefragt wird, ob ganz links
eine 0 steht. Falls ja wird dieses Digit mit Ziffer[10] belegt, was
nichts anzeigt. Nun wird abgefragt ob die zweite Stelle eine 0 ist. Wenn
ja, dann wird nachgesehen, ob die vorherige Stelle unterdrückt wurde, um
nicht Nullen in der Zahl zu unterdrücken(soll ja vorkommen). Das geht
dann immer so weiter.
Ich habe auch schon überlegt, das in einer for-schleife zu machen,
jedoch gab es da große Komplikationen und ich wollte, bevor ich weiter
überlege erst einmal wissen, ob der Ansatz richtig ist.
Des weiteren wird "EEEE" ausgegeben, wenn die Zahl höher ist als 9999.
Das wird am Anfang der Berechnungen durchgeführt.
Fragen die ich noch direkt habe:
1. Ist die Schleife notwendig, oder gibt es einen Weg, gleich mehreren
Stellen eines Arrays den gleichen Wert zuzuweisen(Ziffer[0,1,2,3] mag
der Compiler nicht)?
2. Wie ist die Null-Unterdrückung zu verbessern(Schleife?)?
Ich danke nochmals!
Edgar
Ähm, ich habe gerade bemerkt, dass nicht EEEE ausgegeben wird, sondern
er wieder neu anfängt zu zählen. Zumindest beim erstem mal, bei den
nächsten flackert er wieder, weil er über das Array hinausschießt.
Das kann ich mir irgendwie nicht erklären... Naja, ich werde noch mal
nachsehen.
Edgar
> uint8_t Ausgabe_Ziffern[4] ;> for (i=0; i<5; i++)
***
> {> Ausgabe_Ziffern[i] = Ziffer[11];> }
Ein Array der Länge 4 hat die Indizes
0, 1, 2, 3
das sind 4 Stück ... zähl nach. Und der höchste gültige Index ist 3.
Also 1 weniger als die Arraylänge.
eine Schleife
> for (i=0; i<5; i++)
durchläuft den Schleifenkörper mit Werten in i von
0, 1, 2, 3, 4
nach der nächsten Erhöhung von i wäre i dann 5 und für 5 gilt dann i < 5
nicht mehr und die Schleife bricht ab.
Aber:
Innerhalb der Schleife greifst du daher auf
Ausgabe_Ziffern[0], Ausgabe_Ziffern[1], Ausgabe_Ziffern[2],
Ausgabe_Ziffern[3] und Ausgabe_Ziffern[4]
zu.
Nur: Ein Ausgabe_Ziffern[4] existiert nicht! Der höchste zulässige Index
war 3!
Ich sollte auch zurück schreiben, wenn ich das Problem gelöst habe und
nich erst Tage warten...
Das Problem liegt auf der Hand: er stellt es schön auf "EEEE" ein ... um
danach wie üblich alles für die Ziffern zu berechnen und Mist
auszugeben...
Deshalb habe den Rest nach der for-Schleife in Klammern eines else
gesteckt.
Edgar