Cheap Ultrasonic Range Finder

A PIC ultrasonic distance sensor for embedded projects

PIC ultrasonic ranger

A simple ultrasonic range finder for embedded projects: presence detector, robotics, car parking.

Overview

A simple ultrasonic range finder for embedded projects: presence detector, robotics, car parking. With a few cheap components and less than 200 bytes of code, it works from 30 to 200 cm with approximately 1 cm accuracy.

Component side
Component side
Solder side
Solder side

How it Works

Sound travels at approximately 340 m/s in air. The principle is simple: send a 40 KHz ultrasonic pulse, then listen for the echo. The time between transmission and reception gives you the distance.

The PWM output at 40 KHz drives the ultrasonic transmitter via a transistor and resonator circuit. The resonator circuit boosts the voltage to about 20V, giving a range of up to 200 cm. The receiver is connected directly to the PIC ADC input.

Scope screen capture
Scope screen capture

Circuit Schematic

Ultrasonic range finder circuit schematic

C Source Code

sonar.c
/*
 * file         : sonar.c
 * project      : Simple UltraSonic Range Finder
 * compiler     : mikroC V6.2
 * date         : september 30, 2006
 *
 * description  :
 *      This is a basic ultrasonic range finder, from 30 to 200 centimeters
 *
 * target device :
 *      PIC16F877A with 8 Mhz crystal
 *      or any PIC with at least one ADC and PWM channel
 *
 * configuration bits :
 *      HS clock, no watchdog, no power up timer,
 *      no brown out, LVP disabled, ICD disabled
 *
 *******************************************************************************
 */

/********************
 * MACRO DEFINITIONS
 ********************/
#define PULSELEN        300     // ultra sonic pulse length in microseconds
#define BUFSIZE         10      // circular buffer size for averaging

#define LCDPORT PORTD
#define LCDTRIS TRISD

/*******************
 * GLOBAL VARIABLES
 *******************/
unsigned char   outOfRange ;            // out of range flag
unsigned int    buf[BUFSIZE] ;          // samples buffer
unsigned char   idx = 0 ;               // current sample index

/*****************************************
 * INTERRUPT SERVICE ROUTINE
 * handles TIMER1 overflow only
 *****************************************/
void    interrupt(void)
        {
        if(PIR1.TMR1IF)
                {
                outOfRange = 1 ;
                PIR1.TMR1IF = 0 ;
                }
        }

/************
 * MAIN LOOP
 ************/
void    main()
        {
        ADCON1 = 0 ;            // enables ADC

        TRISA = 0xff ;          // PORTA as inputs
        PORTA = 0 ;

        TRISC = 0 ;             // PORTC as outputs
        PORTC = 0 ;

        T1CON = 0b00001100 ;    // prescaler 1:1, osc enabled, internal clk, stopped

#ifdef   LCDPORT
        Lcd_Init(&LCDPORT) ;
        Lcd_Cmd(Lcd_CLEAR) ;
        Lcd_Cmd(Lcd_CURSOR_OFF) ;
        Lcd_Out(1, 1, "UltraSonicRanger") ;
        Lcd_Out(2, 5, "cm") ;
#endif

        // init PWM : 40 KHz, 50% duty cycle
        PWM1_Init(40000) ;
        PWM1_Change_Duty(128) ;

        INTCON.GIE = 1 ;
        INTCON.PEIE = 1 ;
        PIE1.TMR1IE = 0 ;
        PIR1.TMR1IF = 0 ;

        for(;;)
                {
                unsigned char   i ;
                unsigned long   cm ;
                unsigned char   str[4] ;

                // prepare timer
                T1CON.TMR1ON = 0 ;
                outOfRange = 0 ;
                TMR1H = 0 ;
                TMR1L = 0 ;

                T1CON.TMR1ON = 1 ;              // start timer 1
                PIE1.TMR1IE = 1 ;               // enable overflow interrupt

                // send pulse
                PWM1_Start() ;                  // pulse at ultrasonic frequency
                Delay_us(PULSELEN) ;            // during PULSELEN microseconds
                PWM1_Stop() ;

                Delay_us(PULSELEN * 2) ;        // wait to prevent false start

                while(Adc_Read(1) < 1)          // while no echo detected
                        {
                        if(outOfRange) break ;  // too late, out of range
                        }

                T1CON.TMR1ON = 0 ;              // stop timer 1
                PIE1.TMR1IE = 0 ;

#ifdef LCDPORT
                if(outOfRange)
                        {
                        Lcd_Out(2, 8, "OverRange") ;
                        }
                else if(TMR1H < ((PULSELEN * 6 * Clock_kHz()) / (1000 * 4 * 256)))
                        {
                        Lcd_Out(2, 8, "UnderRnge") ;
                        }
                else
                        {
                        buf[idx] = TMR1H ;              // build 16-bit value from timer1
                        buf[idx] <<= 8 ;
                        buf[idx] += TMR1L ;

                        idx++ ;
                        if(idx == BUFSIZE)
                                {
                                idx = 0 ;
                                }

                        cm = 0 ;
                        for(i = 0 ; i < BUFSIZE ; i++)
                                {
                                cm += buf[i] ;
                                }
                        cm /= BUFSIZE ;                 // average samples

                        /*
                         * cm contains now the number of clock cycles
                         * from start of ultrasonic transmission to first echo
                         * duration in seconds: s = cm / (Clock_Khz() * 1000 / 4)
                         * sound speed in air: 340 m/s
                         * distance (forth and back): d = s * 340 * 100 / 2
                         * d = 34 * 2 * cm / Clock_Khz()
                         */
                        cm *= 34 * 2 ;
                        cm /= Clock_Khz() ;

                        ByteToStr(cm, str) ;
                        Lcd_Out(2, 1, str) ;
                        Lcd_Out(2, 8, "         ") ;
                        }
#endif

                Delay_ms(10) ;                  // 10 ms before next sample
                }
        }

Note

No separate project ZIP file was provided for this project. The source code above is the complete mikroC program — copy and paste it into your mikroC IDE to build the project.