PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : PWM



Gerko
18.06.2006, 09:45
Hallo Leute!

Ich fange gerade an meine ersten Sachen zu programmieren, allerdings kommt das STK500 das ich bestellt habe erst in einer Woche. Da ich allerdings trotzdem schon mal ein Programm schreiben will (bissi einlesen) habe ich gleich einmal mit LEDs angefangen. Was sicher jeder schon kennt is das ein und ausschalten. Ich habe allerdings noch eine kleine Erweiterung dazu, ich will das LED auch dimmen. Dazu verwende ich eine Hardware PWM. Da ich allerdings noch nie etwas damit gemacht habe, kenn ich mich quasi 0 aus.

Derzeit kann ich (glaube ich) nur eine bestimmte LED dimmen. Ich will das aber auch auf einen anderen eingang machen.
Wie kann man einstellen das man nicht den OCR0 Port als Ausgang für den Timer haben will, sondern PortB, 0?

Hier ist mein Assembler Code:


.include "m16def.inc"

.def temp = r16

.org 0x000 ; kommt ganz an den Anfang des Speichers
rjmp main ; Interruptvektoren überspringen
; und zum Hauptprogramm
rjmp EXT_INT0 ; IRQ0 Handler
rjmp EXT_INT1 ; IRQ1 Handler
reti ;TIM2_COMP
reti ;TIM2_OVF
reti ;TIM1_CAPT ; Timer1 Capture Handler
reti ;TIM1_COMPA ; Timer1 CompareA Handler
reti ;TIM1_COMPB ; Timer1 CompareB Handler
reti ;TIM1_OVF ; Timer1 Overflow Handler
rjmp TIM0_OVF ; Timer0 Overflow Handler
reti ;SPI_STC ; SPI Transfer Complete Handler
reti ;USART_RXC ; USART RX Complete Handler
reti ;USART_DRE ; UDR Empty Handler
reti ;USART_TXC ; USART TX Complete Handler
reti ;ADC ; ADC Conversion Complete Interrupt Handler
reti ;EE_RDY ; EEPROM Ready Handler
reti ;ANA_COMP ; Analog Comparator Handler
reti ;TWSI ; Two-wire Serial Interface Handler
reti ;SPM_RDY ; Store Program Memory Ready Handler

main:
;Stackpointer
ldi temp, LOW(RAMEND)
out SPL, temp
ldi temp, HIGH(RAMEND)
out SPH, temp
;Aus/Eingänge
ldi temp, 0x00
out DDRD, temp ;Eingang

ldi temp, 0xFF
out DDRB, temp ;Ausgang
;Interrupteingänge
ldi temp, 0b00001001 ;INT0 und INT1 konfigurieren
out MCUCR, temp

ldi temp, 0b11000000 ;INT0 und INT1 aktivieren
out GIMSK, temp
;PWM
ldi temp, 0b01110010 ;Einstellungen siehe Seite 81-83
out TCCR0, temp

ldi temp, 0b11111111
out OCR0, temp ;Stellt die Einschaltzeit ein (Alles gesetzte --> immer ein, alles aus --> immer aus)
;Interrupts freigeben
sei ;Interrupts allgemein aktivieren

Ende:
jmp ende


EXT_INT0:
sbi PortB, 0
reti

EXT_INT1:
cbi PortB, 0
reti

TIM0_OVF:
reti

Ich weiß allerdings nicht warum ich für die PWM den Time0_OVF brauche, kann ich den auch weglassen?

mfg Gerko

Schokohoernl
18.06.2006, 10:35
hallo!

erstmal: assembler ist sehr sehr schwierig (kann ich selber nicht wirklich).

hier mal ein beispiel in BASCOM (Basic):




Timer0 = Timer, Prescale = 256 'alle 256 quarzschwingungen geht timer0 um 1 hoch

do
if timer0 > 204 then
reset portb.0
elseif timer0 < 204 then
set portb.0
end if
loop

zur erklärung: am anfang wird timer0 intialisiert (ist ein 8 bit timer, also von 0 bis 255 und wieder zurück)

wenn jetzt timer0 über 204 ist (sind 80% von 255) dann wird portb.0 ausgeschaltet. der timer zählt noch 51 schritte bis 255 hoch, und dreht dann um (zählt rückwärts). wenn er jetzt wieder unter 204 ist, dann schaltet er portb.0 an und zählt runter bis 0 und dreht wieder um. dadurch hast du ein tastverhältnis von 408 zu 102, was genau 80% entspricht.

anstatt 204 kannst du auch eine variable nehmen, die du dann irgendwo in deinem programm änderst. auch kannst du mehr als nur einen PWM port erzeugen. theoretisch unendlich viele (mal abgesehen dass der AVR nicht unendlich viele hergibt)

mfg

schoko

teslanikola
18.06.2006, 10:45
erstmal: assembler ist sehr sehr schwierig (kann ich selber nicht wirklich).


Gschwääätz!!! ASM ist echt EASY, das was es so komplex macht, ist das jeder ders nicht kann sagt es wäre schwer!! Nein mal im ernst ASM ist recht einfach, das komplexe daran ist, das man sein Hirn benutzen muss und auch mitdenken muss was man schreibt, aber ASM ist Logisch aufgebaut und auch einfach zu lernen.

