Erste Anfänge mit C und AVR

Kani

Neues Mitglied
07. Jan. 2009
339
0
0
Spenge
Sprachen
Mahlzeit,

also ich komm mal ein bisschen zur Sache:
Ich bin im www.dmxcontrol.de Forum aktiv. Neulich fingen wir an, Schrittmotor-Steuerungen zu bauen. Das alles basierend auf einen Atmega 8515.
http://www.hoelscher-hi.de/hendrik/light/dmxstep.htm
Die Seite unseres "Lehrers". xD

Ich habe ein AVRISP mkII und habe dieses Tutorial durchgelesen.
http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial
Aber nicht nur überflogen sondern wirklich gelesen. Hab ca. 5 Stunden oder so gebraucht, weiß nicht genau.

Jetzt wollte ich in den Praktischen Teil gehen. Und zwar mit so Sachen:
-Wie bringe ich eine LED zum leuchten?
-Wie schreib ich eine PWM. (Zum dimmen von z.B. LEDs)
-Ich drücke einen Taster und eine LED geht an (-bzw. auch wieder aus)

Und all sowas für den Anfang.
Jetzt dazu, wenn ich fragen hab, könntet ihr mir dann vllt. netterweise helfen?

Lg,
Kani

EDIT: Achja, ich sollte vllt noch erwähnen, dass ich die neueste Version vom AVR Studio und WinAVR benutze. Soein bisschen habe ich mich schon in die Materie eingearbetet.
 
Servus,

wegen den LEDs leuchten:

Code:
#include <avr/io.h>
#include <util/delay.h>

int main (void) {
DDRC |= (1<<PC1); // Pin 1 vom Port C als Ausgang festlegen

while (1) {
PORTC |=(1 << PC1); // Ausgang 1 aktivieren
_delay_ms(1000); // 1 Sekunde warten
PORTC &= ~(1 << PC1); // Ausgang 1 deaktivieren
_delay_ms(1000); // 1 Sekunde warten
}

}

Grüsse
Heinrich
 
So, ich habe mal ein bissl gewerkelt. Bin aber nicht zu einem erfreulichen Ergebnis bekommen. Ziel war es, LED 1 (PA0) und LED 2 (PA1) abwechselnd zum leuchten zu bringen. Und zwar mit einem 1 Sekündigen Intervall.

Quellcode:
Code:
/* Testschaltung
Version: 0.1
Datum: 07.01.2009
Autor: Finn 'Kani' Schürmann
Target: AtMega 8515
Lizenz: Keine
*/

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


int main(void)
{
	
	
	DDRA = 0xFF; //PORTA auf Ausgang stellen
	PORTA = 0x00; //Sicher gehen das zum Start alle Bits "aus" sind.
	DDRB = 0x00;  //PORTB als Eingang zur Vorbeugung von Kurzschlüssen
 	DDRC = 0x00;  //PORTC als Eingang zur Vorbeugung von Kurzschlüssen
 	DDRD = 0x00;  //PORTD als Eingang zur Vorbeugung von Kurzschlüssen

		while( 1 )
		{
	
			PORTA = (1 << PA1); //LED 1 an
  			PORTA = (0 << PA0); //LED 2 aus
 
   				_delay_ms(1000); //Zeitintervall 1sek
 
  			PORTA = (1 << PA0); //LED 2 an
  			PORTA = (0 << PA1); //LED 1 aus
		}	
}

Aktueller Stand: Alle LEDs sind am dauerhaften Leuchten.
Anbei noch das komplette Projekt.
Hat es was mit den Fusebits zutun?

Die LEDs habe einen gemeinsamen + und der - ist an den Beinchen vom PORT A. Kann es sein, dass ich deßwegen PORTA auf Eingang DDRA = 0x00 schalten muss?
 

Anhänge

  • Testschaltung.zip
    10,8 KB · Aufrufe: 6
Hi,

eine Beule ist die Werte Zuweisung für PORTA mit dem " = " Zeichen, d.h. es wird immer das ganze Byte reingeschrieben und somit der ganze Port überschrieben:

Code:
PORTA = (1 << PA1);  -> PORTA = 0x02
PORTA = (0 << PA0);  -> PORTA = 0    :-(  

_delay_ms(1000); 

PORTA = (1 << PA0);  -> PORTA = 0x01
PORTA = (0 << PA1);  -> PORTA schon wieder 0   :-(

Im Ergebnis ist PA0 und PA1 also immer "0"!

Besser sollte es mit der Bit-Manipulation gehen:

Code:
PORTA |= (1 << PA1);    // Bit PA1 wird gesetzt
PORTA &= ~(1 << PA0); // Bit PA0 wird gelöscht

_delay_ms(1000); 

PORTA |= (1 << PA0);   // Bit PA0 wird gesetzt
PORTA &= ~(1 << PA1);  // Bit PA1 wird gelöscht

Ansonsten können auch die Macros für SetBit und ClearBit verwendet werden.
Ich glaube die Macros werden in AVR-GCC nicht mehr unterstützt können aber nachträglich definiert werden.

Code:
#ifndef cbi
  #define cbi(sfr, bit)     (_SFR_BYTE(sfr) &= ~_BV(bit)) 
#endif
#ifndef sbi
  #define sbi(sfr, bit)     (_SFR_BYTE(sfr) |= _BV(bit))  
#endif

damit sieht der Code dann so aus:

Code:
sbi(PORTA,PA1)  // set PA1
cbi(PORTA,PA0)  // clear PA0

_delay_ms(1000); 

sbi(PORTA,PA0) // set PA0
cbi(PORTA,PA1) // clear PA1

Gruß
rangar
 
Hi Kani ,

ich programmier zwar in Assembler und verstehe nicht so viel von C, aber
ich glaube, es liegt nicht an den Fuse-Bits sondern eher an deinen
Operationen, mit denen du die Port-Bits löscht und setzt.

Deine Version ...


CodeBox C

PORTA = (1 << PA1); //LED 1 an
PORTA = (0 << PA0); //LED 2 aus


Die Version von Heinrich ...


CodeBox C

PORTC |=(1 << PC1); // Ausgang 1 aktivieren
PORTC &= ~(1 << PC1); // Ausgang 1 deaktivieren


Ich schätze mal, diese |= und &= sind UND- und ODER-Verknüpfungen
und die Tilde ( ~ ) vor der Klammer negiert den Inhalt (liege ich da richtig ?)

In Assembler mache ich das mit sbi / cbi (Set und Clear von einem Bit im Input-Register)
oder sbr / cbr (Set und Clear von einem Bit in einem normalen Register).



CodeBox ASM
cbi portc,7			; PortC7 auf Low
sbi portc,7 ; PortC7 auf High
sbi ddrc,7 ; PortC7 auf Output

cbr r18,0b00001001 ; Bit 0 und 3 im Register r18 löschen
sbr r18,0b00001001 ; Bit 0 und 3 im Register r18 setzen

@Dirk : Das Highlighting funktioniert ganz gut :D
Wenn du dir so viel Mühe damit gegeben hast, sollte man es auch benutzen :)

So einfach mal als Code-Beispiel ...
Ich würde an deiner Stelle also mal an dem Punkt weitersuchen wo du die
Bits in den Ausgangsregistern setzt und löschst.

- - - - - - - -
Da hat Rangar wohl zur selben Zeit geantwortet :D
Der letzte Code-Abschnitt von ihm sieht schon sehr stark nach Assembler aus :)

Gruß
Dino
 
Hi, ich habe das mal in
Code:
/* Testschaltung
Version: 0.1
Datum: 07.01.2009
Autor: Finn 'Kani' Schürmann
Target: AtMega 8515
Lizenz: Keine
*/

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


int main(void)
{
	
	
	DDRA = 0xFF; //PORTA auf Ausgang stellen
	PORTA = 0xFF; //Sicher gehen das zum Start alle Bits "aus" sind.
	DDRB = 0x00;  //PORTB als Eingang zur Vorbeugung von Kurzschlüssen
 	DDRC = 0x00;  //PORTC als Eingang zur Vorbeugung von Kurzschlüssen
 	DDRD = 0x00;  //PORTD als Eingang zur Vorbeugung von Kurzschlüssen

		while( 1 )
		{
			PORTA |= (1 << PA1); // Bit PA1 wird gesetzt
			PORTA &= ~(1 << PA0); // Bit PA0 wird gelöscht 

				_delay_ms(1000);

			PORTA |= (1 << PA0); // Bit PA0 wird gesetzt 
			PORTA &= ~(1 << PA1); // Bit PA1 wird gelöscht

                                _delay_ms(1000)

		}	
}

geändert.
So passt es. Es fehlte noch ein Delay.

Ich hab da mal eine Frage noch:

Kommt man aus einer Whileschleife irgendwie raus? Es ist doch so als wenn sich der Mikrocontroller in dieser Schleife aufhängt oder nicht?
Wofür steht die Zahl while(1)?
 
Hi Kani,
Ich hab da mal eine Frage noch:

Kommt man aus einer Whileschleife irgendwie raus? Es ist doch so als wenn sich der Mikrocontroller in dieser Schleife aufhängt oder nicht?
Wofür steht die Zahl while(1)?

Wiederhole das ganze so lange die Bedingung wahr ist.
1 ist aber immer wahr => also Endlosschleife.
Das ist die Hauptschleife (Das Hauptprogramm, die Hauptroutine)
So macht man das aber auch unter Perl, Assembler, ...

Man baut am Anfang die Initialisierung des Systems (die Ports, LCD, ...)
und läuft danach in die Hauptroutine, die bis zum Stromabschalten
durchlaufen wird.

Die Hauptschleife wird z.B. durch Interrupts verlassen, wenn man
z.B. eine Taste gedrückt hat, der USART was empfangen hat, der
Timer einen Interrupt (z.B. Überlauf) generiert hat, ...
Danach wird mit einem RETI oder RET oder wie auch immer wieder
in die Hauptschleife gesprungen.

Wenn Du die Endlosschleife nicht hast, dann blinkt dein uC nur einmal
hin und her und läuft danach in den nichtbenutzten Bereich deines
Programm-Flashs. Was da dann passiert, kann keiner mehr vorhersagen.
Wenn der Bereich nur 0-Byte enthält führt er nur NOPs aus und fängt
danach wenn er hinten rausläuft wieder vorne im Programm-Flash mit
Adresse 0x0000 an. Er läuft dann also auch im Kreis :D
Wenn aber statt dessen der Bereich 0xFF enthält (nicht programmierte
Flash-Zellen) dann führt er den Befehl aus, der durch diesen Wert
gebildet wird (was das auch immer ist). Die Schleife ist also
notwendig. Außerdem soll er nach dem Blinken ja nicht in deine
hinter der Hauptroutine liegenden Subroutinen laufen.

