Hallo liebe Mitstreiter,
Ich weis, manche können die Überschrift schon gar nicht mehr lesen,
schon wieder einer der es nicht hinbekommt. Da ich mich ja nun geoutet
habe
(mein Gott wie das klingt) hoffe ich doch Ihr helft mir weiter...Danke
im vorraus allen Beteiligten.
Nun denn, ich bin seit Wochen damit beschäftigt eine vernünftige
Ladereglung für eine Projektarbeit zu konstruieren.
Das größte Problem war bisher der StepDown und dessen Ansteuerung, was
aber nun halbwegs zufriedenstellend funktioniert.
Nun ist es aber so das der StepDown lastabhängig fungiert, heist wenn
ich z.B. eine 12V/20W Halogenlampe anschließe muss ich mit anderen
Vorladewerten (tc0/ocr0) arbeiten als mit einem Akku .
Ich versuche gerade mittels Vergleich von einer vorher ermittelten
festen Spannungsvorgabe von 13.6V die sich ändernde Ausgangsspannung am
StepDown zu regeln.
Wie Ihr euch wahrscheinlich denken könnt ohne Erfolg ( sonst gäbe es ja
diesen Beitrag nicht).
Verwendete Schaltung und Programm sind dabei, vielleicht hat ja einer ne
Idee, auch wie man es vielleicht anders machen könnte aber ohne fertige
Spannungsregler wie z.B lm2596 und die anderen
üblichen Verdächtigen (find ich herrlich diese Formulierung)
Ich hab auch schon viel selber recherchiert, aber irgendwie nix
brauchbares gefunden.
Für die C-Freunde unter euch, es muss in Assembler sein weil ich das
langsam zu verstehen anfange.
Gruß Werner
Ich hab noch vergessen zu erwähnen das als Spannungsquelle für den
Versuchsaufbau ein Kleinspannungsstelltrafo mit Gleichspannungsausgang
fungiert, welchen ich auf 17V gestellt habe.
Gruß Werner
Ich find schon mal unschön, daß der FET nicht aktiv gesperrt wird. Ohne
einen vernünftigen FET-Treiber brauchst Du keinen Step-Down bauen weil
das vom Wirkungsgrad her nicht viel besser wird als ein Linearregler. So
ein Wandler braucht 40-50kHz damit er vernünftig funktioniert. Unter 10
Mhz CPU-Takt für einen 8 Bit Timer würd ich da nichts probieren (gibt
dann 39kHz für die PWM).
EDIT: die 5V an ARef sind auch mal **würg**... Genau wie das nicht
verstandene und nur ausprobierte Widerstandsgewirre um den Transistor.
Ich glaube wir sollten erstmal die Grundlagen durchkauen.
werner schrieb:> Für die C-Freunde unter euch, es muss in Assembler sein weil ich das> langsam zu verstehen anfange.
Mit Verlaub, aber das ist ganz großer Unsinn, solange du sowas ...
1
call timer0ini ;Timer 0 initialisieren
2
call adini ;A-D Wandler AD0 initialisieren
3
4
....
5
6
adini:
7
cli
8
ldi temp1, 0b01000000 ;;AREF, linkssbündig,pa0
9
out ADMUX, temp1
10
ldi temp1,0b11101101
11
out ADCSR,temp1 ;adcen,adcie,adcfr,teiler 32
12
sei ;Interrupts global aktivieren
13
jmp main
14
*********
... schreibst.
Du hast Assembler nicht verstanden und du verfügst auch nicht über die
Programmierdisziplin, die dazu notwendig ist. Du machst dir nur selber
das Leben schwer, indem du keinen Compiler benutzen willst, der dich von
diesem ganzen Kleinkram befreit.
- Wieso initialisierst du den Stack doppelt? (einmal falsch, dann
richtig)
- Wieso verarbeitest Du am Anfang irgendwelche ADC-Ergebnisse obwohl
noch gar keine Wandlung gestartet wurde?
- Was sollen die NOPs?
- Dein Programm hat keinerlei Zeitbasis, es ist sehr wahrscheinlich daß
es ständig zwischen irgendwelchen PWM-Werten hin und her pendelt ohne
sich auf den Akku einstellen zu können...
- Feste RAM-Adressen sind K*cke, da sollte man Labels verwenden.
- Wo ist die Stromregelung fürs Laden? Wenn Du da einen Akku dranklemmst
der 50A wegschluckt wird Dein Programm niemals die Akkuspannung
erreichen. Du kannst also nicht nur auf die Akkuspannung einregeln...
- Jo, Funktionen nicht mit RET abgeschlossen bzw. Stackprinzip nicht
verstanden... nur der Vollständigkeit halber.
Das Programm hat gravierende Fehler und Unschönheiten und kann IMHO nie
funktioniert haben.
Ich erkenne keinen Sinn in .org 0x30.
Der Stack wird zweimal initialisiert, Code wirkt dadurch
zusammenkopiert.
Konfigurationen werden max. unportabel und unlesbar in
Absolutschreibweise gemacht, obwohl es Bitnamen dafür gibt. (Folgendes
gemäß Kommentare; Absolutwerte kontrolliere ich nicht mehr)
Der ADC wird ausgelesen, ohne dass geprüft wird, ob ein regulärer Wert
vorliegt. Die erste Wandlung wird ausgelesen, ohne dass überhaupt eine
Wandlung gestartet wurde.
Es werden IRQs (adcie, ocie0) freigegeben für die es keine ISR gibt.
Es wird eine Variable wild ins RAM (0x0072) gespeichert (und nie daraus
gelesen). Wenn es eine dauerhafte Sicherung sein soll, liegt liegt
eventuell ein Fehlverständnis RAM EEPROM vor. Temporäre Sicherung im RAM
könnte einfacher mit dem Stack gemacht werden.
Bei Prescaler 0 (ori r17, 0b01101010 ;tccr0, fast pwm, prescaler 0,
lösche oc0 bei gleichstand) läuft kein Timer!
In der Initialisierung von Timer0 ist nur ein Teil der Initialisierung
vorhanden. Es fehlt die Angabe des Vergleichswertes.
Der Code hat Durchfall (nach der Stelle brlo down).
Vielen Dank für Euere Antworten,
Ben
"die 5V an ARef sind auch mal **würg**... Genau wie das nicht
verstandene und nur ausprobierte Widerstandsgewirre um den Transistor."
werden die 5V denn nicht so an ARef angeschlossen, ich dächte das so
gelesen zu haben.
Und vollkommen richtig erkannt mit dem "nur ausprobierte
Widerstandsgewirre um den Transistor" weil ich nur so ein brauchbares
Signal am Fet habe und er durchschaltet und damit Uds gering bleibt.
Ich habe verschiedene Ansätze zu Ansteuerung probiert, unter anderem
angehängtes Beispiel. Habe den Emitterwiderstand so dimensioniert das
ca. 13mA fließen und über dem Kollectorwiderstand 10V abfallen aber ohne
Erfolg. Das "nur ausprobierte Widerstandsgewirre um den Transistor" war
bis dato die beste Lösung.
Karl Heinz Buchegger
Wenn du schreibst das das Unsinn ist was ich da geschrieben habe, dann
glaub ich Dir das erstmal, aber ich habe trotzdem keinen Schimmer was da
Unsinn ist! Avr-Studio hat es nicht angemeckert.
Vielleicht hast du ja einen genauern Hinweis was verkehrt ist.
Gruß Werner
Ein Assembler meckert über nichts außer Syntaxfehler. Ansonsten kannst
Du jeden Scheiß machen (das ist ja das Tolle an Assembler). Du kannst
gleich mit der ersten Anweisung ins Nirvana springen oder den Stack in
die IO-Register klatschen... Das meiste davon ist Unsinn, manches birgt
ungeahnte Möglichkeiten, die eine Hochsprache wie C niemals zulassen
würde.
Der Haken an der Sache ist meiner Meinung, daß Du einfach nicht der
geborene Programmierer bist. Du hast viele logische Fehler drin,
entweder weil Du nicht programmieren kannst und noch Übung brauchst oder
weil Du den Aufbau eines (Assembler-) Programms noch nicht verstanden
hast. Das Programm macht wirklich einen sehr zusammenkopierten
Eindruck... so als hättest Du Flicken zu je drei-fünf Zeilen aus
irgendwelchen Beispielen herauskopiert ohne daß Du sie oder das ganze
Programm verstehst.
An ARef gehört ein 100nF gegen Masse. Die 5Vcc als Referenz werden dann
intern gewählt. AVcc gehört sauber entstört wenn Du gerade so dicht an
einem Schaltregler akkurate Messergebnisse haben willst.
Dein Gate-Treiber ist vom Prinzip her richtig, aber unnötig kompliziert.
Da wurde versucht mit den Widerständen die Gate-Spannung zu begrenzen
damit's dem FET bei 36V nicht das Gate zerreißt. Da gibts einfachere
Varianten solange Du unter 20V Eingangsspannung bleibst.
Hallo Krapao,
das der Stack 2 mal intiallisiert wird ist mir simmulieren aufgefallen,
aber ich habe keinen Grund dafür gefunden.
Du hast recht mit dem auslesen des adch,adcl ..ich hatte den Start erst
vor dem Auslesen stehen, dachte dann aber das die Zeit zum Auslesen
nicht reicht um hab es verändert. Ich hatte folgende Abfrage drin:
fertig:
sbis ADCSR,ADIF ;warten bis wandlung
jmp fertig ;beendet
aber dort blieb der Simulator immer hängen.
Prescaler ist 8, habe ich im Kommentar vergessen zu ändern, sorry.
Der ocr0 Vergleichswert soll nach der Wandlung und dem Vergleich mit
v_store
ins ocr0 geschrieben werden.
Wenn ich das lese dann hab ich den Timer0 bis jetzt wohl immer falsch
verwendet, ich habe immer mit dem Verändern von ocr0 die Pulsweite
varriert??
Gruß Werner
werner schrieb:> Karl Heinz Buchegger> Wenn du schreibst das das Unsinn ist was ich da geschrieben habe, dann> glaub ich Dir das erstmal, aber ich habe trotzdem keinen Schimmer was da> Unsinn ist! Avr-Studio hat es nicht angemeckert.
Das bestärkt mich eigentlich noch viel mehr in meiner Meinung, dass du
Assembler nicht verstanden hast.
Du argumentierst wie jemand, der sagt, dass er "Deutsch als
Fremdsprache" verstehen würde und dann
"Das U-Boot schält die Gabel im Keller des Lastwagens."
von sich gibt.
Formal, also grammatikmässig ist das ein völlig legitimer deutscher
Satz, an dem es nichts auszusetzen gibt. Jede Syntaxprüfung würde dir
ein "OK" bescheinigen.
Und trotzdem ist der Satz so unsinnig, wie er nur sein kann.
Hallo Ben
die 36V Variante ist hier aus dem Forum, ich hab das für 18V
ausgerechnet und es hat trotzdem nicht funktioniert. Du schreibst für
unter 20V gibts besser Möglichkeiten, ja bitte ich bin für jeden
Vorschlag dankbar.
Wie im Schaltplan beschrieben liegt Vpmax bei 17,35V.
Klar hab ich mir anderen ihren Code angesehen und hab dann alles so
geschrieben wie ich mir das vorstelle.
Die Auswahl mit dem Vergleich hab ich mir komplett selber ausgedacht.
Den Rest hab ich Anhand der Dokumentation zusammengesucht.
Gruß Werner
und mit dem 'Plan' kannst du jetzt mal anfangen, dein Programm zu
schreiben.
Aber ohne so einen Plan, einfach drauflos programmieren, wird das
nichts. In diesem Ablaufplan kannst du erst mal losgelöst von jeglicher
Programmiersprache überprüfen, ob deine Logik sinnvoll ist.
Sorry für den Plan als JPG, gabs bei Gugel nicht anders.
Für Dein aktuelles Projekt würde ich die Interrupts erstmal komplett
vergessen, dafür braucht man sie nicht unbedingt. Also alles raus was
damit zu tun hat. Damit kannst Du mal in einem Testsystem rumspielen um
zu lernen wie das funktioniert.
Mach Dich unbedingt schlau wie der Stack funktioniert. Sonst kriegst Du
kein einziges Unterprogramm in Assembler zustande. Stichworte:
CALL/RCALL/RET, PUSH/POP.
EDIT: @Karl Heinz Buchegger
Dein Diagramm ist toll, aber dafür brauche ich kein Programm. Das kann
jeder Step-Down-Regler-IC in Hardware... ;)
Ben _ schrieb:> EDIT: @Karl Heinz Buchegger> Dein Diagramm ist toll, aber dafür brauche ich kein Programm. Das kann> jeder Step-Down-Regler-IC in Hardware... ;)
Wenn er es doch programmieren will :-)
Hallo Karl Heinz
ja so ähnlich hab ich das auch gemacht und was dabei rausgekommen ist
hast du ja gesehen, hab allerdings den PWM-Wert nicht mit einbezogen
sondern mich dann nur auf die enstehende Vergleichsspannung
konzentriert.
Ist denn mein Gedankengang dahingehend richtig den ocr0 Wert zu
verändern?
Es wird auch bemängelt das ich oben .org 0x30 geschrieben habe, müssen
denn nicht die Interuptvektoren übersprungen werden. Ich habe
festgestellt das manche die Vektoren mit reinschreiben und manche nicht.
Wäre das so besser:
.org adccaddr
jmp adini ;Einsprung AD - Wandlung mit Interrupt
.org ovf0addr jmp timer0ini ;Einsprung Timer 0 Interrupt
das hab ich probiert und eine Fehlermeldung erhalten.
Gruß Werner
Hallo Ben,
danke für den Plan, probier ich morgen gleich aus.
Was ist den an der Stackpointerinitialisierung falsch, es wird das obere
und
unter Ende des SRAM definiert.
LDI temp1, high (RAMEND)
OUT SPH, temp1 ;SPH am Ende phys. SRAM
LDI temp1, low (RAMEND)
OUT SPL, temp1 ;SPL am Ende phys. SRAM
??????????????????
oO ... Bitte befasse Dich auch mit 16 Bit Zahlen auf einem 8 Bit µC.
Dann verstehst Du was es mit SPH,SPL und high(), low() auf sich hat.
Das untere Ende vom RAM interessiert den Stack bzw. den µC nicht. Wenn
Du zuviel auf den Stack legst z.B. mit
labelx: push r16
rjmp labelx
dann schreibt er Dir gnadenlos bis in die IO-Register alles mit dem
Inhalt von R16 voll und crasht deswegen irgendwann.
Die Interrupts machen wir später... Nur soviel: Jeder Interrupt braucht
in der Interrupttabelle eine Ansprungaddresse. Diese ist fest vorgeben
und enthält den Befehl was der µC machen soll wenn ein Interrupt
aufgetreten ist - meistens ein JMP isrX. Steht dort nichts weil keine
Interrupttabelle definiert wurde dann interpretiert der µC dieses Nichts
als Befehl und macht irgendwas was Du nicht willst... Endet so gut wie
sicher mit einem Crash des Programms. Sprich ohne Interrupttabelle (und
Stack) kannst Du keine Interrupts verwenden.
Was Deinen Fehler dazu angeht: Mir kommts langsam so vor als ob Du nicht
mal das Datenblatt gelesen hast... Mal eben kopiert aus dem Datenblatt
des ATMega32:
---[schnipp]----------
$000 jmp RESET ; Reset Handler
$002 jmp EXT_INT0 ; IRQ0 Handler
$004 jmp EXT_INT1 ; IRQ1 Handler
$006 jmp EXT_INT2 ; IRQ2 Handler
$008 jmp TIM2_COMP ; Timer2 Compare Handler
$00A jmp TIM2_OVF ; Timer2 Overflow Handler
$00C jmp TIM1_CAPT ; Timer1 Capture Handler
$00E jmp TIM1_COMPA ; Timer1 CompareA Handler
$010 jmp TIM1_COMPB ; Timer1 CompareB Handler
$012 jmp TIM1_OVF ; Timer1 Overflow Handler
$014 jmp TIM0_COMP ; Timer0 Compare Handler
$016 jmp TIM0_OVF ; Timer0 Overflow Handler
$018 jmp SPI_STC ; SPI Transfer Complete Handler
$01A jmp USART_RXC ; USART RX Complete Handler
$01C jmp USART_UDRE ; UDR Empty Handler
$01E jmp USART_TXC ; USART TX Complete Handler
$020 jmp ADC ; ADC Conversion Complete Handler
$022 jmp EE_RDY ; EEPROM Ready Handler
$024 jmp ANA_COMP ; Analog Comparator Handler
$026 jmp TWI ; Two-wire Serial Interface Handler
$028 jmp SPM_RDY ; Store Program Memory Ready Handler
---[schnapp]----------
Da hast Du alle Interruptadressen... Wird ein Interrupt nicht benötigt
sollte man anstelle des JMP einen RETI schreiben. Das verhindert
meistens einen Crash wenn ein undefinierter Interrupt wider Erwarten
doch mal auftritt.
Weiter gehts dann mit:
RESET: ldi r16,high(RAMEND) ; Main program start
out SPH,r16 ; Set Stack Pointer to top of RAM
ldi r16,low(RAMEND)
out SPL,r16
se_ NEIN das darfst Du erst machen wenn Du den ganzen Rest
verstanden hast und Dir einigermaßen sicher bist, daß Du damit umgehen
kannst.
Wenn der Timer korrekt läuft und Dein Programm nicht laufend durch
Stack-Overflows oder irgendwelche undefinierten Interrupts in den Reset
getrieben wird dann kannst Du mit OCR0 problemlos das PWM-Verhältnis
einstellen. Aber vorher machste Dich erstmal schlau mit welcher Frequenz
so ein Step-Down laufen muß oder am Besten... wie so ein Ding
funktioniert!
> Es wird auch bemängelt das ich oben .org 0x30 geschrieben habe, müssen> denn nicht die Interuptvektoren übersprungen werden. Ich habe> festgestellt das manche die Vektoren mit reinschreiben und manche nicht.
Das hängt vom Programmierstil, der Programmlogik und von den vorhandenen
ROM-Resourcen ab.
Zunächst muss man wissen, dass der AVR bei Adresse 0 startet. Dort
gehört also der Sprung in die eigene erste Programmanweisung hin. Dein
.org 0x30 funktioniert nur, weil der Programmzähler von Adresse 0 über
ein paar NOPs auf deinen JMP an Adresse 0x30 schlittert...
Hinter dem ersten Sprung ab Adresse 0 sind Adressen, die beim Auftreten
von Interrupts von der Interrupt-Hardware des AVR angesprungen werden.
Das ist die sog. Interruptvektortabelle.
Hat man viel Platz, lässt man allen Interruptvektoren ihren Platz in der
Tabelle und richtet eventuell auch noch die unbelegten Vektoren auf
einen gemeinsamen ISR Programmcode.
Ist dieser Platz aber zu kostbar, weil man knapp an ROM ist und will man
dort statt Vektoren das letzte Quentchen eigenen Code haben, kann man
seinen Hauptcode mit .org an bestimmte Positionen legen z.B. hinter den
letzten benutzten Interruptvektor.
> Dein .org 0x30 funktioniert nur, weil der Programmzähler von Adresse 0> über ein paar NOPs auf deinen JMP an Adresse 0x30 schlittert...
Sorry, du hast das richtig, du hast den JMP an Adresse 0 und du
überspringst die Interruptvektoren mit dem .org. das ist richtig so.
> .org adccaddr> jmp adini ;Einsprung AD - Wandlung mit Interrupt>> .org ovf0addr jmp timer0ini ;Einsprung Timer 0 Interrupt>> das hab ich probiert und eine Fehlermeldung erhalten.
Im Prinzip richtig
1
JMP start
2
3
.org OVF0addr
4
jmp timer0ini
5
6
.org ADCCaddr
7
jmp adini
8
9
.org 0x30 ; optional
10
start:
Bezog sich die Fehlermeldung auf Schreibweise (hier Großschreibung gemäß
m16def.inc) und Anordnung (hier aufsteigend gemäß m16def.inc)
> .org ovf0addr jmp timer0ini ;Einsprung Timer 0 Interrupt>> das hab ich probiert und eine Fehlermeldung erhalten.
Das funktioniert nur nicht weil das .org und der JMP nicht auf der
gleichen Zeile stehen dürfen.
Hallo Ben,
vielen Dank für deine Zeilen, ich hab jetzt gesehen was Krapao gemeint
hat mit den Stack 2 mal initialisieren, war ja totaler Mist von mir.
Ich hab den Code mal geändert (Stack,Interruptvekt.,AD-Wandlung) und
dran gehangen... ist das so richtig?
Hab gerade Krapo´s Text gesehen. Die Fehlermeldung weis ich nicht mehr,
und ja ich habs in eine Zeile geschrieben.
Ich hab die Interruptvekt.Tabelle eingefügt und hab wieder
Fehlermeldungen,
(mit+++++++gekennzeichnet) wenn das mit der Tabelle irgendwann
funktioniert muss ich dann weiter unten den Timer0 und den AD-Wandler
nochmal mit call aufrufen.
Gruß Werner
Die Fehlermeldung bei ++++++++++++ kann davon kommen, dass TIM0_OVF und
ADC unkompatibel definierte Namen sind oder weil du diese Funktionen
nirgends im Code hast.
Hallo Krapao,
erstmal vielen Dank für Eure Hilfe, ich hab den Code nochmal geändert,
klar wenn man drüber nachdenkt oder draufgestoßen wird ist es logisch
das da die Bezeichnungen hin müssen die im Programm stehen.
Du hattest oben geschrieben "Der Code hat Durchfall (nach der Stelle
brlo down)." , was meinst du damit, ist die Verzweigung falsch gemacht?
Gruß Werner
Hallo Krapao
der Timer0 soll eigentlich keinen Interrupt auslösen.
Er soll nur eine konstante Frequenz ausgeben und ich will mit dem
Vorladewert
in ocr0 spielen, oder hab ich da einen (ha "einen") Denkfehler?
Gruß Werner
cp v_store, v_akku ;vergleiche gespeicherte mit gemessener
2
brsh up ;akkuspannung, wähle aus ob höher oder niedriger
3
brlo down ;und springe zu up oder down
4
; ###
5
down:
6
dec v_akkupwm ;akkuspannung zu hoch, zaehle runter ;
ist gemeint. Allgemein erwartet man bei einem Muster
1
Rechenanweisung zum Prüfen
2
Wenn x zutrifft mache Aktion zu x
3
Wenn y zutrifft mache Aktion zu y
4
Wenn weder x,y zutrifft mach ganz was anderes
5
6
Aktion X
7
...
8
9
Aktion Y
10
...
In deinem Code fehlt "Wenn weder x,y zutrifft mach ganz was anderes"
sondern an dieser Stelle stehen Anweisungen aus Aktion X (down: dec
v_akkupwm...). Der Programmzähler fällt durch...
In diesem Fall ist das nicht kritisch, weil BRSH und BRLO komplementär
zu einander sind, d.h. wenn die eine nicht zutrifft, dann die andere.
Und beide ändern das Ergebnis (Carry) der CP Prüfung nicht.
Bei komplexerem Code programmiert man so schnell Fehler. Anschaulicher
ist der Code IMHO, wenn er so geschrieben ist:
;auswahl
cp v_store, v_akku
brsh up ; Sprung wenn v_store >= v_akku
brlo down ; Sprung wenn v_store < v_akku
jmp main
Down:
...
Wenn du gemäß Kommatar "ocie0-löst beim erreichen das ladewertes irq
aus" einen IRQ auslöst, musst du eine ISR haben.
Wenn dein Code anders als der Kommentar keinen IRQ auslöst, brauchst du
keine ISR zu haben. Man kann eine Hardware-PWM ohne ISR programmieren,
wenn man keinen IRQ auslöst (enabled). Das ist eine Einstellungssache
der Bits u.a. in TIMSK.
Hallo Krapao
Einen hab ich noch,
ich hab gestern beiligenden Code wirklich selber geschrieben, natürlich
mit den heute gelernten Anpassungen versehen.
Ein 20k Poti an PA0 regelt dort die Pulsweite mittels ocr0.
Das Wandlungsergebnis muss verstärkt werden (mul) sonst passiert nicht
viel.
Mit dem Versuch wollte ich heute weitermachen und die Spannung selber
Regeln lassen...das war eigentlich das Ziel.
Danke nochmal, vielleicht schaut ja nochmal einer rein.
Gruß Werner
Hi
>Danke nochmal, vielleicht schaut ja nochmal einer rein.>jmp RESET ; Reset Handler>reti ;EXT_INT0 ; IRQ0 Handler>reti ;EXT_INT1 ; IRQ1 Handler <<<<<<<<<<<<<<< Müll
In der Interruptvektortabelle belegt jeder Eintrag 2 Word (jmp). Ein
'reti' belegt aber nur 1 Word. Damit sind die nachfolgenden Einträge
falsch.
MfG spess
Hi
>Alle mit ++++
Kann es sein, das dir einfach die Grundlagen fehlen?
Selbstverständlich meckert er dich an, wenn du ein 'jmp' ohne Sprungziel
verwendest. Ein Programmgerüst für einen ATMega32 würde bei mir so wie
im Anhang aussehen.
Mfg Spess
> danke erstmal, also muss die irq, die ich gar nicht brauche auch nicht> hinschreiben, oder?
Du hast zwei Optionen
(Beitrag "Re: Hilfe Ladereglung Atmega16 Assembler"):
1/ Alle hinschreiben. Dabei dürfen nicht benutzte auch auf die gleiche
ISR verweisen z.B. eine die man unbenutzten_irg_handler nennt.
2/ Nicht alle hinschreiben, nur die benötigten. Diese und der Beginn des
Programmcodes (i.A. die Stackinitialisierung und folgenden Code) müssen
aber an den richtigen Adressen liegen. Das macht man mit passenden .org
<adresse> Assembleranweisungen. Um sich nicht zu vertun, ist <adresse>
meist keine direkte Zahl(Adresse), sondern ein vordefinierter Makroname
aus dem Includedatei des betreffenden µCs z.B. ADCCaddr
Egal ob Option 1 oder 2: Alle ISRs deren Sprungadressen in der Tabelle
eingetragen sind, müssen auch als Programmcode im Programm vorhanden
sein. Andernfalls Absturz beim Auftreten des IRQs. Alle ISRs enden mit
einem RETI.
Hi
>danke erstmal, also muss die irq, die ich gar nicht brauche auch nicht>hinschreiben, oder?
Wohin?
Du ersetzt für einen benutzten Interrupt die entsprechende Zeile
xyz: reti
durch deinen Code für den Interrupthandler. Fertig.
MfG Spess
Hallo Spess
Ich hab die Interrupttabelle des Atmega16 jetzt eingefügt und Fehler
bei der AD Wandlung beseitig. Jetzt funktioniert auch das Warten
auf das Ende Wandlung . Dann hab ich noch den Interrupt beim timer0
rausgenommen.
Aber das Programm tut immer noch nicht was es soll, nähmlich die
Ausgangsspannung durch Vergleich mit einem Sollwert und der aktuellen
Ausgangsspannung durch Verändern des Duty Cycle zu regeln.
Hat noch jemand ne Idee????
Gruß Werner
du kannst dir gar nicht vorstellen, wie verhasst diese Schreibweise im
Forum ist. Um dir zu helfen, bleibt nichts anderes übrig, als das
Datenblatt rauszukramen und mit viel Scrollen zu identifizieren, welche
Bits da auf 1 gesetzt sind um erst mal rauszukriegen, wie sie heißen und
dann (wieder mit viel gescrolle) nachzulesen, was dieses Bit bewirkt.
Ok, den letzten Teil weiß man oft auswendig, dazu sind ja die Namen da.
Aber an welcher Bitposition im Register welches Bit sitzt, wissen die
wenigsten auswendig. Und da interessiert auch dein Kommentar nicht. Denn
der kann falsch sein.
Warum nicht
das hast du aber so nicht aus dem Tutorial. Man kann natürlich ADIF zur
ABfrage benutzen, ob der ADC fertig ist. Nur: Wenn kein ADC Interrupt
gemacht wird, dann ist man selber dafür zuständig dieses Flag wieder zu
löschen. Daher wird im Tutorial ganz einfach ADSC für diesen Zweck
abgefragt: Dein Programm setzt es auf 1 und wenn der ADC fertig ist
setzt er es wieder auf 0. Ziemlich banal.
Tritt zwar bei dir nicht auf - aber
reti ;TIMER1 COMPA ;Timer/Counter1 Compare Match A
8
reti ;TIMER1 COMPB ;Timer/Counter1 Compare Match B
9
reti ;TIMER1 OVF ;Timer/Counter1 Overflow
10
jmp timer0ini ;Timer/Counter0 Overflow
11
reti ;SPI, STC ;Serial Transfer Complete
12
reti ;USART, RXC ;USART, Rx Complete
13
reti ;USART, UDRE ;USART Data Register Empty
14
reti ;USART, TXC ;USART, Tx Complete
15
jmp adini ;ADC Conversion Complete
16
reti ;EE_RDY ;EEPROM Ready
17
reti ;ANA_COMP ;Analog Comparator
18
reti ;TWI ;Two-wire Serial Interface
19
reti ;INT2 ;External Interrupt Request 2
20
reti ;TIMER0 COMP ;Timer/Counter0 Compare Match
21
reti ;SPM_RDY ;Store Program Memory Ready
Bist du dir ganz sicher, dass die adequate Reaktion auf das Auftreten
eines Timer bzw. ADC Interrupts darin besteht, das 'Gerät' neu zu
initialisieren? Ausserdem: Ich habs zwar auch nicht mehr 100% im Kopf,
aber ich bin mir ziemlich sicher, dass das falsch ist, weil ein reti
nicht die richtige Befehlslänge hat, damit sich bei den vielen reti in
Folge es sich so ausgeht, dass die jmp an der richtigen Stelle sitzen.
Hä?
Wenn du nach Up kommst, steht schon fest, wie das Verhältnis von v_store
zu v_akkupwm ist. Du musst da nichts mehr abfragen. Und vor allen Dingen
musst du nicht in einer Schleife solange v_akkupwm erhöhen, bis die sich
angenähert haben. Da kannst du auch gleich ganz einfach v_akkupwm auf
den Wert von v_store setzen! Das hat genau den gleiche Effekt.
Wenn du EINMAL nach up kommst, dann wird der Wert v_akkupwm um 1 erhöht!
Nicht mehr. Das wars schon. Mehr ist nicht zu tun. Der um 1 erhöhte Wert
wird dann an die PWM gegeben, woraufhin sich die Spannung erhöht.
Theoretisch. Danach beginnt wieder alles von vorne, mit dem ADC wird die
jetzt anstehende Spannung gemessen und wenn die dann immer noch kleiner
als die Vorgabe ist, dann wird eben wieder um 1 erhöht - im nächsten
Durchlauf, bei der nächsten Auswertung der gemessenen Spannung.
Jetzt hab ich dir so ein schönes Bildchen gemalt. Aber scheinbar hast du
es nicht studiert - oder nicht verstanden.
Ausserdem hast du etwas ganz wesentliches übersehen.
Gesetz den Fall, das alles würde so erst mal korrigiert, funktioniert
deine Regelung immer noch nicht. Warum? Weil die PWM erst mal mindestens
1 Periode benötigt, ehe sich der neue OCR Wert überhaupt auf die externe
Spannung auswirkt. Je nach Aussenbeschaltung dauert das sogar noch
länger.
werner schrieb:> Hat noch jemand ne Idee????
Du solltest zuerst mal die grundsätzlichen Fehler, die bereits mehrfach
genannt wurden, korrigieren:
- Karl Heinz Buchegger:
"adini:" muss mit "ret" enden (nicht mit "jmp main"), da Du es mit "call
adini" als Unterprogramm aufrufst.
- spess:
In der Interruptvektortabelle belegt jeder Eintrag 2 Word (jmp). Ein
'reti' belegt aber nur 1 Word. Damit sind die nachfolgenden Einträge
falsch.
Lösung: schreibe statt "reti" besser "jmp noint"; dann irgendwo
"noint:
reti"
(bei allen nicht benutzten Interrupts Sprung zur Marke "noint" = kein
Interrupt, wo dann der "reti" gemacht wird).
Gruß Dietrich
Hallo Dietrich,
danke für Deine Antwort, ich hab es jetzt so eingepflegt wie du
geschrieben hast ,und Spess53 auch.
Ich kenn das nicht das man da eine Interruptvektortabelle anlegen muß,
sonder nur das die Interruptvektoren mit einem Sprung ala "0x30" zu
überspringen sind damit in dem Bereich keine Programme laufen.
Frage: Also ist die Interruptvektortabelle immer notwendig oder nur wenn
man auch Interrupts verwendet?
Das mit dem jmp main hab ich vergessen gestern noch rauszunehmen . Misst
wenn mans nicht gleich macht vergissts man es wieder.
Gruß Werner
werner schrieb:> Frage: Also ist die Interruptvektortabelle immer notwendig oder nur wenn> man auch Interrupts verwendet?
Wenn du keine Interrupts benutzt, werden die Vektoren auch nie
angesprungen.
Was folgt daraus?
Hallo Karl Heinz,
ich weis garn nicht wo ich anfangen soll.
Zu meiner Verteidigung ADCH auslesen geht nur mit ADLAR , das weis ich
und hab es als Kommentar "linksbündig" in der adini beachtet.
Die von Dir bevorzugte Schreibweise hat schon was für sich, aber ich
habs mir so (0b0000...) angewöhnt weil ich die Begriffe noch nicht so
gut kenne und ,wie peinlich eigentlich, weil ich den ||||| Strich auf
der Tastatur damals nicht gefunden habe. Ich werd mal versuchen die
Schreibweise zu übernehmen.
Wegen der "fertig" Abfrage: Die hat ewig nicht funktioniert , bis ich
dann gestern Abend herausgekommen habe,das das ADIE aus sein muss damit
es funktioniert. Du schreibst das es banal ist , ich hab gefeiert als es
ging...!
Wie geht es denn anders , vielleicht auch richtiger/besser?
Das kann natürlich stimmen das die Hardware zu langsam ist. Dann wäre es
besser einen Timer z.B 1 laufen zu lassen der zu jeder Sekunde die
Wandlung anstößt, oder?
Ich hab mir dein schönes Diagramm, das ist wirklich ernst gemeint... wie
das manche so toll hinbekommen im Editor so was zu kreieren, angesehen
und danach im Simulator mit gesetzten ADCH Werten die "auswahl"
durchlaufen lassen und das hat so wie ich mir das vorgestellt habe
funktioniert.
Außer den ocr0 Wert, den hab ich nie im Simulator eingetragen gesehen.
Kann das daran liegen wie du schon schreibst das erstmal ein Durchlauf
gemacht wird.
Hat eigentlich schon mal einer von Euch so was wie ich hier Versuche
gemacht? Nicht das das vielleicht gar nicht gehen kann..warum auch
immer!
Vielen Dank
Gruß Werner
werner schrieb:> Zu meiner Verteidigung ADCH auslesen geht nur mit ADLAR , das weis ich> und hab es als Kommentar "linksbündig" in der adini beachtet.
Jetzt hast du ja gesehen, welche Falle da lauert.
Kommentare sind Schall und Rauch. Die zählen nichts.
Schreib ich es so
1
ldi temp1, (1<<REFS0) | (1<<ADLAR) ;; AVcc als Referenz
2
out ADMUX, temp1
brauch ich im Prinzip nicht mehr kommentieren, dass ADLAR gesetzt ist.
Steht alles im 'relevanten' Teil: Im Code selber.
> Wegen der "fertig" Abfrage: Die hat ewig nicht funktioniert , bis ich> dann gestern Abend herausgekommen habe,das das ADIE aus sein muss damit> es funktioniert. Du schreibst das es banal ist , ich hab gefeiert als es> ging...!> Wie geht es denn anders , vielleicht auch richtiger/besser?
So wie im Tut
1
sample_adc:
2
sbi ADCSRA, ADSC ; den ADC starten
3
4
wait_adc:
5
sbic ADCSRA, ADSC ; wenn der ADC fertig ist, wird dieses Bit gelöscht
6
rjmp wait_adc
7
8
; ADC einlesen:
9
10
in adlow, ADCL ; immer zuerst LOW Byte lesen
11
in adhigh, ADCH ; danach das mittlerweile gesperrte High Byte
dazu ist das Tut ja geschrieben worden, damit man sich dort Dinge
abschauen kann. Kein Interrupt Flag notwendig. Und da man dieses Bit
nicht beachten muss, muss man es auch nicht zurücksetzen.
> Das kann natürlich stimmen das die Hardware zu langsam ist. Dann wäre es> besser einen Timer z.B 1 laufen zu lassen der zu jeder Sekunde die> Wandlung anstößt, oder?
Wäre eine Möglichkeit, wobei natürlich 1 Sekunde schon wieder ein Extrem
in die andere Richtung ist. Du musst bedenken, dass die PWM ja nur
Schrittweise auf die Sollspannung nachgeführt wird, wenn die also 20 OCR
Einheiten daneben liegt, dann würde das dann auch 20 Sekunden dauern,
bis sie so eingestellt ist, dass sich wieder die gewünschte
Ausgangsspannung ergibt.
Also: Ein bischen schneller darf es schon sein.
> Hat eigentlich schon mal einer von Euch so was wie ich hier Versuche> gemacht?
Natürlich.
Hallo Karl Heinz
Mensch klar das ADSC Bit, so einfach wäre es wenn man sich das tut
genauer angeschaut hätte.
> Hat eigentlich schon mal einer von Euch so was wie ich hier Versuche> gemacht?
Natürlich.
Und....was ist dabei rausgekommen. Mach mir mal ein bisschen Hoffnung,
ich sitze seit Oktober 2011 und versuche die Aufgabe zu lösen.
Ist noch ein bisschen mehr, muss noch an RS232 ausgegeben werden, den
Gesamtertrag/Verbrauch der Anlage ermitteln und wenn noch Zeit ist,
woran ich nicht mehr glaube, auf einem Display anzeigen.
Also probier ich mal mit ner halben Sekunde.
Gruß Werner
werner schrieb:> Natürlich.>> Und....was ist dabei rausgekommen.
Was soll dabei rauskommen.
Per Poti stell ich die gewünschte Ausgangsspannung ein, die dann auch
per PWM (und Siebglied) erreicht wird und am Voltmeter kann ich die
Spannung ablesen. Häng ich einen Lastwiderstand an, dann seh ich am LCD
wie der OCR Wert von der PWM sofort höher geht um die eingestellte
Ausgangsspannung zu halten. d.h. ein bischen bricht sie ein, fängt sich
aber sofort wieder. AM Voltmeter seh ich nur ganz kurz, dass die
Spannung offenbar ein bischen abweicht. Danach ist wieder alles paletti
und das Voltmeter zeigt mir wieder die Spannung die ich auch am LCD
durch die Potivorgabe angezeigt habe.
Der interessantere Teil war dann, den simplen Regler durch einen PID
Regler zu ersetzen :-)
> ich sitze seit Oktober 2011
Echt?
Lange Zeit für ein 1 Abend-Spielprojekt.
werner schrieb:> das ich Sie nicht hinschreiben muss????
Das verstehe ich nicht....
Aber wenn Du die Interrupt-Tabelle meinst: müssen musst Du nur die
Interrupts eintragen, die Du auch verwendest (=eingeschaltet hast).
Aber da Du den Platz im Code ja überspringst, ist der Bereich ja frei.
Du hast also nur Schreibarbeit, wenn Du dort die Liste hast. Dann stehen
die Jumps auch schon an der richtigen Adresse (Interrupt-Vektor). Du
brauchst also (nur) bei den nicht benutzten Interrupts eine Sprung zu
einer Default-Interruptroutine (mein Vorschlag war "noint: reti")
eintragen.
- Nachteil: Liste schreiben
- Vorteil: wird im Programm "aus Versehen" mal ein Interrupt
eingeschaltet oder ausgelöst, für den es keine Routine gibt, dann
schmiert das Programm nicht ab.
Der Rest ist Geschmacksache...
Gruß Dietrich
Hallo Dietrich,
> das ich Sie nicht hinschreiben muss????
war nur rein hypothetisch,
ich werde die Tabelle jetzt immer mit in den Code übernehmen.
Ist sicherer wie du schon geschrieben hast. Mir war eben nur nicht klar
was es für Auswirkungen hat wenn sie fehlt. Wieder was gelernt.Danke.
Verstanden hab ich jetzt auch das an der Stelle des Interrupts in der
Tabelle, meine Sprungmarke (Name wie adini,timer0ini) stehen muss.
Gruß Werner
Hallo Dietrich,
noch was,
ich hab das noint:
reti
ans Ende des Codes setzen müssen, weil der Cursor immer wieder während
des Programms dort hin gesprungen ist.
werner schrieb:> Verstanden hab ich jetzt auch das an der Stelle des Interrupts in der> Tabelle, meine Sprungmarke (Name wie adini,timer0ini) stehen muss.
Aber die Sprungmarke der ISR (Interrupt Service Routine). Die
Unterprogramme "adini", "timer0ini" dürfen da ganz sicher nicht stehen!
Das ist doch Deine Initialisierung, die nur 1x ganz am Anfang ausgeführt
wird.
Die ISR tut das, was auf Grund des Ereignises (das den Interrupt
ausgelöst hat) getan werden soll; z.B. Timerinterrupt: Zählen einer
Zeit, die im Programm benötigt wird, Entprellen von Tastern ...
Hinweis:
Unterprogramme enden mit "ret" (zurück zur Adresse, wo das Programm
aufgerufen wurde).
ISRs enden mit "reti" (zurück zur Adresse, wo das Programm durch den
Interrupt unterbrochen wurde, mit zusätzlichem (wieder) Freigeben des
Interrupts (wie "sei").
werner schrieb:> ich hab das noint:> reti>> ans Ende des Codes setzen müssen, weil der Cursor immer wieder während> des Programms dort hin gesprungen ist.
Welcher Cursor? Zeig mal das aktuelle Programm!
Gruß Dietrich
Hallo Dietrich,
dann hab ichs doch falsch verstanden.
Naja du wirst ja sehen wie ich mir das gedacht habe.
Momentan hab ich das Problem das 2 Frequenzen an PB3 rauskommen, die von
Timer0, ist gewollt, und die von Timer1, ist nicht gewollt.
Gruß Werner
Hi Werner
Wenn du schon so lange an deinem programm arbeitest... oha, da wird es
aber mal zeit, die Grundlagen von ASM zu verstehen. Ich fall jetzt nicht
in den Tenor ein und mach dich fertig, denn Assembler ist ein schönes
mächtiges Werkzeug. Doch du musst verstehen, wie es funktioniert. Ein
Interrupt leitet den µC an eine feste Adresse in der
Interrupt-Vektor-Tabelle. Wenn da ein RETI steht, ist die Sache für ihn
erledigt. Steht dort ein JMP ... wird lediglich der nächste Befehl aus
der interrupt-Service-Routine abgerufen und dort auch die Bearbeitung
fortgesetzt. Ist manchmal etwas unverständlich, das ein JMP auf einen
Programmteil mit RETI beendet werden muß.
Mal einfach
1
MAin
2
...
3
RCALL irgendwas
4
...
5
6
7
Irgendwas:
8
a ...
9
b ...
10
c ...
11
d ...
12
e ...
13
RET
Nun tun wir mal so, als wenn an der Stelle, wo "Irgendwas:" steht, nur 1
Befehl platz hätte, also halt nur Platz für ein "RET" oder einen "JMP"
wäre.
Dann könnte man ja mit einem JMP auf einen freien Speicherbereich
"springen" und dort weitere Befehle unterbringen.
1
MAin
2
...
3
RCALL irgendwas
4
...
5
6
7
Irgendwas: JMP ABCDE
8
9
10
ABCDE:
11
a ...
12
b ...
13
c ...
14
d ...
15
e ...
16
RET
Damit erreiche ich im Prinzip das selbe. Bei der IVT ist es so, das ein
Interrupt eine feste Adresse hat, die angesprungen wird. Nun weis aber
niemand, wie groß letztendlich so eine Bearbeitung eines Interrupts
wird. So läßt man halt nur den Sprung zu einer ISR zu, und dann reicht
es aus, nur soviel Speicher für einen Befehl zuzuteilen. Ich hoffe, das
dir das ein wenig hilft.
Um dir das Arbeiten mit Assembler etwas einfacher zu machen, solltest du
auch hier ruhig mehr Unterprogramme aufbauen. Dazu überlegst du, was
dein programm braucht. Eine Eingabe, eine Ausgabe, einen Regler,
Bearbeitung für zu klein und Bearbeitung für zu groß.
Zerlege deine Aufgabe in möglichst kleine Funktionseinheiten.
Dann fügst du nach und nach diese in dein Programm ein. Vorteil: sind
die Funktionseinheiten geprüft und laufen stabil, dann kannst du sie mit
einem Haken versehen und brauchst nicht immer wieder darin
herumzufummeln. Und wenn dein Programm schon gewachsen ist, zögere
nicht, eventuell neu anzufangen und mach nicht den Fehler und fang das
Stricken an. Es ist sicherlich nicht falsch, wenn du mit Papier und
Bleistift verschiedene Elemente in Form eines PAP's zur Ansicht bringst
und dich daran orientierst. Den Faden nicht zu verlieren ist keine
Kunst, wenn man sich an die Regeln hält. Fängst du aber an, mit
Klimmzügen hier und da Korrekturen reinzubasteln, ist schnell der FAden
weg.
Gruß oldmax
Hallo Oldmax,
in meinem Programm soll aller 235ms der AD-Wandler angestoßen werden.
Das soll Timer1 machen. Wenn der Interrupt kommt springt der in der IVT
auf den "adpa0" und dann im Programm auf diese Stelle.
Ich hab gerade gesehen das "main:" muss kurz über "jmp main".
Das ist der Fehler mit der Frequenzänderung und nicht der Timer1.
Gruß Werner
Hallo Werner,
es scheint so, dass Du mit Interrupts noch nicht so richtig vertraut
bist.
Weist Du, was Interrupts sind und wozu man sie verwendet? Lies mal
http://www.mikrocontroller.net/articles/AVR-Tutorial:_Interrupts.
Grundsätzliches: Interrupts sind Ereignisse, die von einer Hardware
innerhalb des µC-Chips (sei es Timer, AD-Wandler etc.) oder außerhalb
(z.B. INT0) ausgelöst werden, also an einer beliebigen Stelle des
Hauptprogramms stattfinden können.
Deiner Anwendung benötigt wohl noch überhaupt keine Interrupts, wenn Du
noch gar nicht an ISR gedacht hast (also: was tue ich bei dem
Interrupt). Vielleicht kannst Du das Ganze Thema "Interrupt" auch erst
mal vergessen - und dann solltest Du im Programm auch keinen einzelnen
Interrupt und den gesamten (per "sei") freigeben.
Es ist oft nicht nötig, mit Interrupts zu arbeiten, aber oft ergeben
sich damit "schönere" Programme bzw. Programme mit wesentlich besserem
Zeitverhalten.
Wenn man beliebig viel Zeit hat, braucht man (fast?) nie Interrupts!
Gruß Dietrich
Edit: Ich sehe, Du brauchst (oder willst) doch den Timer-Interrupt.
Ich sehe gerade noch: dort, wo Dein "noint:" stand, geht das nicht, da
Du nach "Reset:" ja direkt in diese Routine hineinläufst und bei "reti"
in die Wüste springst (auf dem Stack ist noch keine Rückkehradresse
gespeichert; das würde erst ein "call" oder "Interrupt" (=call über
Hardware ausgelöst) machen.
Ein von der Logik her "schöner" Platz für das "noint:" wäre z.B.:
1
.org 0x30 ;programm ab flashadresse 30h speichern
2
;=========== ISR fuer nicht geplante Interrupts =============
3
noint:
4
reti
5
;=============== stackpointer ini ===========================
Hallo Dietrich,
ich hab nochmal im TUT nachgesehen und mir Gedanken zur IVT gemacht.
Es gibt da so viele Varianten welche ist denn nun richtig?
Die unterste Variante ist die die nach meiner Meinung dann auf mein
Programm zutreffen müsste.
Gruß Werner
Hi
>Die unterste Variante ist die die nach meiner Meinung dann auf mein>Programm zutreffen müsste.>.org ADC ;ADC Conversion Complete> rjmp adiniNein . Die Initialisierung des ADC ist kein Interrupt! Genau so
wenig wie die Timerinitialisierungen.
Der ADC-Interrupt wird z.B. ausgelöst, wenn er freigegeben ist und der
ADC mit einer Wandlung fertig ist. Genau dann springt der Controller zur
Adresse $1C. und dort findet er den 'jmp' zu der Routine, die
abgearbeitet werden soll, wenn eine Wandlung beendet ist. Und das ist
nicht die Initialisierung.
MfG Spess
Hallo Sess,
also ist es genau umgedreht wie ich mir das gedacht habe,
aus dem TUT
.include "m8def.inc"
.def temp = r16
.org 0x000
rjmp main ; Reset Handler
.org INT0addr 3.)+++und in der routine
ausgeführt
rjmp int0_handler ; IRQ0 Handler 2.)+++dann hier zugeordnet
.org INT1addr
rjmp int1_handler ; IRQ1 Handler
.
.
.
int0_handler: 1.)+++ und hier wird der interrupt (Taste) ausgelöst
sbi PORTB, 0
reti
Gruß Werner
cp v_store, v_akku ;vergleiche gespeicherte mit gemessener
19
brsh up ;akkuspannung, wähle aus ob höher oder niedriger
20
brlo down ;und springe zu up oder down
21
jmp main
22
down:
23
dec v_akkupwm ;akkuspannung zu hoch, zaehle runter ;
24
cp v_store, v_akkupwm ;und vergleiche mit gespeicherter spannung
25
brne down ;noch nicht gleich? springe zu down
26
breq sichern ;wenn gleich, sichern
27
jmp main
28
up:
29
inc v_akkupwm
30
;cp v_store, v_akkupwm
31
;brne up
32
jmp sichern
33
jmp main
34
sichern:
35
sts 0x0072, v_akkupwm ;wert im sram gesichert, fuer spaeter
36
mul r19,r21 ;ergebnis mit r20 verstaerken
37
mov r19, r0 ;und in r19 verschieben
38
out ocr0, v_akkupwm ;als vorladewert nach ocr0
39
40
;out ocr0,r19 ;und ins vorladeregister von tc0 gespeichert
41
main: ; lt. simulation klappt das nicht, ich weis
42
;mir keinen rat warum???
43
jmp main
Schau dir doch mal deinen Code an und versuche zu folgen. Der Grundsatz
beim Assembler ist:
Befehl 1
Befehl 2
Befehl 3
usw
Es kann immer ein Befehl kommen, der heißt:
Springe zu Befehl 1
Wenn du in deinem Programm u.U. folgendes stehen hast:
JMP Main
dann tut das der Controller auch, auch wenn die Marke "Main" VOR dem
Sprungbefehl steht. Das Ergebnis kannst du dir dann ja denken, oder ?
Lies nochmal alle hilfreichen Antworten, versuche eine Struktur in dein
Programm zu bekommen. Die Initialisierungen rufst du mit Calls auf,
warum bleibst du nicht bei dieser Technik und baust in der
Programmschleife auch Calls ein
Wenn du so deine Schleife aufbaust, dann kannst du die Unterprogramme
nach und nach mit Inhalten füllen.
Damit dein Programm aber auch ohne diese Inhalte läuft, setzt du bei
einem Unterprogramm erst mal ein RET. So wächst dein Programm langsam
und du kannst jederzeit weitere Bearbeitungsschritte einfügen.
Gruß oldmax
Hallo,
ich hab mal Oldmax seine Hinweise versucht in die Tat umzusetzen
und das Programm strukturiert.
Hinzugekommen ist das Auslesen von PA1+2 mittels OVF1addr.
Gruß Werner
- Du hast immer_noch in der Interrupt-Vektortabelle
"timer1ini"
"timer0ini"
"adini"
die dort nichts zu suchen haben!
Da Du zur Zeit noch keine Anwendung für den Interrupt hast (?), spring
zumindest an eine im Moment noch leere ISR ("Marke:" + "reti").
- in "auswahl:" hast Du noch mehrere "jmp main". Ein Unterprogramm darf
nur mit "ret" verlassen werden!!!
- "adwand:" wird (noch?) nicht verwendet; ist das eine "stille Reserve"?
Gruß Dietrich
cp v_store, v_akku ;vergleiche gespeicherte mit gemessener
15
brsh up ;akkuspannung, wähle aus ob höher oder niedriger
16
brlo down ;und springe zu up oder down
17
jmp main
wieso geht es hier mit einem jmp main zu main zurück? auswahl ist per
rcall aufgerufen worden! Also muss es hier mit einem ret zurückgehen!
Die Sache, was in die Interrupt Vektoren reinkommt, hast du auch noch
nicht bereinigt. Da stehen immer noch die Calls zu den Init Funktionen
drinnen (wenn auch jetzt die Syntax in Ordnung ist).
Weiters hast du eine ADC-Init Funktion, die du auch aufrufst, aber in
der Wandlungsfunktion änderst du die komplette Konfiguration wieder, was
du musst, weil du mit 2 Kanälen arbeiten willst. Dann schmeiss das Zeug
aus der Init Funktion raus! Wenn du den ADC mit ADLAR betreibst, dann
brauchst du auch das Low-Byte vom ADC nicht auslesen. Lass überflüssige
Sachen weg! Sie verwirren nur und ziehen das Programm unnötig in die
Länge und verringern dadurch die Übersicht! Gerade in Assembler ist es
wichtig die Übersicht zu behalten. Du tust dir nichts gutes damit, wenn
du Anweisungen drinnen lässt, die du nicht brauchst. Im Sprichwort heißt
das dann: Man sieht den Wald vor lauter Bäumen nicht mehr.
Und zur Initialisierung der Timer, bzw. generell zur Verwendung von
Zahlensystemen. Man benutzt immer das Zahlensystem, das in der Situation
am logischten ist. Wenn du den Timer auf 0 initialisieren willst, dann
schreib das auch so
ldi temp1, 0
out TNCT1L, temp1
Oder (im Zusammenhang mit 16 Bit Zahlen)
ldi temp1, HIGH(0)
out TCNT1H, temp1
ldi temp1, LOW(0)
out TNCT1L, temp1
an dieser Stelle gibt es keinen wie auch immer gearteten Grund, die Zahl
die nach TCNT1 geladen werden soll, als Binärzahl anzugeben. Wenn du den
Timer mit 8765 laden willst, dann schreibst du
ldi temp1, HIGH(8765)
out TCNT1H, temp1
ldi temp1, LOW(8765)
out TNCT1L, temp1
und rechnest dir nicht die Bitmuster aus. Denn die Bitmuster sagen dir
als Mensch, der mit dem Dezimalsystem aufgewachsen ist, nichts. Niente,
nada, nothing. Aber als Dezimalzahl sagt dir das was. Mit 8765
verbindest du eine Vorstellung über die Größe der Zahl. Mit der
identischen Zahl als Bitmuster 00100010 00111101 verbindest du erst mal
gar nichts, ausser das das irgendwelche 0-en und 1-en sind.
Und zum Thema: Initialisierung des Timer 0. Da kann ich nur den Kopf
schütteln. Wieder magische Binärkonstanten anstelle sprechender
Bitnamen.
Und was die Klammerung in den Initfunktionen
cli
....
sei
bringen soll, muss mir auch einmal wer erklären.
Progammaufbau:
1
rjmp init
2
3
... Interrupt Vektoren
4
5
init:
6
7
... Initialisierungen, Hardware in den korrekten Zustand bringen
8
... Timer initialisieren etc.
9
10
sei
11
12
main:
13
14
... Hauptlogik des Programms
15
16
rjmp main
17
18
...
Da gibt es EINEN sei. Der steht am Programmbeginn, nachdem die Hardware
konfiguriert wurde und bevor dann die Hauptschleife übernimmt. Aber
versteck ihn dir NICHT in den Konfigurierfunktionen! Und vor allen
Dingen nicht mehrere.
Wieder: Schaff dir Übersicht. Funktionen sollen das tun, wie sie heißen!
Lass unnötige Dinge weg. Du brauchst in den Init Funktionen keine
cli-sei Klammerung, also lass sie weg. Ganz im Gegenteil: Der
Programmteil 'die Hardware bzw. benötigte Komponenten initialisieren'
muss meistens sowieso ohne eingeschaltete Interrupts ablaufen. Und zwar
komplett! Denn was hilft es dir wenn Komponente A schon fertig
konfiguriert ist, Komponente B aber noch nicht und aus irgendeinem Grund
wird ein Interrupt ausgelöst? Das Gesamtsystem ist noch nicht fertig
konfiguriert - also machen Interrupts in den meisten Fällen hier noch
gar keinen Sinn.
Du brauchst aber, wenn du Interrupts benutzen willst (und nur dann),
EINEN sei, der gemacht werden muss, nachdem alles korrekt eingestellt
wurde. Genau das ist der Fall an der Übergabestelle vom Vorgeplänkel
(alles initialisieren) zur Hauptschleife - wenn der Teil nach 'init' zu
Ende geht und 'main' beginnt.
Dietrich L. schrieb
> Da Du zur Zeit noch keine Anwendung für den Interrupt hast (?), spring> zumindest an eine im Moment noch leere ISR ("Marke:" + "reti").
Hab ich auch gedacht.
Aber siehe: Initialisierung des Timer 1
1
ori temp1, (1<<TOIE1) ;Timer1 Overflow Interrupt aktiviert
2
out TIMSK, temp1
Ab jetzt gibt es tatsächlich auftretende Interrupts.
Was natürlich den Aufruf der Init Funktion nicht besser macht.
Hallo Dietrich und Karl Heinz,
danke für eure Hinweise, die jmp main in auswahl habe ich so von
"krapao"
weiter oben übernommen. Ich habe jetzt die IVT bereinigt und den "Käse"
rausgenommen, das "sei" hab ich am Ende der letzten ini des "adini"
hingeschrieben oder muss das unter den letzten rcall Aufruf der inis.
Die Schreibweise ldi temp1, HIGH(8765) ist mir nicht geläufig deshalb
verwende ich sie auch nicht, um Fehler zu vermeiden.
Nochmal zur IVT, da ja Timer1 jetzt läuft, muss der da nun mit erwähnt
werden so wie ich es gemacht habe.... siehe neuen Code?
Gruß Werner
werner schrieb:> Hallo Dietrich und Karl Heinz,>> danke für eure Hinweise, die jmp main in auswahl habe ich so von> "krapao"> weiter oben übernommen.
Grundsätzlich darfst du Code NIEMALS einfach nur so übernehmen!
Du musst verstehen, was der Code macht, dieses Verständnis in den
Kontext deines Programmes setzen und dann überlegen, was der Vorschlag
jetzt für dich heißt.
Aber Code einfach unbesehen zu kopieren, ist ein sicherer Weg ins
Nirwana. Es gibt ein paar Ausnahmen von dieser Regel aber im großen und
ganzen: Copy%Paste funktioniert nicht.
> Die Schreibweise ldi temp1, HIGH(8765) ist mir nicht geläufig> deshalb verwende ich sie auch nicht, um Fehler zu vermeiden.
Jetzt ist sie dir geläufig.
Also verwende sie.
und tschüss.
Tippen wir eigentlich so undeutlich, oder wie ist das.
Die richtige Aktion, die bei einem Overflow des Timers zu machen ist,
ist NICHT ihn neu zu intialisieren.
Die richtige Aktion wird etwas anderes sein, was du noch nicht hast,
aber es ist auf keinen Fall ein Aufruf der Init-Funktion des Timers!
Mach halte einen reti rein, wenn du die Aktion noch nicht hast.
Und nein. Das ist kein Kavaliersdelikt, das ist ein schwerer Fehler!
Andere wurden schon wegen weniger geteert und gefedert.
Hallo Karl Heinz,
"manchmal hat man das Gefühl, man redet gegen eine Wand"
Ich bin mir nur nicht sicher deswegen hab nochmal nachgefragt.
An der Stelle ist doch die Initialisierung abgeschlossen,oder doch
lieber
rcall timer1ini ;TIMER 1 initialisieren
rcall timer0ini ;Timer 0 initialisieren
rcall adini ;A-D Wandler AD0 initialisieren
da sei hinschreiben?????
Aber ich muss Euch wirklich Danken, seit dem Ihr Kommentiert sieht doch
das Programm viel Übersichtlicher aus. Es wäre jetzt unverschämt hier
" weiter so " hinzuschreiben aber wüsste ohne Euch nicht weiter ;-)
Misst ist eben bloß das es noch nicht funktioniert.
Gruß Werner
werner schrieb:> Hallo Karl Heinz,>> "manchmal hat man das Gefühl, man redet gegen eine Wand">> Ich bin mir nur nicht sicher deswegen hab nochmal nachgefragt.> An der Stelle ist doch die Initialisierung abgeschlossen,
Und was tust du, wenn dann irgendwann zb die LCD Initialisierung dazu
kommt? Verschiebst du dann den sei von der ADC Initialisierung in die
LCD Initialisierung?
Mach ihn doch gleich dort hin, wo er auf immer und ewig bleiben kann!
Hallo Karl Heinz,
Gott sei Dank ist ein dünnes Kabel zwischen uns, wo weder Teer noch
Federn durchpassen !!!
Der Timer1 soll aller 239ms den AD-Wandler "adwand" anstoßen, also muss
der da hin.
Ich dachte wenn ich das ins SFIOR schreibe langt das, wieder falsch.
Gruß Werner
werner schrieb:> Hallo Karl Heinz,>> Gott sei Dank ist ein dünnes Kabel zwischen uns, wo weder Teer noch> Federn durchpassen !!!> Der Timer1 soll aller 239ms den AD-Wandler "adwand" anstoßen, also muss> der da hin.
Ja.
Aber sicher nicht dadurch, dass du die INIT Funktion aufrufst!
Die Init Funktion kommt einmal zum Einsatz. EINMAL.
Eine Funktion, die über einen Interrupt Vektor aufgerufen wird, muss
nämlich etwas anders aussehen. Daher kannst du die jetzige Funktion
nicht benutzen!
Ausserdem macht sie das falsche.
Mal eine Zwischenfrage:
Ist das dein erstes Programm?
Du hast da so dermassen Schwächen, dass ich mich Frage was wir im
Tutorial falsch gemacht haben. Ich bin immer noch der Meinung: Wer
langsam und behutsam das Tutorial durcharbeitet, arbeitet nicht
durchlesen!, der kann auf deine Probleme gar nicht stossen. Das eine
oder andere mag missverständlich geschrieben sein, aber bei dir fehlts,
verzeih, an allen Ecken und Enden.
Tutorial durcharbeiten heißt nicht: Ich les mir das durch, nicke 2 mal
mit dem Kopf und geh weiter zum nächsten Kapitel. Tutorial durcharbeiten
heißt: Ich programmier die Dinge, die im jeweiligen Kapitel vielleicht
nur angerissen wurden zu Ende, ich mach Variationen davon, ich denk mir
selbst ähnliche Problemstellungen aus und erst dann, wenn ich das Gefühl
habe, eine gewisse Sattelfestigkeit erreicht zu haben, gehts weiter zum
nächsten Kapitel.
Ich merke bei dir irgendwie nicht, dass du verstehst was du da tust. Mir
kommt vor, du malst im wahrsten Sinne des Wortes den Code nur ab, ohne
dir Gedanken darüber zu machen, was da im Code eigentlich passiert,
warum der so aussieht, etc. Eben wie jemand, der sofort beim vorletzten
Kapitel im Tutorial einsteigt und meint das davor brauche er alles
nicht. Und dann stolpert er von einem Problem ins nächste.
Hallo Karl Heinz,
Das ist das erste umfangreichere Programm was ich schreiben will.
Vorher waren das nur kleine Progrämmschen als Trockenübung im Simulator.
Jetzt ist alles auf dem Steckbrett.
Das Tut hab ich mir schon gut angesehen, aber ich habe z.B. nicht
gefunden wie man den AD-Wandler mit Timer1 aller x s/ms anstößt.
Im DB habe ich dann gesehen das im ADCSRA das (1<<ADATE) setzen muß (was
ich wie ich eben gesehen habe vergessen hab reinzuschreiben)und im SFIOR
als Triggerquelle den Overflow des Timer1 auswählen
(1<<ADTS2)|(1<<ADTS1), richtig??
Gruß Werner
Hi
Es erstaunt mich doch immer wieder, wie viel Geduld man haben kann. Hab
mir grad nochmal den Eingangspost durchgelesen.... da denkt man doch, es
mit jemandem zu tun zu haben, der nicht nur lesen und schreiben kann,
sondern der auch sonst noch etwas helle ist und mit der Materie
vertraut....
Zitat:
>Nun denn, ich bin seit Wochen damit beschäftigt eine vernünftige>Ladereglung für eine Projektarbeit zu konstruieren.>Das größte Problem war bisher der StepDown und dessen Ansteuerung, was>aber nun halbwegs zufriedenstellend funktioniert.>Nun ist es aber so das der StepDown lastabhängig fungiert, heist wenn>ich z.B. eine 12V/20W Halogenlampe anschließe muss ich mit anderen>Vorladewerten (tc0/ocr0) arbeiten als mit einem Akku .
Vielleicht hab ich da doch etwas viel hinein interpretiert....
Da es den Anschein hat, das Werner doch große Schwierigkeiten mit dem
Lesen und Verstehen hat, gebe ich hier auf. Entweder er macht sich einen
Spaß darraus uns zu verarschen, oder er ist 12 und besucht noch die
Grundschule.....
oder er hat einfach einen schlechten Dozenten. Ich weiß es nicht, aber
im Tut steht fast alles, was er braucht. Es sind soviele Tips gegeben
und der Murks ist immer noch ein völlig verkorkstes Programmgerüst, was
immer wieder mit kleinen (unsinnigen) Änderungen präsentiert wird.
Ich bin raus
Gruß oldmax
Hallo Oldmax,
zuerst vielen Dank für Deine Geduld.
Ich will hier wirklich keinen Verarschen , warum auch.
Die Programmschnippsel die wir mit unserem Dozenten gemacht haben
unterscheiden sich schon gewaltig von dem wie Ihr das macht.
Vielleicht stelle ich mich ja wirklich zu doof an , aber ich gebe
trotzdem nicht auf. Was für Dich bestimmt kleine unsinnige Veränderungen
sind , ist für mich wieder was gelernt zu haben.
Ich habe versucht das Programm nach Euren Hinweisen zu strukturieren,
wenn es
da Unterschiede im Verständnis gibt ist das nur allzu menschlich, da ich
ja nicht wissen kann wie deine Vorstellungen sind. Mein 12.Lebensjahr
ist auch schon eine Weile her, vielleicht ist das ja auch mit ein Grund
das ich so meine Problemchen mit der Mikroelektronik habe, weil ich noch
Relaissteuerungen hatte , wo man sehen konnte was passiert und was nicht
geht konnte man dann auch ausmessen. Heute sitzt man vor einer Blackbox
und kann nur hoffen das man es Ihr richtig sagt und wenns dann nicht
geht ist es ein Problem herauszufinden warum.
Gruß Werner
werner schrieb:> Hallo Karl Heinz,>> Das ist das erste umfangreichere Programm was ich schreiben will.
Verzeih.
Aber das bisherige ist eben NICHT umfangreich. Das ist
ADC initialisieren
PWM initialisieren
main: <------+
|
ADC auslesen -> Wert |
|
if( Wert kleiner Vorgabewert ) | Hauptschleife
PWM um 1 erhöhen |
|
if( Wert größer Vorgabewert ) |
PWM um 1 erniedrigen |
|
bischen warten |
|
rjmp main >----+
fertig. Das ist alles für eine erste funktionierende Version. Das ist
keineswegs umfangreich. Auch in Assembler sollte so etwas nicht länger
als 1 bis 2 Abende (a 3 oder 4 Stunden) dauern, ehe das sauber läuft.
Jeder der eine LED blinken lassen kann, ala
main: <-----+
|
LED einschalten |
|
bischen warten |
|
LED ausschalten |
|
bischen warten |
|
rjmp main >----+
sollte das eigentlich hinkriegen, wenn er die zusätzlichen Bausteine
'ADC', 'Timer mit PWM' und 'wie vergleiche ich Werte' unter Kontrolle
hat.
Und da kann man dann jetzt anfangen sich zu fragen, wie man zb den Teil
'bischen warten' loswerden kann und durch etwas besseres ersetzt und wie
dann die Struktur aussehen muss.
> Das Tut hab ich mir schon gut angesehen, aber ich habe z.B. nicht> gefunden wie man den AD-Wandler mit Timer1 aller x s/ms anstößt.
Und genau da ist das Problem. Das findet sich so auch nicht im Tutorial.
Im Tutorial findet sich, wie man den ADC bedient und es findet sich wie
man mit einem Timer regelmässige Dinge tut.
Und jetzt müsstest du die beiden kombinieren.
Im Tutorial ist das am Beispiel einer Uhr gezeigt, die regelmässig den
(gedachten) Sekundenzeiger um 1 weiterstellt und bei dir wäre es dann
eben das Auslösen 1 "Regelschritts" der aus "mit dem ADC messen und
gegebenenfalls die PWM nachstellen" besteht. Ich hätt sogar kein Problem
damit, wenn du dafür kein Job-Flag benutzt sondern das alles in der
Interrupt Service Routine machst - aber: soweit bist du noch lange
nicht.
Es ist wie beim Schachspielen: Du lernst wie die Figuren ziehen und dann
geht es los, wie Kombinationen von diesen Zügen etwas bewirken.
Die Programmierung steckt in den Kombinationen! Die einzelnen Dinge
(ANweisungen) einer Programmierpsrache sind vergleichsweise einfach und
trivial. Die Kombination macht die Power. Nur bei dir fehlt es an vielen
Stellen an den 'Einzelzügen'. Im ganzen Thread, der sich jetzt schon
über weite Strecken hinzieht, geht es in Wirklichkeit noch gar nicht
darum, dass wir uns über Kombinationen unterhalten und verschiedene
Möglichkeiten und deren Vorteile/Nachteile unterhalten, sondern wir
stecken bildlich gesprochen immer noch dort fest, ob ein Bauer beim
Schach jetzt 1 Feld oder 2 Felder vorrücken darf.
Und das ist dann etwas frustrierend, wenn man eigentlich darüber reden
möchte (durchaus auch von dir angestossen), ob eine Rochade an dieser
Stelle jetzt sinnvoll ist oder nicht, sich dann immer wieder in den
Niederungen wiederzufinden, dass man dich wieder und wieder ermahnen
muss, dass ein Läufer nur diagonal ziehen kann.
Da hilft es auch nichts, wenn du irgendwo eine Schachpartie 'Bobby
Fischer gegen Spasski' ausgräbst und dir von dort eine Finesse des
Endspiels abschaust. Das ist mit deinem Kentnissstand noch viel zu
schwer, das alles zu überblicken und es bringt auch nichts, wenn dir
hier wer erklärt, warum der Turmzug eine gute Idee war und er dadurch
Kontrolle über das Zentrum erhielt. Das ist ein völlig anderer Level auf
dem wir uns da unterhalten müssten.
Hallo Karl Heinz,
auch ein Schachspieler. Ist bei mir schon eine Weile her, ich habs mal
von meinem Vater gelernt, meistens verloren, bin dann eine Weile in
einem Schachklub gewesen, habe eine Weile auch aktiv gespielt. Dann hat
mein Vater meistens verloren und er hatte keine Lust mehr. Ich habe
immer die Simultanspieler bewundert die in einem Raum mit mehreren
gespielt haben und
nicht mal ans Brett gegangen sind um den nächsten Zug zu machen! Na ja
lang her.
Vielen Dank für deinen Anstoss, ich habe das Programm verändert und es
sollte nun eigentlich den vorgeschlagenen Ablauf folgen.
Die Warte-Routine habe ich aus dem LCD TUT genommen.
Gruß Werner
Hi
> ldi temp1,(1<<ADSC)> out ADCSRA,temp1 ;Wandlung pa0 starten
Nein. Damit machst du deine vorherigen Einstellungen in ADCSRA platt.
Die Ausführung von Karl-Heinz sollten dir eigentlich sagen, das dir
selbst für ein so simples Programm die Basics fehlen. Dem schließe ich
mich an.
MfG Spess
Hallo Spess,
vielen Dank für deinen Hinweis.
Das war wirlich ein böser Fehler. Ich wollte eigentlich nur das ADSC Bit
setzen. Hab es gleich korrigiert
sbi ADCSRA,ADSC
ist besser so.
Gruß Werner
OK.
Ich hab mal aus deinem Programm einen Auszug gemacht, um auf etwas
hinzuweisen und dir eine Frage dazu zu stellen
1
.def v_akku = r12 ;Messwert Akku-Spannung PA1
2
...
3
.def v_akkupwm = r19 ;ermittellter Vorladewert ocr0 für 13.4v
4
...
5
6
auswahl:
7
mov v_akkupwm, v_akku
kannst du mir logisch begründen, warum du hier die gemessene Spannung
(v_akku) an den OCR Wert (v_akkupwm) zuweisen willst?
Der OCR Wert wird doch vollkommen lösgelöst von der Akkuspannung
ermittelt.
Ist die gemessene Spannung höher als der Sollwert, dann wird der OCR
Wert verringert. Ist die gemessene SPannung geringer als der Sollwert,
dann wird die OCR erhöht. Der Zusammenhang OCR Wert zu gemessener
Spannung ist nur indirekt. Genau das ist ja der ganze Witz an einer
Regelgung: Dass sich der OCR Wert von selbst ergibt, in dem er solange
verändert wird, bis gemessener Wert und Sollwert übereinstimmen.
Wenn du die Stellgröße (den OCR Wert) direkt per Formel an eine andere
Größe koppelst, dann hast du einen "Steller".
Ein Poti wird gedreht und je nach Potistellung leuchtet eine
Lampe dunkler oder hellt. Am Poti ist eine Skala: Will ich
die Lampe x Einheiten hell haben, dann drehe ich das Poti
auf eine bestimmte Stellung.
Ein Regler hingegen vergleicht eine Messgröße mit der Sollgröße und
abhängig vom Ergebnis wird die Stellgröße verändert. Es existiert kein
direkter formelmässiger Zusammenhang mehr zwischen der Stellgröße und
der Sollgröße. Der ergibt sich von selbst, indem man die Stellgröße (den
OCR Wert) solange verändert, bis sich das gewünschte Ergebnis einstellt.
Das ist dann ein "Regler".
Eine Photozelle misst die Helligkeit der Lampe und wenn die zu
dunkel ist, dreht sie das Poti weiter auf. Ist sie zu hell
dreht sie das Poti zurück.
Es gibt keine Skala mehr am Poti. Sie ist ersetzt durch den
Vergleich "gemessener Wert - gewünschter Wert" und dem
daraus sich ergebenden "Weiter nach Links" oder "Weiter nach Rechts"
drehen des Potis.
Hallo Karl Heinz,
ich will hier den gemessene akkuspannung in die v_akkupwm übergeben,
damit der gemessene akkuspannungswert nicht verändert wird.
Ich betrachte sozusagen v_akkupwm als Arbeitsregister für die Ermittlung
des
ocr0-Wertes.
Gruß Werner
werner schrieb:> Hallo Karl Heinz,>> ich will hier den gemessene akkuspannung in die v_akkupwm übergeben,> damit der gemessene akkuspannungswert nicht verändert wird.> Ich betrachte sozusagen v_akkupwm als Arbeitsregister für die Ermittlung> des> ocr0-Wertes.
Dann solltest du vielleicht mal deinen Kommentar noch einmal lesen,
welchen Wert eigentlich das Register v_akkupwm repräsentiert!
Und auch mal den Rest deines Programmes lesen, was mit diesem Wert
weiter passiert. v_akkupwm IST dein ocr Wert. Nur eben in einem
Arbeitsregister und nicht direkt im OCR Register.
Hallo Karl Heinz,
ja schon es ist das Register in welchem der ocr0 Wert ermittelt wird um
Ihn dann an das ocr0 zu übergeben.
Man merkt schon das der AD-Wandler jetzt funktioniert, aber bei
Lastwechsel sehe ich keine optische Veränderung an der PWM und die
Spannung wird auch nicht nachgeregelt. Allerdings bricht der
Spannungswert jetzt nicht mehr so stark ein.
Gruß Werner
werner schrieb:> ja schon es ist das Register in welchem der ocr0 Wert ermittelt wird um> Ihn dann an das ocr0 zu übergeben.
Dann schau mal in Deinem Programm (wie Karl Heinz schon bemerkte):
1
auswahl:
2
mov v_akkupwm, v_akku
jetzt steht in v_akkupwm die Akku-Spannung ...
1
....
2
down:
3
dec v_akkupwm ;akkuspannung zu hoch, zaehle runter ;
...die Du jetzt runterzählst (oder bei "up:" rauf)...
1
jmp sichern ;sprung zum sichern/ausgabe ocr0
2
....
3
sichern:
4
5
sts vakkupwm, v_akkupwm ;wert im sram gesichert, fuer spaeter
6
out ocr0, v_akkupwm ;als vorladewert nach ocr0
7
ret
... dann sicherst und in das ocr0-register lädst (also immer noch die
Spannung!).
Das gesicherte wird nie gelesen, also auch nie benutzt.
Merkst Du was?
Ich verrate es schon mal (sonst zieht sich das ja noch ewig hin und so
jung bin ich auch nicht mehr):
Du musst den gesicherten Wert "vakkupwm" holen und dann diesen mit "dec"
bzw. "inc" verändern!
Gruß Dietrich
Edit: Wenn Du das Programm änderst, musst Du "vakkupwm" auch noch
initialisieren, damit nach Einschalten das PWM nicht unkontrolliert
beginnt. Außerdem muss noch sicher gestellt werden, dass bei "inc" und
"dec" der Wert innerhalb eines gültigen Bereiches bleibt.
Dietrich L. schrieb:> Ich verrate es schon mal (sonst zieht sich das ja noch ewig hin und so> jung bin ich auch nicht mehr):> Du musst den gesicherten Wert "vakkupwm" holen und dann diesen mit "dec"> bzw. "inc" verändern!
Oder aber er macht ganz einfach mit v_akkupwm weiter und pfeift komplett
auf den gesicherten Wert (der momentan sowieso keine Funktion erfüllt).
Hat ja sowieso keinen Zweck. Mit Hinweisen kommen wir nicht weiter
1
auswahl:
2
cp v_store, v_akku ; vergleiche messwert mit sollwert
Karl Heinz Buchegger schrieb:> Hat ja sowieso keinen Zweck. Mit Hinweisen kommen wir nicht weiter
Da hast Du leider recht! Der Wirkungsgrad diese Threads bewegt sich
maximal im einstelligen %-Bereich. Ich wollte ja auch schon aufgeben,
aber dann überkam es mich nochmal...
Gruß Dietrich
Hallo Dietrich,
ich hab mir jetzt deinen Wink mit dem Gartenzaun ein paar mal
durchgelesen,
aber ich raffs einfach nicht.
Der Wert v_akkupwm ist doch gleich dem Wert vakkupwm(sram)...
ich glaub jetzt hab ichs , ich lade ja immer wieder den selben akkuwert
aus v_akku in v_akkupwm, also wird der ja nie kleiner oder
größer...richtig?
Gruß Werner
werner schrieb:> ich glaub jetzt hab ichs , ich lade ja immer wieder den selben akkuwert> aus v_akku in v_akkupwm, also wird der ja nie kleiner oder> größer...richtig?
Und der Wert in v_akku ist was?
Das ist dein Messwert vom ADC.
Also hast du letztendlich einfach nur den Messwert vom ADC auf die PWM
gegeben. Ein bischen verschleiert, aber darauf läuft es hinaus.
Das ist aber genau nicht das was du willst.
Du willst:
PWM Wetr sei 0
dein Vorgabewert für die zu erzeugende Spannung sei 45
jetzt läuft dein Programm los.
Der ADC misst die Spannung und stellt fest: 0
Jetzt kommt die Logik zum Zug. Die stellt fest: zu klein
Also wird die PWM ein bischen erhöht: Neuer Wert für die PWM -> 1
Also Folge davon erzeugt deine PWM eine etwas höhere SPannung. Keine
Ahnung, 0.2V
wieder misst der ADC die externe Spannung und kommt mit einem Messwert
hoch: 10
immer noch zu klein. Also wird der PWM Wert wieder um 1 erhöht. Die PWM
erzeugt jetzt wieder eine etwas höhere Spannung: 0.3V
Nächster Durchlauf durch die Schleife.
Der ADC misst die Spannung und kommt mit einem Wert an: 13
13 ist immer noch kleiner als die Vorgabe von 45
Also wird der Wert für die PWM wieder um 1 erhöht.
usw. usw. Das ganze Spiel läuft so lange, bis durch immer weitere
Schleifendurchläufe in main der PWM Wert solange in kleinen Schritten um
1 erhöht wurde, bis die PWM eine Spannung erzeugt, die genau so groß
ist, dass der ADC einen Wert von 45 misst. Dann hört das Erhöhen auf.
Jetzt belastest du von aussen die Spannung etwas. Als Folge davon bricht
sie ein. Der ADC misst nur noch 43. Für deine Auswahllogik bedeutet
dass, das der Messwert kleiner als die Vorgabe geworden ist, also wird
die PWM um 1 erhöht. Die von der PWM erzeugte Spannung wird etwas
angehoben. Die Regelung hat begonnen, den Abfall auszugleichen, indem
der PWM Wert höher wird.
Und auch umgekehrt. Du nimmst die externe Last wieder weg. Die externe
Spannung steigt wieder. Der ADC misst 46. Die Ausgleichslogik stellt
fest: Das ist zu viel! Also wird der PWM Wert um 1 erniedrigt. Als Folge
davon sinkt die Spannung die die PWM erzeugt.
Merkst du was: Der PWM Wert wird NIE zugewiesen sondern immer nur
entweder um 1 erhöht oder um 1 erniedrigt (oder es passiert gar nichts).
Was der PWM Wert macht (machen soll), hängt davon ab, ob das was der ADC
misst kleiner oder größer als die Vorgabe ist.
Und jetzt regelst du und stellst nicht nur!
Du machst doch mit deinem Auto genau das gleiche. Wenn du 50 fahren
sollst und du merkst du bist zu langsam, dann steigst du ein bischen
mehr aufs Gas, bis es passt. Bist du umgekehrt zu schnell, dann lässt du
das Pedal etwas rauskommen, bis es wieder passt. Aber wo das Pedal genau
steht - das interesiert dich nicht die Bohne. Du korregierst einfach nur
die Pedalstellung abhängig davon ob du zu schell oder zu langsam bist.
Hallo,
ich möchte mich recht herzlich bei allen die zum gelingen beigetragen
haben bedanken, besonderen Dank möchte ich hiermit Dietrich und Karl
Heinz aussprechen die bis zum Schluß an die Intelligenz im Menschen
geglaubt haben.
Anbei der bereinigte Code.
So da das ja so gut geklappt hat können wir nun mit Teil 2 der
mehrteiligen Sendereihe fortfahren ;-)).
Ich will ja noch den Ertrag und Verbrauch ermitteln. Das funktioniert ja
nicht ohne eine Zeitmessung, ist es zwingend dafür eine Uhr zu benutzen
oder hat jemand noch ne andere Idee?
Gruß Werner
werner schrieb:> So da das ja so gut geklappt hat können wir nun mit Teil 2 der> mehrteiligen Sendereihe fortfahren ;-)).> Ich will ja noch den Ertrag und Verbrauch ermitteln. Das funktioniert ja> nicht ohne eine Zeitmessung, ist es zwingend dafür eine Uhr zu benutzen> oder hat jemand noch ne andere Idee?
Da muss ich dich enttäuschen. Das tu ich mir in Assembler sicher nicht
mehr an. Da schreib ich lieber den 3 Zeiler in C, lass den Compiler sich
den Kopf zerbrechen, wie er die Berechnungen umsetzt und geh heut Abend
lieber auf ein Bier, während der Mega8 fleissig mAh zählt und auf dem
LCD (das du auch noch nicht hast) ausgibt. Und auf die 5% Performance
Verlust durch den C-Compiler ist gesch.....
werner schrieb:> Anbei der bereinigte Code.
... wobei Du "Regelung" immer noch nicht kapiert hast und Karl Heinz's
Vorschlag vom 15.02.2012 19:38 ignoriert hast ...
Ich gebe es auf.
und Tschüss Dietrich
Dietrich L. schrieb:> werner schrieb:>> Anbei der bereinigte Code.>> ... wobei Du "Regelung" immer noch nicht kapiert hast und Karl Heinz's> Vorschlag vom 15.02.2012 19:38 ignoriert hast ...
Hab noch gar nicht nachgesehen. Ich geh ja doch davon aus ...
< Gleich mal ansehen. >
Jup. Wo ist die nächste Tischkante?
Hallo Dietrich und Karl Heinz,
das ist mir aber jetzt unangenehm. Ich hab das gestern (19.22 und 19.38)
nicht mehr gelesen und heute früh einfach geantwortet.
Sorry.
Karl Heinz, ich hatte befürchtet das du sowas schreibst:
"geh heut Abend
lieber auf ein Bier, während der Mega8 fleissig mAh zählt und auf dem
LCD (das du auch noch nicht hast) ausgibt. Und auf die 5% Performance
Verlust durch den C-Compiler ist gesch....."
nicht das ich dir das Bier nicht gönne, sonder das es in C wohl etwas
einfacher zu machen sein soll, aber davon habe ich ja nun überhaupt
keine Ahnung.
Vielen Dank nochmal euch beiden, auch wenn es Streckenweise nicht
einfach mit mir war. Ich probiere das jetzt gleich mal aus.
Gruß Werner
werner schrieb:> nicht das ich dir das Bier nicht gönne, sonder das es in C wohl etwas> einfacher zu machen sein soll, aber davon habe ich ja nun überhaupt> keine Ahnung.
Das hab ich schon befürchtet, das du sowas schreibst.
Der springende Punkt ist: Du hast ja auch von Assembler eigentlich keine
Ahnung.
Vergleich mal deine Assembler Lösung mit
1
#include<avr/io.h>
2
#include<util/delay.h>
3
4
uint8_tAkkuSpannung;
5
uint8_tSollWert;
6
uint8_tPWMWert;
7
8
intmain()
9
{
10
SollWert=139;
11
12
DDRB=(1<<PB3);// PORTB, 3 auf Ausgang, das ist OC0
TCCR0=0b01101010;// fast pwm, prescaler 8, lösche oc0 bei gleichstand
18
19
while(1==1){
20
21
//
22
// ADC auslesen
23
// wegen ADLAR interessiert nur ADCH
24
//
25
ADCSRA|=(1<<ADSC);
26
while(ADCSRA&(1<<ADSC))
27
;
28
AkkuSpannung=ADCH;
29
30
//
31
// und PWM gegebenenfalls nachstellen
32
//
33
if(AkkuSpannung<SollWert)
34
PWMWert=PWMWert+1;
35
36
elseif(AkkuSpannung>SollWert)
37
PWMWert=PWMWert-1;
38
39
OCR0=PWMWert;
40
41
//
42
// damit sich die Spannung am PWM Pin einstellen kann:
43
// warte 5 Millisekunden, das sollte reichen.
44
_delay_ms(5);
45
}
46
}
Es findet sich alles wieder. Die Hardware-Register haben genau die
gleichen Namen, nur dass man nicht mit Zwischenregistern rummachen muss.
Um so Dinge wie Interrupt Vektoren oder Stack Initialisierung muss ich
mich überhaupt nicht kümmern.
Und dann sind halt noch eine handvoll C-Schlüsselwörter, deren Bedeutung
man kennen muss. Die sind aber nicht so schwer
if( Bedingung )
mach was;
if übersetzt sich zu: Wenn
else übersetzt sich zu: anderfalls
< heißt wie in der MAthematik kleiner, > ist größer.
== ist Vergleich auf Gleichheit und != ist Vergleich auf ungleich.
while( Bedingung )
{
mach was
}
while übersetzt sich: solange wie.
Etwas wird also wiederholt, so lange wie eine bestimmte Bedingung
erfüllt ist.
Ich wette, dass du dieses Programm ziemlich problemlos lesen kannst.
(OK, über | bzw. & müsste man noch reden, aber für den Moment ist das
geschenkt)
Das heißt jetzt um Gottes Willen NICHT; dass ich dir Assembler ausreden
will. Ich will dir nur zeigen, dass es auch andere Dinge gibt, bei denen
man sich nicht um die vielen, vielen kleinen Details kümmern muss. Wenn
ich nur daran denke, wie lange es gedauert hat, bis deine Interrupt
Vektor Tabelle richtig war .....
Hi
Es juckt halt in den Fingern.... denn das war der erste Teil...?!
Werner, tu dir selbst einen Gefallen und verstehe erst mal das Wort
"Programmieren". Du hast doch nach eigenen Aussagen Vorkenntnisse , wenn
auch mit Relaissteuerungen. Das ist nix anderes, nur statt Befehl halt
Draht.
Wenn du ein wenig Zeit übrig hast, bei AVR-Praxis steht unter FAQ ein
Beitrag "keine Angst vor Assembler". Vielleicht tust du dir den erst mal
an, bevor du hier wieder versuchst, einen Megathread anzuleiern. Denk
dran, Tischkanten sind auch nicht endlos und ich hätte an KHB's Stelle
mindestens alle 10cm reingebissen...
Ich will nicht sagen, das es zwecklos ist, aber du mußt dir endlich
angewöhnen, die gebotene Hilfe zu lesen, geistig zu verstehen und dann
einzusetzen und nicht einfach die Schnipsel zu nehmen und irgendwo in
deinen vermurksten Code einzubauen. Du hast ja versucht, die Hinweise
umzusetzen, aber immer nur soweit, wie sie dir vorgekaut wurden. Sobald
eigene Befehle dazukamen, war's wieder zum Haare raufen. Als Dozent von
dir wär ich vermutlich nach Feierabend mit einem Moralischen völlig
versackt. Du willst doch programmieren... dann reiß dich zusammen, ordne
deine Gedanken und wenn ich mich hier wiederhole: Nimm Papier und
Bleistift dazu. Halte fest, welches Register, welche Variable wofür
eingesetzt ist und nimm dieses Blatt dann beim Programmieren zu Hilfe.
Mal dir die Funktionen deines Programmes als Blackbox auf und verbinde
sie in der Reihenfolge, die logisch ist. Dann öffne deine schwarzen
Kisten und befüll sie mit, wie du so schön gesagt hast, "Intelligenz"
Das ist vom Programmgerüst her relativ einfach. Jede Blackbox steht für
ein Unterprogramm. Und da du die Funktionalität nach und nach
einpflegst, steht erst mal nur der Name (die Marke) und ein RET
1
;--------------- Einlesen der Analogwerte --------------
Zugegeben, es ist jetzt nicht unbedingt die beste Lösung, aber bevor du
dich an große Programmblöcke wagst, ist der Weg der kleinen Schritte
besser. Außerdem kannst du dein Programm auch optisch mit
Kommentarzeilen aufbessern, wie ich es bei der Eingabe und beim Regler
angedeutet habe. So ist ein Programm hinterher prima lesbar. So kannst
du auch beispielsweise Rückgabewerte definieren.
Der Vorteil dieser Methode ist, das du dir das komplette Programmgerüst
aufbauen und zwischendurch auch den Fortschritt testen kannst. Zur Not,
indem du Rückgabewerte erst mal manuell reinsetzt. Dann hast du
Anhaltspunkte für die Suche in Tutorials, wie etwas gelöst werden muß
Bei den Initialisierungen bist du ja auch den Weg gegangen. Also, setz
dich erst mal hin, trink dir einen Tee oder auch was anderes und
versuche, deine Gedanken zu ordnen. Dann mach eine Bestandsaufnahme: Was
hast du, was kannst du. Das gilt für die Hard- und Software
gleichermaßen. Dann erstell deinen Ablaufplan. Spätestens hier merkst
du, wo deine Gedanken noch wirr sind. Daher, wieder tief durchatmen und
daran denken "es ist kein Zauberwerk".
Es wird schon....
Gruß oldmax
Hallo Ihr Drei,
der C-Code sieht ja eigentlich sehr übersichtlich aus. Ich vermute mal
in der io.h steht die IVT oder so was ähnliches und die delay.h sagt ja
schon der Name wird die Zeitschleife sein. Ein Freund von mir hatte mich
auch schon angesprochen warum ich das nicht in C mache. Hab nun mal mit
Assembler angefangen, aber es wäre ein Versuch Wert, doch erst wenn
hiermit fertig bin.
Die Aufteilung in Unterprogramme hab ich ja schon weitestgehend gemacht,
ich hab halt die Auswahl/Hoch/Runter entscheidung zusammengelassen weil
es für mich übersichtlicher war.
Den Code von Karl Heinz
auswahl:
cp v_store, v_akku ; vergleiche messwert mit sollwert
breq doReturn ; gleich? dann gibts nichts zu tun
brsh up ; zu groß?
; -> nicht zu groß, also muss es zu klein sein
down:
dec v_akkupwm ; pwm bischen verringern
jmp sichern
up:
inc v_akkupwm ; pwm bischen vergrößern
sichern:
out ocr0, v_akkupwm ; und externe PWM setzen
doReturn:
ret
hab ich so ins Programm übernommen, aber jetzt regelt er die Spannung
ständig hoch und runter, auf der PWM sieht das aus als wenn ständig
durchgezählt wird.
Ich hab Spess seinen Code mal ins Programm integriert und zeigt sich
auch keine Raktion auf Spannungsschwankungen, man sieht da ein ganz
seltsames Verhalten der PWM , es ist ein ständiger Wechsel von großem
und kleinem Duty Cycle zu sehen.
Ich hab mal das Programm mir Karl Heinz seinem Code von gestern Abend
und meine weiterentwicklung mit RS232+BCD Umwandlung mit drangehangen.
Es ist schon seltsam, wenn man dem Prorammablauf folgt, auf die
Auswahlund das Hoch und Runterzählen bezogen, dann müßte es doch
eigentlich funktionieren. Ich hab schon am Wandlereingang ein kleinen C
drangehangen um event. Schwankungen auszugleichen, aber nix.
Wenn ich den AD-Eingang abziehe ist aber immer eine Reaktion zu sehen,
also funktioniert der ja.
Gruß Werner
werner schrieb:> hab ich so ins Programm übernommen, aber jetzt regelt er die Spannung> ständig hoch und runter, auf der PWM sieht das aus als wenn ständig> durchgezählt wird.
Klar. Weil keine Überprüfung eingebaut ist, ob der PWM Wert überhaupt
weiter erhöht werden darf. Wenn er 255 erreicht hat, kann er nicht mehr
erhöht werden. Mehr als 255 geht nicht. Wenn du allerdings zu 255 noch 1
dazuzählst, dann landest du wieder bei 0. Und geneu das passiert. Der
Regler möchte gerne den PWM Wert weiter erhöhen, nur wenn er das tut
macht er aus 255 eben nicht 256 sondern 0.
Bei eine entsprechende Sicherung ein (ebenso in der anderen Richtung,
kleiner als 0 geht nicht).
Wie sieht eigentlich deine Aussenbeschaltung aus?
Hallo Karl Heinz,
ok mache ich , meine Aussenbeschaltung steht ganz oben im ersten
Beitrag, ich bin aber immer noch nicht ganz zufrieden mit der P-Fet
Ansteuerung.
Uds ist immer noch zu groß, da verbrate ich nur Energie.
Hab schon mit verschiedenen Varianten experimentiert.
Gruß Werner
Hallo
ich weiß, ich habe mich lange nicht gemeldet, aber ich wollte erst meine
Hausaufgaben machen.
Es funktioniert...das ist so geil, vielen vielen Dank allen die mir
geholfen
haben und besonders KH, spess53 und oldmax die trotz allem durchgehalten
haben. Ich habe mich die letzten Tage mit der Ansteuerung des P-Fets und
dem Problem des großem Uds angenommen. Ein Freund hat mir da auch
hilfreich zur Seite gestanden. Die angehängte Schaltung habe ich so
ähnlich auch schon versucht, aber mich nie getraut RE1+2 so klein werden
zu lassen...war ein Fehler...wieder was gelernt!
Ich habe mit beiden Widerständen noch etwas experimentiert und für meine
Schaltung ist das wie auf dem Bild optimal. Das Programm hab ich auch
nochmal dran gehangen mit dem ich die Schaltung ausprobiert habe,
vielleicht hilft es anderen weiter.
Gruß Werner