Forum: Mikrocontroller und Digitale Elektronik IIR Besselfilter in Attiny85 - 32 Bit Berechnung möglich?


von Andreas (Gast)


Lesenswert?

Hallo zusammen,
ich habe einen IIR-Besselfilter gebaut und mit den Messwerten aus der 
Schaltung in Excel ausprobiert. Dort funktioniert der wunderbar.

Nun hat der ATtiny85 meines Wissens keine Gleitkommaberechnung und nicht 
genug Platz für die double-Bibliothek, also habe ich noch ein Excelsheet 
und einen Tag drangehängt und die Berechnung komplett ohne negative 
Zahlen und ohne Gleitkomma möglich gemacht. Die habe ich dann 
ausführlich mit unechten und mit realen Messergebnissen getestet. Siehe 
da, rein theoretisch funktioniert es schon mal.

Leider sehe ich keine Möglichkeit, das auch noch in 16 Bit 
reinzuquetschen. Und das ist der Haken.

Die Filterkoeffizienten haben teilweise ein 10^-5 dranhängen. Sprich, 
ohne Kommazahlen merkt man erst dann was von dem Koeffizienten, wenn man 
die Berechnung mit ungefähr 10^5 multipliziert oder um 15 bis 16 Bit 
nach links schiebt. Wenn ich das mit den Messergebnissen mache, habe ich 
leider umgehend die 16 Bit gesprengt.

Der Attiny85 ADC liefert mir 10 Bit Messergebnisse. Ich habe uint32_t 
verwendet. Meine Berechnung funktioniert scheinbar im Attiny nicht.

Hier die Frage:
Kann man mit dem Datentyp uint32_t auch rechnen? Wenn nicht, gibt es 
eine Bibliothek oder irgendwas zum runterladen, so dass man dem Attiny 
auch 32 Bit beibringen kann?

Ich habe den ganzen Tag gegoogelt aber nichts gefunden. Vielleicht bin 
ich auch nur zu blind um den Baum im Wald zu finden. Kann mir einer von 
euch helfen?

von O. D. (odbs)


Lesenswert?

Poste mal deinen Quelltest. Mit großer Wahrscheinlichkeit hast du bei 
der Umsetzung in C die klassischen Fehler gemacht.

Der avr-gcc kann jedenfalls Maschinencode für Berechnungen mit uint32_t 
erzeugen!

von Purzel H. (hacky)


Lesenswert?

Es sollte nicht allzu schwierig sein mit 32 bit auf einem Tiny 85 
rechnen zu wollen. Solange es nicht allzu viele Koeffizienten sind...

von Andreas W. (calidus)


Lesenswert?