Dein Programm was du in den Controller schreibst ist ja nicht wie
ein Programm, was du z.B bei Windows auf der Console aufrufst
und nach der Ausführung wieder in der Console landest. Es ist eher
so wie das Betriebssystem des Minicomputers. Und Windows läuft ja
auch nicht einmal durch und schaltet sich danach wieder ab. Irgendwie
soll es ja auch was tun :D

Gruß
Dino
 
Okay danke, ich denke ich hab das soweit verstanden.

Jetzt möchte ich eine LED aufleuchten lassen.
Dass soll aber nicht An/Aus gehen, sondern langsam einsteigen. Also muss Pulsweitenmodulation her.

Der ATMEGA8515 hat ein 8 und ein 16 bit Timer/Counter.
Kann ich auch PWM über normale Ausgänge programmieren?
(Soft PWM?)
 
Hi Kani,
Kann ich auch PWM über normale Ausgänge programmieren?
(Soft PWM?)

Klar geht das. Das ist nicht weiter kompliziert :)

Du hast ja deine Hauptschleife die endlos vor sich hinschleift :D
Da baust du einen 8-Bit-Zähler rein, der bei jedem Schleifendurchlauf
um 1 hochgezählt wird. Wenn er die 255 erreicht hat, wird er beim
nächsten Hochzählen überlaufen und wieder bei 0 anfangen. Also
ungefähr so :

0, 1, 2, .... 253, 254, 255, 0, 1, 2, 3, .... usw

Das ist dein Rampenzähler. Im analogen PWM wäre das dein Sägezahngenerator.
Jetzt nimmst Du einfach eine Variable/Register was auch immer wo dein
Stellwert reinkommt. Also wieder ein 8-Bit-Register mit Werten
von 0 bis 255.

Jetzt kommt ein Vergleich, ob der Wert in deinem Rampenzähler größer ist
als in deinem Stellwert-Register. Wenn der Rampenzähler einen größeren
Wert enthält, dann wird der Ausgangspin auf 0 gesetzt und wenn er
kleiner oder gleich ist, dann wird der Ausgangspin auf 1 gesetzt.
Damit ist deine PWM im groben schon lauffähig ;)
Diese Aufgabe macht im Analogen Fall ein Komparator.

Aber es gibt einen Spezialfall. Wenn der Stellwert 0 ist mußt du den Ausgang
auch auf 0 setzen obwohl der Rampenzähler kleiner oder gleich deinem
Stellwert ist. Das macht der Komparator in Analogtechnik gleich mit ;)
Wenn Du den Sonderfall nicht behandelst würden auf deinem Ausgang
immer kurze Spikes auftauchen, da du ihn nie komplett über den Stellwert
ausschalten könntest.

Das war es dann schon.

Verstanden ?

Code:
Rampenzähler = 0
Stellwert = irgendwas

Schleife:

    Ausgangspuffer = 0
    Rampenzähler kleiner-oder-gleich Stellwert ? => dann Ausgangspuffer = 1
    Stellwert gleich 0 ? => dann Ausgangspuffer = 0 (Sonderfall)
    Ausgangspuffer auf den Ausgangspin der ATmega übertragen

    (irgendwo hier zwischen kann man den Stellwert noch ändern, z.B. über Tastenabfrage)

    Rampenzähler + 1 (increment)
Sprung zum Label Schleife: (deine Endlosschleife)

Hier noch eine Grafik zum besseren Verständnis ...
(Ein Bild sagt mehr als tausen Worte ;) )
Soft-PWM.gif

So sieht eine Soft-PWM aus wenn man es universell beschreibt.
Viel Spaß bei der Umsetzung :stupido: ;)

Gruß
Dino
 
Hi Dino,

also was PWM macht, habe ich verstanden. Ist es nicht so, dass der Puls immer an einer fortgeschritteren Position einschaltet, wieder aus und sofort bei einer weiteren ein.
(Die Sägezähne)

Bei mir hapert es noch an der Umsetzung.
PA01 und PA05 sollen Abwechselnd aufleuchten. Also PA01 "dimmt" hoch bis 100% und wieder ab. Wenn PA01 dann 0% erreicht hat, soll das gleiche mit PA05 passieren und so weiter.
An den Ausgängen sind LEDs.


Könntest du mir mal bitte einen Code dafür als Beispiel schreiben?
Ich habe ja von dir die Erklärung bekommen, nur keine Befehle, was ich wie effektiv schreiben muss.

Könntest du das mal machen bitte? Danke!
 
Hi Kani,

also was PWM macht, habe ich verstanden. Ist es nicht so, dass der Puls immer an einer fortgeschritteren Position einschaltet, wieder aus und sofort bei einer weiteren ein.
(Die Sägezähne)
Die Frequenz des Sägezahns (oder Dreiecks oder Sinus oder was man möchte)
ist identisch mit der Frequenz des PWM-Signals. Kann es sein das du
verstanden hast was PWM macht aber nicht wie es das macht? Oder ich
versteh gerade den gequoteten Teil nicht ganz :eek: Na mal sehn ;)

Bei mir hapert es noch an der Umsetzung.
PA01 und PA05 sollen Abwechselnd aufleuchten. Also PA01 "dimmt" hoch bis 100% und wieder ab. Wenn PA01 dann 0% erreicht hat, soll das gleiche mit PA05 passieren und so weiter.
An den Ausgängen sind LEDs.
Also 2 PWM Ausgänge die abwechselnd hoch und runter dimmen sollen.
Kein Thema :) Mein erstes Projekt war nen RGB-Dimmer mit Weiß-Boost
und Farbmischtabelle. War nicht weiter kompliziert ;) Das bring ich dir
schon bei :cool:

Könntest du mir mal bitte einen Code dafür als Beispiel schreiben?
Ich habe ja von dir die Erklärung bekommen, nur keine Befehle, was ich wie effektiv schreiben muss.
Könntest du das mal machen bitte? Danke!
Ich kann aber nur in Assembler :( Mußt du dann in C umsetzen oder einfach
mal das Assembler-Proggi zum anschauen raufproggen.

So. Erst mal weg zum Beispiel basteln :)

Gruß
Dino
 
Ein kleiner Soft-PWM

Hi Kani !

Hier die erste Vorstufe mit nur einer LED :)
Der Rest kommt noch...



CodeBox ASM

; --------------------------------------
; Test-Projekt: dimmende LED an Port PA0
; --------------------------------------
;
.include "m8515def.inc" ; Definitionsdatei laden
.cseg ; Beginn eines Code-Segmentes
.org 0 ; Startadresse=0
;


start: ldi r16,low(ramend) ; Diese 4 Befehle kann man sich sparen, wenn man ohne Subroutinen
ldi r17,high(ramend) ;und push/pop oder Interrupts auskommt
out spl,r16 ; Stackpointer auf
out sph,r17 ; RAM-Ende setzen


ldi r16,0b11111111 ; PortA: PA0-7 auf Ausgang
out ddra,r16 ; setzen

ldi r18,0x24 ; Der Dimm-Wert fuer LED PA0 (gewuenschte Helligkeit)

clr r20 ; unser Rampenzaehler - erst mal auf 0x00 setzen
ldi r21,0xFF ; unser Ausgangspuffer - auf 0xFF da die LEDs ja nach +5V gehen
; +5V auf beiden Seiten der LED => LED ist aus


mainloop: ; Anfang der Hauptschleife (ein Label oder Sprungmarke)

; Hier kann man jetzt irgendwas mit dem Dimmwert machen
; groeßer machen = LED wird heller
; kleiner machen = LED wird dunkler

; ===== Unser PWM-Generator fuer PA0 =====
sbr r21,0b00000001 ; Bit 0 im Ausgangspuffer setzen => LED 0 aus
cpi r18,0 ; Dimmwert PA0 auf 0 ? Unser Spezialfall
breq pa0off ; PA0 ist aus (Branch If Equal => Springe wenn gleich)

cp r18,r20 ; r18 kleiner r20 ? ( Dimmwert kleiner als Rampe ? )
brlo pa0off ; Wenn r18 kleiner als Rampe => Ausgang aus (Branch If Lower)
cbr r21,0b00000001 ; Bit 0 loeschen (Low = LED 0 an)
pa0off: ;


out porta,r21 ; Ausgangspuffer auf Port A schreiben


inc r20 ; Rampenzähler +1
rjmp mainloop ; und die Endlosschleife => zurueck zum Label mainloop und ab da weiter

Den ersten Teil hab ich Formatmäßig noch ein wenig an das Forum angepaßt
(Tabulatorbreiten sind zwischen Editor/Forumeditor und Darstellung unterschiedlich)
Für die 2te Hälfte hatte ich dann keine Lust. Sieht man evtl ein wenig :rolleyes:
Also die Zeilen machen ein wenig PWM mit dem vorgegebenen Wert. Getestet
hab ichs nicht, sollte aber auf deinem Prozzi laufen.

Kannst Du ja mal ins AVR-Studio laden, assemblieren und dann auf den uC
übertragen. Wenn du den Wert in Register 18 änderst dann verändert sich
die Helligkeit der LED.

So jetzt gehts weiter mit 2 LEDs und rumgedimme :D

Gruß
Dino
 
Der Rest vom Schützenfest

Hi Kani,

Hier kommt der Rest :D

Als Projekt im AVR-Studio ...
Anhang anzeigen 2LED-Dimmer.zip
Mit allem drum und dran (Hex-Datei, Quellcode, ...)

Und hier auch für die nicht angemeldeten der Quellcode ...


CodeBox ASM

; ---------------------------------------------
; Test-Projekt: dimmende LEDs an Port PA0 + PA5
; ---------------------------------------------
;
.include "m8515def.inc" ;Definitionsdatei laden
.cseg ;Beginn eines Code-Segmentes
.org 0 ;Startadresse=0
;


start: ldi r16,low(ramend)
ldi r17,high(ramend)
out spl,r16 ;Stackpointer auf
out sph,r17 ;RAM-Ende setzen


ldi r16,0b11111111 ;PortA: PA0-7 auf Ausgang
out ddra,r16 ;setzen

clr r18 ; Der Dimm-Wert fuer LED PA0
clr r19 ; Der Dimm-Wert fuer LED PA5

clr r20 ; unser Rampenzaehler - erst mal auf 0x00 setzen
ldi r21,0xFF ; unser Ausgangspuffer - auf 0xFF da die LEDs ja nach +5V gehen
; +5V auf beiden Seiten der LED => LED ist aus

clr r22 ; noch nen Zaehler fuer Verzoegerung
ldi r23,0b00000001 ; Ein wenig Statuskram
; Bit 0 = PA0 heller
; Bit 1 = PA0 dunkler
; Bit 2 = PA5 heller
; Bit 3 = PA5 dunkler



loop: ; Anfang der Hauptschleife (ein Label oder Sprungmarke)






