infrared Liquid level detector

Thread Starter

fantabulous68

Joined Nov 3, 2007
51
lol
im not a HE im a SHE, this is new to me, i will do a test on some liquids with the GP2 and multimeter and check if it corresponds to the graph from the datasheet. Thanks for your OP, you are right. I will do some tests. Im here to learn.
 
Last edited:

rjenkins

Joined Nov 6, 2005
1,013
jpanhalt, the Sharp GP2 internally uses geometric (triangulation) measurement of distance, then converts it's measurement to an analog signal.

Looking at the light path examples in the data, it *should* work with reflection from the surface of a liquid, but the only way to know for sure is testing it in practice...
 

jpanhalt

Joined Jan 18, 2008
11,087
Looking at the light path examples in the data, it *should* work with reflection from the surface of a liquid, but the only way to know for sure is testing it in practice...
The question is not whether the surface reflects. It will. Any interface will have reflection. My concern is that the intensity of reflection from the surface may be insignificant compared to reflection from other objects, such as the container, when the fluid is transparent or effectively transparent to the wavelength being used. In that case, the device might read the other surface and not detect the liquid level at all.

Regardless of all of that, it seems we all agree on the need to do some preliminary testing before finalizing the design.

Out of curiosity, I would be interested in results from water, water with a dark blue or black dye in it (i.e., IR opaque) and purified mineral/paraffin oil (like you can take orally, e.g., Nujol). The latter should be transparent in the IR (can't find a spectrum at 940 nm, though) and less refractile and toxic than other IR transparent liquids.

John
 

THE_RB

Joined Feb 11, 2008
5,438
I just found this thread. The C code the OP is using looks familiar (hehe I wrote it) see
http://www.romanblack.com/junebug2.htm (see project4).

Well I've never used a Sharp GP2D12 with water, but I did do some tests with clear acrylic 3mm thick (not that the thickness would matter).

The Sharp sensors are fairly tolerant of a clear window when it is very close to the sensor. This makes sense considering their intended use, ie for restroom hand air dryers etc the sensor is behind a little clear window. This might also be the reason for the Sharp sensors having that little dead zone close to the sensor, the focus of the optic receiving lens is poor at that distance so they tolerate clear windows that are very close.

BUT if you put a clear window in most parts of the sensing range the unit detects the window itself, not shine through it. This was a bit random, ie with a very clean bit of acrylic with no scratches I could get a slightly erratic reading from something behind the window, maybe even a good reading if the window was superclean and tilted a little. But with the slightest bit of dust or some fine scratches the window itself was detected as the object.

If the sensor was pointed direct at the water I would think it will detect the water surface. Even with clear water. You could optimise this a little by reducing the reflecivity of the tank bottom, ie paint it black or put a black object in. The beam from the GP2D12 is quite narrow, only about an inch across at the end of it's range (80cm). So you would only need a small black object placed in the tank there.

With opaque liquid there would not be a problem. Likewise you could float something on top of the liquid (bubblewrap?) but then you always run the risk of fouling or wrinkling etc as the level cycles.

As for the water with transparent IR dye in it, I don't think that would matter. The top of the surface is a high gloss and the Sharp sensor will detect a high gloss surface very easily (yes I tested gloss black too) if it is fairly perpendicular to the sensor.

I can't see waves/ripples being a problem the Sharp sensor will detect round objects (pencils etc) easily. The readings will vary a little with round object lateral movement due to the geometric nature of the sensor. The datsasheet covers this a bit. But like any analog sensor you just average a number of readings, averaging will be needed anyway to remove real level changes like sloshing.
 

Thread Starter

fantabulous68

Joined Nov 3, 2007
51
Wow lol this is SO exciting. I am honored to meet you Mr RB. I plan to use part of your code for the GP2 on the pic 16f690. Going to use it for my final design at university...Don't worry you will definitely be acknowledged in my report. :).
 

Thread Starter

fantabulous68

Joined Nov 3, 2007
51
I was advised to use a digital filter to reduce the fluctuating readings i get when measuring liquid levels. The filter is coded using structures

I was advised by Noggin:
// Whenever you read your adc, just call Update_Filter( &adc_filter, adc_value ).
//Wherever you perform a calculation on the ADC value, use adc_filter.output instead.
I added the code in RED Highlight below...I just want to know if im using it in the correct place...

Rich (BB code):
#include <pic.h>
#include "pic.h"
#include "delay.h"
#include "math.h" 
#include <stdio.h>
#include <stdlib.h>	//

#define FALSE 0
#define TRUE  1
struct filter_type {
   unsigned int accumulator;
   unsigned int output;
   unsigned char shift_value;
   unsigned char initialized;
} ;

struct filter_type adc_filter;

void userMenu(char pos); //  

void init(void);
char WaitForInput(char expire);  

unsigned char user_input(void);
void home_screen(void);
void EnterHeight(void);
void EnterScreen(void);
void ShowDigits(unsigned char val);
void calc_distance(void);
void main(void);




unsigned char cm2LCD;
unsigned char posneg;
unsigned char LLHigh, LLLow;
unsigned int LiquidLevel;
    
#define LCD_RS RC0		//LCD RS pin
#define LCD_EN RC1		//LCD EN pin
#define LCD_STROBE()	LCD_EN = 1; asm("nop"); asm("nop"); LCD_EN = 0

unsigned char cm10;		//
unsigned char cm;		//
unsigned int math;		// used for voltage calculations


unsigned char NumDec;
unsigned char NumSep[2];
   
unsigned char i,j,k;

unsigned char height=50;
unsigned char range=10;
unsigned char area;


char input_sw;

  
char mnuPOS;

unsigned char MyVal;
unsigned char MyValLCD[2];
unsigned char MyMaxVal;
unsigned char MyMinVal;
unsigned long bres;		// for bresenham 2-second timer system

unsigned char ;

#define HOME_SW RC2				//HOME switch	
#define INCREASE_SW RC3			//INCREASE switch
#define DECREASE_SW RC4			//DECREASE switch
#define ENTERSETTINGS_SW RA4	//ENTERSETTINGS switch


   

void init(void)
{   
	
	// setup the PIC 16f690
	OSCCON = 0x72;      	// internal osc, 8MHz


	PORTA = 0;
	TRISA = 0b10010010; 	// RA7 high imp, RA3 is serial out, RA4 button input 


	
 	PORTB = 0;          	// PORTB not used
    WPUB = 1;				// PORTB pullups ON		
    RABPU = 0;


    /* Init ADC */
    ADCON0 = 0b10000101;	// bit 7 right justify,analogue channel select bits bits5-2  0001=AN1,ADC ON, RA1 is ADC input
	ADCON1 = 0b00100000;	//bits6-4  fosc/32
    ADON=1;	                // turn on the A2D conversion module

	
	ANSEL=0x02;            //set RA1 as analog input for GP2 sensor
	ANSELH=0x00;

    T1CON = 0b00010001;     // TMR1 is ON, 1:2 prescale, =1MHz
	T2CON = 0b00000101;     // TMR2 is ON, 1:4 prescale, =1MHz



	MyVal = 0; //initializn these variables here
	MyMinVal = 0;
	MyMaxVal = 99;
  
	TRISB=0x00; 
	TRISC=0xFC;
    
	lcd_init(); //call LCD initialisation

}

void Init_Filter( struct filter_type *filter, unsigned char shift )
{
   filter->initialized = FALSE;
   filter->shift_value = shift;
}

void Update_Filter( struct filter_type *filter, unsigned int new_value )
{
   if (filter->initialized != TRUE)
   {
      filter->output = new_value;
      filter->accumulator = new_value << filter->shift_value;
      filter->initialized = TRUE;
   }
   else
   {
      filter->accumulator -= filter->output;
      filter->accumulator += new_value;
      filter->output = filter->accumulator >> filter->shift_value;
   }
}







void userMenu(char pos){
    unsigned int delaytime = 100000; // 100ms CHANGE THIS FOR YOUR BELOW DELAY 
	lcd_clear();
	lcd_goto_L1();

	switch(pos){
		case 0:
			lcd_puts("    HEIGHT    ");
			break;		
		case 1:
			lcd_puts("    RANGE     ");
			break;		
		case 2:
			lcd_puts(" SURFACE AREA ");
			break;							//
		case 3: 
			lcd_puts("   MEASURED   ");
			 while(WaitForInput(1) != 2){		//Wait for user to press enter to leave loop

 				// wait for 2 seconds, uses TMR1 free running at 1Mhz
   				while(!TMR1IF)  		// wait for TMR1 overflow
				TMR1IF = 0; 			// clear overflow flag

				bres += 65536;			// add 65536uS to bres value
				if(bres >= delaytime )		// if reached 2 seconds!
	 			{
    				bres -= delaytime ;	// subtract 2 seconds, keep error 
		  	
					// read the ADC voltage RA1 (Sharp GP2 sensor)
					//	Whenever you read your adc, just call Update_Filter( &adc_filter, adc_value ).
					GODONE=1;					// initiate conversion on the channel 0
					while(GODONE) continue;  	// Wait convertion done
					Update_Filter( &adc_filter, adc_value );
					calc_distance();			// convert ADC value to distance

					lcd_goto_L2();				//Only change line 2
//
					if(posneg == 'p')
						lcd_data('+');
					else
						lcd_data('-');
//
					lcd_data(LLHigh);
					lcd_data(LLLow);
	    			lcd_puts(" [cm] ");			//comment this out if you want
			   } 
		} 
		lcd_goto_L1();
		lcd_puts(" Loading Home ");
		lcd_goto_L2();
		lcd_puts("              ");
		DelayS(1);
		break;
	}

	if(pos == 3) return;
	lcd_goto_L2();
	lcd_puts("Press Up/Down"); //home screen message (line 2)
}




void ShowDigits(unsigned char val){

    MyValLCD[0] = val /10;    //returns the quotient (if temp = 35 the result is 3)
    MyValLCD[1] = val % 10; 	//Returns remainder   (if temp = 35 the result is 5)

	MyValLCD[0] += 0x30;	//to ASCII
	MyValLCD[1] += 0x30;	//to ASCII

	EnterScreen();
	lcd_goto_L2();
	lcd_data(MyValLCD[0]);	//to LCD
	lcd_data(MyValLCD[1]);  //to LCD
}

  
void calc_distance(void)
{
	unsigned int tmp;
unsigned int mathKeep;		// used for voltage calculations backup
	// from the transeiver datasheet the analog voltage is
	// the inverse of distance, so distance can be calculated
	// d = (1 / volts) then just scaled to suit the transeiver

	// load ADC value in 16bit math var

//Wherever you perform a calculation on the ADC value, use adc_filter.output instead.
	
//	math = ADRESH;
    math=adc_filter.output;
	math = (math * 256);
//	math += ADRESL;
	math +=adc_filter.output;


	// now invert it; (1 / volts) use (6050 / volts) for scaling
	math = (6050 / math);
	if(math >= 2) math -=2;		// fix linear error (-2)
	if(math > 99) math = 99;	// max limit at 99cm

	//Create a copy of math for more use
	mathKeep = math;
    //LiquidLevel=height-;	

	// convert from 0-99 to 2 decimal digits, 0-99cm
	cm10=0;
	while(math >= 10)
	{
		cm10++;
		math -= 10;
	}
	cm = math; 

	math = mathKeep;	//Now our original data is back and can be used.
	//This will check if negative
	LiquidLevel=height-math;	

//
	posneg = 'p';
        if((LiquidLevel < 0) || (LiquidLevel > 0xFF00)){
		LiquidLevel = -LiquidLevel ;
		posneg = 'n';
	}
//
	//if below zero which will be in the 0xFFFF range i just chose any 0xFFxx number :D
      //LiquidLevel is higher than 09 so spilt the variable LiquidLevel into 2 //
      LLHigh = ( LiquidLevel / 10 ) + '0'; // 
      LLLow = ( LiquidLevel % 10 ) + '0';  // 
      


}
 //
unsigned char user_input(void)		//This will return what we want
{
	char done = 0;
 
	MyVal = 0;			//Start on 0
	while(done == 0){
   	  input_sw = WaitForInput(0);
		
      switch(input_sw){
		case 1:
			done = 0xff; 			//This tells us the user finished entering
			lcd_goto_L1();
			lcd_puts("      OK       "); //home screen message (line 1)
			break;
      	case 3:
			if(MyVal < MyMaxVal)
      			MyVal++;
			EnterScreen();
			ShowDigits(MyVal);
			break;
      	case 4:
			if(MyVal > MyMinVal)
      			MyVal--;
			EnterScreen();
			ShowDigits(MyVal);
        	break;
		default: 
			break;
      }
	  
	}
	DelayMs(250);
	DelayMs(250);
	return MyVal;
}

void home_screen(void){
	mnuPOS = 0;
	lcd_clear();
	lcd_goto_L1();
	lcd_puts("INFRARED LIQUID"); //home screen message (line 1)
	lcd_goto_L2();
	lcd_puts("LEVEL DETECTOR"); //home screen message (line 2)
  
	input_sw = 0;			//Reset the value

	while(input_sw != 1)  	//Wait until enter is pressed
		input_sw = WaitForInput(0);

	userMenu(0);
	DelayMs(2);				//shorter delay 
    height = user_input();	//The HEIGHT var will have the myVal

	userMenu(1);
	DelayMs(2);				//shorter delay 
    range = user_input();	//The HEIGHT var will have the myVal

	userMenu(2);
	DelayMs(2);				//shorter delay 
    area = user_input();	//The HEIGHT var will have the myVal
//
	userMenu(3);
	DelayMs(2);				//shorter delay 
	input_sw = 0;			//Reset the value


}
  

void main(void)
{  
    init();	// initialise I/O ports, LCD
    while(1){
 	home_screen();


        
              
 
   } 
}


Im a bit unsure about:


Rich (BB code):
//Wherever you perform a calculation on the ADC value, use adc_filter.output instead.
	
//	math = ADRESH;
    math=adc_filter.output;
	math = (math * 256);
//	math += ADRESL;
	math +=adc_filter.output;

Can adc_filter.output be broken up into a high & low value then assigned to math?
OR do i assign adc_filter output directly to math as is??
 

BMorse

Joined Sep 26, 2009
2,675
I don't know if the GP2D12 sensor will detect the liquid itself if it is clear considering they use it for under water navigation in robotic fish.... http://www.youtube.com/watch?v=eO9oseiCTdk

I have used it quite a bit in different projects including my Pic32 based B.U.M. System for turning a faucet on or off depending on distance of the users hand to the faucet, but not for detecting clear liquids...
 

THE_RB

Joined Feb 11, 2008
5,438
I did some testing on the other forum which seemed a copy (or the original) of this thread posted by the same OP.

The Sharp sensor WILL detect the surface of water, but it is fussy and needs to be a critical angle (pointed exactly down). Changing angle even a couple of degrees makes it hard to see the water surface and it starts to read the bottom of the container instead. I only tested with water and some variables for about 1/2 an hour but my take on it was that you could probably get it reliable under controlled conditions, ie in a dark water tank that won't move and where you can also fix and adjust the sensor angle exactly.

With the addition of a simple surface float it gets very easy of course, but you always risk float jamming etc and that is more work. Someone suggested throwing a heap of ping pong balls on top of the water to act as a floating reflector, which would be another good solution.
 

jpanhalt

Joined Jan 18, 2008
11,087
<snip>
With the addition of a simple surface float it gets very easy of course, but you always risk float jamming etc and that is more work. Someone suggested throwing a heap of ping pong balls on top of the water to act as a floating reflector, which would be another good solution.
If the device requires adding a float, that may not be consistent with the problem statement:
The design should be useful in liquid level or proximity detection. It should operate by detecting the distance from the target by reflection of an infra-red beam. It should safely detect the level of a liquid in a tank without any contact with the liquid itself.
One could quibble that the tank came with the float, so the float is not part of the device, but I don't think that is in the spirit of the problem. Ping pong balls are used to slow evaporation of hot-water baths. In this application, given the stringent geometric requirements for that detector, they might cause multiple reflections and not solve the problem at all.

In following the other thread, I suspect the lecturer wanted the students to focus on the firmware solution as fantabulous68 has done, not solve the physics of the reflection problem.

John
 
Top