Forum: Mikrocontroller und Digitale Elektronik Lüftersteuerung mit ATtiny13a, Tachosignal lesen


von Christian F. (neo2001)


Lesenswert?

Mache seit ein paar Tagen mit dieser Lüftersteuerung auf Basis eines 
ATtiny13a mit einzelnem 12V 4-Pin-PWM-Lüfter und TMP36 analogem 
Temperatursensor herum (AVR-GCC bzw. avrlibc). Das Problem ist dabei gar 
nicht das lesen des Temperaturesensors oder erzeugen des PWM-Signals, 
sondern das Auslesen des Tachosignals. Da der Pin für den externen 
Interrupt INT0 bereits mit PWM belegt ist, verwende ich den PCINT auf 
Pin 7 (zumindest ist das das Ziel). Das funktioniert auch wunderbar, 
solang ich nicht gleichzeitig die restliche Lüftersteuerung laufen 
lassen will. Sprich der Code/Ansatz für das Auslesen des Tachosignals in 
einem eigenen Programm funktioniert.

Für die korrekte Berechnung der RPM ist es natürlich wichtig, dass diese 
Berechnung und Zurücksetzen des Zählers in zeitlich korrekten Abständen 
erfolgt.

Nachdem ich das (synchrone) Auslesen des ADC zunächst zusätzlich in der 
main() Funktion 1x pro Sekunde erledigt habe und realisiert habe, dass 
das ja relativ lang dauert (:-)) habe ich dies ebenfalls mit Hilfe eines 
Interrupts ausgelagert bzw. den Free Running Mode verwendet. Leider hat 
das auch nichts geholfen - obwohl das reine Auskommentieren der ADC 
betreffenden Zeilen in der main() zunächst das erhoffte Ergebnis hatte 
(korrekt Ausgabe der RPM).

Irgendwie habe ich das Gefühl, dass dieser PCI alles durcheinander 
bringt - und das obwohl ich ihn doch eigentlich nur auf diesen einen Pin 
beschränkt habe!?
Lustiges Detail: Ich kann den AVR nur flashen wenn der Lüfter sich nicht 
gleichzeitig dreht. Allein das stört mich aber nicht, wenn der Rest 
funktionieren würde.

Vielleicht kann sich ja jemand erbarmen mal über den Code zu schauen ob 
irgendwas auffällt. Ich blicke langsam überhaupt nicht mehr durch. :-(

Habe alles nach bestem Wissen kommentiert (vielleicht sind daraus evtl. 
auch falsche Annahmen erkennbar). Bitte nicht über meine Blink-Funktion 
lachen! :-)

