www.micro-examples.com : PicoOSD

Views
From www.micro-examples.com
Jump to: navigation, search
PicoOSD components

An On-Screen-Display with only 5 components !

Contents

PicoOSD : a PIC-based simple and cheap OSD

A short video clip is sometimes better than a long explanation :


The idea of this circuit is to push an 8-bit PIC to the limits to build a cheap video superimposer : MCU extracts PAL sync in real-time and overlays a line of text to the video.

If you like it, please see also my Pic Pal Video Library, to turn a PIC18 into a black and white PAL video generator.

Circuit Schematic

PicoOSD-schematic.jpg

  • The PIC12 MCU is slightly overclocked : 25 MHz instead of 20 MHz max
  • There is no signal mixer : output signal is directly added to input signal, not very clean but fine for the demo

C Source code

/*
 *******************************************************************************
 * pico OSD : simple PIC OSD PAL video superimposer
 *******************************************************************************
 *
 * This program shows how to superimpose a text on a PAL composite video signal
 * with a PIC and only 4 resistors.
 *
 * source code for mikro C compiler V7.0.0.3
 * feel free to use this code at your own risks
 *
 * target : PIC12F683 @25MHz
 * HS clock, no watchdog.
 *
 * Author : Bruno Gavand, October 2007
 * see more details on http://www.micro-examples.com/
 *
 *******************************************************************************
 */
#include        "built_in.h"
#include        "fonts.h"

/*************************
 * CONSTANTS DEFINITIONS
 *************************/
 
/*
 * CVREF = (VR<3:0>/24) × VDD
 * PAL CLIP LEVEL : 0.625 V
 */
#define CLIP_VOLTAGE    625             // in mV
#define CLIP_LEVEL      (CLIP_VOLTAGE * 24 / 5000)    // for VR bits CMCON<3:0>

#define OUT             GPIO.F2         // video output

#define HSYMBOLS        11              // number of horizontal symbols
#define FONTH           7               // pixel font height

#define T0FREQ          (Clock_kHz() * 1000 / 4)                // number of TMR0 ticks in one second
#define T028us          (unsigned char)(T0FREQ * 0.000028)      // number of TMR0 ticks in 28 µs

/************************
 * PSEUDO FUNCTIONS
 ************************/

/*
 * this macro definition sets message display from line l, color c (1=white, 0=black), font size s
 */
#define setMsg(l, c, s) vStart = l ; vStop = vStart + FONTH * s ; lHeight = s ; OUT = c

/*
 * set output depending on bit number x of the current position indexed to by FSR
 * Note : if you use a 20 Mhz crystal instead of a 25 Mhz crystal, remove NOP
 */
#define SETPIXEL(x)     asm { BTFSC     INDF, x         ; skip next intruction if bit x is set                  } \
                        asm { BCF       TRISIO, 2       ; set GP2 as output and superimpose pixel               } \
                        asm { NOP                       ; no operation (enlarges pixel)                         } \
                        asm { BSF       TRISIO, 2       ; set GP2 as high Z input (no pixel superimposed)       }

/*
 * write the 5 bits of the current character in the current line,
 * then increment FSR to point to the next character
 */
#define WRITECHAR()             SETPIXEL(0)     \
                                SETPIXEL(1)     \
                                SETPIXEL(2)     \
                                SETPIXEL(3)     \
                                SETPIXEL(4)     \
                                FSR++

/***********************
 * VARIABLES DEFINITIONS
 ***********************/
 
/*
 * demo message to be scrolled
 */
const unsigned char scroll_msg[] = "Scroll text : pico OSD is scrolling this very long message on your screen, and then will rewind it very fast. Ready ? Now !" ;

unsigned char   line = 0 ;              // current line number
unsigned char   ctrLine = 0 ;           // counter of line to be repeated (to make big sized fonts)

unsigned char   bm[FONTH][HSYMBOLS] absolute 0x23 ;     // bitmap to be superimposed to video
unsigned char   display absolute 0xa0 ;                 // address of data to be displayed, ensure it is in the same memory bank as TRISIO

unsigned char   msg[HSYMBOLS + 1] ;                     // dynamic string to be displayed

volatile unsigned char  vStart = 255 absolute 0x20,     // vertical start : first line to be superimposed
                        vStop absolute 0x21,            // vertical stop : last line to be superimposed
                        lHeight absolute 0x22 ;         // line height : number of time the same line must be displayed

unsigned int    frm ;           // frame counter
unsigned char   sec  = 0,       // RTC seconds,
                mn = 0,         // minutes,
                hours = 0 ;     // hours

/****************************
 * Interrupt Service routine
 ****************************/
/*
 * since no other interrupts but CMIE are enabled, no need to check this flag.
 */