Also das du den OCR-Pin veränderst geht nicht, aber du kannst ja einen Interrupt aufrufen, wenn dein Timer 1. Überläuft und 2. wenn der Compare-Wert dem Timerwert entspricht ( Comparemodus ), dabei springst du immer in die SELBE Interruptroutine und tooglest den Portpin, oder du springst in 2 verschiedene und Setzt einmal den Portpin, das andere mal löscht du ihn.

das einfachste wäre aber du nimmst einfach den OCR-Pin, dann musste dich Softwareseitig nur um die Init kümmern, den rest macht dann die Hardware.

Gerko
18.06.2006, 10:56
Das ich nur den OCR-Pin verwende ist zwar einfach, aber später möchte ich damit ja auch 2 Motoren steuern. diese will ich aber im "Hintergrund" laufen lassen. Ist es nicht möglich das man den OCR einfach mit einem anderen Ausgang verbindet?

Sorry wenn ich mir das alles so einfach vorstelle, aber ich wills nunmal verstehn ^^

mfg Gerko

Schokohoernl
18.06.2006, 11:12
Das ich nur den OCR-Pin verwende ist zwar einfach, aber später möchte ich damit ja auch 2 Motoren steuern. diese will ich aber im "Hintergrund" laufen lassen. Ist es nicht möglich das man den OCR einfach mit einem anderen Ausgang verbindet?

Sorry wenn ich mir das alles so einfach vorstelle, aber ich wills nunmal verstehn ^^

mfg Gerko

hä?? nimm doch einal OC1A und OC1B (beim ATmega16). dann hast du 2 PWM ports, und brauchst nur 1 Timer.

Mfg

Schoko

P.S.: @teslanikola: ich hab mit ASM aufgehört, als ich ne kleine uhr programmieren wollte (bei addition wars mir dann zuviel) ;-)

Gerko
18.06.2006, 11:23
Ich tu mich da immer ein bischen schwer mit den Englischen Anleitungen, kann mir jemand sagen ob ich das alles richtig verstanden habe?

Also für die PWM von timer0 benötigt man das Register TCCR0, dort stellt man den Timer auf PWM ein.

TCCR0:
Bit 7: high... PWM aus
low... PWM ein
Bit 6+3: Wellenform (gewählt Phase Correct PWM)
Bit 5+4: Ausgangsverhalten (00...kein Ausgang für PWM,
01...Belegt (was heißt das, bzw. was passiert da?)
10...OC0 Ausgang auf high beim Raufzählen und low beim runterzählen
11...wie 10 nur umgekehrt
Bit 2-0: Vorteiler (Da kann man die 256 Schritte nochmal verkleinern)

Wie kann ich jetzt einstellen das ich nur in der Software einen Ausgang haben will, nicht aber bei einem Hardwareausgang? Also wie kann ich einen Interrupt auslösen?

P.S. Ich hab doch nur 3 timer im ATmega16, da muss ich hald sparsam damit umgehn, sonst kann ich ja niemals eine KI Programmieren ^^

mfg Gerko

teslanikola
18.06.2006, 12:06
P.S.: @teslanikola: ich hab mit ASM aufgehört, als ich ne kleine uhr programmieren wollte (bei addition wars mir dann zuviel)


Hmm aber fürs addiren gibts doch ein befehl: Einfach den SUB befehlo nehmen und dein "Subtrahent" negieren

Hanni
18.06.2006, 12:47
P.S.: @teslanikola: ich hab mit ASM aufgehört, als ich ne kleine uhr programmieren wollte (bei addition wars mir dann zuviel)


Hmm aber fürs addiren gibts doch ein befehl: Einfach den SUB befehlo nehmen und dein "Subtrahent" negieren

Hmm, schon bei der Addition ausgestiegen ?!
na Respekt, da frage ich mich doch, wie es dann bei Multiplikationen oder Divisionen aussieht :D

@ Tesko: oder einfach add, adc und adiw für Additionen benutzen :D

und @Schokohoernl: nix gegen dich oder Basic, zumal es sich ja für kleinere private Projekte anbietet.

teslanikola
18.06.2006, 13:19
so gehts natürlich auch^^^^

rungo
18.06.2006, 22:03
P.S. Ich hab doch nur 3 timer im ATmega16, da muss ich hald sparsam damit umgehn, sonst kann ich ja niemals eine KI Programmieren ^^


Hallo Gerko, willkommen im Club :-)
Ein Vorteil der AVR-Familie ist die große Typenvielfalt. Wenn du Zwei Motoren steuern willst und das Ganze möglichst im "Hintergrund" ablaufen soll, dann bietet es sich an einen kleineren Controller zu nehmen und ein Motormdul daraus zu machen. Es ist nämlich nicht damit getan, einfach nur Motor an und auszumachen. Es braucht ja auch Rückmeldungen von den Motoren. Wie will z.B. der Controller 'wissen', daß die Motoren auch wirklich laufen? Oder wie schnell drehen sich die Räder? Will sagen es braucht doch schon ein paar mehr Pins und Register und darum bietet es sich an aus der ganzen Sache ein Modul zu machen, also AVR und Motortreiber auf einer Platine mit Platz für Erweiterungen. Mit welchen Befehlen das dann angesteuert wird, das ist dann deiner Fantasie überlassen.
Ich denke, daß z.B. der ATtiny2313 für diesen Zweck interessant ist. Der Typ ist aber auch noch aus einem anderen Grunde interessant, und zwar deshalb, weil man für dessen Vorgänger (AT90S2313) viel Beispiel-Code im Netz findet und er sich darum besonders gut zum Lernen eignet.

@Uhr in Assembler.
Es braucht da doch überhaupt keine komplizierte Rechnerei. Das einfache Einmaleins reicht vollkommen aus und an Assemblerbefehlen braucht es dazu auch nur INC oder DEC, wüßte gar nicht wo da was addiert werden müßte? Oder hab ich was übersehen.

Grüße!

Gerko
19.06.2006, 11:50
Simmt, da hast du natürlich recht das das vielleicht ein Vorteil wäre gleich einen eigenen µC dafür zu verwende. Trotzdem würde ich gerne wissen wie Timer funktionieren.

Wie kann ich meinen PWM Ausgang auf ein Interrupt legen? Wenn jedesmal ein Interrupt ausgelöst wird, wenn der Timmer umschaltet, kann ich ja Softwartechnisch alles dazu machen das ich daraus eine Aktion Programmiere um Ausgänge ein/aus zu schalten. Ich verstehe nur nicht wie ich einen Interrupt auslöse :(

mfg Gerko

rungo
19.06.2006, 12:27
Hallo Gerko,
bin auch Lernender und bin mir nicht sicher, daß ich deinen Code richtig verstehe. Wenn du einen Timer-Int willst, wenn der Timer0 einen OV-Int auslösen soll, dann muß ins Register TIMSK noch TOIE0 hinein.

Gruß!

Gerko
19.06.2006, 13:01
Hallo Leute!

Ich habe jetzt wieder mal ein bischen herumexperimentiert, aber ich komme zu keinem Interrupt. Mitlwerweile verwende ich nicht mehr Phase Correct PWM, sondern CTC.

Ist CTC überhaupt noch PWM?, dabei wird doch die Frequenz verändert oder?

Mit diesem Programmcode wird zwar ein Interrupt ausgelöst, aber das Programm springt nicht in die Interruptvektortabelle, sondern einfach nur 3 zeilen weiter rauf. Das Hilft mir sogut wie garnicht.


.include "m16def.inc"

.def temp = r16

.org 0x000 ; kommt ganz an den Anfang des Speichers
rjmp main ; Interruptvektoren überspringen
; und zum Hauptprogramm
rjmp EXT_INT0 ; IRQ0 Handler
rjmp EXT_INT1 ; IRQ1 Handler
reti ;TIM2_COMP
reti ;TIM2_OVF
reti ;TIM1_CAPT ; Timer1 Capture Handler
reti ;TIM1_COMPA ; Timer1 CompareA Handler
reti ;TIM1_COMPB ; Timer1 CompareB Handler
reti ;TIM1_OVF ; Timer1 Overflow Handler
reti ;TIM0_OVF ; Timer0 Overflow Handler
reti ;SPI_STC ; SPI Transfer Complete Handler
reti ;USART_RXC ; USART RX Complete Handler
reti ;USART_DRE ; UDR Empty Handler
reti ;USART_TXC ; USART TX Complete Handler
reti ;ADC ; ADC Conversion Complete Interrupt Handler
reti ;EE_RDY ; EEPROM Ready Handler
reti ;ANA_COMP ; Analog Comparator Handler
reti ;TWSI ; Two-wire Serial Interface Handler
reti ;SPM_RDY ; Store Program Memory Ready Handler

main:
;Stackpointer
ldi temp, LOW(RAMEND)
out SPL, temp
ldi temp, HIGH(RAMEND)
out SPH, temp
;Aus/Eingänge
ldi temp, 0x00
out DDRD, temp ;Eingang

ldi temp, 0xFF
out DDRB, temp ;Ausgang
;Interrupteingänge
ldi temp, 0b00001001 ;INT0 und INT1 konfigurieren
out MCUCR, temp

ldi temp, 0b11000000 ;INT0 und INT1 aktivieren
out GIMSK, temp
;PWM
ldi temp, 0b00001001 ;Einstellungen siehe Seite 81-83
out TCCR0, temp

ldi temp, 0b00111111
out OCR0, temp ;Stellt die Einschaltzeit ein (Alles gesetzte --> immer ein, alles aus --> immer aus)

ldi temp, (1<<OCIE0)
out TIMSK, temp

ldi temp, (1<<OCF0)
out TIFR, temp
;Interrupts freigeben
sei ;Interrupts allgemein aktivieren

Ende:
jmp ende

EXT_INT0:
sbi PortB, 0
reti

EXT_INT1:
cbi PortB, 0
reti

Im Register TIMSK habe ich nun OCIE0 aktiviert, mit TOIE0 passiert garnichts :(

Im Register TIFR habe ich OCF0 aktiviert. Brauche ich das TIFR Register überhaupt für einen Interrupt?

Ich merke allerdings je mehr ich herumprobiere, desto mehr weiche ich von einer richtigen PWM ab. Es muss doch möglich sein, das man einfach mit einer Phase Correct PWM einen Interrupt bei jeder Änderung aufruft oder?

mfg Gerko

rungo
19.06.2006, 13:34
Das TIFR brauchst du dabei nicht. Du kannst aber im Simulator während er läuft auf das entsprechende Kästchen klicken, warauf dann der entsprechende Int ausgelöst wird.
Mit Timer0 im Simular habe ich auch Probleme, weil ich noch eine ältere Version des AVR-Studio habe. Das war auch hier meine erste Frage, weil ich sicher war alles richtig gemacht zu haben, nur es einfach nicht funktionieren wollte. Ich habe dann das Ganze probehalber mit Timer1 laufen lassen und da ging es.
Ja, es wäre vielleicht besser wenn du das Ganze ersteinmal auf eine einfache PWM-Ausgabe an einem Pin reduzierst. Wenn das Codestückchen funktioniert, dann kann man davon ausgehend dazu bauen, bzw umbauen - in kleinen Schritten.

Weniger ist oft mehr!

Grüße

Gerko
19.06.2006, 15:19
Was mich beunruhigt ist das ich für den Timer0 nur einen Eintrag in der Interruptvektortabelle habe. Für alle anderen sind mehrere vorhanden.

Wo ist eigentlich der unterschied zwischen Timer0, 1 und 2? Ich weiß das der Timer0 eien Auflösung von 8Bit hat, und die anderen 2 eine von 16Bit. Gibts sonst noch unterschiede?

Sollte das OCIE0 Bit im TIMSK Register den Interrupt TIM0_OVF auslösen oder einen anderen?

mfg Gerko

Hanni
19.06.2006, 16:35
Hallo.

ich fange mal ganz oben an bei der Beantwortung der Fragen die sich bisher aufgestaut haben.

Wie kann man einstellen das man nicht den OCR0 Port als Ausgang für den Timer haben will, sondern PortB, 0?

In dem man anstatt der Hardware PWM, die manche Timer generieren können eine Software PWM nutzt.

Ist es nicht möglich das man den OCR einfach mit einem anderen Ausgang verbindet?

Nein, das ist nicht möglich.

... aber später möchte ich damit ja auch 2 Motoren steuern ...

Wenn du dafür 2 PWM's benötigst, nimm am besten den Timer 1 oder einen µC, der dieMöglichkeit hat mit jedem Timer 2 Hardware PWM's zu generieren.

Wie kann ich jetzt einstellen das ich nur in der Software einen Ausgang haben will, nicht aber bei einem Hardwareausgang?

Du lässt die COM?0 & COM?1 Bits auf 0 stehen.

... Also wie kann ich einen Interrupt auslösen? ... {Anm: Bezug vorherige Frage}

In dem du im TIMSK Register die einsprechenden Bits setzt.
Damit kann z.B. ein Interupt ausgelöst werden wenn:
a) der Zähler überläuft
b) Der Zählerstand = dem Wert im OCR Register entspricht.

