Hallo zusammen,
Ich will einen Spannungsteiler mit einer 10khz Rechteckspannung
bestromen. Also zu einen Zeitpunkt soll die eine Seite des
Spannungsteiler mit Masse und der zweite Pol mit VCC verbunden sein. In
zweiten hälfte des Taktes soll die Polung umgedreht werden. So dass sich
die Stromflussrichtung ständig ändert. In der Mitte der ersten
Takthälfte soll dann eine AD Wandlung durchgeführt werden.
Generell hat der uC noch weitere Aufgaben zu erledigen. Teilweise dauern
auch manche Interruptroutinen länger als 1000 Takte. Es handelt sich um
einen Atmel Atmega. Dieser läuft mit 8 MHz.
Mein Problem an der Sache ist die begrenzte Zeit. Eine AD Wandlung
dauert 14 Takte. Wenn ich einen Timer aufrufe der mir die 10 khz
erzeugt, dann hab ich zwischen den Timeraufrufen nur ca. 400 Takte Zeit.
Wenn jetzt aber eine andere Interruptroutine länger dauert habe ich ein
Problem.
Kann man das ganze irgendwie per PWM oder so im Hintergrund laden
lassen. Ohne grosse CPU Last?
Danke
FLorian Unbekannto schrieb:> Kann man das ganze irgendwie per PWM oder so im Hintergrund laden> lassen. Ohne grosse CPU Last?
Atmegas machen PWM ohne CPU-Last. Nur die alten Gurken wie Atmega8 sind
da etwas eingeschränkt. Aber die verwendet ja heute kaum noch jemand.
mfg.
FLorian Unbekannto schrieb:> In der Mitte der ersten> Takthälfte soll dann eine AD Wandlung durchgeführt werden.
Das heisst, du brauchst einen 40 kHz Timer! Bei 0 und 2 den Ausgang
umschalten, bei 1 ADC, bei 3 kann sich der Prozessor eine
Verschnaufpause gönnen. Oder du rufst mit einem 10 kHz Interrupt die
ADC-Wandlung auf, das musst du ja sowieso, aber dann müsstest du das
Rechtecksignal mit der richtigen Phase durch eine externe Schaltung
erzeugen.
Ein PWM-Ausgang würde vermutlich nicht synchron mit der ADC-Wandlung
laufen.
Georg
Hi
>Mein Problem an der Sache ist die begrenzte Zeit. Eine AD Wandlung>dauert 14 Takte.
Nein, 13 Takte. Aber ADC-Takte. Und der ADC-Takt soll zwischen
50...200kHz betragen.
Das passt nicht zusammen.
MfG Spess
FLorian Unbekannto schrieb:> Wenn jetzt aber eine andere Interruptroutine länger dauert habe ich ein> Problem.
Das hast du definitiv. Was du brauchst ist eine ISR für Rechteck und
ADC, die Priorität vor allen anderen hat, also auch andere laufende ISR
unterbrechen kann. Sofern das Ganze überhaupt funktioniert: mit der
ADC-Wandlucg ist es ja nicht getan, irgenwie müssen die Daten ja
weiterverarbeitet werden, und alle 100 µs kommen neue.
Georg
Georg schrieb:> Was du brauchst ist eine ISR für Rechteck und> ADC, die Priorität vor allen anderen hat, also auch andere laufende ISR> unterbrechen kann.
Tolle Idee, das gibt es auf einem Atmega nur leider nicht.
FLorian Unbekannto schrieb:> Also zu einen Zeitpunkt soll die eine Seite des> Spannungsteiler mit Masse und der zweite Pol mit VCC verbunden sein. In> zweiten hälfte des Taktes soll die Polung umgedreht werden. So dass sich> die Stromflussrichtung ständig ändert. In der Mitte der ersten> Takthälfte soll dann eine AD Wandlung durchgeführt werden.
Klingt nach nem Denkfehler. "Die Polung umdrehen" bedeutet ja, dass der
uC plötzlich negative Betriebsspannung liefern müsste.
Wenn du die Polung umdrehen willst, musst du den einen Anschluss deines
Spannungsteilers auf einen Pin (nennen wir ihn mal P1) legen und den
anderen Anschluss auf den anderen Pin (P2).
An P1 liegt dann das Rechtescksignal an und an P2 das ganze invertiert.
edit: O.K. mein Fehler ich glaube genau das meintest du. Notiz an mich:
2 mal lesen...
> Generell hat der uC noch weitere Aufgaben zu erledigen. Teilweise dauern> auch manche Interruptroutinen länger als 1000 Takte.
Müssen so lange ISR wirklich sein?
1
volatileboolmy_event=false;// globale eventvariable für ???
> Es handelt sich um> einen Atmel Atmega. Dieser läuft mit 8 MHz.
Welcher ATmega? Wenn es an die PWM geht wärs ganz gut zu wissen ob man
nen 8 bit Timer oder vieleicht sogar nen 12 oder 16 bit Timer zu
Verfügung hat
> Mein Problem an der Sache ist die begrenzte Zeit. Eine AD Wandlung> dauert 14 Takte. Wenn ich einen Timer aufrufe der mir die 10 khz> erzeugt, dann hab ich zwischen den Timeraufrufen nur ca. 400 Takte Zeit.> Wenn jetzt aber eine andere Interruptroutine länger dauert habe ich ein> Problem.> Kann man das ganze irgendwie per PWM oder so im Hintergrund laden> lassen. Ohne grosse CPU Last?>> Danke
Mit einer 8 bit PWM und einem Preescaler von 2 kommst du grob
überschlagen auf 15,625 kHz und bei Prescale 4 auf 7,8125 kHz. On zu Off
time ist dann natürlich 1:1
Müssen es genau 10kHz sein?
Jim Meba schrieb:> Tolle Idee, das gibt es auf einem Atmega nur leider nicht.
Und was willst du damit sagen? Dass du eine bessere Idee hast? Dann her
damit.
Man kann sich sowas selbst zusammenprogrammieren, wenn auch nicht so
effektiv wie in Hardware, man kann aber auch zum Schluss kommen, dass
die Hardware für die Aufgabe unzureichend ist.
Egal wie, für die beschriebene Aufgabe muss man dafür sorgen, dass die
worst case latency des Interrupts kürzer ist als der für das Rechteck
zulässige Jitter. Darüber ist aber eh nichts Genaues bekannt.
Georg
FLorian Unbekannto schrieb:> Teilweise dauern auch manche Interruptroutinen länger als 1000 Takte.
Warum? Das hört sich für mich nach "Programmfehler" an...
Bernhard F. schrieb:>> Mit einer 8 bit PWM und einem Preescaler von 2 kommst du grob> überschlagen auf 15,625 kHz und bei Prescale 4 auf 7,8125 kHz. On zu Off> time ist dann natürlich 1:1> Müssen es genau 10kHz sein?
Wieso --> Mit 8 MHz Clock Frequenz
bekomme ich doch mit einem 8Bit Timer ein 10KHz Rechteck Signal generier
--> Prescaler 8
CTC - Mode Anschalten
Toggle OC0A on Compare Match
0CR Register auf 0x63
Ohne Gewähr und wenn ich mich nicht verrechnet habe.
Georg schrieb:> Das heisst, du brauchst einen 40 kHz Timer! Bei 0 und 2 den Ausgang> umschalten, bei 1 ADC, bei 3 kann sich der Prozessor eine> Verschnaufpause gönnen. Oder du rufst mit einem 10 kHz Interrupt die> ADC-Wandlung auf, das musst du ja sowieso, aber dann müsstest du das> Rechtecksignal mit der richtigen Phase durch eine externe Schaltung> erzeugen.>> Ein PWM-Ausgang würde vermutlich nicht synchron mit der ADC-Wandlung> laufen.
Genau so meinte ich es. Nur das geht auf keinen Fall. Viel zu viel
Rechenzeit. Deshalb war meine Idde das ganze irgendwie in die PWM Sache
zu verlegen. Nur weiß ich nicht, ob man zwei PWM Kanäle genau
gegensätzlich betreiben kann, das ganze auch noch synchronisiert. Ich
könnte mich auch damit anfreuden den ADC nur mit 100 Hz aufzurufen. Nur
Ka, wie ich das synchronisiert bekommen soll. Bzw ob das überhaupt
möglich ist.
Hi
>0CR Register auf 0x63>Ohne Gewähr und wenn ich mich nicht verrechnet habe.
Du hast dich verrechnet. Eine 10kHz Frequenz bekommst du bei 0x31.
MfG Spess
spess53 schrieb:>> Du hast dich verrechnet. Eine 10kHz Frequenz bekommst du bei 0x31.>> MfG Spess
..... Schade um Faktor zwei vertan .... Kopfrechnen Schwach Religion
sehr gut :-) Scherz bei seite.
Aber ein Ziemlich Stabilies Rechtecksignal kann ich damit erzeugen.
Danke Spess53 für die Korrektur
Gruß
Detlev
Hi
Ich hab mir mal die Mühe gemacht und dein Vorhaben kurz nach meinem
Verständnis skizziert. Eigentlich ist das dein Job, denn nur so ist
eindeutig, worüber hier diskutiert wird.
Ich seh da überhaupt kein zeitproblem, bei richtigem Programmaufbau und
klar,
>Teilweise dauern auch manche Interruptroutinen länger als 1000 Takte.
ist das ein Fehler im Konzept. Eine ISR sollte kurz und schmerzlos nur
das erforderliche tun- so ist es kein Problem, alle 25 µSek in einer ISR
Flags zu setzen sowie einen Zähler aufzuaddieren. Bei 1 startest du im
Hauptprogramm die Messung, Ergebnis kommt in ISR des AD-Wandlers. Bei 2
wechselst du im Hauptprogramm die Signallage der Portpins. bei 4 setzt
du den Zähler wieder auf 0 und wechselstebenfalls wieder die Signallage
der Portpins. Kurz über den Daumen kalkuliert bei 8 MHz und einer
CPU-Taktzeit von 125 nSek alles ca. in 15 bis 20 µSek. abgearbeitet.
Aufruf ISR mit Inc Zähler ca. 10 Takte
1
ISR_Timer: ;2 Takte
2
IN Status_Save, SREG ; Statusregister in Statuskopie 1 Takt
3
INC ISR_STEP ; reserviertes Zählregister 1 Takt
4
OUT SREG, Status_Save ; Statusregister zurückschreiben 1 Takt
5
RETI ;4 Takte
Auswertung Zähler ca. max. 30 Takte
1
CPI ISR_Step, 1
2
BREQ Start_AD
3
CPI ISR_Step, 2
4
BREQ Swap_Portbit
5
CPI ISR_Step, 4
6
BRLO Weiter
7
CLR ISR_Step
8
RJMP Swap_Portbit
9
Start_AD:
10
....
11
RJMP weiter
12
Swap_PortBit:
13
.....
14
weiter:
Wie du siehst, keine große Sache. Ausrechnen kannst du dir das selbst.
Gruß oldmax
Detlev Neumann schrieb:> Bernhard F. schrieb:>>>> Mit einer 8 bit PWM und einem Preescaler von 2 kommst du grob>> überschlagen auf 15,625 kHz und bei Prescale 4 auf 7,8125 kHz. On zu Off>> time ist dann natürlich 1:1>> Müssen es genau 10kHz sein?>> Wieso --> Mit 8 MHz Clock Frequenz> bekomme ich doch mit einem 8Bit Timer ein 10KHz Rechteck Signal generier>> --> Prescaler 8> CTC - Mode Anschalten> Toggle OC0A on Compare Match>> 0CR Register auf 0x63>> Ohne Gewähr und wenn ich mich nicht verrechnet habe.
Natürlich geht das so. O.K. bis auf ein kleines Detail:
In dem Beispiel dürfte ein Rechteck-Puls 100us lang. Periodendauer wären
aber 2 Pulse? der Timer dürfte also nur bis 49 Zählen? Also müsste OCR =
0x31 sein wenn ich mich grade nicht vertue.
Ich war gedanklich noch in ein vergleichbares Projekt vertieft.
Da hab ich die 2 PWM pins bei Bottom und (OCR=265/2)-1 geschaltet, den
Timer bis zu 0xFF (fast pwm) hochzählen lassen, und mir einen Overflow
Interrupt generiert.
Ich wollte auf jeden Fall nur einen Interrupt pro Periode. Man hätte
natürlich im CTC-Mode nur jeden 2. Interrupt mitnehmen können für das
gleiche Ergebnis, aber das ganze war schon spitze auf Knopf gerechnet
und höchst zeitkritisch.(Nach dem der Wert endlich gewandelt wurde muss
er noch ausgewertet und auf LEDs ausgegeben werden und das möglichst
bevor der nächste Wert aufgenommen wird) Dafür hab ich dann gerne eine
feste Frequenz in Kauf genommen. Zugegeben im Nachinein würde ich das
auch anders lösen, aber da der Threadsteller eh schon zu viele
Interrupts hat, hab ich das gleich mal irgendwie im Hirn miteinander
verbunden...
Klang alleine so dahstehend bestimmt seltsam ... ich merk es selbst...
Felix schrieb:
Nur weiß ich nicht, ob man zwei PWM Kanäle genau
> gegensätzlich betreiben kann, das ganze auch noch synchronisiert. Ich> könnte mich auch damit anfreuden den ADC nur mit 100 Hz aufzurufen. Nur> Ka, wie ich das synchronisiert bekommen soll. Bzw ob das überhaupt> möglich ist.
Klar ist das möglich.
OCRA und OCRB sind ja am gleichen Timer.
oldmax schrieb:> Bei 1 startest du im> Hauptprogramm die Messung
Genau dasselbe habe ich ihm ja auch schon vorgeschlagen - entweder er
versteht es nicht oder er will es einfach nicht. Er hat sich wohl an der
PWM festgefressen, aber ich sehe nicht, wie man Rechteck und ADC
phasenrichtig steuern kann ausser wie von uns beiden beschrieben. Kann
man halt nix machen.
Georg
ey Georg,
ich bin beratungsresistent. Hatte das bei dir nicht so verstanden und
außderdem kam zu deinem Post ein anderer Post, wo drin stand, dass das
nicht so geht. Bin gerade dabei das umzustetzen, wie oldmax das gesagt
hat.
Bzw ihr das gesagt habt;-)
O.K. kleiner Denkansatz ich bitte darum ich zu korrigieren wenn ich
falsch liege.
Da leider nicht bekannt ist welche Variante des AT-Mega benutzt wird
hoffe ich einfach mal, dass dieser einen 16 bit timer hat.
1)
Der Timer wird mittels
Prescaler 8
Mode 14 Fast PWM
ICRn = 0x0063 (dec 99)
auf die Frequenz von 10kHz eingestellt.
2) OCnA und OCnB erhalten die Werte 0x0031 (dec49)
a) OCnA set on BOTTOM Clear on Match (non-inverting)
b) OCnB clear on Bottom set on compare match (inverting)
-> übernehmen das Toggeln bei Pos 0 und Pos 2
3) OCnC erhält den Wert 0x0018 (dec 24)
im entsprechenden TIMSK wird der Interupt für Compare Match scharf
geschaltet.
-> generiert einen Interrupt bei Pos 1
Könnte das so klappen?
Bernhard F. schrieb:> Klingt nach nem Denkfehler. "Die Polung umdrehen" bedeutet ja, dass der> uC plötzlich negative Betriebsspannung liefern müsste.
Da hast du einen Denkfehler. Es reicht, wenn das Signal vom µC über
einen Kondensator geführt und dadurch der Nullpunkt verschoben wird ...
Ok, das mit der Polung war missverständlich ausgedrückt. Da es sich um
einen Leitwertmesser handelt, ist nur die Stromflussrichtung
interessant. Macht euch darum keine Gdannken.
Ich hoffe es kommen jetzt nicht wieder Leute an und motzen rum, dass ich
nicht alle Fakten gesagt hab. Das war aus meiner Sicht unnütze
Information.
Es ist ein ATmega 16 mit 16 Bit Timern.
Georg schrieb:> Er hat sich wohl an der> PWM festgefressen, aber ich sehe nicht, wie man Rechteck und ADC> phasenrichtig steuern kann ausser wie von uns beiden beschrieben.
Meine Fresse. Das ist doch nun wirklich extrem simpel. Alles, was man
braucht, ist ein Timer, der die drei Register OCRA, OCRB und ICR besitzt
(bzw. bei einem 16Bit-Timer jeweils die H- und L-Teilregister dieser
drei Register).
Dann braucht man noch den Systemtakt und die möglichen Prescalerwerte
für den Timer. Nehmen wir als Beispiel einfach mal einen Mega32@8MHz.
Dieser Methusalem besitzt nur einen Timer mit zwei PWM-Kanälen, den
Timer1. Also nimmt man den. Der am besten für die Aufgabe geeignete
Timermodus ist FastPWM mit ICR1 als TOP (Modus 14). Mögliche Prescaler
für Timer1 sind:
1
1/8
1/64
1/256
1/1024
Der maximale Zählumfang eines 16-Bit-Timers ist 65536.
Damit kann man erstmal den bestmöglichen Prescaler ermitteln. Das ist
genau der, der bei vollem Zählumfang des Timers gerade noch eine
Überlauffrequenz des Timers liefert, die kleiner ist als die
gewünschte von 10kHz.
Also gilt es folgende, für manche Leute scheinbar schon überaus
komplizierte Rechenaufgaben zu lösen:
8.000.000/(65536*1) = 122,0703125
8.000.000/(65536*8) =
8.000.000/(65536*64) =
8.000.000/(65536*256) =
8.000.000/(65536*1024)=
Nunja, hier können wir offensichtlich schon nach dem ersten Anlauf
aufhören mit Rechnen, denn 122Hz sind sehr deutlich unterhalb von 10
kHz. Der bestmögliche Prescaler ist hier also 1. Der entsprechende Wert
für die CS1x-Bits in TCCR1B ist (zufällig) auch 1.
Damit bleibt als einziger Freiheitsgrad noch der Wert für ICR1H/L, um
den korrekten PWM-Zyklus zu erreichen. Wie man den berechnet steht im
Datenblatt. Auch wieder so eine überaus komplizierte Division, obendrein
gefolgt von der unheimlich anspruchsvollen Aufgabe, vom Ergebnis dann
noch eins abzuziehen...
8.000.0000/10.000 - 1 = 799 bzw. $31F
ICR1H ist also mit 3 zu laden, ICR1L mit $1F.
So, damit hat man erstmal die richtige PWM-Frequenz und zwar sogar ganz
exakt.
Nun soll aber ein Rechteck erzeugt werden (vermutlich ein möglichst
symmetrisches, also eins mit gleich langen High- und Low-Phasen). Dafür
benutzt man OCR1AH/L und OCR1BH/L. Beide bekommen den gleichen Wert,
denn beide sollen das gleiche Rechtecksignal erzeugen. Damit das
symmetrisch ist, muß der Wert einfach die "Hälfte" des ICR-Wertes
betragen. Die korrekte Formel ist:
OCR=(ICR+1)/2-1
(warum das die korrekte Formel ist, steht wieder im Datenblatt, nämlich
in der Funktionsbeschreibung des Timers)
Also:
OCR=(799+1)/2-1=399 bzw. $18F
OCR1AH und OCR1BH sind also jeweils mit 1 zu laden, OCR1AL und OCR1BL
jeweils mit $8F.
Damit haben wir zwei gleiche und synchrone Rechtecksignale mit der
richtigen Frequenz. Nun müssen wir die bloß noch an die entsprechenden
Pins lotsen und eins davon bei dieser Gelegenheit invertieren. Das geht
über die COM1Ax- und COM1Bx-Bits in TCCR1A. Für den FastPWM-Modus wären
die entsprechenden Bitkombinationen 0b10 bzw. 0b11 (was natürlich auch
wieder dem Datenblatt zu entnehmen ist), was davon man als "normal" und
"invertiert" betrachtet, liegt allein im Auge des Betrachters, wichtig
ist für die konkrete Anwendung nur eins:
Wenn man für den einen Kanal die eine Bitkombination verwendet und für
den anderen die andere, dann erhält man auf jeden Fall bei sonst
identischer Initialisierung der beiden Kanäle an einem der beiden
OC1x-Pins genau das invertierte Signal des anderen. (Tatsächlich stimmt
das nicht 100%ig, aber für den konkreten Fall mit symmetrischen
Rechtecken stimmt es ganz sicher).
So, das war die erste Hälfte der Aufgabe, der Mann hat erstmal ohne jede
CPU-Last sein antisymmetrisches Rechtecksignal an zwei Pins.
Bleibt noch, die ADC zu synchronisieren. Auch das geht (fast) ohne jede
CPU-Last, denn die ADC läßt sich durch gewisse Timer-Ereignisse starten.
Daß das passieren soll, legt man über das Setzen des ADATE-Bits in
ADCSRA fest, welches Timerereignis der Trigger sein soll, legt man über
die ADTSx-Bits (beim Mega32 im SFIOR-Register) fest. Für Timer1 als
Quelle stehen hier drei Varianten zur Verfügung, die im Prinzip für die
konkrete Anwendung alle gleichermaßen geeignet sind, es ist also in
erster Näherung scheißegal, welche man davon verwendet. Overflow und
Capture triggern nahezu zur gleichen Zeit, CompareMatchB einen halben
PWM-Zyklus später.
Fast ohne CPU-Last gilt bezüglich der Triggerung deshalb, weil ohne
jede Timer-ISR die ADC-ISR selber das Trigerflag zurücksetzen muß. Naja,
zwei Takte aller 100µs sollten machbar sein. Wenn es aber aus
irgendwelchen Gründen sowieso eine Timer-ISR geben muß, dann wird man
natürlich als Triggerquelle genau diese wählen, weil dann das manuelle
Zurücksetzen des Flags entfällt.
Was letztlich noch zu tun bleibt, ist noch nur eine Sache: einen
geeigneten ADC-Prescaler zu wählen, der sicherstellt, daß die ADC
innerhalb eines PWM-Zyklus auch sicher fertig wird. Im Datenblatt steht,
wie man das berechnet...
Von der ganzen CPU-Last bleibt also genau nur eine Sache: die ADC-ISR.
Entweder mit oder ohne Rücksetzen des Triggerflags. Da das in der ISR
alles Operationen sind, die ohne Flagveränderung auskommen können, ist
die ISR sehr bescheiden bezüglich der CPU-Last. Folgendes Beispiel zeigt
das für die Verwendung von OCR1B als Quelle mit Rücksetzen:
adc_handler: ; 6 (Normalfall mit rcall im Vector)
push R16 ; 2 Hilfsregister retten
ldi R16,1<<OCF1B ; 1 Triggerflag zurücksetzen
out TIFR,R16 ; 1
in R16,ADCL ; 1 ADC-Wert im Speicher ablegen
sts adc_value+0,R16; 2
in R16,ADCH ; 1
sts adc_value+1,R16; 2
pop R16 ; 2
reti ; 4
;--
;22
Die ISR benötigt also 10.000*22=220.000 Takte pro Sekunde, um alles zu
tun, was nötig ist. Von 8.000.000 verfügbaren Takten. Also benötigt die
ganze Sache genau 2,75% der verfügbaren Rechenzeit.
Für den Fall, daß aus irgendwelchen anderen Gründen sowieso ein
Timerinterrupt gebraucht wird und damit das Rücksetzen des Triggerflags
in der ADC-ISR entfallen kann, sinkt deren Rechenzeitbedarf auf 20
Takte= 2,5%.
SO werden µC programmiert. Alle Features der Hardware ausnutzen. Erst,
wenn man damit nicht mehr die gewünschte Funktion erreichen kann, kommt
überhaupt erst Code in's Spiel. Und da erreicht man dann die maximale
Performance in genau einer Sprache: Der Assemblersprache des
Zielsystems.
c-hater (Gast) super Ausführung aber nicht jeder beherrscht Assembler
wenn Du es kannst sei glücklich
Aber ich denke auch in einer Hochsprache ist dein Szenario
programmierbar
Gruß
Detlev
Detlev Neumann schrieb:> Aber ich denke auch in einer Hochsprache ist dein Szenario> programmierbar
Natürlich.
Aber solange das hier
> Generell hat der uC noch weitere Aufgaben zu erledigen. Teilweise> dauern auch manche Interruptroutinen länger als 1000 Takte.
nicht abgestellt wird, ist alles andere Makulatur. Egal ob in Assembler
oder in Makro-Assembler (alias 'C').
Hey,
die tausend Takte versuche ich gerade abzustellen, allerdings krieg ich
es noch nicht hin. Ich setze jetzt in der ISR einen bool auf true und
dann polle ich den bool in der main(). Wenn true, dann führe ich die
1000 Takte aus. Ich hoffe das ist im Prinzip richtig und sollte gehen.
Allerdings funktioniret das nicht...obwohl ich den bool volatile
markiert habe.
@c hater: danke für die Ausführung. Das ganze in C zu machen ist kein
Problem, allerdungs wird meines Verständnisses nach, der ADC direkt
gestartet nach dem ein Pegelwechsel vorliegt. Also Compare match wird
aufgerufen, dann Signale toggeln und sofort startet der ADC. Das wollte
ich zwecks einschwingen ja eigentlich nicht. Oder versteh ich was
falsch?
VG
FLorian Unbekannto schrieb:> Ich setze jetzt in der ISR einen bool auf true und> dann polle ich den bool in der main(). Wenn true, dann führe ich die> 1000 Takte aus. Ich hoffe das ist im Prinzip richtig und sollte gehen.> Allerdings funktioniret das nicht...obwohl ich den bool volatile> markiert habe.
Vermutlich hast du noch einen Programmierfehler - Zeile 42 schon mal
genauer angeguckt? ;-)
Nachdem du in der main() die Sache abgearbeitet hast, solltest du deine
bool dort auf false setzen, damit die ISR wieder eine Chance hat, einen
neuen Durchlauf anzufordern. Außerdem musst du dir sicher sein, dass die
zusätzlichen Takte in der main() die Durchlauffrequenz nicht zu sehr
verringern. Sonst könntest du die 1000 Takte in Teilaufgaben aufteilen,
die in aufeinanderfolgenden Hauptschleifendurchläufen abgearbeitet
werden. Dann hätten die anderen "Tasks" häufiger eine Chance, dran zu
kommen.
Karl Heinz schrieb:> Aber solange das hier>> Generell hat der uC noch weitere Aufgaben zu erledigen. Teilweise>> dauern auch manche Interruptroutinen länger als 1000 Takte.>> nicht abgestellt wird, ist alles andere Makulatur. Egal ob in Assembler> oder in Makro-Assembler (alias 'C').
Nö. Eine ISR kann auch lang sein. Was möglichst kurz sein muß, ist "nur"
der Teil ihrer Zeit, den sie exklusiv arbeitet, also die Zeit, in der es
nicht möglich ist, daß andere Interrupts ausgelöst werden.
Aber gerade diese mit "nur" apostrophierte Aufgabe stellt doch oft eine
ziemliche Knobelaufgabe dar. Und wenn du abstreiten willst, daß diese
sich nur in Asm mit dem besten aller denkbaren Ergebnisse lösen lassen
wird, dann lügst du.
In C kannst du im allerbesten Fall das gleiche Ergebnis wie in Asm
erreichen. In der Praxis wirst du aber (gerade bezüglich ISRs) nur ein
etwas bis sogar sehr deutlich schlechteres Ergebnis erzielen können. Je
kürzer die ISR, desto größer der Nachteil für C, weil der nutzlose
C-Overhead dann immer relevanter wird.
Die gezielte Wahl der bestmöglichen Optimierung für das konkrete Problem
ist nur in Asm möglich. Eben weil diese Optimierung oft bedeuten wird,
daß man zumindest in der ISR selber auf den "Komfort" von C verzichten
muß. Im Falle geistig etwas minderbemittelter Compiler, die keine
explizite Reservierung von Registern für wirklich zeitkritische
Operationen unterstützen, muß man ggf. sogar im ganzen Programm auf C
verzichten, um das theoretisch Machbare auch praktisch umsetzen zu
können und landet so auf ganz natürliche Art bei der gottgegebenen
Sprache für kleine µC: Asm.
Detlev Neumann schrieb:> c-hater (Gast) super Ausführung aber nicht jeder beherrscht Assembler> wenn Du es kannst sei glücklich
Das hat nix mit Glück zu tun. Ich habe mir einfach nur die Mühe gemacht,
auch AVR-Asm zu lernen. Neben vielen anderen Sprachen.
> Aber ich denke auch in einer Hochsprache ist dein Szenario> programmierbar
Dieses ja. Andere nicht.
Je enger es an die Grenzen des theoretisch Möglichen geht, desto
wichtiger wird es, dem Controller notfalls auch direkt in seiner
Sprache sagen zu können, wie genau er es am Besten machen soll.
FLorian Unbekannto schrieb:> allerdungs wird meines Verständnisses nach, der ADC direkt> gestartet nach dem ein Pegelwechsel vorliegt.
Ja. Die ADC wird hier auf jeden Fall immer anläßlich eines Pegelwechsels
an den Ausgängen gestartet.
Allerdings heißt "Starten" der ADC nicht, daß auch in diesem Moment der
Meßwert gesampelt wird. Das passiert erst später. Wann genau, ist
berechenbar. Wie genau das berechnet wird, steht (du ahnst es sicher
schon) im Datenblatt...
Vielleicht liest du das verdammte Ding doch einfach mal, bevor du die
Hardware benutzt, die es beschreibt?
Hi
Ein kleiner Denkfehler meinerseits... Bei einem Zeitinterrupt von 25
µSek. und der von mir beschriebenen Vorgehensweise muss die Main in
weniger als 25 µSek. abgearbeitet sein. Das ist nicht grad viel Zeit und
deshalb macht es Sinn, die Befehle alle in die ISR zu packen. So ist
auch sichergestellt, das die Zeittaktung mit dem Zähler funktioniert.
Der Swap für die IO Bits ist einfach mit einer EOR-Maske erledigt und
der ADC wird lediglich gestartet. Das Ergebnis liefert dann seine eigene
ISR. Sollten weitere Zeitabhängige Jobs zuu erledigen sein, würde ich
einen zweiten Zähler bis 40 (0-39) zählen lassen und mir dann ein
mSek-Flag bilden. Das kann dann in der Main gepollt werden, denn eine
Zykluszeit < 1mSek. dürfte kein Problem sein.
Gruß oldmax
Hi
>Ein kleiner Denkfehler meinerseits... Bei einem Zeitinterrupt von 25>µSek. und der von mir beschriebenen Vorgehensweise muss die Main in>weniger als 25 µSek. abgearbeitet sein. Das ist nicht grad viel Zeit...
Nicht viel Zeit? Das sind bei 8MHz 200000 Takte oder ca. 120000...150000
Assemblerbefehle.
MfG Spess
spess53 schrieb:>>weniger als 25 µSek. abgearbeitet sein. Das ist nicht grad viel Zeit...>> Nicht viel Zeit? Das sind bei 8MHz 200000 Takte oder ca. 120000...150000> Assemblerbefehle.
µ nicht m. 'n Kaffee und nochmal :)
Hi
Ja, es war noch früh am Tag, aber auch meine Rechnung hat bei 8 MHz
ergeben 1/f=0,000000125Sek= 125nSek und das sind für 10 KHz 800 Impulse.
Da auf 40 KHz aufgelöst wird eben nur noch 200 und bei durchschnittlich
2 Takten pro Befehl bleiben ca. 100 Befehle.
Ich hoffe jetzt, ich hab mich da nicht auch noch verrechnet...
Gruß oldmax
oldmax schrieb:> Hi> Ein kleiner Denkfehler meinerseits... Bei einem Zeitinterrupt von 25> µSek. und der von mir beschriebenen Vorgehensweise muss die Main in> weniger als 25 µSek. abgearbeitet sein. Das ist nicht grad viel Zeit und> deshalb macht es Sinn, die Befehle alle in die ISR zu packen. So ist> auch sichergestellt, das die Zeittaktung mit dem Zähler funktioniert.> Der Swap für die IO Bits ist einfach mit einer EOR-Maske erledigt und> der ADC wird lediglich gestartet. Das Ergebnis liefert dann seine eigene> ISR. Sollten weitere Zeitabhängige Jobs zuu erledigen sein, würde ich> einen zweiten Zähler bis 40 (0-39) zählen lassen und mir dann ein> mSek-Flag bilden. Das kann dann in der Main gepollt werden, denn eine> Zykluszeit < 1mSek. dürfte kein Problem sein.> Gruß oldmax
Das rall ich nicht. Erst hauste hier einen raus alles muss mit Hardware
sein und jetzt gehste den weg von Georg, den du erst so Kacke fandest?
FLorian Unbekannto schrieb:> Erst hauste hier einen raus alles muss mit Hardware> sein und jetzt gehste den weg von Georg, den du erst so Kacke fandest?
Irgendwie bist du völlig von der Rolle und wirfst alles durcheinander -
ich finde nirgends, dass oldmax nicht meiner Meinung ist, im Gegenteil,
er hat zeimlich genau das Gleiche wie ich (schon im 3. Post)
vorgeschlagen und noch weiter ausgeführt. Mehr Hilfe geht nicht.
FLorian Unbekannto schrieb:> ey Georg,>> ich bin beratungsresistent.
Manchmal unterläuft dir doch versehentlich eine zutreffende
Einschätzung.
Georg
Hi Florian
Nimm einmal zur Kenntnis, niemals würde ich eine Programmlösung "Kacke"
finden, egal von wem. Vielleicht ungeeignet, so auch mein erster
Vorschlag, in der ISR einfach nur zu zählen und den Zähler in der Main
zu prüfen. Das funktioniert nämlich nur, wenn die Main eine Zykluszeit <
25µSek. hat. Nur in diesem Zeitraumwerden die Zählerstände sauber 0, 1,
2 und 3 immer erkannt. Braucht die Main länger, dann kommt schon mal
eine Lücke vor... 1, 0, 3, 1 usw. Also nicht zuverlässig und daher in
der Form ungeeignet. Das heißt aber nicht, das alles Blödsinn war,
sondern nur, das die Auswertung vom Wert in ISR_Step in der ISR erfolgen
muss. Das bläht aber die ISR nicht unnötig auf. Start-AD ist einfach nur
das Setzen des Bits zum Starten des ADC und den Bit_Swap erledigst du
mit
1
2
Push R16
3
Push R17
4
In R16, PortB
5
LDI R17, 0b00000011
6
EOR R16, R17
7
Out PortB, R16
8
Pop R17
9
Pop R16
Der Vorteil von Assembler ist halt, das man genau nachrechnen kann, wie
lange diese ISR braucht. Bei ca. 40 Befehlen und einer mittleren
Taktzahl von 1,5 landest du grob überschlagen bei 0,125*60 = 7,5 µSek.
Das sind auf 25 µSek bezogen ca. 35% Leitest du dir noch einen
Systemtakt ab, sind die 40% schnell erreicht oder gar überschritten.
Hier ist dann schon mal eine genaue Rechnung erforderlich. Das bedeutet
aber nicht, das deine Main keine zeit hat, aber die ISR wird vermutlich
in einem Programmzyklus mehrfach aufgerufen und verlängert sozusagen das
Programm. Ist bei jedem Rechteckssignal eine Messung erforderlich,
könnte auch hier der ein oder andere Wert verloren gehen. Vielleicht ist
aber auch nur jeder 5. oder 10. Wert erforderlich.
Gruß oldmax
O.K. auch wenn ich vorhin ab und an mal ansatzweise wirres Zeug
geschrieben hab hab ich meinen "Denkansatz" von vorhin weitergesponnen
und das ganze auf einen Atmega gebracht.
Eins vorweg: er läuft mit 16 MHZ darum habe ich auch ein 20kHZ Signal
wie man auf den Bilder sieht.
Bild 1: die beiden Rechtecke
-exakt synchron
-NULL Interrupts zum erzeugen
-leider auf dem Bild "ineinandergeschoben"(eins um 1 V nach oben das
andere um 1 V nach unten) aber ich denke man erkennt sie.
-alles in Hardware
Bild 2: der Triggerpunkt um die ADC zu starten.
- ein Interrupt pro 100us Periode
- Inhalt ist nur das manuelle starten einer ADC das ist ein Bit das
gesetzt wird. Wer das in Assembler ausrechnen möchte: gerne, ich hab
grade keine Lust dazu...
- zum debuggen wird ein PIN an CH2 des Oszilloskopes getoggelt
- CH1 ist das nicht invertierte Rechteck
Bild 3: während sich der uC in der ADC-complete ISR befindet wird CH2
des Oszilloskopes runtergezogen.
- ein Interrupt pro 100us Periode
- inhalt ist eigentlich nur das auslesen des ADC-Wertes in einen Puffer,
auch das sind nur wenige Takte
Den dazugehörigen C-Code poste ich gleich, muss ihn noch bisschen
kommentieren, sonst ist er nie und nimmer vorzeigbar...
Ich habe versucht auch auf die Funktion mit den 1000 Takten einzugehen,
die in der Main ist.
Mein Lösungsansatz ist einfach die Messwerte so lange aufzusummiern / zu
mitteln, wie die Main anderweitig beschäftigt ist. Falls dein
Spannungsteiler sich nicht so schnell ändert, sollte das gehen, wenn
nicht dann wars das halt...
oder du lässt einfach ein paar Messwerte aus...
Die ISRs jetzt noch in Assembler umschreiben wäre sicher auch eine
Option. Ich hab dem C-Compiler vertraut, dass er bei so kurzen ISRs
nicht zu viel Murks macht...
1
/*
2
* PWM10khz.c
3
*
4
* Created: 03.09.2014 13:07:57
5
* Author: Bernhard F.
6
*/
7
8
9
#include<avr/io.h>
10
#include<avr/interrupt.h>
11
#include<stdbool.h>
12
13
volatileboolADC_summe_changed=false;
14
volatileuint16_tADC_summe=0;
15
volatileuint8_tADC_zaehler=0;
16
uint16_tADC_ergebnis=0;
17
18
voidinitialisierung();
19
voidTIMER0_init();
20
voidTIMER1_init();
21
voidADC_init();
22
uint16_tTakte_1000();
23
24
25
ISR(ADC_vect)// einmal pro 100us
26
{
27
PORTC^=(1<<PC2);// debugpin toggeln
28
29
// grundsätzlich:
30
// buffer=ADC;
31
32
// versuch einer Mittelwertbildung wenn die Main noch nicht so weit ist
33
ADC_summe_changed=true;
34
ADC_summe=ADC_summe+ADC;
35
ADC_zaehler++;
36
37
38
PORTC^=(1<<PC2);// debugpin toggeln
39
}
40
41
ISR(TIMER0_COMPB_vect)einmalpro//100us
42
{
43
PORTA^=(1<<PA4);// debugpin toggeln
44
ADCSRA=ADCSRA|(1<<ADSC);//ADC start conversion
45
}
46
47
ISR(TIMER1_OVF_vect)// genau einmal zu beginn des Programms um Timer 0 auf Timer 1 zu synchronisieren
48
{
49
TCNT0=0;
50
TIMSK1&=~(1<<TOIE2);// Overflow interupt aus
51
}
52
53
54
intmain(void)
55
{
56
initialisierung();
57
PORTA^=(1<<PA4);
58
PORTC^=(1<<PC2);// debugpin toggeln
59
while(1)
60
{
61
Takte_1000();// do complex things
62
63
ADC_summe_changed=false;// es wäre blöd wenn der interrupt während dieser Berechnung zuschlägt -> vermeiden
64
do
65
{
66
ADC_ergebnis=ADC_summe/ADC_zaehler;
67
68
}while(ADC_summe_changed==true);
69
70
//TODO:: Please write your application code
71
}
72
}
73
74
75
voidinitialisierung()
76
{
77
// Debug PINS festgelegt und als Ausgang geschaltet
78
DDRA|=(1<<PA4);
79
DDRC|=(1<<PC2);
80
81
TIMER1_init();
82
ADC_init();
83
TIMER0_init();
84
85
sei();
86
}
87
88
89
voidTIMER0_init()
90
{
91
92
93
TCCR0B&=~(1<<WGM02);
94
TCCR0A|=(1<<WGM01);
95
TCCR0A&=~(1<<WGM00);// WGM 010 CTC Mode
96
97
98
TCCR0B&=~(1<<CS02);
99
TCCR0B|=(1<<CS01);
100
TCCR0B&=~(1<<CS00);// CS 010 für Prescaler 8
101
102
OCR0A=99;// Timer PER = 100us
103
OCR0B=8;// magic number eigentlich müsste hier 24 stehen um die mitte zu treffen aber dank
104
//später einsetzendem ADC und C overhead wird schon früher ausgelöst
105
106
TIMSK0|=(1<<OCIE0B);// Compare interupt a
107
108
109
}
110
111
voidTIMER1_init()
112
{
113
TCCR1A|=(1<<COM1A1);
114
TCCR1A&=~(1<<COM1A0);// 10 clear on match set @ bottom
115
TCCR1A|=(1<<COM1B1);
116
TCCR1A|=(1<<COM1B0);// 11 set on match clear @ bottom invert mode
117
118
TCCR1B&=~(1<<CS22);
119
TCCR1B|=(1<<CS21);
120
TCCR1B&=~(1<<CS20);// CS 010 für Prescaler 8
121
122
TCCR1B|=(1<<WGM13);
123
TCCR1B|=(1<<WGM12);
124
TCCR1A|=(1<<WGM11);
125
TCCR1A&=~(1<<WGM10);// WGM 1110 fast pwm 0-ICR1
126
127
ICR1=99;
128
OCR1A=49;
129
OCR1B=49;
130
131
TIMSK1|=(1<<TOIE2);// Overflow interupt
132
133
DDRB|=(1<<DDB6);// PB6 = OC1B
134
DDRB|=(1<<DDB5);// PB5 = OC1A
135
136
137
}
138
139
voidADC_init()
140
{
141
142
ADMUX=ADMUX&~(1<<REFS0);//Referenzspannung 1,1 Volt interne Referenz
143
ADMUX=ADMUX|(1<<REFS1);
144
145
ADMUX=ADMUX&~(1<<ADLAR);//Ergebnis rechtsbündig
146
147
ADMUX=ADMUX&~(1<<MUX4);
148
ADMUX=ADMUX&~(1<<MUX3);
149
ADMUX=ADMUX&~(1<<MUX2);//Singleended mit Gain = 1 adc1
150
ADMUX=ADMUX&~(1<<MUX1);
151
ADMUX=ADMUX|(1<<MUX0);
152
153
ADCSRA|=(1<<ADPS2);
154
ADCSRA&=~(1<<ADPS1);
155
ADCSRA&=~(1<<ADPS0);// 100 für preescaler 16
156
157
// ADCSRA|=(1<<ADATE); // auto Trigger enable
158
159
ADCSRA|=(1<<ADIE);// ADC Interrupt bei fertiger Wandlung
160
161
// ADCSRB&=~(1<<ADTS2);
162
// ADCSRB|=(1<<ADTS1); // auto start conversion bei Timer 0 Compare match a
163
// ADCSRB|=(1<<ADTS0);
164
165
166
ADCSRA=ADCSRA|(1<<ADEN);//ADC Enable
167
168
169
}
170
171
172
uint16_tTakte_1000()// einfach nur wirrer kram um eine lange funktion in die main zu setzen
173
{
174
uint16_ttemp1=200;
175
for(uint8_ti=0;i<=30;i++)
176
{
177
temp1+=(1<<(i&0b00001111));
178
}
179
returntemp1;
180
}
edit: Ich hoffe der Code ist nicht zu lang, um ihn direkt zu posten.
oldmax schrieb:> Nimm einmal zur Kenntnis, niemals würde ich eine Programmlösung "Kacke"> finden, egal von wem
Georg hatte recht, ich meinte auch den C-hater, der ist ja immer etwas
aufbrausender^^. War einfach vom ganzen coden verwirrt,....SRY
ADC_summe_changed=false;// es wäre blöd wenn der interrupt während dieser Berechnung zuschlägt -> vermeiden
2
do
3
{
4
ADC_ergebnis=ADC_summe/ADC_zaehler;
5
6
}while(ADC_summe_changed==true);
müsste ich nochmal überdenken. Könnte damit ein Deadlock produziert
haben, wenn die Division länger dauert als der Abstand zwischen 2
AD/Wandlungen...
Denn Bock hab ich aber sowiso geschossen.
Wenn die Werte ausgewertet werden müssen die Variablen ja auf 0
zurückgesetzt werden, sonst gibt es nen Überlauf. Das hatte ich
vergessen...
Besser wäre es die Variablen zwischenzupuffern.
Ich hab jetzt die Variablendeklaration der Puffer nicht mit in den
Codeschnipsel gehauen...
1
// es wäre blöd wenn der interrupt während dieser Berechnung zuschlägt -> vermeiden
2
do
3
{
4
ADC_summe_changed=false;
5
ADC_summe_puffer=ADC_summe;
6
ADC_puffer_zaehler=ADC_zaehler;
7
ADC_summe=0;
8
ADC_zaehler=0;
9
}while(ADC_summe_changed==true);
10
ADC_ergebnis=ADC_summe_puffer/ADC_zaehler_puffer;
Aber bevor ich mich so auf die Mittelung einschieße:
Wie willst du es handhaben wenn die Main noch beschäftigt ist aber ein
Messwert auftritt?
1) Willst du den Messwert fallen lassen?
2) Willst du nur jeden X. Wert aufnehmen ?
3) Willst du die Messwerte solange mitteln bis die main wieder zeit hat?
4) Willst du die Main so gestalten, dass sie nicht beschäftigt sein
kann?
5) Hast du ne besere Idee ?
FLorian Unbekannto schrieb:> Naja das habe ich mir schon durchgelesen. Aber 1.5 ADC Cycles ist jetzt> nicht sooo die Welt...Für Einschwingvorgänge eher suboptimal.
Du hast es vielleicht gelesen, aber definitiv nicht begriffen. 1.5
ADC-Cycles sind was völlig anderes als 1.5 Takte.
Im Minimum sind es drei Takte, denn der kleinstmögliche ADC-Prescaler
ist 2.
Nun hatte ich aber geschrieben, daß ein geeigneter ADC-Prescaler gewählt
werden sollte. 2 ist für die konkrete Anwendung aber nicht optimal
geeignet. Besser ausgedrückt: absolut ungeeignet.
Schon wieder so eine schwierige Division:
8.000.000/(2*13)=307692,3
Viel zu viel, du brauchst nur etwas mehr als 10kHz Samplerate. Mal ganz
abgesehen davon, daß bei 8.000.000/2=4MHz ADC-Clock die ADC im
Wesentlichen nur noch Müll liefern würde.
Du suchst also erstmal nach den bekannten Methoden einen wirklich
geeigneten ADC-Prescaler. Der ist 32. Damit erreichst du nämlich eine
Samplerate von ca. 19kHz, der nächstgrößere Prescaler würde nur noch ca.
9,5 kHz erlauben, was definitiv weniger ist als die 10kHz deiner PWM und
damit sicher ungeeignet, um in jedem PWM-Zyklus eine Messung zu machen.
Was bedeutet nun ein ADC-Prescaler von 32?
Zum einen bedeutet es, daß du 250kHz ADC-Clock benutzt und damit
wenigstens nicht allzuweit jenseits dessen bist, wofür Atmel 10Bit
Auflösung garantiert. Mit effektiv neuneinhalb Bit oder so kann man
meist auch noch sehr gut leben.
Weiterhin bedeutet es, daß ein ADC-Cycle 32 MCU-Takte umfaßt,
1.5ADC-Cycles also 48 MCU-Takten entsprechen. Der Meßzweitpunkt liegt
also bei 12% des zeitlichen Meßfensters zwischen zwei Pegelwechseln.
c-hater schrieb:> Weiterhin bedeutet es, daß ein ADC-Cycle 32 MCU-Takte umfaßt,> 1.5ADC-Cycles also 48 MCU-Takten entsprechen. Der Meßzweitpunkt liegt> also bei 12% des zeitlichen Meßfensters zwischen zwei Pegelwechseln.
Oops, die Interpretation vergessen:
12% ist im Allgemeinen schon ein recht günstiger Zeitpunkt. Kurzzeitige
Überschwinger nach den Pegelwechseln sollten hier weitestgehend
abgeklungen sein. Wenn nicht, stimmt was mit der betriebenen Schaltung
oder dem Layout der Leiterplatte nicht. Das hat dann typisch weit
schlimmere Folgen als nur einen versauten Meßwert...
FLorian Unbekannto schrieb:> Georg hatte recht, ich meinte auch den C-hater, der ist ja immer etwas> aufbrausender^^. War einfach vom ganzen coden verwirrt,....SRY
Nur hatte c-hater zu deinem Code noch überhaupt garnichts geschrieben.
Der arme Mann muß nämlich tagsüber i.d.R. malochen und hat dann oft
keine Zeit, sich mit solchen Trivialitäten zu beschäftigen...
Hätte er es allerdings getan, wäre die Kritik allerdings in der Tat
absolut vernichtend gewesen, das hast du sicher geahnt und deshalb
c-hater in der Art einer selbsterfüllenden Prophezeiung eine solche
Äußerung schonmal unterstellt, bevor sie überhaupt erfolgt ist...