PDA

View Full Version : Using floating point



andywpg
- 24th February 2015, 16:57
First off, I should mention that math and I don't get along very well..... :)

I doing a complete redesign of my charcoal smoker temperature controller. It worked very well, but I made some hardware mistakes when I designed the board, and I ran out of memory on the 16F886 I was using BEFORE I ran out of features I wanted to add.

To that end, I redesigned the board for an 18F2550, and decided on a fresh start with the program as I made some poor efficiency decisions on the first one. I am using Henrik's PID routines as before, and DT's Analog oversampling this time.

I want to have the temperature displays have one decimal place instead of just integer, so I am trying to comprehend the floating point example in 4FUNC.BAS (provided by MELabs). I am currently at work (testing a fire alarm system in a huge building), but I am basically sitting here while the Sprinkler Fitter moves around sending in his signals once in a while. That means I have time to play a bit.

C_TEMP and COOK_ADJUST are word variables.

Can someone 'vet' the following code and let me know if I'm on the right track here? Any suggestions or advice would be appreciated. I have tried to whittle down the example code so it only works for one decimal place. I have included the 18F 24-bit include file at the beginning of the program.

Thanks



C_TEMP = C_TEMP * 100 'MULTIPLY RAW ADC READING BY 100 THEN DIVIDE BY CALIBRATION VALUE TO GET
'CELCIUS TEMPERATURE
aint = C_TEMP
resulthold = aint
Gosub itofa ' Convert aint to float
bint = COOK_ADJUST 'DIVIDE BY ADJUST VALUE TO GET CELCIUS
Gosub itofb ' Convert int to float
Gosub fpdiv ' FP divide (/ COOK_ADJUST)
resulthold = aint 'STORE RESULT OF DIVISION IN RESULTHOLD
'NOW NEED TO CONVERT CELCIUS TO FARENHEIGHT
'F = ((C * 9) / 5) + 32
Gosub itofa ' Convert aint to float aint=result from divide
bint = 9
Gosub itofb ' Convert int to float
Gosub fpmul ' FP multiply (*9)
resulthold = aint 'STORE RESULT OF MULTIPLICATION IN RESULTHOLD
Gosub itofa ' Convert int back to float - aint=result from multiply
bint = 5
Gosub itofb ' Convert int to float
Gosub fpdiv ' FP divide (/5)
resulthold = aint 'STORE RESULT OF DIVISION IN RESULTHOLD
Gosub itofa ' Convert aint to float
bint = 32
Gosub itofb ' Convert bint to float
Gosub fpadd ' FP add (+32)
fpplaces = 1 'DISPLAY WITH ONE DECIMAL PLACE
Gosub fpdisplayr ' Call display routine

'OTHER STUFF

fpdisplayr:
'I DISPENSED WITH THE IF STATEMENTS, ONLY EVER WANT ONE DECIMAL PLACE
' Set floating point barg to 0.05
bexp = $7A
bargb0 = $4C
bargb1 = $CD

fpdisplay:
bexp = aexp ' Store the FP value of aarg to the barg variables
bargb0 = aargb0
bargb1 = aargb1
Gosub ftoia ' Convert aarg to integer
ahold = aint ' Save this value for the final display
Gosub itofa ' Convert integer back to float
Swap aexp,bexp ' Swap the FP values of aarg and barg before subtraction
Swap aargb0,bargb0
Swap aargb1,bargb1
Gosub fpsub ' Subtract the integer portion from the full number
bint = 10 ' Make bint = 10 E fpplaces
Gosub itofb ' Convert bint to integer prior to FP multiply
Gosub fpmul ' Multiply the decimal portion x 10 E fpplaces
Gosub ftoia ' Convert result to aint integer
'DISPLAY CT:xxx.x COOKER TEMP TO ONE DECIMAL PLACE
Lcdout $FE, 1, "CT:", dec abs ahold, ".", dec abs aint
RETURN