Es muss doch möglich sein, das man einfach mit einer Phase Correct PWM einen Interrupt bei jeder Änderung aufruft oder?

Wenn sich die Aussage auf eine Änderung des Zählerstandens bezieht, also z.B. von 21 auf 22, dannmuss die Antwort NEIN lauten.

Was mich beunruhigt ist das ich für den Timer0 nur einen Eintrag in der Interruptvektortabelle habe. Für alle anderen sind mehrere vorhanden.

Es sind auch mehrere ... du hast uns hier schlicht und einfach einen unterschlagen (schau mal im Datenblatt auf Seite 45).


So, und hier noch ein kleines Codebeispiel (für den ATmega16) zum Thema: "Wie erzeuge ich an jedem X - beliebigen Pin eine Pulsweitenmodulation":



.nolist
.include "m16def.inc"
.list


.def sregsave = r4 ; SREG Backup
.def ch_1 = r5 ; PWM Werte der LED's
.def ch_2 = r6
.def ch_3 = r7
.def ch_4 = r8

.def temp = r16 ; temporäre Daten
.def pwm_cnt = r17

.CSEG
.ORG 0

jmp isr_reset ; Reset Interupt Handler
jmp isr_not_used ; INT 0 Interupt Handler
jmp isr_not_used ; INT 1 Interupt Handler
jmp isr_not_used ; Timer 2 Compare Interupt Handler
jmp isr_not_used ; Timer 2 Overflow Interupt Handler
jmp isr_not_used ; Timer 1 Capture Interupt Handler
jmp isr_not_used ; Timer 1 Compare A Interupt Handler
jmp isr_not_used ; Timer 1 Compare B Interupt Handler
jmp isr_not_used ; Timer 1 Overflow Interupt Handler
jmp isr_not_used ; Timer 0 Overflow Interupt Handler
jmp isr_not_used ; SPI Transfer Complete Interupt Handler
jmp isr_not_used ; USART RX Complete Interupt Handler
jmp isr_not_used ; USART UDR empty Interupt Handler
jmp isr_not_used ; USART TX Complete Interupt Handler
jmp isr_not_used ; ADC Conversion Complete Interupt Handler
jmp isr_not_used ; EEPROM Ready Interupt Handler
jmp isr_not_used ; Analog Comperator Interupt Handler
jmp isr_not_used ; TWI Interupt Handler
jmp isr_not_used ; INT 2 Interupt Handler
jmp isr_t0_cp ; Timer 0 Compare Interupt Handler
jmp isr_not_used ; Store Program Memory Ready Interupt Handler


isr_reset:
ldi temp, high(RAMEND) ; STACK initialisieren
out SPH, temp
ldi temp, low(RAMEND)
out SPL, temp

in temp, MCUCSR ; Disable JTAG Interface
ori temp, (1<<JTD)
out MCUCSR, temp
out MCUCSR, temp

clr temp ; Ports konfigurieren:
ldi temp, (1<<DDA0) ; PIN A0, B0, C0, D0 -> Ausgang +H
; Rest: Eingang + Pullup
out DDRA, temp
out DDRB, temp
out DDRC, temp
out DDRD, temp

