77 |
77 |
* are multiple concurrent calls to the orb*set* functions, one of them is ignored and the orbs are never left in an
|
78 |
78 |
* inconsistent state. For example, if the orbs are set to green by the main thread and to red by an interrupt handler,
|
79 |
79 |
* the resulting color will be either red or green, but never yellow.
|
|
80 |
* Thread safety is achieved by grabbing a lock at the beginning of all functions that modify the orb code and releasing
|
|
81 |
* the lock at the end. If the lock is already taken, the function just returns doing nothing.
|
80 |
82 |
*
|
81 |
83 |
* Some performance measurements:
|
82 |
84 |
* - Time for setting new orb values (PWM mode): 35us-72us (depending on the degree to which the array is already in
|
... | ... | |
87 |
89 |
* - Total CPU use for interrupts (PWM mode only): 0.03%+0.75% max (TODO old values)
|
88 |
90 |
* - Maximum time spent in synchronized code: TODO
|
89 |
91 |
* There are some possible optimizations. See the source code for more information.
|
|
92 |
*
|
|
93 |
* A note on robustness: if the output compare interrupt is disabled for too long, either due to a long ISR or a long
|
|
94 |
* synchronized code block, the orbs will flicker to brighter values for being turned off too late. With software PWM,
|
|
95 |
* there's nothing at all to be done about that. The problem can be alleviated by using a lower PWM frequency, but then
|
|
96 |
* the orbs will start flickering all the time due to the low update frequency.
|
|
97 |
* Some measurements: with 100us synchronized blocks, the flickering is accepptably low. Longer synchronized blocks
|
|
98 |
* mean more flickering. At 1ms synchronized blocks, the flickering is quite bad, especially for low orb values. Note
|
|
99 |
* that orb value 0 never flickers at all because the corresponding channels are not turned on at all.
|
|
100 |
* Test code (not the _delay_us restrictions!)
|
|
101 |
* orb_set (1,1,1); while (1) { SYNC { for (uint8_t m=0; m<10; ++m) { _delay_us(10); } } }
|
90 |
102 |
**/
|
91 |
103 |
|
92 |
104 |
/*
|
... | ... | |
98 |
110 |
/*
|
99 |
111 |
* Possible optimizations:
|
100 |
112 |
* - Use pointers instead of indicies for current_pwm_channel
|
101 |
|
* - Optimize the output compare interrupt
|
|
113 |
* - Optimize output_compare()
|
102 |
114 |
* - Use a different sorting algorithm (see sort_orbs_buffer for further comments on this issue)
|
103 |
115 |
* - Use pointers in fill_orbs_buffer
|
104 |
|
* - Optimized orb_set (use the knowledge that there are only 3 distinct values)
|
|
116 |
* - Optimized orb_set (use the knowledge that there are only 3 distinct values, don't use a loop but unroll the
|
|
117 |
* sorting, which is no problem for 3 values)
|
105 |
118 |
* - Use a lower update frequency. The next higher prescaler value leads to a frequency of 30Hz which is too low (the
|
106 |
119 |
* orbs are flickering). So the timer would have to be reloaded manually after 127 to generate 60Hz. This would
|
107 |
120 |
* decrease the resolution from 8 to 7 bit, but 128 steps should still be enough.
|
... | ... | |
109 |
122 |
|
110 |
123 |
|
111 |
124 |
/*
|
112 |
|
TODO:
|
113 |
|
- Make thread safe
|
114 |
|
- Check delayed interrupts
|
115 |
|
|
116 |
125 |
- Timing tests
|
117 |
126 |
- How long do the interrupt handlers take?
|
118 |
127 |
- How long may interrupts be blocked?
|
... | ... | |
250 |
259 |
// Not volatile because it is only accessed in the interrupt handler.
|
251 |
260 |
uint8_t current_pwm_channel=0;
|
252 |
261 |
|
|
262 |
|
|
263 |
static void output_compare (void) {
|
|
264 |
// This function is called when an output compare condition may have occured.
|
|
265 |
|
|
266 |
// If the OC interrupt is executed without delay, TCNT0==time+1 (where time==OCR0), because the interrupt flag is
|
|
267 |
// queued at the next timer clock cycle after an output compare.
|
|
268 |
|
|
269 |
// What may happen here is that the interrupt is delayed for more than one timer clock cycle (33 us). In that case,
|
|
270 |
// the timer has already counted on and TCNT0 is bigger than current_channel_timer. Also, while during the ISR no
|
|
271 |
// other interrupts will occur, the timer may still count on. Thus, we have to check the following channel as well.
|
|
272 |
|
|
273 |
// Some optimization is possible in this function.
|
|
274 |
|
|
275 |
while (1) {
|
|
276 |
// The timer value at which the output compare interrupt should occur (one timer clock cycle after the output
|
|
277 |
// compare condition is detected).
|
|
278 |
uint8_t current_channel_time=pwm_read_buffer->channel[current_pwm_channel].time+1;
|
|
279 |
|
|
280 |
// If the counter is not at this time yet, we don't have to do anything right now.
|
|
281 |
if (current_channel_time>TCNT0) return;
|
|
282 |
|
|
283 |
// We have an output compare condition for the current channel.
|
|
284 |
|
|
285 |
// Turn the current channel off
|
|
286 |
ORBPORT|=pwm_read_buffer->channel[current_pwm_channel].mask;
|
|
287 |
|
|
288 |
// Increment the channel index
|
|
289 |
current_pwm_channel++;
|
|
290 |
|
|
291 |
// If there is a next channel, load its OCR value
|
|
292 |
if (current_pwm_channel<=(num_pwm_channels-1))
|
|
293 |
if (pwm_read_buffer->channel[current_pwm_channel].time<255)
|
|
294 |
OCR0=pwm_read_buffer->channel[current_pwm_channel].time;
|
|
295 |
}
|
|
296 |
}
|
|
297 |
|
253 |
298 |
SIGNAL (SIG_OVERFLOW0) {
|
254 |
299 |
#ifdef LIGHTS_DEBUG
|
255 |
300 |
LIGHTS_DEBUG_OVERFLOW_INTERRUPT_START
|
... | ... | |
275 |
320 |
// Load the first OCR
|
276 |
321 |
OCR0=pwm_read_buffer->channel[current_pwm_channel].time;
|
277 |
322 |
|
|
323 |
// If this interrupt was delayed, we might already have an output compare condition.
|
|
324 |
output_compare ();
|
|
325 |
|
278 |
326 |
#ifdef LIGHTS_DEBUG
|
279 |
327 |
LIGHTS_DEBUG_OVERFLOW_INTERRUPT_END
|
280 |
328 |
#endif
|
... | ... | |
285 |
333 |
LIGHTS_DEBUG_OUTPUT_COMPARE_INTERRUPT_START
|
286 |
334 |
#endif
|
287 |
335 |
|
288 |
|
// If this interrupt is executed without delay, TCNT0==time+1 (where time==OCR0)
|
289 |
|
|
290 |
|
// TODO delayed interrupt
|
291 |
|
while (TCNT0==pwm_read_buffer->channel[current_pwm_channel].time+1) {
|
292 |
|
// Turn the current channel off
|
293 |
|
ORBPORT|=pwm_read_buffer->channel[current_pwm_channel].mask;
|
294 |
|
|
295 |
|
// Increment the channel
|
296 |
|
current_pwm_channel++;
|
297 |
|
|
298 |
|
// If there is a next channel, load its OCR value
|
299 |
|
if (current_pwm_channel<=(num_pwm_channels-1))
|
300 |
|
if (pwm_read_buffer->channel[current_pwm_channel].time<255)
|
301 |
|
OCR0=pwm_read_buffer->channel[current_pwm_channel].time;
|
302 |
|
}
|
|
336 |
// We have an output compare condition.
|
|
337 |
output_compare ();
|
303 |
338 |
|
304 |
339 |
#ifdef LIGHTS_DEBUG
|
305 |
340 |
LIGHTS_DEBUG_OUTPUT_COMPARE_INTERRUPT_END
|
... | ... | |
488 |
523 |
// All of these functions use set_orb_values to set the actual values, and then call apply_orbs() to apply the changes.
|
489 |
524 |
// set_orb_values should be used (even though it would be faster to set the array directly) because the binary/pwm mode
|
490 |
525 |
// has to be handled.
|
|
526 |
// All of these functions must be
|
491 |
527 |
|
|
528 |
uint8_t orb_lock=0;
|
|
529 |
|
492 |
530 |
/**
|
493 |
531 |
* Sets the specified orb to the specified color. The orbs must be initialized before this function may be used.
|
494 |
532 |
* Note that, when setting both orbs, using orbs_set is faster then setting the orbs individually because the values are
|
... | ... | |
501 |
539 |
* @see
|
502 |
540 |
*/
|
503 |
541 |
void orb_n_set (uint8_t num, uint8_t red, uint8_t green, uint8_t blue) {
|
|
542 |
REQUIRE_LOCK_OR_RETURN(orb_lock);
|
|
543 |
|
504 |
544 |
set_orb_values (num, red, green, blue);
|
505 |
545 |
apply_orbs ();
|
|
546 |
|
|
547 |
RELEASE_LOCK(orb_lock);
|
506 |
548 |
}
|
507 |
549 |
|
508 |
550 |
/**
|
... | ... | |
517 |
559 |
* @see orb_init
|
518 |
560 |
**/
|
519 |
561 |
void orb1_set (uint8_t red, uint8_t green, uint8_t blue) {
|
|
562 |
REQUIRE_LOCK_OR_RETURN(orb_lock);
|
|
563 |
|
520 |
564 |
set_orb_values (0, red, green, blue);
|
521 |
565 |
apply_orbs ();
|
|
566 |
|
|
567 |
RELEASE_LOCK(orb_lock);
|
522 |
568 |
}
|
523 |
569 |
|
524 |
570 |
/**
|
... | ... | |
533 |
579 |
* @see orb_init
|
534 |
580 |
**/
|
535 |
581 |
void orb2_set (uint8_t red, uint8_t green, uint8_t blue) {
|
|
582 |
REQUIRE_LOCK_OR_RETURN(orb_lock);
|
|
583 |
|
536 |
584 |
set_orb_values (1, red, green, blue);
|
537 |
585 |
apply_orbs ();
|
|
586 |
|
|
587 |
RELEASE_LOCK(orb_lock);
|
538 |
588 |
}
|
539 |
589 |
|
540 |
590 |
/**
|
... | ... | |
547 |
597 |
* @see orb_init, orb1_set, orb2_set
|
548 |
598 |
**/
|
549 |
599 |
void orb_set (uint8_t red, uint8_t green, uint8_t blue) {
|
|
600 |
REQUIRE_LOCK_OR_RETURN(orb_lock);
|
|
601 |
|
550 |
602 |
set_orb_values (0, red, green, blue);
|
551 |
603 |
set_orb_values (1, red, green, blue);
|
552 |
604 |
apply_orbs ();
|
|
605 |
|
|
606 |
RELEASE_LOCK(orb_lock);
|
553 |
607 |
}
|
554 |
608 |
|
555 |
609 |
/**
|
... | ... | |
570 |
624 |
void orbs_set (
|
571 |
625 |
uint8_t red1, uint8_t green1, uint8_t blue1,
|
572 |
626 |
uint8_t red2, uint8_t green2, uint8_t blue2) {
|
|
627 |
|
|
628 |
REQUIRE_LOCK_OR_RETURN(orb_lock);
|
|
629 |
|
573 |
630 |
set_orb_values (0, red1, green1, blue1);
|
574 |
631 |
set_orb_values (1, red2, green2, blue2);
|
575 |
632 |
apply_orbs ();
|
|
633 |
|
|
634 |
RELEASE_LOCK(orb_lock);
|
576 |
635 |
}
|
577 |
636 |
|
578 |
637 |
|
... | ... | |
580 |
639 |
// ** Predefined color setting **
|
581 |
640 |
// ******************************
|
582 |
641 |
|
|
642 |
// This functions just call the corresponding orb*_set functions. If the orbs array is accessed in any other way, it
|
|
643 |
// must be synchronized on orb_lock (REQUIRE_LOCK_OR_RETURN and RELEASE_LOCK)! Note that one synchronized function
|
|
644 |
// cannot call another one with this lock implementation.
|
|
645 |
|
583 |
646 |
// Macros for extracting a color.
|
584 |
647 |
#define C_RED(col) (((col & 0xE0) >> 5) * 36)
|
585 |
648 |
#define C_GREEN(col) (((col & 0x1C) >> 2) * 36)
|
... | ... | |
666 |
729 |
// Using an 8 bit timer has the added advantage that all the comparisons are faster.
|
667 |
730 |
|
668 |
731 |
// Normal mode, Compare match output off, Prescaler
|
669 |
|
TCCR0=_BV(CS02) | _BV(CS01); // 1024, 30 Hz
|
|
732 |
TCCR0=_BV(CS02) | _BV(CS01); // 256, 120 Hz
|
670 |
733 |
|
671 |
734 |
// Enable the interrupts
|
672 |
735 |
TIMSK|= _BV(OCIE0) | _BV(TOIE0);
|