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
};

69 comments :

  1. 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.

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

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

    ReplyDelete
  3. Hi excellent article, you can also consider taking embedded training in Chennai to secure career in IT industry. As embedded systems are heart of every larger machine, it offers huge career prospects for talented professionals. embedded systems training in Chennai

    ReplyDelete
  4. Well Written and Structured Blog. Totally a fan of your writing, I found this blog unique and i have been going through the posts in your blog. Independence Day Speech for Students

    ReplyDelete
  5. thumbsup for the author well explained. very usefull thanks for sharing.

    Data Science Training in Chennai

    ReplyDelete

  6. Thankyou for sharing this good information,nice blog.Python Training in Chennai

    ReplyDelete
  7. This comment has been removed by the author.

    ReplyDelete
  8. I would like to thank you for your nicely written post, its informative and your writing style encouraged me to read it till end. Thanks

    angularjs-Training in annanagar

    angularjs Training in chennai

    angularjs Training in chennai

    angularjs Training in bangalore

    ReplyDelete
  9. Awesome article. It is so detailed and well formatted that i enjoyed reading it as well as get some new information too.
    python training Course in chennai | python training in Bangalore | Python training institute in kalyan nagar

    ReplyDelete
  10. Really great post, Thank you for sharing This knowledge.Excellently written article, if only all bloggers offered the same level of content as you, the internet would be a much better place. Please keep it up!

    advanced excel training in bangalore

    ReplyDelete
  11. Thanks for the good words! Really appreciated. Great post. I’ve been commenting a lot on a few blogs recently, but I hadn’t thought about my approach until you brought it up. 

    Java training in Annanagar | Java training in Chennai

    Java training in Chennai | Java training in Electronic city

    ReplyDelete
  12. This comment has been removed by the author.

    ReplyDelete
  13. Your post is really awesome. Your blog is really helpful for me to develop my skills in a right way. Thanks for sharing this unique information with us.
    - Learn Digital Academy

    ReplyDelete
  14. Hello! This is my first visit to your blog! We are a team of volunteers and starting a new initiative in a community in the same niche. Your blog provided us useful information to work on. You have done an outstanding job.

    Best AWS Training in Chennai | Amazon Web Services Training in Chennai
    AWS Training in Bangalore | Amazon Web Services Training in Bangalore
    Amazon Web Services Training in OMR , Chennai | Best AWS Training in OMR,Chennai

    ReplyDelete
  15. It’s hard to come by experienced people about this subject, but you seem like you know what you’re talking about! Thanksmovie box

    ReplyDelete
  16. This comment has been removed by the author.

    ReplyDelete
  17. I still remember my childhood when i used to play videos games, now a days i play clash royale mod hack version apk with full joy.

    ReplyDelete
  18. Very amazing post. It would be really handy to use. Health Insurance Quotes

    ReplyDelete
  19. Nice Post. Thanks for sharing information about it. List of drug rehabs

    ReplyDelete
  20. Amazing blog. Thanks for posting about this article. Rehab programs near me

    ReplyDelete
  21. I really like this blog. Thanks for sharing. DHT conditioner

    ReplyDelete
  22. Great Information sharing. Thank you very much for this great post. ArticleBase

    ReplyDelete
  23. Amazing your content. Thank you for sharing. How to create www.gmail.com account? You want to create gmail account? Visit this website https://teachnow.xyz/gmail-login/

    ReplyDelete
  24. Amazing your article. Thank you for sharing. buy clones

    ReplyDelete
  25. All are saying the same thing repeatedly, but in your blog I had a chance to get some useful and unique information, I love your writing style very much, I would like to suggest your blog in my dude circle, so keep on updates.
    aws online training

    data science with python online training

    data science online training

    rpa online training

    ReplyDelete
  26. I Really Like your blog. Thanks for sharing. mobil telefonlar

    ReplyDelete
  27. Its a good post and keep posting good article.its very interesting to read.
    R Programming Training in chennai

    ReplyDelete
  28. 8 ball pool is a standout amongst the most famous diversions around the globe. This well known billiard 8 ball pool is straightforward and an arcade diversion played by each sex and distinctive ages. You will locate this amusement in each edge of the world. There are some abnormal state rivalries of this amusement done online for trophies and cash.

    https://miniclipgamesguide.jimdofree.com/2019/03/25/hoggart-twins-set-for-world-pool-champs/

    ReplyDelete
  29. Thank you for your post, I look for such article along time, today i find it finally. this post give me lots of advise it is very useful for me. Health blogger

    ReplyDelete
  30. Good post. I learn something totally new and challenging on sites I stumbleupon on a daily basis. It will always be interesting to read through content from other writers and practice a little something from other websites.
    Azar Mod APK Brief Info

    ReplyDelete
  31. Avast customer service is available around the clock . you can call our avast customer support team for solution of all kind of avast antivirus related issues like avast login problem, avast account issues, avast billing problems etc. Just need to dial our avast customer service number 1-855-499-1999.
    Avast Customer Service

    ReplyDelete
  32. Canon Printer Support is a team of experts who takes care of your canon device issue. If face any problem with your device like your device is crashed , device is not working properly or others then contact Canon Printer Support Phone Number and get instant help.

    ReplyDelete
  33. This comment has been removed by the author.

    ReplyDelete
  34. You are doing a great job. I would like to appreciate your work for good accuracy
    web design company in velachery

    ReplyDelete