banner

Tuesday, April 10, 2012

Drawing geometric figures on a PAL TV using ATmega32 (128x64 resolution)


Photos of my TV screen:





Introduction:
    I am interested to draw lines, square, rectangle, circle etc on my TV screen. At first I was confused where to start. While thinking about it, a pencil and an eraser came to my mind. If we have a good pencil, eraser and a paper, then we can draw on it according to our own logic. If we use the pencil with compass and scale, we can draw circle, line, box etc on the paper. This is the basic idea I implemented in this small hobby project.
    
I divided my tv screen into 128 x 64 pixels. I am using atmega32 microcontroller. It got 2kb RAM. So, if each pixel takes 1 bit, then 128 x 64 pixels takes (128*64/8) = 1024bits of RAM(screen buffer). Still I can increase the resolution but any way at present I am satisfied with this because it is my first attempt and I can improve it later. Here a horizontal line in TV(two lines) takes 16byte, ie horizontal resolution now is 16*8 = 128 pixel. Similar 64 bytes are stacked up and thus the vertical resolution now is 64 lines. I am using fake interlacing so total vertical lines are 312 and here again I combine 2 lines to a single line which takes 1 16byte row of the screen buffer.
          I told about a pencil at the starting of the post, that is nothing but a function which could set a pixel. The eraser is a function which clear a pixel. Now the paper is the a digital display of 128x64 pixels, the smallest dot is 1 pixel.
 
PENCIL =  setpixel(x,y);
ERASER = clrpixel(x,y);
PAPER = 128x64 screen
building block = one pixel

Now my first aim is to create the pencil and eraser so that I can draw any thing on my TV screen using simple mathematics....

I am setting a character array disp_buffer[64][16] ie 1024 bits (128x64) which stores each pixel information and we will be playing on this buffer to draw pictures.
A brief explanation about the working of TV is provided at my previous post.. Ok, now before going to the pencil, we need to set the paper. Here we need to set the display buffer to show it on TV. May be this will be one of the most important part of the code. A raster scanning of a TV takes 64uS. So we need to generate timer interrupts on each 64us and we need to use more precise and peak value clock, the external crystal of 16MHz. But unfortunately, I don't have a 16MHz but have a 16.450MHz. So I stick with it and all of my code will be related to that. I will update it soon when I get a 16MHz.  We can easily calculate the timer compare value to be loaded into OCCR1A using below equation.
    OCR1A = (F_CPU/1000000)*64; (see timer1_init function)
So now we can directly enter to the timer interrupt routine and look at the code and see how it works...
ISR (TIMER1_COMPA_vect)
{
    ZERO;
    char b;
    unsigned char index;
    if(line == 312) {
        _delay_us(28);BLACK;_delay_us(4);ZERO;_delay_us(28);BLACK;_delay_us(4);
        TCNT1=0;
    }
    
    _delay_us(4);BLACK;_delay_us(10);
    
    if(line >= 100 && line <228) {
        index = pgm_read_byte(&(line_lookup[line-100]));
        DDRB |= 1<< PB5;
        for(b=15;b>=0;b--) {
            SPDR = disp_buffer[index][b];
            _delay_us(1.85);
            
        }
        SPDR=0;
        DDRB &= ~(1<<PB5);
    }
    if(line==312) {
        line=0;
        TIFR |= 1<<OCF1A;
    }
    line++;
}
Yes, the above code is the interrupt service routine where we enters on every 64uS. The first line makes the O/P zero which is a part of V or H synchronization. Then it checks if it reached the bottom line. If so we need to provide a vertical synchronization as mentioned in my previous post. So, the line 7 will do it. Then we need to reset the timer otherwise our future calculations may not work as expected. Then as usual, we need to give H sync (Horizontal synchronization).
Later we start to display the contents on the buffer. Now we are not interested in all of the lines because we need some time of processor for some other activities like picture drawing etc because some drawing need some more processor time for quick result. Also we have only 64 pixel in vertical. So,
if(line >= 100 && line <228) 
will select lines in between 100 and 228. ie 228-100=128 = 64*2
So one pixel line is shared by two lines on the tv.
 Now the code
index = pgm_read_byte(&(line_lookup[line-100])); 
will find the index of the y axis of the two dimensional display buffer. It is implemented as look up table to reduce the time wastage for calculations. Now the next part of the code is to push each byte(15 to 0) bit by bit to the TV as 'white voltage'. The better method for this is to use the inbuilt SPI module. The reason is it can push one by one bits in background from the time we put the 8 bit value to it, so that we can do some other work while it is automatically pushing x axis bits from the display buffer to tv.... So when 16*8 bits are pushed, we displayed 128 pixels. Then we return from the interrupt. Similarly we displays all the 64 lines and then it continues like that and thus we MAPPED THE DISPLAY BUFFER TO OUR TV.... 60% WORK IS FINISHED... ;-)

