Revision 1138
Fixed synchronization
Tested robustness
lights.c | ||
---|---|---|
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); |
Also available in: Unified diff