ldi temp, 0xFF
out PORTA, temp
out PORTB, temp
out PORTC, temp
out PORTD, temp

ldi temp, (1<<WGM01) | (1<<CS00) ; Timer 0 konfigurieren
out TCCR0, temp

ldi temp, 0x80
out OCR0, temp

ldi temp, (1<<OCIE0) ; Timerinterupts aktivieren
out TIMSK, temp

ldi temp, 0x80 ; Defaultwerte setzen
mov ch_1, temp
mov ch_2, temp
mov ch_3, temp
mov ch_4, temp
clr temp

sei ; Interupts Global aktivieren

main_loop:

; Irgendwelcher sonstiger Code
; Irgendwelcher sonstiger Code
; Irgendwelcher sonstiger Code

jmp main_loop

isr_not_used: ; Blanko für nicht genutzte Interupt Handler
reti

isr_t0_cp: ; Timer 0 Compare Interupt Handler
in sregsave, SREG ; Statusregister sichern

inc pwm_cnt ; Hilfszähler für die PWM um eins erhöhen

cp ch_1, pwm_cnt ; Vergleiche gewünschtes Tastverhältniss mit aktuellem PWM Hilfszählerstand
brlo PC+4
breq PC+3
sbi PORTA, 0
rjmp PC+2
cbi PORTA, 0

cp ch_2, pwm_cnt
brlo PC+4
breq PC+3
sbi PORTB, 0
rjmp PC+2
cbi PORTB, 0

cp ch_3, pwm_cnt
brlo PC+4
breq PC+3
sbi PORTC, 0
rjmp PC+2
cbi PORTC, 0

cp ch_4, pwm_cnt
brlo PC+4
breq PC+3
sbi PORTD, 0
rjmp PC+2
cbi PORTD, 0

out SREG, sregsave ; Statusregister wiederherstellen
reti


Eine Anmerkung zu diesem Code:

Speziell auf die PWM bezogen ist er sicherlich nicht das Optimum an Code. Dieses hat unter anderem den ganz einfachen Grund, das der derselbige doch noch irgendwo verständlich erscheinen soll, auch ohne das man erst im groß im Datenblatt nachsehen muss, um beispielsweise herauszufinden: Welche Flags denn in welchem Fall bei diversen Befehlen beeinflusst werden und welche Sonderfälle in der PWM hier nun möglich sind.

Im übrigen, ist eine Software PWM doch eher eine Taktzeitaufwändige Aktion (also in dem Fall 128 * 256 Takte für eine Periode) um die so gedimmten LED's noch flimmerfrei wahrzunehmen (also ich sag mal mit einer Refreshrate von 200 Hz) sollte der µC schon mit ca. 6,55 MHz laufen (128 x 256 x 200) was aufgerundet eben mal 8 MHz sind.

Vielleicht sollte man noch erwähnen, das es einen kleinen Fallstrick beim STK 500 gibt, da dort die LED's nicht mit "H" sondern mit "L" Pegel leuchten.

@ Schokohoernl:

Sicherlich kann man mit einem Vernünftig durchdachtem Code eine 16 oder gar 24 Kanal Software PWM zum Dimmen von LED's erzeugen, aber sicherlich nicht "theoretisch" unendlich viele.

Die limitierenden Faktoren sind direkt:
- die Taktfrequenz des Mikrocontrollers
- der Dauer die die Befehle zum Ausführen benötigen

und indirekt:
- die Programmiersprache des Programmieres.


Grüße,

da Hanni.

rungo
19.06.2006, 17:24
Hallo da Hanni,
vielen Dank für den Code. Das meiste davon ist ja typunabhängig und den Teil zu durchblicken brauch ich ein wenig Zeit. Gleich aufgefallen ist mir aber das mit der direkten Verwendung des PC. Die Möglichkeit ist mir bislang noch nicht begegnet. Prima!

Gruß!

Hanni
19.06.2006, 17:35
Das meiste davon ist ja typunabhängig


Ja, sicherlich ist der Großteil recht universell auf diversen ATmegas einsetzbar.


Gleich aufgefallen ist mir aber das mit der direkten Verwendung des PC. Die Möglichkeit ist mir bislang noch nicht begegnet.

Naja, ehe ich mich bei diesem eher simpel gestricktem Code mit 20 Sprungmarken selbst verwirre, mach ich es in dem Fall lieber so.

Aber das entscheide ich je nach Fall individuell.

Grüße,

Hanni.

Gerko
20.06.2006, 15:30
Danke für diese Ausführliche und sehr Hilfreiche Antwort von Hanni. Ich werde jetzt noch ein Wenig an meinem Code herumbasteln, und anschließend warten bis das STK 500 kommt.

Das das STK 500 die LEDs bei low und nicht bei high zum leichten bringt war mir bisher nicht bekannt, aber das ist ja für meine jezigen Testzwecke auch nicht so wichtig denke ich.

Grüße,
Gerko

Hanni
20.06.2006, 17:06
Kein Problem, wenn du den Code soweit verstanden hast, meld dich einfach mal, dann erkläre ich mal eine Variante, wie man das ganze unter gewissen Voraussetzungen wesentlich verkürzen kann.

Grüße,

Hanni.