Danke
Hier ist der Code.
Er ist ein bisschen lang mit seinen Kommentaren, darum habe ich ihn 
erstmal nicht gepostet gehabt.
1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3
#include <inttypes.h> 
4
5
//PB2: ADC1 ist der Eingang für die Fotodiode.
6
//PB0: ist der Ausgang für den Motor.
7
//PB4: PCINT4 ist der Steuerausgang für die Fotodioden-Beleuchtung
8
9
// Wenn der Motor dreht, ziehen abwechselnd schwarze und weiße Streifen an
10
// der Fotodiode vorbei. Bei sieben gezählten Streifen soll er wieder anhalten.
11
#define ANZAHL_STREIFEN 7
12
char schwarze_streifen; //Zähler für die schwarzen Streifen
13
char wirdsHeller; //wird es heller oder dunkler?
14
15
16
// Wenn der Motor steht, soll das Teil 5 Minuten Pause machen bis er wieder anfängt
17
// zu drehen.
18
#define PAUSENLAENGE 600 // halbe Sekunden
19
unsigned int pausenzeit; //zähler für die halben Sekunden
20
21
22
// generelle Statemachine im Hauptprogramm
23
#define WARTEN_AUF_MESSUNG  0
24
#define MESSEN_STARTEN  2
25
#define MESSEN_FERTIG  3
26
27
#define PAUSE_EINLAEUTEN 6
28
#define PAUSE 4
29
#define WEITERDREHEN 5
30
31
char cSREG;
32
33
34
//Filtervariablen
35
uint32_t mess_hilf, mess_0, mess_1, mess_2, mess_3, mess_4;
36
uint32_t ergebnis_4, ergebnis_3, ergebnis_2, ergebnis_1, ergebnis_0;
37
uint32_t A, B; // Filter zwischenergebnisse
38
39
char communication; //Statemachine Steuervariable
40
41
42
ISR(TIM1_COMPA_vect) // Timer 1 meldet einen OCR1A Vergleich
43
{
44
    cSREG = SREG;
45
    // Interrupt Code 
46
  if (communication == PAUSE)
47
  {
48
    pausenzeit = pausenzeit + 1;
49
    
50
    if (pausenzeit >= PAUSENLAENGE)
51
    {
52
      communication = WEITERDREHEN;
53
    }
54
  }
55
  else
56
  {
57
    communication = MESSEN_STARTEN; //Messintervall vorüber
58
  }
59
60
  SREG = cSREG;
61
  //TIFR = 0x7E;
62
  sei();
63
}
64
65
//Der ADC meldet sich hier, wenn die Messung fertig ist.
66
ISR (ADC_vect)
67
{
68
  communication = MESSEN_FERTIG;
69
  sei();
70
}
71
72
73
void adc_einstellen(void);
74
void messwert_filtern(void);
75
76
void timer1_einstellen(unsigned char); // umstellen von Pause zur Messen und andersherum
77
78
void motor_ein(void); // ein
79
void motor_aus(void); // aus
80
81
int main(void)
82
{
83
   // Initialisierungen: globale Variablen
84
85
  //Vor-Initialisierung, damit der Filter schneller auf Stand kommt
86
  ergebnis_4 = 32768;  
87
  ergebnis_3 = 32768;
88
  ergebnis_2 = 32768;
89
  ergebnis_1 = 32768;
90
  ergebnis_0 = 32768;
91
92
  //Statemachine initialisieren; mit "drehen" starten.
93
  communication = WEITERDREHEN;
94
95
  wirdsHeller = 2; //wird es heller oder dunkler?
96
  schwarze_streifen = 0; //wie viele Streifen sind am Sensor vorbeigezogen?
97
  pausenzeit = 0; //Aktuell verstrichene Pausenzeit
98
99
  // Initialisierungen: Clock, Ports, Timer, ADC, Interrupts
100
  // Clock
101
  // Der interne Takt ist eingestellt auf 8 MHz 
102
103
104
  // Watchdog deaktivieren
105
  //  WDTCR = 0x00;
106
107
108
  // Portpin
109
  DIDR0 = 0x00;
110
  MCUCR = 0x00; // Pull Up Widerstände aktivieren
111
  DDRB  = 0x3F; // Data Direction Register, vorerst komplett als Ausgang
112
  PORTB = 0x00; // Alle Pins auf Low
113
  //PINB      // Eingangsregister -> will ich nicht -> weggelassen
114
115
// nötige Peripherals einstellen
116
  timer1_einstellen(WEITERDREHEN); 
117
  adc_einstellen();
118
  pwm_einstellen();
119
120
// LED einstellen
121
  DDRB |= (1 << PB4); // Pin für LED auf "Ausgang" schalten
122
123
124
125
// Timer-Interrupts aktivieren
126
  TIMSK = 0x40; // Timer 1 OCR1A Compare Match Interrupt enabled
127
  SREG |= 0x80;
128
129
   
130
/*********************************************************************************/   
131
   
132
   
133
   while(1) { 
134
     // Statemachine              
135
    switch (communication)
136
    {
137
/**************************************************/
138
    case PAUSE: { 
139
    // Warte auf nächsten Zyklusstart; 
140
    // State wird im Interrupt verändert
141
142
      break;
143
    }
144
/**************************************************/
145
    case WEITERDREHEN: { // Start eines neuen Zyklus
146
      // Sensorbeleuchtung starten
147
      PORTB |= (1 << PB4); // LED für den Sensor einschalten
148
      // Timer 1 umstellen für Sensor Interrupts
149
      timer1_einstellen(WEITERDREHEN);
150
      // Motor starten
151
      motor_ein();
152
      // Messung starten
153
      communication = MESSEN_STARTEN;
154
      break;
155
    }
156
/**************************************************/
157
    case MESSEN_STARTEN: { //Messung starten
158
159
      ADCSRA |= (1<<ADSC); // ADSC auf 1 setzen startet den ADC
160
      communication = WARTEN_AUF_MESSUNG;
161
      break;
162
163
    }
164
/**************************************************/
165
    case WARTEN_AUF_MESSUNG: { 
166
    // Wartet auf nächstes Messwert-Timer-Intervall oder auf
167
    // die Fertigstellung der Messung. Beide States werden 
168
    // in den Interrupt Handlern gesetzt
169
170
      break;
171
    }
172
/**************************************************/
173
    case MESSEN_FERTIG: //Messwert fertig gemessen
174
    {
175
176
      mess_0 = ADCL; 
177
      mess_hilf = ADCH;
178
      mess_hilf = mess_hilf * 256;
179
      mess_0 = mess_0 + mess_hilf;
180
      messwert_filtern();
181
      if (ergebnis_0 > ergebnis_4)
182
      {
183
        wirdsHeller = wirdsHeller + 1;
184
        if (wirdsHeller == 4) //einige Male in Folge heller geworden?
185
        {  //-> Ein schwarzer Streifen ist vorbeigezogen. Zählen!
186
          schwarze_streifen = schwarze_streifen + 1;
187
        }
188
        if (wirdsHeller > 5)
189
        {
190
          wirdsHeller = 5;
191
        }
192
193
      }
194
      if (ergebnis_0 < ergebnis_4)
195
      {  // Es wird dunkler
196
        wirdsHeller = 0;
197
      }
198
      if (schwarze_streifen >= ANZAHL_STREIFEN)
199
      {
200
        communication = PAUSE_EINLAEUTEN;
201
      }
202
      else communication = WARTEN_AUF_MESSUNG;
203
204
      break;
205
    }
206
/**************************************************/
207
    case PAUSE_EINLAEUTEN:
208
    {
209
      // Motor stoppen
210
      motor_aus();
211
      // LED ausschalten
212
      PORTB &= ~(1 << PB4); // LED für den Sensor ausschalten
213
      // Pause machen bis zum nächsten Mal
214
      communication = PAUSE;
215
      // Timer 1 umstellen auf Pausenmodus
216
      timer1_einstellen(PAUSE);
217
      communication = PAUSE;
218
      break;
219
    }
220
/**************************************************/
221
    default : {
222
      communication = PAUSE_EINLAEUTEN;
223
      // Im Fehlerfall lieber den Motor stoppen und in sichere States gehen
224
      break;
225
      }
226
    }
227
  }                        
228
 /***********************************************************************/
229
   return 0;    
230
}
231
232
/*************************************************************
233
Ausgelagerte ADC-Einstellen-Funktion für die Übersichtlichkeit
234
*************************************************************/
235
void adc_einstellen(void)
236
{
237
  /*
238
      #define ADMUX   _SFR_IO8(0x07)
239
      #define REFS1   7
240
      #define REFS0   6
241
      #define ADLAR   5
242
      #define REFS2   4
243
      #define MUX3    3
244
      #define MUX2    2
245
      #define MUX1    1
246
      #define MUX0    0
247
      */  
248
  ADMUX = 0xE1; //Interne 1.1V Referenz, ADLAR = 1 für linksbündige Ergebnisse, Pin PB2 (ADC1D) als ADC-Eingang
249
250
  /*
251
    ADCSRB
252
    Bit 7: Bei 1 wird bipolar gemessen. 0.
253
    Bit 5: Bei 1 wird der Eingang negativ gemessen. 0.
254
    Bits 2:0 : Auto Trigger Source. Brauche ich nicht, weil ADATE = 0 ist.
255
  */
256
  ADCSRB = 0x00;
257
  // ADC1 Pin als Eingang schalten
258
  DIDR0 |= (1<<ADC1D);   // Digital Input Disable Register;  Bit 5 auf 1, damit der AD-Pin nicht digital verwendet wird.
259
  DDRB  &= ~(1<<ADC1D); //Pin ADC1D als Eingang
260
  /* 
261
    ADCSRA
262
    Bit 7, ADEN: ADC Enable. Auf 1.
263
    Bit 6, ADSC: ADC Start Converstion. Auf 1 zum Initialisieren vom AD
264
    Bit 5, ADATE: ADC Auto trigger enable: Bei bestimmten Ereignissen automatisch starten. 0 für Aus.
265
    Bit 4, ADIF: ADC Interrupt Flag, wird automatisch beschrieben wenn der ADC fertig ist
266
    Bit 3, ADIE: ADC Interrupt Enable, auf 1 wenn ein Interrupt nach der Wandlung ausgeführt werden soll. 1.
267
    Bit 2:0, ADPS2:0, ADC Prescaler Bits für System Clock. 110 für 8MHz geteilt durch 64 = 125kHz
268
  */
269
  ADCSRA = 0xCE; //führt auch gleich die erste Konversion für die Initialisierung mit durch
270
}
271
272
/*************************************************************
273
Ausgelagerte Motor-Steuern-Funktion für die Übersichtlichkeit
274
Benötigt OCR0A als PWM Ausgang vor-eingestellt
275
*************************************************************/
276
277
void motor_ein(void)
278
{
279
  PORTB |= (1 << PB0); //Eins auf den Motorpin
280
}
281
void motor_aus(void)
282
{
283
  PORTB &= ~(1 << PB0);//Null auf den Motorpin
284
}
285
286
/*************************************************************
287
Ausgelagerte Pausentimer-Einstellen-Funktion für die Übersichtlichkeit
288
Der Pausentimer soll messen, wann die Pause vorbei ist.
289
Er soll außerdem den ADC Takt vorgeben wenn keine Pause ist.
290
Die Funktion soll nur aufgerufen werden um den Timer-Modus umzustellen!
291
*************************************************************/
292
void timer1_einstellen(unsigned char state)
293
{
294
  if (state == PAUSE)
295
  {
296
    GTCCR = 0x80; // Stoppen vom Timer via setzen von 1 im TSM Bit
297
    TCCR1 = 0x8F; // Clear on compare match mit OCR1C; Sysclock/16384 ist die Timer Clock (488 Hz)
298
    OCR1A = 0x0A; // 0x0A = 10; an dieser Zählmarke wird der OCR1A-Interrupt aktiviert.
299
    OCR1C = 244;  // Nach 244 Zählern wird der Timer rückgesetzt -> 2 Interrupts / sec
300
    GTCCR = 0x00; // kein PWM B, kein Output force, kein löschen von TCCR1, 0 ins TSM Bit -> restart
301
  }
302
  else
303
  {  
304
    GTCCR = 0x80; // Stoppen vom Timer via setzen von 1 im TSM Bit
305
    TCCR1 = 0x8A; // Clear on compare match mit OCR1C; Sysclock/512 ist die Timer Clock (15625 Hz)
306
    OCR1A = 0x0A; // 0x0A = 10; an dieser Zählmarke wird der OCR1A-Interrupt aktiviert.
307
    OCR1C = 31;   // Nach 31 Zählern wird der Timer rückgesetzt -> 504 Interrupts / sec
308
    GTCCR = 0x00; // kein PWM B, kein Output force, kein löschen von TCCR1, 0 ins TSM Bit -> restart
309
  }    
310
}
311
312
313
/****************************************************************************
314
Messwerte filtern
315
316
Benötigt:
317
- 5 Ergebnisvariablen mit uint32_t
318
- 5 Messwertvariablen mit uint32_t
319
- Messwerte 16 Bit breit linksbündig (sorry)
320
- Abtastfrequenz 500Hz
321
- Filtervariablen 32 Bit breit
322
323
 Die Filterkoeffizienten wurden mit dem Excel aus dem Projektordner bestimmt.
324
 Es handelt sich um ein IIR Filter mit einer Grenzfrequenz von 12,5Hz
325
 und 4 Filterstufen nach Butterworth
326
327
*****************************************************************************/
328
void messwert_filtern(void)
329
{
330
331
  A =   12 * mess_0; //TAP 0
332
  A = A + 50 * mess_1; //TAP 1
333
  A = A + 75 * mess_2; //TAP 2
334
  A = A + 50 * mess_3; //TAP 3
335
  A = A + 12 * mess_4; //TAP 4
336
  
337
  A = A + 50; // Faktor entfernen I: die Hälfte vom Faktor drauflegen gegen Rundungsfehler
338
  A = (int)(A/100); //Faktor entfernen II: Teilen und abrunden
339
  
340
  B =   14359 * ergebnis_1; //TAP 1
341
  B = B - 19405 * ergebnis_2; //TAP 2
342
  B = B + 11696 * ergebnis_3; //TAP 3
343
  B = B - 2652  * ergebnis_4; //TAP 4
344
345
  ergebnis_0 = A + B + 2000;// Faktor entfernen I: die Hälfte vom Faktor drauflegen gegen Rundungsfehler
346
  ergebnis_0 = (int)(ergebnis_0 / 4000); //Faktor entfernen II: Teilen und abrunden
347
348
  //Messwerte durch die TAPs schieben
349
  mess_4 = mess_3;
350
  mess_3 = mess_2;
351
  mess_2 = mess_1;
352
  mess_1 = mess_0; //mess0 wird vom ADC gefüllt
353
  
354
  //Ergebnisse durch die TAPs schieben
355
  ergebnis_4 = ergebnis_3;
356
  ergebnis_3 = ergebnis_2;
357
  ergebnis_2 = ergebnis_1;
358
  ergebnis_1 = ergebnis_0; //ergebnis_0 wird weiter oben berechnet.
359
}

von gasd12 (Gast)


Lesenswert?

ich würde dir zum einen das
http://www.mikrocontroller.net/articles/Festkommaarithmetik
empfehlen und dann mal die frage sind adcl und adch nicht uint16_t ???

von Helmut L. (helmi1)


Lesenswert?

Fuer sowas nimmt man die sogenannten Q-Formate.

http://en.wikipedia.org/wiki/Q_%28number_format%29

Auch darf die Samplefrequenz und deine hoechste Signalfrequenz nicht zu 
weit auseinander liegen. Je weiter die auseinander liegen um so kleiner 
werden deine Filterkonstanten.

von eProfi (Gast)


Lesenswert?

0. Wichtige Regeln - erst lesen, dann posten!
> Groß- und Kleinschreibung verwenden
> Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang


1. Erzähl doch mal bitte in Prosa, was das Ding machen soll.

2. Es erscheint mir alles viel zu kompliziert hingeschrieben.
Meine Meinung: man kann das ganze Problem in 30 Zeilen lösen.

3. IIR zum "Entprellen" der Fotozelle?
Warum machst Du nicht gleich noch eine Fourie-Analyse?
In diesem Fall reicht doch ein Einfachst-Filter.
Oder übersehe ich etwas?

4.
> A = (int)(A/100); //Faktor entfernen II: Teilen und abrunden
> ergebnis_0 = (int)(ergebnis_0 / 4000); //Faktor entfernen II

