mikrocontroller.net

Forum: Mikrocontroller und Digitale Elektronik AVR: Umdrehung / Spalten


Autor: Tobi (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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;
    }
}

Autor: Andreas K. (a-k)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Andreas K. (a-k)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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".

Autor: Andreas K. (a-k)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Tobi (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo Andreas,
leider ist der Hall Sensor schon am Ext Int angeschlossen, dann kann ich 
den Input Capture doch garnicht nutzen, oder?

Gruß Toby

Autor: Andreas K. (a-k)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Tobi (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

Autor: Andreas K. (a-k)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Tobi (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo Andreas,

also, was soll ich jetzt genau machen?

Gruß Tobi

Autor: Andreas K. (a-k)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hab ich doch geschrieben.

Autor: Tobi (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

Autor: Andreas K. (a-k)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> 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.

Autor: Gast (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

Autor: Andreas K. (a-k)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> 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?

Autor: Tobi (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

Autor: Tobi (Gast)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
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

Autor: Andreas K. (a-k)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
@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.

Autor: Hagen Re (hagen)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
>>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

Autor: Tobi (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

Autor: Hagen Re (hagen)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

Autor: Tobi (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

Autor: Hagen Re (hagen)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

Autor: Hagen Re (hagen)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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:
#define Font_Width    6
#define ASCII_Count   512 / Font_Width 

volatile uint8_t ASCII_Index = 0;
volatile uint8_t ASCII_Row   = 0;
volatile uint8_t DisplayRAM[ASCII_Count] = {};

ISR(TIMER1_COMPARE) {

  uint8_t ASCII  = DisplayRAM[ASCII_Index];
  uint8_t Pixels = FontTable[ASCII * 6 + ASCII_Row];
  LED_PORT := Pixels;
  if (++ASCII_Row >= Font_Width) {
    ASCII_Row = 0;
    if (++ASCII_Index >= ASCII_Count) 
      ASCII_Index = 0;
  }

}

void SetDisplayOffset(uint16_t PixelOffset) {
  
  ASCII_Index := PixelOffset div Font_Width;
  ASCII_Row   := PixelOffset mod Font_Width;
}

volatile uint16_t DisplayOffset = 0;

ISR(ICP) { // Hallsensor

  // berechne hier OCR1A für Timer1
  SetDisplayOffset(Display_Offset mod 512); 
}

Du kannst nun DisplayOffset periodisch inkrementieren was dann dazu 
führt das der Text aus dem DisplayRAM[] im Display scrollt.

Gruß Hagen

Autor: Hauke Radtki (lafkaschar) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Hagen Re (hagen)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

Autor: Tobias Tetzlaff (tobytetzi)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

Autor: Hagen Re (hagen)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

Autor: Tobias Tetzlaff (tobytetzi)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

Autor: TobyTetzi (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

Autor: TobyTetzi (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

Autor: Hagen Re (hagen)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
pgm_read_byte_near(&FontTable[ASCII, ASCII_Row]);

Gruß Hagen

Autor: Tobias Tetzlaff (tobytetzi)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Danke Hagen,

und die 1. Frage?

Gruß Toby

Autor: Simon K. (simon) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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:
Pixels = pgm_read_byte_near(&FontTable[ASCII][ASCII_Row]);
//                          ^

Autor: TobyTetzi (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

Autor: TobyTetzi (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

Autor: TobyTetzi (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

Autor: TobyTetzi (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

Autor: Tobias Tetzlaff (tobytetzi)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.