Arduino + MAX7456 OSD

Being able to overlay text on video has a number of uses. I needed this ability while building a couple of different SMPTE timecode readers. While there were a selection of inexpensive OSD chips, for use in VHS and TV sets, that market seems to dried up lately. Luckily Maxim has just come out with new OSD chip.

This is a platform for experimenting with the MAX7456 OSD chip. It is a surface mount chip and is more easily used if attached to breakout board.

The shield wiring is pretty straight forward, +5, ground some .1 bypass caps, however you can get it all to fit the best. Please see the schematic.

Schematic

Parts you will need:
5 .1uf caps
2 75 ohm resistors
1 10K ohm resistor
1 100uf cap
1 22uf cap
1 27Mhx crystal
1 SparkFun 28 pin SSOP to DIP Adapter
1 NKC proto shield
1 28 DIP socket

The OSD accepts commands from the Arduino over the SPI bus. I did comment the code, more then I normally do anyway. So I hope is easy to follow. Once you get the board setup you can issue serial commands from the sketch serial monitor, or HyperTerm. You can also create a text file and download it into the overlay.

Part 1

#include <EEPROM.h> //Needed to access eeprom read/write functions

#define DATAOUT 11//MOSI
#define DATAIN  12//MISO 
#define SPICLOCK  13//sck
#define MAX7456SELECT 10//ss
#define VSYNC 2// INT0

//MAX7456 opcodes
#define DMM_reg   0x04
#define DMAH_reg  0x05
#define DMAL_reg  0x06
#define DMDI_reg  0x07
#define VM0_reg   0x00
#define VM1_reg   0x01

//MAX7456 commands
#define CLEAR_display 0x04
#define CLEAR_display_vert 0x06
#define END_string 0xff
// with NTSC
#define ENABLE_display 0x08
#define ENABLE_display_vert 0x0c
#define MAX7456_reset 0x02
#define DISABLE_display 0x00

// with PAL
// all VM0_reg commands need bit 6 set
//#define ENABLE_display 0x48
//#define ENABLE_display_vert 0x4c
//#define MAX7456_reset 0x42
//#define DISABLE_display 0x40

#define WHITE_level_80 0x03
#define WHITE_level_90 0x02
#define WHITE_level_100 0x01
#define WHITE_level_120 0x00

// with NTSC
#define MAX_screen_size 390
#define MAX_screen_rows 13

// with PAL
//#define MAX_screen_size 480
//#define MAX_screen_rows 16

#define EEPROM_address_hi 510
#define EEPROM_address_low 511
#define EEPROM_sig_hi 'e'
#define EEPROM_sig_low 's'

volatile byte screen_buffer[MAX_screen_size];

volatile byte writeOK;
volatile byte valid_string;
volatile byte save_screen;
volatile int  incomingByte;
volatile int  count;

//
//##############################
//# 30 characters per line     #
//# 13 lines per screen        #
//# start'[' end ']' 390 chars #
//# add '~' if front of '[' to #
//# force write to eeprom      #
//# '~[]' to zero out eeprom   #
//##############################
//
//[
//
//
//       THIS IS A TEST
//
//    Arduino / MAX7456-OSD
//
//  ABCDEFGHIJKLMNOPQRSTUVWXYZ
//         0123456789
//]
//

//////////////////////////////////////////////////////////////
void setup()
{
  byte spi_junk, eeprom_junk;
  int x;
  Serial.begin(9600);
  Serial.flush();

  pinMode(MAX7456SELECT,OUTPUT);
  digitalWrite(MAX7456SELECT,HIGH); //disable device

  pinMode(DATAOUT, OUTPUT);
  pinMode(DATAIN, INPUT);
  pinMode(SPICLOCK,OUTPUT);
  pinMode(VSYNC, INPUT);

  // SPCR = 01010000
  //interrupt disabled,spi enabled,msb 1st,master,clk low when idle,
  //sample on leading edge of clk,system clock/4 rate (4 meg)
  SPCR = (1<<SPE)|(1<<MSTR);
  spi_junk=SPSR;
  spi_junk=SPDR;
  delay(250);

  // force soft reset on Max7456
  digitalWrite(MAX7456SELECT,LOW);
  spi_transfer(VM0_reg);
  spi_transfer(MAX7456_reset);
  digitalWrite(MAX7456SELECT,HIGH);
  delay(500);

  // set all rows to same charactor white level, 90%
  digitalWrite(MAX7456SELECT,LOW);
  for (x = 0; x < MAX_screen_rows; x++)
  {
    spi_transfer(x + 0x10);
    spi_transfer(WHITE_level_90);
  }

  // make sure the Max7456 is enabled
  spi_transfer(VM0_reg);
  spi_transfer(ENABLE_display);
  digitalWrite(MAX7456SELECT,HIGH);


  // clear the array
  for (x = 0; x < MAX_screen_size; x++)
  {
    screen_buffer[x] = 0x00;
  }

  writeOK = false;
  valid_string = false;
  save_screen = false;
  incomingByte = 0;
  count = 0;

 // check to see if we have a default screen stored in eeprom
  eeprom_junk = EEPROM.read(EEPROM_address_hi);
  if (eeprom_junk == EEPROM_sig_hi)
  {
    eeprom_junk = EEPROM.read(EEPROM_address_low);
    if (eeprom_junk == EEPROM_sig_low)
    {
      for (x = 0; x < MAX_screen_size; x++)
      {
        screen_buffer[x] = EEPROM.read(x);
      }
      writeOK = true;
      count = MAX_screen_size;
    }
  }

  Serial.println("Ready for text file download");
  Serial.println("");
  delay(100);  
}

Arduino with MAX7456 Shield

**
[this is a test] prints on line one upper left screen

**
[
this is a test] prints on the third line center of the screen

**
[] erases the video overlay

**
~[this is a test] writes the text to eeprom memory and displays it at boot time

**
~[] zeros out the eeprom

Output

Here is Part 2 of the Ardunio + MAX7456 code

//////////////////////////////////////////////////////////////
void loop()
{
  int x, junk;
  
  if (Serial.available() > 0)
  {
    // read the incoming byte:
    incomingByte = Serial.read();

    if (incomingByte == 126) // this is the '~' for saving the screen
      save_screen = true;
      
    switch(incomingByte)
    {
      case 0x5b: // [ start of text string
        //Serial.println("start of text");
        count = 0;
        valid_string = true;
      break;
      case 0x0a: // line feed, ignore
        //Serial.println("ln");   
      break;
      case 0x0d: // carridge return, skip to start of next line
        //Serial.println("cr");   
        if (count < 30)
          count = 30;
        else
        {
          junk = (count % 30);
          count = (count + (30 - junk));
        }
      break;
      case 0x5d: // ] end of text
        //Serial.println("end of text");
        Serial.flush();
        valid_string = false;
        writeOK = true;
      break;
      default:
        if (valid_string)
        {
          //Serial.println("buffer chars");
          if (count == MAX_screen_size) // something is wrong, too many characters
            count = count -1;  // drop it back one to position 389 
          screen_buffer[count] = convert_ascii(incomingByte);
          count++;
        }
      break;
    }
  }

  if (writeOK)
  {
    writeOK = false;
    write_new_screen();

    // have we received a '~' to save screen to eeprom
    if (save_screen)
    {
      save_screen = false;
      
      // write contents of screen buffer to eeprom
      if (count > 0)
      {
        for (x = 0; x < count; x++)
        {
          EEPROM.write(x, screen_buffer[x]);
        }
        EEPROM.write(EEPROM_address_hi, EEPROM_sig_hi);
        EEPROM.write(EEPROM_address_low, EEPROM_sig_low);
      }
      else // to erase all eeprom 'send ~[]'
      {
        for (x = 0; x < 512; x++)
        {
          EEPROM.write(x, 0);
        }
      }
    }

    // clear out screen_buffer
    for (x = 0; x < MAX_screen_size; x++)
    {
      screen_buffer[x] = 0x00;
    }
    count = 0;
  }  
}

//////////////////////////////////////////////////////////////
byte spi_transfer(volatile byte data)
{
  SPDR = data;                    // Start the transmission
  while (!(SPSR & (1<<SPIF)))     // Wait the end of the transmission
  {
  };
  return SPDR;                    // return the received byte
}

//////////////////////////////////////////////////////////////
byte convert_ascii (int character)
{
// for some reason the MAX7456 does not follow ascii letter
// placement, so you have to have this odd lookup table
// todo... create an eeprom table that matches ascii
// and burn to the MAX7456

  byte lookup_char;

  if (character == 32)
    lookup_char = 0x00; // blank space
  else if (character == 48)
    lookup_char = 0x0a; // 0
  else if ((character > 48) && (character < 58))
    lookup_char = (character - 48); // 1-9
  else if ((character > 64) && (character < 90))
    lookup_char = (character - 54); // A-Z
  else if ((character > 96) && (character < 123))
    lookup_char = (character - 60); // a-z
  else if (character == 34)
    lookup_char = 0x48; // "
  else if (character == 39)
    lookup_char = 0x46; // '
  else if (character == 40)
    lookup_char = 0x3f; // (
  else if (character == 41)
    lookup_char = 0x40; // )
  else if (character == 44)
    lookup_char = 0x45; // ,
  else if (character == 45)
    lookup_char = 0x49; // -
  else if (character == 46)
    lookup_char = 0x41; // .
  else if (character == 47)
    lookup_char = 0x47; // /
  else if (character == 58)
    lookup_char = 0x44; // :
  else if (character == 59)
    lookup_char = 0x43; // ;
  else if (character == 60)
    lookup_char = 0x4a; // <
  else if (character == 62)
    lookup_char = 0x4b; // >
  else if (character == 63)
    lookup_char = 0x42; // ?
  else
    lookup_char = 0x00; // out of range, blank space

 return (lookup_char);

}

