Habe heute malwieder gut was geschafft ... Mein derzeitiges Projekt: Stromanzeige für die Hifi-Anlage im KFZ Bisher habe ich eine 4x40 Anzeige angesteuert von einem Atmega8 (auf STK500). Auf dem Display werden die Ströme angezeigt, Momentanwert (etwas gemittelt), Peak und ein kleiner Bargraph. Das ganze für 4 Kanäle. Die Datenaufbereitung geschieht mittels vier Stromsensoren von LEM welche vor den jeweiligen Endstufen sitzen (bis 500A), welche über eine Bürde gute 5V bei 500A primärseitig erzeugen. Derzeitiger Programmablauf sieht wie folgt aus: # Timerint - Einlesen eines Kanals - Peak vergleichen bzw. neu schreiben - aktuellen Wert zum Summierregister addieren -- das ganze für alle 4 Kanäle nacheinander --- alle 4 Kanäle werden zusammen 64 mal gesampled und aufsummiert - Mittelwerte > BCD und an LCD Ausgeben, alle 4 Kanäle nacheinander - Peaks > BCD und an LCD ausgeben - bargraph berechnen und ausgeben (ebenfalls für jeden kanal) Mein Problem ist derzeit: für den Bargraph ist die Anzeige zu langsam, für den Mittelwert zu schnell zum ablesen. Welche Mögleichkeiten gibt es die Aufgaben geschickter zeitlich zu verteilen? Am liebsten wäre mir: - kontinuierliches Auslesen der Kanäle und mit Peak vergleichen - Werte für Mittelwertbildung in einem Ringspeicher(LIFO) - LCD Ausgabe so schnell wie möglich (für saubere Bar-Graph-Anzeige) - Mittelwertanzeige etwas langsamer, damit man das auch besser ablesen kann Vor einer kon. Auswertung im Mainloop bin ich bisher zurückgeschritten, weil ich angst habe, dass der Timer dann vllt. in irgendeine Berechnung eingreift und mir dann teils nur müll ausgibt bzw. verfälschte werte. Im Anhang ist mal ein Beispiel, wie das derzeit aussieht ... die Werte sind aber nicht REAL ;)
Den ADC würde ich im Free-Run-Mode laufen lassen und im Timer-Interrupt reiherum die ADC-Werte einlesen und dabei gleitende Mittelwerte über 256 Messungen bilden. ADC-Takt und Timer-Intervall werden so aufeinander abgestimmt, dass zwischen den Abfragen mindestens zwei Wandlungen stattgefunden haben, von denen die erste ignoriert wird. Die Timer-ISR macht dann also Folgendes: - ADMUX einlesen, um die aktuell gemessene Quelle zu bestimmen, - Pointer auf der Quelle entsprechende RAM-Zelle positionieren, - ADC-Mittelwert und Nachkommawert aus RAM holen, - 1/256 des Mittelwertes davon subtrahieren (Byteshifting), - ADC-Messwert einlesen und mit 1/256 Wertigkeit (Byteshifting) addieren, - Mittelwert und Nachkommawert zurück ins RAM, - Messquelle erhöhen und Zählumfang begrenzen, - neue Messquelle in ADMUX schreiben Die Mainloop kann dann reiherum die vier Messwerte aus dem RAM holen, in ASCII umwandeln und ausgeben und nach jedem Messwert in ASCII alle 4 Messwerte als Bargraph. Damit gibt es ein schnelles Bargraph-Update, aber eine langsamere Textausgabe. Falls der Text noch zu schnell kommt, dann eben zweimal (oder öfter) alle Bargraphen ausgeben, ehe der nächste Text ausgegeben wird. ...
die Auflösung des Bargraps ist einfach zu gering drum erscheint es dir so langsam. Du könntest ja eigene Zeichen aufs LCD ausgeben und so die Auflösung um das 5fache erhöhen. Hier mal ein Beispiel von 5 Zeichen die du selbst generieren müsstest. Auflösunf ist etwas klein ab besten abspeichern und dann mit einem Programm öfnnen mit dem du es vergrößern kannst, falls dein Browser das nicht kann.
Zusätzlich könntest du noch einen Peakholder anzeigen lassen der alle ~1s refreshed wird. Aber ist es wirklich nötig die Anzeige der Stromaufname so schnell wie ein dB-Meter zu machen?
@Hannes Danke für die Aufschlüsselung ... das mit dem Pointer muss ich mir erstmal anschauen, lade das derzeit alles manuell aus dem RAM und in den RAM an fest vorgeschriebene Stellen. Wobei ich langsam schon mit der optimierung anfange. Derzeit habe ich eh nicht mehr ganz so große Probleme mit dem LCD, da das jetzige schon per UART angesteuert wird .. was vieles vereinfacht. Leider nur bis max. 19200 Baud, was bei 4x40 Zeichen + Steuerbefehle dann nicht wirklich viel ist. Vllt sollte man die Darstellung separieren ... bargraph so oft wie möglich aktualisieren, Peak-Werte nur neu schreiben, wenn neue sind und die aktuellen werte nur alle 2-3 mal pro Sekunde. @Thomas Nein, es läuft tatsächlich etwas sehr langsam ... selbst wenn ich ihn auf 40 Symbole aufpluster wirkt es ansatzweise lahm. @Johannis Peak-Hold ist angedacht, muss ich aber sehen. Bei 8 Zeichen Bargraph meiner Meinung nach nicht wirklich sinnvoll. Bei nem grafikschen Display wäre dann dann schon eher interessant. PS: Die Bragraphen habe ich gestern noch etwas angepasst, an die tatsächlichen Wertebereiche ... Subwoofer: bis 128 A HT, MT und Charge: bis 64 A Damit bekomme ich dann wenigstens was angezeigt. Meine Peak-Messwerte sind derzeit woofer: 110 A mt: 39 A ht: 39 A charge: 39 A
Erik D. wrote: > @Hannes > > Danke für die Aufschlüsselung ... das mit dem Pointer muss ich mir > erstmal anschauen, lade das derzeit alles manuell aus dem RAM und in den > RAM an fest vorgeschriebene Stellen. Das ist eigentlich gaaaanz einfach... Für gleitenden Mittelwert über 256 Messungen setze ich gern den hier vorgestellten Algorithmus ein: Beitrag "Re: ADC Werte sauber aufs display bringen" Der Code ist aber nur für einen ADC-Kanal gedacht, weshalb die drei Bytes (Mittelwert H, Mittelwert L und Nachkomma) auch in drei Registern liegen. Bei mehreren ADC-Kanälen muss man diese ins RAM auslagern, sonst werden die Register knapp. Es sind (bei 10 Bit Auflösung) 3 Bytes je Kanal, bei 8 Bit Auflösung nur 2 Bytes. Für 4 Kanäle (möglichst ADC0, ADC1, ADC2 und ADC3, denn da kann man schön den Zählumfang mit ANDI zahl,3 begrenzen) und 10 Bit Auflösung brauchst Du 12 Bytes RAM. Um die zu reservieren brauchst Du ja im DSEG ein Label: .dseg adc_werte: .byte 12 ;12 Bytes für ADC-Werte reservieren Dieses Label ist Deine Basisadresse für den Pointer. Du lädtst also den Z-Pointer oder Y-Pointer (der X-Pointer taugt dazu nix, da er kein LDD/STD unterstützt) mit dem Label: ldi zl,low(adc_werte) ;Pointer auf Anfang ldi zh,high(adc_werte) ;des Arrays Dann addierst Du die Nummer des ADC-Kanals dazu: add zl,nummer ;Nummer dazu, adc zh,null ;evtl. Übertrag (Carry) auch Nun zeigt je nach Nummer (0 bis 3) der Pointer auf eines der 4 ersten Bytes des Arrays. Der Einfachheit halber haben wir vereinbart, dass in den ersten 4 Bytes das H-Byte aller 4 Kanäle steht, dann kommen die L-Bytes aller 4 Kanäle und danach die Nachkommawerte aller 4 Kanäle. Warum?? - Der AVR hat die Befehle LDD und STD, mit denen mit nur einem Pointer und einem als Konstante angegebenem Offset mehrere Bytes adressiert werden können. Mit: ldd temp1,z+0 ;H-Byte holen, ldd temp2,z+4 ;L-Byte holen, ldd temp3,z+8 ;Nachkomma holen holst Du alle drei Bytes über einen Pointer (mit ADC-Kanal als Index) in drei Register, um sie (gemäß Link) zu verarbeiten. Der Pointer wurde dabei nicht verändert und kann nach der Verarbeitung wieder zum Zurückschreiben benutzt werden: std z+0,temp1 ;H-Byte, std z+4,temp2 ;L-Byte und std z+8,temp3 ;Nachkomma zurück ins RAM > Wobei ich langsam schon mit der optimierung anfange. Naja, bis zur wirklichen Optimierung ist noch ein weiter Weg... > > Derzeit habe ich eh nicht mehr ganz so große Probleme mit dem LCD, da > das jetzige schon per UART angesteuert wird .. was vieles vereinfacht. > Leider nur bis max. 19200 Baud, was bei 4x40 Zeichen + Steuerbefehle > dann nicht wirklich viel ist. Da kann aber ein herkömmlicher LCD-Treiber schneller sein. Man darf nur nicht direkt in das LCD schreiben wollen. Für ein 4x40-LCD nutze ich 160 Bytes "Bildschirmspeicher" im RAM des AVRs. Die LCD_Data-Routine wirft die auszugebenden Zeichen einfach (an die richtige Position) in diesen Bildschirmspeicher. Im Hintergrund (entweder im Timer-Int, oder durch Timer-Int synchronisiert in der Mainloop) wird dann alle etwa 1 ms ein Zeichen aus dem Bildschirmspeicher an das LCD geschaufelt. Das sind 6 Updates pro Sekunde, so schnell können wir nicht lesen. Wenn das zu langsam für die Bargraphen ist, dann lässt sich die Update-Routine auch so umschreiben, dass sich eine der oberen drei Zeilen mit der unteren Zeile abwechselt. Das ergibt dann 12 Updates pro Sekunde für die Bargraphen und 4 Updates pro Sekunde für die oberen drei Zeilen. > > Vllt sollte man die Darstellung separieren ... bargraph so oft wie > möglich aktualisieren, Peak-Werte nur neu schreiben, wenn neue sind und > die aktuellen werte nur alle 2-3 mal pro Sekunde. Ja, sicher, wobei ich mir über die Peaks noch keine Gedanken gemacht habe. Die müsste man irgendwie bei der Mittelwert-Berechnung mit behandeln. Z.B. für jeden Kanal einen weiteren Wert im RAM halten, diesen bei jeder Messung herunterzählen und auf den aktuellen Messwert anheben, falls dieser größer war. Somit erfasst man damit auch Spitzen, die in den Mittelwert gar nicht einfließen. Möglichkeiten gibt es viele. Ich hoffe, Dir ein paar Denkanstöße gegeben zu haben. ...
Oh vielen dank .. sehr ausführlich ... habe das eben mal überflogen und verstehe noch Bahnhof aber werd das nachher mal schritt für schritt durchgehen. Die peaks ermittle ich derzeit nach jeder Einzelmessung. D.h. ADC einlesen, Peaks aus RAM holen, vergleichen, ggf. neu schreiben und normal weitermachen.
Erik D. wrote: > Oh vielen dank .. sehr ausführlich ... habe das eben mal überflogen und > verstehe noch Bahnhof aber werd das nachher mal schritt für schritt > durchgehen. > > Die peaks ermittle ich derzeit nach jeder Einzelmessung. D.h. ADC > einlesen, Peaks aus RAM holen, vergleichen, ggf. neu schreiben und > normal weitermachen. Aha, dann kann das die Mittelwertbildung nebenher mit erschlagen. Schreib' mal, ob Du mit 8 Bit ADC-Auflösung auskommst, oder ob Du unbedingt die vollen 10 Bit brauchst. Denn am Bargraph kannst Du die 10 Bit sowiso nicht darstellen, ja nichtmal die 8 Bit. Begrenzung auf 8 Bit macht die Sache nämlich einfacher und schneller. ...
Derzeit nutze ich die vollen 10 Bit, summiere 64 Werte und das passt dann alles in zwei Register :) Der Bargraph hat ja nur sogesehn 3 Bit ;D Die erste Zeile ist eh statisch und muss auch nur einmal geschrieben werden, somit spare ich mir damit schon einen ganzen String zu senden :)
Erik D. wrote: > Derzeit nutze ich die vollen 10 Bit, Ich wollte eigentlich nicht wissen, ob Du sie nutzt, sondern ob Du sie brauchst. > summiere 64 Werte und das passt > dann alles in zwei Register :) Das ist eine Methode, die ich (ich persönlich) nicht besonders mag. Denn man bekommt nur alle 64 Messungen einen neuen Wert, wobei dieser auch noch sehr stark vom vorherigen Wert abweichen kann. Und wenn man es dann noch mit Busywait macht, wie im Tutorial vorgeschlagen wurde, dann hat der AVR seine ABM, die ihn schon fast auslastet. Ich mache sowas lieber "im Vorbeigehen" und nutze die Pausen dazwischen für andere Arbeiten. Aber es soll Jeder machen, was er für richtig hält. > > Der Bargraph hat ja nur sogesehn 3 Bit ;D > > Die erste Zeile ist eh statisch und muss auch nur einmal geschrieben > werden, somit spare ich mir damit schon einen ganzen String zu senden :) ...
Naja, wenn ich auf 8 Bit runtergehe wäre das sicherlich auch kein Verlust. Hatte eh vor die Vref auf 2,5 bzw. 2,56 Volt runterzusetzen, da der bereich bis 250 A sicher ausreichen sollte. Die Bürde an den Stromwandler ist so ausgelegt, dass ich bei 500 A -> 5 V erhalte. Ich weiß jetzt nicht, ob es alternativ möglich wäre den Bürdenwiderstand weiter zu erhöhen um z.B. schon bei 250A meine 5V zu erhalten, laut Datenblatt ist meine Bürde so ziemlich am ausreizen des machbaren. Der Wandler setzt den primärstrom 2000:1 in einen Sekundärstrom um. Bei 500A wären das 250 mA, dieser wird über eine Bürde (Widerstand) geschickt um eine proportionale Spannung zu erhalten. Derzeit sind diese Bürden bei mir mit 20 Ohm bemessen, was bei 250 mA Stromfluss 5 V ergibt. Laut Dtaenblatt sind für meinen Strombereich, bei der Versorgungsspanung von +/- 15V maximal 25 Ohm als Bürde angegeben. Ja, derzeit arbeitet mein Programm noch mit diesem Busy-Flag und wartet jeweils auf ne fertige Wandlung. Dass man diese Zeit sinnvoller nutzen kann habe ich mir auch schon gedacht. Vllt ist es sinnvoller die AD-Wandlung zu starten, dann die Arbeit zu machen, welche zwischen den Wandlungen stattfindet und dann den neuen wert abrufen und weitermachen.
Erik D. wrote: > Naja, wenn ich auf 8 Bit runtergehe wäre das sicherlich auch kein > Verlust. Das macht Einiges einfacher... > Der Wandler ... Da halte ich mich raus. Stromwandler waren für mich vor 40 Jahren ein Thema, aber die konnten nur Wechselstrom und brauchten eine sehr niederohmige Bürde, die mit 5A belastet wurde... ;-) > Ja, derzeit arbeitet mein Programm noch mit diesem Busy-Flag und wartet > jeweils auf ne fertige Wandlung. Dass man diese Zeit sinnvoller nutzen > kann habe ich mir auch schon gedacht. Vllt ist es sinnvoller die > AD-Wandlung zu starten, dann die Arbeit zu machen, welche zwischen den > Wandlungen stattfindet und dann den neuen wert abrufen und weitermachen. Vielleicht ginge in Deinem Fall ja Folgendes: Der ADC läuft im Free-Run-Mode ohne Interrupt durch. Du rechnest Dir (anhand Vorteiler und Wandlungsdauer) aus, wie lange es dauert, zwei Wandlungen durchzuführen und rundest diese Zeit etwas auf (bis auf knapp 3 Wandlungen). Dies ist nun das Intervall für einen Timer, der nun im Prinzip das ganze Programm synchronisiert. In dessen ISR setzt Du einen Merker (Bitvariable), der dem Hauptprogramm mitteilt, dass es "Zeit ist", mal was Anderes zu tun. Im Hauptprogramm läuft als Schleife die LCD-Ausgabe. Diese hat prinzip-bedingt ein Busywait (Abfrage, ob LCD (oder UART) bereit ist). Immer vor diesem Busywait prüfst Du Deinen Merker und verzweigst bei gesetztem Merker zu einem Unterprogramm, das den Merker wieder löscht und den ADC ausliest (mit Mittelwert- und Peak-Aufbereitung). Es können auch noch Berechnungen stattfinden, falls das nötig ist. Somit hast Du im günstigsten Fall die Arbeit gemacht, ehe das LCD wieder bereit ist, im schlechtesten Fall muss das LCD eben etwas länger warten. Die Schleife im Hauptprogramm organisierst Du am besten so, dass Du wechselweise die Zahlen eines Kanals und die Bargraphen aller Kanäle ausgibst, also etwa so (Pseudocode): Pointer=Basis (Datensatz 0) mainloop: rcall printwerte rcall printbargraph adiw Pointer,1 if pointer > Basis+4 then Pointer=Basis rjmp mainloop printwerte: LCD-Position für Wert aus Datensatz holen und an LCD schicken ADC-Mittelwert und Nachkomma (zusammen 16 Bit) aus Datensatz holen, rcall print16 LCD-Position für Peak aus Datensatz holen und an LCD schicken Peak aus Datensatz holen, 0 als Nachkomma, ergibt auch 16 Bit ;-) rcall print16 ret printbargraph: LCD-Position auf Beginn Bargraph 0 Peak0 absolut aus RAM holen rcall machgraph LCD-Position auf Beginn Bargraph 1 Peak1 absolut aus RAM holen rcall machgraph LCD-Position auf Beginn Bargraph 2 Peak2 absolut aus RAM holen rcall machgraph LCD-Position auf Beginn Bargraph 3 Peak3 absolut aus RAM holen rcall machgraph ret machgraph: Erstellung der ASCII-Zeichen wie bisher, rcall lcd_data für jedes Zeichen ret print16: durch Subtraktionsmethode in ASCII-Ziffern zerlegen, wenn eine Ziffer da ist, rcall lcd_data, wenn alle Ziffern durch sind, dann ret. lcd_data: Merker fragen, ob gültiger ADC-Wert vorliegt wenn ja, rcall holadc LCD noch beschäftigt? - wenn ja, rjmp lcd_data LCD-Ausgabe wie auch immer... ret holadc: cbr merker,1<<adcda ;Merker löschen, Job wird ja erledigt push zl ;Pointer sichern push zh ldi zl,low(werte) ;Pointer auf Anfang Array ldi zh,high(werte) in temp1,admux ;akt. Messquelle holen andi temp1,3 ;nur die Messquelle stehen lassen add zl,temp1 ;als Index auf Array adc zh,null ;zum Pointer addieren in temp3,adch ;ADC-Wert H holen inc temp1 ;Messquelle erhöhen andi temp1,3 ;auf 0 bis 3 begrenzen ori temp1,muximmer ;Konstante mit Bits für Referenz und adlar dazu out admux,temp1 ;Messquelle ist umgeschalten, temp1 wieder frei ldd temp1,z+8 ;Peak holen cp temp1,temp3 ;ist neuer Wert > peak? brcc holadc1 ;nein... mov temp1,temp3 ;ja, übernehmen holadc1: dec temp1 ;Peak verringern brcc holadc2 ;Unterlauf? - nein clr temp1 ;ja, zurück auf 0 holadc2: std z+8,temp1 ;neuen Peak speichern, temp1 wieder frei ldd temp1,z+0 ;Mittelwert holen ldd temp2,z+4 ;Nachkomma holen sub temp2,temp1 ;1/256 rausnehmen sbc temp1,null ;natürlich mit Übertrag add temp2,temp3 ;1/256 vom Neuwert reinlegen adc temp1,null ;Übertrag (im Carry) natürlich auch pop zh ;Pointer wiederherstellen pop zl ret ;fertig Im SRAM anzulegendes Array: werte: .byte 20 - 4 Bytes Mittelwert - 4 Bytes Nachkomma - 4 Bytes Peak - 4 Bytes LCD-Position Stromausgabe (Werte per Init erzeugt) - 4 Bytes LCD-Position Peakausgabe (Werte per Init erzeugt) Der Code ist "aus dem Hut" geschrieben, also nicht geprüft, und kann daher Fehler enthalten. Er sollte aber verständlich machen, was ich meine. ...
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.