Project

General

Profile

Revision 1138

Fixed synchronization
Tested robustness

View differences:

branches/library_refactor/behaviors/library_test/main.c
104 104
		orbs_set (0, 0, 0, 0, 0, 255); delay_ms (500);
105 105
	}
106 106

  
107
	if (false)
108
	{
109
		orb_set (1,1,1);
110
		while (1)
111
		{
112
			SYNC
113
			{
114
				for (uint8_t m=0; m<100; ++m)
115
				{
116
					_delay_us(10);
117
				}
118
			}
119
		}
120
	}
121
	
107 122
	if (true)
108 123
	{
124
		while (1)
125
		{
126
			for (uint8_t x=0; x<255; ++x)
127
			{
128
				orbs_set (x, 1, 2, 3, 4, 5);
129
				
130
				for (uint8_t n=0; n<80; ++n)
131
				{
132
					SYNC
133
					{ for (uint8_t m=0; m<1; ++m) _delay_us(50); }
134
				}
135
			}
136

  
137
			for (uint8_t x=255; x>0; --x)
138
			{
139
				orbs_set (x, 1, 1, 1, 1, 1);
140
				delay_ms (4);
141
			}
142
		}
143
	}
144

  
145
	if (true)
146
	{
109 147
		if (!button2_read ())
110 148
		{
111 149
			orb_set_mode (orb_mode_pwm);
branches/library_refactor/lib/include/libdragonfly/dragonfly_lib.h
103 103
/** @brief shortcut for ATOMIC_BLOCK(ATOMIC_RESTORESTATE) **/
104 104
#define SYNC ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
105 105

  
106
/** @brief atomically grab a lock if it is free, return otherwise **/
107
#define REQUIRE_LOCK_OR_RETURN(LOCK) do { SYNC { if (LOCK) return; LOCK=1; } } while (0)
106 108

  
109
/** @brief atomically release a lock **/
110
#define RELEASE_LOCK(LOCK) do { LOCK=0; } while (0)
111

  
112

  
113

  
107 114
#endif
108 115

  
branches/library_refactor/projects/libdragonfly/dragonfly_lib.h
103 103
/** @brief shortcut for ATOMIC_BLOCK(ATOMIC_RESTORESTATE) **/
104 104
#define SYNC ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
105 105

  
106
/** @brief atomically grab a lock if it is free, return otherwise **/
107
#define REQUIRE_LOCK_OR_RETURN(LOCK) do { SYNC { if (LOCK) return; LOCK=1; } } while (0)
106 108

  
109
/** @brief atomically release a lock **/
110
#define RELEASE_LOCK(LOCK) do { LOCK=0; } while (0)
111

  
112

  
113

  
107 114
#endif
108 115

  
branches/library_refactor/projects/libdragonfly/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