Controlling MAX7456 OSD using Bus Pirate
MAX7456 is a nifty chip that lets you insert fixed-width characters and sprites into a TV signal. Sparkfun has a very useful breakout board for $40, which ends up being much cheaper than the MAX7456 eval board from Maxim priced around $100. You can find Youtube examples of DIY projects that have used the MAX7456, and to get a quick idea of how the display looks, take a peek at the MAX7456 datasheet. In this article, we will look at how to interface an MAX7456 to a PC using a Bus Pirate board.
MAX7456 Overview
MAX7456 can be used to inexpensively show text overlaid on a video feed. You can do minimal graphics using sprites. The total display area is 13×30 characters in NTSC and 16×30 characters in PAL feeds. You have a total of 256 characters to choose from. The chip comes pre-programmed with the character set below, but you can reporgram any of the characters as needed. The size of each character is 12 pixels by 18 pixels. You can set each pixel to black or white, or leave it transparent. You can also selectively display just the text or the text overlaid on the video feed. All control communication with the MAX7456 happens over an SPI bus.
MAX7456 and Bus Pirate : Not Quite Friends
If you look at the MAX7456 datasheets, you will notice that it uses 5V logic, where as the Bus Pirate uses 3.3V logic for communications. The MAX7456 can read SPI communications as long as the High level on the bus is above 2V. But the 5V swing gets completely wiped out by the Bus Pirate when the MAX7456 writes to the MISO line. Anyone with an explanation of the underlying cause, please chime in. It took me the better part of a day with an oscilloscope to figure out that the Bus Pirate was eating up all signals on the MISO line.
The solution is to use a voltage divider on the MISO line. The proper scaling would be 66% of the 5V swing, but even if you use 50%, you will be fine. So, the MISO line hookup looks like this:
Also note the pullup resistor on the Reset line. Without that, the MAX7456 stays powered down. I found that the 5V supply of the Bus Pirate was not delivering enough current to the MAX7456, and so the voltage was dropping to 4.74V, just below the 4.75V minimum the MAX7456 needs to run. So, I had to provide it a separate 5V source.
Configuring MAX7456
The chip doesn’t require any configuration at power up. You can simply write characters to its Display Memory, and turn on OSD using the VM0 SPI register, and you will instantly have something showing up on your screen.
Writing Characters
The Max7456 datasheet is a very well written document. One should have no problems following along to understand how to use the chip. I’m giving a brief outline here and skipping the details. Look up the datasheet to make sense of the register names (VM0, DMM, etc.)
The sequence to follow to write characters is:
- Disable OSD (VM0)
- Set Display Memory Mode (DMM)
- Write most significant bit of display location to DMAH
- Write lower 8 bits of display location to DMAL
- Write character code to DMDI
- Enable OSD (VM0)
The chip allows you to set the attributes for each character location. You can choose the states for Background Control, Blink and Invert. You can write to each location in 2 write modes – 8 bit and 16 bit.
In 16 bit write mode, you only write the character attributes once to DMM and write as many character codes as needed to DMDI (after setting DMAH and DMAL as needed). The attribute in DMM is used for all of the characters written in 16 bit mode.
In 8 bit mode, you write both the character code and attribute to DMDI, but you choose the meaning of the DMDI register using bit 1 of DMAH. When DMAH[1] = 0, DMDI is getting the character code and when DMAH[1] = 1, it’s getting the character attribute.
Below is a Python script that uses the pyBusPirateLite library to write characters to screen. The COM port used for talking to the Bus Pirate is hard coded as COM3. You should change it or make it a command line parameter if you use a different port. Look at the script below for an example of reading the COM port from the command line.
import sys from pyBusPirateLite.SPI import * import logging class OSDDisplay: def __init__(self): self.spiPort = None self.mapCode = {' ':0, '1':1, '2':2, '3':3, '4':4, '5':5, '6':6, '7':7, '8':8, '9':9, '0':10, 'A':11, 'B':12, 'C':13, 'D':14, 'E':15, 'F':16, 'G':17, 'H':18, 'I':19, 'J':20, 'K':21, 'L':22, 'M':23, 'N':24, 'O':25, 'P':26, 'Q':27, 'R':28, 'S':29, 'T':30, 'U':31, 'V':32, 'W':33, 'X':34, 'Y':35, 'Z':36, 'a':37, 'b':38, 'c':39, 'd':40, 'e':41, 'f':42, 'g':43, 'h':44, 'i':45, 'j':46, 'k':47, 'l':48, 'm':49, 'n':50, 'o':51, 'p':52, 'q':53, 'r':54, 's':55, 't':56, 'u':57, 'v':58, 'x':59, 'y':60, 'z':61, '(':62, ')':63, '.':64, '?':65, ';':66, ':':67, ',':68, '\'':69, '/':70, '"':71, '-':72, '<':73, '>':74, '@':75, '\xa9':76 } def InitMax7456(self, comPortName="COM3"): # Open Buspirate Port if not self.spiPort: self.spiPort = SPI(comPortName, 115200) else: logging.debug("Port already open") # endif # Enter binmode if not self.spiPort.BBmode(): logging.error("Couldn't start Bitbang Mode") #self.spiPort.port.close() #self.spiPort = None return False # endif # Enter SPI mode if not self.spiPort.enter_SPI(): logging.error("Couldn't start SPI Mode") #self.spiPort.port.close() #self.spiPort = None return False # endif # Set speed (125 KHz) #self.spiPort.set_speed(SPISpeed._125KHZ) #self.spiPort.set_speed(SPISpeed._250KHZ) self.spiPort.set_speed(SPISpeed._1MHZ) # Configure SPI mode for Max7456 self.spiPort.cfg_spi(SPICfg.OUT_TYPE) # Output mode Normal (3.3V) rather than HiZ # Set SPI timeout self.spiPort.timeout(0.05) return True def ResetBP(self): self.spiPort.resetBP() self.spiPort.port.close() self.spiPort = None def ShowString(self, strText, posX, posY, enableDisplay = True): """ Prints strText at location (posX, posY), with starting location being (0,0) """ if self.spiPort is None: logging.error("Max7456 communication not initialized") return False # endif # Convert string to character code arrCode = [] for ch in strText: if self.mapCode.has_key(ch): arrCode.append(self.mapCode[ch]) else: arrCode.append(0x00) # endif # end for arrCode.append(0xff) # Turn off OSD self.spiPort.CS_Low() self.spiPort.bulk_trans(2, [0x00, 0x00]) self.spiPort.CS_High() # Write characters to display memory startIndex = posY * 30 + posX self.spiPort.CS_Low() self.spiPort.bulk_trans(2, [0x05, 0x00]) # Position MSB = 0 self.spiPort.CS_High() self.spiPort.CS_Low() self.spiPort.bulk_trans(2, [0x06, startIndex]) # Position to start writing at self.spiPort.CS_High() self.spiPort.CS_Low() self.spiPort.bulk_trans(2, [0x04, 0x01]) # 16 bit operation, autoincrement self.spiPort.CS_High() logging.debug("Array: %s" % repr(arrCode)) for code in arrCode: # self.spiPort.CS_Low() # self.spiPort.bulk_trans(2, [0x06, startIndex]) # Position to start writing at # self.spiPort.CS_High() # startIndex += 1 self.spiPort.CS_Low() self.spiPort.bulk_trans(1, [code]) # character code self.spiPort.CS_High() # end for # Turn on OSD if needed if enableDisplay: self.spiPort.CS_Low() self.spiPort.bulk_trans(2, [0x00, 0x08]) # Enable OSD self.spiPort.CS_High() else: self.spiPort.CS_Low() response = self.spiPort.bulk_trans(2, [0x81, 0x00]) # Read VM1 self.spiPort.CS_High() logging.info("VM1 = {0:08b}".format(ord(response[1]))) # endif def ShowChars(self, arrChars, posX, posY, enableDisplay = True): """ Prints strText at location (posX, posY), with starting location being (0,0) """ if self.spiPort is None: logging.error("Max7456 communication not initialized") return False # endif # Convert string to character code arrCode = [char for char in arrChars] arrCode.append(0xff) # Turn off OSD self.spiPort.CS_Low() self.spiPort.bulk_trans(2, [0x00, 0x00]) self.spiPort.CS_High() # Write characters to display memory startIndex = posY * 30 + posX self.spiPort.CS_Low() self.spiPort.bulk_trans(2, [0x05, 0x00]) # Position MSB = 0 self.spiPort.CS_High() self.spiPort.CS_Low() self.spiPort.bulk_trans(2, [0x06, startIndex]) # Position to start writing at self.spiPort.CS_High() self.spiPort.CS_Low() self.spiPort.bulk_trans(2, [0x04, 0x01]) # 16 bit operation, autoincrement self.spiPort.CS_High() logging.debug("Array: %s" % repr(arrCode)) for code in arrCode: self.spiPort.CS_Low() self.spiPort.bulk_trans(1, [code]) # character code self.spiPort.CS_High() # end for # Turn on OSD if enableDisplay: self.spiPort.CS_Low() self.spiPort.bulk_trans(2, [0x00, 0x08]) # Enable OSD self.spiPort.CS_High() else: self.spiPort.CS_Low() response = self.spiPort.bulk_trans(2, [0x81, 0x00]) # Read VM1 self.spiPort.CS_High() logging.info("VM1 = {0:08b}".format(ord(response[1]))) # endif if __name__ == '__main__': logging.basicConfig(level=logging.DEBUG) displayHandler = OSDDisplay() if not displayHandler.InitMax7456("COM3"): # Reset and try again displayHandler.ResetBP() logging.error("BusPirate not responding") else: displayHandler.ShowString("Bus Pirate is the Dude!!", posX = 2, posY = 1, enableDisplay = True) # endif |
Programming Character Bitmaps
You program the character bitmaps over the same SPI bus by writing 54 bytes of data to a shadow RAM, and then committing it to flash. To build the character bitmaps, you can use the very handy MAX7456EVKit software. The software is available for free download from Maxim’s website. You can edit the existing character set from the file Defaultcm.mcm and save it to a different filename. Then, you can use the script below to upload the character map to the chip. Mind you, the communication is very slow, so it may take you 15 minutes to transfer all the characters. The script doesn’t have much error handling, so it assumes that you have a valid MCM file generated by the EVKit software, with all 256 characters available in the file. You have to supply the MCM filename and the COM port at the command line to run the script.
import sys, optparse from pyBusPirateLite.SPI import * import logging class MAX7456Writer: def __init__(self, comPortName = "COM3"): self.spiPort = None self.comPortName = comPortName def InitMax7456(self): # Open Buspirate Port if not self.spiPort: self.spiPort = SPI(self.comPortName, 115200) else: logging.debug("Port already open") # endif # Enter binmode if not self.spiPort.BBmode(): logging.error("Couldn't start Bitbang Mode") #self.spiPort.port.close() #self.spiPort = None return False # endif # Enter SPI mode if not self.spiPort.enter_SPI(): logging.error("Couldn't start SPI Mode") #self.spiPort.port.close() #self.spiPort = None return False # endif # Set speed (125 KHz) #self.spiPort.set_speed(SPISpeed._125KHZ) self.spiPort.set_speed(SPISpeed._1MHZ) # Configure SPI mode for Max7456 self.spiPort.cfg_spi(SPICfg.OUT_TYPE) # Output mode Normal (3.3V) rather than HiZ # Set SPI timeout self.spiPort.timeout(0.3) return True def ResetBP(self): self.spiPort.resetBP() self.spiPort.port.close() self.spiPort = None def WriteCharactersToMemory(self, inputFile): """ Writes characters to MAX7456 character memory from given MCM file """ # Open file try: input = open(inputFile, 'r') except IOError: logging.error("Cannot open file [{0}]".format(inputFile)) return # end try # Read first line to verify it's MCM line = input.readline() line = line.strip() if line != "MAX7456": logging.error("{0} not an MCM file".format(inputFile)) input.close() return # endif self.spiPort.CS_Low() self.spiPort.bulk_trans(2, [0x00, 0x00]) # disable OSD self.spiPort.CS_High() for i in range(256): sys.stdout.write(".") self.spiPort.CS_Low() self.spiPort.bulk_trans(2, [0x09, i]) # select character number self.spiPort.CS_High() # Read 54 lines for character data for j in range(54): line = input.readline().strip() val = int(line, 2) # convert binary string to integer self.spiPort.CS_Low() self.spiPort.bulk_trans(2, [0x0A, j]) # write pixel group number self.spiPort.CS_High() self.spiPort.CS_Low() self.spiPort.bulk_trans(2, [0xB, val]) # write pixel data self.spiPort.CS_High() # end for # Read 10 dummy lines for j in range(10): input.readline() # end for self.spiPort.CS_Low() self.spiPort.bulk_trans(2, [0x08, 0xA0]) # commit to NVM self.spiPort.CS_High() # end for if __name__ == '__main__': logging.basicConfig(level=logging.DEBUG) if len(sys.argv) < 2: logging.error("No character file provided") else: inputFile = sys.argv[1] if len(sys.argv) > 2: comPort = sys.argv[2] else: comPort = "COM3" # endif #endif charWriter = MAX7456Writer(comPort) if not charWriter.InitMax7456(): # Reset and try again charWriter.ResetBP() logging.error("BusPirate not responding") else: charWriter.WriteCharactersToMemory(inputFile) # endif |