Now the next step is to make our PENCIL...
Fortunately the pencil is a very simple code:
void setpix(unsigned char x, unsigned char y)
{
    disp_buffer[63-y][pgm_read_byte(&h_pixel[2*x])] |= pgm_read_byte(&h_pixel[2*x + 1]);
}


So we made the pencil now!!!

to make the eraser , we just need to use &= ~( in the above code instead of |=
......

So the building tools are ready.
Now the remaining is pure mathematics.

See how a line is drawn on the screen:
drawline(unsigned char x1, unsigned char y1,unsigned char x2,unsigned char y2)
{
    unsigned char l,b,q;
    
    if(x1>127)x1=127;
    if(y1>63)y1=63;
    if(x2>127)x2=127;
    if(y2>63)y2=63;
    if(x1>x2){q=x1;x1=x2;x2=q;q=y1;y1=y2;y2=q;}
    
    if(y2>y1) {
        l=x2-x1;
        b=y2-y1;
        if(l>=b)
        for(q=0;q<=l;q++)
        setpix(x1 + q, (y1 + ((q*b)/l)));
        else
        for(q=0;q<=b;q++)
        setpix((x1 + ((q*l)/b)), y1 + q);
    }
    else{
        l=x2-x1;
        b=y1-y2;
        if(l>=b)
        for(q=0;q<=l;q++)
        setpix(x1 + q, (y1 - ((q*b)/l)));
        else
        for(q=0;q<=b;q++)
        setpix((x1 + ((q*l)/b)), y1 - q);
    }
}

The above code is the line drawing function. It draws line if we provide two points.

to draw a line from 0,0 to 120,60
drawline(0,0,120,60);
How simple it is!
Similarly there are many function here,
like draw circle, draw box, clear box, fill box, fill circle etc etc.. Also we can easily make new functions according to our need...

Another interesting thing is , we can do some sort of animations here, by some tricky loop codings etc..

Any way, here is the complete source code of this stuff. It draws a cube and a nearby circle as in one of the pic...
R1=6.2K, R2=2.2K, R3=470 ohms. 
10k is connected to reset(forgot to mark it). 
NOTE: Actually there should be a 75 ohms internal impedance for the composite video input of TV, that is what I learned from many reference sites., but any way I don't know why it is not there in my converter. So I am using above circuit, but some time it may not work in ur case, then assume 75 ohms internal impedence is there and design the R1,R2 accordingly (or check my reference in my previous post, there u can see Rickard's pong game circuit).
Condition is: 
PC0=1, MOSI=0   => .3v o/p
    PC0=1,MOSI=1  =>.8 ~1v o/p
FINAL SOURCE CODE:
/*
Drawing geometric figures on PAL TV using Atmega32 
By Vinod S  

Date: 10/04/2012

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
#include<avr/io.h>
#define F_CPU 16450000
#include <util/delay.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <string.h>
#define ZERO PORTC=0
#define BLACK PORTC=1

extern const unsigned char line_lookup[] PROGMEM;
extern const unsigned char h_pixel[] PROGMEM;

volatile unsigned int line;
volatile unsigned char disp_buffer[64][16];
volatile unsigned char and1,and2,and3,and4;
volatile unsigned char ret[3];
unsigned char ball1 = 1;
unsigned char ball2 = 8;
unsigned char box1[4];

void spi_init();
void timer1_init();
void setpix(unsigned char, unsigned char);
void clrpix(unsigned char, unsigned char);
void drawbox(unsigned char, unsigned char,unsigned char,unsigned char);
void clrbox(unsigned char, unsigned char,unsigned char,unsigned char);
void fillbox(unsigned char, unsigned char,unsigned char,unsigned char);
void emptybox(unsigned char, unsigned char,unsigned char,unsigned char);
void drawline(unsigned char, unsigned char,unsigned char,unsigned char);
void clrline(unsigned char, unsigned char,unsigned char,unsigned char);
void drawcircle(unsigned char, unsigned char,unsigned char);
void clrcircle(unsigned char, unsigned char,unsigned char);
void drawchessboard(unsigned char, unsigned char,unsigned char,unsigned char);
void clrchessboard(unsigned char, unsigned char,unsigned char,unsigned char);


ISR (TIMER1_COMPA_vect)
{
    ZERO;
    char b;
    unsigned char index;
    if(line == 313) {
        _delay_us(28);BLACK;_delay_us(4);ZERO;_delay_us(28);BLACK;_delay_us(4);
        TCNT1=0;
    }
    
    _delay_us(4);BLACK;_delay_us(10);
    
    if(line >= 100 && line <228) {
        index = pgm_read_byte(&(line_lookup[line-100])); 
        DDRB |= 1<< PB5;
        for(b=15;b>=0;b--) {
            SPDR = disp_buffer[index][b];
            _delay_us(1.85);
            
        }
        SPDR=0;
        DDRB &= ~(1<<PB5);
    }
    if(line==313) {
        line=0;
        TIFR |= 1<<OCF1A;
    }
    line++;
}



void main()
{
    DDRC = 1;
    spi_init();
    timer1_init();
    
    while(1) {
        
        //cube///
        drawbox(10,10,30,30);
        drawbox(20,20,30,30);
        drawline(40,40,50,50);
        drawline(40,10,50,20);
        drawline(10,10,20,20);
        drawline(10,40,20,50);
        //circle//
        drawcircle(74,30,20);
        //filled box//
        fillbox(40,0,10,5);
        while(1);
        
    }
}

/*---------------------------FUNCTIONS----------------------------------*/

void setpix(unsigned char x, unsigned char y)
{
    disp_buffer[63-y][pgm_read_byte(&h_pixel[2*x])] |= pgm_read_byte(&h_pixel[2*x + 1]);
}


void clrpix(unsigned char x, unsigned char y)
{
    disp_buffer[63-y][pgm_read_byte(&h_pixel[2*x])] &= ~pgm_read_byte(&h_pixel[2*x + 1]);
}

void drawbox(unsigned char x1, unsigned char y1,unsigned char l,unsigned char b)
{
    if(x1>127)x1=127;
    if(y1>63)y1=63;
    unsigned char q,temp1,temp2;
    temp1=x1+l;
    if(temp1>127)temp1=127;
    temp2=y1+b;
    if(temp2>63)temp2=63;
    for(q=x1;q<=temp1;q++) {
        setpix(q,y1);
        setpix(q,temp2);
    }
    for(q=y1;q<=temp2;q++) {
        setpix(x1,q);
        setpix(temp1,q);
    }
}

void clrbox(unsigned char x1, unsigned char y1,unsigned char l,unsigned char b)
{
    if(x1>127)x1=127;
    if(y1>63)y1=63;
    unsigned char q,temp1,temp2;
    if(temp1>127)temp1=127;
    temp2=y1+b;
    if(temp2>63)temp2=63;
    for(q=x1;q<=temp1;q++) {
        clrpix(q,y1);
        clrpix(q,temp2);
    }
    for(q=y1;q<=temp2;q++) {
        clrpix(x1,q);
        clrpix(temp1,q);
    }
}

void fillbox(unsigned char x1, unsigned char y1,unsigned char l,unsigned char b)
{
    if(x1>127)x1=127;
    if(y1>63)y1=63;
    unsigned char q,temp1,temp2,r;
    temp1=x1+l;
    if(temp1>127)temp1=127;
    temp2=y1+b;
    if(temp2>63)temp2=63;
    for(q=x1;q<=temp1;q++) {
        for(r=y1;r<temp2;r++)
        setpix(q,r);
    }
    for(q=y1;q<=temp2;q++) {
        for(r=y1;r<temp1;r++)
        setpix(r,q);
    }
}

void emptybox(unsigned char x1, unsigned char y1,unsigned char l,unsigned char b)
{
    if(x1>127)x1=127;
    if(y1>63)y1=63;
    unsigned char q,temp1,temp2,r;
    temp1=x1+l;
    if(temp1>127)temp1=127;
    temp2=y1+b;
    if(temp2>63)temp2=63;
    for(q=x1;q<=temp1;q++) {
        for(r=y1;r<temp2;r++)
        clrpix(q,r);
    }
    for(q=y1;q<=temp2;q++) {
        for(r=y1;r<temp1;r++)
        clrpix(r,q);
    }
}

void drawline(unsigned char x1, unsigned char y1,unsigned char x2,unsigned char y2)
{
    unsigned char l,b,q;
    
    if(x1>127)x1=127;
    if(y1>63)y1=63;
    if(x2>127)x2=127;
    if(y2>63)y2=63;
    if(x1>x2){q=x1;x1=x2;x2=q;q=y1;y1=y2;y2=q;}
    
    if(y2>y1) {
        l=x2-x1;
        b=y2-y1;
        if(l>=b)
        for(q=0;q<=l;q++)
        setpix(x1 + q, (y1 + ((q*b)/l)));
        else
        for(q=0;q<=b;q++)
        setpix((x1 + ((q*l)/b)), y1 + q);
    }
    else{
        l=x2-x1;
        b=y1-y2;
        if(l>=b)
        for(q=0;q<=l;q++)
        setpix(x1 + q, (y1 - ((q*b)/l)));
        else
        for(q=0;q<=b;q++)
        setpix((x1 + ((q*l)/b)), y1 - q);
    }
}
void clrline(unsigned char x1, unsigned char y1,unsigned char x2,unsigned char y2)
{
    unsigned char l,b,q;
    
    if(x1>127)x1=127;
    if(y1>63)y1=63;
    if(x2>127)x2=127;
    if(y2>63)y2=63;
    if(x1>x2){q=x1;x1=x2;x2=q;q=y1;y1=y2;y2=q;}
    
    if(y2>y1) {
        l=x2-x1;
        b=y2-y1;
        if(l>=b)
        for(q=0;q<=l;q++)
        clrpix(x1 + q, (y1 + ((q*b)/l)));
        else
        for(q=0;q<=b;q++)
        clrpix((x1 + ((q*l)/b)), y1 + q);
    }
    else{
        l=x2-x1;
        b=y1-y2;
        if(l>=b)
        for(q=0;q<=l;q++)
        clrpix(x1 + q, (y1 - ((q*b)/l)));
        else
        for(q=0;q<=b;q++)
        clrpix((x1 + ((q*l)/b)), y1 - q);
    }
}

void drawcircle(unsigned char x,unsigned char y,unsigned char r)
{
    unsigned char p,q;
    unsigned int sqlow, sqhigh;
    unsigned int value;
    sqlow = ((unsigned int)(r-1) * r-1);
    sqhigh = ((unsigned int)(r+1) * r+1);
    for(p=x-r;p<=x+r;p++)
    for(q=y-r;q<=y+r;q++) {
        value = (unsigned int)(p-x)*(p-x) + (unsigned int)(q-y)*(q-y);
        if(value >sqlow && value < sqhigh) {
            setpix(p,q);
        }
    }
}


void clrcircle(unsigned char x,unsigned char y,unsigned char r)
{
    unsigned char p,q;
    unsigned int sqlow, sqhigh;
    unsigned int value;
    sqlow = ((unsigned int)(r-1) * r-1);
    sqhigh = ((unsigned int)(r+1) * r+1);
    for(p=x-r;p<=x+r;p++)
    for(q=y-r;q<=y+r;q++) {
        value = (unsigned int)(p-x)*(p-x) + (unsigned int)(q-y)*(q-y);
        if(value >sqlow && value < sqhigh)
        clrpix(p,q);
    }
}

void drawchessboard(unsigned char x1, unsigned char y1,unsigned char l,unsigned char b)
{
    if(x1>127)x1=127;
    if(y1>63)y1=63;
    unsigned char q,temp1,temp2,r;
    temp1=x1+l;
    if(temp1>127)temp1=127;
    temp2=y1+b;
    if(temp2>63)temp2=63;
    for(q=x1;q<=temp1;q++) {
        for(r=y1;r<temp2;r++)
        if(q&1) {
            if(r&1)setpix(q,r);
            else(clrpix(q,r));
        } else
        if(r&1)clrpix(q,r);
        else(setpix(q,r));
    }
}
void clrchessboard(unsigned char x1, unsigned char y1,unsigned char l,unsigned char b)
{
    if(x1>127)x1=127;
    if(y1>63)y1=63;
    unsigned char q,temp1,temp2,r;
    temp1=x1+l;
    if(temp1>127)temp1=127;
    temp2=y1+b;
    if(temp2>63)temp2=63;
    for(q=x1;q<=temp1;q++) {
        for(r=y1;r<temp2;r++)
        clrpix(q,r);
    }
}

void spi_init()
{
    DDRB |= (1<<5)|(1<<7)|(1<<4);
    SPCR = (1<<SPE)|(1<<MSTR)|(1<<CPHA);
    
}

void timer1_init()
{
    TCCR1B |= (1 << WGM12)|(1 << CS10);
    TCNT1 = 0;
    OCR1A = (F_CPU/1000000)*64;
    TIMSK |= (1 << OCIE1A);
    sei();
}


const unsigned char line_lookup[] PROGMEM = {
    0,0,
    1,1,
    2,2,
    3,3,
    4,4,
    5,5,
    6,6,
    7,7,
    8,8,
    9,9,
    10,10,
    11,11,
    12,12,
    13,13,
    14,14,
    15,15,
    16,16,
    17,17,
    18,18,
    19,19,
    20,20,
    21,21,
    22,22,
    23,23,
    24,24,
    25,25,
    26,26,
    27,27,
    28,28,
    29,29,
    30,30,
    31,31,
    32,32,
    33,33,
    34,34,
    35,35,
    36,36,
    37,37,
    38,38,
    39,39,
    40,40,
    41,41,
    42,42,
    43,43,
    44,44,
    45,45,
    46,46,
    47,47,
    48,48,
    49,49,
    50,50,
    51,51,
    52,52,
    53,53,
    54,54,
    55,55,
    56,56,
    57,57,
    58,58,
    59,59,
    60,60,
    61,61,
    62,62,
    63,63
};



PROGMEM const unsigned char h_pixel[] = {
    
    15,1<<7,
    15,1<<6,
    15,1<<5,
    15,1<<4,
    15,1<<3,
    15,1<<2,
    15,1<<1,
    15,1<<0,
    
    14,1<<7,
    14,1<<6,
    14,1<<5,
    14,1<<4,
    14,1<<3,
    14,1<<2,
    14,1<<1,
    14,1<<0,
    
    13,1<<7,
    13,1<<6,
    13,1<<5,
    13,1<<4,
    13,1<<3,
    13,1<<2,
    13,1<<1,
    13,1<<0,
    
    12,1<<7,
    12,1<<6,
    12,1<<5,
    12,1<<4,
    12,1<<3,
    12,1<<2,
    12,1<<1,
    12,1<<0,
    
    11,1<<7,
    11,1<<6,
    11,1<<5,
    11,1<<4,
    11,1<<3,
    11,1<<2,
    11,1<<1,
    11,1<<0,
    
    10,1<<7,
    10,1<<6,
    10,1<<5,
    10,1<<4,
    10,1<<3,
    10,1<<2,
    10,1<<1,
    10,1<<0,
    
    9,1<<7,
    9,1<<6,
    9,1<<5,
    9,1<<4,
    9,1<<3,
    9,1<<2,
    9,1<<1,
    9,1<<0,
    
    8,1<<7,
    8,1<<6,
    8,1<<5,
    8,1<<4,
    8,1<<3,
    8,1<<2,
    8,1<<1,
    8,1<<0,
    
    7,1<<7,
    7,1<<6,
    7,1<<5,
    7,1<<4,
    7,1<<3,
    7,1<<2,
    7,1<<1,
    7,1<<0,
    
    
    
    6,1<<7,
    6,1<<6,
    6,1<<5,
    6,1<<4,
    6,1<<3,
    6,1<<2,
    6,1<<1,
    6,1<<0,
    
    5,1<<7,
    5,1<<6,
    5,1<<5,
    5,1<<4,
    5,1<<3,
    5,1<<2,
    5,1<<1,
    5,1<<0,
    
    4,1<<7,
    4,1<<6,
    4,1<<5,
    4,1<<4,
    4,1<<3,
    4,1<<2,
    4,1<<1,
    4,1<<0,
    
    3,1<<7,
    3,1<<6,
    3,1<<5,
    3,1<<4,
    3,1<<3,
    3,1<<2,
    3,1<<1,
    3,1<<0,
    
    2,1<<7,
    2,1<<6,
    2,1<<5,
    2,1<<4,
    2,1<<3,
    2,1<<2,
    2,1<<1,
    2,1<<0,
    
    1,1<<7,
    1,1<<6,
    1,1<<5,
    1,1<<4,
    1,1<<3,
    1,1<<2,
    1,1<<1,
    1,1<<0,
    
    0,1<<7,
    0,1<<6,
    0,1<<5,
    0,1<<4,
    0,1<<3,
    0,1<<2,
    0,1<<1,
    0,1<<0
};

5 comments:

ismailpp2008 said...

Da puliyea................

canalizando informação said...

Good!

xD

Embedded For You said...

Not working with 16 Mhz crystal or any other higher crystal and MCU options.
Is there anything else that should be changed apart from F_CPU if using 16 Mhzoc.

Владимир Ворошилов said...

Hi, Your site does not exist. So, no spam in this good man blog.

llewellyn said...

hey doesnt atmega work max at 16Mhz? u have used a 16.45 Mhz?/

Post a Comment

SHARE IT on FB