Did you ever need to display some informations on your TV screen, for your home application project for example ? But you gave up, because the hardware and software involved in this kind of project is not easy to operate.
This time is over ! This page will show you, how to turn a PIC18 into a B&W Text & Graphics PAL video processor with a very few and inexpensive hardware, and a minimum of coding effort !
See also my tiny PIC PAL composite video superimposer
Page index :
WHY A PIC PAL LIBRARY ?
If you are into PICs, you maybe tried one day or another to build a software video processor, either for fun or for a project.
If you tried to generate video signals, you surely know some of this web pages :
This projects are fun, but I wanted to build a general purpose software video processor, to drive a TV screen just like a GLCD could be.
I kept the idea to use a 2-resistor ladder as a fast and cheap digital to analog converter, and started to work on the software.
PIC PAL SOFTWARE VIDEO LIBRARY
Since the video screen has to be mapped in memory, only PICs with enough RAM are concerned, that's why the PIC PAL Library is for PIC18 family only.
The PIC must be clocked at 32 Mhz with a 8 Mhz crystal, to get the 64 µs horizontal synchronization timing of the PAL system.
The library generates a 625 lines interlaced PAL video signal, and can display up to 248 vertical lines of 128 pixel. Any video display device with a PAL video composite input should be able to display the picture generated by the PIC.
Because timing is critical, the part of the software producing the video uses in-line assembly mixed with C.
You can download the full mikroC project, including the library source code.
Here is the user's manual of the PIC PAL Library :
INITIALIZATION
PROTOTYPE | void PAL_init(unsigned char y) |
PARAMETERS |
y : number of vertical lines, up to 128 |
RETURNS | nothing |
DESCRIPTION |
prepare the PIC PAL video library Warnings :
|
REQUIRES |
the file PAL_library.h must be included in user's source code. specified hardware with 2 resistors, see schematic |
EXAMPLE | PAL_init(128) ; |
Video control
PROTOTYPE | void PAL_control(unsigned char st, unsigned char rd) |
PARAMETERS |
st : PAL synchronization control rd : rendering control |
RETURNS | nothing |
DESCRIPTION |
control the video generation when the PAL synchro is started, the unsigned long global variable PAL_frameCtr is incremented 25 times per second. |
REQUIRES |
PAL_init() must have been called |
EXAMPLE | PAL_control(PAL_CNTL_START, PAL_CNTL_RENDER) ; |
PROTOTYPE | void PAL_fill(unsigned char c) |
PARAMETERS |
c : filling pattern |
RETURNS | nothing |
DESCRIPTION |
fill screen with pattern c |
REQUIRES |
PAL_init() must have been called |
EXAMPLE | PALL_fill(0) ; |
SET SCREEN BORDER COLOR
PROTOTYPE | void PAL_setBorder(unsigned char border) |
PARAMETERS |
border : either PAL_COLOR_BLACK or PAL_COLOR_WHITE |
RETURNS | nothing |
DESCRIPTION |
change the screen border color surrounding the picture |
REQUIRES |
PAL_init() must have been called |
EXAMPLE | PAL_border(PAL_COLOR_BLACK) ; |
SET PIXEL
PROTOTYPE | void PAL_setPixel(char x, char y, unsigned char mode) |
PARAMETERS |
x : pixel column, from 0 to 127 |
RETURNS | nothing |
DESCRIPTION |
set the color of the pixel located a column x, row y |
REQUIRES |
PAL_init() must have been called |
EXAMPLE | PAL_setPixel(10, 20, PAL_COLOR_REVERSE) ; |
draw a line
PROTOTYPE | void PAL_line(char x0, char y0, char x1, char y1, unsigned char mode) |
PARAMETERS |
x0, y0 : column and row of the start of the line |
RETURNS | nothing |
DESCRIPTION |
draw a line from (x0, y0) to (x1, y1) |
REQUIRES |
PAL_init() must have been called |
EXAMPLE | PAL_line(0, 0, 127, 127, PAL_COLOR_WHITE) ; |
PROTOTYPE | void PAL_circle(char x, char y, char r, unsigned char mode) |
PARAMETERS |
x : column of the center of the circle |
RETURNS | nothing |
DESCRIPTION |
draw a circle of radius r centered at (x, y) |
REQUIRES |
PAL_init() must have been called |
EXAMPLE | PAL_circle(30, 30, 5, PAL_COLOR_WHITE) ; |
DRAW A SOLID BOX
PROTOTYPE | void PAL_box(char x0, char y0, char x1, char y1, unsigned char mode) |
PARAMETERS |
x0, y0 : top left of the box |
RETURNS | nothing |
DESCRIPTION |
fill a rectangle from corner (x0, y0) to corner (x1, y1) |
REQUIRES |
PAL_init() must have been called |
EXAMPLE | PAL_box(10, 10, 30, 30, PAL_COLOR_WHITE) ; |
draw a rectangle
PROTOTYPE | void PAL_rectangle(char x0, char y0, char x1, char y1, unsigned char pcolor) |
PARAMETERS |
x0, y0 : top left of the rectangle |
RETURNS | nothing |
DESCRIPTION |
draw a rectangle from corner (x0, y0) to corner (x1, y1) |
REQUIRES |
PAL_init() must have been called |
EXAMPLE | PAL_rectangle(10, 10, 30, 30, PAL_COLOR_WHITE) ; |
draw a character
PROTOTYPE | void PAL_char(unsigned char x, unsigned char y, unsigned char c, unsigned char size) |
PARAMETERS |
x : pixel column of the top left position of the character, from 0 to 127 |
RETURNS | nothing |
DESCRIPTION |
draw char c at (x, y), always in white on black (use PAL_box() to reverse video) |
REQUIRES |
PAL_init() must have been called |
EXAMPLE | PAL_char(3, 5, 'A', PAL_CHAR_DSIZE) ; |
DRAW A STRING
PROTOTYPE | void PAL_write(unsigned char lig, unsigned char col, unsigned char *s, unsigned char size) |
PARAMETERS |
lig : text line of the string |
RETURNS | nothing |
DESCRIPTION |
write string s at position (lig, col) |
REQUIRES |
PAL_init() must have been called |
EXAMPLE |
PAL_write(0, 5, myString, PAL_CHAR_STANDARD) ; |
DRAW a CONSTANT STRING
PROTOTYPE | void PAL_constWrite(unsigned char lig, unsigned char col, const unsigned char *s, unsigned char size) |
PARAMETERS |
lig : text line of the string |
RETURNS | nothing |
DESCRIPTION |
same as PAL_write(), but s is a string located in ROM |
REQUIRES |
PAL_init() must have been called |
EXAMPLE | PAL_write(0, 5, myConstantString, PAL_CHAR_STANDARD) ; |
DRAW A PICTURE
PROTOTYPE | void PAL_picture(unsigned char x, unsigned char y, const unsigned char *bm, unsigned char sx, unsigned char sy) |
PARAMETERS |
x : top left pixel column of the picture |
RETURNS | nothing |
DESCRIPTION |
draw the picture pointed to by bm at position (x, y) |
REQUIRES |
PAL_init() must have been called |
EXAMPLE |
PAL_picture(0, 0, pict, 128, 128) ; |
PROTOTYPE | void PAL_ISR() |
PARAMETERS |
none |
RETURNS | nothing |
DESCRIPTION |
this function must not be called directly by user, but must be placed within the interrupt() function. Warning : other interrupts may cause bad video synchronization if they are enabled. |
REQUIRES |
PAL_init() must have been called |
EXAMPLE |
void interrupt(void) |
CIRCUIT EXAMPLE
Click on the picture to enlarge
The core of the circuit is a PIC18F4620 :
C1, C3 and C4 are decoupling capacitors
The PIC is clocked with a 8 Mhz crystal
D1 is used as temperature sensor connected to PIC ADC
Video signal is mixed through R8 and R9
Switches with pull-downs are used for settings
You can connect the video out signal directly to the composite video input of your TV.
PROGRAM EXAMPLE
This program example shows you most of the features of the PIC PAL Library.
On a PIC18F4620, it uses 25% of the ROM and 55% of the RAM only !
The first screen of the program example is a 128x128 picture with a blinking border
The program waits for the key RB7 to be pressed and then goes to the second screen
The second screen is a calendar clock with temperature.
The time is displayed both as digital and graphic clock.
Press RB0 to change minutes, RB1 to change hours, RB2 to change month day, RB3 to change month, RB4 to change year.
Press RB5 to adjust temperature.
Press RB7 at the same time to decrement values, instead of to increment.
MIKROC SOURCE CODE EXAMPLE
Here is the mikroC source code of the program example. You have to use the zipped mikroC project to build it.
This program shows how to use the new mikroC time library too, very usefull to build rapidly clocks & calendars !
/* * file : PALdemo.c * project : PIC PAL SOFTWARE VIDEO GENERATOR DEMO * author : Bruno Gavand * compiler : mikroC V6.2 * date : January 17, 2006 * * description : * This program displays a clock, a calendar and the temperature on a TV screen * and shows how to use the PIC PAL library. * press RB7 to skip the welcome screen * to adjust clock and calendar, press : * RB0 to adjust minute * RB1 to adjust hour * RB2 to adjust day * RB3 to adjust month * RB4 to adjust year * RB5 to adjust temperature * press RB7 at the same time to decrement. * * target device : * PIC18F4620 @ 32 Mhz (8 Mhz crystal + HS PLL) * * Licence : * Feel free to use this source code at your own risks. * * history : * created january 2007 * * see more details on http://www.micro-examples.com/ */ #include "PAL_Library.h" #include "pictures.h" /************* * DEFINITIONS *************/ /* * graphic clock */ #define CLK_CENTER_X 90 // center #define CLK_CENTER_Y 60 #define CLK_RADIUS_PSS 28 // clock radius #define CLK_RADIUS_SS 25 // seconds #define CLK_RADIUS_MN 20 // minutes #define CLK_RADIUS_HH 15 // hours #define DEG_NBHISTO 16 // number of temperature samples /* * number of vertical pixels * from 1 to 128 included * the more pixels you have : * - the less RAM you have * - the less MCU time you have */ #define PAL_Y 128 /* * simple time structure definition */ typedef struct { unsigned char ss ; // seconds unsigned char mn ; // minutes unsigned char hh ; // hours unsigned char md ; // day in month, from 1 to 31 unsigned char wd ; // day in week, monday=0, tuesday=1, .... sunday=6 unsigned char mo ; // month number, from 1 to 12 (and not from 0 to 11 as with unix C time !) unsigned int yy ; // year Y2K compliant, from 1892 to 2038 } TimeStruct ; /******************** * ROM CONSTANTS ********************/ /* * month names */ const unsigned char monthStr[13][4] = { "???", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" } ; /* * day of week names */ const unsigned char wDaystr[7][4] = { "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" } ; /******************** * RAM VARIABLES ********************/ /* * screen memory map * do not change this line ! */ unsigned char PAL_screen[PAL_X * PAL_Y / 8] ; /* * general purpose string */ unsigned char str[20] ; char degRef ; // DAC temperature reference char degHisto[DEG_NBHISTO] ; // temperature samples buffer char tIdx = 0 ; // temperature samples index unsigned long secOffset = 0 ; // reference timestamp unsigned long oldCtr = 0 ; // frame counter backup TimeStruct ts ; // time struct /************************* * FUNCTIONS *************************/ /* * adjust time struct member */ void adjust(unsigned char *v, unsigned char min, unsigned char max) { if(PORTB.F7) { if(*v == min) *v = max ; else (*v)-- ; } else { if(*v == max) *v = min ; else (*v)++ ; } } /* * convert value v into string pointed to by p, leading zero blanks if blk is set */ void char2str(unsigned char *p, unsigned char v, unsigned char blk) { *p = v / 10 + '0' ; if(blk && (*p == '0')) { *p = ' ' ; } p++ ; *p = v % 10 + '0' ; p++ ; *p = 0 ; } /* * draw screen with decoration if full is set, using video mode mode */ void drawScreen(unsigned char full, unsigned char mode) { static unsigned char osx = CLK_CENTER_X, osy = CLK_CENTER_Y, omx = CLK_CENTER_X, omy = CLK_CENTER_Y, ohx = CLK_CENTER_X, ohy = CLK_CENTER_Y ; unsigned int i ; int t ; unsigned char ss ; unsigned char sx, sy, mx, my, hx, hy ; PAL_control(PAL_CNTL_START, mode) ; if(full) // draw full screen with decoration { PAL_fill(0) ; PAL_constWrite( 0, 0, "\xC9\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xBB", PAL_CHAR_STANDARD) ; PAL_constWrite( 1, 0, "\xBA PAL LIBRARY DEMO \xBA", PAL_CHAR_STANDARD) ; PAL_constWrite( 2, 0, "\xC8\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xBC", PAL_CHAR_STANDARD) ; PAL_box(0, 0, 127, 21, PAL_COLOR_REVERSE) ; PAL_constWrite( 3, 0, "\xC9\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCB\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xBB", PAL_CHAR_STANDARD) ; PAL_constWrite( 4, 0, "\xBA \xBA \xBA", PAL_CHAR_STANDARD) ; PAL_constWrite( 5, 0, "\xBA \xBA \xBA", PAL_CHAR_STANDARD) ; PAL_constWrite( 6, 0, "\xBA \xBA \xBA", PAL_CHAR_STANDARD) ; PAL_constWrite( 7, 0, "\xBA \xBA \xBA", PAL_CHAR_STANDARD) ; PAL_constWrite( 8, 0, "\xBA \xBA \xBA", PAL_CHAR_STANDARD) ; PAL_constWrite( 9, 0, "\xBA \xBA \xBA", PAL_CHAR_STANDARD) ; PAL_constWrite(10, 0, "\xBA \xBA \xBA", PAL_CHAR_STANDARD) ; PAL_constWrite(11, 0, "\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCA\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xB9", PAL_CHAR_STANDARD) ; PAL_constWrite(12, 0, "\xBA \xBA", PAL_CHAR_STANDARD) ; PAL_constWrite(13, 0, "\xBA \xBA", PAL_CHAR_STANDARD) ; PAL_constWrite(14, 0, "\xBA \xBA", PAL_CHAR_STANDARD) ; PAL_constWrite(15, 0, "\xC8\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xBC", PAL_CHAR_STANDARD) ; PAL_write(4, 3, "H :", PAL_CHAR_DHEIGHT) ; PAL_constWrite(12, 3, "TEMP. : ", 0x31) ; PAL_constWrite(12, 17, "\xf8C", 0x31) ; for(ss = 0 ; ss < 60 ; ss++) { sx = CLK_CENTER_X - (cosE3(90 + 6 * ss) * CLK_RADIUS_PSS) / 1000 ; sy = CLK_CENTER_Y - (sinE3(90 + 6 * ss) * CLK_RADIUS_PSS) / 1000 ; PAL_setPixel(sx, sy, PAL_COLOR_WHITE) ; if((ss % 5) == 0) { PAL_setPixel(sx + 1, sy, PAL_COLOR_WHITE) ; PAL_setPixel(sx - 1, sy, PAL_COLOR_WHITE) ; PAL_setPixel(sx, sy + 1, PAL_COLOR_WHITE) ; PAL_setPixel(sx, sy - 1, PAL_COLOR_WHITE) ; } } } if(PAL_frameCtr > OldCtr) // it's time to update the clock & calendar { unsigned char h ; oldCtr = PAL_frameCtr + 24 ; // prepare oldCtr for next update time Time_EpochToDate(secOffset + PAL_frameCtr / 25, &ts) ; // convert timestamp to date and time /* * draw analog clock */ sx = CLK_CENTER_X - (cosE3(90 + 6 * ts.ss) * CLK_RADIUS_SS) / 1000 ; sy = CLK_CENTER_Y - (sinE3(90 + 6 * ts.ss) * CLK_RADIUS_SS) / 1000 ; mx = CLK_CENTER_X - (cosE3(90 + 6 * ts.mn) * CLK_RADIUS_MN) / 1000 ; my = CLK_CENTER_Y - (sinE3(90 + 6 * ts.mn) * CLK_RADIUS_MN) / 1000 ; h = (ts.hh % 12) * 5 + (ts.mn / 8) ; hx = CLK_CENTER_X - (cosE3(90 + 6 * h) * CLK_RADIUS_HH) / 1000 ; hy = CLK_CENTER_Y - (sinE3(90 + 6 * h) * CLK_RADIUS_HH) / 1000 ; if((hx != ohx) || (hy != ohy)) { PAL_line(CLK_CENTER_X, CLK_CENTER_Y, ohx, ohy, PAL_COLOR_BLACK) ; } if((mx != omx) || (my != omy)) { PAL_line(CLK_CENTER_X, CLK_CENTER_Y, omx, omy, PAL_COLOR_BLACK) ; } if((sx != osx) || (sy != osy)) { PAL_line(CLK_CENTER_X, CLK_CENTER_Y, osx, osy, PAL_COLOR_BLACK) ; } PAL_line(CLK_CENTER_X, CLK_CENTER_Y, hx, hy, PAL_COLOR_WHITE) ; PAL_line(CLK_CENTER_X, CLK_CENTER_Y, mx, my, PAL_COLOR_WHITE) ; PAL_line(CLK_CENTER_X, CLK_CENTER_Y, sx, sy, PAL_COLOR_WHITE) ; /* * print date and time */ char2str(str, ts.ss, 0) ; PAL_write(4, 7, str, PAL_CHAR_DHEIGHT) ; char2str(str, ts.mn, 0) ; PAL_write(4, 4, str, PAL_CHAR_DHEIGHT) ; char2str(str, ts.hh, 1) ; PAL_write(4, 1, str, PAL_CHAR_DHEIGHT) ; PAL_constWrite(6, 2, wdayStr[ts.wd], PAL_CHAR_STANDARD) ; PAL_constWrite(7, 2, monthStr[ts.mo], PAL_CHAR_DHEIGHT) ; char2str(str, ts.md, 1) ; PAL_write(6, 5, str, 0x32) ; wordToStr(ts.yy, str) ; PAL_write(9, 1, str + 1, PAL_CHAR_DSIZE) ; /* * save old value for fast analog clock cleaning at next update */ osx = sx ; osy = sy ; omx = mx ; omy = my ; ohx = hx ; ohy = hy ; t = degRef - Adc_Read(4) ; // read temperature sensor t *= 221 ; // temperature coefficient of the silicon junction t /= 102 ; t = 25 + t ; // get the result in celcius /* * adjust limits */ if(t < -99) { t = -99 ; } if(t > 99) { t = 99 ; } /* * average values */ degHisto[tIdx] = t ; tIdx++ ; if(tIdx == DEG_NBHISTO) { tIdx = 0 ; } t = 0 ; for(i = 0 ; i < DEG_NBHISTO ; i++) { t += degHisto[i] ; } t /= DEG_NBHISTO ; /* * print temperature */ if(t < 0) { i = -t ; PAL_constWrite(12, 11, "-", 0x31) ; } else { i = t ; PAL_constWrite(12, 11, " ", 0x31) ; } char2str(str, i, 1) ; PAL_write(12, 12, str, 0x32) ; } PAL_control(PAL_CNTL_START, PAL_CNTL_RENDER) ; // restore video rendering if it was stopped } /* * interrupt service routine */ void interrupt(void) { /* * do PAL stuff */ PAL_ISR() ; // library call } /* * main program */ void main(void) { unsigned char i ; /* * I/O configuration */ ADCON1 = 0x0f ; TRISA = 0xff ; PORTA = 0 ; TRISB = 0xff ; PORTB = 0 ; TRISC = 0xff ; PORTC = 0 ; TRISD = 0 ; PORTD = 0 ; TRISE = 0 ; PORTE = 0 ; degRef = EEPROM_read(0) ; // get temperature calibration from EEPROM /* * default time and date */ ts.ss = 0 ; ts.mn = 0 ; ts.hh = 12 ; ts.md = 1 ; ts.mo = 1 ; ts.yy = 2007 ; secOffset = Time_dateToEpoch(&ts) ; /* * start video and display first screen */ PAL_init(PAL_Y) ; // init PAL library PAL_fill(0) ; // clear screen PAL_picture(0, 0, logo_bmp, 128, 128) ; // paint picture PAL_control(PAL_CNTL_START, PAL_CNTL_RENDER) ; // start video and rendering i = 0 ; while(PORTB == 0) // wait for a key to be pressed { /* * change border color two times per second */ if(PAL_frameCtr > 12) { PAL_setBorder(i) ; i = !i ; PAL_frameCtr = 0 ; } } PAL_setBorder(PAL_COLOR_BLACK) ; // clear border drawScreen(1, PAL_CNTL_BLANK) ; // draw full screen in blank mode (faster) for(;;) { if(PORTB & 0b1111111) // a key is pressed { Time_EpochToDate(secOffset + PAL_frameCtr / 25, &ts) ; /* * calendar settings */ if(PORTB.F0) { adjust(&ts.mn, 0, 59) ; ts.ss = 0 ; } if(PORTB.F1) { adjust(&ts.hh, 0, 59) ; ts.ss = 0 ; } if(PORTB.F2) { adjust(&ts.md, 1, 31) ; } if(PORTB.F3) { adjust(&ts.mo, 1, 12) ; } if(PORTB.F4) { if(PORTB.F7) ts.yy-- ; else ts.yy++ ; } secOffset = Time_dateToEpoch(&ts) ; // new timestamp /* * temperature calibration */ if(PORTB.F5) { if(PORTB.F7) { degRef-- ; EEPROM_write(0, degref) ; } else { degRef++ ; EEPROM_write(0, degref) ; } } while(PORTB & 0b1111111) ; // wait for the key to be released PAL_frameCtr = 0 ; // reset counters oldCtr = 0 ; } drawScreen(0, PAL_CNTL_RENDER) ; // update screen } } |
PROJECT DOWNLOAD
You can use this software as you wish, if you accept to do it at your own risks.
Download PIC PAL Library with demo example for mikroC : zipped file, 29.64 Ko
content of the archive :
You can get mikroC from here : http://www.mikroe.com/en/compilers/mikroc/pic/
Please report any bug, comment or suggestion in my forums. Thanks !