Turn your Uno into a VGA output device!

Do keep us updated on this...Wicked stuff, no doubt !!

I'm very interested in this. I also found this: Google Code Archive - Long-term storage for Google Code Project Hosting. which has currently : 64x64 - 64colors

I'm Nabe.
I'm glad that you were interested in my work.

That work started from here.
http://arduino.cc/forum/index.php/topic,76020.15.html
Sorry, I have been very busy so that project has been stopped.
I have not to do programing for several month. =(
http://arduino.cc/forum/index.php/topic,76020.45.html

one clock resolition soft timer - it was able to achieve.
So, I drwew "sine wave".
I had upped last version video before.
Sorry, the sound is too loud.

I have made some video signals with Arduino and registers.

To realize that it has a variety of techniques.
Many pepole generate many ways.
So It's intersting!
Limit the ability of Arduino seems to be beyond more than we think.

Let's enjoy with Arduino!

Thanks!
sorry I'm poor in English.

Thanks for that! (I had to turn the music off, sorry).

I haven't done "moving" graphics yet but I thought a "pong" game would be interesting.

one clock resolition soft timer - it was able to achieve.

How did you do that?

very very sorry too loud music :cold_sweat:

one clock resolition soft timer - it was able to achieve.

How did you do that?

ATmega328 - NOP -> 1clock
I did a simulation for operation check.
Arduino IDE generate .elf file. So I opened the ".elf " file with AVRstudio(free soft) and simulated.
https://picasaweb.google.com/117534855759200010314/Twitter_Recently#5680380556146248114

I got a advice in this forum.
http://arduino.cc/forum/index.php/topic,80320.0.html

I had upped the code to Google Code site.
can download from here.
http://code.google.com/p/arduino-vgaout/downloads/list
INT_ICF1_RGB_DRAW.cpp - interrupt routine for line draw
INT_OutDraw.cpp - one clock resolution soft delay

sorry poor code.
I have studied C++.

Thanks!

I modified my sketch to output a sine wave:

It could be faster if I pre-calculated the sine figures and stored them in flash memory.

/*
 VGA colour video generation - Sine wave generation
 
 Author:   Nick Gammon
 Date:     22nd April 2012
 Version:  1.0
 
 Version 1.0: initial release

 Connections:
 
 D3 : Horizontal Sync (68 ohms in series) --> Pin 13 on DB15 socket
 D4 : Red pixel output (470 ohms in series) --> Pin 1 on DB15 socket
 D5 : Green pixel output (470 ohms in series) --> Pin 2 on DB15 socket
 D6 : Blue pixel output (470 ohms in series) --> Pin 3 on DB15 socket
 D10 : Vertical Sync (68 ohms in series) --> Pin 14 on DB15 socket
 
 Gnd : --> Pins 5, 6, 7, 8, 10 on DB15 socket


 Note: As written, this sketch has 34 bytes of free SRAM memory.
 
 PERMISSION TO DISTRIBUTE
 
 Permission is hereby granted, free of charge, to any person obtaining a copy of this software 
 and associated documentation files (the "Software"), to deal in the Software without restriction, 
 including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 
 and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 
 subject to the following conditions:
 
 The above copyright notice and this permission notice shall be included in 
 all copies or substantial portions of the Software.
 
 
 LIMITATION OF LIABILITY
 
 The software is provided "as is", without warranty of any kind, express or implied, 
 including but not limited to the warranties of merchantability, fitness for a particular 
 purpose and noninfringement. In no event shall the authors or copyright holders be liable 
 for any claim, damages or other liability, whether in an action of contract, 
 tort or otherwise, arising from, out of or in connection with the software 
 or the use or other dealings in the software. 

*/

#include <TimerHelpers.h>
#include <avr/pgmspace.h>
#include <avr/sleep.h>

const byte hSyncPin = 3;     // <------- HSYNC

const byte redPin = 4;       // <------- Red pixel data
const byte greenPin = 5;     // <------- Green pixel data
const byte bluePin = 6;      // <------- Blue pixel data

const byte vSyncPin = 10;    // <------- VSYNC

const int horizontalBytes = 50;  // 480 pixels wide
const int verticalPixels = 480;  // 480 pixels high

// Timer 1 - Vertical sync

// output    OC1B   pin 16  (D10) <------- VSYNC

//   Period: 16.64 mS (60 Hz)
//      1/60 * 1e6 = 16666.66 uS
//   Pulse for 64 uS  (2 x HSync width of 32 uS)
//    Sync pulse: 2 lines
//    Back porch: 33 lines
//    Active video: 480 lines
//    Front porch: 10 lines
//       Total: 525 lines

// Timer 2 - Horizontal sync

// output    OC2B   pin 5  (D3)   <------- HSYNC

//   Period: 32 uS (31.25 kHz)
//      (1/60) / 525 * 1e6 = 31.74 uS
//   Pulse for 4 uS (96 times 39.68 nS)
//    Sync pulse: 96 pixels
//    Back porch: 48 pixels
//    Active video: 640 pixels
//    Front porch: 16 pixels
//       Total: 800 pixels

// Pixel time =  ((1/60) / 525 * 1e9) / 800 = 39.68  nS
//  frequency =  1 / (((1/60) / 525 * 1e6) / 800) = 25.2 MHz

// However in practice, it we can only pump out pixels at 375 nS each because it
//  takes 6 clock cycles to read one in from RAM and send it out the port.

const int verticalLines = verticalPixels / 16;  
const int horizontalPixels = horizontalBytes * 8;

const byte verticalBackPorchLines = 35;  // includes sync pulse?
const int verticalFrontPorchLines = 525 - verticalBackPorchLines;

volatile int vLine;
volatile int messageLine;
volatile int backPorchLinesToGo;
volatile byte newFrame;

#define nop asm volatile ("nop\n\t")

// bitmap - gets sent to PORTD
// For D4/D5/D6 bits need to be shifted left 4 bits
//  ie. 00BGR0000

char message [verticalLines]  [horizontalBytes];

// ISR: Vsync pulse
ISR (TIMER1_OVF_vect)
  {
  vLine = 0; 
  messageLine = 0;
  backPorchLinesToGo = verticalBackPorchLines;
  newFrame = true;
  } // end of TIMER1_OVF_vect
  
// ISR: Hsync pulse ... this interrupt merely wakes us up
ISR (TIMER2_OVF_vect)
  {
  backPorchLinesToGo--;    
  } // end of TIMER2_OVF_vect


void setup()
  {
  
  // initial bitmap ... change to suit
  for (int y = 0; y < verticalLines; y++)
    for (int x = 0; x < horizontalBytes; x++)
      message [y] [x] = (7) << 4;
   
  // disable Timer 0
  TIMSK0 = 0;  // no interrupts on Timer 0
  OCR0A = 0;   // and turn it off
  OCR0B = 0;
  
  // Timer 1 - vertical sync pulses
  pinMode (vSyncPin, OUTPUT); 
  Timer1::setMode (15, Timer1::PRESCALE_1024, Timer1::CLEAR_B_ON_COMPARE);
  OCR1A = 259;  // 16666 / 64 uS = 260 (less one)
  OCR1B = 0;    // 64 / 64 uS = 1 (less one)
  TIFR1 = _BV (TOV1);   // clear overflow flag
  TIMSK1 = _BV (TOIE1);  // interrupt on overflow on timer 1

  // Timer 2 - horizontal sync pulses
  pinMode (hSyncPin, OUTPUT); 
  Timer2::setMode (7, Timer2::PRESCALE_8, Timer2::CLEAR_B_ON_COMPARE);
  OCR2A = 63;   // 32 / 0.5 uS = 64 (less one)
  OCR2B = 7;    // 4 / 0.5 uS = 8 (less one)
  TIFR2 = _BV (TOV2);   // clear overflow flag
  TIMSK2 = _BV (TOIE2);  // interrupt on overflow on timer 2
 
  // prepare to sleep between horizontal sync pulses  
  set_sleep_mode (SLEEP_MODE_IDLE);  
  
  // pins for outputting the colour information
  pinMode (redPin, OUTPUT);
  pinMode (greenPin, OUTPUT);
  pinMode (bluePin, OUTPUT);
  
}  // end of setup

// draw a single scan line
boolean doOneScanLine ()
  {
    
  // after vsync we do the back porch
  if (backPorchLinesToGo > 0)
    {
    backPorchLinesToGo--;
    return false;   
    }  // end still doing back porch
    
  // if all lines done, do the front porch
  if (vLine == verticalPixels)
    return newFrame;
    
  // pre-load pointer for speed
  register char * messagePtr =  & (message [messageLine] [0] );

  delayMicroseconds (1);
  
  // how many pixels to send
  register byte i = horizontalBytes;

  // blit pixel data to screen    
  while (i--)
    PORTD = * messagePtr++;

  // stretch final pixel
  nop; nop; nop;
  
  PORTD = 0;  // back to black
  // finished this line 
  vLine++;

  // every 16 pixels it is time to move to a new line in our text
  if ((vLine & 0xF) == 0)
    messageLine++;
    
  return false;
  }  // end of doOneScanLine

float radians = 0;
const float pi = 3.1415926;
const float radiansIncrement = (pi / 2.0) / (horizontalBytes / 2);
byte x;
boolean Up = true;
byte colour = 0;
boolean Calc = true;

void advanceLine ()
  {
  if (Calc)
    {
    x = sin (radians) * horizontalBytes;
    if (Up)
      {
      radians += radiansIncrement;
      if (radians >= pi / 2)
        Up = false;
      }
    else
      {
      radians -= radiansIncrement;
      if (radians <= 0)
        {
        Up = true;
        radians = 0;
        colour++;
        }
      }
    Calc = false;
    }
  else
    {
    memmove (& message [0] [0], & message [1] [0], sizeof message - horizontalBytes);  
    memset (&message [verticalLines - 1] [0], (colour + 1) << 4, horizontalBytes);
    memset (&message [verticalLines - 1] [0], colour << 4, x);
    Calc = true;
    }

  newFrame = false;
  }
  
void loop() 
  {
  // loop to avoid overhead of function call
  while (true)
    {
    // sleep to ensure we start up in a predictable way
    sleep_mode ();
    if (doOneScanLine ())
      advanceLine ();
    }  // end of while
 }  // end of loop

I know this is just a proof of concept more than anything, but wouldnt there be a way of centring the display, despite the limitations u already mentioned ?!? Sorry for my "doh!!!"ness on the subject !

Sure. But my particular monitor is "smart" enough to left-justify on the start of the visible area. Initially when I tested it was more centered. I strongly suspect that the monitor firmware tries to detect where the visible signal starts (because different VGA cards might be out by a few nanonseconds one way or another) and then remembers something like "oh this card has a back porch of 2.1 uS, so we'll start drawing after that each time".

Judging by the logic analyzer output the signal is fairly correctly centered.

The monitor has a "horizontal position" menu setting. If I play with that I can center it. Ditto for vertical.

This video shows the text-based output in action. You can see how quickly the text draws, and it zooms in to show the clarity of the letters. You can also see commands from the second Uno to clear the screen, clear a line, do cursor addressing, and scrolling:

I saw a method of doing this the other day that used the SPI shift register to spit the bits out, that took a large load of the uC as you just have to handle V and H-sync. Only mono of course.


Rob

Was that my method? I am using SPI and the output is mono.

But the processor still has to work pretty hard (well it would get bored otherwise wouldn't it?).

You have 32.746 uS to draw a line. The line consists of 20 characters (160 pixels). To spit them out using SPI means sending 20 bytes. The highest clock rate you can output with SPI is the processor clock rate / 2, which is once every 125 nS. So you must take:

125 * 20 * 8 = 20000 nS (20 uS).

So that leaves 12 uS over. It will take about 3 to enter and leave the ISR, which is required for synchronization. A couple more to load up the register with the correct line information. So really, not much spare time.

May have been, I guess you know about it then :slight_smile:


Rob

From another thread:

db2db:
Your video project looks really nice. Do you have an easy suggestion if I just want larger letters or numbers on the screen?

Or maybe just a single large number?

For something large I would probably go with the "colour" version which basically draws a large bitmap: 60 x 30 pixels.

Then store the patterns for whatever you want to display in progmem. The original text version has the bitmaps for every letter in it (one pit per pixel). You could expand that out to one byte per pixel. Then a letter or number would be 8 x 8 pixels, so you could fit 7 across and about 3 down. That also lets you choose the colour of each letter.

You can also see commands from the second Uno to clear the screen, clear a line, do cursor addressing, and scrolling:

Can you do text scrolling? just sending a text to the output atmega and make it scroll over the screen (left to right)? Could you show a video on that, like in the video I showed you before?

Thanks.

I haven't built in a "scroll sideways" interface option at this stage. Personally I wouldn't because of the artifacts you get when updating the screen buffer, so "smoothly scrolling sideways text" probably isn't going to work ... unless you can solve the timing issues.

You could do it to an extent by doing something like:

  • Carriage-return (take you to column 1)
  • Clear to end of line
  • Cursor position to desired column
  • Draw desired text

I suggest you make one up and try it out - the parts are only about $5 (depending on how much the connector costs).

I think it would be perfect for something like a home security system / weather monitoring where you want to have more text than you can fit onto an LCD, and don't mind a bit of flicker when it updates.

You could do it to an extent by doing something like:

Carriage-return (take you to column 1)
Clear to end of line
Cursor position to desired column
Draw desired text

wouldn't that make it scroll per character? I was thinking a per pixel scroll.
But I guess you're system is only able to position in rows and columns and not in pixels. So actually yours is a great system for text output aka LCD character displays

I suggest you make one up and try it out - the parts are only about $5

you are right, I should. Let's see if I can clip some things.

JO3RI:
wouldn't that make it scroll per character? I was thinking a per pixel scroll.

That's right. But to store individual pixels is going to take a lot of memory. As it is, each character is looked-up from a font array on the fly.

This isn't supposed to be as fancy as a sideways scrolling shooter. Not with a $5 chip.

The intention was to show that you could display 20 x 30 characters just using a Uno, and not having to spend $30 on a graphics board. And quite possibly more if you wanted bitmapped graphics.

The intention was to show that you could display 20 x 30 characters just using a Uno, and not having to spend $30 on a graphics board. And quite possibly more if you wanted bitmapped graphics.

yeah, sorry about that. But the thing is, I have been with the video-out library in my head the whole time. So I was thinking, you where making the same thing but for vga-out (and maybe it wouldn't be a bad idea, to do so)

any way good work. You could say, if you want something like the video-out library, why don't you try to make it yourself? Well it's over my head. :roll_eyes: I know how to use it, but not how to make it.

Try this:

and:

It looks like the vertical is the same (60 Hz) so Timer 1 would be unchanged.
It also looks like the vertical sync pulses are the same (it still uses 525 lines).

So I would be inclined to try and see.

If what I read is correct the colour information is sent over 4 consecutive lines, I'm not too sure about that and don't have a CGA monitor to test it on. So you might get something to appear if you tweak delays a bit (eg. the delay at the start of each line).

Is it really a CRT (i.e. is it quite deep behind the screen?) or is it a plasma display?

Wow, a genuine "luggable".


Rob