// http://www.batsocks.co.uk/readme/video_timing.htm // http://www.rn-wissen.de/index.php/Inline-Assembler_in_avr-gcc // 9/8: 450/900 470/940 470/1k | PAL-Signal // 0x11 1.000 0.966 0.950 | 1.073 WHITE // 0x10 0.667 0.644 0.646 | 0.339 BLACK // 0x01 0.333 0.322 0.304 | 0.285 BLANK // 0x00 0.000 0.000 0.000 | 0.000 SYNC #include #include #define _SYNC 0x00 #define _BLACK 0x01 #define _GRAY 0x02 #define _WHITE 0x03 #define MSB_PIN 9 // PB1 ---[ 470 ]---. (TV Video In) | (TV) | #define LSB_PIN 8 // PB0 ---[ 1k0 ]---+--------o--------|---[ 75 ]--- GND | #define RANDOM_PIN 0 // Leave unconnected or use a simple wire as antenna. //#define DEBUG // Print debug info on the serial interface. #define O3 // Change nop-count when compiled with -O3. #define TEENSY #ifdef TEENSY #define CELL_SIZE 6 #else #define CELL_SIZE 7 #endif #define START_SCAN_LINE 36 // Should be at least 23. #ifdef O3 #ifdef TEENSY #define WIDTH 54 #define HEIGHT 46 #else #define WIDTH 47 #define HEIGHT 39 #endif #else #define WIDTH 45 // WIDTH * HEIGHT must fit in the SRAM of an ATmega328P. #define HEIGHT 39 // About 1833 (47*39) is the limit (when DEBUG is turned off). #endif // About 2484 (54*46) is the limit for an ATmega32U4. // http://www.nongnu.org/avr-libc/user-manual/FAQ.html#faq_varinit // "Global and static variables are guaranteed to be initialized to 0 by the C standard." byte fb[ HEIGHT ][ WIDTH ]; // frame buffer volatile byte shift = 0; volatile unsigned int scanLine = 310; volatile unsigned int fieldCounter = 0; byte fb_y = 1; // 1 because the 0th array line is black anyway. byte fb_modulo = 0; inline void vBlank() { // scan line 310 PORTB = _SYNC; delayMicroseconds( 2 ); // 310 PORTB = _BLACK; delayMicroseconds( 30 ); PORTB = _SYNC; delayMicroseconds( 2 ); PORTB = _BLACK; delayMicroseconds( 30 ); PORTB = _SYNC; delayMicroseconds( 2 ); // 311 PORTB = _BLACK; delayMicroseconds( 30 ); PORTB = _SYNC; delayMicroseconds( 2 ); PORTB = _BLACK; delayMicroseconds( 30 ); PORTB = _SYNC; delayMicroseconds( 2 ); // 312 PORTB = _BLACK; delayMicroseconds( 30 ); PORTB = _SYNC; delayMicroseconds( 2 ); // extra half scan line PORTB = _BLACK; delayMicroseconds( 30 ); // scan line 1 PORTB = _SYNC; delayMicroseconds( 28 ); // 1 PORTB = _BLACK; delayMicroseconds( 4 ); PORTB = _SYNC; delayMicroseconds( 28 ); PORTB = _BLACK; delayMicroseconds( 4 ); PORTB = _SYNC; delayMicroseconds( 28 ); // 2 PORTB = _BLACK; delayMicroseconds( 4 ); PORTB = _SYNC; delayMicroseconds( 28 ); PORTB = _BLACK; delayMicroseconds( 4 ); PORTB = _SYNC; delayMicroseconds( 28 ); // 3 PORTB = _BLACK; delayMicroseconds( 4 ); PORTB = _SYNC; delayMicroseconds( 2 ); PORTB = _BLACK; delayMicroseconds( 30 ); PORTB = _SYNC; delayMicroseconds( 2 ); // 4 PORTB = _BLACK; delayMicroseconds( 30 ); PORTB = _SYNC; delayMicroseconds( 2 ); PORTB = _BLACK; delayMicroseconds( 30 ); PORTB = _SYNC; delayMicroseconds( 2 ); // 5 PORTB = _BLACK; delayMicroseconds( 30 ); PORTB = _SYNC; delayMicroseconds( 2 ); PORTB = _BLACK; delayMicroseconds( 30 ); } ISR( TIMER1_COMPA_vect ) { ++scanLine; if ( scanLine > 310 ) { vBlank(); // Blocks for 8 scan lines. ++fieldCounter; scanLine = 6; fb_y = 1; } // Compensate ISR jitter. #ifdef O3 switch ( TCNT1 & 0b00000011 ) { case 0: asm volatile ( "nop\n\tnop\n\tnop\n\tnop" ); break; case 1: asm volatile ( "nop\n\tnop\n\tnop\n\tnop" ); break; case 2: asm volatile ( "nop\n\tnop" ); break; case 3: asm volatile ( "nop\n\tnop" ); break; } #else switch ( TCNT1 & 0b00000001 ) { case 0: asm volatile ( "nop\n\tnop" ); break; case 1: asm volatile ( "nop" ); break; } #endif // hSync (12 us) PORTB = _SYNC; delayMicroseconds( 4 ); // Sync 4 us. #ifdef O3 PORTB = _BLACK; delayMicroseconds( 9 ); // Back porch 8 us. #else PORTB = _BLACK; delayMicroseconds( 8 ); // Back porch 8 us. #endif if ( scanLine >= START_SCAN_LINE && scanLine < START_SCAN_LINE + ( HEIGHT - 2 ) * CELL_SIZE ) // - 2 because the first and the last array line is black anyway. { if ( fb_modulo < CELL_SIZE - 1 ) { byte * pCell = &fb[ fb_y ][ 0 ]; byte * pEndCell = pCell + WIDTH; ++pCell; // Because the first and the last --pEndCell; // cell of a line is black anyway. byte s = shift; // Use a local variable because access to a volatile variable cannot be optimized. PORTB = _GRAY; #ifdef O3 if ( s != 1 ) { asm volatile ( "nop" ); } #endif do { byte value; if ( s == 1 ) { value = *pCell | 0b00000001; #ifdef O3 asm volatile ( "nop" ); #endif } else { value = *pCell << 1 | 0b00000001; // To increase speed we do not care for the level of pin 10. } PORTB = _BLACK; PORTB = value; ++pCell; #ifdef O3 #ifdef TEENSY asm volatile ( "nop\n\tnop" ); #else asm volatile ( "nop\n\tnop\n\tnop\n\tnop" ); #endif #endif } while ( pCell < pEndCell ); #ifdef O3 if ( s != 1 ) { asm volatile ( "nop\n\tnop" ); } asm volatile ( "nop\n\tnop" ); #else asm volatile ( "nop\n\tnop\n\tnop\n\tnop\n\tnop\n\tnop\n\tnop\n\tnop" ); #endif PORTB = _BLACK; PORTB = _GRAY; #ifdef O3 asm volatile ( "nop\n\tnop\n\tnop\n\tnop\n\tnop\n\tnop\n\tnop\n\tnop\n\tnop" ); #else asm volatile ( "nop\n\tnop\n\tnop\n\tnop\n\tnop\n\tnop\n\tnop\n\tnop" ); #endif PORTB = _BLACK; ++fb_modulo; } else { fb_modulo = 0; ++fb_y; } } } // Set up Timer 1 to send a scan line every interrupt. void startScanLineTimer() { cli(); // Set CTC mode (Clear Timer on Compare Match) (p.133) // Have to set OCR1A *after*, otherwise it gets reset to 0! TCCR1B = ( TCCR1B & ~_BV( WGM13 ) ) | _BV( WGM12 ); TCCR1A = TCCR1A & ~( _BV( WGM11 ) | _BV( WGM10 ) ); // No prescaler (p.134) TCCR1B = ( TCCR1B & ~( _BV( CS12 ) | _BV( CS11 ) ) ) | _BV( CS10 ); // Set the compare register (OCR1A). // OCR1A is a 16-bit register, so we have to do this with // interrupts disabled to be safe. OCR1A = F_CPU / ( 1 / 64E-6 ); // 16e6 / 15625 = 1024 // Enable interrupt when TCNT1 == OCR1A (p.136) TIMSK1 |= _BV( OCIE1A ); // Disable other interrupts used by Arduino. TIMSK0 = 0; sei(); } void setup() { #ifdef TEENSY CLKPR = 0x80; CLKPR = 0; #endif #ifdef DEBUG Serial.begin( 115200 ); #endif pinMode( LSB_PIN, OUTPUT ); pinMode( MSB_PIN, OUTPUT ); putPattern( 0b00000001, WIDTH / 2, HEIGHT / 2 ); startScanLineTimer(); } void putPattern( byte mask, byte x, byte y ) { // f-Pentomino ( fb[y-1][x] = fb[y-1][x+1] = mask ); ( fb[y][x-1] = fb[y ][x] = mask ); ( fb[y+1][x] = mask ); // Glider //( fb[y-1][x] = mask ); //( fb[y ][x+1] = mask ); //( fb[y+1][x-1] = fb[y+1][x] = fb[y+1][x+1] = mask ); } unsigned int iterationCounter = 0; byte ss = 0; // source shift byte sm = 0b00000001; // source mask byte ts = 1; // target shift byte tm = 0b00000010; // target mask void loop() { ++iterationCounter; if ( iterationCounter == 51 ) { iterationCounter = 1; putPattern( sm, 2 + analogRead( RANDOM_PIN ) % ( WIDTH - 4 ), 2 + analogRead( RANDOM_PIN ) % ( HEIGHT - 4 ) ); } for ( byte y = 1; y < HEIGHT - 1; ++y ) { for ( byte x = 1; x < WIDTH - 1; ++x ) { byte sum = ( (fb[y-1][x-1]&sm) + (fb[y-1][x]&sm) + (fb[y-1][x+1]&sm) + (fb[y ][x-1]&sm) + (fb[y ][x+1]&sm) + (fb[y+1][x-1]&sm) + (fb[y+1][x]&sm) + (fb[y+1][x+1]&sm) ); if ( ( fb[ y ][ x ] & sm ) == 1 << ss ) { if ( sum == 2 << ss || sum == 3 << ss ) { fb[ y ][ x ] |= tm; } else { fb[ y ][ x ] &= ~tm; } } else { if ( sum == 3 << ss ) { fb[ y ][ x ] |= tm; } else { fb[ y ][ x ] &= ~tm; } } } } byte tempShift = ss, tempMask = sm; ss = ts; sm = tm; ts = tempShift; tm = tempMask; #ifdef DEBUG unsigned int fc = fieldCounter, sl = scanLine; Serial.print( fc ); Serial.print( " " ); Serial.println( sl ); #endif // 50 fields per second. Start a new iteration only every N-1th field. while ( fieldCounter < 6 ) { // 6 is equal to ten iterations per second. // Busy waiting. } fieldCounter = 1; shift = ss; // Because of the busy waiting above this happens shortly after the vBlank. }