Dependencies:   mbed

Files at this revision

API Documentation at this revision

Comitter:
robyounger
Date:
Sun Nov 15 17:24:38 2009 +0000
Commit message:

Changed in this revision

composite.cpp Show annotated file Show diff for this revision Revisions of this file
mbed.bld Show annotated file Show diff for this revision Revisions of this file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/composite.cpp	Sun Nov 15 17:24:38 2009 +0000
@@ -0,0 +1,548 @@
+////////////////////////////////////////////////////////////
+// Software generation of a grayscale composite TV signal //
+// Puts a 105x128 grayscale fractal zoom onscreen (slow!) //
+//                                                        //
+// Hacked together, (ab)uses the LPC1768 DAC output (p18) //
+// with some shifty looking timing sensitive code         //
+//                                                        //
+//                                                        //
+// Rob Younger 26th Oct 2009, (tweaked 15th Nov 2009)     //
+////////////////////////////////////////////////////////////
+
+////////////////////////////////////////////////////////////
+// fire demo routine based on http://demo-effects.sourceforge.net/ 
+// Copyright (C) 2003 W.P. van Paassen - peter@paassen.tmfweb.nl
+// which I guess means this version is GPL.
+//
+// note it uses a secondary buffer for the fire, which is 
+// copied into the main buffer, innefficient but quick!
+// limits on available resolution due to inneficient mem use
+////////////////////////////////////////////////////////////
+
+// Generating video like it's 1982!
+
+// Warning : this is *very* hacky code - just proof of concept!
+// This might blow up your mbed or your TV.
+// I claim no responsibility for anything :-)
+
+// Start with a 180 Ohm resistor in series with the DAC output
+// before connecting to a composite AV input,
+// DAC is about 0-3.3v output, Composite in 1v p-p, with a 75 Ohm termination, so 180 Ohms is about right.
+
+// but it also worked without any resistor for me! Start with a higher value if you aren't sure.
+// More likely to burn out your mbed or TV with low/no resistor - Use at your own risk!
+
+
+// HOW THIS WORKS:
+
+// The DAC output is written as fast as possible to software generate a composite signal
+// dac.write_u16() seems to take about 0.5 us: I worked this timing out using a big loop of
+//        dac.write_u16(0);
+//        ....
+//        dac.write_u16(0);
+//        dac.write_u16(0xFFFF);
+//        ....
+//        dac.write_u16(0xFFFF);
+// Until I got a frequency I could measure on a multimeter.
+//
+// At full speed gives us about 1MHz max frequency -
+// I don't have an oscilloscope to see how well this actually works, probably totally out of spec!
+//
+// The software just runs loads of these to generate the composite signal as fast as possible!
+//
+// Since a TV output is generated continuously this would use 100% CPU time.
+//
+// Clever to-the-metal code would do things like use the horizontal and vertical blanking
+// intervals to do any required calculation. This isn't clever to-the-metal code!
+// Instead, I just don't draw the bottom few percent of the TV picture, and use this free time to run code.
+// This may well cause your TV to loose sync, but it works for me - I did say it was a hack!
+//
+// Driving the display takes 90%, main code gets 10% to play with at the end of each frame.
+// Tweak these percentages up and down, but loose too many lines and the tv is much more likely to
+// drop the signal, equally as you hit 100% CPU the frame calls might start to overlap and it all goes a bit wrong!
+//
+// This code actually starts with the end of previous frame signalling first, then all the setup, then the actual picture.
+//
+// It's coded up as a routine that draws a whole frame (field), which is called from main on a timer interrupt (at 50Hz for PAL)
+// This makes it easy to have a main routing that can operate normally, without you having to worry (too much) about the timing involved.
+// The picture elements of the signal are created by dumping a global frame buffer over to the DAC:
+// unsigned short int framebuffer[HEIGHT][WIDTH];
+// The values in this framebuffer are the actual composite signal, NOT just shades of gray!
+// In other words, only write values between 0x56DB (black) and 0xFFFF (bright white).
+// For this reason, it's important to initialize the buffer to all 0x56DB or above
+
+// Yes - there's probably a much better way to do this - but you don't want to slow down the DAC writes at all.
+// Adding checks or shifting the value from a normal range might be to slow - over to the real programmers to work out how to do this...
+
+// The frame buffer is 105 pixels wide - this is just because 105 dac writes take up the time required for a horizontal tv line.
+// height is more arbitrary, as we draw every scan line - but I double or quadruple scan to get squarish pixels!
+// Use a modulo value in the picture write line to repeat the picture for small framebuffers.
+
+// This program has a couple of demo routines. One draws a fractal, and the other just writes random values to the framebuffer
+
+// A future enhancement could be to have two small framebuffers 105x64 and do double buffering? Needs to all be in fast memory though.
+// The code could definitely do with some tuning as the sync delays are all a bit off...
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+#include "mbed.h"
+
+//Framebuffer size
+#define WIDTH 105
+#define HEIGHT 86
+
+//TV signal generation controlling:
+#define LINES 256 //Visible lines drawn to screen
+#define SCAN 3 //Number of scanlines per pixel (vertical)
+#define DRAWWIDTH 105 //Pixels per line
+
+// LINES: theoretically up to 286 for PAL, 241 for NTSC). 285 seems to be about 100% CPU on PAL. Smaller values means I stop drawing the signal early.
+// SCAN: controls double scan (e.g. 128 pixels to 256 lines)
+// DRAWWIDTH: number of pixels to attempt to draw in a line (should be =< framebuffer WIDTH). Very timing critical - expect different values to break
+
+// Composite signal values for DAC output. These should really be scaled for 1v peak-to-peak
+#define IRE_m40 0x0000 //0volts
+#define IRE_0   0x4920 //Baseline
+#define IRE_7p5 0x56DB //Black
+#define IRE_100 0xFFFF //White
+// DAC is 10bit, but i'm using write_u16 to write to the DAC.
+// IRE is a definition:
+// the levels are -40 (0volts), 0 (baseline below black), 7.5 (Black), 100 (White).
+// IRE -40 is 0v, 100 is 1v, so scale accordingly!
+
+AnalogOut dac(p18); // Video out pin
+Ticker timer;  // Timer for calling the frame
+
+DigitalOut led1(LED1);//Some status lights...
+DigitalOut led2(LED2);
+DigitalOut led3(LED3);
+DigitalOut led4(LED4);
+
+// Framebuffer actually has video signal levels in it - not just grayscale data
+// This means it must be initialised to at least all black IRE_7p5 before it's used.
+// zero values will likely kill the output and TV will loose sync.
+unsigned short int framebuffer[HEIGHT][WIDTH];
+
+
+
+/////////////////////////////////////////////////////////////
+//Software composite signal generation (very timing specific)
+/////////////////////////////////////////////////////////////
+
+void createframe() {
+
+// Procedure to create a output frame to a tv - needs to run on a very regular sync (e.g. 50Hz or 60Hz)
+// Using the DAC to create this output, which seems to happily run at 2MHz update
+// dac.write_u16 seems to take almost spot on 0.5us, so I'm using multiples of this to create a signal.
+
+// Could maybe be done with timing precision through multiple digital outputs and a resistor ladder to create an external DAC, but this didn't need any external components!
+
+// Someone with an oscilloscope can tweak this to get the delays more up to standard!
+
+// TV signal specs
+
+// For 50Hz PAL, each line takes up 64us, and there are 625 lines, but split into two fields of about 312 lines.
+// I'm treating both fields exactly the same, so we have a 312(ish) lines  at 50Hz.
+// NTSC is actually very similar but with slightly different timings/counts. (525 lines at 60Hz).
+
+// Some info found through google:
+
+//525line     (NTSC) - required timing in us for a line
+//NAME       LENGTH LEVEL
+//Front porch   1.5 IRE_0
+//Sync Tip      4.7 IRE_m40
+//Breezeway     0.6 IRE_0
+//Color Burst   2.5 IRE_0
+//Back Porch    1.6 IRE_0
+//Active Video 52.6 IRE_7p5 - IRE100
+
+//Total line time = 63.5us ( * half of 525 lines * 60Hz)
+
+//625line     (PAL) - required timing in us for a line
+//NAME       LENGTH LEVEL
+//Front porch   1.65 IRE_0
+//Sync Tip      4.7  IRE_m40
+//Breezeway     0.9  IRE_0
+//Color Burst   2.25 IRE_0
+//Back Porch    2.55 IRE_0
+//Active Video 51.95 IRE_7p5 - IRE100
+
+//Total line time = 64us ( * half of 625 lines * 50Hz)
+
+// There actually seem to be a lot of variations on this, but they all seem roughly the same.
+
+// Colour needs a precision ~4MHz carrier signal applied over the 'color burst' and active video
+// with precise phase and amplitude control (sounds like a lot of work!)
+
+// So for colour, Use svideo, VGA, or use 3 of these signals to generate an RGB scart signal?
+
+//The basic frame format is
+//1) few lines of special start pulses,
+//2) some off screen lines, which had things like teletext/close captions
+//3) the tv picture bit you see,
+//4) some special pulses to say end of screen, go back to the top.
+// Then straight back to 1 for the next frame.
+
+// To get the timing right - I do this:
+//4) some special pulses to say end of screen, go back to the top.
+//1) few lines of special start pulses,
+//2) some off screen lines, which had things like teletext/close captions
+//3) the tv picture bit you see,
+
+// You can get away dropping the last few lines of 3)
+// I use this to drop back to the main program to run as normal.
+
+// Ideally you'd use the few cycles between each line to do stuff, but that's going to be hard to get timing right.
+
+
+////////////////////////////////////////////////////////////
+//Start of Frame
+////////////////////////////////////////////////////////////
+
+//Each dac.write is ~0.5us, so multiply up to create the timings.
+
+// (This is a mix of PAL and NTSC - hack as appropriate)
+
+// There are 21 lines per field in a vertical blanking period
+// the last 4 lines of a field indicate are just before flyback
+// then there are 5 blank lines for flyback itself...
+
+//END OF A FRAME + FLYBACK + START OF NEW FRAME signalling (9 lines)
+    for (int i = 0; i < 6; i++) { //6 equalizing pulses (time = 6 half lines)
+        dac.write_u16(IRE_m40); //2.4us
+        dac.write_u16(IRE_m40);
+        dac.write_u16(IRE_m40);
+        dac.write_u16(IRE_m40);
+//        dac.write_u16(IRE_m40);
+        dac.write_u16(IRE_0); //29.4us
+        wait_us(28);
+    }
+    for (int i = 0; i < 6; i++) {// 6 serrated vertical pulses (time = 6 half lines)
+        dac.write_u16(IRE_0);  //2.4us
+        dac.write_u16(IRE_0);
+        dac.write_u16(IRE_0);
+        dac.write_u16(IRE_0);
+//        dac.write_u16(IRE_0);
+        dac.write_u16(IRE_m40); //29.4us
+        wait_us(28);
+    }
+    for (int i = 0; i < 6; i++) { // 6 equalizing pulses (time = 6 half lines)
+        dac.write_u16(IRE_m40); //2.4us
+        dac.write_u16(IRE_m40);
+        dac.write_u16(IRE_m40);
+        dac.write_u16(IRE_m40);
+//        dac.write_u16(IRE_m40);
+        dac.write_u16(IRE_0); //29.4us
+        wait_us(28);
+    }
+
+
+// The lines just above the top of the picture used for setup/teletext/closed captions etc.
+// about 17 lines for PAL, 12 for NTSC?
+    for (int i = 0; i < 17; i++) {
+        //10.9us (NTSC) or 12.5us (PAL) for horizontal blanking interval
+        dac.write_u16(IRE_0); //Front porch 1.6us
+        dac.write_u16(IRE_0);
+        dac.write_u16(IRE_0);
+        dac.write_u16(IRE_m40); //Sync Tip    4.7us
+        dac.write_u16(IRE_m40);
+        dac.write_u16(IRE_m40);
+        dac.write_u16(IRE_m40);
+        dac.write_u16(IRE_m40);
+        dac.write_u16(IRE_m40);
+        dac.write_u16(IRE_m40);
+        dac.write_u16(IRE_m40);
+        dac.write_u16(IRE_m40);
+//        dac.write_u16(IRE_m40); //extra for PAL timing
+        dac.write_u16(IRE_0); //Breezeway   0.5us
+        dac.write_u16(IRE_0); //ColorBurst  2.5us
+        dac.write_u16(IRE_0);
+        dac.write_u16(IRE_0);
+        dac.write_u16(IRE_0);
+        dac.write_u16(IRE_0);
+        dac.write_u16(IRE_m40); //Back Porch  1.6us (2.55us in PAL)
+        dac.write_u16(IRE_m40);
+        dac.write_u16(IRE_m40);
+        dac.write_u16(IRE_m40); //extra for PAL timing
+        dac.write_u16(IRE_m40); //extra for PAL timing
+
+//        for (int j = 0; j < DRAWWIDTH; j++) {
+//            dac.write_u16(IRE_0);
+//        }  //next pixel
+
+
+        //Then that video signal for 52.6us (52 for PAL)
+        dac.write_u16(IRE_0); //Video signal for 52.6us
+        wait_us(51); // replaces another 104 dac.write_u16(IRE_0)
+    }
+
+
+//Draw the actual visible lines on screen: exactly same header as previous, but followed by real video data.
+    // intentionally dropping the last few lines to throw some time to main()
+    // otherwise this loop would use 100% of CPU.
+    for (int i = 0; i < LINES; i++) {
+        //10.9us (NTSC) or 12.5us (PAL) for horizontal blanking interval
+        dac.write_u16(IRE_0); //Front porch 1.6us
+        dac.write_u16(IRE_0);
+        dac.write_u16(IRE_0);
+        dac.write_u16(IRE_m40); //Sync Tip    4.7us
+        dac.write_u16(IRE_m40);
+        dac.write_u16(IRE_m40);
+        dac.write_u16(IRE_m40);
+        dac.write_u16(IRE_m40);
+        dac.write_u16(IRE_m40);
+        dac.write_u16(IRE_m40);
+        dac.write_u16(IRE_m40);
+        dac.write_u16(IRE_m40);
+//       dac.write_u16(IRE_m40); //extra for PAL timing
+        dac.write_u16(IRE_0); //Breezeway   0.5us
+        dac.write_u16(IRE_0); //ColorBurst  2.5us
+        dac.write_u16(IRE_0);
+        dac.write_u16(IRE_0);
+        dac.write_u16(IRE_0);
+        dac.write_u16(IRE_0);
+        dac.write_u16(IRE_m40); //Back Porch  1.6us (2.55us in PAL)
+        dac.write_u16(IRE_m40);
+        dac.write_u16(IRE_m40);
+        dac.write_u16(IRE_m40); //extra for PAL timing
+        dac.write_u16(IRE_m40); //extra for PAL timing
+
+        //Then that video signal for 52.6us (52 for PAL):
+
+        ////////////////////////////////////////////////////////////
+        //Write out the video data
+        //(very timing sensitive as must last ~53us, no more or less)
+        ////////////////////////////////////////////////////////////
+        
+        // Examples:
+        
+        //1) draw random shade per line
+        // dac.write_u16(rand() % 40000 + IRE_7p5);    //Video signal for 52.6us
+        // wait_us(52);
+        
+        //2) draw black
+        // dac.write_u16(IRE_7p5);
+        // wait_us(51);
+        
+        //3) draw white
+        // dac.write_u16(IRE_100);
+        // wait_us(51);
+        
+        //4) draw framebuffer
+        
+        // Code here is very timing critical.
+
+        // loop count is instruction dependent, if you add some code here, it will need be a different width
+        // We have ~52.5us and a a dac write takes 0.5us so 104/105px seems correct.
+        // Trial+error shows ~100-110 pixels to be OKish on a particular old TV.
+
+        int k =(i/ SCAN ); //double scan the framebuffer, particularly convenient for 128 vertical px but 256 line resolution..
+
+        // The modulo is only needed if screen output size is bigger than framebuffer (wrapping occurs).
+        // Stick to powers of 2 for modulo wrapping (or likely too slow).
+        for (int j = 0; j < DRAWWIDTH; j++) {
+            dac.write_u16( framebuffer[k%128][j%128] ); //modulo used to wrap framebuffer. Keep to power of 2 =< framebuffer sizes.
+        }  //next pixel
+
+    } //next line loop
+    
+     //Default back to black when we don't bother drawing the last few lines of a frame!
+    dac.write_u16(IRE_7p5); 
+}  //End of createframe routine
+
+
+
+////////////////////////////////////////////////////////////
+// randomfill the framebuffer                             //
+////////////////////////////////////////////////////////////
+void randomfill () {
+    for (int j = 0; j < HEIGHT; j++) {
+        for (int i = 0; i < WIDTH; i++) {
+            framebuffer[j][i] = rand();
+            if (framebuffer[j][i] < IRE_7p5 ) {
+                framebuffer[j][i] = IRE_7p5;
+            }
+        }
+    }
+}
+
+////////////////////////////////////////////////////////////
+// blank the framebuffer                                  //
+////////////////////////////////////////////////////////////
+void blankfill () {
+    for (int j = 0; j < HEIGHT; j++) {
+        for (int i = 0; i < WIDTH; i++) {
+            framebuffer[j][i] = IRE_7p5;
+            //framebuffer[j][i] = IRE_100;
+        }
+    }
+}
+
+////////////////////////////////////////////////////////////
+// zooming mandelbot fractal in the framebuffer           //
+////////////////////////////////////////////////////////////
+
+void mandelbrot () {
+//Mandelbrot escape time algorithm (doubles+iteration=slow)
+//Taken from wikipedia pseudocode,
+//tweaked by using the speeded up version that google found on geocities
+//(oops - Geocities has shut down in the 3 weeks since I wrote this! first time I've used it in years!)
+//http://www.geocities.com/CapeCanaveral/5003/Mandel.txt
+//then put in a loop to zoom in on a intersting co-ords point i saw elsewhere...
+    double zoom;
+    for (int z = 0; z < 200; z++) {//2^50 is quite a lot of zoom - thats why you need precision!
+        zoom= pow((double)1.2,z);
+
+        led1=0;
+        led2=0;
+        led3=0;
+        led4=0;
+
+        double x,y;
+        double x0,y0;
+        //            double xtemp;
+        double xsq;
+        double ysq;
+
+        unsigned short int iteration = 0;
+        unsigned short int max_iteration = (z*2)+20; //arbitrary scaling so there are more interation allowed as you zoom
+        for (int j = 0; j < HEIGHT; j++) {
+            //little status hack as as drawing fractals (particularly with doubles on only 10% of a cpu is slow!)
+            if (j== (( HEIGHT /4)-1)) {
+                led1=1;
+            } else if (j==(( HEIGHT /2)-1)) {
+                led2=1;
+            } else if (j==(3*( HEIGHT /4)-1)) {
+                led3=1;
+            } else if (j==( HEIGHT -1)) {
+                led4=1;
+            }
+            //end of little status hack
+
+
+            for (int i = 0; i < WIDTH; i++) {
+                //            x0=(((float) i) -32.0)/32.0;//redefine 0to63 as -1to+1 mandelbrot window
+                //            y0=(((float) j) -32.0)/32.0;//redefine 0to63 as -1to+1 mandelbrot window
+                //-1.865725138512217656771 moves center point to something interesting
+                x0=((((double) i) - ( WIDTH /2)) /zoom)-1.865725138512217656771;//redefine 0to63 as -1to+1 mandelbrot window
+                y0=(((double) j) - ( HEIGHT /2)) /zoom;//redefine 0to63 as -1to+1 mandelbrot window
+                iteration = 0;
+
+                //Standard version of mandelbrot loop based on wikipedia pseudocode
+                //                    x=0;
+                //                    y=0;
+                //                    while ( ((x*x + y*y) <= (2*2))  &&  (iteration < max_iteration) ) {
+                //                        xtemp = x*x - y*y + x0;
+                //                        y = 2*x*y + y0;
+                //                        x = xtemp;
+                //                        iteration++;
+                //                    }
+
+                //Speedy version of main mandelbrot loop (algorithm from geocities page)
+                x=x0+x0*x0-y0*y0;
+                y=y0+x0*y0+x0*y0;
+                for (iteration=0;iteration<max_iteration && (ysq=y*y)+(xsq=x*x)<4;iteration++,y=y0+x*y+x*y,x=x0-ysq+xsq) ;
+
+                //Iteration count determines color (clamp max iteration to zero, and normalize for black to white)
+                framebuffer[j][i] = (( iteration == max_iteration ) ? (IRE_7p5) : (IRE_7p5 + ((iteration%20)*2000)) );
+            }
+        }
+    }//zoom loop
+}
+
+////////////////////////////////////////////////////////////
+// fire the framebuffer                                   //
+////////////////////////////////////////////////////////////
+// based on demo at http://demo-effects.sourceforge.net/
+// refer back to original code for how it should be done :-)
+void fire () {
+    static unsigned char fire[WIDTH * HEIGHT];
+    int i,j,index,temp;
+
+    /* draw random bottom line in fire array*/
+
+    j = WIDTH * (HEIGHT- 1);
+    for (i = 0; i < WIDTH - 1; i++) {
+        int random = 1 + (int)(16.0 * (rand()/(RAND_MAX+1.0)));
+        if (random > 8) /* the lower the value, the intenser the fire, compensate a lower value with a higher decay value*/
+            fire[j + i] = 255; /*maximum heat*/
+        else
+            fire[j + i] = 0;
+    }
+
+    /* move fire upwards, start at bottom*/
+
+    for (index = 0; index < HEIGHT-4 ; ++index) {
+        for (i = 0; i < WIDTH - 1; ++i) {
+            if (i == 0) { /* at the left border*/
+                temp = fire[j];
+                temp += fire[j + 1];
+                temp += fire[j - WIDTH];
+                temp /=3;
+            } else if (i == WIDTH - 1) { /* at the right border*/
+                temp = fire[j + i];
+                temp += fire[j - WIDTH + i];
+                temp += fire[j + i - 1];
+                temp /= 3;
+            } else {
+                temp = fire[j + i];
+                temp += fire[j + i + 1];
+                temp += fire[j + i - 1];
+                temp += fire[j - WIDTH + i];
+                temp >>= 2;
+            }
+            if (temp > 1)
+                temp -= 1; /* decay */
+
+            fire[j - WIDTH + i] = temp;
+        }
+        j -= WIDTH;
+    }
+
+//dirty hack to convert the fire data to the framebuffer. better graduation of shades would look better!
+    for (int j = HEIGHT-3 ; j >= 0; --j) {
+        for (int i = WIDTH-1; i >= -1 ; --i) {
+            framebuffer[j][i] = ((fire[j * WIDTH + i])*150)+IRE_7p5;
+        }
+    }
+
+
+}
+
+
+////////////////////////////////////////////////////////////
+// main() showing use framebuffer                         //
+// Puts a grayscale pic in it                             //
+////////////////////////////////////////////////////////////
+
+int main() {
+
+    blankfill(); //set framebuffer to blank values
+
+    timer.attach_us(&createframe,20000);//attach the display (at 50Hz)
+
+    // int attached=0; //attach frame
+    // //If you had a lot of setup in a main game loop, you could do something like this:
+    // if (attached==0) {
+    //     timer.attach_us(&createframe,20000);
+    //     attached=1;
+    // }
+
+    //Program loop
+    while (1) {
+        // Add you own demo code here. Expect it to get regularly interupted by the screen draw call!
+        // very simple code can run at full fps.
+        //Example - change HEIGHT to 64 and SCAN to 4 and use randomfill...
+        //randomfill();  //random pixel fill
+
+        //Example - change HEIGHT to 86 and SCAN to 3 and use fire;    
+        fire();
+
+        //Example - change HEIGHT to 128 and SCAN to 2 and use mandelbrot...         
+        //mandelbrot(); //mandelbrot procedure is a 200 loop zoom so takes ages - and each scene redraw takes a few seconds!
+    } //while
+    
+} //main
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mbed.bld	Sun Nov 15 17:24:38 2009 +0000
@@ -0,0 +1,1 @@
+http://mbed.org/users/mbed_official/code/mbed/builds/737756e0b479