An On-Screen-Display with only 5 components!

Overview

An On-Screen-Display with only 5 components! Pushes an 8-bit PIC to the limits to build a cheap PAL video superimposer: MCU extracts PAL sync in real-time and overlays a line of text to the video.

How it works

PIC12F683 overclocked at 25 MHz (from 20 MHz max). Uses PIC internal comparator for horizontal sync detection, internal voltage reference for PAL clip level (625mV). No signal mixer: output directly added to input signal.

Inline assembly macros (SETPIXEL, WRITECHAR) for bit-banging video output. ISR handles comparator interrupts for sync detection and pixel output.

Demo features: welcome messages, vertical scrolling, horizontal scrolling, text resize, color change, RTC display.

Circuit Schematic

PicoOSD circuit schematic
PicoOSD circuit schematic

C Source Code

picoOSD.c
/*
 *******************************************************************************
 * 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.
 *
 *******************************************************************************
 */
#include        "built_in.h"
#include        "fonts.h"

/*************************
 * CONSTANTS DEFINITIONS
 *************************/

/*
 * CVREF = (VR<3:0>/24) x VDD
 * PAL CLIP LEVEL : 0.625 V
 */
#define CLIP_VOLTAGE    625             // in mV
#define CLIP_LEVEL      (CLIP_VOLTAGE * 24 / 5000)

#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)
#define T028us          (unsigned char)(T0FREQ * 0.000028)

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

/*
 * set 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 by FSR
 * Note : if you use a 20 Mhz crystal instead of 25 Mhz, remove NOP
 */
#define SETPIXEL(x)     asm { BTFSC     INDF, x         } \
                        asm { BCF       TRISIO, 2       } \
                        asm { NOP                       } \
                        asm { BSF       TRISIO, 2       }

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

/***********************
 * VARIABLES DEFINITIONS
 ***********************/

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

unsigned char   bm[FONTH][HSYMBOLS] absolute 0x23 ;     // bitmap to superimpose
unsigned char   display absolute 0xa0 ;                 // address of display data

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

volatile unsigned char  vStart = 255 absolute 0x20,     // vertical start line
                        vStop absolute 0x21,            // vertical stop line
                        lHeight absolute 0x22 ;         // line height

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

/****************************
 * Interrupt Service routine
 ****************************/
void interrupt()
        {
        if(CMCON0.COUT)                                 // comparator output high
                {
                if((line >= vStart) && (line < vStop))  // in display window
                        {
                        FSR = display ;

                        WRITECHAR() ;
                        WRITECHAR() ;
                        WRITECHAR() ;
                        WRITECHAR() ;
                        WRITECHAR() ;
                        WRITECHAR() ;
                        WRITECHAR() ;
                        WRITECHAR() ;
                        WRITECHAR() ;
                        WRITECHAR() ;
                        WRITECHAR() ;

                        ctrLine++ ;
                        if(ctrLine == lHeight)
                                {
                                display += HSYMBOLS ;
                                ctrLine = 0 ;
                                }
                        TMR0 = 0 ;
                        }
                else if(TMR0 >= T028us)                 // new frame sync
                        {
                        line = 0 ;
                        ctrLine = 0 ;
                        display = (char)bm ;
                        }
                }
        else                                            // comparator output low
                {
                TMR0 = 0 ;
                if(line < 254)
                        {
                        line++ ;
                        }

                /*
                 * Real Time Clock
                 */
                frm++ ;
                if(frm == 625 * 25)                     // 25 frames of 625 lines/sec
                        {
                        frm = 0 ;
                        sec++ ;
                        if(sec >= 60)
                                {
                                sec = 0 ;
                                mn++ ;
                                if(mn >= 60)
                                        {
                                        mn = 0 ;
                                        hours++ ;
                                        }
                                }
                        }
                }
        PIR1.CMIF = 0 ;
        }

/*
 * update display bitmap with character c at column pos
 */
void updatechar(unsigned char c, unsigned char pos)
        {
        unsigned char   py, col ;

        if(c < 32) c = 32 ;
        else if(c > 128) c = 32 ;
        else c -= 32 ;

        for(col = 0 ; col < 5 ; col++)
                {
                unsigned char fnt = fonts[c * 5 + col] ;
                unsigned char mask = 1 << col ;

                for(py = 0 ; py < FONTH ; py++)
                        {
                        if(fnt.F0)
                                {
                                bm[py][pos] |= mask ;
                                }
                        else
                                {
                                bm[py][pos] &= ~mask ;
                                }
                        fnt >>= 1 ;
                        }
                }
        }

/*
 * update display with constant string m at offset o
 */
void updateMsg(const char *m, unsigned char o)
        {
        unsigned char   n, l, c ;

        l = 0 ;
        while(m[l++]) ;

        for(n = 0 ; n < HSYMBOLS ; n++)
                {
                c = m[o++] ;
                o %= l ;
                updateChar(c, n) ;
                }
        }

/*
 * 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 set to one (white text)
        TRISIO = 0b110 ;                        // GP1 video in, GP2 video out
        CMCON0 = 0b10100 ;                      // comparator, internal voltage ref
        VRCON = 0b10100000 | CLIP_LEVEL ;       // voltage reference, low level
        ANSEL = 0b10 ;                          // GP1 as analog

        OPTION_REG = 0b10001000 ;               // no prescaler on TMR0

        PIE1.CMIE = 1 ;                         // enable comparator interrupt
        INTCON.PEIE = 1 ;
        INTCON.GIE = 1 ;

        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 axis */
        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) ;
                }
        }

Download project

PicoOSD project files

Download PicoOSD-project.zip for mikroC PRO for PIC12F683:

PicoOSD-project.zip

Includes:

  • mikroC PRO project files for PIC12F683
  • C source code (~400 lines)
  • HEX files