(Die Lüfter"regelung" ist nur ein Dummy bis der Rest funktioniert)
1
#define F_CPU 9600000UL
2
#include <avr/io.h>
3
#include <avr/interrupt.h>
4
#include <util/delay.h>
5
6
/*
7
 * Pin 1 - Reset
8
 * Pin 2 - [PB3] Taster
9
 * Pin 3 - [ADC2] TMP36 Sensor
10
 * Pin 4 - GND
11
 * Pin 5 - [PB0] LED
12
 * Pin 6 - [OC0B] PWM Lüfter
13
 * Pin 7 - [PCINT2] Tacho
14
 * Pin 8 - VCC
15
*/
16
17
const uint8_t PWM_BOTTOM = 0;
18
const uint8_t PWM_TOP = 48;  // 48 inkl. 0 (?)
19
const uint8_t VREF = 5;
20
21
uint16_t volatile to_counter;  // Zähler für Timer-Overflows
22
uint16_t volatile fan_tacho_signals;  // Zähler für halbe Umdrehungen
23
int8_t volatile temperature = 20;  // Temperature in °C
24
uint16_t fan_rpm;  // Lüfter RPM
25
26
// Timer Overflow Interrupt ISR
27
// Aufruf der ISR setzt das TOV0 Flag im TIFR Register zurück.
28
ISR(TIM0_OVF_vect) {
29
  to_counter++;
30
}
31
32
// ADC Conversion Complete Interrupt ISR
33
// Aufruf der ISR setzt das ADIF Flag im ADCSRA Register zurück.
34
ISR(ADC_vect) {
35
  uint16_t voltage = ((uint32_t) (ADC)) * VREF * 1000 / 1024;  // Auf ref. Spannung beziehen
36
  voltage -= 500;  // Offset abziehen
37
  temperature = (voltage + 10) / 10;  // +10 um korrekt zu runden
38
  // Nächste Messung startet automatisch (Free Running Mode)
39
}
40
41
// Pin Change Interrupt ISR
42
// Aufruf der ISR setzt das PCIF Flag im GIFR Register zurück.
43
ISR(PCINT0_vect) {
44
  if (PINB & (1 << PB2)) {
45
    // Steigende Flanke
46
  } else {
47
    // Fallende Flanke
48
    fan_tacho_signals++;
49
  }
50
}
51
52
void setupPWM(void) {
53
  // Register TCCR0A/B initialisieren
54
  // Fast-PWM Modus wählen (TOP = OCR0A)
55
  // OCR0B entspricht Duty-Cycle
56
  TCCR0A |= (1 << COM0B1) | (0 << COM0B0) | (1 << WGM01) | (1 << WGM00);
57
  
58
  // Counter initialisieren bzw. festlegen
59
  OCR0A = PWM_TOP;
60
  OCR0B = PWM_BOTTOM;  // Duty-Cycle
61
  
62
  // Prescaler auf 8 setzen
63
  // Formel für Fast-PWM-Frequenz: F_CPU / (Prescaler * 256)
64
  // 9,6 MHz: 9.600.000 / (8 * 48) = 25.000 Hz --> 25 kHz
65
  TCCR0B |= (1 << WGM02) | (0 << CS02) | (1 << CS01) | (0 << CS00);
66
  
67
  // Timer Overflow Interrupt einschalten
68
  // Erhöht Zählervariable; zur Auslösung einer ADC-Messung
69
  TIMSK0 |= (1 << TOIE0);
70
  to_counter = 0;
71
}
72
73
void setupADC(void) {
74
  // ADC Multiplexer Selection Register
75
  // REFS0: VCC als Referenzspannung (Standard).
76
  // ADLAR: Left adjust ausschalten (siehe unten!)
77
  // MUX[1:0]: ADC2 (PB4, Pin 3) als analogen Eingang festlegen.
78
  ADMUX |= (0 << REFS0) | (0 << ADLAR) | (1 << MUX1) | (0 << MUX0);
79
  
80
  // Digitalen Input für analogen Pin abschalten
81
  DIDR0 |= (1 << ADC2D);
82
  
83
  // ADC Control and Status Register A
84
  // ADEN: ADC einschalten
85
  // ADSC: Einzelne Messung starten
86
  // ADIE: ADC Conversion Complete Interrupt einschalten
87
  // ADPS[2:0]: ADC Prescaler auf 64 (Ziel: zwischen 50 und 200 kHz)
88
  // 9,6 MHz: 9.600.000 Hz / 64 = 150.000 Hz --> 150 kHz
89
  ADCSRA |= (1 << ADEN) | (1 << ADSC) | (1 << ADIE) | (1 << ADPS2) | (1 << ADPS1) | (0 << ADPS0);
90
  
91
  // Ergebnis wird in ADCL und ADCH (ADC Low Byte und ADC High Byte)
92
  // gespeichert, da 10 Bit Wert. ADC enthält vollständigen Wert.
93
  while (ADCSRA & (1 << ADSC));  // Ergebnis abwarten...
94
  (void) (ADC);  // Erste Messung verwerfen (lt. Datenblatt empfohlen)
95
}
96
97
void setupTachoRead(void) {
98
  // Pin 7 für Tachosignal konfiguriern
99
  // Open-Collector, zieht 2x pro Umdrehung auf GND
100
  PORTB |= (1 << PORTB2);  // Pull-up-Widerstand einschalten
101
  GIMSK |= (1 << PCIE);  // Pin-Change-Interrupt einschalten
102
  PCMSK |= (1 << PCINT2);  // PCI (nur!) für Pin 7 einschalten
103
}
104
105
void blinkLED(uint8_t times) {
106
  PORTB &= ~(1 << PORTB0);  // LED aus
107
  for (uint8_t i = 0; i < times; i++) {
108
    PORTB |= (1 << PORTB0);
109
    _delay_ms(200);
110
    PORTB &= ~(1 << PORTB0);
111
    _delay_ms(200);
112
  }
113
}
114
115
void blinkMyInt(uint16_t someInt) {
116
  if (someInt == 0) return;
117
  uint8_t zt = someInt / 10000;
118
  blinkLED(zt + 1);
119
  _delay_ms(500);
120
  uint8_t t = (someInt - (zt * 10000)) / 1000;
121
  blinkLED(t + 1);
122
  _delay_ms(500);
123
  uint8_t h = (someInt - (zt * 10000) - (t * 1000)) / 100;  // z.B. 123 / 100 = 1,23 ==> 1
124
  blinkLED(h + 1);
125
  _delay_ms(500);
126
  uint8_t z = (someInt - (zt * 10000) - (t * 1000) - (h * 100)) / 10;  // z.B. (123 - 100) / 10 = 2,3 ==> 2
127
  blinkLED(z + 1);
128
  _delay_ms(500);
129
  uint8_t e = (someInt - (zt * 10000) - (t * 1000) - (h * 100) - (z * 10));   // z.B. 123 - ... = 3 ==> 3
130
  blinkLED(e + 1);
131
  _delay_ms(5000);
132
}
133
134
int main(void) {
135
  DDRB |= (1 << DDB1) | (1 << DDB0); // Pin 3 u. 6 als Ausgang festlegen (Lüfter/LED).
136
  PORTB |= (1 << PORTB3);  // Taster Pull-up ein
137
  blinkLED(1);
138
  
139
  setupADC();
140
  
141
  setupPWM();
142
  
143
  setupTachoRead();
144
  
145
  // ADC in Free Running Mode versetzen und erste Messung starten
146
  ADCSRA |= (1 << ADSC) | (1 << ADATE);
147
  
148
  sei();  // Interrupts einschalten
149
150
  while(1) {
151
    if (to_counter > (25 * 1000)) {  // ca. einmal pro Sekunde
152
      cli();
153
      to_counter = 0;
154
      
155
      fan_rpm = (fan_tacho_signals / 2) * 60;
156
      fan_tacho_signals = 0;
157
      
158
      if (temperature <= 20) {
159
        OCR0B = PWM_BOTTOM;
160
      } else if (temperature > 20 && temperature < 25) {
161
        OCR0B = PWM_TOP / 2;
162
      } else {
163
        OCR0B = PWM_TOP;
164
      }
165
      sei();
166
    }
167
    
168
    if (!(PINB & (1 << PINB3))) {
169
      cli();
170
      blinkMyInt(fan_rpm);
171
      sei();
172
    }
173
  }
174
}

