Forum: Mikrocontroller und Digitale Elektronik AVR gesteuerte Kerze für bis zu 8 LEDs


von Orikson (Gast)


Lesenswert?

Hallo,

es Weihnachtet schon sehr, diese Woche hab ich im Aldi Prospekt schon 
Beleuchtung etc. gesehen und da ist mir wieder die Idee gekommen, dass 
ich auch noch was brauche. Aber natürlich nichts gekauftes, dafür aber 
umso dekorativer. Unsere Krippe hat nämlich letztes Jahr schon eine 
Feuerstelle dazu bekommen, allerdings noch keine bzw. nur statische 
Beleuchtung. Das wollte ich letztes Jahr schon in Angriff nehmen, was 
aber aus Zeitmangel scheiterte. Dieses Jahr soll das nun nachgeholt 
werden, weshalb ich heute schon mal etwas am Code gebastelt habe. 
Hardwareseitig kommt einfach an jeden Port des µC eine LED mit 
Vorwiderstand, die Tatkung übernimmt der interne Oscillator, welchen ich 
auf 4Mhz gestellt habe. Als Controller hab ich hier jetzt einen Mega8 
verwendet, jeder andere (bis runter zum ATiny 13 sollte aber auch 
gehen).

Die LEDs werden über Software-PWM gedimmt, den Code dazu habe ich von 
dieser Seite (http://www.mikrocontroller.net/articles/Soft-PWM) hier 
übernommen. Dahinter habe ich eigentlich nur noch einen kleinen 
Zufallsgenerator für jede LED gehängt, und diesen Wert vom aktuellen 
abziehen lassen. Somit vermeide ich hässliche Helligkeitssprünge, was ja 
auch bei normalen Kerzen eher selten vorkommt. Außerdem flackert jede 
LED unabhängig von der anderen, und nicht alle gleich.

Allerdings würde ich gerne noch eine Kleinigkeit in meinem Code 
verändern. Ich habe unten 8 mal if-Abragen mit welcher ich die 
Zufallszahlen für die Helligkeit der LEDs erzeuge. Das ganze 
funktioniert zwar, ist aber nicht sonderbar schön und wenn man einen 
Wert ändern will muss man das 8 mal machen... Ich hatte da an eine 
for-Schleife gedacht (for (x=1,x=PWM_CHANNELS,x++)), allerdings weiß ich 
nicht wie ich das in den if-Abfragen dann umschreiben müsste. Die Zahlen 
hinter i müssten dann aus der Variable x erzeugt werden, aber ix 
anstelle von i1, i2 etc. geht nicht! Wie kann ich das noch lösen?

Außerdem muss ich gestehen, dass ich nicht ganz weiß, wie die Ausgabe 
mit diesem Soft-PWM Code gedacht ist. Der Code der Testanwendung 
funktioniert aber ganz gut ;-P


1
#define F_CPU         4000000L           // Systemtakt in Hz
2
#define F_PWM         100L               // PWM-Frequenz in Hz
3
#define PWM_PRESCALER 8                  // Vorteiler für den Timer
4
#define PWM_STEPS     256                // PWM-Schritte pro Zyklus(1..256)
5
#define PWM_PORT      PORTB              // Port für PWM
6
#define PWM_DDR       DDRB               // Datenrichtungsregister für PWM
7
#define PWM_CHANNELS  8                  // Anzahl der PWM-Kanäle
8
 
9
// ab hier nichts ändern, wird alles berechnet
10
 
11
#define T_PWM (F_CPU/(PWM_PRESCALER*F_PWM*PWM_STEPS)) // Systemtakte pro PWM-Takt
12
//#define T_PWM 1   //TEST
13
 
14
#if ((T_PWM*PWM_PRESCALER)<(111+5))
15
    #error T_PWM zu klein, F_CPU muss vergrösst werden oder F_PWM oder PWM_STEPS verkleinert werden
16
#endif
17
 
18
#if ((T_PWM*PWM_STEPS)>65535)
19
    #error Periodendauer der PWM zu gross! F_PWM oder PWM_PRESCALER erhöhen.   
20
#endif
21
// includes
22
 
23
#include <stdint.h>
24
#include <string.h>
25
#include <avr/io.h>
26
#include <avr/interrupt.h>
27
#include <util/delay.h>
28
 
29
// globale Variablen
30
 
31
uint16_t pwm_timing[PWM_CHANNELS+1];          // Zeitdifferenzen der PWM Werte
32
uint16_t pwm_timing_tmp[PWM_CHANNELS+1];      
33
uint8_t  pwm_mask[PWM_CHANNELS+1];            // Bitmaske für PWM Bits, welche gelöscht werden sollen
34
uint8_t  pwm_mask_tmp[PWM_CHANNELS+1];        
35
uint8_t  pwm_setting[PWM_CHANNELS];           // Einstellungen für die einzelnen PWM-Kanäle
36
uint8_t  pwm_setting_tmp[PWM_CHANNELS+1];     // Einstellungen der PWM Werte, sortiert
37
 
38
volatile uint8_t pwm_cnt_max=1;               // Zählergrenze, Initialisierung mit 1 ist wichtig!
39
volatile uint8_t pwm_sync;                    // Update jetzt möglich
40
 
41
// Pointer für wechselseitigen Datenzugriff
42
 
43
uint16_t * isr_ptr_time  = pwm_timing;
44
uint16_t * main_ptr_time = pwm_timing_tmp;
45
uint8_t *  isr_ptr_mask  = pwm_mask;
46
uint8_t *  main_ptr_mask = pwm_mask_tmp;
47
 
48
// Zeiger austauschen
49
// das muss in einem Unterprogramm erfolgen,
50
// um eine Zwischenspeicherung durch den Compiler zu verhindern
51
 
52
void tausche_zeiger(void) {
53
    uint16_t * tmp_ptr16;
54
    uint8_t * tmp_ptr8;
55
 
56
    tmp_ptr16 = isr_ptr_time;
57
    isr_ptr_time = main_ptr_time;
58
    main_ptr_time = tmp_ptr16;
59
    tmp_ptr8 = isr_ptr_mask;
60
    isr_ptr_mask = main_ptr_mask;
61
    main_ptr_mask = tmp_ptr8;
62
}
63
 
64
// PWM Update, berechnet aus den PWM Einstellungen
65
// die neuen Werte für die Interruptroutine
66
 
67
void pwm_update(void) {
68
    uint8_t i, j, k, min;
69
    uint8_t tmp;
70
 
71
    // PWM Maske für Start berechnen
72
    // gleichzeitig die Bitmasken generieren und PWM Werte kopieren
73
 
74
    tmp=0;
75
    j = 1;
76
    for(i=1; i<=(PWM_CHANNELS); i++) {
77
        main_ptr_mask[i]=~j;                        // Maske zum Löschen der PWM Ausgänge
78
        pwm_setting_tmp[i] = pwm_setting[i-1];
79
        if (pwm_setting_tmp[i]!=0) tmp |= j;        // Maske zum setzen der IOs am PWM Start
80
        j <<= 1;
81
    }
82
    main_ptr_mask[0]=tmp;                           // PWM Start Daten 
83
 
84
    // PWM settings sortieren; Einfügesortieren
85
 
86
    for(i=1; i<=PWM_CHANNELS; i++) {
87
        min=255;
88
        k=i;
89
        for(j=i; j<=PWM_CHANNELS; j++) {
90
            if (pwm_setting_tmp[j]<min) {
91
                k=j;                                // Index und PWM-setting merken
92
                min = pwm_setting_tmp[j];
93
            }
94
        }
95
        if (k!=i) {
96
            // ermitteltes Minimum mit aktueller Sortiertstelle tauschen
97
            tmp = pwm_setting_tmp[k];
98
            pwm_setting_tmp[k] = pwm_setting_tmp[i];
99
            pwm_setting_tmp[i] = tmp;
100
            tmp = main_ptr_mask[k];
101
            main_ptr_mask[k] = main_ptr_mask[i];
102
            main_ptr_mask[i] = tmp;
103
        }
104
    }
105
 
106
    // Gleiche PWM-Werte vereinigen, ebenso den PWM-Wert 0 löschen falls vorhanden
107
 
108
    k=PWM_CHANNELS;             // PWM_CHANNELS Datensätze
109
    i=1;                        // Startindex
110
 
111
    while(k>i) {
112
        while ( ((pwm_setting_tmp[i]==pwm_setting_tmp[i+1]) || (pwm_setting_tmp[i]==0))  && (k>i) ) {
113
 
114
            // aufeinanderfolgende Werte sind gleich und können vereinigt werden
115
            // oder PWM Wert ist Null
116
            if (pwm_setting_tmp[i]!=0)
117
                main_ptr_mask[i+1] &= main_ptr_mask[i];        // Masken vereinigen
118
 
119
            // Datensatz entfernen,
120
            // Nachfolger alle eine Stufe hochschieben
121
            for(j=i; j<k; j++) {
122
                pwm_setting_tmp[j] = pwm_setting_tmp[j+1];
123
                main_ptr_mask[j] = main_ptr_mask[j+1];
124
            }
125
            k--;
126
        }
127
        i++;
128
    }
129
    
130
    // letzten Datensatz extra behandeln
131
    // Vergleich mit dem Nachfolger nicht möglich, nur löschen
132
    // gilt nur im Sonderfall, wenn alle Kanäle 0 sind
133
    if (pwm_setting_tmp[i]==0) k--;
134
 
135
    // Zeitdifferenzen berechnen
136
    
137
    if (k==0) { // Sonderfall, wenn alle Kanäle 0 sind
138
        main_ptr_time[0]=(uint16_t)T_PWM*PWM_STEPS/2;
139
        main_ptr_time[1]=(uint16_t)T_PWM*PWM_STEPS/2;
140
        k=1;
141
    }
142
    else {
143
        i=k;
144
        main_ptr_time[i]=(uint16_t)T_PWM*(PWM_STEPS-pwm_setting_tmp[i]);
145
        j=pwm_setting_tmp[i];
146
        i--;
147
        for (; i>0; i--) {
148
            main_ptr_time[i]=(uint16_t)T_PWM*(j-pwm_setting_tmp[i]);
149
            j=pwm_setting_tmp[i];
150
        }
151
        main_ptr_time[0]=(uint16_t)T_PWM*j;
152
    }
153
 
154
    // auf Sync warten
155
 
156
    pwm_sync=0;             // Sync wird im Interrupt gesetzt
157
    while(pwm_sync==0);
158
 
159
    // Zeiger tauschen
160
    cli();
161
    tausche_zeiger();
162
    pwm_cnt_max = k;
163
    sei();
164
}
165
 
166
// Timer 1 Output COMPARE A Interrupt
167
 
168
ISR(TIMER1_COMPA_vect) {
169
    static uint8_t pwm_cnt;
170
    uint8_t tmp;
171
 
172
    OCR1A += isr_ptr_time[pwm_cnt];
173
    tmp    = isr_ptr_mask[pwm_cnt];
174
    
175
    if (pwm_cnt == 0) {
176
        PWM_PORT = tmp;                         // Ports setzen zu Begin der PWM
177
        pwm_cnt++;
178
    }
179
    else {
180
        PWM_PORT &= tmp;                        // Ports löschen
181
        if (pwm_cnt == pwm_cnt_max) {
182
            pwm_sync = 1;                       // Update jetzt möglich
183
            pwm_cnt  = 0;
184
        }
185
        else pwm_cnt++;
186
    }
187
}
188
 
189
int main(void) {
190
 
191
    PWM_DDR = 0xFF;         // Port als Ausgang
192
    
193
    // Timer 1 OCRA1, als variablem Timer nutzen
194
    TCCR1B = 2;             // Timer läuft mit Prescaler 8
195
    TIMSK |= (1<<OCIE1A);   // Interrupt freischalten
196
    sei();                  // Interrupts gloabl einschalten
197
 
198
  int i1,i2,i3,i4,i5,i6,i7,i8;
199
200
    //for (x=1,x=PWM_CHANNELS,x++)
201
  while (1)
202
  {
203
204
    if (i1 > rand()%235)
205
  {
206
    i1 = i1 - rand()%20;
207
  }
208
  else
209
  {
210
      i1 = i1 + rand()%20;
211
  };    
212
  
213
  if (i2 > rand()%235)
214
  {
215
    i2 = i2 - rand()%20;
216
  }
217
  else
218
  {
219
      i2 = i2 + rand()%20;
220
  };
221
  
222
  if (i3 > rand()%235)
223
  {
224
    i3 = i3 - rand()%20;
225
  }
226
  else
227
  {
228
      i3 = i3 + rand()%20;
229
  };
230
231
  if (i4 > rand()%235)
232
  {
233
    i4 = i4 - rand()%20;
234
  }
235
  else
236
  {
237
      i4 = i4 + rand()%20;
238
  };
239
  
240
  if (i5 > rand()%235)
241
  {
242
    i5 = i5 - rand()%20;
243
  }
244
  else
245
  {
246
      i5 = i5 + rand()%20;
247
  };
248
  
249
  if (i6 > rand()%235)
250
  {
251
    i6 = i6 - rand()%20;
252
  }
253
  else
254
  {
255
      i6 = i6 + rand()%20;
256
  };
257
  
258
  if (i7 > rand()%235)
259
  {
260
    i7 = i7 - rand()%20;
261
  }
262
  else
263
  {
264
      i7 = i7 + rand()%20;
265
  };
266
  
267
  if (i8 > rand()%235)
268
  {
269
    i8 = i8 - rand()%20;
270
  }
271
  else
272
  {
273
      i8 = i8 + rand()%20;
274
  };
275
276
277
278
279
  const uint8_t t1[8] = {i1, i2, i3, i4, i5, i6, i7, i8};
280
    memcpy(pwm_setting, t1, 8);
281
    pwm_update();
282
  _delay_ms(rand()%50+50);
283
284
285
  };
286
287
    return 0;
288
}

von Falk B. (falk)


Lesenswert?

@ Orikson (Gast)

>Allerdings würde ich gerne noch eine Kleinigkeit in meinem Code
>verändern. Ich habe unten 8 mal if-Abragen mit welcher ich die
>Zufallszahlen für die Helligkeit der LEDs erzeuge. Das ganze

Kleiner Tip: Netiquette.
Lange Quelltexte bitte als Anhang.

>funktioniert zwar, ist aber nicht sonderbar schön und wenn man einen
>Wert ändern will muss man das 8 mal machen... Ich hatte da an eine
>for-Schleife gedacht (for (x=1,x=PWM_CHANNELS,x++)), allerdings weiß ich
>nicht wie ich das in den if-Abfragen dann umschreiben müsste. Die Zahlen
>hinter i müssten dann aus der Variable x erzeugt werden, aber ix
>anstelle von i1, i2 etc. geht nicht! Wie kann ich das noch lösen?

Wie du schon richtig sagtest, mit einer Schleife. Und einem Array.
1
my_delay(uint16_t ms) {
2
  for(; ms>0; ms--) _delay_ms(1);
3
}
4
5
int main(void) {
6
 
7
    PWM_DDR = 0xFF;         // Port als Ausgang
8
    
9
    // Timer 1 OCRA1, als variablem Timer nutzen
10
    TCCR1B = 2;             // Timer läuft mit Prescaler 8
11
    TIMSK |= (1<<OCIE1A);   // Interrupt freischalten
12
    sei();                  // Interrupts gloabl einschalten
13
 
14
  uint8_t zufall[8];
15
  uint8_t i;
16
17
  while (1) {
18
     for (i=0; x<PWM_CHANNELS; i++) {
19
       if (zufall[i] > rand()%235) {
20
         zufall[i] -= rand()%20;
21
       }
22
       else {
23
         zufall[i] += rand()%20;
24
       };
25
     }
26
     memcpy(pwm_setting, zufall, 8);
27
     pwm_update();
28
     my_delay(rand()%50+50);
29
  };
30
31
    return 0;
32
}


>Außerdem muss ich gestehen, dass ich nicht ganz weiß, wie die Ausgabe
>mit diesem Soft-PWM Code gedacht ist. Der Code der Testanwendung
>funktioniert aber ganz gut ;-P

Du solltest dich mal mit ein paar Grundlagen befassen, Arrays und so. 
Und der _delay_ms() Funktion, die mag keine variablen Parameter.

http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial#Warteschleifen_.28delay.h.29

MFG
Falk

von Orikson (Gast)


Lesenswert?

Habe mir jetz mal die Schaltung mit einem Tiny2313 aufgebaut 
(http://pic.leech.it/i/3a475/bb60de9bild1.png), aber leider noch ein 
kleines Problem...

Zwar flackert die LED (hab erst 2 Widerstände und LEDs dran, der Rest 
kommt erst noch) schon, aber die geht sehr oft auch komplett aus und 
wieder an, auch mehrmals hinter einander! Der Tiny2313 läuft mit 
internen 4 Mhz, das erhöhen auf 8 MHz bringt aber auch keinen 
Unterschied, außer dass alles schneller abläuft, dass die LED aus ist 
sieht man aber trotzdem noch.

Woran kann das liegen? Programierkabel ist abgezogen, Spannung passt und 
Kurzschluss oder so hab ich auch nicht. Habe auch schon im Programm die 
PWM Kanäle runter, die PWM-Frequenz hoch und runter und den Prescaler 
verändert, alles ohne Abhilfe...

von Falk B. (falk)


Lesenswert?

@  Orikson (Gast)

>(http://pic.leech.it/i/3a475/bb60de9bild1.png), aber leider noch ein
>kleines Problem...

Das sollte auf jeden Fall ein 100nF NAH an den AVR. 100nF am Reset 
schaden auch nicht, sind aber nicht zwingend.

MFG
Falk

von Orikson (Gast)


Lesenswert?

@Falk Brunner: Nah genug ;-P
http://pic.leech.it/i/1412e/876a671fimgp0798.jpg

Ne, das war es nicht. Kann das an der Software liegen oder ist das n 
Hardwareproblem?

von Chris W. (squid1356)


Lesenswert?

die Platine sieht sehr "unsauber" aus.

erstens sind die tonerreste (war doch direkttonermethode ?!) nicht 
sorgfältig entfernt, und es sind auf den ersten blick ein paar unsaubere 
lötstellen zu erkennen.

besonders die dritte von oben rechts sieht "kalt" aus. am besten nochmal 
alles mit aceton säubern, evtl. fluxen und nachlöten.
an der schaltung scheint es sonst nicht zu liegen.

software habe ich noch nicht angesehen...

von Orikson (Gast)


Lesenswert?

Die Platine ist ok, habs auch nochmal überprüft und einige Sachen noch 
weggekratzt. Allerdings hat das nicht wirklich geholfen.

Dafür konnte ich den Fehler im Code finden, verstehen tu ich ihn aber 
nicht...

Der Fehler tritt auf, sobald ein zufälliger Wert an die Funktion 
my_delay übergeben wird. Wenn man ihn fest auf einen Wert über 30 setzt 
tritt das Problem auch auf. Wenn man ihn fest auf einen Wert unter 30 
setzt geht es ohne blinken! Wenn man den Wert aber per Zufall unterhalb 
von 30 erzeugt blinkt es wieder!

Das selbe tritt auch auf, wenn ich direkt _delay_ms verwende oder ein 
void vor der my_delay Funktion ergänze (an dem meckert er eh rum).

Wodurch kann der Fehler also verursacht werden? Vor allem läuft das ja 
z.B. im Mega8!

von Herr Jemine (Gast)


Lesenswert?

Du nimmst aber viel Lötzinn! Man braucht eigentlich gar kein Aceton, ich 
bürste den Toner mit einem Stahlwolle-Topfreiniger ab. Hoffentlich wird 
das im Klärwerk irgendwie rausgeholt.

von Chris W. (squid1356)


Lesenswert?

Herr Jemine wrote:
> Du nimmst aber viel Lötzinn! Man braucht eigentlich gar kein Aceton, ich
> bürste den Toner mit einem Stahlwolle-Topfreiniger ab. Hoffentlich wird
> das im Klärwerk irgendwie rausgeholt.

aceton hinterlässt halt nicht die hässlichen kratzer im kupfer. finde 
ich sauberer und ist bei feinen leitungen sicherer.

von Orikson (Gast)


Lesenswert?

@Herr Jemine: Jein, ich habe nur keinen 1 mm Bohrer (darüh 0,8mm) für 
größere Löcher. Deshalb nimm ich zur Zeit noch so nen Spitzen Fräser 
fürn Dremel, allerdings werden dann manche Löcher hald etwas größere. 
Und damit ich vernünftig Kontakt habe kommt da ordentlich Lötzinn drauf 
hust

Die Tonervariante ist eigentlich nicht mein Ding, hab die jetz nur mal 
ausprobiert, weil ich so miese und billige Fotoplatinen gekauft habe, 
die sich beschi**** belichten lassen...

Keiner ne Ahnung, was das mit dem Fehler sein könnte?

von martin (Gast)


Lesenswert?

hallo ich habe letzte winter mir ein LED Lagerfeuer und Schweißlicht 
selber gebaut...
Es sollte so klein wie möglich sein und am ende kam eine kleine Platine 
raus,
mit der man 8 Leds unterschiedlich flackern lassen kann... sieht aus wie 
echt.

Abmaße der Platine sind ca 3x4cm

Die daten findet ihr unter folgendem Link: 
http://bluematrixi.bl.funpic.de/html/meinwebzeugs/index.php/elektronik-avr/kerzen-flackern

von R. M. (rmax)


Lesenswert?

martin schrieb:

> Die daten findet ihr unter folgendem Link:
> 
http://bluematrixi.bl.funpic.de/html/meinwebzeugs/index.php/elektronik-avr/kerzen-flackern

Schön wär's aber da kommt leider nur 404.

von martin (Gast)


Lesenswert?

sorry hatte noch ne kleine Änderung in der Seite...
der Link ist: 
http://bluematrixi.bl.funpic.de/index.php/elektronik-avr/avr-led-kerzen

oder eben nur die Seite und dann im Menu finden: 
http://bluematrixi.bl.funpic.de

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.