IIR Besselfilter in Attiny85 - 32 Bit Berechnung möglich?

Gast #1995252
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?
#1995615
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
}
Gast #1995732
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.
#1995811
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.
#1997145
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
}
Gast #1997223
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.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren