]>
git.zerfleddert.de Git - fnordlicht-mini/blob - firmware/fnordlicht-firmware/pwm.c
1 /* vim:ts=4 sts=4 et tw=80
5 * for additional information please
6 * see http://lochraster.org/fnordlichtmini
8 * (c) by Alexander Neumann <alexander@bumpern.de>
9 * Lars Noschinski <lars@public.noschinski.de>
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.
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
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/>.
26 #include "../common/io.h"
30 #include <avr/interrupt.h>
31 #include <avr/pgmspace.h>
33 #include "../common/common.h"
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)
44 /* TYPES AND PROTOTYPES */
45 #define PWM_MAX_TIMESLOTS (2*(PWM_CHANNELS+2))
47 /* encapsulates all pwm data including timeslot and output mask array */
56 /* store timslots in a queue */
57 struct timeslot_t slot
[PWM_MAX_TIMESLOTS
];
59 uint8_t read
; /* current read head in 'slot' array */
60 uint8_t write
; /* current write head in 'slot' array */
63 /* internal data for the fading engine */
64 struct fading_engine_t
66 /* a timer for each channel */
67 timer_t timer
[PWM_CHANNELS
];
69 /* and a bitmask, which stands for 'timer is running' */
73 /* timer top values for 256 brightness levels (stored in flash) */
74 static const uint16_t timeslot_table
[] PROGMEM
=
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 };
109 /* GLOBAL VARIABLES */
110 struct global_pwm_t global_pwm
;
111 static struct timeslots_t timeslots
;
112 static struct fading_engine_t fading
;
114 /* next output bitmask */
115 static volatile uint8_t pwm_next_bitmask
;
117 /* FUNCTIONS AND INTERRUPTS */
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);
126 /* initialize pwm hardware and structures */
129 /* init output pins */
132 /* set all pins high -> leds off */
133 P_PORT
|= PWM_CHANNEL_MASK
;
135 /* set all pins low -> leds off */
136 P_PORT
&= ~(PWM_CHANNEL_MASK
);
139 #if CONFIG_SECONDARY_PWM
141 /* set all pins high -> leds off */
142 P2_PORT
|= PWM2_CHANNEL_MASK
;
144 /* set all pins low -> leds off */
145 P2_PORT
&= ~(PWM2_CHANNEL_MASK
);
149 /* configure pins as outputs */
150 P_DDR
|= PWM_CHANNEL_MASK
;
152 #if CONFIG_SECONDARY_PWM
153 P2_DDR
|= PWM2_CHANNEL_MASK
;
156 /* initialize timer 1 */
158 /* no prescaler, CTC mode */
159 TCCR1B
= _BV(CS10
) | _BV(WGM12
);
161 /* enable timer1 overflow (=output compare 1a)
162 * and output compare 1b interrupt */
163 _TIMSK_TIMER1
|= _BV(OCIE1A
) | _BV(OCIE1B
);
165 /* set TOP for CTC mode */
168 /* load initial delay, trigger an overflow */
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;
177 /* calculate initial timeslots (2 times) */
178 update_pwm_timeslots(&global_pwm
.current
);
179 update_pwm_timeslots(&global_pwm
.current
);
181 /* set initial timeslot */
183 dequeue_timeslot(&t
);
184 pwm_next_bitmask
= t
.mask
;
186 /* disable fading timers */
190 /* prepare new timeslots */
193 /* refill timeslots queue */
194 while (timeslots_fill() < (PWM_MAX_TIMESLOTS
-PWM_CHANNELS
-2))
195 update_pwm_timeslots(&global_pwm
.current
);
198 /* update color values for current fading */
199 void pwm_poll_fading(void)
202 for (uint8_t i
= 0; i
< PWM_CHANNELS
; i
++) {
203 /* check running timers */
204 if ( (fading
.running
& mask
) && timer_expired(&fading
.timer
[i
])) {
206 fading
.running
&= ~mask
;
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
;
222 /** update pwm timeslot table (for target color) */
223 void update_pwm_timeslots(struct rgb_color_t
*target
)
225 static uint8_t sorted
[PWM_CHANNELS
] = {0, 1, 2};
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
]]) {
234 sorted
[i
] = sorted
[j
];
240 /* calculate initial bitmask */
241 uint8_t initial_bitmask
;
243 initial_bitmask
= PWM_CHANNEL_MASK
;
248 uint8_t chanmask
= 1;
249 for (uint8_t i
= 0; i
< PWM_CHANNELS
; i
++) {
250 if (target
->rgb
[i
] > PWM_MIN_BRIGHTNESS
) {
252 initial_bitmask
&= ~chanmask
;
254 initial_bitmask
|= chanmask
;
261 /* insert first timeslot */
262 enqueue_timeslot(initial_bitmask
, 65000);
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
]];
270 /* if color is (nearly off) or max, process next color */
271 if (brightness
<= PWM_MIN_BRIGHTNESS
|| brightness
== 255)
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);
280 /* if brightness is new, allocate a new timeslot */
281 if (brightness
> last_brightness
) {
283 /* update mask and last_brightness */
284 last_brightness
= brightness
;
286 mask
|= _BV(sorted
[i
]);
288 mask
&= ~_BV(sorted
[i
]);
291 /* save (normal) timeslot */
292 uint16_t top
= pgm_read_word(×lot_table
[target
->rgb
[sorted
[i
]] - 1 ]);
293 enqueue_timeslot(mask
, top
);
295 /* just update mask of last timeslot */
297 mask
|= _BV(sorted
[i
]);
299 mask
&= ~_BV(sorted
[i
]);
302 update_last_timeslot(mask
);
306 /* if all interrupts happen before the middle interrupt, insert it here */
307 if (last_brightness
< 181)
308 enqueue_timeslot(mask
, 65000);
311 /** fade any channels not already at their target brightness */
312 void update_rgb(uint8_t c
)
314 /* return if target reached */
315 if (global_pwm
.current
.rgb
[c
] == global_pwm
.target
.rgb
.rgb
[c
])
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
];
322 if (diff
>= global_pwm
.fade_step
[c
])
323 global_pwm
.current
.rgb
[c
] += global_pwm
.fade_step
[c
];
325 global_pwm
.current
.rgb
[c
] += diff
;
328 uint8_t diff
= global_pwm
.current
.rgb
[c
] - global_pwm
.target
.rgb
.rgb
[c
];
330 if (diff
>= global_pwm
.fade_step
[c
])
331 global_pwm
.current
.rgb
[c
] -= global_pwm
.fade_step
[c
];
333 global_pwm
.current
.rgb
[c
] -= diff
;
337 /* timeslot queue handling */
338 void enqueue_timeslot(uint8_t mask
, uint16_t top
)
340 struct timeslot_t
*t
= ×lots
.slot
[timeslots
.write
];
343 timeslots
.write
= (timeslots
.write
+ 1) % PWM_MAX_TIMESLOTS
;
346 void dequeue_timeslot(struct timeslot_t
*d
)
348 struct timeslot_t
*t
= ×lots
.slot
[timeslots
.read
];
351 timeslots
.read
= (timeslots
.read
+ 1) % PWM_MAX_TIMESLOTS
;
354 void update_last_timeslot(uint8_t mask
)
357 if (timeslots
.write
> 0)
358 i
= timeslots
.write
-1;
360 i
= PWM_MAX_TIMESLOTS
-1;
362 timeslots
.slot
[i
].mask
= mask
;
365 uint8_t timeslots_fill(void)
367 if (timeslots
.write
>= timeslots
.read
)
368 return timeslots
.write
- timeslots
.read
;
370 return PWM_MAX_TIMESLOTS
- (timeslots
.read
- timeslots
.write
);
373 /* convert hsv to rgb color
374 * (see http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_HSV_to_RGB )
376 * http://www.enide.net/webcms/uploads/file/projects/powerpicrgb-irda/hsvspace.pdf
378 void pwm_hsv2rgb(struct dual_color_t
*color
)
380 if (color
->hsv
.saturation
== 0) {
381 for (uint8_t i
= 0; i
< PWM_CHANNELS
; i
++)
382 color
->rgb
.rgb
[i
] = color
->hsv
.value
;
386 uint16_t h
= color
->hsv
.hue
% 360;
387 uint8_t s
= color
->hsv
.saturation
;
388 uint8_t v
= color
->hsv
.value
;
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;
399 color
->rgb
.rgb
[0] = v
;
400 color
->rgb
.rgb
[1] = t
;
401 color
->rgb
.rgb
[2] = p
;
404 color
->rgb
.rgb
[0] = q
;
405 color
->rgb
.rgb
[1] = v
;
406 color
->rgb
.rgb
[2] = p
;
409 color
->rgb
.rgb
[0] = p
;
410 color
->rgb
.rgb
[1] = v
;
411 color
->rgb
.rgb
[2] = t
;
414 color
->rgb
.rgb
[0] = p
;
415 color
->rgb
.rgb
[1] = q
;
416 color
->rgb
.rgb
[2] = v
;
419 color
->rgb
.rgb
[0] = t
;
420 color
->rgb
.rgb
[1] = p
;
421 color
->rgb
.rgb
[2] = v
;
424 color
->rgb
.rgb
[0] = v
;
425 color
->rgb
.rgb
[1] = p
;
426 color
->rgb
.rgb
[2] = q
;
431 /* convert rgb to hsv color
432 * (see http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV )
434 * http://www.enide.net/webcms/uploads/file/projects/powerpicrgb-irda/hsvspace.pdf
436 void pwm_rgb2hsv(struct dual_color_t
*color
)
438 /* search min and max */
439 uint8_t max
= color
->rgb
.red
;
442 if (color
->rgb
.green
> max
)
443 max
= color
->rgb
.green
;
444 if (color
->rgb
.blue
> max
)
445 max
= color
->rgb
.blue
;
447 if (color
->rgb
.green
< min
)
448 min
= color
->rgb
.green
;
449 if (color
->rgb
.blue
< min
)
450 min
= color
->rgb
.blue
;
453 uint8_t diff
= max
- min
;
454 uint8_t diffh
= diff
/2;
456 /* compute value and saturation */
457 color
->hsv
.value
= max
;
458 color
->hsv
.saturation
= 0;
461 color
->hsv
.saturation
= ((255 * diff
)+max
/2)/max
;
463 color
->hsv
.saturation
= 0;
464 color
->hsv
.hue
= 0; /* undefined */
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;
480 color
->hsv
.hue
= hue
;
483 /* stop fading, hold current color */
484 void pwm_stop_fading(void)
486 memcpy(&global_pwm
.target
.rgb
, &global_pwm
.current
.rgb
, sizeof(struct rgb_color_t
));
488 /* ignore all timers */
493 static uint8_t diff_abs(uint8_t a
, uint8_t b
)
501 static void compute_speed(uint8_t step
, uint8_t delay
)
503 /* search for max distance */
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
]);
516 /* adjust fading speeds, relative to max distance */
517 global_pwm
.fade_step
[max
] = step
;
518 global_pwm
.fade_delay
[max
] = delay
;
520 for (uint8_t i
= 0; i
< PWM_CHANNELS
; i
++) {
524 uint8_t d
= diff_abs(global_pwm
.target
.rgb
.rgb
[i
], global_pwm
.current
.rgb
[i
]);
528 ratio
= (dist
+d
/2)/d
;
533 global_pwm
.fade_delay
[i
] = ratio
* delay
;
534 global_pwm
.fade_step
[i
] = step
;
538 void pwm_fade_rgb(struct rgb_color_t
*color
, uint8_t step
, uint8_t delay
)
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
);
544 /* set target color */
545 for (uint8_t i
= 0; i
< PWM_CHANNELS
; i
++)
546 global_pwm
.target
.rgb
.rgb
[i
] = color
->rgb
[i
];
548 /* compute correct speed for all channels */
551 compute_speed(step
, delay
);
557 void pwm_fade_hsv(struct hsv_color_t
*color
, uint8_t step
, uint8_t delay
)
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
);
564 memcpy(&global_pwm
.target
.hsv
, color
, sizeof(struct hsv_color_t
));
567 remote_apply_hsv_offset(&global_pwm
.target
.hsv
);
569 /* update rgb color in target */
570 pwm_hsv2rgb(&global_pwm
.target
);
572 /* compute correct speed for all channels */
573 compute_speed(step
, delay
);
579 bool pwm_target_reached(void)
581 for (uint8_t i
= 0; i
< PWM_CHANNELS
; i
++) {
582 if (global_pwm
.target
.rgb
.rgb
[i
] != global_pwm
.current
.rgb
[i
])
589 /* modify current color */
590 void pwm_modify_rgb(struct rgb_color_offset_t
*color
, uint8_t step
, uint8_t delay
)
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
];
601 global_pwm
.target
.rgb
.rgb
[i
] = LO8(current
);
604 compute_speed(step
, delay
);
610 void pwm_modify_hsv(struct hsv_color_offset_t
*color
, uint8_t step
, uint8_t delay
)
612 /* convert current target color from rgb to hsv */
613 pwm_rgb2hsv(&global_pwm
.target
);
615 /* apply changes, hue */
616 global_pwm
.target
.hsv
.hue
+= color
->hue
;
619 int16_t sat
= global_pwm
.target
.hsv
.saturation
;
620 sat
+= color
->saturation
;
625 global_pwm
.target
.hsv
.saturation
= LO8(sat
);
628 int16_t val
= global_pwm
.target
.hsv
.value
;
634 global_pwm
.target
.hsv
.value
= LO8(val
);
636 /* re-convert to rgb */
637 pwm_hsv2rgb(&global_pwm
.target
);
639 /* compute correct speed for all channels */
640 compute_speed(step
, delay
);
649 /** timer1 overflow (=output compare a) interrupt */
650 ISR(TIMER1_COMPA_vect
)
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
);
660 /* prepare next interrupt */
662 dequeue_timeslot(&t
);
664 /* if next timeslot would happen too fast or has already happened, just spinlock */
665 while (TCNT1
+ 100 > t
.top
)
667 /* spin until timer interrupt is near enough */
668 while (t
.top
> TCNT1
);
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
);
676 /* load next timeslot */
677 dequeue_timeslot(&t
);
680 /* save values for next interrupt */
682 pwm_next_bitmask
= t
.mask
;
685 /** timer1 output compare b interrupt */
686 ISR(TIMER1_COMPB_vect
, ISR_ALIASOF(TIMER1_COMPA_vect
));