Beitrag "Re: Möglichst Resourcenschonend programmieren"
Beitrag "Re: sinus LUT über Interrupt auslesen"
-->
> A = (int)(A/128); //Faktor entfernen II: Teilen und abrunden
> ergebnis_0 = (int)(ergebnis_0 / 4096); //Faktor entfernen II
Die vorherigen Faktoren müssen natürlich ebenfalls angepasst werden.

von Andreas W. (calidus)


Lesenswert?

Das mit der Festkommaarythmetik/Q-Format finde ich gut. Danke für den 
Tipp, die waren mir nicht bekannt.

@Helmut: Leider muss ich den Filter so gestalten wie er ist, da ich 
sonst falsche Ergebnisse bekomme. Das habe ich bereits in meinem Excel 
ausführlich getestet. Wenn ich die Grenzfrequenz näher an die 
Samplefrequenz heranschiebe oder die Samplefrequenz verringere, 
verschwinden die Störsignale nicht so vollständig wie gewünscht.

@eProfi:

1. und 4. Es dreht einen Motor. Der Motor dreht eine Scheibe. Auf der 
Rückseite der Scheibe sind schwarze und weiße Streifen. Diese Streifen 
schaut sich ein Sensor an, während sie vorbeiziehen. Es sind ca 1 bis 
maximal 3 schwarze Streifen pro Sekunde. Nach sieben Streifen (ungefähr 
420° Drehung) soll der Motor stoppen und fünf Minuten lang ruhig sein.