von C. (Gast)


Lesenswert?

Schau doch erstmal ob der RPM input korrrekt funktioniert, befor du mit 
ADC und PWM weiter machst.

Soviel ich weiß, ist das Tachosignal am Lüfter ein 
Open-Collector-Ausgang.
Das heißt, er setzt das Signal für jeden Inpuls über einen Transistor 
kurz auf GND.

Daszählen über einen Pin Change Interrupt zu erledigen ist gefärlich, je 
hoher die Frequns und so häufiger muss die CPU in die ISR springen. Und 
der Wesel in die ISR und aus der ISR kostet eine mehge CPU-Takte.
Besser wäre es einen externen Clock eingang dafür zu verwenden.

von C. (Gast)


Lesenswert?

Timer0 kann über PB2 als Zählinpulse bekommen

von C. (Gast)


Lesenswert?

Oh, noch einmal das Datenblaut überflogen, der Tiny hat nur einen 
einigen Timer/Counter. Dann wird es so mit dem µC nicht gehen.
Dann bleibt doch nur der Pin Change Interrupt übrig.

PWM und Zeitbasis muss du dann über den einen Timer machen.

von R. M. (Gast)


Lesenswert?

C. schrieb:
> Oh, noch einmal das Datenblaut überflogen, der Tiny hat nur einen
> einigen Timer/Counter. Dann wird es so mit dem µC nicht gehen.
> Dann bleibt doch nur der Pin Change Interrupt übrig.
>
> PWM und Zeitbasis muss du dann über den einen Timer machen.

Eventuell kann man auch den Watchdog als periodische Interruptquelle 
missbrauchen (16ms * 2 hoch N).

von Christian F. (neo2001)


Lesenswert?

C. schrieb:
> Schau doch erstmal ob der RPM input korrrekt funktioniert, befor du mit
> ADC und PWM weiter machst.

Der funktioniert. Habe es auf zwei verschiedenen µCs inkl. dem ATtiny13a 
selbst mit verschiedenen Lüftern getestet. Sprich PCINT wird ausgelöst 
(und auch nicht, wenn der Lüfter sich nicht dreht), ISR prüft ob HIGH 
oder LOW und zählt nur bei LOW. Der Zähler wird dann 1x pro Sekunde 
ausgewertet und ausgeben (hab es mir auch auf einem Arduino mit 
serieller Schnittstelle anzeigen lassen). Erhalte in allen Fällen 
saubere und konstante Werte (die auch zu den Angaben der Lüfter passen).