void interrupt()
        {
        if(CMCON0.COUT)                                 // if comparator output changed to high
                {
                if((line >= vStart) && (line < vStop))  // if we are in display window
                        {
                        FSR = display ;                 // load FSR with current bitmap address

                        WRITECHAR() ;                   // set character pixels
                        WRITECHAR() ;
                        WRITECHAR() ;
                        WRITECHAR() ;
                        WRITECHAR() ;
                        WRITECHAR() ;
                        WRITECHAR() ;
                        WRITECHAR() ;
                        WRITECHAR() ;
                        WRITECHAR() ;
                        WRITECHAR() ;

                        ctrLine++ ;                     // increment same line counter
                        if(ctrLine == lHeight)          // do we have to change ?
                                {
                                display += HSYMBOLS ;   // yes, change display location to next bitmap row
                                ctrLine = 0 ;           // reset counter
                                }
                        TMR0 = 0 ;                      // keep timer 0 clear to prevent false start of frame detection
                        }
                else if(TMR0 >= T028us)                 // if, check low pulse duration
                        {
                        line = 0 ;                      // we are in a new frame sync, reset line counter
                        ctrLine = 0 ;                   // reset same line counter
                        display = (char)bm ;            // points to the bitmap start
                        }
                }
        else                                            // compatator output changed to low
                {
                TMR0 = 0 ;                              // clear timer 0
                if(line < 254)                          // if we can
                        {
                        line++ ;                        // increment line counter (only first 255 lines are used)
                        }

                /*
                 * Real Time Clock
                 */
                frm++ ;                                 // increment frames counter
                if(frm == 625 * 25)                     // PAL is made of 25 frames of 625 lines per second
                        {
                        frm = 0 ;                       // clear counter
                        sec++ ;                         // increment seconds
                        if(sec >= 60)                   // check for seconds rollover
                                {
                                sec = 0 ;               // clear seconds
                                mn++ ;                  // increment minutes
                                if(mn >= 60)            // check for minutes rollover
                                        {
                                        mn = 0 ;        // clear minutes
                                        hours++ ;       // increment hours
                                        }
                                }
                        }
                }
        PIR1.CMIF = 0 ;                                 // clear comparator interrupt flag
        }
        
/*
 * update display bitmap with character c at column pos
 */
void updatechar(unsigned char c, unsigned char pos)
        {
        unsigned char   py, col ;

        /*
         * check for under/overflow
         */
        if(c < 32) c = 32 ;
        else if(c > 128) c = 32 ;
        else c -= 32 ;                                  // control characters under space in ASCII table are not displayed

        for(col = 0 ; col < 5 ; col++)                  // for each character columns
                {
                unsigned char fnt = fonts[c * 5 + col] ;        // get bitmap font
                unsigned char mask = 1 << col ;                 // build bitmap mask

                for(py = 0 ; py < FONTH ; py++)         // for each character lines
                        {
                        if(fnt.F0)
                                {
                                bm[py][pos] |= mask ;   // set pixel
                                }
                        else
                                {
                                bm[py][pos] &= ~mask ;  // clear pixel
                                }
                        fnt >>= 1 ;                     // shift bitmap mask
                        }
                }
        }

/*
 * update display message with constant string pointed to by m with offset o within the string
 */
void updateMsg(const char *m, unsigned char o)
        {
        unsigned char   n, l, c ;

        /*
         * get string length
         */
        l = 0 ;
        while(m[l++]) ;

        for(n = 0 ; n < HSYMBOLS ; n++)         // for all symbols
                {
                c = m[o++] ;                    // get character
                o %= l ;                        // circularize string
                updateChar(c, n) ;              // put character to display bitmap
                }
        }

/*
 * 10 ms delay, c times
 */
void    delay10ms(unsigned char c)
        {
        do
                {
                Delay_ms(10) ;
                }
        while(--c) ;
        }

/*
 * program entry
 */