Ein LDR war mein erster Ansatz, der war aber zu langsam. (ich habe es 
erst nicht geglaubt, aber es ist so.)
Also wurde es eine Fotodiode. Die Fotodiode ist schnell genug. Die 
schaut sich natürlich auch die 50Hz aus der Energiesparlampe, die 150Hz 
aus meiner Osram Neonröhre und die Schaltungsinternen Störfrequenzen aus 
dem Motor mit an. Deshalb soll vernünftig gefiltert werden. Ein 
Einfachst-Filter (gleitender Durchschnitt oder sowas) kann das NICHT. Da 
erscheinen die Störfrequenzen an der Abtastrate gespiegelt 
unvorhersehbar irgendwo - falls die Abtastrate in der Nähe der 
Störfrequenz liegt. Bei dieser Anwendung wäre das leiter nötig. Daher 
baue ich lieber gleich einen echten Filter. Von der Geschwindigkeit und 
Rechenkapazität und Speicherkapazität kann der Attiny85 das ganz locker. 
Klar soweit?

Die Filterauslegung braucht man heutzutage nicht mehr rechnen, das 
machen online Tools wie dieses hier: 
http://www.dsptutor.freeuk.com/IIRFilterDesign/IIRFiltDes102.html ganz 
kostenlos und einfach für dich. Deshalb ist das auch überhaupt kein 
großer Aufwand - Rechenkapazität braucht es vergleichbar viel wie ein 
guter gleitender Durchschnitt, es funktioniert nur viel besser.