Aber sobald die andere Funktionalität ins Spiel kommt, kommt nur noch 
Käse raus.

von C. (Gast)


Lesenswert?

Ich sehe gerade bei deiner Timer-Konfiguration nicht durch.

Du braucht eine PWM-Frequenz von 25kHz (oder zumindest in der nähe 
davon)
Und Gleichzieitig eine Zeitbassis.

Und hast nur einen Timer.


Ich hätte den Timer im CTC Modus betrieben.
Bei 9,6Mhz CPU Takt und einen Prescaler von 2 und OCR0A auf 191 setzten, 
dann hat man 25kHz.

mit OCR0B die PWM einstellen. Werte gehen dann aber nur von 0 bis 191 
wegen CTC-Modus.

ISR (TIMER0_COMPA_vect){} wird dann mit 25KHz aufgerufen

Einen Zähler bis 25000 zählen lassen und man hat eine Sekundenbasis

von Christian F. (neo2001)


Lesenswert?

Ja, 25 kHz waren/sind das Ziel für PWM.

Mein Ansatz war der den ohnehin laufenden PWM-Zähler (weil ich den auch 
zuerst implementiert hatte) auch für das bestimmten der Zeit zu 
benutzen.

Der zählt, bei einem Prescaler von 8, von 1 bis 48.
Also 9,6 MHz / (8 * 48) = 25.000 Hz

Der Timer Overflow Interrupt löst dann jeweils aus und erhöht meine 
Zählervariable. Wenn diese 25.000 erreicht hat gehe ich davon aus, dass 
eine Sekunde vorbei ist.

(Nur zur Erklärung meines Gedankengangs)

von Kirsch (Gast)


Lesenswert?

Christian F. schrieb:
> Der zählt, bei einem Prescaler von 8, von 1 bis 48.
> Also 9,6 MHz / (8 * 48) = 25.000 Hz

Im CTC-Modus würde das einen Takt von 24489,79592 Hz entsprechen
OCR0A=47 wäre richtig

aber läuft der Timer überhaut im CTC Modus, musste jetzt die COM0 und 
WGN0 Bits nachschlafen

Im FastPWM läuft der Zähler nämlich einfach weiter bis 255 und es kommt 
zum Overflow-Interrupt.
Im CTC Modus kommt es hingen nicht zum Überlauf, deswegen muss man den 
COMPA-Interrupt nehmen.

von Kirsch (Gast)


Lesenswert?

OK, beim WGM = 111b läuft der Zähler auch bei Fast PWM nur bis OCRA.
Aber zum überlauf kommt es dann aber trotzdem nicht. Auch dann muss man 
COMPA-Interrupt nehmen.

von Christian F. (neo2001)


Lesenswert?

Kirsch schrieb:
> OK, beim WGM = 111b läuft der Zähler auch bei Fast PWM nur bis OCRA.
> Aber zum überlauf kommt es dann aber trotzdem nicht. Auch dann muss man
> COMPA-Interrupt nehmen.

Ich bin da nach der Tabelle auf Seite 73 im Datasheet gegangen:

Mode 7, FastPWM mit TOP = OCRA und ganz rechts steht "TOV Flag Set on" 
"TOP".

Da die Temperature geregelt wird (also OCR0A dynamisch angepasst wird) 
und zumindest irgendein Datenmüll in fan_rpm landet muss die to_counter 
Variable ja auch hochgezählt werden. Insofern gehe ich davon aus, dass 
der Interrupt auslöst.

von Kirsch (Gast)


Lesenswert?

Christian F. schrieb:
> Ich bin da nach der Tabelle auf Seite 73 im Datasheet gegangen:
>
> Mode 7, FastPWM mit TOP = OCRA und ganz rechts steht "TOV Flag Set on"
> "TOP".

Stimmt, du hast recht.
Dann wird TOV einen Zählertakt nach COMPA getriggert.

Christian F. schrieb:
> 9,6 MHz / (8 * 48) = 25.000 Hz

Die Formel stimmt nicht ganz, so ist richtig: f_ov = f_clk / ( pre * ( 
OCRA + 1 ) )

ich hätte es eher so gemacht um auf 16bit integer zu verzichten
1
volatile uint8_t flag = 0;
2
ISR(TIM0_OVF_vect) {
3
  static uint8_t a = 0;
4
  static uint8_t b = 0;
5
  a++;
6
  if( a == 250) {
7
    a=0
8
    b++;
9
    if( b == 100 ) {
10
      b = 0;
11
      flag = 1;
12
    }
13
  }  
14
}

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.