PDA

View Full Version : Please help with multiplying irrational numbers. volt meter project!



Tony_tsi
04-09-2012, 07:50 PM
I am using an eight bit a/d converter to check the voltage on a circuit. I am very close my calculations are just a little off and I do not know why. I am pretty sure that the problem is in line 56 of my code.
The a/d converter has 255 steps between ground and 5v, so to obtain the proper voltage I must divide 5 by 255 this gives me the amount of voltage that each step is worth. Now if I take the number coming from the a/d converter and multiply it by (5/255) then it should in theory give me the correct voltage reading. But I just can't get it to work this way.
If anyone has any suggestions for how I can fix this problem please help me out.

pedward
04-09-2012, 07:59 PM
Have you debug printed all of the values you are getting at each step? The ADC reading, the volt_step, and volt_Calc?

Tony_tsi
04-09-2012, 08:05 PM
yes for volt_step i get 0.01960 (correct as as rounded irrational number)
the ADC reading has a range of 0 to 255 (correct)
The volt_ calc should be the voltage reading it is off by + 2

Beau Schwabe
04-09-2012, 08:08 PM
In an 8-bit adc there are actually 256 steps... zero counts, so your number should be 0.01953125 volts per step.

How far are you off? do you have a voltage divider on the front of the ADC or anything else that might affect the result?

pedward
04-09-2012, 08:09 PM
2 counts or 2 volts? Can you provide actual sample values of these variables? I have a couple of ideas off the top of my head: signed value conversion, floating point rounding error.

EDIT: Beau also touched on 256 and 255. Although 255 is the max count, and .0195 * 255 != 5 volts

MagIO2
04-09-2012, 08:11 PM
Without knowing to much about the float objects, I'd expect it to work with float-numbers. But you call the functions with 5, 255 ... which will be converted to integers by the compiler.
So, even if the number has no fractionals you should call it with 5.0, 255.0 ....

I'm also not sure wheather your binaryToDecimal is correct. I'd expect that the float objects provide some functions to convert an integer to a float.

Duane Degn
04-09-2012, 08:13 PM
When you use a floating point object, you want to make sure all the values you use with it are floating point.

I don't see where the value "ADC_value" is every converted to a floating point number.

What program is "ADC_Driver"?

I don't see why you need the method "BinaryToDecimal"?

Binary and decimal numbers are just to make it easier for us humans to read them. Normally they are stored the same in the uC's memory.

I bet you don't need a floating point object at all. I think all your math could be done as pseudoreal (you might want to try "pseudo-real" and "pseudo real" with your search).

Tony_tsi
04-09-2012, 08:15 PM
I changed my code slightly so that i can view all three values at the same time. when ADC_value is 255 voltage_calc is 7.00649

Tony_tsi
04-09-2012, 08:17 PM
In an 8-bit adc there are actually 256 steps... zero counts, so your number should be 0.01953125 volts per step.

How far are you off? do you have a voltage divider on the front of the ADC or anything else that might affect the result?

this is much closer but still off now at ADC_value 255 the Volt_calc is 5.6

MagIO2
04-09-2012, 08:18 PM
Oh .. and by the way ... I don't know why you want to use floats at all?!

Most ADC have a inaccuracy - for example 1 or 2 LSBs. This means that with 5V and 255 steps you have an inaccuracy of
0,01960784313725490196078431372549 which means that it's worthless to show more than 3 digits.

By simply using 500 as an equivalent of 5V you can use simple integer math to calculate your voltages.

Tony_tsi
04-09-2012, 08:23 PM
Oh .. and by the way ... I don't know why you want to use floats at all?!

Most ADC have a inaccuracy - for example 1 or 2 LSBs. This means that with 5V and 255 steps you have an inaccuracy of
0,01960784313725490196078431372549 which means that it's worthless to show more than 3 digits.

By simply using 500 as an equivalent of 5V you can use simple integer math to calculate your voltages.

I am new to programming most of the stuff you see in this code is copy, paste, modify. I only want to see 2 decimal places i just don't know how to do that yet.

Tony_tsi
04-09-2012, 08:25 PM
When you use a floating point object, you want to make sure all the values you use with it are floating point.

I don't see where the value "ADC_value" is every converted to a floating point number.

What program is "ADC_Driver"?

I don't see why you need the method "BinaryToDecimal"?

Binary and decimal numbers are just to make it easier for us humans to read them. Normally they are stored the same in the uC's memory.

I bet you don't need a floating point object at all. I think all your math could be done as pseudoreal (you might want to try "pseudo-real" and "pseudo real" with your search).