2. Ich bin der Meinung dass es mit einigen Subfunktionen und viel 
Kommentar später leichter für mich verständlich ist. Ich verwende meine 
Subs in späteren Programmen wieder, und da ist es gut wenn sie von 
vornherein freigeschnippelt und kommentiert sind. Der Compiler löscht 
den überflüssigen Kram schließlich, der AVR kriegt davon nicht viel mit.

3. Klar, das ist sinnvoll Zweierpotenzen zu nehmen. Das setze ich um. 
Danke.

von Andreas W. (calidus)


Lesenswert?

Ich habe den möglichen Fehler gefunden, ich komme aber nicht darauf 
weshalb das so ist.

Ich habe herausgefunden:
Er verwendet die beiden ISRs.
Er geht in die States
- Weiterdrehen
- Messen_starten
- Warten_auf_Messung

Was er anscheinend nicht tut:

Obwohl er in die ISR vom ADC geht, geht er hinterher nicht in den State 
Messen_fertig.

Hat einer eine Ahnung warum?
Habe ich den Interrupt falsch programmiert? Hier noch mal in kürze der C 
Code von der ISR
1
//Der ADC meldet sich hier, wenn die Messung fertig ist.
2
ISR (ADC_vect)
3
{
4
  communication = MESSEN_FERTIG;
5
  sei();
6
}

von EM (Gast)


Lesenswert?

Muss das ADC Int Flag gelöscht werden, oder wird das automatisch bei 
Auslösen der Int Prozedur erledigt? Hab jetzt gerade kein Datenblatt zur 
Hand.

Grüße

von EM (Gast)


Lesenswert?

Und setz mal communication auf volatile, damit der Compiler da 
"Bescheid" weiß.

von Ulrich (Gast)


Lesenswert?

GCC (und die anderen C Compiler wohl auch) kümmert sich schon selbst um 
das retten des SREG, und das SEI am Ende der ISR  ist auch nicht nötig 
aber eventuell gefährlich.

Das Interrupt-flag  vom AD wird schon beim Aufruf der ISR vom µC 
gelöscht.

von Andreas W. (calidus)


Lesenswert?

Entweder war es der Volatile oder der Sei()...

jedenfalls geht es jetzt. Vielen Dank euch allen!

von Christian B. (casandro)


Lesenswert?

Kleiner Tipp übrigens, setzte die Abtastrate auf ein größeres Vielfaches 
der Störfrequenz. Dann falten sich die Störungen tendenziell eher auf 
höhere Frequenzen. Davor muss aber ein Filter rein.

von Andreas W. (calidus)


Lesenswert?

Danke!
Es funktioniert inzwischen.

http://www.youtube.com/watch?v=C0HQmS3KMBU

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.