Forum: Mikrocontroller und Digitale Elektronik Überlegungen zu ATtin85 und PWM aus Timer 1


von philipp (Gast)


Lesenswert?

Hallo zusammen,

ich hangel mich aktuell von Info zu Info, zu einem vermeintlich 
einfachen Vorhaben...

Ich möchte mit einem ATtin85 eine simple PWM-Steuerung für einen 4-Pin 
PC-Lüfter realisieren. Also ein 5V PWM-Signal @ 20,48KHz, welches ich 
über den Duty-Cycle modulieren kann.

Ich wüsste nun gern um eure Meinung zu meinen bisherigen Überlegungen.


Aus dem Datenblatt zum ATtin85 sowie diversen Online-Tutorials habe ich 
mir die Grundlagen angelesen und daraus für mich das Folgende 
konstruiert.

Als ISP nutze ich den Arduino mit dem ATTiny Core von SpeceKonde.

Die 20,48KHz ergeben sich aus den Specs des Lüfters (18-30KHz) und der 
Gleichung (64MHz / 16) / (199 + 1) = 20,48KHz

Ich möchte also den Timer 1 des ATtiny85 nutzen, mit 64MHz, einem 
Prescaler von 16 und einem TOP-Wert von 199.


Im PLLCSR Register muss ich keine Bits setzen, da dies bereits durch den 
ATTiny Core / Bootloader geschieht. Hier kann ich bereits einen Takt von 
64MHz für den Timer 1 vorgeben.

Anderfalls müssten die Einstellungen hier aber wie folgt aussehen:
1
PLLCSR = _BV(PLLE); // PLL Enable
2
3
while(!(PLLCSR & _BV(PLOCK))); // PLL Lock Detector
4
5
PLLCSR = _BV(PCKE); // PCK Enable


Nun im TCCR1 die Bits für PWM1A, COM1A1 und den Prescaler setzen:
1
TCCR1 = _BV(PWM1A) | _BV(COM1A1) | _BV(CS12)| _BV(CS10);


Den TOP-Wert auf 199 setzen:
1
OCR1C = 199;


Nach meinem Verständnis sollten ich doch nun mit OCR1A den Duty-Cycle 
zwischen 0-199 (0-100%) betimmen können, richtig?
1
OCR1A = 100; // 50% Duty-Cycle


Ausgabe-Pin wäre PB1 bzw. Pin6 des ATtiny85.

Habe ich in meinen Überlegungen etwas vergessen / übersehen?


Danke für eure Hilfe!


Philipp

von H.Joachim S. (crazyhorse)


Lesenswert?

Erst PLL auf 64MHz und dann wieder mit Vorteiler 16? Wozu?
Nimm doch einfach den 8MHz-Takt.

von spess53 (Gast)


Lesenswert?

Hi

>Die 20,48KHz ergeben sich aus den Specs des Lüfters (18-30KHz) und der
>Gleichung (64MHz / 16) / (199 + 1) = 20,48KHz

Da kommen bei mir 20kHz raus.

MfG Spess

von philipp (Gast)


Lesenswert?

H.Joachim S. schrieb:
> Erst PLL auf 64MHz und dann wieder mit Vorteiler 16? Wozu?
> Nimm doch einfach den 8MHz-Takt.

Hatte ich aus dem Datenblatt.
Aber 8MHz mit Prescaler 2 und TOP=199 im synchronen Modus sollte ebenso 
funktionieren :)

von philipp (Gast)


Lesenswert?

Kommen die übrigen Überlegungen zu den zu setzenden Bits hin?

von philipp (Gast)


Lesenswert?

Kann ich statt
1
OCR1A = 100;
auch analogWrite() nehmen, um den entsprechenden Pin anzusprechen?
Dürfte doch theoretisch keinen Unterschied machen?!

von Stefan F. (Gast)


Lesenswert?

> Kommen die übrigen Überlegungen zu den zu setzenden Bits hin?

Probieren geht über studieren. Bedenke, dass dein µC mehrfach 
programmierbar ist. Wir sind nicht mehr in den 80er Jahren.

> Kann ich statt OCR1A = 100; auch analogWrite() nehmen

Das würde ich nicht machen. Entweder nimmt man die Mittel des 
Frameworks, oder programmiert es selbst. Aber dieser Mix ist sehr 
riskant. Vielleicht funktioniert das heute, aber beim nächsten Update 
nicht mehr.

Hatten wir das nicht gerade erst vor ein paar Tagen? Ich erlebe gerade 
ein Dejavu Gefühl.

> Dürfte doch theoretisch keinen Unterschied machen?!

