Forum: Mikrocontroller und Digitale Elektronik Attiny85 Software PWM


von Alexander W. (alexw)


Lesenswert?

Hallo,

bei meinem aktuellen Projekt soll ein Attiny85 zwei leicht verschiedene 
PWM-Signale erzeugen.

Da es am einfachsten war, die Pins 2 und 3 (PB3 und PB4) zu nehmen, 
dachte ich an eine Lösung mittels Software PWM.

Dabei wird über einen Timer (alle 2 us Interrupt) eine Variable 
(counter) hochgezählt. Erreicht diese bestimmte Werte, so werden die 
entsprechenden Pins geschaltet.
1
volatile int counter = 0;
2
3
const int mag_freq = 6240;
4
const int mag_duty = 256;
5
6
const int led_freq = 6211;
7
const int led_duty = 5;
8
9
const int mag = PB3; // vllt
10
const int led = PB4; // tauschen
11
12
int main {
13
  //Set timer0 
14
  //Interrupt every 2 us
15
  cli();
16
  TCCR0A = (1 << WGM01); // set ctc mode
17
  TCCR0B = (1 << CS00); // set no prescaler
18
  OCR0A = 15; // set ctc value
19
  TIMSK = (1 << OCIE0A); // enable interrupt
20
21
  sei();
22
23
  DDRB = (1 << mag) | (1 << led); // set outputs
24
25
  while(1) {
26
27
    if(counter % mag_freq == 0) { // mag on
28
      PORTB |= (1 << mag);
29
    }
30
    else if(counter % mag_freq == mag_duty) { // mag off
31
      PORTB &= ~(1 << mag);
32
    }
33
    if(counter % led_freq == 0) { //led on
34
      PORTB |= (1 << led);
35
    }
36
    else if(counter % led_freq == led_duty) { // led off
37
      PORTB &= ~(1 << led);
38
    }
39
  }
40
}
41
42
ISR(TIMER0_COMPA_vect) {
43
  ++counter;
44
}

Leider, und für mich unverständlicherweise, funktioniert der Code so 
nicht. Auf PB3 erhalte ich gar kein Signal und auf PB4 ein durchgängiges 
HIGH.

Da es ein Weihnachtsgeschenk werden soll, wäre ich für schnelle 
Antworten sehr dankbar!
Gruß Alex

von Karl M. (Gast)


Lesenswert?

Hallo,

2µs das ist sportlich, rechne dir bitte die Interrupt Reponsezeit und 
den Zeitbedarf einer ISR aus.

Den Timer0 muss man dann auch konfigurieren.

Bei FCPU = 8MHz sind das nur 16 Takte für alles.
Wählt man 16Mhz ist es mit 32 Takten immer noch sehr eng.

von Alexander W. (alexw)


Lesenswert?

Danke für die Antwort!

Karl M. schrieb:
> Den Timer0 muss man dann auch konfigurieren.

Das habe ich am Anfang der main gemacht

Karl M. schrieb:
> 2µs das ist sportlich

Ja, ich versuchs mal mit einem größeren Intervall

von Karl M. (Gast)


Lesenswert?

Hallo,

bist Du dir sicher, das dies korrekten Code erzeugt ?
1
DDRB = (1 << mag) | (1 << led); // set outputs

von Alexander W. (alexw)


Lesenswert?

Alexander W. schrieb:
> const int mag = PB3;
> const int led = PB4;

Karl M. schrieb:
> bist Du dir sicher, das dies korrekten Code erzeugt ?
> DDRB = (1 << mag) | (1 << led);

Müsste doch eigentlich? "mag" und "led" sind ja vorher deklariert...

von S. Landolt (Gast)


Lesenswert?

an Karl M.
Es sind immer 16 Takte, denn
>  TCCR0B = (1 << CS00); // set no prescaler
>  OCR0A = 15; // set ctc value

Und in der Tat, ob das in C so ohne weiteres klappt?

von Alexander W. (alexw)


Lesenswert?

Ja, 16 Takte ist wahrscheinlich zu knapp...

Hab jetzt gerade mal das Timerintervall auf 128 Takte hochgesetzt und 
dementsprechend die konstanten Werte oben runtergesetzt (alle durch 8 
geteilt).

Jetzt erhalte ich auch einen Output an beiden Pins, jedoch weder sehr 
regelmäßig, noch mit der gewünschten Frequenz (gewünscht ~80 Hz, 
tatsächlich ~unter 10 Hz).

