www.micro-examples.com : LCDscope

Views
From www.micro-examples.com
(Difference between revisions)
Jump to: navigation, search
(LCDscope : a text LCD oscilloscope)
 
(5 intermediate revisions by one user not shown)
Line 1: Line 1:
How to build a simple and low-cost oscilloscope with a text LCD ?
+
[[File:LCDscope.jpg|thumb|LCDscope screen shot]]
 +
How to build a simple and low-cost [[wikipedia:Oscilloscope|oscilloscope]] with a text LCD ?
 
==LCDscope : a text LCD oscilloscope==
 
==LCDscope : a text LCD oscilloscope==
Let me introduce LCDscope to you with this short video clip :
+
A short video clip is sometimes better than a long explanation :
 
{{#ev:youtube|71LeCc8I5t0}}
 
{{#ev:youtube|71LeCc8I5t0}}
  
The idea of this circuit is use text LCD programmable characters to display a signal. A function generator was used to record the videoclip.
+
The idea of this circuit is use text LCD programmable characters to display a signal. A [[wikipedia:Function generator|function generator]] was used to generate the signal we can see in the the videoclip.
  
 
==Circuit Schematic==
 
==Circuit Schematic==
Line 702: Line 703:
 
[[Category:Projects]]
 
[[Category:Projects]]
 
[[Category:PIC18]]
 
[[Category:PIC18]]
 +
==Discussion and Comments==
 +
{{#w4grb_rate:}}
 +
<discussion />

Latest revision as of 22:57, 12 February 2012

LCDscope screen shot

How to build a simple and low-cost oscilloscope with a text LCD ?

Contents

 [hide

LCDscope : a text LCD oscilloscope

A short video clip is sometimes better than a long explanation :


The idea of this circuit is use text LCD programmable characters to display a signal. A function generator was used to generate the signal we can see in the the videoclip.

Circuit Schematic

LCDscope-schematic.png

  • LCD1 is a 4 x 20 text LCD, but it works also with a classic 2x 16 text LCD (see source code) :

LCDscope-LCD-2x16.jpg

  • input signal is 0...5V
  • you can find the LCD at CircuitED

C Source code

/*
 *******************************************************************************
 * LCDscope : a PIC18 oscilloscope on a 20x4 text LCD
 * also works with a standard 16x4 text LCD
 *******************************************************************************
 *
 * This program shows how to use custom chars on a text LCD to build a tiny graphic screen
 * To illustrate this, here is a quick & dirty mini oscilloscope.
 *
 * Author : Bruno Gavand, February 2009
 * see more details on http://www.micro-examples.com/
 *
 * source code for mikro C compiler V8.2
 * feel free to use this code at your own risks
 *
 * target : PIC18 with 10 Mhz crystal, HS PLL
 *
 * PIC PIN Assignemnt :
 *
 * RA0 : analog input, 0-5V
 *
 * control buttons on PORTB (internal pull-ups, switchs to GND) :
 * RB0/RB1 : change horizontal frequency
 * RB2/RB3 : change input range
 * RB4 : hold/release screen
 *
 * 4-bit LCD on PORTD :
 * RD2 : RS
 * RD3 : E
 * RD4 : D4
 * RD5 : D5
 * RD6 : D6
 * RD7 : D7
 * Note : R/W pin of LCD must be tied to ground.
 *
 * Credits to CircuitED for the 20x4 black/yellow LCD
 * http://www.circuit-ed.com/
 * Thank you Warren !
 *
 *******************************************************************************
 */

/*********************
 * CONSTANTS
 *********************/

// if you have a 16x2 standard LCD, untag this line :
//#define LCD16x2
// otherwise, you are supposed to have the nice 20x4 LCD
#ifndef LCD16x2
#define LCD20x4
#endif

#define C_X             4               // number of columns in pseudo-graphic screen
#define C_Y             2               // number of rows in pseudo-graphic screen

#define miniGLCD_x      (5*C_X)         // number of X pixels in pseudo-graphic screen
#define miniGLCD_y      (8*C_Y)         // number of Y pixels in pseudo-graphic screen

#define LEVEL           16              // trigger sensitivity (number of ADC points)

// colors of pseudo-graphic screen
#define miniGLCD_COLOR_WHITE         0
#define miniGLCD_COLOR_BLACK         1
#define miniGLCD_COLOR_REVERSE       2

// pseudo-graphic function prototypes
void    miniGLCD_fill(unsigned char c) ;
void    miniGLCD_setPixel(char x, char y, unsigned char mode) ;
void    miniGLCD_line(char x0, char y0, char x1, char y1, unsigned char pcolor) ;
unsigned char miniGLCD_getPixel(char x, char y) ;

/***************
 * RAM VARIABLES
 ***************/

// horizontal frequency structure
struct TIMEBASE
        {
        unsigned char   t0con ;         // timer 0 configuration
        unsigned char   period[8] ;     // period value
        unsigned char   unit ;          // period unit
        } timeBase[] =
                {
                        {       0b10000100, "1.04857", ' ' },
                        {       0b10000011, "524.288", 'm' },
                        {       0b10000010, "262.144", 'm' },
                        {       0b10000001, "131.072", 'm' },
                        {       0b10000000, "65.536 ", 'm' },
                        {       0b11000111, "32.768 ", 'm' },
                        {       0b11000110, "16.384 ", 'm' },
                        {       0b11000101, "8.192  ", 'm' },
                        {       0b11000100, "4.096  ", 'm' },
                        {       0b11000011, "2.048  ", 'm' },
                        {       0b11000010, "1.024  ", 'm' },
                        {       0b11000001, "512    ", '\xe4' },
                        {       0b11000000, "256    ", '\xe4' },
                        {       0b11001000, "128    ", '\xe4' }
                } ;
unsigned char   tbase = 0 ;             // current timebase index

// vertical input range structure
struct INPUT
        {
        unsigned char   div ;           // power of 2 of input divider
        unsigned char   *ampl ;         // range value in volts
        } input[] =
                {
                        {       4, "2.500" },
                        {       2, "1.250" },
                        {       1, "0.625" },
                } ;
unsigned char   ipt = 0 ;               // current input range index
unsigned char   vdiv ;                  // current power of 2 of input divider

#ifdef LCD20x4
// scrolling message
unsigned char msg[][8] =
        {
        "       ",
        "       ",
        "       ",
        "       ",
        "  LCD  ",
        " SCOPE ",
        "  By   ",
        "BrunoG ",
        "*      ",
        "       ",
        "       ",
        "  see  ",
        " more  ",
        "details",
        "  and  ",
        "dwnload",
        "mikroC ",
        "source ",
        "code on",
        "       ",
        "www.   ",
        "micro- ",
        "example",
        "s.com  ",
        "*      ",
        "       ",
        "       ",
        "20x4LCD",
        "Black/ ",
        "Yellow ",
        "BkLight",
        "specs.&",
        "price :",
        "       ",
        "www.   ",
        "circuit",
        "-ed.com",
        "*      ",
        "       ",
        "       ",
        " Thank ",
        "  you  ",
        "Warren!",
        ""
        } ;
unsigned char   firstMsg = 0 ;          // scrolling message index
#endif

unsigned char   miniGLCD_screen[C_X * C_Y * 8] ;        // pseudo-screen bitmap

unsigned char   samples[miniGLCD_x] ;   // sample table
unsigned char   sIdx = 0 ;              // sample index

unsigned char   trigger = 0 ;           // trigger status
unsigned char   trigValue = 0 ;         // trigger value

unsigned char   hold = 0 ;              // hold screen flag

unsigned int    t0ctr ;                 // timer 0 overflow counter

/****************************
 * INTERRUPT ROUTINE
 ****************************/
void    interrupt()
        {
        // only timer 0 overflow is due to service
        if(INTCON.TMR0IF)
                {
                if(sIdx < miniGLCD_x)                   // is sampling in progress ?
                        {
                        if(trigger == 2)                // is sampling triggered ?
                                {
                                // read ADC sample, adjust to range and store to sample buffer
                                samples[sIdx++] = miniGLCD_y - (ADRESH >> vdiv) ;
                                }
                        else if(trigger == 1)           // maximum was detected
                                {
                                // is signal rising down ?
                                if((trigValue > LEVEL) && (ADRESH < trigValue - LEVEL))
                                        {
                                        // yes, triggers sampling
                                        trigger = 2 ;
                                        }
                                else
                                        {
                                        // no, update maximum value
                                        if(ADRESH > trigValue)
                                                {
                                                trigValue = ADRESH ;
                                                }
                                        }
                                }
                        else                            // looking for maximum
                                {
                                // is signal rising up ?
                                if((trigValue < 255 - LEVEL) && (ADRESH > trigValue + LEVEL))
                                        {
                                        // yes, next step is to wait for signal rising down
                                        trigger = 1 ;
                                        trigValue = 0 ;
                                        }
                                else
                                        {
                                        // no, update minimum value
                                        if(ADRESH < trigValue)
                                                {
                                                trigValue = ADRESH ;
                                                }
                                        }
                                }
                                
                        // start ADC, no sampling is required since ADC
                        // is always connected to the same input
                        ADCON0.GO = 1 ;
                        }

                t0ctr++ ;
                INTCON.TMR0IF = 0 ;
                }
        }

/*******************************
 * UPDATE SETTINGS & DRAW SCREEN
 *******************************/
void    mkScreen()
        {
        T0CON = timeBase[tbase].t0con ;                         // new timer 0 settings
        vdiv = input[ipt].div ;                                 // store input divider

        // ADC settings
        ADCON1 = 0b00001110 ;
        ADCON0 = 0b11000001 ;

#ifdef  LCD20x4
        LCD_out(1, 14, timeBase[tbase].period) ;                // display period value
        LCD_chr(2, 15, timeBase[tbase].unit) ;                  // display period unit

        LCD_out(3, 16, input[ipt].ampl) ;                       // display input range

        // hold screen ?
        LCD_out(1, 9, hold ? "Hold" : "\xff\xff\xff\xff") ;
#else
        LCD_out(1, 12, timeBase[tbase].period) ;                // display period value
        LCD_chr(2, 12, timeBase[tbase].unit) ;                  // display period unit

        LCD_out(1, 1, input[ipt].ampl) ;                       // display input range

        // hold screen ?
        LCD_out(1, 6, hold ? "H" : "\xff") ;
#endif
        }

/************************
 * UPDATE SCREEN AND DEBOUNCE PORTB KEYS
 ************************/
void    debounce()
        {
        mkScreen() ;
        
        Delay_ms(20) ;
        while(PORTB != 0xff) ;
        Delay_ms(20) ;
        }

/***********************
 * PSEUDO GRAPHIC FUNCTIONS
 ***********************/
 
/************************************************
 * miniGLCD_fill : fill graphic screen with pattern
 * parameters :
 *      c : filling pattern
 *              for example : 0 for black screen, 0xff for white screen
 * returns :
 *      nothing
 * requires :
 *      miniGLCD_init must have been called
 * notes :
 *      none
 */
void    miniGLCD_fill(unsigned char c)
        {
        memset(miniGLCD_screen, c, sizeof(miniGLCD_screen)) ;
        }

/********************************************
 * miniGLCD_setPixel : write pixel
 * parameters :
 *      x : pixel row
 *      y : pixel column
 *      mode : miniGLCD_COLOR_WHITE or miniGLCD_COLOR_BLACK or miniGLCD_COLOR_REVERSE
 * returns :
 *      nothing
 * requires :
 *      miniGLCD_init must have been called
 * notes :
 *      none
 */
void    miniGLCD_setPixel(char x, char y, unsigned char mode)
        {
        unsigned char *ptr ;
        unsigned char mask ;

        /*
         * do nothing if pixel is out of bounds
         */
        if(x < 0) return ;
        if(y < 0) return ;
        if(x > miniGLCD_x) return ;
        if(y > miniGLCD_y) return ;

        ptr = miniGLCD_screen + (((y * (C_X * 8)) + x) / 8) ;      // points to byte in screen map
        mask = 1 << (x & 7) ;                           // pixel bit mask

        switch(mode)
                {
                case miniGLCD_COLOR_BLACK:
                        *ptr &= ~mask ;                 // clear bit
                        break ;
                case miniGLCD_COLOR_WHITE:                   // set bit
                        *ptr |= mask ;
                        break ;
                default:
                        *ptr ^= mask ;                  // toggle bit
                        break ;
                }
        }

/********************************************
 * miniGLCD_setPixel : read pixel
 * parameters :
 *      x : pixel row
 *      y : pixel column
 * returns :
 *      color of pixel at (x, y)
 * requires :
 *      miniGLCD_init must have been called
 * notes :
 *      none
 */
unsigned char miniGLCD_getPixel(char x, char y)
        {
        unsigned char *ptr ;
        unsigned char mask ;

        /*
         * do nothing if pixel is out of bounds
         */
        if(x < 0) return(0) ;
        if(y < 0) return(0) ;
        if(x > miniGLCD_x) return(0) ;
        if(y > miniGLCD_y) return(0) ;

        ptr = miniGLCD_screen + (((y * (C_X * 8)) + x) / 8) ;      // points to byte in screen map
        mask = 1 << (x & 7) ;                           // pixel bit mask

        return(*ptr & mask) ;
        }

/******************************
 * miniGLCD_line : draw a line
 * parameters :
 *      x0, y0 : pixel start coordinates
 *      x1, y1 : pixel end coordinates
 *      pcolor : miniGLCD_COLOR_WHITE or miniGLCD_COLOR_BLACK or miniGLCD_COLOR_REVERSE
 * returns :
 *      nothing
 * requires :
 *      miniGLCD_init must have been called
 * notes :
 *      uses Bresenham's line drawing algorithm
 */
void miniGLCD_line(char x0, char y0, char x1, char y1, unsigned char pcolor)
        {
        int     dy ;
        int     dx ;
        int     stepx, stepy ;

        dy = y1 - y0 ;
        dx = x1 - x0 ;

        if(dy < 0)
                {
                dy = -dy ;
                stepy = -1 ;
                }
        else
                {
                stepy = 1 ;
                }

        if(dx < 0)
                {
                dx = -dx ;
                stepx = -1 ;
                }
        else
                {
                stepx = 1 ;
                }

        dy <<= 1 ;
        dx <<= 1 ;

        miniGLCD_setPixel(x0, y0, pcolor) ;

        if(dx > dy)
                {
                int fraction = dy - (dx >> 1) ;

                while(x0 != x1)
                        {
                        if(fraction >= 0)
                                {
                                y0 += stepy ;
                                fraction -= dx ;
                                }
                        x0 += stepx ;
                        fraction += dy ;
                        miniGLCD_setPixel(x0, y0, pcolor) ;
                        }
                }
        else
                {
                int fraction = dx - (dy >> 1) ;

                while(y0 != y1)
                        {
                        if(fraction >= 0)
                                {
                                x0 += stepx ;
                                fraction -= dy ;
                                }
                        y0 += stepy ;
                        fraction += dx ;
                        miniGLCD_setPixel(x0, y0, pcolor) ;
                        }
                }
        }

/*************************************************************
 * program custom character n at line pos_row column pos_char
 * if mode is not zero, also write custom char to LCD
 */
void CustomChar(unsigned char mode, unsigned char n, char pos_row, char pos_char)
        {
        unsigned char    i, j ;

        LCD_Cmd(64 + n * 8) ;
        for(i = 0 ; i < 8 ; i++)
                {
                unsigned char bm = 0 ;

                for(j = 0 ; j < 5 ; j++)
                        {
                        bm <<= 1 ;
                        bm |= miniGLCD_getPixel(pos_char * 5 + j, pos_row * 8 + i) ? 1 : 0 ;
                        }
                LCD_Chr_Cp(bm) ;
                }
        LCD_Cmd(LCD_RETURN_HOME) ;
#ifdef LCD20x4
        if(mode) LCD_Chr(pos_row + 2, pos_char + 9, n) ;
#else
        if(mode) LCD_Chr(pos_row + 1, pos_char + 7, n) ;
#endif
        }

/******************
 * MAIN LOOP
 ******************/
void    main()
        {
        unsigned char i, j ;
        unsigned int    wait ;

        TRISA = 0xff ;                          // set PORTA as inputs

        TRISB = 0xff ;                          // set PORTB as inputs
        INTCON2.NOT_RBPU = 0 ;                  // enables PORTB weak pull-ups
        
        TRISD = 0 ;                             // PORTD is output (LCD in 4bit mode)

        // enables timer 0 overflow interrupt
        INTCON.TMR0IF = 0 ;
        INTCON.TMR0IE = 1 ;
        INTCON.GIE = 1 ;

        // LCD configuration
        LCD_Init(&LATD) ;                      // Initialize LCD connected to PORTD
        LCD_Cmd(Lcd_CLEAR) ;                   // Clear display
        LCD_Cmd(Lcd_CURSOR_OFF) ;              // Turn cursor off
        
        // display layout
#ifdef LCD20x4
        LCD_out(1, 8, "\xff\xff\xff\xff\xff\xff") ;
        LCD_out(2, 8, "\xff    \xff  s/Div") ;
        LCD_out(3, 8, "\xff    \xff") ;
        LCD_out(4, 8, "\xff\xff\xff\xff\xff\xff  V/Div") ;
#else
        LCD_out(1, 1, "     \xff    \xff") ;
        LCD_out(2, 1, "V/Div\xff    \xff s/Div") ;
#endif

        // send custom chars
        CustomChar(1, 0, 0, 0) ;
        CustomChar(1, 1, 0, 1) ;
        CustomChar(1, 2, 0, 2) ;
        CustomChar(1, 3, 0, 3) ;
        CustomChar(1, 4, 1, 0) ;
        CustomChar(1, 5, 1, 1) ;
        CustomChar(1, 6, 1, 2) ;
        CustomChar(1, 7, 1, 3) ;

        mkScreen() ;

        for(;;)                 // forever
                {
                // if not in hold mode and samples buffer is full
                if((hold == 0) && (sIdx == miniGLCD_x))
                        {
                        // clear pseudo-screen
                        miniGLCD_fill(0) ;

                        // draw wave
                        for(i = 0 ; i < miniGLCD_x - 1 ; i++)
                                {
                                j = i + 1 ;

                                miniGLCD_line(i, samples[i], j, samples[j], miniGLCD_COLOR_WHITE) ;
                                }

                        // program custom chars
                        CustomChar(0, 0, 0, 0) ;
                        CustomChar(0, 1, 0, 1) ;
                        CustomChar(0, 2, 0, 2) ;
                        CustomChar(0, 3, 0, 3) ;
                        CustomChar(0, 4, 1, 0) ;
                        CustomChar(0, 5, 1, 1) ;
                        CustomChar(0, 6, 1, 2) ;
                        CustomChar(0, 7, 1, 3) ;

                        // restart trigger and samples index
                        trigValue = 255 ;
                        trigger = 0 ;
                        sIdx = 0 ;
                        }

                // change horizontal frequency
                if(PORTB.F0 == 0)
                        {
                        tbase++ ;
                        if(tbase == sizeof(timeBase) / sizeof(struct TIMEBASE))
                                {
                                tbase = 0 ;
                                }
                        hold = 0 ;
                        debounce() ;
                        }
                else if(PORTB.F1 == 0)
                        {
                        if(tbase == 0)
                                {
                                tbase = sizeof(timeBase) / sizeof(struct TIMEBASE) - 1 ;
                                }
                        else
                                {
                                tbase-- ;
                                }
                        hold = 0 ;
                        debounce() ;
                        }

                // change vertical range
                else if(PORTB.F2 == 0)
                        {
                        ipt++ ;
                        if(ipt == sizeof(input) / sizeof(struct INPUT))
                                {
                                ipt = 0 ;
                                }
                        hold = 0 ;
                        debounce() ;
                        }
                else if(PORTB.F3 == 0)
                        {
                        if(ipt == 0)
                                {
                                ipt = sizeof(input) / sizeof(struct INPUT) - 1 ;
                                }
                        else
                                {
                                ipt-- ;
                                }
                        hold = 0 ;
                        debounce() ;
                        }

                // hold/release screen
                else if(PORTB.F4 == 0)
                        {
                        hold ^= 1 ;
                        debounce() ;
                        }

                // scrolling message
#ifdef LCD20x4
                if(wait)
                        {
                        if(t0ctr > (1u << (tbase + 5)))
                                {
                                firstMsg++ ;
                                if(msg[firstMsg][0] == 0)
                                        {
                                        firstMsg = 0 ;
                                        }

                                t0ctr = 0 ;
                                wait = 0 ;
                                }
                        }
                else if(t0ctr > (1u << (tbase + 1)))
                        {
                        j = firstMsg ;
                        for(i = 1 ; i <= 4 ; i++)
                                {
                                if((i == 4) && (msg[j + 1][0] == '*'))
                                        {
                                        wait++ ;
                                        }
                                if(msg[j][0] == '*')
                                        {
                                        LCD_out(i, 1, "       ") ;
                                        }
                                else
                                        {
                                        LCD_out(i, 1, msg[j]) ;
                                        }

                                j++ ;
                                if(msg[j][0] == 0)
                                        {
                                        j = 0 ;
                                        }
                                }

                        firstMsg++ ;
                        if(msg[firstMsg][0] == 0)
                                {
                                firstMsg = 0 ;
                                }

                        t0ctr = 0 ;
                        }
#endif
                }
        }

Download project

Download LCDscope-project.ZIP file for mikroC : File:LCDscope-project.zip

Includes :

  • mikroC PRO project files for PIC18F4620, should work also with most of PIC
  • LCDscope C source code
  • LCDscope .HEX files

Discussion and Comments

Current user rating: 87 (70 votes)
You didn't vote on this yet.

 87%

CrisKrueger
(11 months ago)
Reply
The south of Brazil has the most brilliant minds, a shame they are in Brazil.
oz
(1 year ago)
Reply
this is compatible whit pic 18f46k22?
led
(1 year ago)
Reply
 
mohiudin bhatti
(1 year ago)
Reply
dear sir
plz send me the hex files of lcd scope.
i m very thankful to u if u send me a pic homemade programmer diagrame please.
email id: md_bhatti99@yahoo.com
rob
(1 year ago)
Reply
Xloader for the C++ into hex.

Cheers!
Rob from electric-canada.com and whatisacnc.com
Giannis
(1 year ago)
Reply
Please,can you tell me if this firmware is working with PIC18F252?(P-DIP 28)

Best regards

Giannis
Thes/niki-Greece.
ponshankar
(1 year ago)
Reply
hi,i dont know how to write the lcd code for pic16f877 in mplab.
rc.ozzy
(1 year ago)
Reply
nice job!
rc.ozzy
(1 year ago)
Reply
Brunog, it's a great work! i've done the modifications to a pic18f4550 and it works fine! now i'm trying with a 18f4520, sure it works!
thanx by your great job, and microC is a great compiler.
Euclides
(1 year ago)
Reply
Whats the name of the music in the video?
mikevectra
(1 year ago)
Reply
i have 16x4 hex email me cristeamihi@yahoo.com
...

Navigation
Others
Donation
You can help :
with Paypal
Share
Personal tools
www.micro-examples.com Electronic circuits with micro-controllers, projects with source code examples