Dependencies:   mbed

Files at this revision

API Documentation at this revision

Comitter:
robyounger
Date:
Sun Nov 15 15:52:54 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
diff -r 000000000000 -r fb93ebe5f84f composite.cpp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/composite.cpp	Sun Nov 15 15:52:54 2009 +0000
@@ -0,0 +1,475 @@
+////////////////////////////////////////////////////////////
+// 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)     //
+////////////////////////////////////////////////////////////
+
+// 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 128
+
+//TV signal generation controlling:
+#define LINES 256 //Visible lines drawn to screen
+#define SCAN 2 //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
+}
+
+
+////////////////////////////////////////////////////////////
+// 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 instead of mandelbrot...
+
+        //randomfill();  //random pixel fill
+        mandelbrot(); //mandelbrot procedure is a 200 loop zoom so takes ages - and each scene redraw takes a few seconds!
+    } //while
+    
+} //main
+
+
diff -r 000000000000 -r fb93ebe5f84f mbed.bld
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mbed.bld	Sun Nov 15 15:52:54 2009 +0000
@@ -0,0 +1,1 @@
+http://mbed.org/users/mbed_official/code/mbed/builds/737756e0b479