#define F_CPU 1000000UL //Definition von F_CPU in Herz
4
#endif
5
#include<util/delay.h> //Warteschleifen
6
#include<stdint.h>
7
8
intmain(void)//Hauptfunktion
9
{//Anfang der Hauptfuktion
10
DDRB=0b11111111;//Alles Ausgänge
11
uint16_ta;//Variable 16Bit
12
chari;//Zählvariable für die Schleife
13
a=1000;//Der Impuls soll 1ms lang sein
14
while(1)
15
{
16
i=50;//Durch diese Definierung ist die While(i) Schleife immer 1s (50*0,02s) lang
17
while(i)
18
{
19
PORTB=0b11111111;
20
_delay_us(a);//Impulslänge
21
PORTB=0b00000000;
22
_delay_us(20000-a);//Hierdurch wird erreicht, dass das Gesamtsignal genau 20ms lang ist
23
i--;
24
}
25
a=a+100;//Ich will aber das die Impulse länger werden
26
if(a==2000)//Da aber ab einer Impulslänge von über 2ms der Servo kaputt gehen kann, wird hier nun die Notbremse gezogen
27
{
28
a=1000;
29
}
30
}
31
return0;
32
}
Hallo,
Dieser Code hat die Funktion 11 verschiedene Pulslängen an PORTB eines
Attiny2313 zu bilden. Das Problem ist aber, dass der Code eine Größe
erreicht die etwa doppelt so groß ist, wie der Speicherplatz eines
Attiny2313. Die Optimierung 0s ist eingestellt.
Vielleicht könnte mir ja jemand helfen. Danke schonmal im Vorraus.
MfG
Inox
Dein Flashfresser dürfte die float-Arithmetik sein, die von _delay_us
benötigt wird.
Es wäre besser, wenn du einen Timer programmierst und aus der
Interruptroutine heraus dem Hauptprogramm signalisierst, daß soundsoviel
Zeit vergangen ist.
Das geht dann mit reiner int-Arithmetik...
Hallo,
vielleicht wegen diesen Zeilen:
> _delay_us(20000-a);>a=a+100;
Soweit ich weiss mag die AVR Lib C dies nicht. Dort steht,
das für die _delay_us Funktionen möglichst Konstanten benutzt
werden sollen.
Quote AVR LibC Reference:
In order for these functions to work as intended, compiler optimizations
must be enabled, and the delay time must be an expression that is a
known constant at compile-time. If these requirements are not met, the
resulting delay will be much longer (and basically unpredictable), and
applications that otherwise do not use floating-point calculations will
experience severe code bloat by the floating-point library routines
linked into the application.
Vielleicht solltest du dich mit den Timern der Avr beschäftigen.
Die Tutorials bieten hier einen guten Einstieg.
gruß ralf
Hallo,
nö da werden Floating Point Routinen hinzugelinkt.
experience severe code bloat by the floating-point library routines
linked into the application.
gruß ralf
Hi,
du kannst dir ja mal das Assemblerlisting oder das map-file angucken. Da
siehst du, was dein Programm so aufbläst.
_delay_us ist so deklariert:
1
void_delay_us(double__ms)
und macht auch float-Berechnungen während der Laufzeit, die nicht weg
optimiert werden können, wenn du keine Konstante übergibst.
Alternativ kannst du mal versuchen, eine eigene for-Schleife zu
schreiben und _delay_loop_1() oder _delay_loop_2() aufzurufen. Die sind
in der "delay_basic.h" deklariert.
Oder du nimmst einen Timer und wartest, bis das Overflow-Bit gesetzt
wird.
Gruß
Thomas
Nimm ne For Schleife und schreib in den Schleifenkörper delay_us(1); und
lass dann die schleife sooft ablaufen, wie du es brauchst, dann hat das
delay eine Konstante als Parameter und alle sind happy oder nimm gleich
den Timer.
Mehr oder weniger sinnvoll. Die for() muss erst bei jedem Durchlauf
geprüft werden, was auch wieder Zeit braucht. Somit wird das ganze in
längeren Wartezeiten doch ziemlich ungenau.
hm aber die idee mit dem "loopen" der delay_us(1) ist gar nicht
schlecht.
Werde es jetzt vorerst auch mal so machen, mal schaun wie genau das
wird...
Danke für diesen Tipp!
Wenn du mit delay_us(1) einen Aufruf von _delay_us mit Parameter int 1
meinst, wirst du wenig Glück haben. _delay_us braucht selbst
Fließkommaarithmetik.
Aber du könntest die Routinen aus delay_basic.h verwenden...
> hm aber die idee mit dem "loopen" der delay_us(1) ist gar nicht> schlecht.> Werde es jetzt vorerst auch mal so machen, mal schaun wie genau das> wird...
Da die Delay-Zeiten immer Vielfache von 100µs sind, würde ich eine
Schleife um einen _delay_us(100) machen. Das macht die Sache genauer.
Reicht die Genauigkeit immer noch nicht, machst du die 100 um so viel
kleiner, wie es dem Schleifen-Overhead entspricht. Das Argument von
_delay_us darf dabei durchaus auch eine gebrochene Zahl, also z.B.
97.4 sein, wichtig ist nur, dass es konstant ist.
Aber früher oder später solltest du es auch mit Timern versuchen, den
dafür sind diese da, und du hast ja schließlich auch Geld dafür bezahlt.
Der Hauptvorteil ist, dass damit der komplette Zeitbedarf des restlichen
Codes der in den Schleifen deines obigen Programms steht, weitgehend
kompensiert wird. Und solltest du irgendwann noch irgendwelche
Interrupts verwenden, stimmen die Zeiten der _delay_xx-Funktionen
überhaupt nicht mehr, da jede Unterbrechung durch einen Interrupts ihre
Ausführungszeit um den Rechenzeitbedarf des Interrupthandlers
verlängert.
Uhu Uhuhu schrieb:
> Wenn du mit delay_us(1) einen Aufruf von _delay_us mit Parameter int 1> meinst, wirst du wenig Glück haben. _delay_us braucht selbst> Fließkommaarithmetik.
Aber nur zur Compilezeit, nicht zur Laufzeit, deswegen stört es nicht.
nimm assembler!
so kleine controller sind effektiv nur in assembler zu programmieren.
nutze den timer wenn er noch zur verfügung steht, das ist sinnvoller als
zeitschleifen. der code wird dann nicht mehr als 10% des flashs
beanspruchen.
Halte ich für ein Gerücht. Man kann auch den Maschinencode, den der
Compiler erzeugt hat, untersuchen und den Code so modifizieren, daß
möglichst wenig unnötiger Code entsteht.
Das ist weniger Gefummel, als wenn man alles in ASM schreibt und man
lernt dabei, sehr effizienten C-Code zu schreiben und nebenbei noch den
Assembler.
Mein Problem mit dem Timer ist aber im Moment folgendes und zwar bekommt
mein Timer0 (8Bit) 1000000 Impulse in der Sekunde (kein Vorteiler), das
heißt wiederum das mein Timer 3906,25 pro Sekunde überläuft und das ist
schon zu langsam, weil der Timer folglich dann nur alle 0,256ms
überläuft. Der Timer müsste aber alle 0,1ms überläufen.
Mach ich nur en Denkfehler oder besteht die Lösung darin dem µC einen
höheren Takt zu geben? Oder ließe sich das noch ganz anders lösen?
MfG
Inox
PS: Frohe Weihnachten! :-)
Ich kenne zwar die Tinys nicht,
aber Du kannst doch das Maximum von TCNT bestimmen bei dem die ISR
ausgelöst und TCNT zurückgesetzt wird... ?!
Frohe Weihnachten
Dann kannst du TCNT aber mit einem Wert Vorladen !
So das der Timer von z.B. 128 bis 255(überlauf) Zählt, oder ?
Ich glaube das was ich meinte war der CTC Modus...
das was du da machen willst, kann der timer ganz allein! benutze den
pwm-modus und stelle den vorteiler so ein, dass der timer nach 20ms
überläuft und einen interrupt auslöst. mit dem compare-register stellst
du dann die breite der impule ein, die du haben willst. wenn der
interrupt 50mal durchgelaufen ist, änderst du den wert der in das
compare-register geladen wird. trenne dich von deinen zeitschleifen, das
ist nonsens, die hardware kann das viel besser und effizienter und der
code wird viel kürzer.
Im Datenblatt auf Seite 68 im Absatz "Clear Timer on Compare Match Mode"
Im CTC Modus Zählt der Counter (TCNT++) hoch bis zu einem Vergleichswert
der in OCR0A (TCNT==OCR0A) gespeichert ist.
Der hat den Modus also doch.
Das Vorladen des TCNT im Normalen Modus sollte aber auch gehen.
Gast wrote:
> Im Datenblatt auf Seite 68 im Absatz "Clear Timer on Compare Match Mode">
wenn er sich nun mit dem timer ein delay zaubern will, dann macht das
sinn. so wie sein programm aussieht will er eine pwm und das kann der
timer auch, dass einzige was sein programm tun muss ist die
compare-werte für das tastverhältnis nachzuladen, den rest macht der
timer selbst.
das is natürlich gut, werd ich sofort ausprobieren. Weil wenn ich mit
dem Timer alleine meine PWM bekommen kann, ist das natürlich wesentlich
präziser und einfacher.
Guten Abend,
Ich hab mir gedacht ich berichte euch mal was ich nun gemacht habe:
1
#include<avr/io.h> //Grundfunktionen
2
#ifndef F_CPU //Vordefinieren für delay.h
3
#define F_CPU 1000000UL //Definition von F_CPU in Herz
4
#endif
5
#include<util/delay.h> //Warteschleifen
6
#include<stdint.h>
7
#include<avr/interrupt.h>
8
9
ISR(TIMER1_COMPA_vect)
10
{
11
OCR1A=2500-OCR1A;//Im ISR wird das Steuersignal für den Servo, aus der Differenz der Periodenlänge und des vorherigen Vergleichswert gebildet
12
}
13
14
15
intmain(void)//Hauptfunktion
16
{
17
DDRB=0b11111100;//Alles Ausgänge bis auf PB0 und PB1
18
PORTB=0b00000011;//Pullup für PB0 und PB1
19
TCCR1A=0b01010000;//Tooglen von PB3
20
TCCR1B=0b00001010;//CTC-Mode und Prescaler 8; Prozessortakt
21
TIMSK=0b01000000;//Interrupt bei Timer1 Comp.Match A
22
OCR1A=2375;//das bringt den Servo in Neutrallage (1,5ms)
23
sei();//Interrupts an
24
while(1)//Programmschleife
25
{
26
if(!(PINB&(1<<PINB0)))//Abfrage eines Schalters
27
{
28
OCR1A=OCR1A+3;
29
_delay_ms(50);//Verlangsamung der Veränderung
30
}
31
if(!(PINB&(1<<PINB1)))//Abfrage eines Schalters
32
{
33
OCR1A=OCR1A-3;
34
_delay_ms(50);//Verlangsamung der Veränderung
35
}
36
}
37
return0;//C-Standard
38
}
Letzendlich bin ich am CTC-Modus hängengeblieben ;), nie gedacht, dass
es so simpel ist, auch mit den Interrupts...
Dieses Programm erzeugt mit Erfolg meine gewünschte PWM, die ich für den
Servo brauche. Zusätzlich kann man das PWM Signal in 0,024ms Stufen
verändern, d.h. den Servo aus der Neutralposition links oder rechts
drehen lassen. Dies geschieht durch die 2 angeschlossenen Schalter.
Mehr dazu bald unter Modellbauservo Ansteuerung im wiki.
Danke nochmal für eure Hilfe,
MfG
Inox
Hallo Daniel
Die Timer-Version ist natürlich die sauberere Lösung.
Aber wie Kürbis schon sagte, so wärs auch gegangen:
AVR Memory Usage
Device: attiny2313
Program: 198 bytes (9.7% Full)
1
#include<avr/io.h>
2
#include<util/delay.h>
3
4
void
5
my_delay_us(unsignedintms)// delay ms
6
{
7
while(ms){
8
_delay_us(0.96);
9
ms--;
10
}
11
}
12
13
int
14
main(void)
15
{
16
DDRB=0b11111111;//Alles Ausgänge
17
uint16_ta;//Variable 16Bit
18
chari;//Zählvariable für die Schleife
19
20
a=1000;//Der Impuls soll 1ms lang sein
21
while(1)
22
{
23
i=50;//Durch diese Definierung ist die While(i)
24
//Schleife immer 1s (50*0,02s) lang
25
while(i)
26
{
27
PORTB=0b11111111;
28
my_delay_us(a);//Impulslänge
29
PORTB=0b00000000;
30
my_delay_us(20000-a);//Hierdurch wird erreicht, dass das
31
//Gesamtsignal genau 20ms lang ist
32
i--;
33
}
34
a=a+100;//Ich will aber das die Impulse länger werden
35
if(a==2000)//Da aber ab einer Impulslänge von über 2ms
Stimmt so wärs auch gegangen, aber auf die zahlreichen Anregungen hier
Richtung Timer + ISR hab ich beschlossen, dann doch mal Neuland zu
betreten. Will ja schließlich was lernen.^^
Letzendlich erfüllen beide Programme den gleichen Zweck und welche nun
die bessere ist, finde ich, muss jeder selbst wissen.
MfG
Inox
Daniel B. wrote:
> OCR1A=OCR1A+3;
Auch wenn dein Programm funktioniert, so hat es noch ein paar Fußangeln.
Sehr versteckt im Tutorial findest du dazu das hier:
http://www.mikrocontroller.net/articles/Interrupt#Atomarer_Datenzugriff
OCR1A ist ein 16-Bit Register, das nicht atomav verändert werden kenn.
Wenn während des Inkrementierens um 3 eine IRQ auftritt, kann das
zuProblemen führen (reca condition) und das Servo macht nen Sprung.
Stimmt, das stellt ein Problem dar. Nur wenn gerade just ein Interrupt
während des in-das-Register-Schreibens passiert fehlt eine Periode
einfach und noch viel schlimmer: Die Perioden würden ihre Polarität
ändern! Fragt sich was das geringere Übel ist...
Hi Daniel,
die Idee mit dem atomaren Zugriff ist zwar im Prinzip richtig, beim
Zugriff auf die Timerregister (ausnahmsweise) überflüssig: den atomaren
Zugriff regelt die Timer-Hardware selber (siehe ATtiny Manual S. 88:
accessing 16 Bit registers).
Ein anderes, noch wichtigeres Problem regelt die Timerhardware im
PWM-Modus aus selbstständig:
angenommen, Dein OCR1A steht auf 15 und Dein Counter zählt hoch:
10, 11, 12, 13, 14, 15, 16, 17, 18, ....
Zum Zeitpunkt, an dem der Counter auf 13 steht, liest Du OCR1A aus
(=15), ziehst 3 ab und schreibst das Ergebnis (=12) zurück nach OCR1A.
Das Ergebnis ist:
Dein Counter hat den Match mit OCR1A verpasst!
Um das zu vermeiden, ist OCRnA doppelt-gepuffert (siehe auch Man. S.
95):
Wenn Du OCRnA änderst, änderst Du nur ein Schattenregister. Dessen Wert
wird immmer dann in das "richtige" OCRnA übernommen, wenn der Timer
überläuft bzw. NULL erreicht (genaueres steht im Man. bei den
verschiedenen PWM-Modi beschrieben).
Fazit:
Dein erstes Hardware-PWM Programm war korrekt.
Viel Spass noch, Stefan
@ Stefan Kleinwort (sk)
>die Idee mit dem atomaren Zugriff ist zwar im Prinzip richtig, beim>Zugriff auf die Timerregister (ausnahmsweise) überflüssig: den atomaren>Zugriff regelt die Timer-Hardware selber (siehe ATtiny Manual S. 88:>accessing 16 Bit registers).
Das reicht nicht!
MFG
Falk
also ich verstehe das alles nicht. warum werden hier komische
zeitschleifen programmiert, wenn der timer einen pwm-modus hat und es
ganau das ist worum es hier geht. ich habe das ganz zum anfang schon
gesagt. der threadersteller hat dann ja den ctc-modus genutzt und sich
wieder ein delay gebastelt. warum macht man sowas?? nur weil das auch
gehen muss??? ich kapiers nicht, gibt es da einen wichtigen grund für??
das ist doch so, als wenn ich mir sage: "mensch eine tür müsste man in
autos einbauen und dann könnte man sogar einsteigen und mitfahren." und
danach wird tagelang darüber diskutiert, wie man fenster so verändern
könnte, dass man dadurch bequem einsteigen kann, dabei gibt es die tür
längst!
ich versteh es nicht!
Hallo Frank,
der TS benutzt doch mittlerweile den PWM, und das ist immerhin Dein
Verdienst!
Die Delays, die noch in dem Programm sind, sin ja für die
"Bedienoberfläche", das ist ne andere Baustelle und ich denke, so nur
zum Testen gedacht.
Viele Grüße, Stefan
@Frank
In der Programmschleife ist nur en delay drin, weil ich dafür net auch
noch extra nen Timer verwenden will und es somit auch einfacher bleibt.
Meine PWM (auch der eigentliche Threadgegenstand) hab ich nun ja (dank
dir) mit dem CTC-Modus gelöst.
@Stefan
Genau so hab ich es gemeint.
MfG
Inox
@ Stefan (Gast)
>WAS reicht nicht?
Den atomaren Zugriff auf die 16 Bit Timerregister per Hardware zu
sichern.
cli() und sei() bzw. bei neueren AVR GCC Versionen ein atomic block sind
notwendig.
MFG
Falk
@Falk:
Manual ATmega32, S.89:
"When the low byte of a 16-bit register is written by the CPU, the high
byte stored in the temporary register, and the low byte written are both
copied into the 16-bit register in the same clock cycle. When the low
byte of a 16-bit register is read by the CPU, the high byte of the
16-bit register is copied into the temporary register in the same clock
cycle as the low byte is read".
Solange ein IR nicht das temporary register ändert, ist ein atomarer
Zugriff nicht notwendig - diesen erledigt in diesem Fall die
Timer-Hardware (beide 8-Bit-Register werden von der HW gleichzeitig [in
the same clock cycle] in die 16-Bit Timer-Register geschrieben).
Gruß, Stefan
@ Stefan (Gast)
>Solange ein IR nicht das temporary register ändert, ist ein atomarer>Zugriff nicht notwendig
Das tut sie in diesem Beispiel aber.
1
OCR1A=2500-OCR1A;
> - diesen erledigt in diesem Fall die>Timer-Hardware (beide 8-Bit-Register werden von der HW gleichzeitig [in>the same clock cycle] in die 16-Bit Timer-Register geschrieben).
Du verwechselt den atomaren Zugriff auf Hardwareregister mit dem
atomaren Zugriff auf normale Variabeln im RAM. Bei BEIDEN muss man
aufpassen und mit cli/sei arbeiten, wenn auf die selbe Variable/Register
im Hauptprogramm UND einer ISR zugegriffen wird. Der Mechanismus mit dem
temp-Register ermöglicht "nur" den atomren zugriff auf 16 Bit
Hardwareregister mit einer 8 Bit CPU. cli/sei würden dort gar nicht
nützen, das beeinflusst die 16 Bit Register keine Nanosekunde.
MFG
Falk
Falk Brunner wrote:
> @ Stefan (Gast)>>>Solange ein IR nicht das temporary register ändert, ist ein atomarer>>Zugriff nicht notwendig>> Das tut sie in diesem Beispiel aber.
Zudem handelt es sich im Hauptprogramm bei ... OCR1A = OCR1A+3 nicht nur
um einen Zugriff, sondern um zwei, nämlich ein Lese- und ein
Schreibzugriff.
@Falk, @Johann:
Ok, ich bin vom Source des TS von 12:50 ausgegangen, dort ist kein
Interrupt vorhanden. (wahrscheinlich ist er aber einfach nicht
mitgepostet worden).
Mit IR, jedenfalls so, wie er um 00:54 gepostet wurde, habt Ihr
natürlich Recht.
@Daniel:
Warum benutzt Du den CTC-Mode? Der Timer hat so viele schöne PWM-Modi,
die komplett in der Timer-Hardware, ohne einen Software-Eingriff laufen.
Gruß, Stefan
@Stefan
Tut mir leid, hab da nur die Hauptfunktion gepostet. Wollte aus
Platzgründen nicht den Rest noch dazu posten.
Ja zudem war das mein erstes Programm, das den Timer nutzt. Warum hab
ich nicht einen anderen Modus genutzt? Gute Frage, mir wurde der
CTC-Modus schonmal vorgeschlagen und naja da hab ich mir gedacht, der
wird wohl mehr Ahnung ham als ich, also machst du es mit dem CTC-Modus^^
Nunja fürs nächstemal werd ich genauer schaun, welcher Modus für mein
Problem passend ist.
MfG
Inox