Das hast du geraten, nicht wahr? Schau doch mal in den Quelltext der 
Funktion rein:
1
// Right now, PWM output only works on the pins with
2
// hardware support.  These are defined in the appropriate
3
// pins_*.c file.  For the rest of the pins, we default
4
// to digital output.
5
void analogWrite(uint8_t pin, int val)
6
{
7
  // We need to make sure the PWM output is enabled for those pins
8
  // that support it, as we turn it off when digitally reading or
9
  // writing with them.  Also, make sure the pin is in output mode
10
  // for consistenty with Wiring, which doesn't require a pinMode
11
  // call for the analog output pins.
12
  pinMode(pin, OUTPUT);
13
  if (val == 0)
14
  {
15
    digitalWrite(pin, LOW);
16
  }
17
  else if (val == 255)
18
  {
19
    digitalWrite(pin, HIGH);
20
  }
21
  else
22
  {
23
    switch(digitalPinToTimer(pin))
24
    {
25
      // XXX fix needed for atmega8
26
      #if defined(TCCR0) && defined(COM00) && !defined(__AVR_ATmega8__)
27
      case TIMER0A:
28
        // connect pwm to pin on timer 0
29
        sbi(TCCR0, COM00);
30
        OCR0 = val; // set pwm duty
31
        break;
32
      #endif
33
34
      #if defined(TCCR0A) && defined(COM0A1)
35
      case TIMER0A:
36
        // connect pwm to pin on timer 0, channel A
37
        sbi(TCCR0A, COM0A1);
38
        OCR0A = val; // set pwm duty
39
        break;
40
      #endif
41
42
      #if defined(TCCR0A) && defined(COM0B1)
43
      case TIMER0B:
44
        // connect pwm to pin on timer 0, channel B
45
        sbi(TCCR0A, COM0B1);
46
        OCR0B = val; // set pwm duty
47
        break;
48
      #endif
49
50
      #if defined(TCCR1A) && defined(COM1A1)
51
      case TIMER1A:
52
        // connect pwm to pin on timer 1, channel A
53
        sbi(TCCR1A, COM1A1);
54
        OCR1A = val; // set pwm duty
55
        break;
56
      #endif
57
58
      #if defined(TCCR1A) && defined(COM1B1)
59
      case TIMER1B:
60
        // connect pwm to pin on timer 1, channel B
61
        sbi(TCCR1A, COM1B1);
62
        OCR1B = val; // set pwm duty
63
        break;
64
      #endif
65
66
      #if defined(TCCR1A) && defined(COM1C1)
67
      case TIMER1C:
68
        // connect pwm to pin on timer 1, channel B
69
        sbi(TCCR1A, COM1C1);
70
        OCR1C = val; // set pwm duty
71
        break;
72
      #endif
73
...
74
75
      case NOT_ON_TIMER:
76
      default:
77
        if (val < 128) {
78
          digitalWrite(pin, LOW);
79
        } else {
80
          digitalWrite(pin, HIGH);
81
        }
82
    }
83
  }
84
}

Für die Werte 0 und 255 müsste man dann noch in den Quelltext von 
digitalWrite() schauen.

Noch Fragen?

von philipp (Gast)


Lesenswert?

Danke für deine Ausführungen!

Denke, dann gehe ich den vermeintlich "sichereren" Weg, und programmiere 
es über die Register selbst.

von philipp (Gast)


Lesenswert?