Gerko
20.06.2006, 20:02
Also deinen Code habe ich fast zur gänze verstanden, ich weiß nur nicht wofür ich solche Sachen wie JTAG oder .CSEG brauche. Ich habe meinen Code jetzt so verändert das man mithilfe zweier Taster die LED heller oder dünkler machen kann, zumindest mit dem Simmulator funktionierts schon mal. Das STK 500 bekomm ich hald erst in ein paar tagen.

Hier mal mein Code, wenn der funktioniert, dann is das ein richtig einfach verständlicher Code zum erlernen von PWM ^^

.include "m16def.inc"

.def temp = r16
.def PWMstatus = r17
.def Helligkeit = r18

.org 0x000
jmp main ; Reset Interupt Handler
jmp EXT_INT0 ; INT 0 Interupt Handler
jmp EXT_INT1 ; INT 1 Interupt Handler
jmp Kein_Interrupt ;TIM2_COMP ; Timer 2 Compare Interupt Handler
jmp Kein_Interrupt ;TIM2_OVF ; Timer 2 Overflow Interupt Handler
jmp Kein_Interrupt ;TIM1_CAPT ; Timer 1 Capture Interupt Handler
jmp Kein_Interrupt ;TIM1_COMPA ; Timer 1 Compare A Interupt Handler
jmp Kein_Interrupt ;TIM1_COMPB ; Timer 1 Compare B Interupt Handler
jmp Kein_Interrupt ;TIM1_OVF ; Timer 1 Overflow Interupt Handler
jmp Kein_Interrupt ;TIM0_OVF ; Timer 0 Overflow Interupt Handler
jmp Kein_Interrupt ;SPI_STC ; SPI Transfer Complete Interupt Handler
jmp Kein_Interrupt ;USART_RXC ; USART RX Complete Interupt Handler
jmp Kein_Interrupt ;USART_DRE ; USART UDR empty Interupt Handler
jmp Kein_Interrupt ;USART_TXC ; USART TX Complete Interupt Handler
jmp Kein_Interrupt ;ADC ; ADC Conversion Complete Interupt Handler
jmp Kein_Interrupt ;EE_RDY ; EEPROM Ready Interupt Handler
jmp Kein_Interrupt ;ANA_COMP ; Analog Comperator Interupt Handler
jmp Kein_Interrupt ;TWSI ; TWI Interupt Handler
jmp Kein_Interrupt ;EXT_INT2 ; INT 2 Interupt Handler
jmp TIM0_COMP ; Timer 0 Compare Interupt Handler
jmp Kein_Interrupt ;SPM_RDY ; Store Program Memory Ready Interupt Handler

main:
;Stackpointer
ldi temp, LOW(RAMEND)
out SPL, temp
ldi temp, HIGH(RAMEND)
out SPH, temp
;Aus/Eingänge
ldi temp, 0xFF
out DDRB, temp ; Ausgang

;PWM
ldi temp, (1<<WGM00) | (1<<CS00) ; Einstellungen siehe Seite 81-83
out TCCR0, temp

ldi Helligkeit, 0b00000000
out OCR0, Helligkeit ; Stellt die Einschaltzeit ein (Alles gesetzte --> immer ein, alles aus --> immer aus)

ldi temp, (1<<OCIE0)
out TIMSK, temp
;External Interrupt
ldi temp, 0b00001111
out MCUCR, temp ; Steigende Flanke löst Ext. Int. 0 und 1 aus

ldi temp, 0b11000000
out GICR, temp ; Ext. Int 0 und 1 aktivieren
;Interrupts freigeben
sei ; Interrupts allgemein aktivieren

Ende:
jmp ende

Kein_Interrupt:
reti

TIM0_COMP:
inc PWMstatus
ldi temp, 0b00000001
andi PWMstatus, 0b00000001
cp PWMstatus, temp
breq PC+3
cbi PortB, 0
reti
sbi PortB, 0
reti

EXT_INT0:
ldi temp, 0b11111111
cp Helligkeit, temp
breq PC+4
ldi temp, 0b00001111
add Helligkeit, temp
out OCR0, Helligkeit
reti

EXT_INT1:
ldi temp, 0b00000000
cp Helligkeit, temp
breq PC+4
ldi temp, 0b00001111
sub Helligkeit, temp
out OCR0, Helligkeit
reti

Ich habe jetzt noch vor den Ausgangsport auch per Taster verändern zu können, das Problem ist nur, jetzt habe ich nur noch einen EXT_int.

Sehr hilfreich war der Hinweis das meine Interruptvektortabelle nicht korrekt ist, die hab ich einfach aus dem tuturial von mikrocontroller.net abgeschrieben ^^, wusste nicht das das beim mega16 anders ist.

Für Verbesserungsvorschläge bin ich natürlich immer offen.

Grüße,

Gerko

Hanni
20.06.2006, 20:37
Okay, ich hab den Code einmal überflogen.

Was mir hierbei auffällt sind die folgenden Dinge:


Die folgende Sequenz hatte durchaus seinen Sinn.
Damit wird das JTAG Interface des Mikrocontrollers deaktivert. Welches, sollte man dieses bei den FUSE Bit Einstellungen vergessen mal eben den kompletten PORT C blockiert.



in temp, MCUCSR ; Disable JTAG Interface
ori temp, (1<<JTD)
out MCUCSR, temp
out MCUCSR, temp

Die Assemblerdirektive .CSEG heisst das die nachfolgenden Zeilen im Codesegment abgelegt werden (FLASH).
Weitere Möglichkeiten sind .DSEG und .ESEG
Genauere Infos findest du in der AVR Studio Hilfe.