What is a floating point decimal? sorry I don't mean to ask dumb questions I am new to programming.

Duane Degn
04-09-2012, 08:28 PM
The thread linked to below talks about pseudo real numbers a bit.

Post #4 (http://forums.parallax.com/showthread.php?137578-Math-Question&p=1070196&viewfull=1#post1070196) is a method I wrote to output an integer with a decimal point to make it look like a floating point number had been used.

Phil Pilgrim (PhiPi)
04-09-2012, 08:33 PM
In an 8-bit adc there are actually 256 steps... zero counts, so your number should be 0.01953125 volts per step.
Not quite. There are 256 values, but only 255 steps between 0 and 255. So 5.0 / 255.0 == .019607843 is correct. That's assuming, of course, that 255 actually corresponds to 5V and not the (non-existent) 256.

-Phil

Tony_tsi
04-09-2012, 08:38 PM
WHY IS 5/255 = 0 I do not understand this! This is why I am tiring to use floating point numbers but I don't understand them either.

Phil Pilgrim (PhiPi)
04-09-2012, 08:46 PM
5/255 is integer division. It gets truncated to zero. You need to use floating point division to get a floating point constant: i.e. 5.0 / 255.0. Or, if done at run time, it would be f.fdiv(5.0, 255.0).

-Phil

Duane Degn
04-09-2012, 09:04 PM
The Prop chops off everything after the decimal point. So while 5/255 equals 0.0196 on a calculator, with the Prop, it equals 0.

If you use 50,000 / 255 you'll get 196 with the Prop. This should be a usable number.

You know 196 is really 0.0196 and you keep this in mind when you write the rest of your program.

If 196 was stored in the variable "value", you could display it with a decimal point using the method (linked to earlier) "DecPoint" by calling it like this:


DecPoint(value, 10000)

You'd use the number "10000" since that what you multiplied "5" by to get "50000".

There's more discussion about pseudo real numbers in that thread.

Floating point numbers are stored differently in the Propeller's memory than the way normal integers are stored. You can't mix the two with math functions. You also can't use normal Spin math and comparisons with floating point numbers (if you want the answers to be meaningful).

Edit: codeviper's calculator (http://forums.parallax.com/showthread.php?139272-working-floating-point-calculator.-with-source)is a good example of using floating point numbers. He made the common mistake of mixing integer math operators with floating point operators in his expoent function "^". The rest of the functions work correctly.

Tony_tsi
04-09-2012, 09:14 PM
The ADC_value and volt_step values are both correct!
The problem has to be in line 57 or 61!
Keep in mind volt_calc:= ACD_value*volt_step
table of output:

ADC_value volt_step volt calc
0 0.01960784 0
20 0.01960784 0
40 0.01960784 0
.................................................. .........
50 0.01960784 0
51 0.01960784 1.40
.................................................. ............
101 0.01960784 1.40
102 0.01960784 2.8
.................................................. ..........
152 0.01960784 2.8
143 0.01960784 4.2
.................................................. .........
203 0.01960784 4.2
204 0.01960784 5.6
...........................................
255 0.01960784 7.0

Tony_tsi
04-09-2012, 09:18 PM
The ADC_value and volt_step values are both correct!
The problem has to be in line 57 or 61!
Keep in mind volt_calc:= ACD_value*volt_step
table of output:

ADC_value volt_step volt calc
0 0.01960784 0
20 0.01960784 0
40 0.01960784 0
.................................................. .........
50 0.01960784 0
51 0.01960784 1.40
.................................................. ............
101 0.01960784 1.40
102 0.01960784 2.8
.................................................. ..........
152 0.01960784 2.8
143 0.01960784 4.2
.................................................. .........
203 0.01960784 4.2
204 0.01960784 5.6
....................................
255 0.1960784 7.0

I have tried 3 times to space the numbers out and it keeps pushing them to the left in the table sorry!

Tony_tsi
04-09-2012, 09:35 PM
line 57 volt_Calc :=(fmath.Fmul(ADC_value,volt_step))
This is my problem this only puts out an integer I need a number with 2 decimal places.
how do i fix this?

Duane Degn
04-09-2012, 09:43 PM
I think a lot of your difficulty is understanding the way numbers are stored in memory.

Floating point numbers are stored differently the integers.

While all numbers are stored as one and zeros, floating point numbers use the 32 bits differently (I don't really worry about it myself, I just know I can't mix integers and floating point numbers without converting one to the other first).


Different Topic:

Binary, hexadecimal and decimal numbers are just ways of displaying numbers for us humans. The Propeller stores them the same way (excluding floating point numbers).