//////////////////////////////////////////////////////////////
void write_new_screen()
{
  int x, local_count;
  byte char_address_hi, char_address_lo;
  byte screen_char;
  
  local_count = count;
  
  char_address_hi = 0;
  char_address_lo = 0;
 //Serial.println("write_new_screen");   

  // clear the screen
  digitalWrite(MAX7456SELECT,LOW);
  spi_transfer(DMM_reg);
  spi_transfer(CLEAR_display);
  digitalWrite(MAX7456SELECT,HIGH);

  // disable display
  digitalWrite(MAX7456SELECT,LOW);
  spi_transfer(VM0_reg); 
  spi_transfer(DISABLE_display);

  spi_transfer(DMM_reg); //dmm
  //spi_transfer(0x21); //16 bit trans background
  spi_transfer(0x01); //16 bit trans w/o background

  spi_transfer(DMAH_reg); // set start address high
  spi_transfer(char_address_hi);

  spi_transfer(DMAL_reg); // set start address low
  spi_transfer(char_address_lo);

  x = 0;
  while(local_count) // write out full screen
  {
    screen_char = screen_buffer[x];
    spi_transfer(DMDI_reg);
    spi_transfer(screen_char);
    x++;
    local_count--;
  }

  spi_transfer(DMDI_reg);
  spi_transfer(END_string);

  spi_transfer(VM0_reg); // turn on screen next vertical
  spi_transfer(ENABLE_display_vert);
  digitalWrite(MAX7456SELECT,HIGH);
}

WOW!

Great! I'm gonna try this for my FPV Plane!
How fast is the refresh rate on the Screen?

Thx
Geko

looks great :smiley:

The MAX7456 will display text as soon as you tell it to, or for a cleaner picture you can delay until the next vertical interval. It can be set for NTSC or PAL. so that would be 60 or 50 times a second.

There seems to be a lot of work with it happening in RC groups from Germany (thank-you babelfish) and I have seen some nice setups with GPS, direction, windspeed, all those good things for fliers.

I'm working next on a sketch that will change the MAX7456's character eeprom to match the ascii character order and content. That way I can remove the overhead of having a lookup table.

Thx a lot. May you send me the link from the Forum. I can speak German
so it would be easyer for me to read it there.

THx
Geko

Geko

Here are some links that I bookmarked while learning to work with the chip.
I hope they are of some use to you.

RCGroups: Remote Control, Radio Control Planes, Drones, Cars and Boats...=732783&page=4

http://cascade.dyndns.org/~cascade/scripts/max7456/

http://www.rcmovie.de/search/MAX7456

http://www.rclineforum.de/forum/thread.php?postid=2337043

http://www.roboternetz.de/phpBB2/viewtopic.php?t=39380

dfraser

Wow this is awesome! I just saw a post on RCG about the MAX 7456, and was wondering how difficult it would be to get it to work with arduino. Question answered and samples ordered! :o

Cheers,
-Z-

I have another addition to the pack of (hopefully forth coming) MAX7456 utilities. This one will allow you to change the eeprom font set. As it comes from Maxim the default burned in fonts are incomplete and in a rather strange order.

default fonts

This means if you are sending ascii data to the arduino for the MAX7456 to print on screen you have to use a rather convoluted lookup table.

So using the MAX7456 evaluation kit software I built this font set and burned it to my MAX7456 osd.

ascii fonts

This set has the proper offset and order for ascii text.

URL for the Maxim evaluation kit software

This utility creates text files that are representative bitmaps of the font set. Stored in a .mcm file will be 65000+ lines of

01010101
01010101
10101010

This has to be converted to a 'real' value and uploaded to the MAX7456.

01010101 = 0x55 = 4 translucent pixels
01010101 = 0x55 = 4 translucent pixels
10101010 = 0xaa = 4 white pixels

So here is file of the sketch code to do that,
http://home.comcast.net/~ds-fraser/forumimages/MAX7456_font.txt

and the .mcm file I built for the ascii set.
http://home.comcast.net/~ds-fraser/forumimages/ascii_font_set.mcm

When you run the sketch it will first print out the existing font set, wait until you upload the new .mcm file, parse and burn the new set to the MAX7456 and then print that to the screen.