; ===== Unser PWM-Generator fuer PA0 =====
sbr r21,0b00000001 ; Bit 0 im Ausgangspuffer setzen => LED 0 aus
cpi r18,0 ; Dimmwert PA0 auf 0 ? Unser Spezialfall
breq pa0off ; PA0 ist aus (Branch If Equal => Springe wenn gleich)

cp r18,r20 ; r18 kleiner r20 ? ( Dimmwert kleiner als Rampe ? )
brlo pa0off ; Wenn r18 kleiner als Rampe => Ausgang aus (Branch If Lower)
cbr r21,0b00000001 ; Bit 0 loeschen (Low = LED 0 an)
pa0off: ;


; ===== Unser PWM-Generator fuer PA5 =====
sbr r21,0b00100000 ; Bit 5 im Ausgangspuffer setzen => LED 5 aus
cpi r19,0 ; Dimmwert PA5 auf 0 ? Unser Spezialfall
breq pa5off ; PA0 ist aus (Branch If Equal => Springe wenn gleich)

cp r19,r20 ; r19 kleiner r20 ? ( Dimmwert kleiner als Rampe ? )
brlo pa5off ; Wenn r19 kleiner als Rampe => Ausgang aus (Branch If Lower)
cbr r21,0b00100000 ; Bit 5 loeschen (Low = LED 5 an)
pa5off: ;


out porta,r21 ; Ausgangspuffer auf Port A schreiben


inc r20 ; Rampenzähler +1


; - - - - zwischen diesen beiden Befehlen NICHTS einfuegen - - - - -
; Der Platz ist nur zum trennen der Funktionsbloecke hier drin !!



; ===== Ab hier ist alles fuer das langsame rummdimmen, mit Betonung auf langsam ;-)
brne time1 ; hat Rampenzaehler 0 erreicht? nein => also weiter
; dieser Befehl MUSS direkt hinter dem inc r20 (Rampenzaehler+1) stehen
; da er die Veraenderungen in den Flags auswertet (Carry,Zero,Sign,...)

; ===== An dieser Stelle haben wir die Rampe einmal hochgezaehlt (= 256 Schleifendurchlaeufe)

inc r22 ; Rampenzaehler war auf 0 also den Verzoegerungszaehler eins hoch
brne time1 ; hat Verzoegerungszaehler 0 erreicht? nein => also weiter

; ===== An dieser Stelle haben wir die Rampe 256 mal hochgezaehlt (= 65535 Schleifendurchlaeufe)
; jetzt darf der Dimmwert sich aendern

pa0up:
sbrs r23,1 ; Soll PA0 heller werden ? (Skip next Command if Bit 0 in Reg23 is Set)
rjmp pa0dn ; Sollte wohl nicht heller werden = also weiter
inc r18 ; PA0 etwas heller machen
cpi r18,0xFF ; Ist der Dimmwert ganz oben ?
brne time1 ; nein also naechster Durchlauf
lsl r23 ; War ganz oben => Also Statuskram aendern (Logical Shift Left)
; Aus 00000001 im Reg23 wird 00000010
; => also im naechsten Durchlauf weiter mit PA0 dunkler
rjmp time1 ; naechster Durchlauf


pa0dn:
sbrs r23,1 ; Soll PA0 dunkler werden ? (Skip next Command if Bit 1 in Reg23 is Set)
rjmp pa5up ; Sollte wohl nicht dunkler werden = also weiter
dec r18 ; PA0 etwas dunkler machen
cpi r18,0x00 ; Ist der Dimmwert ganz unten ?
brne time1 ; nein also naechster Durchlauf
lsl r23 ; War ganz unten => Also Statuskram aendern (Logical Shift Left)
; Aus 00000010 im Reg23 wird 00000100
; => also im naechsten Durchlauf weiter mit PA5 heller
rjmp time1 ; naechster Durchlauf



pa5up:
sbrs r23,2 ; Soll PA5 heller werden ? (Skip next Command if Bit 2 in Reg23 is Set)
rjmp pa5dn ; Sollte wohl nicht heller werden = also weiter
inc r19 ; PA5 etwas heller machen
cpi r19,0xFF ; Ist der Dimmwert ganz oben ?
brne time1 ; nein also naechster Durchlauf
lsl r23 ; War ganz oben => Also Statuskram aendern (Logical Shift Left)
; Aus 00000100 im Reg23 wird 00001000
; => also im naechsten Durchlauf weiter mit PA5 dunkler
rjmp time1 ; naechster Durchlauf



pa5dn:
sbrs r23,3 ; Soll PA5 dunkler werden ? (Skip next Command if Bit 3 in Reg23 is Set)
rjmp time1 ; Sollte wohl nicht dunkler werden = also weiter
dec r19 ; PA5 etwas dunkler machen
cpi r19,0x00 ; Ist der Dimmwert ganz unten ?
brne time1 ; nein also naechster Durchlauf
ldi r23,0b00000001 ; War ganz unten => Also Statuskram aendern (Logical Shift Left)
; Wir fangen wieder mit 00000001 im Reg23 an
; => also im naechsten Durchlauf weiter mit PA0 heller
; naechster Durchlauf (wir sind schon an der Sprungmarke time1)


time1:




rjmp loop ; und die Endlosschleife => zurueck zum Label loop und ab da weiter

Hat sauber und fehlerfrei assembliert. Sollte hoffentlich auf dem Mega8515
laufen (getestet hab ichs nicht). Wie schnell das dimmt oder blinkt weiß ich
also nicht. Mußt Du ein wenig mit dem Prozessortakt spielen. 1MHz oder
8MHz oder 16MHz. Je nach Laune.:dirol:

Wie du siehst ist der PWM-Kram das kleinste Problem an der Sache.

Gruß
Dino
 
Okay, danke schonmal für deine große Mühe.
Respekt!

Aber ob ich das in C umsetzen kann weis ich noch nicht ganz. Was macht dir Rampe?
Die sorgt doch für den Übergang zwischen Aus und An oder? Also wie "weich" der Übergang sein soll.
Erstmal nur spielerei.

Später möchte ich meine Vitrine beleuchten. Die hat 4 Böden, von denen jeweils 3 Rote, 3 Grüne und 3 Blaue Leds hochstrahlen sollen.

Was ist die Beste Methode, um die Farben zu ändern?
Kann ich einen Taster für jede "Ebene" machen? Bei jedem Tastendruck ändert sich dann das für die Ebene vorgesehene Programm.
Also z.B. nur Rot, Rot und Blau, Dimmen von Rot zu Grün zu Blau.

Also bräuchte ich dann 4 Taster. Später dann vllt. für jede Ebene 2 Taster (hoch+runter) damit man wenn man einmal zu viel gedrückt hat, nicht nochmal durch muss.
Das sollte dann erweiterbar sein und mir als Lern-Projekt dienen.

Aber erstmal das mit der PWM.
 
Hi Kani,

Was macht dir Rampe?
Die sorgt doch für den Übergang zwischen Aus und An oder? Also wie "weich" der Übergang sein soll.
Erstmal nur spielerei.
So ganz scheint Dir wohl die interne Funktion eines PWM noch nicht ganz klar zu sein ;) (scheint mir so)

Bitte lies dir den Text jetzt mal genau durch ...

Also nochmal ganz von vorn.

Ich nehme jetzt mal an, das 1 Anschaltet und 0 Ausschaltet.

PWM (Pulsweitenmodulation) ist einfach ein Rechtecksignal, was in der
Frequenz meißtens gleich bleibt. Es wird nur im Puls-Pause-Verhältnis
verändert. Also z.B. 2us Puls und 8us Pause. Das sind dann 10us Länge.
Wenn man das jetzt umrechnet sind das 1/10us = 100kHz Frequenz.
Durch das Tastverhältnis von 2 zu 8 hast Du 20% Leistung gedimmt.
Wenn man das Tastverhältnis jetzt auf 4us Puls und 6us Pause ändert
hast Du immer noch 10us Signallänge = 100kHz. Aber das Tastverhältnis
hat sich auf 4 zu 6 geändert. Also 40% Leistung.
Du hast auch noch 2 Extreme ...
1. Nur Puls (also Dauer 1 am Ausgang) = 100% Leistung
2. Nur Pause (also Dauer 0 am Ausgang) = 0% Leistung

Wie bekommt man jetzt aber ein veränderliches Puls/Pausenverhältnis
hin? Das ist eigentlich sehr einfach.

Ich erzähl erst mal die Analogversion ...
Du nimmst einen Sägezahngenerator mit deinen 100kHz !!!!
Also der Frequenz deines PWM-Signals.
Dann nimmst Du einen Komparator (Vergleicher) und setzt an einen
Eingang deinen Sägezahn und an den anderen deine Stellgröße.
Wenn jetzt die Stellgröße höher ist als die aktuelle Spannung deines
Sägezahnsignals schaltet der Komparator den Ausgang an (dein Puls)
und wenn der Sägezahn dann soweit angestiegen ist (seine Rampe)
dann ist die Steuerspannung logischerweise kleiner als deine aktuelle
Spannung des Sägezahnsignals und damit wird der Ausgang abgeschaltet.
Das ist dann deine Pause.
Je nachdem wie hoch die Steuerspannung ist, wird der Komparator
früher oder später zwischen Puls und Pause umschalten. Damit
veränderst Du also das Puls Pause verhältnis.

siehe in meinem Bild ...
Soft-PWM.gif

Die beiden roten Linien sind ZWEI VERSCHIEDENE Stellwerte und wenn
du dir schwarzen Linien verfolgst siehst Du an welcher Stelle der Komparator
am Sägezahn was erkennt und wie sich unten das Puls/Pause-Verhältnis
des PWM-Signals dadurch verändert.

In der Programmversion ist das genauso einfach ...

Der Rampenzähler bildet da den Sägezahn nach und deine Stellgröße
ist der Wert deiner Dimmerhelligkeit. Den Komparator bildest du mit
einem Vergleichsbefehl nach.
Wenn der Rampenzähler kleiner als dein Dimmwert ist, dann schaltest
du den Ausgang an und wenn der Rampenzähler größer ist dann schaltest
du den Ausgang aus.
Du mußt lediglich aufpassen, das dir die beiden Spezialfälle nicht irgendwie
durch die Lappen gehen. Das ist aber für die hauptsächliche Aufgabe
erst einmal nebensächlich. Das PWM läßt sich dann nur nicht auf volle
100% oder auf 0% dimmen. Dann bleibt immer ein kleines Quentchen
übrig.

Da du deine LEDs in deiner Schaltung vom Ausgang auf +5V gelegt
hast mußt du dein PWM-Ausgangssignal lediglich invertieren.
Also aus 0 wird 1 (aus) und aus 1 wird 0 (an).

Ich hoffe mal, das erklärt es jetzt.
Merke: Wenn man die Grundlagen VOLLSTÄNDIG verstanden hat, dann
kann man die Aufgabe schnell in beliebiger Art lösen.

Gruß
Dino
 
2 kleine Grafiken zu PWM

Hallo ,
ich hab noch ein wenig gewerkelt :)

Ein kleines Bild von einem Pulsweiten-Modulator ...
PWM-Gen.png
Der Sägezahngenerator wird durch den Rampenzähler im Programm
nachgebildet. Die Spannung in der Analog-Version ist im Programm
dann der Zählerstand des Rampenzählers.
Die Stellgröße (roter Punkt) ist der gewünschte Dimmwert der
im Programm auch in einem Register als Wert nachgebildet wird.
Der Komparator wird durch einen Vergleichsbefehl (Compare) und
anschließenden bedingten Sprung nachgebildet.

Nun eine kleine Animation des Ablaufes ...
PWM-Ani.gif
Die Animation läuft nur wenn man das Bild anklickt. Das Thumbnail hat keine Animation.
Man sieht wie sich die Stellgröße (der Dimm-Wert) ändert und
wie das PWM-Signal daraus gebildet wird.
Wenn der Dimm-Wert größer ist als die Rampe wird der Ausgang
angeschaltet (Puls) und wenn der Rampenwert den Dimm-Wert
übersteigt wird der Ausgang ausgeschaltet (Pause).
Die Frequenz der Sägezahnrampe und des PWM-Signals sind identisch.

Für die, die es interessiert ...
Die beiden Bilder wurden mit "DIA" und "GIMP" erzeugt. Auch die
Animation. GPL-Software kann auch ne Menge ;)

Gruß
Dino
 
Hi Dino,

schönes Assembler-Beispiel, gut kommentiert, man kann es so auch relativ einfach in C umschreiben. Für diejenigen, die sich in Assembler reinarbeiten möchten, ist es ein gutes Übungsbeispiel. Und für diejenigen, die allgemein einen Einstieg in die Programmierung suchen, erklärt das Beispiel schön, wie man mittels Software PWM erzeugt.

Nun ein bisschen :offtopic:...

Es wurden ja schon einige gute Codebeispiele im Forum veröffentlicht, zum Beispiel von Markus und Thomas (Knickohr), von anderen natürlich auch :). Auch kommt es manchmal bei Diskussionen zu interessanten Ergebnissen. Ich mache mir mal darüber Gedanken, wie man die Beiträge irgendwie in einer Art Wissensdatenbank (knowledge base) o.ä. integrieren kann, damit diese Beiträge nicht irgendwann mal in der Vielzahl von Forenbeiträgen "untergehen". Wie ich das machen werde, weiß ich noch nicht genau. Angefangen hatte ich Anfang letzten Jahres schon mal mit Forenlinks in einer mySQL-Datenbank und deren Zuordnung zu bestimmten Tags (Sprache, Thema, mit Code, mit Schaltplan/Layout ...), so daß man mittels Such-Oberfläche die entsprechenden Beiträge schnell findet. Soetwas ist leider nicht von heute auf morgen zu machen und muß auch gepflegt werden :rolleyes: ich versuche es aber nun doch endlich mal in Angriff zu nehmen. :)

Grüße,
Dirk
:ciao:
 
Hallo,

hier eine mal eine Umsetzung in C nach "dino03" -Art :D plus Erweiterung.

@dino03: Da hast Du eine super Beschreibung der Soft-PWM abgeliefert, echt Klasse.:)

Die Soft-PWM wird Interrupt gesteuert mit Hilfe eines Timers zu erzeugt.
Der Rampenzähler ist dabei ein Microsekundenzähler (Pwm_Lapsed). Die Stellgöße ist die High Phase der PWM (Pwm_Phase_High)

In dem Beispiel startet eine PWM mit 10ms Periodendauer und einem Tastverhältnis von 2us/10000us (High Phase/Low Phase).
Nach Ablauf von 1ms beträgt das Verhältnis 4us/10000us, dann nach 1ms 6us/10000us usw bis das Verhältnis 10000us/10000us beträgt
dann beginnt alles von vorne.

weiter Anmerkungen zur Software:
- Die PWM beginnt immer mit einer High Phase
- Im 100us Timerinterrupt wird dann nachgeschaut ob die eingestellte "High" Zeit abgelaufen ist (Pwm_Phase_High)
und die Ports entsprechend geschaltet.
- damit das Dimmen richtig funktioniert muß das eingestellte Tastverhältnis über einige Zeit konstant sein
bevor man es ändert. Dazu wird in der Timer ISR überprüft ob dieses Intervall abgelaufen ist (Pwm_Change_Duty_cycle)
und entsprechend das Verhältnis geändert.
softpwm.jpg


Auf meinem MEGA128-Board läuft das soweit ganz gut.
Es sind sicherlich noch Verbesserungen/Optimierungen möglich.

Hier der Code, Projekt ist auch angehängt:


CodeBox C

//##### Module Includes ####################################################
#include <avr/pgmspace.h>
#include <avr\io.h>
#include <avr\signal.h>
#include <avr\interrupt.h>

//##### Module Specific Defines and Enums ##################################

#define PWM_SIGNAL_PORT_1 PORTA
#define PWM_SIGNAL_PORT_DIR_1 DDRA
#define PWM_SIGNAL_PORT_PIN_1 PINA7