The method "BinaryToDecimal" copies a number one bit at a time and returns the copied number. Since it only copies eight bits, the only numbers it will change are numbers larger than 255.

You could do the same thing with.


value &= 255

The above code will zero out all but the lowest 8 bits.

Just to be sure I wrote a quick demo of "BinaryToDecimal".


CON
_CLKMODE = XTAL1 + PLL16X
_CLKFREQ = 80_000_000
_DebugBaud = 57_600

OBJ
Debug : "Parallax Serial Terminal"

PUB Main | localIndex
waitcnt(clkfreq * 3 + cnt) ' time to open terminal window.

Debug.Start(_DebugBaud)
Debug.Clear
repeat localIndex from 0 to 255
Debug.Str(string(13, "localIndex before conversion = "))
Debug.dec(localIndex)
result := BinaryToDecimal(localIndex)
Debug.Str(string(13, "localIndex after BinaryToDecimal conversion = "))
Debug.dec(result)
repeat

PUB BinaryToDecimal(aBinVal) : rVal | multiplier, bitMask
rVal := 0 'empty returned val
multiplier := 1 'start with multiplier of 1
bitMask := 1 'start with bitMask of 1 (LSB)
repeat 8 'repeat for all 8 bits
if aBinVal & bitMask > 0 'if bit at position of bitMask is 1
rVal += multiplier 'add multiplier to aVal
multiplier *= 2 'double value of multiplier
bitMask := bitMask << 1 'move bit in bitMask to left
return rVal



Here's a portion of the output.


localIndex before conversion = 0
localIndex after BinaryToDecimal conversion = 0
localIndex before conversion = 1
localIndex after BinaryToDecimal conversion = 1
localIndex before conversion = 2
localIndex after BinaryToDecimal conversion = 2
localIndex before conversion = 3
localIndex after BinaryToDecimal conversion = 3
localIndex before conversion = 4
localIndex after BinaryToDecimal conversion = 4
localIndex before conversion = 5
localIndex after BinaryToDecimal conversion = 5
localIndex before conversion = 6
localIndex after BinaryToDecimal conversion = 6
localIndex before conversion = 7
localIndex after BinaryToDecimal conversion = 7
localIndex before conversion = 8
localIndex after BinaryToDecimal conversion = 8

So you don't need the method. I don't think you need to "and" the value either.

The main problem you're having is not keeping floating point numbers and integer numbers separate. One has to be converted to the other before you can do math with them.

Tony_tsi
04-09-2012, 09:52 PM
The Prop chops off everything after the decimal point. So while 5/255 equals 0.0196 on a calculator, with the Prop, it equals 0.

If you use 50,000 / 255 you'll get 196 with the Prop. This should be a usable number.

You know 196 is really 0.0196 and you keep this in mind when you write the rest of your program.

If 196 was stored in the variable "value", you could display it with a decimal point using the method (linked to earlier) "DecPoint" by calling it like this:


DecPoint(value, 10000)

You'd use the number "10000" since that what you multiplied "5" by to get "50000".

There's more discussion about pseudo real numbers in that thread.

Floating point numbers are stored differently in the Propeller's memory than the way normal integers are stored. You can't mix the two with math functions. You also can't use normal Spin math and comparisons with floating point numbers (if you want the answers to be meaningful).

Edit: codeviper's calculator (http://forums.parallax.com/showthread.php?139272-working-floating-point-calculator.-with-source)is a good example of using floating point numbers. He made the common mistake of mixing integer math operators with floating point operators in his expoent function "^". The rest of the functions work correctly.

Thanks this takes out a lot of stuff that i do not understand how to use and it works great! I have not figured out how to get a decimal point yet but i am still working on it.

Tony_tsi
04-09-2012, 09:59 PM
value &= 255

This is awesome!!

Duane Degn
04-09-2012, 10:05 PM
value &= 255

This is awesome!!

I don't think you need it though.

I don't have an LCD setup nor do I have the LCD object you're using (in the right place for the Prop Tool to find it) so I couldn't even compile the code below.

I think the "Char" method is a private method in the LCD object you're using. You need to make it public by changing PRI to PUB.

Here's the code:


CON
_clkmode = xtal1 + pll16x ' use crystal x 16
_xinfreq = 5_000_000 ' 5 MHz cyrstal (sys clock = 80 MHz)
adcCS_Pin = 13 'pins used for ADC chip
adcCLK_Pin = 15
adcDO_Pin = 14
INIT_OK = 0
OBJ
lcd : "LCD_16X2_8BIT"
ADC : "ADC_Driver"
VAR
byte ADC_value '>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>adc value in dec can range from 1 to 255
long volt_calc '>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>adc value multiplied by volt_step (volt step should = 5/255)
long volt_step '>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>should be equal to 5/255 this number repersents the voltage per 1 step of adc

