Rob Younger
/
fire_demo
Revision 0:f81b117f7df3, committed 2009-11-15
- 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