//##### Module Specific Macros and Typedefs ################################
#define PWM_SIGNAL_SET_OUTPUT_1 (PWM_SIGNAL_PORT_DIR_1 |= (1<<PWM_SIGNAL_PORT_PIN_1))
#define PWM_SIGNAL_SET_ON_1 (PWM_SIGNAL_PORT_1 |= (1<<PWM_SIGNAL_PORT_PIN_1))
#define PWM_SIGNAL_SET_OFF_1 (PWM_SIGNAL_PORT_1 &= ~(1<<PWM_SIGNAL_PORT_PIN_1))
#define TIMER_ISR_INTERVALL 100 // in Microsekunden
#define PWM_DUTY_CYCLE_INCREMENT 2 // in Microsekunden
//##### Definition of Global Variables #####################################

//##### Definition of Static Variables #####################################
long MicroSecCnt = 0; // Microsekunden Zähler
long Pwm_Periode = 10000; // Periodendauer der PWM in Microsekunden
long Pwm_Change_Duty_cycle = 1000; // nach Ablauf dieser Zeit wird das Tastverhältnis geändert
long Pwm_Lapsed = 0; // abgelaufene Zeit eine PWM Periode
long Pwm_Phase_High = 0; // "High" Phase der PWM

//##### Prototypes of Static Functions #####################################
void Start_Timer2(void);


// #######################################################################
int main
(
void
)
//# DESCRIPTION : ###
//# RETURN : none
//#######################################################################
{
PWM_SIGNAL_SET_OUTPUT_1; // Port is Output

Pwm_Phase_High = 0; //

sei(); // Interrupts freigeben

Start_Timer2();

while(1)
{

}

return 0;
}


//#######################################################################
SIGNAL
(
SIG_OUTPUT_COMPARE2
)
//# DESCRIPTION : ###
//# RETURN : ###
//#######################################################################
{
// verstrichene Zyklus Zeit berechnen
MicroSecCnt += TIMER_ISR_INTERVALL;
// verstrichene Perioden Zeit berechnen
Pwm_Lapsed += TIMER_ISR_INTERVALL;

//#####################################################
// Checken, ob das Tastverhältnis geändert werden soll
//#####################################################
if(MicroSecCnt % Pwm_Change_Duty_cycle == 0)
{
// Zyklus Ende erreicht, d.h Tastverhältnis 100%?
if(Pwm_Phase_High >= Pwm_Periode)
{
// Zyklus von vorne starten
MicroSecCnt = 0;
Pwm_Phase_High = 0;
Pwm_Lapsed = 0;
}
else
{
// Tastverhältnis ändern
Pwm_Phase_High += PWM_DUTY_CYCLE_INCREMENT;
}
}

//############################################################
// Port entsprechend des eingestellten Tastverhältnis setzen
//############################################################
// Ist die Periodendauer erreicht ?
if(Pwm_Lapsed >= Pwm_Periode)
{
// PWM Intervall beginnt bei 0
Pwm_Lapsed = 0;
// Port einschalten
PWM_SIGNAL_SET_ON_1;
}

// "Low" Phase der PWM?
if(Pwm_Lapsed >= Pwm_Periode-Pwm_Phase_High)
{
// Port ausschalten
PWM_SIGNAL_SET_OFF_1;
}
}

//#######################################################################
void Start_Timer2
(
void
)
//# DESCRIPTION : ###
//# RETURN : ###
//#######################################################################
{
// 10ms -> 0x9C @ 16MHz
// 1ms -> 0x0F @ 16MHz
// 100us -> 0x02 @ 16MHz


OCR2 = 0x2; // 8 Bit - Compare
TIMSK |= _BV(OCIE2); // IRQ Freigeben Overflow
TCCR2 = 0x0D; // Clk2Ts / 1024 from Prescaler, Clear On Timer Compare#else

}


Ich habe gerade mal wieder gemerkt wie schwierig das ist so einen Codeschnippsel zu dokumentieren. Also, wenn das nicht ganz durchsichtig ist einfach nochmal nachfragen.

Gruß
rangar
 

Anhänge

  • mega_soft_pwm.zip
    6 KB · Aufrufe: 11
Hm, ich bräuchte am besten ein Code-Beispiel für C. Assembler bringt mir nicht viel.
Ich habe es jetzt voll verstanden.
Danke.

@Off Topic:
Wie wäre es, wenn man eine Wiki aufmacht, in der jeder etwas reinschreiben kann.



EDIT: Zu spät. xD
 

Über uns

  • Makerconnect ist ein Forum, welches wir ausschließlich für einen Gedankenaustausch und als Diskussionsplattform für Interessierte bereitstellen, welche sich privat, durch das Studium oder beruflich mit Mikrocontroller- und Kleinstrechnersystemen beschäftigen wollen oder müssen ;-)
  • Dirk
  • Du bist noch kein Mitglied in unserer freundlichen Community? Werde Teil von uns und registriere dich in unserem Forum.
  •  Registriere dich

User Menu

 Kaffeezeit

  • Wir arbeiten hart daran sicherzustellen, dass unser Forum permanent online und schnell erreichbar ist, unsere Forensoftware auf dem aktuellsten Stand ist und der Server regelmäßig gewartet wird. Auch die Themen Datensicherheit und Datenschutz sind uns wichtig und hier sind wir auch ständig aktiv. Alles in allem, sorgen wir uns darum, dass alles Drumherum stimmt :-)

    Dir gefällt das Forum und unsere Arbeit und du möchtest uns unterstützen? Unterstütze uns durch deine Premium-Mitgliedschaft!
    Wir freuen uns auch über eine Spende für unsere Kaffeekasse :-)
    Vielen Dank! :ciao:


     Spende uns! (Paypal)