void main()
        {
        unsigned char   i ;
        
        GPIO = 0b100 ;                          // GP2 is set to one be default (text is superimposed in white)
        TRISIO = 0b110 ;                        // GP1 is video input, GP2 is video output (set to high Z first)
        CMCON0 = 0b10100 ;                      // comparator module : no output, uses internal voltage reference
        VRCON = 0b10100000 | CLIP_LEVEL ;       // voltage reference module : inverted output, low level
        ANSEL = 0b10 ;                          // all pins but GP1 (comparator input) as digital

        OPTION_REG = 0b10001000 ;               // no prescaler on TMR0
        
        PIE1.CMIE = 1 ;                         // enable comparator interrupt
        INTCON.PEIE = 1 ;                       // enable peripheral interrupt
        INTCON.GIE = 1 ;                        // enable global interrupt

        /*
         * init display window
         */
        lHeight = 1 ;
        vStart = 10 ;
        vStop = 10 ;

        /*************************
         * pico OSD demonstration
         *************************/

        /*
         * welcome messages
         */
        updateMsg("Welcome to ", 0) ;
        for(i = 1 ; i < 10 ; i++)
                {
                setMsg(50, 1, i) ;
                delay10ms(5) ;
                }
        delay10ms(100) ;

        updateMsg("  pico OSD ", 0) ;
        setMsg(100, 1, 6) ;
        delay10ms(100) ;

        updateMsg("the tiniest", 0) ;
        setMsg(100, 1, 6) ;
        delay10ms(100) ;

        updateMsg("OSD of the ", 0) ;
        setMsg(100, 1, 6) ;
        delay10ms(100) ;

        updateMsg("WORLD !!!!!", 0) ;
        setMsg(100, 1, 6) ;
        delay10ms(500) ;

        updateMsg(" It can... ", 0) ;
        setMsg(100, 1, 6) ;
        delay10ms(500) ;

        /*
         * move message along vertical axe
         */
        updateMsg(" Move text ", 0) ;
        for(i = 20 ; i < 195 ; i++)
                {
                setMsg(i, 1, 6) ;
                delay10ms(1) ;
                }
        for( ; i > 20 ; i--)
                {
                setMsg(i, 1, 6) ;
                delay10ms(1) ;
                }
        for( ; i < 100 ; i++)
                {
                setMsg(i, 1, 6) ;
                delay10ms(1) ;
                }
        delay10ms(100) ;

        /*
         * horizontal scroll
         */
        updateMsg(scroll_msg, 0) ;
        delay10ms(200) ;
        for(i = 0 ; i < sizeof(scroll_msg) - HSYMBOLS ; i++)
                {
                updateMsg(scroll_msg, i) ;
                setMsg(100, 1, 6) ;
                delay10ms(10) ;
                }
        delay10ms(50) ;
        for( ; i > 0 ; i--)
                {
                updateMsg(scroll_msg, i) ;
                setMsg(100, 1, 6) ;
                delay10ms(3) ;
                }
        updateMsg(scroll_msg, 0) ;
        delay10ms(100) ;

        /*
         * change text size
         */
        updateMsg("Resize text", 0) ;
        delay10ms(100) ;
        for(i = 6 ; i < 20 ; i++)
                {
                setMsg(100, 1, i) ;
                delay10ms(10) ;
                }
        for( ; i > 0 ; i--)
                {
                setMsg(100, 1, i) ;
                delay10ms(10) ;
                }
        for(i = 1 ; i < 6 ; i++)
                {
                setMsg(100, 1, i) ;
                delay10ms(10) ;
                }
        delay10ms(100) ;

        /*
         * change text color
         */
        for(i = 0 ; i < 2 ; i++)
                {
                updateMsg("  In Black ", 0) ;
                setMsg(100, 0, 6) ;
                delay10ms(100) ;

                updateMsg("  Or White ", 0) ;
                setMsg(100, 1, 6) ;
                delay10ms(100) ;
                }

        /*
         * prepare to display run time
         */
        updateMsg("Run time : ", 0) ;
        setMsg(100, 1, 6) ;
        delay10ms(100) ;
        setMsg(40, 1, 3) ;
        delay10ms(100) ;

        updateChar('H', 2) ;
        updateChar('m', 5) ;
        updateChar('i', 6) ;
        updateChar('n', 7) ;
        updateChar('s', 10) ;

        /*
         * update run time clock display
         */
        for(;;)
                {
                updateChar(hours / 10 + '0', 0) ;
                updateChar(hours % 10 + '0', 1) ;

                updateChar(mn / 10 + '0', 3) ;
                updateChar(mn % 10 + '0', 4) ;

                updateChar(sec / 10 + '0', 8) ;
                updateChar(sec % 10 + '0', 9) ;
                }
        }

As you can see, there is no assembly in C source code but the SETPIXEL(c) macro. Video synchronization and generation is made by interrupt routine, main loop only controls shape and position of the text :

To superimpose text to a PAL video signal, we need to control timing with precision to get a stable picture. We have to deal with vertical synchronization, which tells us when a new frame starts, and with horizontal synchronization, which tells us when a new line starts.

Usually, and external circuit is used to extract both vertical and horizontal synchronization pulses from the PAL video signal, the LM1881 integrated circuit does it very well for example.

Since I wanted to have a very simple circuit, I had to find a way to make the PIC do this job.

First, we must be able to know when a video line starts : we will use the PIC internal comparator module to do it. The internal voltage reference module will be programmed with a voltage clip level, the comparator will then trigger an interrupt each time the input voltage will become lower or higher than the clip level. This will be our horizontal sync separator.

Second, we must be able to know when a frame starts do get vertical sync : PAL signal uses special sync pulses to announce a new frame. We have to detect a 28 µs low level pulse, there are five of them in the vertical sync and none elsewhere. The internal timer module of the PIC will be used to count time of low level pulses.

This done, we must be able to know what to superimpose to the video signal. A bitmap representation of the text to be displayed is built in RAM from a 5x7 fonts table. On each new line interrupt, we check if we are in display time window for adding pixels or not.

To add a pixel to the video signal, we change output pin from high Z state to output, the output then imposes +Vcc or 0V to display either a white or a black pixel. The result is a superimposed text on transparent background .

Download project

Download PicoOSD-project.zip file for mikroC : File:PicoOSD-project.zip

Includes :

  • mikroC PRO project files for PIC12F683, should work also with most of PIC
  • picoOSD C source code
  • picoOSD .HEX files

Discussion and comments

Current user rating: 93/100 (58 votes)

 You need to enable JavaScript to vote

You need JavaScript enabled for viewing comments

Navigation
Others
Donation
You can help :
with Paypal
Share
Personal tools
www.micro-examples.com Electronic circuits with micro-controllers, projects with source code examples