Beim Einsprung in eine Interupt Routine sollte immer das folgende auf den Stack wandern[list:833f66c656] Alle Register die sonst noch im Programm verwendet werden,
das Statusregister SREG
Sicherlich erscheint es in diesem Fall unnötig, allerdings sollte man es sich schon angewöhnen diese zu sichern, da es sonst bei Programmerweiterungen schnell zu unerwünschten Nebeneffekten kommen kann.

Nicht belegte Pins sollte man auf einen definierten Pegel ziehen. Ich habe mir angewöhnt, diese als Eingang mit internem Pullup zu schalten.
Der Grund ist unter anderem der jene, das man damit unerwünschte Pinwackeleien vermeidet.

Du initialisierst mit deinem Code eine Hardware PWM, also das genaue Gegenteil von dem, was ich dir in diesem Beispiel aufgezeigt habe.
Dieses ist an und für sich nicht schlimm, es ist sogar der zu bevorzugende Weg, wenn man nur 1-4 PWM's benötigt und die entsprechenden Pins am ATmega noch frei sind (OC0, OC1A, OC1B, OC2).
Das Problem bei dir ist allerdings darin, das du diese Hardwaregenerierte Pulsweitenmodulation im µC behältst und nicht ausgibst.
Mit deinem Code generierst du sicherlich eine PWM an jedem beliebigem PIN, allerdings erscheint mir der Aufwand dafür etwas übertrieben.

Auch denke ich, das du damit bei gewissen Werten saubere Rechteckimpulse mit 50% Puls Pausenverhältniss generierst.
[/list:o:833f66c656]

So, das war ein kleiner Auszug aus 'Hannis aktueller Meckerstunde' ich hoffe du nimmst es mir nicht übel.

Grüße,

Hanni.

Gerko
21.06.2006, 14:34
Hallo,

Ich nehme deine "Meckerstunde" ja nicht böse, ist ja sehr hilfreich für mich.

Den Befehl .CSEG kannte ich bisher noch nicht. Bis jetzt hat es auch immer ohne dem funktioniert, aber ich lasse mir gerne einreden das dieser Befehl nicht unwichitg ist.

Das mit dem SREG habe ich gerade nachgelesen, bisher dachte ich das die sicherung davon automatisch geschieht.

Ich wollte ja von Anfang an eine Hardware PWM, und ich finde nicht das ich das so umständlich gemacht habe. Ich glaube sogar das meine Methode schneller läuft als deine, denn bei dir muss jedesmal ein ganzes Register durchlaufen werden. Dadurch wird dieFrequenz erheblich gesenkt denke ich. Außerdem ist deine Aussage das ich max 4 Ausgänge verwenden kann nicht richtig, ich kann ja alle ansteuern wenn ich will.

Was meinst du mit 50% Pausenverhältniss?

Grüße,

Gerko

EDIT: Mir ist gerade aufgefallen das du die Register r4, r5,... und so verwendest. Mir hat mal jemand erzählt das diese Register für andere sachen vorbehalten sind und man nur die Register R16-R32 als Arbeitsregister verwenden kann. Fals das stimmt, für was kann man die register r1-r15 dann verwenden?, ich hab in dem saulangen AVR datenblatt nichts darüber gefunden :(

Hanni
22.06.2006, 11:44
Okay:

1. Die Register 0-15 haben eigentlich nur die Einschränkung, das gewisse Befehle mit diesen nicht funktionieren.

also z.B. ldi cpi andi

ansonsten bieten sich diese in diesem Fall für mich regelrecht an, da der Inhalt der von diesen Verarbeitet wird in meinem Fall aus dem RAM kommen würde.

2. 50% Pausenverhältniss:
bei gewissen Werten im OCR (um genau zu sein ist es 0 & der maximalwert) erhältst du exakt einen Compare Interupt je 0-max-0 Zyklus. Daraus folgt, das du nur einen Sauberen Rechteck mit einem Puls - Pausenverhältniss von 50% / 50% erzeugst.

3. "Außerdem ist deine Aussage das ich max 4 Ausgänge verwenden kann nicht richtig, ich kann ja alle ansteuern wenn ich will."

Nuja, mit meiner Routine kann ich aber 4 verschiedene PWM's erzeugen (also 4 verschiedene Puls - Pausenverhältnisse).
Das dürfte mit deiner Routine eben nicht funktionieren.

Grüße,

da Hanni.

Gerko
23.06.2006, 20:16
Hi!

Ich habe gestern mein STK 500 bekommen :) und jetzt verstehte ich endlich was du damit meinst, das es nicht perfekt ist ^^. Ich habe mein Programm daher jetzt komplett umgekrempelt und verwende nun den OC0 Ausgang. Anschließend überprüfe ich einfach in einer Routine ob dieser Ausgang gesetzt oder nicht gesetzt ist, und wende dies auf andere Ausgänge an, diese kann ich natürlich auch aus, bzw. einschalten, je nach belieben.

Wenn es allerdings so weit ist das ich eine Platine baue, und dort auch einen Motortreiber includiere, dann werde ich diesen über den Timer1 laufen lassen, geht einfach schneller, und im Hintergrund.

Das ich bei deiner Routine 4 verschiedene PWMs erzeugen kann weiß ich, wir vielleicht von Vorteil sein, wenn man auf den Rädern unterschiedliche Drehzahlen haben will, ich hatte eigentlich vor den Roboter nur schneller oder langsamer fahren zu lassen, und die Kurfen mit Drehungen zu erledigen. An so "schöne Kurfen" wagte ich bis jetzt noch garnicht zu träumen ^^.