PUB ini

lcd.start 'start junk
lcd.move(1,1)
lcd.str(string("kWH PROJECT"))
lcd.move(1,2)
lcd.str(string("WKU"))
waitcnt(clkfreq*2 + cnt)
volt_step := 50000 / 255

main
pub main | rcLCD, rcADC

lcd.clear
lcd.move(1,1)
lcd.str(string("volts"))
rcADC := ADC.Init(adcCS_Pin, adcCLK_Pin, adcDO_Pin)

if rcADC <> INIT_OK
LCD.Str(string("ADC chip init err"))
return
repeat
lcd.str(string(" "))
LCD.move(10, 1)
ADC_value := ADC.GetConvertedVal 'get value from ADC (binary)
volt_Calc := ADC_value * volt_step
DecPoint(volt_Calc, 100000)
waitcnt(clkfreq/2 + cnt) 'wait for 500ms
PUB DecPoint(value, denominator)
if value < 0
LCD.CHAR("-")
-value

if value => denominator
result := value / denominator
LCD.dec(result)
value //= denominator
else
LCD.CHAR("0")
LCD.CHAR(".")
repeat while denominator > 1
denominator /= 10
if value => denominator
result := value / denominator
LCD.dec(result)
value //= denominator
else
LCD.CHAR("0")


Let me know if if works.

Edit: I see some of the comments wrapped around. You'll need to fix that. I'm editing this post to attach the Spin file. The file should now be attached. If the code doesn't work, use File\Achive\Project to zip all the objects together and post the zip file. This way I can be sure I'm using the same objects you're using.

Edit (July 12, 2012): I fixed a bug the DecPoint method above. I did not fix the bug in the attached version of the code. Thank you Gunstar1 for pointing it out.

Tony_tsi
04-09-2012, 10:22 PM
This one works. I also attached the objects needed to run it.

Tony_tsi
04-09-2012, 10:26 PM
I don't think you need it though.

I don't have an LCD setup nor do I have the LCD object you're using (in the right place for the Prop Tool to find it) so I couldn't even compile the code below.

I think the "Char" method is a private method in the LCD object you're using. You need to make it public by changing PRI to PUB.

Here's the code:


CON
_clkmode = xtal1 + pll16x ' use crystal x 16
_xinfreq = 5_000_000 ' 5 MHz cyrstal (sys clock = 80 MHz)
adcCS_Pin = 13 'pins used for ADC chip
adcCLK_Pin = 15
adcDO_Pin = 14
INIT_OK = 0
OBJ
lcd : "LCD_16X2_8BIT"
ADC : "ADC_Driver"
VAR
byte ADC_value '>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>adc value in dec can range from 1 to 255
long volt_calc '>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>adc value multiplied by volt_step (volt step should = 5/255)
long volt_step '>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>should be equal to 5/255 this number repersents the voltage per 1 step of adc

PUB ini

lcd.start 'start junk
lcd.move(1,1)
lcd.str(string("kWH PROJECT"))
lcd.move(1,2)
lcd.str(string("WKU"))
waitcnt(clkfreq*2 + cnt)
volt_step := 50000 / 255

main
pub main | rcLCD, rcADC

lcd.clear
lcd.move(1,1)
lcd.str(string("volts"))
rcADC := ADC.Init(adcCS_Pin, adcCLK_Pin, adcDO_Pin)

if rcADC <> INIT_OK
LCD.Str(string("ADC chip init err"))
return
repeat
lcd.str(string(" "))
LCD.move(10, 1)
ADC_value := ADC.GetConvertedVal 'get value from ADC (binary)
volt_Calc := ADC_value * volt_step
DecPoint(volt_Calc, 100000)
waitcnt(clkfreq/2 + cnt) 'wait for 500ms
PUB DecPoint(value, denominator)
if value < 0
LCD.CHAR("-")
-value

if value > denominator
result := value / denominator
LCD.dec(result)
value //= denominator
else
LCD.CHAR("0")
LCD.CHAR(".")
repeat while denominator > 1
denominator /= 10
if value > denominator
result := value / denominator
LCD.dec(result)
value //= denominator
else
LCD.CHAR("0")


Let me know if if works.

Edit: I see some of the comments wrapped around. You'll need to fix that. I'm editing this post to attach the Spin file. The file should now be attached. If the code doesn't work, use File\Achive\Project to zip all the objects together and post the zip file. This way I can be sure I'm using the same objects you're using.



This code displays 7 where it should display 5.