This project is designed to show how to build time-dependant applications on PICs in C and to deal with restricted hardware.
It will also help you to have a start point, if you need a simple way to add text to a PAL composite video signal in real time.
With only an 8 pins PIC and a few cheap components, you can superimpose constant or dynamic text to a composite video PAL signal.
How DOES IT work
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 tell us when a new frame starts, and with horizontal synchronization, which tell 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 .
CIRCUIT DIAGRAM
The PIC is overclocked with a 25 Mhz crystal but it should accept it with no problem. If you want not to overclock it, then use a 20 Mhz crystal and see C source code to know what adjustment to do. The result will be thiner pixel width.
You can use GP0 as I/O and GP3 as input if you have to build your own software. These pins are not used for the demo program.
Superimposed text is directly mixed to video input by tying GP1 and GP2 together, we count on video source and video input stages to adjust impedance as best as possible.
Since the circuit is very simplified, it works but you must be aware that it is neither compliant to Microchip PIC12F683 specifications (oscillator frequency and maximum output load), nor to PAL video specifications (impedance and voltages), so use it at your own risks.
Note also that video input and ouput are exactly the same, you can switch them with no problem !
Source code
Here is the mikroC source code, you can also download the full project (see below).
Since 8-pin PIC12F683 does not have a full 8-bit port, the usual trick to shift a port can not be used to generate the video signal (see my PIC PAL video library), and an alternative method is used.
A PIC16 or PIC18 could be used with only a few adjustments : more RAM and ROM will extend the possibilities of this project.
/*
*******************************************************************************
* 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
*/
constunsignedcharscroll_msg[]="Scroll text : pico OSD is scrolling this very long message on your screen, and then will rewind it very fast. Ready ? Now !";
unsignedcharline=0;// current line number
unsignedcharctrLine=0;// counter of line to be repeated (to make big sized fonts)
unsignedcharbm[FONTH][HSYMBOLS]absolute0x23;// bitmap to be superimposed to video
unsignedchardisplayabsolute0xa0;// address of data to be displayed, ensure it is in the same memory bank as TRISIO
unsignedcharmsg[HSYMBOLS+1];// dynamic string to be displayed
volatileunsignedcharvStart=255absolute0x20,// vertical start : first line to be superimposed
vStopabsolute0x21,// vertical stop : last line to be superimposed
lHeightabsolute0x22;// line height : number of time the same line must be displayed
unsignedintfrm;// frame counter
unsignedcharsec=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.
*/
voidinterrupt()
{
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
}
elseif(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
*/
voidupdatechar(unsignedcharc,unsignedcharpos)
{
unsignedcharpy,col;
/*
* check for under/overflow
*/
if(c<32)c=32;
elseif(c>128)c=32;
elsec-=32;// control characters under space in ASCII table are not displayed
for(col=0;col<5;col++)// for each character columns
{
unsignedcharfnt=fonts[c*5+col];// get bitmap font
unsignedcharmask=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
*/
voidupdateMsg(constchar*m,unsignedcharo)
{
unsignedcharn,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
*/
voiddelay10ms(unsignedcharc)
{
do
{
Delay_ms(10);
}
while(--c);
}
/*
* program entry
*/
voidmain()
{
unsignedchari;
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);
}
}
Project download
You can use this software as you wish, if you accept to do it at your own risks.