Aus meiner Sicht scheint es tatsächlich an den zu kurzen 
Timerintervallen zu liegen, oder?

von Karl M. (Gast)


Lesenswert?

Hallo,

Rechne es Dir doch anhand des .lss Listings aus.
Vermutungen helfen Dir nicht weiter.

Das .lss Listing, zeigt des weitern auch noch, was der avr gcc wie 
übersetzt hat und wo man noch etwas optimieren kann.

von S. Landolt (Gast)


Lesenswert?

128 ist einiges, aber was benötigt diese '%'-Operation?

von Alexander W. (alexw)


Lesenswert?

Vielleicht gibt es aber auch einen ganz anderen Ansatz, den ich bis 
jetzt übersehen habe, deshalb jetzt mal mein Ziel:

Der hier 
(https://github.com/cubic-print/timeframe/blob/master/software/TimeFrame_3_0_simple_instructables.ino) 
zu findene Code funktioniert auf meinem Arduino ganz wunderbar, es kommt 
zum gewünschten Effekt.

Da ich einen Arduino aber nicht im Projekt unterbringen kann (zu groß), 
versuche ich den Code auf einen Attiny85 zu bringen, leider bis jetzt 
ohne wirklichen Erfolg...

von S. Landolt (Gast)


Lesenswert?

Einen Faktor von 2 könnte man mit 'High Frequency PLL Clock' erreichen, 
also 16 MHz, mit Ziehen vielleicht auch 20 MHz.

von Alexander W. (alexw)


Lesenswert?

Karl M. schrieb:
> Rechne es Dir doch anhand des .lss Listings aus.

Ich benutze die Arduino IDE zum programmieren, ich glaube die erzeugt 
keine solche Datei

S. Landolt schrieb:
> was benötigt diese '%'-Operation?

Damit sollen die unterschiedlichen Frequenzen mit einem counter 
bearbeitet werden können. Immer wenn der counter % einem bestimmten Wert 
== 0, ist eine Periode um

von S. Landolt (Gast)


Lesenswert?

Nein, die Frage war nicht, was sie bewirkt, sondern wieviele Takte sie 
verbraucht.

von Alexander W. (alexw)


Lesenswert?

S. Landolt schrieb:
> Nein, die Frage war nicht, was sie bewirkt, sondern wieviele Takte sie
> verbraucht.

Genau kann ich es dir leider nicht sagen, aber auf jeden Fall viel zu 
viele (zumindest in diesem Fall).

Habe die % Operation jetzt ersetzt, durch zwei unabhängige Counter 
Variablen, die mit dem Höchstwert verglichen und bei Übereinstimmung 
zurückgesetzt werden.

Jetzt funktioniert alles so wie es soll!

Vielen Dank für eure Hilfe und schöne Feiertage!
Gruß Alex

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

Alexander W. schrieb:
> Jetzt erhalte ich auch einen Output an beiden Pins, jedoch weder sehr
> regelmäßig, noch mit der gewünschten Frequenz (gewünscht ~80 Hz,
> tatsächlich ~unter 10 Hz).
>
> Aus meiner Sicht scheint es tatsächlich an den zu kurzen
> Timerintervallen zu liegen, oder?

 Und aus meiner Sicht scheint es an der CKDIV8 Fuse zu liegen.
 Dein Tiny läuft wahrscheinlich mit 1MHz.

: Bearbeitet durch User
von M. K. (sylaina)


Lesenswert?

Alexander W. schrieb:
> Jetzt erhalte ich auch einen Output an beiden Pins, jedoch weder sehr
> regelmäßig, noch mit der gewünschten Frequenz (gewünscht ~80 Hz,
> tatsächlich ~unter 10 Hz).

Du erwartest was mit 80 Hz und hast was mit rund 10 Hz? Das klingt nach 
einem aktiven CLKDIV8-Fuse. Setze mal bei der Initialisierung folgendes 
zu beginn:
1
CLKPS = (1 << CLKPCE);
2
CLKPS = 0x00;

von Veit D. (devil-elec)


Lesenswert?

Hallo,

sollte man auf die counter Variable nicht Interrupt geschützt zugreifen?
Stichwort "atomic". Davon abgesehen würde ich die PWM Funktion der Timer 
nutzen. Einen Timer haste dafür sowieso schon in der Mangel.

von M. K. (sylaina)


Lesenswert?

Veit D. schrieb:
> Davon abgesehen würde ich die PWM Funktion der Timer
> nutzen. Einen Timer haste dafür sowieso schon in der Mangel.

Da hat er das Problem, dass er PB3 und PB4 benutzen will (warum auch 
immer, schrieb er oben). Die sind PWM-mäßig aber gekoppelt (PB3: !OC1A, 
PB4: OC1A). Die PWM-Signale sollen aber (leicht) verschieden sein. Mit 
der Hardware-PWM sehe ich da erstmal keinen Weg dass so zu realisieren. 
Gut wäre es natürlich wenn ein Pin sich ändern können würde, z.B. statt 
PB3 PB1 zu benutzten. Dann ginge das mit der Harware-PWM.

von Carl D. (jcw2)


Lesenswert?

Alexander W. schrieb:
> Dabei wird über einen Timer (alle 2 us Interrupt) eine Variable
> (counter) hochgezählt. Erreicht diese bestimmte Werte, so werden die
> entsprechenden Pins geschaltet.
...
> Leider, und für mich unverständlicherweise, funktioniert der Code so
> nicht. Auf PB3 erhalte ich gar kein Signal und auf PB4 ein durchgängiges
> HIGH.

Wie schon von anderen erwähnt, der Interrupt verbrät fast die ganze 
CPU-Zeit. Was in der Hauptschleife übrig bleibt soll dann für 4 
16Bit-Modulo-Operatoren ("%") ausreichen?
Der 500kHz Counter wird nämlich mit einer wesentlich kleineren Frequenz 
abgetastet. Allein die 4 Modulos brauchen (ohne Interrupt) schon 60μs. 
Da wäre man bei 16kHz, der Interrupt wird grob geschätzt nicht mehr als 
10% Rechenzeit übrig lassen, dann sind es 1,6kHz.
Dabei muß aber der exakte Wert 0 als Modulo-Ergebnis errechnet werden, 
so steht es aktuell da, was bei der Abtastung mit einen 50stel der 
"Eingangsfrequenz" einem Wunder gleichen würde.

Andere Idee: der Timer setzt nur sein Int-Flag, das die Hauptschleife 
als Takt benutzt. Wartet auf Int-Flag, dieses rücksetzen und einen 
Durchlauf machen. Statt Modulo-Operation auf einem "Zähler", bekommt 
jeder Kanal seinen eigenen, der je Durchlauf um 1 erhöht wird und bei 0 
einschaltet, bei PWM-Wert ausschaltet und bei MAX-Wert auf 0 
zurückgesetzt wird. Also einfach 2 16-Bit-Timer in Software, wenn sie 
die HW schon nicht hat.
Dafür werden vermutlich die 32 Takte (8MHz; 2μs "Zähler-Takt) nicht ganz 
reichen, deshalb langsam anfangen und um das "Warten auf HW-Timer" herum 
einen Pin toggeln, um die "übriggebliebene Zeit" zu messen. Vermutlich 
werden 10μs pro Durchlauf hinzubekommen sein.

von Carl D. (jcw2)


Lesenswert?

M. K. schrieb:
> Veit D. schrieb:
>> Davon abgesehen würde ich die PWM Funktion der Timer
>> nutzen. Einen Timer haste dafür sowieso schon in der Mangel.
>
> Da hat er das Problem, dass er PB3 und PB4 benutzen will (warum auch
> immer, schrieb er oben). Die sind PWM-mäßig aber gekoppelt (PB3: !OC1A,
> PB4: OC1A). Die PWM-Signale sollen aber (leicht) verschieden sein. Mit
> der Hardware-PWM sehe ich da erstmal keinen Weg dass so zu realisieren.
> Gut wäre es natürlich wenn ein Pin sich ändern können würde, z.B. statt
> PB3 PB1 zu benutzten. Dann ginge das mit der Harware-PWM.

Er braucht auch einen 16-Bit-Timer, eher 2, wollte er das mit HW-PWM 
machen, die der kleine 8-Poler gar nicht hat.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

ich würde erstmal das besagte abarbeiten. Dein µC Takt oder Timer Konfig 
scheint nicht zu stimmen. Denn mit 80Hz Solltakt kommt man in keine 
zeitlichen Probleme.

Desweiteren sollte der atomic Zugriff auf counter beachtet werden!

Ob Software oder Hardwaretimer. Er hätte zwei 8Bit Timer zur Verfügung. 
Können also unterschiedlich konfiguriert werden. Aber gut, kommt auf den 
Zweck und Anforderung an.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.