Danke jedenfalls das du mir soweit geholfen hast. Jetzt verstehe ich endlich wie Timer funktionieren, und vorallem wie Interrupts ausgelöstet werden.

Grüße,

Gerko

Hanni
23.06.2006, 22:15
Nichts zu danken, jeder hat mal mit dem kleinem Einmaleins angefangen. Auch wenn dieses so der eine oder andere gelegentlich vergisst.

RP6fahrer
25.02.2012, 13:46
Hallo Leute,

Ich hab neulich angefangen, mit Mikrocontrollern zu experimentieren und hab mir nen Atmega 16 gekauft... Zusätzlich benutze ich das Buch von Elektor "AVR Hardware und C-Programmierung in der Praxis". Ich habe jetzt angefangen mit PWM zu arbeiten. Allerdings schaffe ich es nicht, dass sich der OCR0 Wert verändert. Ich habe folgendes Programm:


#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>

uint8_t wert;

void delay_ms(uint16_t ms)
{
for(uint16_t t=0; t<=ms; t++)
_delay_ms(1);
}

// Timer/Counter0 Overflow
ISR (TIMER0_OVF_vect)
{

PORTD |= (1 << PD0); // PD0 ein

}

// Timer/Counter0 Compare Match
ISR (TIMER0_COMP_vect) // Wenn OCR0 Wert erreicht wird
{
PORTD &= ~(1 << PD0); // PD0 aus

}

void main(void)
{
DDRD |= (1 << DDD0) | (1 << DDD1) ; // PORTB als Ausgang
wert = 10;

TIMSK |= (1 << TOIE0) | (1 << OCIE0); // IRQ bei Überlauf + Compare Match
OCR0 = wert; // Vergleichswert 128 einstellen
sei(); // IRQs enable
PORTD |= (1 << PD0); // PB0
TCCR0 |= (1 << CS02); // Prescaler: 1/1024 (CS1=0 per default) (1 << CS00) |

while (1); // endlos
{

delay_ms(100);
if(wert >= 240)
wert = 10;
else
wert++;
OCR0 = wert;
}
return 0;
}

Der Sinn ist, eine LED zu faden... also von dunkel bis hell. Aber es klappt nicht. Der Wert von OCR0 verändert sich nicht.
Vielen Dank schonmal im voraus.

MfG
RP6fahrer

021aet04
25.02.2012, 13:53
Was passiert mit dem Ausgang? Immer ein, Immer aus, immer mit der gleichen Helligkeit gedimmt?

MfG Hannes

RP6fahrer
25.02.2012, 13:57
Hi,
Danke für die schnelle Antwort.... also die LED leuchtet immer so, als würde OCR0 = 10 sein.

021aet04
25.02.2012, 14:21
Ist das ein Beispiel aus dem Buch? Wenn nicht würde ich das Programm so umschreiben das du den Timer/Counter direkt zur PWM erzeugung nutzt (Phase Correct PWM oder Fast PWM).

MfG Hannes

sternst
25.02.2012, 18:59
while (1); // endlosDas Programm kommt überhaupt nicht bis zu dem Teil, wo OCR0 verändert wird.

021aet04
25.02.2012, 19:33
@sternst
Du hast recht. Das habe ich ganz übersehen.

MfG Hannes

RP6fahrer
26.02.2012, 13:33
Das Programm ist ursprünglich aus dem Buch ich habe es so abgeändert, dass halt in der while Schleife was steht. Im Original steht nichts in der while Schleife.

Das ist mir irgendwo auch bewusst, dass es nie bis zum ändern von OCR0 kommt, aber leider verstehe ich das nicht. Ich hab nämlich im Internet gesucht, wo LEDs ebenfalls über timer und PWM gedimmt werden, aber nie was für mich passendes gefunden. Was müsste ich denn abändern, oder besser gesagt, wie müsste es sein, dass die LED langsam ein geht.

Vielen Dank nochmals.
MFG
RP6fahrer

021aet04
26.02.2012, 14:52
Die while Schleife passt im Prinzip so. Du hast aber einen kleinen Fehler drinnen, der aber eine große Auswirkung hat. Das Semikolon muss entfernt werden. Ein gutes Tutorial für C ist das von Mikrocontroller.net => http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial

Wenn du nur ein Kommando hast musst du ein Semikolon verwenden (z.B. DDRB |= (1<<PB1);). Ansonsten musst du mit den geschwungenen Klammern arbeiten. Da bei deinem While(1) ein Semikolon war, ist immer nur das While (1); ausgeführt worden (abgesehen von den ISR Routinen). Da zwischen while(1) und dem Semikolon nichts ist wird auch nichts ausgeführt. Würdest du z.B. while(1) PORTB ^= (1<<PB1); schreiben würdest du den PB1 immer toggeln (so schnell der µC arbeiten kann). Wenn keine ISR aktiv wäre würde der µC nur den Ausgang toggeln.

MfG Hannes

RP6fahrer
26.02.2012, 15:10
Vielen vielen Dank,
Ich hab das Semikolon hinter while(1) weggenommen und schon ging's. Ich hätte nicht gedacht, das es nur wegen so einem kleinen Fehler nicht geht. Also nochmals vielen Dank.

Mfg
RP6fahrer