Guten Abend,
Ich hab ein Problem mit meinem Assembler Programm.
Die Aufgabenstellung:
Spannungswert über den integrierten AD Wandler (von einem 8051)
auslesen. Dieser Wert soll mit Hilfe eines AD7801BR die Periode von
einer Sinus Welle bestimmen. Dass ganze wird auf einer MCLS Platform
programmiert (Siemens 80535) und mit Hilfe eines Osziloskop validiert
(siehe Mikro2Schematics.png). Stichwort Validierung, es geht um ein
Projekt für ein Lehrveranstaltung die diese Woch endet, Abgabe,
verdammt!
Bis jetzt, können wir die Spannung berechnen (d.h.: Ausgabe auf LCD) und
eine Sinus-Welle ausgeben (mit konstanter Periode). Jedoch hier bei gibt
es schon ein Problem, die Welle ist nicht kontinuierlich und bricht
immer wieder ab. (siehe Foto ResultOscilM2Constant.jpg)
Wenn man jetzt probiert den BCD packed, oder ASCII Wert von der Spannung
einzufügen (als Variabel für die Periode - unten im code "magic
happens"), kommt nicht mehr viel brauchbares raus. (siehe Foto
ResultOscilM2Var1ano.jpg).
Ich denke es hat was mit der Timing/interruptroutine zutun und/oder mit
dem Datentyp wenn man den Wert an die Sinus Funktion übergibt.
Jede Hilfe und Anregung (ok, Darstellung ist nicht ganz exakt d.h.: Pin
Belegung Spannungseingang) wäre nett. Herzlichen Dank
Also, wenn ich das jetzt richtig verstehe, dann soll das Programm über
einen externen DAC einen Sinus ausgeben und über den internen ADC die
ausgegebene Spannung messen? Ist das soweit richtig?
Was mir bei einem extrem schnellen Überflug nicht gefällt ist, dass in
der Timer-2-ISR Funktionen für's Display aufgerufen werden.
Desweiteren stimmen die Kommentare MINDESTENS der Main-Funktion nicht
mehr. Außerdem frage ich mich, warum bei der Ausgabe der Wert aus der
Tabelle gelesen wird, dann der DAC bedient wird, und dann erst der Port
5 (entgegen dem Port 1 aus dem entsprechenden Kommentar) geschrieben
wird -> das könnte bereits den Fehler der Ausgabe erklären.
Du solltest das Programm anders gliedern:
Mach eine StateMachine im Main, in der du nacheinander die jeweiligen
Aufgaben abklapperst, also Sinus ausgeben, Messwert einlesen,
Displayausgabe. Zeitliche Verzögerungen solltest du mit einem Timer
machen und nicht über Verzögerungsschleifen (dafür wurden Timer
schließlich erfunden).
Die StateMachine wechselt immer dann zum nächsten Status, wenn der
aktuelle abgearbeitet ist.
Ralf
Das geht natürlich gar nicht. Die Routinen MESURER und VISUALISER
zerstören den Akku, ohne das du ihn vorher mit push acc sicherst und mit
pop acc am Ende der ISR wiederherstellst. Desgleichen das PSW, das du
unbedingt retten musst.
Ich halte es beim MCS51 eigentlich so, das ich auch während einer ISR
auf eine andere Registerbank schalte. Damit musst du die anderen
Register nicht retten. Für A,B und PSW kommst du aber nicht darum herum.
Ich tendiere auch zu Ralfs Lösung. Nimm den Timer für die Sinuserzeugung
und die Hauptschleife für alles andere. Dann bist du die zeitraubende
ISR los und der Sinus steht im Timing sehr stabil.
Woow, es gibt noch Assembler Helden!
Ja, der interne ADC misst Spannung. Dieser Wert bestimmt die Frequenz
der Sinus Welle der über die externe DAC geformt wird. Mit der Pin
Belegung muss ich überprüfen. Mit der StateMachine hatte ich auch schon
als Gedankensprung, jedoch war es an der Umsetzung gescheitert.
Ich schaue mir dass ganze jetzt noch einmal gründlich und probiere es
morgen umzusetzen.
Vielen Dank für die Tips.
Matthias Sch. schrieb:> clr w ;> setb w ; and back to 1, so that a> setb cs ; new value can be accepted> mov p5,a ; Output to Port 1
Ohne deine Hardware zu kennen, behaupte ich mal, dass diese Zeilen
falsch sind. Es wäre für mich das erste IC, bei dem die Daten erst
nach dem /WR Impuls angelegt werden. Sollte es nicht eher so sein:
clr w ;
mov p5,a ; Output to Port 1
setb w ; and back to 1, so that a
setb cs ; new value can be accepted
Naja, während WR schon wahr ist, die Daten anzulegen, ist ja noch
schlimmer. Max' Originalprogramm funktioniert deswegen, weil ja, ausser
beim allersten Schreibvorgang, die Daten des vorherigen Umlaufs noch an
P5 liegen. Deswegen habe ich davon nichts erwähnt.
Dein Vorschlag hingegen geht gar nicht, wenn der DAC die Daten mit der
fallenden WR Flanke übernimmt. Entweder setzt man P5 also vorher und
lässt dann CS und WR folgen oder man lässt alles so, wie es ist, dann
liegen die Daten eben noch länger an P5, bevor sie übernommen werden.
Ich habe heute morgen probiert die Interrupt Routine umzuschreiben rsp.
zu löschen.
ISR_T2 ist entfernt. Das Programm läuft sogar noch... Es war sogar
möglich mittels Spannungseintstellung die Ausgabe am Oscilloskop zu
verändern (NoISR_5Voltage und NoISR_0Voltage). Beim 2 Durchlauf des
Versuches gab es dann aber nur noch eine konstante Spannung am Ausgang.
Die Sache mit Push/Pop ACC&PSW (in der Interrupt Routine) habe ich auch
mal getestet: anfangs (1-2s) zufällige Signale aus bis dann der DAC auch
nur eine konstante Spannung ausgab.
Den Vorschlag den Timer nur für die Sinusfunktion zu benutzen hab ich
probiert einzubauen. Jedoch, weiss ich nicht wie ich dass umsetzen soll.
Ich denke Ihr meint so was: http://www.keil.com/forum/8340/. Ich hab mal
die TC_T2 und RL_T2 auf SET (anstelle von EQU) gesetzt. Jedoch wenn ich
nur schon die leere Funktion von ISR_T2 mit einem RETI in meinen
Sinusgenerator (Abschnitt main => CALL ISR_T2), gibt das Ding kein
Lebenszeichen.
Abschnitt: Wartesequenz
Was mir auch ein Rätsel ist, (abgesehen dass meine Sinus-Welle nicht
kontinuierlich ausgeben wird) dass meine Periode mit der Variabel #ASCII
sich nicht beeinflussen lässt. Wie kommt das? Dieser Wert wird aber
problemlos an der LCD-Anzeige anzeigt und geupdated? Und die Benutzung
von Konstanten (anstelle von #ASCII) beeinflusst die Frequenz der Sinus
problemlos.
Assembler Experten bin ich nicht, aber jede Anregung, Vorschlag probiere
ich zu verstehen und um zusetzen nur am nötigen Verständnis hapert es.
Max S. schrieb:> Was mir auch ein Rätsel ist, (abgesehen dass meine Sinus-Welle nicht> kontinuierlich ausgeben wird)
Ich würde ehrlich gesagt damit anfangen.
Dazu schmeiss ich das komplette Programm weg, denn da ist irgendwo der
Wurm drinnen.
Solange ein einfaches Programm keinen durchgehenden Sinus produziert,
sondern solche Hecker drinnen hat, wie es dein Oszi Bild zeigt, solange
brauch ich mich um ADC oder LCD-Ausgabe überhaupt nicht kümmern.
Erstmal muss die durch die Tabelle vorgegebene Kurvenform sauber am
Ausgang aufscheinen. Solange das nicht der Fall ist, ist der Rest
erstens uninteressant und zweitens eine potentielle Fehlerquelle. Also
weg damit.
Da dann aber ausser der Tabelle und deren Ausgabe auf den Port nichts
mehr vom Programm übrig bleibt, kann ich auch gleich ein neues anfangen.
Dann spar ich mir wenigstens die Arbeit des Abspeckens.
Matthias Sch. schrieb:> fallenden WR Flanke übernimmt.
Laut Datenblatt müsste es die steigende Flanke sein.
> Entweder setzt man P5 also vorher und> lässt dann CS und WR folgen oder man lässt alles so, wie es ist, dann> liegen die Daten eben noch länger an P5, bevor sie übernommen werden.
Ich würds trotzdem richtig stellen.
So wie es jetzt ist, ist die Sache unlogisch.
Die logische Abfolge wäre für mich
* Chip Select auf Low
* Daten anlegen
* Write auf Low
* Write auf High
* Chip Select auf High
Ob man zuerst die Daten anlegt und dann den Chip Select folgen lässt,
darüber kann man diskutieren. Aber am Write Pin fummle ich erst rum,
wenn die Daten am Port anliegen. Selbst wenn laut Datenblatt es möglich
ist, während Write auf Low liegt, die Datenpins nochmal geeändert
werden. Für mich ist das einfach nur eine Frage des logischen Ablaufs.
So wie bei einem Datumsstempel: zuerst stell ich das Datum ein und dann
drücke ich den Stempel nieder. Auch wenn es theoretisch noch möglich
wäre, das man das Datum einstellt während der Stempel gerade nach unten
saust.
Im Prinzip funktioniert das ja überall im kompletten Leben gleich: Erst
werden Dinge in Ruhe eingestellt und dann kommt das Signal "jetzt
gilts". Schreit man zu früh "Achtung" kommt nur Hektik ins Spiel.
Deine Programmlogik ist genau falsch rum.
Du machst das Zeitkritische (Sinus) in der langsamen Mainloop und das
völlig unkritische (ADC, Ausgabe) im Interrupt.
So kann das nix werden, es gehört genau umgekehrt.
Mach erstmal nur das Zeitkritische, also den Sinus-Interrupt.
Wozu brauchst Du überhaupt das krude packed-BCD?
Also das wird mit dem integrierten ADC des 80535 schon gehen, daß man
einen eingelesenen Wert flott an einem I/O-Port wieder aus gibt. Dazu
würde ich einen Timerinterrupt installieren, der im Interrupt selbst
wieder eine ADC-Messung startet, und das ADC Ready Flag wird beim
nächsten Timerinterrupt bestimmt auch da sein.
Peter Dannegger schrieb:> Wozu?> Einfach den ADC auf continuous conversion setzen. Und wenn die Mainloop> den nächsten Wert haben will, einfach nur auslesen.
Das kann man natürlich auch, wenn man kein bestimmtes Zeitraster haben
will. Im Interrupt sofort den Wert auslesen, und an die Ausgabe
schreiben.
Mir gehts wie Peter.
Ich seh den Sinn nicht dahinter, warum man den ADC per Interrupt
bedienen muss.
Die Komponente, bei der das Timing stimmen muss, das ist die
Waveform-Generierung. Die kommt in den Interrupt.
Ob der ADC jetzt pro auszugebender Sinus-Periode 10 mal oder 11 mal
abgefragt wird, ist IMHO dagegen komplett nebensächlich.
Karl Heinz schrieb:> Mir gehts wie Peter.>> Ich seh den Sinn nicht dahinter, warum man den ADC per Interrupt> bedienen muss.> Die Komponente, bei der das Timing stimmen muss, das ist die> Waveform-Generierung. Die kommt in den Interrupt.> Ob der ADC jetzt pro auszugebender Sinus-Periode 10 mal oder 11 mal> abgefragt wird, ist IMHO dagegen komplett nebensächlich.
Ich meinte halt, den ADC mit einem Timer kombinieren. Da der ADC im
80535 recht flott ist, könnte es sein, daß er im Continuous Mode den
Löwenanteil Rechenzeit beansprucht, und die weiteren Vorgänge hemmt.
Gute Nachrichten,
Sinus Funktion in Timer und der Rest (Anzeige und DAC) des Programmes
wird einfach nebenbei sequentiell ausgeführt. Dass Ganze funktioniert
(Siehe ISRonSineDAConWave)
Der ADC von dem 8051 scheint wie schon erwähnt ganz flott zusein, so
dass ich nur einen Interrupt Timer für die zeitkritische Sinus-Welle
anwende. Das klappt super! Der LCD und die DA Ausgabe am Oszilloskop
erfühlen Ihre Aufabe bedingungslos. Jedoch, dass einzige was hapert ist
den Spannungswert vom AD an den Timer zu übergeben.
Ich muss so etwas einbauen:
http://www.mikrocontroller.net/articles/8051_Timer_0/1#Variable_Timerzeit.
Jedoch lässt sich die Frequenz nicht von der Spannungswert beeindrucken.
Wie muss ich die Frequenz definieren? EQU, SET, SFR?
Wie kann ich den Wert des AD (liegt als ASCII, BCD_LSB ... und als ADDAT
vor) in die Variablen Frequency oder Timer0_repeat_cycles übergeben
werden? MOV Frequency,#ASCII? (Hat nicht geklappt)
Ist es überhaupt möglich nachträglich den Timer neu zusetzen?
Fast geschafft! Vielen Dank!
Max S. schrieb:> erfühlen Ihre Aufabe bedingungslos. Jedoch, dass einzige was hapert ist> den Spannungswert vom AD an den Timer zu übergeben.
Die übergibst du auch nicht 'an den Timer'.
Der Code, den du dir da besorgt hast, realisiert eine DDS.
Hier ....
1
...
2
MOVA,Phase_low
3
ADDA,#lo(Frequency)
4
MOVPhase_low,A
5
MOVA,Phase_high
6
ADDCA,#hi(Frequency)
7
MOVPhase_high,A;16bitphase-accumulator
8
...
... der Teil ist für die Frequenz zuständig. 'Frequency' muss etwas
werden, was du während des Programmlaufs verändern kannst.
Leider sagt mir
1
Frequencysfr20h
überhaupt nichts, was das sein soll. sfr klingt nach "special function
register". Scheint wohl so, dass der Original-Autor sich hier ein
(freies) Register der CPU ausgeborgt und zweckentfremdet hat.
Kann man machen. Allerdings werde ich dann nicht daraus schlau, was die
Verwendung von #lo bzw. #hi in den Additionen soll.
Ich sprech auch zu wenig 8051, um dir sagen zu können, wie die
Additionen dann zu verändern sind, wenn man Frequency in Analogie zum
Phase-Akku in den Speicher legt (bzw. ob das schlau ist). Naiv (und mit
wenig 8051 Kentnissen) hätte ich das halt so gemacht
1
Phase_low: DS 1
2
Phase_high: DS 1
3
Frequ_low: DS 1
4
Frequ_high: DS 1
und dann in der Timer-Routine (nicht wortwörtlich nehmen, ich weiss
nicht wie man beim 8051 die Sache in der Addition anschreiben muss)
1
MOV A,Phase_low
2
ADD A, "den Inhalt der Speicherzelle Frequ_low"
3
MOV Phase_low,A
4
MOV A,Phase_high
5
ADDC A, "den Inhalt der Speicherzelle Frequ_high"
6
MOV Phase_high,A ;16 bit phase-accumulator
durch Verändern der Werte dieser Speicherzellen ändert sich dann auch
die Frequenz. Wobei man natürlich die entsprechenden Werte dann auch
noch korrekt aus dem ADC Wert ausrechnen muss. ABer das ist dann erst
der zweite Schritt. Der erste ist es, diese 'SChrittweite' bei der
Generierung der Tabellen-Indizes variabel zu machen.
Und natürlich erst mal vernünftige Werte in Frequ_low bzw. Frequ_high
eintragen :-)
Aber das sollte ohnehin klar sein.
Leider kenne ich deinen Assembler nicht. "Sfr" bedeutet in der X51 Welt
- wie KHB schon anmerkte - Special Function Register. Die beginnen aber
alle ab Adresse 0x80. Kann es sein, dass du eigentlich schreiben
wolltest "Frequency ds 2", also nur Speicherplatz für eine Variable
reservieren wolltest?
Die Änderung der Frequenz funktioniert nicht, weil du etwas
verwechselst:
ADD A,#lo(Frequency)
MOV Phase_low,A
MOV A,Phase_high
ADDC A,#hi(Frequency)
MOV Phase_high,A ;16 bit phase-accumulator
das # bedeutet "nimm nicht den Inhalt der Variablen sondern die
Adresse". Du kannst an deinem AD Eingang beliebig drehen, die Adresse
der Variablen ändert sich dadurch nicht. Probier es mal mit
ADD A, BIN_LSB
MOV Phase_low,A
MOV A,Phase_high
ADDC A, BIN_MSB
MOV Phase_high,A ;16 bit phase-accumulator
Wir haben es hin bekommen die Frequenz mittels Spannungswert zu
beeinflussen. Ich putze den Quellentext und werde es dann posten! Es ist
jetzt so, dass der Änderungsbereich sich auf 2 Volt beschränkt d.h.:
wenn man diesen Wert überschreitet geht es von vorne los. Ich denke dass
kommt von der Bit-Grösse der Werte (Überlauf, ähnlich wie die untere
beschriebene 16-Bit-Werte)
Ja, die Sache mit dem SFR kommt von
http://mcls-modular.de/DE/helpsys/t_as1.htm#Adresszuweisungen . Ich bin
mir im Klaren dass auch dort steht HINWEIS: Der zugewiesene Wert kann
nicht nachträglich geändert werden ! Jedoch, in der Not probiert man
alles aus dass der verdammte variable Wert endlich berücksichtig wird.
Morgen, werde ich deinen Vorschlag mit BIN_MSB/BIN_LSB einfügen. Jedoch,
hab ich dass in etwas probiert was das Ding nur lahmlegte.
Die Syntaxanweisung #hi(Frequency) und #lo(Frequency) wird durch Makro
von der bitfuncs.inc ermöglicht. Mehr dazu hier
http://mcls-modular.de/DE/helpsys/t_as1.htm#16-Bit-Werten
auf jeden Fall, VIELEN DANK und schönes Fest an euch alle! Heureka!
Max S. schrieb:> Ja, die Sache mit dem SFR kommt von
Mich schaudert... du hast doch DSEG definiert. Überlasse es dem
Assembler, was er wo hin packt. Wenn du feste Adressen vorgibst, kann
das furchtbar in die Beinkleider gehen. Da musst du genau wissen, was du
machst.
> Syntaxanweisung #hi(Frequency) und #lo(Frequency)...
nochmals: # bedeutet, dass du einen festen Wert nimmst. Du willst aber
den Inhalt einer Variablen nehmen. Da ich nicht weiß, wie dein Makro
genau aussieht, kann ich dir nicht sagen, ob es reicht, nur das # weg zu
lassen.
Georg G. schrieb:> Adresse". Du kannst an deinem AD Eingang beliebig drehen, die Adresse> der Variablen ändert sich dadurch nicht. Probier es mal mit> ADD A, BIN_LSB> MOV Phase_low,A> MOV A,Phase_high> ADDC A, BIN_MSB> MOV Phase_high,A ;16 bit phase-accumulator
Ah.
Soweit hab ich dann im Code nicht nachgesehen, was er mit dem ADC Wert
macht. Na wenn der ohnehin schon im Speicher liegt, dann ist das
natürlich naheliegend, den gleich zu verwenden.
Georg G. schrieb:>> Syntaxanweisung #hi(Frequency) und #lo(Frequency)...> nochmals: # bedeutet, dass du einen festen Wert nimmst. Du willst aber> den Inhalt einer Variablen nehmen. Da ich nicht weiß, wie dein Makro> genau aussieht, kann ich dir nicht sagen, ob es reicht, nur das # weg zu> lassen.
Ich würde mal sagen: da die beiden Bytes ja sowieso getrennt im Speicher
ansprechbar sind, braucht die beiden Makros im Grund ja kein Mensch
mehr.
Mir kommt vor, dass Max schon sehr bei den unterschiedlichen
Adressierungsmodi schwimmt. Max. Das musst du ändern!
Karl Heinz schrieb:> Mir kommt vor, dass Max schon sehr bei den unterschiedlichen> Adressierungsmodi schwimmt.
Zu seiner Ehrenrettung wollen wir hier aber festhalten, dass der X51 da
wirklich etwas trickreich ist.
Georg G. schrieb:> Max S. schrieb:>> Ja, die Sache mit dem SFR kommt von>> Mich schaudert... du hast doch DSEG definiert. Überlasse es dem> Assembler, was er wo hin packt. Wenn du feste Adressen vorgibst, kann> das furchtbar in die Beinkleider gehen. Da musst du genau wissen, was du> machst.
Ich denke, der Originale Autor hat da heftig getrickst.
Es geht ihm in Wirklichkeit gar nicht darum, durch
1
Frequency sfr 20h
ein Register zur Verwendung zu benutzen.
Die wirkliche Absicht dahinter ist es, einen 16 Bit Wert zur Verfügung
zu haben (mit dem Wert 00020h), damit er darauf die Makros #lo bzw. #hi
anwenden kann.
Im Originalcode steht da in Wirklichkeit einfach nur
1
MOV A,Phase_low
2
ADD A, #20h
3
MOV Phase_low,A
4
MOV A,Phase_high
5
ADDC A, #00h
6
MOV Phase_high,A ;16 bit phase-accumulator
und für die Konstanten, die er so nicht im Code haben wollte (aus
naheligenden Gründen), hat er sich dann etwas anderes gesucht.
Kommt mir zwar ein bischen komisch vor, dass über sfr zu machen, da gibt
es doch sicherlich auch andere Möglichkeiten in einem Assembler, eine 16
Bit Konstante zu definieren, so dass man High- bzw. Lowbyte Makros drauf
anwenden kann, aber seis drumm.
Das eigentliche Special Function Register an der Adresse 20h wird nie
auch nur angerührt oder verändert. Im Grunde benutzt er einfach nur
seine Adresse als Zahlenkonstante.
Das ist meiner Meinung nach der Trick, der hier zum Einsatz gekommen
ist.
Bei dem verwendeten Assembler ist "sfr" nicht ein Special Function
Register sondern definiert eine Variable an einer festen Adresse.
SFR
Anweisung, dem genannten <Symbol> die genannte <Adresse> des direkt
adressierbaren, internen Datenspeichers zuzuweisen.
<Symbol> SFR <Adresse>
Und Adressen im Direct Data Bereich sind immer 8 Bit... mehr kann der
arme X51 nicht direkt adressieren.
Aber egal, Mist war die Deklaration so oder so.
Karl Heinz schrieb:> Frequency sfr 20h
Bei MCS51 beginnt ab Adresse 20h (also direkt über den normalen 4
Registerbänken) die 'bit addressable area' des RAM, die direkt mit setb
und clrb angesprochen werden kann. Das wäre der einzige Grund, um
explizit 20h zu benutzen.