|
|
View previous topic :: View next topic |
Author |
Message |
artohautala
Joined: 17 Nov 2011 Posts: 187
|
calculating logarithms PIC18F452 [SOLVED] |
Posted: Wed Nov 04, 2015 6:49 am |
|
|
Hello,
I know maybe my question is beyond of this discussion forum...
but I'll be very happy if someone will answer my question and friendly advice me how to do ...
So I have ambi light sensor from modern device
https://moderndevice.com/product/ambi-light-sensor/
and it's output current depends logarithmic way when ambient light is changing ...
My output load is 100 k resistor
I made today some measurements and I think these results are OK:
light result from A/D converter PIC18F452 10 bits 0... 2013
100 lx 200
300 lx 254
1000 lx 303
3000 lx 345
9500 lx 394
30000 lx 442
... light sensor internal resistance is about 94 k and my load resistor is 100 k ...
.... I use 5 V power supply so max. voltage from light sensor is 2.58 V to 100 k load...
... so max. ad-result is (2.58/5) * 1024 = 528 (10 bits ad-conversion, ref. voltage = 5 V)
I made excel curve about those values and it seems to me that's linear OK...
So I want to display those light values in luxes maybe using log10() function ...
but I don't know how to make it work ....
Sorry I'm very poor in mathematics javascript:emoticon('')
friendly asking and best regards
-arto hautala-
Last edited by artohautala on Wed Nov 11, 2015 11:24 am; edited 1 time in total |
|
|
alan
Joined: 12 Nov 2012 Posts: 357 Location: South Africa
|
|
Posted: Wed Nov 04, 2015 7:09 am |
|
|
Depends on how sensitive your application is to timing.
If not just use the log10() function provided by CCS in the math.h file.
Regards |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19493
|
|
Posted: Wed Nov 04, 2015 9:01 am |
|
|
You don't want logarithm.
You want exponential.
Thing is the sensor gives a logarithmic output, from Lux. For you to generate 'Lux', from it's output, you need the inverse equation.
Lux=0.7648*e^(0.0239*adc)
The comment already made about speed, obviously applies.
Also, you may well find the results are significantly better if you buffer the voltage. The currents involved (especially at low values), are well below the values recommended to feed the PIC ADC...
Code: |
#include <math.h>
float lux;
int16 adc_val;
//then read the adc
//suggest probably average for a more stable result....
adc_val=read_adc();
Lux=0.7648*exp(adc_val*0.0239);
//Then print your lux value
|
So for a reading of 200, you get
200*0.0239 = 4.78
e^4.78 = 119
119*0.7648 = 91Lux
While for 442, you get 29603
Which is as close as you are going to get for a simple exponent, with the values given. |
|
|
artohautala
Joined: 17 Nov 2011 Posts: 187
|
|
Posted: Wed Nov 04, 2015 10:40 am |
|
|
Ttelmah wrote: | You don't want logarithm.
You want exponential.
Thing is the sensor gives a logarithmic output, from Lux. For you to generate 'Lux', from it's output, you need the inverse equation.
Lux=0.7648*e^(0.0239*adc)
The comment already made about speed, obviously applies.
Also, you may well find the results are significantly better if you buffer the voltage. The currents involved (especially at low values), are well below the values recommended to feed the PIC ADC...
Code: |
#include <math.h>
float lux;
int16 adc_val;
//then read the adc
//suggest probably average for a more stable result....
adc_val=read_adc();
Lux=0.7648*exp(adc_val*0.0239);
//Then print your lux value
|
So for a reading of 200, you get
200*0.0239 = 4.78
e^4.78 = 119
119*0.7648 = 91Lux
While for 442, you get 29603
Which is as close as you are going to get for a simple exponent, with the values given. |
Ok,
Thank you very much for answering me
I don't understand those numbers:
Lux=0.7648*exp(adc_val*0.0239);
Maybe because of natural logaritm base number 0.7648 ?
Where comes this number 0.0239 ?
I tried it but it goes over 65535 ...
I know max. is about 30000 for 442 adc result...
Here's my code following your good advice :
Code: |
loop:
//get_light() returns unsigned int16 adc value 0...1023, here max. 442 for 30000 lux
light = get_light();
Lux = exp(light*0.0239); //Lux is float
Lux = Lux * 0.7648;
show_calibration((unsigned int16)Lux);
delay_ms(1500);
clear_lcd();
delay_ms(100);
goto loop; |
Thank you very much helping me !
best regards
-arto hautala_ |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19493
|
|
Posted: Thu Nov 05, 2015 2:39 am |
|
|
The numbers are simply values I calculated to make the exp curve work, from the ADC values you quoted.
If you are getting over 65535, then you are getting a momentary ADC reading over 475. As I said, I would say you need to buffer the ADC, and average the results. At the high impedance this circuit is using, you _will_ be getting inaccurate/noisy results. |
|
|
artohautala
Joined: 17 Nov 2011 Posts: 187
|
|
Posted: Fri Nov 06, 2015 8:59 am |
|
|
Ttelmah wrote: | The numbers are simply values I calculated to make the exp curve work, from the ADC values you quoted.
If you are getting over 65535, then you are getting a momentary ADC reading over 475. As I said, I would say you need to buffer the ADC, and average the results. At the high impedance this circuit is using, you _will_ be getting inaccurate/noisy results. |
Thank you for your good advice ...
I changed load resistor to 68 k (67.7 k accurate)
because I know there's maximum light is below 65535 lux ...
I made some better measurements today and internal resistance is 92 k
here'my results:
current to gnd uA Lux Uout in sensor Uout to A/D A/D result
5 3 0,46 0,19 40
10 10 0,92 0,39 80
15 32 1,38 0,58 120
20 100 1,84 0,78 160
25 316 2,30 0,97 199
30 1000 2,76 1,17 239
35 3162 3,22 1,36 279
40 10000 3,68 1,56 319
45 31623 4,14 1,75 359
50 100000 4,60 1,95 399
so with 35 current should be 3126 in theory now it is 930 ....
//Lux = exp(light*0.0239); //Lux is float
//Lux = Lux * 0.7648;
so how I have to change those numbers 0.0239 and 0.7648 ??
Sorry I'm really bad in mathematics and I think I'll never learn it !
all the best and nice and happy weekend for you !
-arto- |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19493
|
|
Posted: Fri Nov 06, 2015 2:59 pm |
|
|
You are still omitting the part about buffering the ADC. The ADC draw _current_. Typically quite a few uA, and requires a reasonably low impedance to recharge the internal capacitor when it connects to the voltage. This is why the data sheet has the line:
"The maximum recommended impedance for analog
sources is 2.5 kΩ."
You are not going to get anything approaching accuracy till you re-design your circuit. I'm not going to waste my time re-calculating the curve, till you produce real values actually read by the ADC. |
|
|
artohautala
Joined: 17 Nov 2011 Posts: 187
|
|
Posted: Sun Nov 08, 2015 6:34 am |
|
|
Ttelmah wrote: | You are still omitting the part about buffering the ADC. The ADC draw _current_. Typically quite a few uA, and requires a reasonably low impedance to recharge the internal capacitor when it connects to the voltage. This is why the data sheet has the line:
"The maximum recommended impedance for analog
sources is 2.5 kΩ."
You are not going to get anything approaching accuracy till you re-design your circuit. I'm not going to waste my time re-calculating the curve, till you produce real values actually read by the ADC. |
HI,
thank's a lot for your patience advicing me ...
today I'm spending rainy sunday afternoon and I add buffer amplifier to buffer PIC A/D pin A0...
my buffer is microchip MCP6272 rail to rail opamp (only one opamp is in use because I have no one opamp version MCP6273)...
and I average A/D results measuring 10 times and then I devide 10
Here's my average code:
Code: |
//*********************************************************************
unsigned int16 get_light(void){
unsigned int8 counter;
unsigned int16 an_value;
unsigned int32 sum_an_value;
set_tris_a(0xFF);
setup_adc_ports(RA0_ANALOG); // pin A0 measures voltage level of light sensor
setup_adc( ADC_CLOCK_INTERNAL );
//measure analog value 10 times and then average it:
sum_an_value = 0;
for(counter = 0; counter < 10; ++ counter){
set_adc_channel(0);
delay_ms(20);
an_value = read_adc(); //A/D conversion
sum_an_value = sum_an_value + an_value;
}
sum_an_value = sum_an_value / 10 ;
an_value = sum_an_value;
return an_value;
}
//********************************************************************* |
I made measurements and I found it don't matter if it is buffered or not ...
same thing about averacing results ...
now whit very bright led bulp I get number 988 A/D result ,,, I think it's reasonable result... so ambi light sensor send 4.8 V ...
I measured ambilight sensor output impedance by halving output voltage and I get value
92 kohms ... (I did'nt find any value from datasheet ??)
so in theory there must be like that: (+5 V reference for A/D)
2 lux = 5 uA to ground = 0,46 V = 94 (A/D result)
10 lux = 10 uA to ground = 0,92 V = 188
100 lux = 20 uA to ground = 1,84 V = 376
1000 lux = 30 uA to ground = 2,76 V = 565
10000 lux = 40 uA to ground = 3,68 V = 753
30000 lux = 45 uA to ground = 4,14 V = 847
50000 lux = 48 uA to ground = 4,42 V = 904
I think 50000 lux is good maximum value in practice ...
But my problem is how to change those A/D results to lux values because I'm really bad in math and maybe I
never learn mathematics because I am so dummy... (sorry I don't know right word)
I'll be very gratefull if you help me to solve this problem ...
all the best for you and have fun !
-arto- |
|
|
temtronic
Joined: 01 Jul 2010 Posts: 9220 Location: Greensville,Ontario
|
|
Posted: Sun Nov 08, 2015 8:05 am |
|
|
don't know about the math but...
1) I'd change the averaging function from 10 counts to 16. You may not notice the result being different but the code is smaller ! Compiler will simply shift to divide by 16, very fast operation.
2) you(or a friend) should be able to put your results into eXcel or Matlab and 'reverse engineer' to create the math required for the numbers you supply.
Jay |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19493
|
|
Posted: Sun Nov 08, 2015 8:14 am |
|
|
OK.
Notice how much larger the actual swing is on the ADC. It's now using 80% of the ADC input range. A lot more hopeful.
However a 'caveat'. Plotting the curve, as an exponential, the top value is off the curve (high), and the bottom one is also a little off. I think you are running into a range limit somewhere.
Try 1.0243*e^0.0122*adc
This does not fit terribly well at either end, but gives the best possible fit through all the other readings.
So adc=753, gives 9999. adc=376, gives 100, but your '904' reading actually converts as 60000, while the bottom reading comes in at 3.2.
If you look at the actual numbers, if it was genuinely logarithmic, given that 10Lux is 10uA, 2 Lux should be just over 3uA, and 50000 Lux should be 46.9uA. the 10, 100, 1000, 10000 & 30000 values all fit perfectly, it is just the end two that go a little wrong.... |
|
|
artohautala
Joined: 17 Nov 2011 Posts: 187
|
|
Posted: Sun Nov 08, 2015 8:21 am |
|
|
temtronic wrote: | don't know about the math but...
1) I'd change the averaging function from 10 counts to 16. You may not notice the result being different but the code is smaller ! Compiler will simply shift to divide by 16, very fast operation.
2) you(or a friend) should be able to put your results into eXcel or Matlab and 'reverse engineer' to create the math required for the numbers you supply.
Jay |
HI Jay,
thank's for your good advice
<1) I'd change the averaging function from 10 counts to 16. You may not notice the result being different but the code is smaller ! Compiler will simply shift to divide by 16, very fast operation.>
In my application speed is not so important ...
<2) you(or a friend) should be able to put your results into eXcel or Matlab and 'reverse engineer' to create the math required for the numbers you supply.>
Yes sure I have no matlab in my computer but excel sure I have ...
it's very fun to try all kind of everything using excel and that's what I'm doing right now ...
I am happy to be your friend I hope you are also my friend !
all the best in the world for you .... and thank's answering me !
(sorry my bad english)
best regards
-arto- |
|
|
artohautala
Joined: 17 Nov 2011 Posts: 187
|
|
Posted: Sun Nov 08, 2015 8:47 am |
|
|
Ttelmah wrote: | OK.
Notice how much larger the actual swing is on the ADC. It's now using 80% of the ADC input range. A lot more hopeful.
However a 'caveat'. Plotting the curve, as an exponential, the top value is off the curve (high), and the bottom one is also a little off. I think you are running into a range limit somewhere.
Try 1.0243*e^0.0122*adc
This does not fit terribly well at either end, but gives the best possible fit through all the other readings.
So adc=753, gives 9999. adc=376, gives 100, but your '904' reading actually converts as 60000, while the bottom reading comes in at 3.2.
If you look at the actual numbers, if it was genuinely logarithmic, given that 10Lux is 10uA, 2 Lux should be just over 3uA, and 50000 Lux should be 46.9uA. the 10, 100, 1000, 10000 & 30000 values all fit perfectly, it is just the end two that go a little wrong.... |
Sorry Thelmah,
there was typing error ,,, 5 uA current to ground should be 3 lux not 2 lux...
I understand that it must not be so accurate ... so I will next follow your good advïce using
1.0243*e^0.0122*adc
so my ambient device behaves log10 but compiler do not understand antilog 10 ??
but it use exp
so I have to change log10 to ln (base number 2.718)
relationship between ln / log10 = 2.303
I'll try this math in excel...
thank's for your patience and all the best
-arto- |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19493
|
|
Posted: Sun Nov 08, 2015 9:21 am |
|
|
Most mathematicians will only ever use e^x, and ln.
Base ten is only a matter of applying a scaling.
It's like sin, where mathematicians tend always to use radians rather than degrees. Very few things (in fact I can't think of any), involve base 10. Base ten is a human creation.
Because your source numbers are 'adc counts', not 'volts', there is going to have to be a scaling applied, so it makes no basic difference if you use e^x, or 10^x.
The compiler has log10, and log functions. Anti log, is just 10^x, which the compiler can do with pow. However key is that pow, is done internally using exp, and log10, is done by using log. Since you are going to have to scale anyway, it is more efficient to use the 'root' functions, rather than their 'descendants'.
Just to take one as an example:
Code: |
float32 log10(float32 x)
{
float32 r;
r = log(x);
r = r*LN10_INV;
return(r);
}
|
So 'log10', is just solved by taking log (base e), and multiplying the result by LN10_INV (0.43429228....).
The compiler actually has a define for the log10 conversion (LN10), and it's reciprocal (used above).
If there was a typing error on the 3Lux value, then the curve fits beautifully except for the very top reading, |
|
|
artohautala
Joined: 17 Nov 2011 Posts: 187
|
|
Posted: Sun Nov 08, 2015 10:15 am |
|
|
Ttelmah wrote: | Most mathematicians will only ever use e^x, and ln.
Base ten is only a matter of applying a scaling.
It's like sin, where mathematicians tend always to use radians rather than degrees. Very few things (in fact I can't think of any), involve base 10. Base ten is a human creation.
Because your source numbers are 'adc counts', not 'volts', there is going to have to be a scaling applied, so it makes no basic difference if you use e^x, or 10^x.
The compiler has log10, and log functions. Anti log, is just 10^x, which the compiler can do with pow. However key is that pow, is done internally using exp, and log10, is done by using log. Since you are going to have to scale anyway, it is more efficient to use the 'root' functions, rather than their 'descendants'.
Just to take one as an example:
Code: |
float32 log10(float32 x)
{
float32 r;
r = log(x);
r = r*LN10_INV;
return(r);
}
|
So 'log10', is just solved by taking log (base e), and multiplying the result by LN10_INV (0.43429228....).
The compiler actually has a define for the log10 conversion (LN10), and it's reciprocal (used above).
If there was a typing error on the 3Lux value, then the curve fits beautifully except for the very top reading, |
So I think this is enough accurate for me ,,, thank's for your help again ... maybe I'll try
to study more math...
now it works fine in practice ,,, thank's for your good advices...
Code: |
loop:
F_light = (unsigned int16)get_light();
//1.0243*e^0.0122*adc
F_light = 1.0243 *exp(0.0122* F_light);
if(F_light > 65535)
F_light = 65535;
show_calibration((unsigned int16)F_light);
delay_ms(1500);
clear_lcd();
delay_ms(100);
goto loop;
|
sure I know it is bad to use loop: goto loop but this is only test purpose ....
maybe I close this discussion but I'm very happy you helped me
all the best
-arto- |
|
|
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
Powered by phpBB © 2001, 2005 phpBB Group
|