Mein Code schaut nun wie folgt aus:
Werde damit morgen wohl einen Testlauf probieren...
Oder habe ich etwas übersehen?
1
void setup() {
2
  // Set 64MHz asynchronus clock mode, if bits not already set by Bootloader
3
  PLLCSR |= (1 << PLLE);        // PLL Enable
4
  while(!(PLLCSR & (1 << PLOCK))); // PLL Lock Detector
5
  PLLCSR |= (1 << PCKE);        // PCK Enable
6
  
7
  // Set PWM frequency to (64MHz / 16) / (199 + 1) = 20KHz
8
  TCCR1 =   
9
            (1 << PWM1A)  |   // Pulse Width Modulator A Enable
10
            (1 << COM1A1) |   // Comparator A Output Mode to OC1x cleared on compare match. Set when TCNT1 = $00; Inverted Output not connected
11
            (1 << CS12)   |   // Set Prescaler to 0101 = 16
12
            (1 << CS10);      // Set Prescaler to 0101 = 16
13
  
14
  OCR1C = 199;                // Set Top-Value to 199
15
  
16
  // 8-bit resolution
17
  // set ADLAR to 1 to enable the Left-shift result (only bits ADC9..ADC2 are available)
18
  // then, only reading ADCH is sufficient for 8-bit results (256 values)
19
20
  ADMUX =
21
            (1 << ADLAR)  |   // left shift result
22
            (0 << REFS1)  |   // Sets ref. voltage to VCC, bit 1
23
            (0 << REFS0)  |   // Sets ref. voltage to VCC, bit 0
24
            (0 << MUX3)   |   // use ADC2 for input (PB4), MUX bit 3
25
            (0 << MUX2)   |   // use ADC2 for input (PB4), MUX bit 2
26
            (1 << MUX1)   |   // use ADC2 for input (PB4), MUX bit 1
27
            (0 << MUX0);      // use ADC2 for input (PB4), MUX bit 0
28
29
  ADCSRA = 
30
            (1 << ADEN)   |   // Enable ADC 
31
            (1 << ADPS2)  |   // set prescaler to 64, bit 2 
32
            (1 << ADPS1)  |   // set prescaler to 64, bit 1 
33
            (0 << ADPS0);     // set prescaler to 64, bit 0
34
            
35
  ADCSRA |= (1 << ADSC);      // start ADC measurement
36
  
37
  pinMode(PB1, OUTPUT);
38
}
39
40
void loop() {
41
    while (ADCSRA & (1 << ADSC)); // wait till conversion complete
42
    OCR1A = map(ADCH, 0, 215, 0, 199) // convert analog input to pwm output
43
}

von Äxl (geloescht) (Gast)


Lesenswert?

Hast Du Angst, Dir brennt die Bude ab? Mach doch einfach drauf los...

von Dieter F. (Gast)


Lesenswert?

philipp schrieb:
> (0 << REFS1)

Bewirkt nichts ...

von Karsten U. (herr_barium)


Lesenswert?

Dieter F. schrieb:
> philipp schrieb:
>> (0 << REFS1)
>
> Bewirkt nichts ..

Na klar bewirkt das etwas: Vcc wird als Referenzspannung genutzt.

Du meinst: Er verdient Schelte, nur weil er die Initialisierung 
vorbildlich übersichtlich und mit allen beteiligten Bits in den 
Registern hingeschrieben hat?

Nein, dem ist nicht so! Das zahlt sich nämlich immer dann aus, wenn 
man das Programm mal wiederverwenden will. Dann ist es ein Leichtes, 
ohne erst in Unterlagen zu wühlen, eine andere Betriebsart einzustellen.

von Dieter F. (Gast)


Lesenswert?

Herr B. schrieb:
> Na klar bewirkt das etwas:

Nö - ein ODER mit 0 (null) bewirkt rein gar nichts.

von Karsten U. (herr_barium)


Lesenswert?

Dieter F. schrieb:
> Nö - ein ODER mit 0 (null) bewirkt rein gar nichts.

Noch mal: Es steht nacher in dem Bit das Gleiche, wie schon vorher 
drinstand. Eine Null. Das ist der Initialisierungszustand, wenn man 
NICHTS macht. Aber: Da er das betreffende Bit schon in den Quelltext 
aufgenommen hat, kann er später für andere Zwecke da eine Eins 
reinschieben und blitzschnell die Referenzspannung umstellen.

von Dieter F. (Gast)


Lesenswert?

Herr B. schrieb:
> Noch mal: Es steht nacher in dem Bit das Gleiche, wie schon vorher
> drinstand.

Ich nehme es zurück - habe übersehen, dass

  ADMUX =

ein = ohne ODER (|) da steht.

Der Compiler nimmt die "veroderte" Zuweisung dann als Initialwert 
(analog = 0b00100010), OHNE zu "verodern".
Würde ich aber trotzdem nie so machen, weil das impliziert, dass es auch 
an anderer Stelle (mit ODER) so funktioniert. Ist aber vermutlich nur 
mein Problem.

Wenn überhaupt (und um vollständig zu sein) würde ich
1
ADMUX =
2
            (0 << REFS1)  |   // Sets ref. voltage to VCC, bit 1
3
            (0 << REFS0)  |   // Sets ref. voltage to VCC, bit 0
4
            (1 << ADLAR)  |   // left shift result                                  
5
            (0 << REFS2)  |   // Sets ref. voltage to VCC, bit 2
6
            (0 << MUX3)   |   // use ADC2 for input (PB4), MUX bit 3
7
            (0 << MUX2)   |   // use ADC2 for input (PB4), MUX bit 2
8
            (1 << MUX1)   |   // use ADC2 for input (PB4), MUX bit 1
9
            (0 << MUX0);      // use ADC2 for input (PB4), MUX bit 0

schreiben, damit alle Bits in der richtigen Reihenfolge (analog 
0b00100010) erkennbar sind.

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.