An On-Screen-Display with only 5 components !
Contents |
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.
/*
*******************************************************************************
* 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 PicoOSD-project.zip file for mikroC : File:PicoOSD-project.zip
Includes :
Current user rating: 93/100 (70 votes)
| powered by commenterra | Recent comments |