]> git.zerfleddert.de Git - fnordlicht-mini/blob - firmware/fnordlicht-firmware/pwm.c
reference firmware
[fnordlicht-mini] / firmware / fnordlicht-firmware / pwm.c
1 /* vim:ts=4 sts=4 et tw=80
2 *
3 * fnordlicht firmware
4 *
5 * for additional information please
6 * see http://lochraster.org/fnordlichtmini
7 *
8 * (c) by Alexander Neumann <alexander@bumpern.de>
9 * Lars Noschinski <lars@public.noschinski.de>
10 *
11 * This program is free software: you can redistribute it and/or modify it
12 * under the terms of the GNU General Public License version 3 as published by
13 * the Free Software Foundation.
14 *
15 * This program is distributed in the hope that it will be useful, but WITHOUT
16 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
17 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
18 * more details.
19 *
20 * You should have received a copy of the GNU General Public License along with
21 * this program. If not, see <http://www.gnu.org/licenses/>.
22 */
23
24 /* includes */
25 #include "globals.h"
26 #include "../common/io.h"
27
28 #include <stdint.h>
29 #include <string.h>
30 #include <avr/interrupt.h>
31 #include <avr/pgmspace.h>
32
33 #include "../common/common.h"
34 #include "pwm.h"
35 #include "timer.h"
36 #include "remote.h"
37
38 /* abbreviations for port, ddr and pin */
39 #define P_PORT _OUTPORT(PWM_PORT)
40 #define P_DDR _DDRPORT(PWM_PORT)
41 #define P2_PORT _OUTPORT(PWM2_PORT)
42 #define P2_DDR _DDRPORT(PWM2_PORT)
43
44 /* TYPES AND PROTOTYPES */
45 #define PWM_MAX_TIMESLOTS (2*(PWM_CHANNELS+2))
46
47 /* encapsulates all pwm data including timeslot and output mask array */
48 struct timeslot_t
49 {
50 uint8_t mask;
51 uint16_t top;
52 };
53
54 struct timeslots_t
55 {
56 /* store timslots in a queue */
57 struct timeslot_t slot[PWM_MAX_TIMESLOTS];
58
59 uint8_t read; /* current read head in 'slot' array */
60 uint8_t write; /* current write head in 'slot' array */
61 };
62
63 /* internal data for the fading engine */
64 struct fading_engine_t
65 {
66 /* a timer for each channel */
67 timer_t timer[PWM_CHANNELS];
68
69 /* and a bitmask, which stands for 'timer is running' */
70 uint8_t running;
71 };
72
73 /* timer top values for 256 brightness levels (stored in flash) */
74 static const uint16_t timeslot_table[] PROGMEM =
75 {
76 2, 8, 18, 31, 49, 71, 96, 126,
77 159, 197, 238, 283, 333, 386, 443, 504,
78 569, 638, 711, 787, 868, 953, 1041, 1134,
79 1230, 1331, 1435, 1543, 1655, 1772, 1892, 2016,
80 2144, 2276, 2411, 2551, 2695, 2842, 2994, 3150,
81 3309, 3472, 3640, 3811, 3986, 4165, 4348, 4535,
82 4726, 4921, 5120, 5323, 5529, 5740, 5955, 6173,
83 6396, 6622, 6852, 7087, 7325, 7567, 7813, 8063,
84 8317, 8575, 8836, 9102, 9372, 9646, 9923, 10205,
85 10490, 10779, 11073, 11370, 11671, 11976, 12285, 12598,
86 12915, 13236, 13561, 13890, 14222, 14559, 14899, 15244,
87 15592, 15945, 16301, 16661, 17025, 17393, 17765, 18141,
88 18521, 18905, 19293, 19685, 20080, 20480, 20884, 21291,
89 21702, 22118, 22537, 22960, 23387, 23819, 24254, 24693,
90 25135, 25582, 26033, 26488, 26946, 27409, 27876, 28346,
91 28820, 29299, 29781, 30267, 30757, 31251, 31750, 32251,
92 32757, 33267, 33781, 34299, 34820, 35346, 35875, 36409,
93 36946, 37488, 38033, 38582, 39135, 39692, 40253, 40818,
94 41387, 41960, 42537, 43117, 43702, 44291, 44883, 45480,
95 46080, 46684, 47293, 47905, 48521, 49141, 49765, 50393,
96 51025, 51661, 52300, 52944, 53592, 54243, 54899, 55558,
97 56222, 56889, 57560, 58235, 58914, 59598, 60285, 60975,
98 61670, 62369, 63072, 63779, 489, 1204, 1922, 2645,
99 3371, 4101, 4836, 5574, 6316, 7062, 7812, 8566,
100 9324, 10085, 10851, 11621, 12394, 13172, 13954, 14739,
101 15528, 16322, 17119, 17920, 18725, 19534, 20347, 21164,
102 21985, 22810, 23638, 24471, 25308, 26148, 26993, 27841,
103 28693, 29550, 30410, 31274, 32142, 33014, 33890, 34770,
104 35654, 36542, 37433, 38329, 39229, 40132, 41040, 41951,
105 42866, 43786, 44709, 45636, 46567, 47502, 48441, 49384,
106 50331, 51282, 52236, 53195, 54158, 55124, 56095, 57069,
107 58047, 59030, 60016, 61006, 62000, 62998 };
108
109 /* GLOBAL VARIABLES */
110 struct global_pwm_t global_pwm;
111 static struct timeslots_t timeslots;
112 static struct fading_engine_t fading;
113
114 /* next output bitmask */
115 static volatile uint8_t pwm_next_bitmask;
116
117 /* FUNCTIONS AND INTERRUPTS */
118 /* prototypes */
119 void update_pwm_timeslots(struct rgb_color_t *target);
120 void update_rgb(uint8_t c);
121 void enqueue_timeslot(uint8_t mask, uint16_t top);
122 void dequeue_timeslot(struct timeslot_t *d);
123 void update_last_timeslot(uint8_t mask);
124 uint8_t timeslots_fill(void);
125
126 /* initialize pwm hardware and structures */
127 void pwm_init(void)
128 {
129 /* init output pins */
130
131 #ifdef PWM_INVERTED
132 /* set all pins high -> leds off */
133 P_PORT |= PWM_CHANNEL_MASK;
134 #else
135 /* set all pins low -> leds off */
136 P_PORT &= ~(PWM_CHANNEL_MASK);
137 #endif
138
139 #if CONFIG_SECONDARY_PWM
140 #ifdef PWM_INVERTED
141 /* set all pins high -> leds off */
142 P2_PORT |= PWM2_CHANNEL_MASK;
143 #else
144 /* set all pins low -> leds off */
145 P2_PORT &= ~(PWM2_CHANNEL_MASK);
146 #endif
147 #endif
148
149 /* configure pins as outputs */
150 P_DDR |= PWM_CHANNEL_MASK;
151
152 #if CONFIG_SECONDARY_PWM
153 P2_DDR |= PWM2_CHANNEL_MASK;
154 #endif
155
156 /* initialize timer 1 */
157
158 /* no prescaler, CTC mode */
159 TCCR1B = _BV(CS10) | _BV(WGM12);
160
161 /* enable timer1 overflow (=output compare 1a)
162 * and output compare 1b interrupt */
163 _TIMSK_TIMER1 |= _BV(OCIE1A) | _BV(OCIE1B);
164
165 /* set TOP for CTC mode */
166 OCR1A = 64000;
167
168 /* load initial delay, trigger an overflow */
169 OCR1B = 65000;
170
171 /* reset structures */
172 for (uint8_t i = 0; i < PWM_CHANNELS; i++) {
173 global_pwm.fade_delay[i] = 1;
174 global_pwm.fade_step[i] = 1;
175 }
176
177 /* calculate initial timeslots (2 times) */
178 update_pwm_timeslots(&global_pwm.current);
179 update_pwm_timeslots(&global_pwm.current);
180
181 /* set initial timeslot */
182 struct timeslot_t t;
183 dequeue_timeslot(&t);
184 pwm_next_bitmask = t.mask;
185
186 /* disable fading timers */
187 fading.running = 0;
188 }
189
190 /* prepare new timeslots */
191 void pwm_poll(void)
192 {
193 /* refill timeslots queue */
194 while (timeslots_fill() < (PWM_MAX_TIMESLOTS-PWM_CHANNELS-2))
195 update_pwm_timeslots(&global_pwm.current);
196 }
197
198 /* update color values for current fading */
199 void pwm_poll_fading(void)
200 {
201 uint8_t mask = 1;
202 for (uint8_t i = 0; i < PWM_CHANNELS; i++) {
203 /* check running timers */
204 if ( (fading.running & mask) && timer_expired(&fading.timer[i])) {
205 update_rgb(i);
206 fading.running &= ~mask;
207 }
208
209 /* if timer is not running and current != target, start timer */
210 if (!(fading.running & mask)
211 && global_pwm.current.rgb[i] != global_pwm.target.rgb.rgb[i]
212 && global_pwm.fade_delay[i] > 0) {
213 timer_set(&fading.timer[i], global_pwm.fade_delay[i]);
214 fading.running |= mask;
215 }
216
217 /* shift mask */
218 mask <<= 1;
219 }
220 }
221
222 /** update pwm timeslot table (for target color) */
223 void update_pwm_timeslots(struct rgb_color_t *target)
224 {
225 static uint8_t sorted[PWM_CHANNELS] = {0, 1, 2};
226
227 /* sort channels according to the current brightness */
228 for (uint8_t i = 0; i < PWM_CHANNELS; i++) {
229 for (uint8_t j = i+1; j < PWM_CHANNELS; j++) {
230 if (target->rgb[sorted[j]] < target->rgb[sorted[i]]) {
231 uint8_t temp;
232
233 temp = sorted[i];
234 sorted[i] = sorted[j];
235 sorted[j] = temp;
236 }
237 }
238 }
239
240 /* calculate initial bitmask */
241 uint8_t initial_bitmask;
242 #ifdef PWM_INVERTED
243 initial_bitmask = PWM_CHANNEL_MASK;
244 #else
245 initial_bitmask = 0;
246 #endif
247
248 uint8_t chanmask = 1;
249 for (uint8_t i = 0; i < PWM_CHANNELS; i++) {
250 if (target->rgb[i] > PWM_MIN_BRIGHTNESS) {
251 #ifdef PWM_INVERTED
252 initial_bitmask &= ~chanmask;
253 #else
254 initial_bitmask |= chanmask;
255 #endif
256 }
257
258 chanmask <<= 1;
259 }
260
261 /* insert first timeslot */
262 enqueue_timeslot(initial_bitmask, 65000);
263
264 /* calculate timeslots and masks */
265 uint8_t mask = initial_bitmask;
266 uint8_t last_brightness = 0;
267 for (uint8_t i = 0; i < PWM_CHANNELS; i++) {
268 uint8_t brightness = target->rgb[sorted[i]];
269
270 /* if color is (nearly off) or max, process next color */
271 if (brightness <= PWM_MIN_BRIGHTNESS || brightness == 255)
272 continue;
273
274 /* check if current timeslot would happen after the middle interrupt */
275 if (last_brightness < 181 && brightness >= 181) {
276 /* insert (normal) middle interrupt */
277 enqueue_timeslot(mask, 65000);
278 }
279
280 /* if brightness is new, allocate a new timeslot */
281 if (brightness > last_brightness) {
282
283 /* update mask and last_brightness */
284 last_brightness = brightness;
285 #ifdef PWM_INVERTED
286 mask |= _BV(sorted[i]);
287 #else
288 mask &= ~_BV(sorted[i]);
289 #endif
290
291 /* save (normal) timeslot */
292 uint16_t top = pgm_read_word(&timeslot_table[target->rgb[sorted[i]] - 1 ]);
293 enqueue_timeslot(mask, top);
294 } else {
295 /* just update mask of last timeslot */
296 #ifdef PWM_INVERTED
297 mask |= _BV(sorted[i]);
298 #else
299 mask &= ~_BV(sorted[i]);
300 #endif
301
302 update_last_timeslot(mask);
303 }
304 }
305
306 /* if all interrupts happen before the middle interrupt, insert it here */
307 if (last_brightness < 181)
308 enqueue_timeslot(mask, 65000);
309 }
310
311 /** fade any channels not already at their target brightness */
312 void update_rgb(uint8_t c)
313 {
314 /* return if target reached */
315 if (global_pwm.current.rgb[c] == global_pwm.target.rgb.rgb[c])
316 return;
317
318 /* check direction */
319 if (global_pwm.current.rgb[c] < global_pwm.target.rgb.rgb[c]) {
320 uint8_t diff = global_pwm.target.rgb.rgb[c] - global_pwm.current.rgb[c];
321
322 if (diff >= global_pwm.fade_step[c])
323 global_pwm.current.rgb[c] += global_pwm.fade_step[c];
324 else
325 global_pwm.current.rgb[c] += diff;
326
327 } else {
328 uint8_t diff = global_pwm.current.rgb[c] - global_pwm.target.rgb.rgb[c];
329
330 if (diff >= global_pwm.fade_step[c])
331 global_pwm.current.rgb[c] -= global_pwm.fade_step[c];
332 else
333 global_pwm.current.rgb[c] -= diff;
334 }
335 }
336
337 /* timeslot queue handling */
338 void enqueue_timeslot(uint8_t mask, uint16_t top)
339 {
340 struct timeslot_t *t = &timeslots.slot[timeslots.write];
341 t->mask = mask;
342 t->top = top;
343 timeslots.write = (timeslots.write + 1) % PWM_MAX_TIMESLOTS;
344 }
345
346 void dequeue_timeslot(struct timeslot_t *d)
347 {
348 struct timeslot_t *t = &timeslots.slot[timeslots.read];
349 d->mask = t->mask;
350 d->top = t->top;
351 timeslots.read = (timeslots.read + 1) % PWM_MAX_TIMESLOTS;
352 }
353
354 void update_last_timeslot(uint8_t mask)
355 {
356 uint8_t i;
357 if (timeslots.write > 0)
358 i = timeslots.write-1;
359 else
360 i = PWM_MAX_TIMESLOTS-1;
361
362 timeslots.slot[i].mask = mask;
363 }
364
365 uint8_t timeslots_fill(void)
366 {
367 if (timeslots.write >= timeslots.read)
368 return timeslots.write - timeslots.read;
369 else
370 return PWM_MAX_TIMESLOTS - (timeslots.read - timeslots.write);
371 }
372
373 /* convert hsv to rgb color
374 * (see http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_HSV_to_RGB )
375 * and
376 * http://www.enide.net/webcms/uploads/file/projects/powerpicrgb-irda/hsvspace.pdf
377 */
378 void pwm_hsv2rgb(struct dual_color_t *color)
379 {
380 if (color->hsv.saturation == 0) {
381 for (uint8_t i = 0; i < PWM_CHANNELS; i++)
382 color->rgb.rgb[i] = color->hsv.value;
383 return;
384 }
385
386 uint16_t h = color->hsv.hue % 360;
387 uint8_t s = color->hsv.saturation;
388 uint8_t v = color->hsv.value;
389
390 uint16_t f = ((h % 60) * 255 + 30)/60;
391 uint16_t p = (v * (255-s)+128)/255;
392 uint16_t q = ((v * (255 - (s*f+128)/255))+128)/255;
393 uint16_t t = (v * (255 - ((s * (255 - f))/255)))/255;
394
395 uint8_t i = h/60;
396
397 switch (i) {
398 case 0:
399 color->rgb.rgb[0] = v;
400 color->rgb.rgb[1] = t;
401 color->rgb.rgb[2] = p;
402 break;
403 case 1:
404 color->rgb.rgb[0] = q;
405 color->rgb.rgb[1] = v;
406 color->rgb.rgb[2] = p;
407 break;
408 case 2:
409 color->rgb.rgb[0] = p;
410 color->rgb.rgb[1] = v;
411 color->rgb.rgb[2] = t;
412 break;
413 case 3:
414 color->rgb.rgb[0] = p;
415 color->rgb.rgb[1] = q;
416 color->rgb.rgb[2] = v;
417 break;
418 case 4:
419 color->rgb.rgb[0] = t;
420 color->rgb.rgb[1] = p;
421 color->rgb.rgb[2] = v;
422 break;
423 case 5:
424 color->rgb.rgb[0] = v;
425 color->rgb.rgb[1] = p;
426 color->rgb.rgb[2] = q;
427 break;
428 }
429 }
430
431 /* convert rgb to hsv color
432 * (see http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV )
433 * and
434 * http://www.enide.net/webcms/uploads/file/projects/powerpicrgb-irda/hsvspace.pdf
435 */
436 void pwm_rgb2hsv(struct dual_color_t *color)
437 {
438 /* search min and max */
439 uint8_t max = color->rgb.red;
440 uint8_t min = max;
441
442 if (color->rgb.green > max)
443 max = color->rgb.green;
444 if (color->rgb.blue > max)
445 max = color->rgb.blue;
446
447 if (color->rgb.green < min)
448 min = color->rgb.green;
449 if (color->rgb.blue < min)
450 min = color->rgb.blue;
451
452 uint16_t hue = 0;
453 uint8_t diff = max - min;
454 uint8_t diffh = diff/2;
455
456 /* compute value and saturation */
457 color->hsv.value = max;
458 color->hsv.saturation = 0;
459
460 if (max > 0)
461 color->hsv.saturation = ((255 * diff)+max/2)/max;
462 else {
463 color->hsv.saturation = 0;
464 color->hsv.hue = 0; /* undefined */
465 return;
466 }
467
468 if (max == min) {
469 hue = 0;
470 } else if (max == color->rgb.red) {
471 hue = (60 * (color->rgb.green - color->rgb.blue) + diffh)/diff + 360;
472 } else if (max == color->rgb.green) {
473 hue = (60 * (color->rgb.blue - color->rgb.red) + diffh)/diff + 120;
474 } else if (max == color->rgb.blue) {
475 hue = (60 * (color->rgb.red - color->rgb.green) + diffh)/diff + 240;
476 }
477
478 hue = hue % 360;
479
480 color->hsv.hue = hue;
481 }
482
483 /* stop fading, hold current color */
484 void pwm_stop_fading(void)
485 {
486 memcpy(&global_pwm.target.rgb, &global_pwm.current.rgb, sizeof(struct rgb_color_t));
487
488 /* ignore all timers */
489 fading.running = 0;
490 }
491
492
493 static uint8_t diff_abs(uint8_t a, uint8_t b)
494 {
495 if (a > b)
496 return a-b;
497 else
498 return b-a;
499 }
500
501 static void compute_speed(uint8_t step, uint8_t delay)
502 {
503 /* search for max distance */
504 uint8_t max = 0;
505 uint8_t dist = 0;
506
507 for (uint8_t i = 0; i < PWM_CHANNELS; i++) {
508 uint8_t d = diff_abs(global_pwm.target.rgb.rgb[i], global_pwm.current.rgb[i]);
509
510 if (d > dist) {
511 max = i;
512 dist = d;
513 }
514 }
515
516 /* adjust fading speeds, relative to max distance */
517 global_pwm.fade_step[max] = step;
518 global_pwm.fade_delay[max] = delay;
519
520 for (uint8_t i = 0; i < PWM_CHANNELS; i++) {
521 if (i == max)
522 continue;
523
524 uint8_t d = diff_abs(global_pwm.target.rgb.rgb[i], global_pwm.current.rgb[i]);
525
526 uint8_t ratio = 1;
527 if (d > 0)
528 ratio = (dist+d/2)/d;
529
530 if (ratio == 0)
531 ratio = 1;
532
533 global_pwm.fade_delay[i] = ratio * delay;
534 global_pwm.fade_step[i] = step;
535 }
536 }
537
538 void pwm_fade_rgb(struct rgb_color_t *color, uint8_t step, uint8_t delay)
539 {
540 /* apply offsets for step and delay */
541 step = remote_apply_offset(step, global_remote.offsets.step);
542 delay = remote_apply_offset(delay, global_remote.offsets.delay);
543
544 /* set target color */
545 for (uint8_t i = 0; i < PWM_CHANNELS; i++)
546 global_pwm.target.rgb.rgb[i] = color->rgb[i];
547
548 /* compute correct speed for all channels */
549 if (delay == 0)
550 delay = 1;
551 compute_speed(step, delay);
552
553 /* disable timer */
554 fading.running = 0;
555 }
556
557 void pwm_fade_hsv(struct hsv_color_t *color, uint8_t step, uint8_t delay)
558 {
559 /* apply offsets for step and delay */
560 step = remote_apply_offset(step, global_remote.offsets.step);
561 delay = remote_apply_offset(delay, global_remote.offsets.delay);
562
563 /* convert color */
564 memcpy(&global_pwm.target.hsv, color, sizeof(struct hsv_color_t));
565
566 /* apply offsets */
567 remote_apply_hsv_offset(&global_pwm.target.hsv);
568
569 /* update rgb color in target */
570 pwm_hsv2rgb(&global_pwm.target);
571
572 /* compute correct speed for all channels */
573 compute_speed(step, delay);
574
575 /* disable timer */
576 fading.running = 0;
577 }
578
579 bool pwm_target_reached(void)
580 {
581 for (uint8_t i = 0; i < PWM_CHANNELS; i++) {
582 if (global_pwm.target.rgb.rgb[i] != global_pwm.current.rgb[i])
583 return false;
584 }
585
586 return true;
587 }
588
589 /* modify current color */
590 void pwm_modify_rgb(struct rgb_color_offset_t *color, uint8_t step, uint8_t delay)
591 {
592 for (uint8_t i = 0; i < PWM_CHANNELS; i++) {
593 int16_t current = global_pwm.target.rgb.rgb[i];
594 current += color->rgb[i];
595
596 if (current > 255)
597 current = 255;
598 if (current < 0)
599 current = 0;
600
601 global_pwm.target.rgb.rgb[i] = LO8(current);
602 }
603
604 compute_speed(step, delay);
605
606 /* disable timer */
607 fading.running = 0;
608 }
609
610 void pwm_modify_hsv(struct hsv_color_offset_t *color, uint8_t step, uint8_t delay)
611 {
612 /* convert current target color from rgb to hsv */
613 pwm_rgb2hsv(&global_pwm.target);
614
615 /* apply changes, hue */
616 global_pwm.target.hsv.hue += color->hue;
617
618 /* saturation */
619 int16_t sat = global_pwm.target.hsv.saturation;
620 sat += color->saturation;
621 if (sat > 255)
622 sat = 255;
623 if (sat < 0)
624 sat = 0;
625 global_pwm.target.hsv.saturation = LO8(sat);
626
627 /* value */
628 int16_t val = global_pwm.target.hsv.value;
629 val += color->value;
630 if (val > 255)
631 val = 255;
632 if (val < 0)
633 val = 0;
634 global_pwm.target.hsv.value = LO8(val);
635
636 /* re-convert to rgb */
637 pwm_hsv2rgb(&global_pwm.target);
638
639 /* compute correct speed for all channels */
640 compute_speed(step, delay);
641
642 /* disable timer */
643 fading.running = 0;
644 }
645
646
647 /** interrupts*/
648
649 /** timer1 overflow (=output compare a) interrupt */
650 ISR(TIMER1_COMPA_vect)
651 {
652 /* read next_bitmask */
653 uint8_t next_bitmask = pwm_next_bitmask;
654 /* output new values */
655 P_PORT = (P_PORT & ~(PWM_CHANNEL_MASK)) | (next_bitmask << PWM_SHIFT);
656 #if CONFIG_SECONDARY_PWM
657 P2_PORT = (P2_PORT & ~(PWM2_CHANNEL_MASK)) | (next_bitmask << PWM2_SHIFT);
658 #endif
659
660 /* prepare next interrupt */
661 struct timeslot_t t;
662 dequeue_timeslot(&t);
663
664 /* if next timeslot would happen too fast or has already happened, just spinlock */
665 while (TCNT1 + 100 > t.top)
666 {
667 /* spin until timer interrupt is near enough */
668 while (t.top > TCNT1);
669
670 /* output new values */
671 P_PORT = (P_PORT & ~(PWM_CHANNEL_MASK)) | (t.mask << PWM_SHIFT);
672 #if CONFIG_SECONDARY_PWM
673 P2_PORT = (P2_PORT & ~(PWM2_CHANNEL_MASK)) | (t.mask << PWM2_SHIFT);
674 #endif
675
676 /* load next timeslot */
677 dequeue_timeslot(&t);
678 }
679
680 /* save values for next interrupt */
681 OCR1B = t.top;
682 pwm_next_bitmask = t.mask;
683 }
684
685 /** timer1 output compare b interrupt */
686 ISR(TIMER1_COMPB_vect, ISR_ALIASOF(TIMER1_COMPA_vect));
Impressum, Datenschutz