before

after

I hope this is of some use, next I want to be able use the other half of the Eval software (display files) and parse and store the .mdm files.

dfraser

Looks like Arduino p0wns the MAX7456 :wink:

Nicely done!

You always think of things you should have added after you hit the 'Post' button.

I used Hyper Terminal to 'Send Text File' in uploading the .mcm files. It takes about 3min to complete the transfer and burning.

I set the serial speed at 9600, you may be able to go faster but as the MAX7456 takes 12ms to burn each character I thought to play it on the safe side.

Also make sure that at the end of the .mcm text file there is at least one carriage return / blank line.

I think that is all I forgot.

This might appear a dumb question, but how do you get the 'video out' to your TV screen? How does it hook up if say you have different input sources, (DVD, Sat. recv., XBox, etc.), to overlay on each of those sources? And what's the purpose of the video in?

(OK, three dumb questions, but all for the price of one!) :slight_smile:

It looks like a great addition to a current project of mine.

The MAX7456 is a composite video overlay device, so you would need to supply some form of video (to the video in). This could come from a camera, output of a VCR or DVD player.

The Arduino then receives serial data and transmits that to the MAX7456 (via spi) so it can overlay that text on the video signal and output that.

The video signal (with overlay) could then be put to TV set (video in) or recorded to tape.

I hope that answers your question.

dfraser

Thanks! Answers it perfectly!

Do the characters have a black outline? Maybe it is my eyes, but looking at the picture I don't think they have.

If I am correct, do you know if it is possible to change it so they do have a black outline? The reason I am asking for this is that the characters will be much better readable on light backgrounds.

Yes, all the characters have a single black pixel outline. Using the Maxim util you could edit any of them to say have a two pixel black outline if you wanted. Also by setting a bit in the command sent to the MAX7456, you can just invert the entire character to black.

dfraser

Hey your code worked perfectly! (Well, after I changed NTSC to PAL, duh!)

Here's my setup:

Breadboard:

After I got it working on breadboard, I put everything together so it will fit in my RC plane:

Hello World!!

Thanks a lot for sharing your work, much less work for me to get it working! :slight_smile:

Cheers,
-Z-

Thank you dfraser,

This is an excellent building block for MAX7456 OSD work. I'm just waiting for a breakout board to mount the MAX device now.

Your code will be a magical head start for me in my intended application.

Thank you very much for sharing your code with us all.

With Regards

Mike.

Hi,

Just to return the favour, I've made an easier to use write_to_screen function that allows you to print a string to a (x,y) location on the screen, and set whether you want it to blink or invert colour:

Example:

OSD_write_to_screen("Hello World", 3, 5, 1,0);

// ============================================================   WRITE TO SCREEN
void OSD_write_to_screen(const char s[], byte x, byte y, byte blink, byte invert){
  unsigned int linepos;
  byte local_count;
  byte settings, char_address_hi, char_address_lo;
  byte screen_char;

  local_count = 0;

  char_address_hi = 0;
  char_address_lo = 0;
  
  /*
  Serial.print(x,DEC);
  Serial.print(" - ");
  Serial.print(y,DEC);
  */
  
  // convert x,y to line position
  linepos = y*30+x;
  
  // divide in to hi & lo byte
  char_address_hi = linepos >> 8;
  char_address_lo = linepos;
  
  // clear the screen
  //OSD_clear();
  
  settings = B00000001;
  
  // set blink bit
  if (blink) {
    settings |= (1 << 4);       // forces nth bit of x to be 1.  all other bits left alone.
    //x &= ~(1 << n);      // forces nth bit of x to be 0.  all other bits left alone.  
  }
  // set invert bit
  if (invert){
    settings |= (1 << 3);       // forces nth bit of x to be 1.  all other bits left alone.
  }

  
  digitalWrite(MAX7456SELECT,LOW);

  spi_transfer(DMM_reg); //dmm
  spi_transfer(settings); 

  spi_transfer(DMAH_reg); // set start address high
  spi_transfer(char_address_hi);

  spi_transfer(DMAL_reg); // set start address low
  spi_transfer(char_address_lo);
  
  
  while(s[local_count]!='\0') // write out full screen
  {
    screen_char = s[local_count];
    spi_transfer(DMDI_reg);
    spi_transfer(screen_char);
    local_count++;
  }
  
  spi_transfer(DMDI_reg);
  spi_transfer(END_string);
  
  spi_transfer(DMM_reg); //dmm
  spi_transfer(B00000000); 

  
  spi_transfer(VM0_reg); // turn on screen next vertical
  spi_transfer(B01001100);
  digitalWrite(MAX7456SELECT,HIGH);
}

Check out the video of my FPV OSD testing using the above code (and some more stuff).