Hallo, ich habe das mal ne Frage. Ich nutze einen ATmega8 mit 16 Mhz. Daran ist per externen Interupt ein HallSensor. Timer 1 ist durch 64 vorgeteilt umd misst die Zeit pro Umdrehung. Diesen Wert teile ich durch 512, und lage damit Timer0 vor, der mir 512 Spalten pro Umdrehung machen soll. bei 1000 U/min dauert eine Umdrehung 60.000 µs. Dann zählt Timer1 bis 15.000. 15.000/512 = 29,296857 Also lade ich Timer0 mit (255-19) vor. Nun ist aber 29*512 = 14.848, die Umdrehung ist aber etwas langsamer. Bei 1500 U/min sind die Werte: 10.000 Timer1 Takte, / 512 = 19,53125, 19*512= 9728. Weiß jemand eine Möglichkeit, diesen "Rechenfehler" zu kompesieren? Danke und lieben Gruß Tobi Hier noch der Code der beiden Timer: Wie gesagt, beide laufen mit 250kHz, 16Mhz/64Vorteiler. /********************************************************** Name: Interrupt 1 Hall Sensor **********************************************************/ ISR(SIG_INTERRUPT1) { timer1wert = TCNT1; ocrwert = timer1wert / 512; spalte = 0; TCNT1=0; TCNT0 = (255 - ocrwert); } /********************************************************** Name: T0 Überlauf_Interrupt 8 Bit Timer **********************************************************/ ISR(SIG_OVERFLOW0) { TCNT0 = (255- ocrwert); spalte++; if (spalte > 512) { spalte = 512; } }
Zeitmessung per nicht per Interrupt-Eingang sondern per Timer-Capture durchführen. Sonst hast du die Interrupt-Latenz in der Messung drin. Aus dem gleichen Grund solltest du nicht den Timer 0 verwenden, sondern den Timer 2 im CTC-Modus und dort OCR2 mit dem Zeitwert programmieren.
Ach ja: 15000/14848 gibt 1% Fehler. Besseres als 0,5% darfst du bei einem 8bit Timer als Spaltenzähler ohnehin nicht erwarten. Bischen genauer wird es immerhin, wenn du vor der Division rundest, also "(timer1wert + 255) / 512".
Drittens: Wenn du Timer0 auf 255-0 programmierst, kriegst du nicht etwa eine Periode von 0 Takten, sondern 1. Analog gibt's bei 255-1 nicht 1 Takt sondern 2.
Hallo Andreas, leider ist der Hall Sensor schon am Ext Int angeschlossen, dann kann ich den Input Capture doch garnicht nutzen, oder? Gruß Toby
Pech / Planungsfehler. Aber bei 60ms hält sich der Fehler in Grenzen. Der andere Timer ist schlimmer dran. EDIT: Nochmal von vorne. Was ist den die minimale Umdrehungszeit? Der andere Timer ist ja 512mal schneller dran, und in dessen Zykluszeit, im µs-Bereich, spielt die Latenzzeit eine teilweise recht spürbare Rolle. Weniger als einige zig µs solltest du dem AVR nicht zumuten.
Hallo, die min Drehzahl sollte so bei 500 bis 1000 U/min sein. Max drehzahl etwa 2000 bis 2500 U/min. Ich müsste doch den Hall Sensor an den Input Capture Pin hängen, richtig? Kann die nächste Hardware ja arauf einstellen. Nur habe ich schon 25 Platinen fertigen lassen. Gruß Toby
Ergibt nach meiner Rechnung 46µs pro Spalten-Interrupt bei 2500U/min.
Das geht grad noch, du musst dann aber den CTC Modus verwenden sonst
wird das hoffnungslos ungenau.
Ausserdem solltest du alle andere Interrupts sehr kurz halten und/oder
dort sofort die Interrupts wieder freigeben und genug Stack für
verschachtelte Interrupts haben. Sonst kann es sein, dass dir
gelegentlich mal ein Interrupt abhanden kommt.
Kann es sein, dass du eine Serienfertigung startest, bevor das Design
überhaupt funktioniert? Mutig...
> Ich müsste doch den Hall Sensor an den Input Capture Pin hängen,
Richtig.
Hallo Andreas, etwas gemein bist Du doch! ;-) Also, Hall Sensor an ext. Int, (besser an ICP1, geht aber aufgrund der Hardware (noch) nicht). Timer 2 im CTC Modus und im Vergleichsregister (OCR2) den Timer1_Wert/512 verwenden. Muss jetzt Timer2 auch mit dem Vorteiler 64 laufen? Was genau macht der CTC Modus des Timer2? Er zählt bis zum OCR2 Wert, macht dann einen Überlauf Interrupt. Das ist dann der Spalten Interupt, ja? Aber den Rundungsfehler bekomme ich damit ja immer noch nicht raus. Ich möchte ja eine feine "Regelung" erzielen. Mit Timer2 im CTC Modus erreiche ich ja nur, das ich keine Interupt Routine mehr habe. Ich glaube, ich sehe den Wald vor lauter Bäumen nicht! Timer1 hat doch auch einen CTC Modus, und sogar 2 Vergleichsregister, bringt mir das denn nichts? Wird es trotz Timer2 im CTC Modus genauer, wenn ich den Timer1 Wert Runde: "(timer1wert + 255) / 512"? Das hier: "Drittens: Wenn du Timer0 auf 255-0 programmierst, kriegst du nicht etwa eine Periode von 0 Takten, sondern 1. Analog gibt's bei 255-1 nicht 1 Takt sondern 2." bedeutet, das (256-0) 0 Timertakte braucht, und (256-1) einen Timertakt, da der Überlauf Interupt erst beim Wechsel vom Timerwert 255 zu 0 passiert. Richtig? War halt nur ein Gedankenfehler, da ich dachte, der Timer kann ja höchstens einen Wert vo 255 haben. Gruß Toby
> etwas gemein bist Du doch! ;-) Geht doch. Sehe keinen Sinn drin, alles bis zum fertigen Programm vorzukauen. Bischen selber denken schadet nicht ;-). > Also, Hall Sensor an ext. Int, (besser an ICP1, geht aber aufgrund der > Hardware (noch) nicht). Naja, Kabel nachträglich ziehen... Aber in der Dimension nicht tragisch. > Timer 2 im CTC Modus und im Vergleichsregister (OCR2) den > Timer1_Wert/512 verwenden. > > Muss jetzt Timer2 auch mit dem Vorteiler 64 laufen? Vorteiler sinnvollerweise grad so klein wählen, dass er bei maximaler Zeit nicht überläuft. > Er zählt bis zum OCR2 Wert, macht dann einen Überlauf Interrupt. > Das ist dann der Spalten Interupt, ja? Ja. Wobei er danach gleich löscht und weiterzählt, und genau das macht deine Version erst im Interrupt nachdem eine nicht genau bekannte Zeit vertrichen ist. > Aber den Rundungsfehler bekomme ich damit ja immer noch nicht raus. Den kriegst du mit einem 8bit Timer auch nicht weg. > Mit Timer2 im CTC Modus erreiche ich ja nur, das ich keine Interupt > Routine mehr habe. Nein. Den Interrupt hast du immer noch, aber dessen Latenz steckt nicht mehr im Abstand zweier Interrupts drin (kumulierender Fehler!). > Timer1 hat doch auch einen CTC Modus, und sogar 2 Vergleichsregister, > bringt mir das denn nichts? Ich meine mich zu erinnern, dass der schon belegt ist ;-). Aber stimmt schon: Sobald du dort mit Capture arbeitest läuft der frei durch und du kannst mit einem Match Register einen zyklischen Interrupt ohne kumulierenden Fehler aufbauen. > Wird es trotz Timer2 im CTC Modus genauer, wenn ich den Timer1 Wert > Runde: > "(timer1wert + 255) / 512"? Ja. > bedeutet, das (256-0) 0 Timertakte braucht, und (256-1) einen Timertakt, > da der Überlauf Interupt erst beim Wechsel vom Timerwert 255 zu 0 > passiert. Richtig? Ja. > War halt nur ein Gedankenfehler, da ich dachte, der Timer kann ja > höchstens einen Wert vo 255 haben. Kann er. Grad weil er eben auch diesen Wert haben kann.
Hallo, t'schultschung wenn ich mich in eure Diskussion einmische. Die Ungenauigkeit wird doch hauptsächlich durch den Vorteiler von 512 und dem bei der Division auftretendem Rundungsfehler hervorgerufen. Warum lässt du den Timer nicht mit dem Prozessortakt laufen und baust dir ein 3. und/oder 4. Timerbyte selbst dazu? Oder habe ich das Problem nicht richtig verstanden. Gruss Gast
> Warum lässt du den Timer nicht mit dem Prozessortakt laufen und baust > dir ein 3. und/oder 4. Timerbyte selbst dazu? Für jedes zusätzliche Bit in Software läuft der Timer doppelt so schnell. So wie jetzt gibt's im Extremfall einen Interrupt pro 46µs. Wie weit meinst du kann man mit einem Handler in C gehen?
Hallo, ich habe nun folgendes: //Timer1 TCCR1B = 0b00000011; // Timer1/64 //Timer2 TCCR2 = 0b00001100; // Timer0/64 / CTC Mode TIMSK = 0b10000000; // Timer2 Compare Interupt ... ... for( ;; ){ if (flag == 1) { Anzeige(); } ... ... ISR(SIG_INTERRUPT1) { timer1wert = TCNT1; ocrwert = (timer1wert + 255) / 522; //eigentlich /512, sonst werden die letzten Spalten verschluckt OCR2 = ocrwert; spalte = 0; TCNT1=0; flag = 1; } ISR(SIG_OUTPUT_COMPARE2) { spalte++; flag = 1; if (spalte > 512) { spalte = 512; } } Trotz allem, ist es nicht wiklich besser geworden. Wenn ich nun z.B. den Timer1 im CTC Mode für die Spalten nehme, kann ich doch die Zeit einer Umdrehung mit dem Timer0 oder 2 messen. Den könnte man ja mit einer 8Bit Variable auf 16 Bit erweitern. Die Frage ist nur, bringt mich das weiter? Gruß Tobi
Hallo, ich habe diesen Code gefunden (im Anhang). Ich verstehe das hier leiden nicht so ganz. Weiß jemand Rat? Lap wird jede Umdrehung hoch gezählt. Ist also ein Umdrehungszähler. Ist Lap > 250, kommt Adder ins Spiel. Was macht er denn damit? Verstehe es wirklich nicht! Externer Interupt: void Crossing_interrupt(void) { static unsigned int LastCount; static unsigned int TotalCount; static int Latch; static unsigned char Lap; Latch = TCNT1; TotalCount = Latch - LastCount; LastCount = Latch; Lap++; if (Lap > 250) { Adder = TotalCount / 378; Lap = 0; } WeelPosition = 0; OCR1 = Latch + Adder; TIFR |= 0x80; Display(); } Vergleichs Interupt OCR1A: void Degre_interrupt(void) { OCR1 = TCNT1 + Adder; Display(); } In der Funktion Display(); wird Wheelposition hochgezählt. Wheelosition ist ja gleich Spalten, nicht? Gruß Toby
@10:57: Auch hier könnte der geneigte Programmierer mal versuchen, rauszukriegen ob bei einem (theoretischen) timer1wert von 0 der andere Timer wirklich das Singularitätsproblem löst und es schafft, seine Taktfrequenz durch 0 zu dividieren. Oder ob er vielleicht doch noch mal nachlesen sollte, was der CTC-Modus wirklich macht.
>>10.000 Timer1 Takte, / 512 = 19,53125, 19*512= 9728.
Ganz einfach.
10.000 - 9728 = 272
Also einen Zähler der pro Timer ISR dekrementiert wird. Am Anfang steht
da 512-272 drinnen. Ab dem Moment wo dieser Zähler <= 0 ist muß der
Timer statt 19 nun 20 Takte dauern.
Ergibt:
19*512+272=10000
Im Grunde rechnest du so
10000 div 512 = 19
10000 mod 512 = 272
In Takten heist dies 19 + 272/512'tel
Um also auf exakt 10.000 Takte zu kommen und dasmit 512 Schritten fehlen
dir als Rest 272 Takte. Nach jedem der 512 Schritte (ISR) dekrementierst
du einen Zähler der pro Umdrehung am Anfang auf 512 - 272 gesetzt wird.
So bald dieser Zähler <= 0 wird muß der Timer um +1 Takt länger dauern.
Das ergibt also in den letzten 272 Schritten einen Timerwert von 20
statt 19.
Also 240*19+272*20=10000.
Gruß Hagen
Hallo,
ich habe bereits ds gleiche Problem erkannt.
>>10.000 Timer1 Takte, / 512 = 19,53125, 19*512= 9728.
Und das ich dabei einen Fehlerwert von 10.000 - 9728 = 272 habe.
Danke Hagen, werde s mal testen.
Nur wusste ich nicht, wie ich es am besten machen kann,
nun 240 19 und 272 20 in den Timer zu laden.
Dabei ist mir noch etwas ganz Anderes eingefallen.
Wenn ich Drehzahlen von 1000 bis 2500 U/min habe,
dann könnte ich doch auch den Timer1 mit dem Vorteiler 64 laufen lassen.
Den Timer2 lasse ich aber mit dem Vorteiler 8 laufen.
Nun rechne ich einfach 10.000 / 64 = 156.
156 * 64 = 9984.
Damit wäre ich schon um einiges genauer, als mit 19*512 = 9728,
da ich nur noch einen Fehlerwert von 24, statt 272 habe.
Geht denn nun vielleicht die Div durch 64 auch etwas schneller,
als eine Div durch 512?
Wenn ja, würde sogar die Externe Interuptroutine kürzer werden.
Gruß Toby
Hm, du benötigst das doch für deine Propeller Clock, richtig ? Dann hast du doch deine 512 LED-Spalten-Positionen und eine ISR die pro Umdrehung 512 mal aufgerufen wird. Exakt in dieser ISR lädst du bei Position 0 den Timer mit 19 und nach 512-272 Aufrufen der ISR lädst du diesen mit 20. Dein Hallsensor läuft per ICP und misst die Takte pro Umdrehung aus, das wäre bei deiner Rechnung der Wert 10000. Das ändert sich immer leicht da ja die Drehzahl nicht ganz exakt konstant ist. Ergo, nachdem der Hall Sensor ausgelösst hat hast du zwei Sachen zu machen 1.) ICP Wert auslesen, also Takte pro Umdrehung, diesen Wert mitteln mit dem letzten gemessenen Wert 2.) Spalten-Position auf 0 setzen, Timer zurücksetzten und den Timerwert berechnen, also 10000 div/mod 512 Das geht mit jedem Teilerverhältnis exakt, weil wir ja die Differenzen auch exakt ausgleichen. Deine virtuell angezeigten LED-Spalten haben dann einen Fehler in ihrer Position (Winkel) von maximal +1 Takt. Je schneller nun der Timer getaktet wird desto exakter können die LED-Spalten-Positionen (Winkel) eingehalten werden. Aber den Fehler durch die nicht exakt teilbaren Takte pro Umdrehung wirst du immer haben, egal wie schnell du den Timer/AVR taktest. Einzigste Außnahme wäre ein Regelkreis der den Motor synchron zum AVR drehen lässt. Statt also deine Timer-ISR auszugleichen wird die Drehzahl so geregelt das immer exakt 512*x Takte rauskommen pro Umdrehung. Das wäre natürlich das beste. Gruß Hagen
Hallo Hagen, ja Richtig, ne Prop Uhr. ch habe gestern erst mal die Sache mit dem schnelleren Timer getestet. Geht wesentlich genauer, da ich ja nur noch durch 256, bzw. 64 Teile. Daher ist ja auch der "Rundungsfehler" kleiner. Als Nächstes werde ich das mit dem Fehler auf die Spalten aufteilen versuchen und berichten. Übrigens, Hagen, deine Ideen sind ja nie schlecht! Habe ja auch die ansteuerung der HCs von Dir übernommen! Eine Sache habe ich da aber noch: Ich zeige derzeit verschiedene Bilder aus einem externen EEProm an. Nun möchte ich aber auch dynamischen Text und Werte (Drehzahl) anzeigen. Ich habe ein Zahlen und Buschstaben Array (7x5Pixel) im Programmspeicher. Gehen wir mal davon aus, das auf 12 Uhr der HallSensor sitzt. Nun möchte ich in der unteren Hälfte einen Text anzeigen. Wie kann man nun die Verbindung von einem String im Flasch ("Hallo") zu dem Array im Flasch in bezug auf die Spaltenposition hin bekommen? Der nächste Schritt wäre dann, eine dynamische Zahl anzuzeigen. Timer1 Wert, oder OCR2 Wert, was auch immer. Fakt ist: Ich habe die Spalten, und bei einer bestimmten Spalte muß ich ein bestimmtes Byte aus dem Buchstabenarray anzeigen. Gruß Toby
Also als erstes brauchst du einen DisplayRAM. Dafür gibt es 2 Lösungen 1.) Textorientiert, dh. das RAM Speichert nur die ASCII Zeichen die du anzeigen möchtest. Du musst dann live in einer Timer ISR für jede Spalte den richtigen ASCII Code aus dem DisplayRAM holen, die Pixel-Spalte im ASCII Buchstaben ausrechnen und dann in eine Zeichentabelle den ASCII Buchstaben nachschlagen und die Pixel aus diesem Fontzeichen dekodieren. Da du Spaltenweise das Display anzeigst macht es Sinn die Codierung dieser Zeichentabelle anderst aufzubauen als üblich. Du sagstest was von 7x7 Pixel Zeichen. Also 5 Pixel breit und 7 Pixel hoch. Also würde ich 7 Bytes pro Zeichen definieren. Das 1. Byte enthält 7 Pixel senkrecht, die 1. Spalte des ASCII Zeichens. Deine Dekodierung würde dann ganz einfach dieses Byte holen und in diesem wären alle senkrechten Pixel drinnenstehen. 2.) Grafik DisplayRAM. Du benötigst dann Anzahl Display Spalten * Anzahl LEDs an Speicher. Sollte die Anzahl deiner LEDs nicht exakt durch 8 teilbar sein dann rundest du auf. Das vereinfacht die DisplayRAM Addressierung. Rechnen wir mal: Dein Display hat 14 LEDs (monochrom) und soll 512 Spalten haben. Anzeigen kannst du also 512*14 Pixel. Ein Grafik DisplayRAM würde ich auf 512*16 virtuell erweitern, macht also 512*2 Bytes = 1024 Bytes an Grafik RAM der pixelexakt auflösen kann. Du kannst dann beliebige Grafiken anzeigen. Im Textmode mit einem 7x7 Font wirst du 512/(7+1) = 64 Zeichen darstellen. Der DisplayRAM würde nun 64*2 Bytes benötigen, da ja dein Display 2 Zeilig arbeitet. Ich würde aber dann auf 128*2=256 Bytes ausbauen. Das heist dein DisplayRAM ist doppelt so groß wie das was deine Clock anzeigen kann. Nun stell dir vor du schreibst einen Text mit 128 Buchstaben in eine Zeile dieses RAMs rein. Nur die ersten 64 Buchstaben werden angezeigt. Über einen Offset/Variable kannst du aber deiner Timr-ISR mitteilen bei welchem Zeichen er beginnen soll. Wenn du diesen Offset nun periodisch inkrementierst dann beginnt dein Text auf dem Display zu laufen, du hast deine Laufschrift. Dieser Offset setzt sich zusammen aus Offset := DisplayRAM Offset * 8 + Pixeloffset in das ASCII Zeichen. Die untersten 3 Bits dieser Variablen stellen den Pixelspaltenindex in deine Fonttabelle dar. Die Darstellung im Textmode durch deine Timer ISR sähe so aus. Die Timer ISR wird 512 mal pro Umdrehung aufgerufen. In der Timer ISR machst du den Ausgleich der Takte wie oben beschrieben. Wenn dein Hallsensor per ISR auslösst muß folgendes passieren 1.) Takte pro Umdrehung aus ICP holen und / 512 rechnen 2.) Timer zurücksetzten 3.) DisplayZeiger auf DisplayOffset setzen Display-Zeiger ist wieder eine globale Variable. Sie speichert die aktuelle Displayspalte die dargestellt werden soll. Damit kann der Wert darin von 0 bis 511 gehen. In deiner TimerISR die 512mal pro Umdrehung aufgerufen wird muß folgendes passieren: 1.) ASCII := DisplayRam[DisplayZeiger / 8]; 2.) Pixels := FontTabelle[ASCII * 8 + DisplayZeiger mod 8]; 3.) DisplayZeiger := (DisplayZeiger +1) mod 512; 4.) out LED_PORT, Pixels Als erstes holen wir den ASCII Buchstaben aus dem DisplayRAM. Wird benutzen dazu den DisplayZeiger. Da ein Zeichen 8 Pixelspalten hat also DisplayZeiger / 8. Als nächstes laden wir zu diesem ASCII Code aus der Fonttabelle das Datenbyte zur aktuellen Pixelposition. Also Pixels = FontTabelle[ASCII * 8 + DisplayZeiger mod 8]. Da pro ASCII in der Fonttabelle 8 Bytes gespeichert sind, jedes Byte stellt eine Pixelspalte des Zeichens von Links nach rechts dar und das letzte Byte ist immer 0. Deshalb müssen wie ASCII * 8 nehmen und mit DisplayZeiger mod 8 indexieren wir die Pixelspalte in unserem Fontzeichen. Dann noch PixelZeiger +1 inkrementieren und daruaf achten das er nicht > 511 werden kann. Das kann passieren wenn wir zb. den DisplayZeiger in der Hall Sensor ISR nicht auf 0 setzen sondern zb. auf 10. Dein Text würde dann um +10 Pixel gescrollt dargestellt werden. In deiner Timer ISR muß dann nur noch der Code für die Korrektur der Takte mit rein. Bei einem Grafik DisplayRAM entfällt die direkte konvertierung eines ASCII in ein Fontzeichen, das muß dann schon deine Zeichenroutine die in das DisplayRAM schreibt machen. Gruß Hagen
In meinem Beispiel würde also ein Fontzeichen pro ASCII Code 8 Bytes benötigen. Die ersten 7 Bytes sind das Zeichen jeweils Spalte für Spalte kodiert. Das 8. Byte ist immer 0, also ein Leerspalte damit deine Texte nicht zusammen-geklitscht sind. In diesem Falle musst du also immer div/mod 8 rechnen beim Zugriff auf DisplayZeiger. Das kannst du natürlich auch anders lösen. Zb. bei einem 5x7 Font würde ich 6 Bytes pro Zeichen im Font speichern. Um die langsamme Division/Modulo Operation zu vermeiden arbeitet man intern mit zwei globalen Variablen für DisplayZeiger statt nur einer. Inetwa so:
1 | #define Font_Width 6
|
2 | #define ASCII_Count 512 / Font_Width
|
3 | |
4 | volatile uint8_t ASCII_Index = 0; |
5 | volatile uint8_t ASCII_Row = 0; |
6 | volatile uint8_t DisplayRAM[ASCII_Count] = {}; |
7 | |
8 | ISR(TIMER1_COMPARE) { |
9 | |
10 | uint8_t ASCII = DisplayRAM[ASCII_Index]; |
11 | uint8_t Pixels = FontTable[ASCII * 6 + ASCII_Row]; |
12 | LED_PORT := Pixels; |
13 | if (++ASCII_Row >= Font_Width) { |
14 | ASCII_Row = 0; |
15 | if (++ASCII_Index >= ASCII_Count) |
16 | ASCII_Index = 0; |
17 | }
|
18 | |
19 | }
|
20 | |
21 | void SetDisplayOffset(uint16_t PixelOffset) { |
22 | |
23 | ASCII_Index := PixelOffset div Font_Width; |
24 | ASCII_Row := PixelOffset mod Font_Width; |
25 | }
|
26 | |
27 | volatile uint16_t DisplayOffset = 0; |
28 | |
29 | ISR(ICP) { // Hallsensor |
30 | |
31 | // berechne hier OCR1A für Timer1
|
32 | SetDisplayOffset(Display_Offset mod 512); |
33 | }
|
Du kannst nun DisplayOffset periodisch inkrementieren was dann dazu führt das der Text aus dem DisplayRAM[] im Display scrollt. Gruß Hagen
Wer Interesse hat, dem kann ich mal mein Programm schicken, es liest die rundenzahl über einen infrarottransistor und infrarot LED als referenzposition per IPC ein, erzeugt den spaltentakt über einen 8-bit timer(das ist mein aktuelles "problem", da 8 bit ein wenig zu wenig auflösung ist, und man die schritte doch gut sehen kann) Gelöst habe ich die anzeige per DisplayRam Dann habe ich noch eine Zeichentabelle angelegt und eine Funktion, die einen String in das DisplayRam laden kann (die funktion holt sich eben über die zeichen des strings das jeweilige "bitmap" für ein zeichen aus der Tabelle und läd diese ins Displayram) Mein display zeigt eine nicht 2er potenz an zeichen an, deswegen musste ich die 16b/16b dividionsroutine von atmel. Für eine Zahlen zu ASCII conversation wird auch noch eine 8b/8b divisionroutine benötigt (um durch 10 zu teilen, das geht zwar auch etwas schmutziger aber ich hab aus einfachheitsgründen die routine benutzt) Das display besteht aus 8 LEDs. Geplant habe ich eine Version mit mega162, da dieser einer der einzigen (oder sogar der einzige??) controller ist, bei dem nicht der 16bit CTC timer durch den ICP blockiert wird. Das ganze programm ist noch nicht ganz fertig und erst recht nicht aufgeräumt aber nen paar kommentarschnipsel sind drin. (ach ja und es ist in assembler geschrieben und für nen mega8) Wenn jemand interesse hat am programm hat soll er mir eine Nachricht schreiben, ich will das Programm noch nicht hier reinstellen, weil es eben noch so unfertig und unübersichtlich ist.
Wenn deine Display 2-zeilig arbeitet dann benötigst du die Variablen ASCII_Index, ASCII_Row und DisplayOffset jeweils zweimal, eben für jede Zeile eine eigene Version. So kannst du später in beiden Zeilen den Text unabhängig voneinander scrollen lassen. Es gibt natürlich noch mehr Optimierungsmöglichkeiten, zb. den Zugriff auf das DisplayRAM[] musst du nur alle 6 Schritte ausführen. Der Zugriff auf die Fonttabelle (im FLASH) kann auch über Zeiger erfolgen und du sparst damit den Array[] Zugriff. Mein obiger Pseudocode soll also nur das Prinzip verdeutlichen. Achso, bei 6 Pixel breiten Zeichen (5+1 Leerspalte) sollte die Anzahl an Pixelspalten auch ohne Rest durch 6 teilbar sein. 512 ist eine schlechte Wahl. 510 oder 516 ist besser. Gruß Hagen
Hallo Hagen, komme erst jetzt dazu, am Display Ram weiter zu machen. Habe erst noch die Hardware estwas geändert. Wie erstelle ich denn in WinAVR ein Display Ram, also eine Variable, mit einzelnen 512 Bytes? "volatile uint8_t DisplayRAM[ASCII_Count] = {};" verstehe ich nicht so ganz, da die {} doch leer sind. Da müssten doch meine 512 Bytes drin stehen, nicht? Gruß Toby
Jo da stehen ASCII_Count bytes drinnen und sie werden durch den Startup Code auf 0 initialisiert. 512 Bytes benötigst du wenn du zb. 512 Spalten a 8 Pixel hättest. Das ist das ein grafisch arbeitendes Display. Mein obigen Ausführungen beschreiben hauptsächlich ein Text orientiertes Display. Dann werden im DisplayRAM[] nicht die Pixel sondern die ASCII Zeichen gespeichert. Wenn nun ein ASCII Zeichen 7x8 Pixel hätte und wir noch +1 Leerspalte vorsehen dann ergibt sich ein DisplayRAM[] mit 512 / (7+1) = 64 Bytes. Also 64 Bytes statt 512 Bytes und dafür ein kleines bischen mehr Aufwand in deiner Timer-Spalten-ISR um das ASCII Zeichen über die Font Tabelle zu dekodieren. Es hängt von dir ab was du möchtest. Gruß Hagen
Hallo Hagen, ich möchte ein Text Ram haben, der aber dynamisch sein soll. Also drin stehen soll z.B.: "Drehzahl: 0000 U/min Spannung: 00,00 Volt ". (Das sind 64 Zeichen.) Die Nullen sollen aber die Werte der Drehzahl und der Spannung vom AD Wandler sein. Ich verstehe noch nicht, wie den Ram beschreibe und an bestimmten Stellen halt die Werte eintragen soll. Gruß Toby
Hallo nochmal, ich habe nun fast alles in meinem Programm untergebracht. Leider bekomme ich bei diesen beiden Variablen uint8_t ASCII = DisplayRAM[ASCII_Index]; uint8_t Pixels = FontTable[ASCII * 6 + ASCII_Row]; immer den Fehler main.c:257: error: initializer element is not constant main.c:258: error: initializer element is not constant Kann jemand weiter helfen? Ach ja, ich habe die Variablen Global definiert, da ich Pixels noch andersweitig gebrauche, und nicht nur im SpaltenInt. Gruß Toby
Hallo, ich habs schon hin bekommen. // Var init volatile uint8_t ASCII = 0; volatile uint8_t Pixels = 0; // Spalten Int ... ASCII = DisplayRAM[ASCII_Index]; Pixels = pgm_read_byte_near(FontTable+(ASCII * 8 + ASCII_Row)); ... nun setze ich ein Flag, das die Spalte angezeigt werden soll, und arbeite das Flag im Main ab. Jetzt habe ich noch 2 Fragen. 1. Ich habe meine Font Daten jetzt so organisiert: prog_char FontTable[]={ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // 0x00 0x00,0x00,0x00,0x7C,0xA2,0x8A,0xA2,0x7C, // 0x01 0x00,0x00,0x00,0x7C,0xD6,0xF6,0xD6,0x7C, // 0x02 ... ... Greife so drauf zu: Pixels = pgm_read_byte_near(FontTable+(ASCII * 8 + ASCII_Row)); Wie greife ich auf folgendes Arrays zu: prog_char FontTable[256][8]={ {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x00 {0x00,0x00,0x00,0x7C,0xA2,0x8A,0xA2,0x7C}, // 0x01 {0x00,0x00,0x00,0x7C,0xD6,0xF6,0xD6,0x7C}, // 0x02 Ich dachte so: Pixels = pgm_read_byte_near(FontTable[ASCII][ASCII_Row]); geht aber nicht. ;-) Meine Überlegung: [ASCII] ist ja das ASCII Zeichen 0-256 also die Zeilen im Array. [ASCII_Row] sind dann die Spalten im Array, 0-7. 2. Wie bekomme ich in diesem String: volatile uint8_t DisplayRAM[ASCII_Count] = {" Drehzahl: 1500 U/min Spannung: 7,20 Volt "}; Die "echten" Werte zur Drehzahl und Spannung rein? Also, wie kann ich eine Integer Zahl als ASCII in diesen String einfügen? Das wärs dann auch schön ;-) Gruß Toby
Tobias Tetzlaff wrote: > Danke Hagen, > > und die 1. Frage? > > Gruß Toby Ich denke mal, dass das die Antwort auf deine erste Frage war ;) Aber ganz richtig ist das doch nicht. Oder irre ich? Ich täts so machen:
1 | Pixels = pgm_read_byte_near(&FontTable[ASCII][ASCII_Row]); |
2 | // ^
|
Hallo, ja, ist schon richtig, ich meinte ja auch "was ist mit der 2. Frage?" Sorry, mein Fehler. Zur 1. Frage: Also, ich bekomme hier: prog_char FontTable[256][8]={ {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x00 {0x00,0x00,0x00,0x7C,0xA2,0x8A,0xA2,0x7C}, // 0x01 {0x00,0x00,0x00,0x7C,0xD6,0xF6,0xD6,0x7C}, // 0x02 ... ... Pixels = pgm_read_byte_near(&FontTable[ASCII, ASCII_Row]); diesen Fehler: main.c:515:59: error: macro "pgm_read_byte_near" passed 2 arguments, but takes just 1 main.c: In function '__vector_3': main.c:515: error: 'pgm_read_byte_near' undeclared (first use in this function) main.c:515: error: (Each undeclared identifier is reported only once main.c:515: error: for each function it appears in.) und hier: Pixels = pgm_read_byte_near(&FontTable[ASCII][ASCII_Row]); den Fehler: main.c:516: error: 'FontTable' undeclared (first use in this function) main.c:516: error: (Each undeclared identifier is reported only once main.c:516: error: for each function it appears in.) Obwohl es ja ohne weitere Änderungen mit dieser Zeile Funktioniert: prog_char FontTable[]={ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // 0x00 0x00,0x00,0x00,0x7C,0xA2,0x8A,0xA2,0x7C, // 0x01 0x00,0x00,0x00,0x7C,0xD6,0xF6,0xD6,0x7C, // 0x02 ... ... Pixels = pgm_read_byte_near(FontTable+(ASCII * 8 + ASCII_Row)); Gruß Toby
Hallo nochmal, ich habe den fehler gefunden. Habe dummerweise bei dieser Zeile #include "6x8_vertikal_MSB_11.h" am Ende die " vergessen. Daher war auch 'FontTable' undeclared. Jetzt gehts auch hiermit: Pixels = pgm_read_byte_near(&FontTable[ASCII][ASCII_Row]); Hiermit: Pixels = pgm_read_byte_near(&FontTable[ASCII, ASCII_Row]); gehts nicht, da man nur ein Argument beim pgm_read_byte_near übergeben muß, nicht 2. Gruß Toby
Hi schon wieder. Wie kann ich denn nun, wenn ich den Display Ram so initialisiere: volatile uint8_t DisplayRAM[ASCII_Count] = {"Drehzahl: 1500 U/min Spannung: 7,20 Volt "}; etwas anderes, wärend des Programmablaufs rein schreiben, so das z.B. das "1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz,:" drin steht? Gruß Toby
Hallo, ich habe schon etliche Versionen ausprobiert, entwerder meckert WinAVR, oder es ändert sich nix. Beispiele: DisplayRAM[ASCII_Count] = "1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz,:"; DisplayRAM[ASCII_Count] = {"1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz,:"}; DisplayRAM = "1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz,:"; DisplayRAM = {"1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz,:"}; ... ... Alles ohne Erfolg, ich bekomme es einfach nicht hin, das Array anders zu beschreiben. Muß Ich vielleicht jedes Byte einzeln schreiben? So, wie ich auch jedes Byte einzeln auslesen muß? Gruß Toby
Hallo, ich habs nun, der Display Ram lag ja nicht im Ram, sondern im Flash! Jetzt gehts so, wie ich will, das ich auch veränderliche Werte anzeigen kann. Danke nochmal an Hagen, obwohl ich die Spaltenkorrektur immer noch nicht implentiert habe, danke an alle anderen, die mir mal mehr, mal weniger mit Rat und Tat zur Seite standen! Hier das Ergebnis: Ein ca. 6MB großes Video. http://www.tobytetzi.de/NightVision/NV2.0Rev7.1.mpg Gruß Toby
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.