Jerson
- 24th February 2015, 17:16
I usually avoid use of floating point on a processor that does not have native support for it. It is an intensive computation and in most cases will eat up a sizeable amount of time to achieve the same result that you could with clever integer manipulations. In your case, you seem pretty clear you need fixed point arithmetic and so it is easier. You may need 32 bit integers to handle larger numbers.

In your case, the equations you show boil down to these

Assuming Temp is the input temperature read via the ADC and you want to calibrate the reading by temp*100/calc_adjust

Temp = Temp *100 ' max temp = 65535/100 ($FFFF hex)
Temp = Temp / Cook_Adjust ' Divide and keep the integer part of result.

Assuming you want a 0.1 deg resolution, I would do this (keep in mind the largest value that Temp can hold. Temp*1000 will define the limit of temperature going into the routine)
Temp = (Temp*1000)/Cook_Adjust (max input temp is now 65 since 65*1000 approximately fills the 16b integer)
You could also do this
Temp = (Temp*100) / (Cook_Adjust / 10) (max input temp is 650. However, cook_adjust should be multiples of 10 to impact the calculations)

Now, convert to F
Temp = Temp + (Temp << 3) ' mul by 9 x + 8*x = 9x
Temp = Temp / 5 ' div by 5
Temp = Temp + (32*10) ' add 32 with 1 decimal accounted for

and display it by splitting the value thus
Display Temp / 10, ".", Temp MOD 10

You need to specify your input range to make this technique practical for you.

andywpg
- 24th February 2015, 17:33
Well, as far as temperature ranges go, the cooker temp can be as high as 500F. The meat temp as high as 230F.

The adjust value for the raw ADC value is actually 1.64 for the meat thermocouple I am using. That was the reason for the 'times 100'.

I suppose it could be done with longs since I am using an 18F2550?

Thanks again for the advice

andywpg
- 24th February 2015, 18:36
Assuming you want a 0.1 deg resolution, I would do this (keep in mind the largest value that Temp can hold. Temp*1000 will define the limit of temperature going into the routine)
Temp = (Temp*1000)/Cook_Adjust (max input temp is now 65 since 65*1000 approximately fills the 16b integer)
You could also do this
Temp = (Temp*100) / (Cook_Adjust / 10) (max input temp is 650. However, cook_adjust should be multiples of 10 to impact the calculations)

Now, convert to F
Temp = Temp + (Temp << 3) ' mul by 9 x + 8*x = 9x
Temp = Temp / 5 ' div by 5
Temp = Temp + (32*10) ' add 32 with 1 decimal accounted for

and display it by splitting the value thus
Display Temp / 10, ".", Temp MOD 10

You need to specify your input range to make this technique practical for you.

Well then, that seems a LOT easier now that I've had time to look at it. I'm going to use longs so that my max temp will fit and do the * 1000.

Thanks for the help!

HenrikOlsson
- 24th February 2015, 18:42
Hi,
So you have a raw reading which you want to divide by 1.64, is that correct? Or do you want to multiply the raw value by 1.64?

TempC = TempC ** 39961 ' Multiplu by 0.60976 which is the same as dividing by 1.64

or

TempC = TempC */ 420 ' Multiply by ~1.641

or

TempC = TempC + (TempC ** 41943) ' Multiply by 0.64 and add result to previous value.

If neither of these works can you give us a bit more details about how the raw values correlates to actual temperature. Ie what raw value do you get at 0 temp and what raw value do you get at max temp (500 degrees in this case).

/Henrik.

andywpg
- 24th February 2015, 19:57
Hi,
So you have a raw reading which you want to divide by 1.64, is that correct? Or do you want to multiply the raw value by 1.64?

TempC = TempC ** 39961 ' Multiplu by 0.60976 which is the same as dividing by 1.64

or

TempC = TempC */ 420 ' Multiply by ~1.641

or

TempC = TempC + (TempC ** 41943) ' Multiply by 0.64 and add result to previous value.

If neither of these works can you give us a bit more details about how the raw values correlates to actual temperature. Ie what raw value do you get at 0 temp and what raw value do you get at max temp (500 degrees in this case).

/Henrik.

Divide by 1.64. I found that the meat thermocouple in boiling water required me to divide by 1.64 to get 100C. The cooker thermocouple was a similar (but slightly different) number. In reality, I have a calibration routine that determines the number then it is stored in COOK_ADJUST or MEAT_ADJUST. This is so I can easily correct if a new thermocouple is installed.

After reading Jerson's post, I changed C_TEMP and M_TEMP (the cooker and meat raw ADC values, respectively) to LONGS. I came up with the following that I will try when I finish building the new board:



C_TEMP = C_TEMP * 1000
C_TEMP = C_TEMP / COOK_ADJUST 'ADJUST * 1000
C_TEMP = C_TEMP + (C_TEMP << 3) ' mul by 1 x + 8*x = 9x
C_TEMP = C_TEMP / 5 ' div by 5
C_TEMP = C_TEMP + 320 ' add 32 with 1 decimal accounted for
M_TEMP = M_TEMP * 1000
M_TEMP = M_TEMP / MEAT ADJUST
M_TEMP = M_TEMP + (M_TEMP << 3) ' mul by 1 x + 8*x = 9x
M_TEMP = M_TEMP / 5 ' div by 5
M_TEMP = M_TEMP + 320 ' add 32 with 1 decimal accounted for
'CT:nnn.nF MT:nnn.nF
LCDOUT $FE, $80, "CT:", DEC3 (C_TEMP / 10), ".", DEC1 (C_TEMP MOD 10), "F"
LCDOUT $FE, $80 + 9, " ", "MT:", DEC3 (M_TEMP /10), ".", DEC1 (M_TEMP MOD 10), "F"


I think this will work. Opinions?

andywpg
- 24th February 2015, 20:24
Henrik, I just had a thought. How is the fact that I changed my temperature value to a long going to affect your PID routine? Specifically, pid_error and my set point are words, the actual temperature is a long.

I've never used longs before, can you mix them with words? And I guess that I will have to use C_TEMP / 10 to get the integer portion in the equation: pid_error = setpoint - temperature?

Jerson
- 25th February 2015, 03:45
Andy, your code looks workable. To simplify things, I use a tool from http://www.miscel.dk called miscellaneous electronic calculations. In that you will find an integer math module that tells you 1.64 is same as 41/25 if you want to use 8 bit math. It's a nice tool to augment your collection and I suggest you give it a try

HenrikOlsson
- 25th February 2015, 06:10
Henrik, I just had a thought. How is the fact that I changed my temperature value to a long going to affect your PID routine? Specifically, pid_error and my set point are words, the actual temperature is a long.
It doesn't really matter as long as you make sure that you don't overflow the pid_Error variable. And remember that pid_Error is a two's complement variable so anything above 32767 is considered negative.



I've never used longs before, can you mix them with words?
Yes, a LONG minus a LONG can still fit a WORD or even a BYTE. There MAY be some differences when it comes to multiplications because LONGs are signed while WORDS and BYTES are not - I'd take a look at the manual in case it's mentioned there.


And I guess that I will have to use C_TEMP / 10 to get the integer portion in the equation: pid_error = setpoint - temperature?
The general rule is to have the PID routine work with your raw numbers as long as the raw numbers aren't big enough to risk overflowing. There's no need for "empty resolution". The PID routine is just crunching numbers, it doesn't know what the numbers mean or represent so there's no need to have the values scaled to degrees C or whatever. As long as YOU remember to have the setpoint and actual temp variable scaled equally when you calculate pid_Error it'll be fine - then you can scale those value up or down all you want for humans to be able to make sense out of them ;-)

/Henrik.