|
|
View previous topic :: View next topic |
Author |
Message |
demedeiros
Joined: 27 Dec 2013 Posts: 71
|
Expressions not evaluating correctly |
Posted: Wed Dec 16, 2015 2:40 pm |
|
|
Hi All,
I am using a PIC16F1829 with PCM compiler, version 5.051.
I am working on a program to communicate with a Measurement Specialties 89BSD pressure sensor.http://meas-spec.com/product/pressure/89BSD.aspx
The majority of the program is working correctly, but I am having problems with two long/complex expressions that are not evaluating correctly.
Code: |
float32 GetTopCoef(){
float32 TOP = D1+C0*pow(2,Q[0]) + C3*pow(2, Q[3]) * (D2/pow(2, 24)) + C4*pow(2, Q[4]) * pow((D2/pow(2, 24)),2);
printf("TOP %f\r", TOP);
return TOP;
}
float32 GetBOTCoef(){
float32 BOT = C1 * pow(2, Q[1]) + C5 * pow(2, Q[5]) * (D2/pow(2,24)) + C6*pow(2, Q[6])*pow((D2/pow(2,24)),2);
printf("BOT %f\r", BOT);
return BOT;
} |
This function works in excel, and has worked on an Arduino. Is there something I'm missing here? Am I using a function incorrectly?
Also, it may be important to note that I am approaching my ROM limit on this PIC, currently at 91%.
Thank you! |
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Wed Dec 16, 2015 3:02 pm |
|
|
Post a test program that shows the variable declarations and the
initialization values, and shows the #define statements for the constants.
Tell us what results you get when you run it in a PIC, and what results
you expected to get. |
|
|
demedeiros
Joined: 27 Dec 2013 Posts: 71
|
|
Posted: Wed Dec 16, 2015 3:12 pm |
|
|
Actual code is below. Here are the values of the variables that are not shown/derived.
Code: |
D1: 4098692
D2: 2940222
C0:-4742
C1: 3088
C2: 99
C3: -122
C4: 181
C5: -142
C6: 137
Q0: 9
Q1: 11
Q2: 9
Q3: 15
Q4: 15
Q5: 16
Q6: 16
|
TOP should evaluate to 1152346.526 and BOT should evaluate to 4969073.057. They are currently evaluating to -18441256.94 and -15428157.40 respectively.
Code: |
#include <main.h>
#include <math.h>
#define PSEN_ADDRESS 0x77
#define CMD_RESET 0x1E // ADC reset command
#define CMD_ADC_READ 0x00 // ADC read command
#define CMD_ADC_CONV 0x40 // ADC conversion command
#define CMD_ADC_D1 0x00 // ADC D1 conversion
#define CMD_ADC_D2 0x10 // ADC D2 conversion
#define CMD_ADC_256 0x00 // ADC OSR=256
#define CMD_ADC_512 0x02 // ADC OSR=512
#define CMD_ADC_1024 0x04 // ADC OSR=1024
#define CMD_ADC_2048 0x06 // ADC OSR=2048
#define CMD_ADC_4096 0x08 // ADC OSR=4096
#define CMD_PROM_RD 0xA0 // Prom read command
unsigned int16 C[8];
float C0=0, C1=0, C2=0, C3=0, C4=0, C5=0, C6=0;
int16 SENA0=0, SENA1=0, SENA2=0;
float Q[7] = {9, 11, 9, 15, 15, 16 ,16};
unsigned int32 D1, D2;
float32 P,TOP, BOT;
//=================================
void PSEN_CMD_RESET(void);
unsigned int16 PSEN_CMD_PROM(unsigned char coef_num);
void Get_Coefficients(unsigned int16 array[]);
int16 TenBitConvertion( int16 Value);
int16 FourteenBitConversion( int16 Value);
unsigned int32 cmd_adc(char cmd);
float32 GetPressure();
float32 GetTopCoef();
float32 GetBOTCoef();
void GetD1D2();
void main()
{
while(1){
if(input(PIN_C6)){ //Run only if user presses button
PSEN_CMD_RESET(); //Reset the pressure sensor
for(int i = 0; i < 8; i++){
C[i] = PSEN_CMD_PROM(i); //Read in the sensor register map
}
Get_Coefficients(C); //Calculate the sensor coefficients
float Pressure = GetPressure(); //Get pressure from sensor
printf("PRESSURE: %f\r\n",Pressure); //Print pressure
delay_ms(250); //Delay for debounce
}
}
}
//Sends the sensor reset command
void PSEN_CMD_RESET(void){
i2c_start(); //Start I2C
i2c_write(PSEN_ADDRESS << 1); //Write device address
i2c_write(CMD_RESET); //Write the reset command
i2c_stop(); //Stop I2C
delay_ms(3);
}
//Read the calibration coefficients
unsigned int16 PSEN_CMD_PROM(char coef_num){
unsigned int16 ret;
unsigned int16 rC = 0;
i2c_start();
i2c_write(PSEN_ADDRESS << 1);
i2c_write(CMD_PROM_RD+coef_num*2);
i2c_start();
i2c_write((PSEN_ADDRESS << 1) | 1); //Send a read request
ret = i2c_read(); //Read with ACK
rC = 256 * ret;
ret = i2c_read(0); //Read with no ACK
rC = rC + ret;
i2c_stop();
return rC;
}
//Calculate the coefficients from the PROM array
void Get_Coefficients(unsigned int16 array[]){
C0 = (array[1] & 0xFFFC) >> 2;
C0 = FourteenBitConversion(C0);
C1 = ((array[1] & 0x03) << 12) + ((array[2] & 0xFFF0) >> 4);
C1 = FourteenBitConversion(C1);
C2 = (((array[2] & 0xF) << 6) + (array[3] >> 10)) & 0x3FF;
C2 = TenBitConvertion(C2);
C3 = array[3] & 0x3FF;
C3 = TenBitConvertion(C3);
C4 = (array[4] >> 6) & 0x3FF;
C4 = TenBitConvertion(C4);
C5 = (((array[4] & 0x3F) << 4) + (array[5] >> 12)) & 0x3FF;
C5 = TenBitConvertion(C5);
C6 = (array[5] >> 2) & 0x3FF;
C6 = TenBitConvertion(C6);
SENA0 = (((array[5] & 0x3) << 8) + (array[6] >> 8)) & 0x3FF;
SENA0 = TenBitConvertion(SENA0);
SENA1 = (((array[6] & 0xFF) << 2) + (array[7] >> 14)) & 0x3FF;
SENA1 = TenBitConvertion(SENA1);
SENA2 = (array[7] >> 4) & 0x3FF;
SENA2 = TenBitConvertion(SENA2);
}
//Convert from twos complement if required
int16 TenBitConvertion( int16 Value){
int16 Converted = Value;
if(Value > 512){
Converted = ((Value - 512 -1)^511)*-1;
}
return Converted;
}
//Convert from twos complement if required
int16 FourteenBitConversion( int16 Value){
int16 Converted = Value;
if(Value > 8192){
Converted = ((Value - 8192 -1)^8191)*-1;
}
return Converted;
}
//Read ADC
unsigned int32 cmd_adc(char cmd){
unsigned int16 ret;
unsigned int32 temp = 0;
i2c_start();
i2c_write(PSEN_ADDRESS << 1);
i2c_write(CMD_ADC_CONV+cmd);
i2c_stop();
switch(cmd & 0x0F){
case CMD_ADC_256 : delay_us(900); break;
case CMD_ADC_512 : delay_ms(3); break;
case CMD_ADC_1024: delay_ms(4); break;
case CMD_ADC_2048: delay_ms(6); break;
case CMD_ADC_4096: delay_ms(10); break;
}
i2c_start();
i2c_write(PSEN_ADDRESS << 1);
i2c_write(CMD_ADC_READ);
i2c_stop();
i2c_start();
i2c_write((PSEN_ADDRESS << 1) | 1);
ret = i2c_read();
temp = 65536*ret;
ret = i2c_read();
temp = temp+256*ret;
ret = i2c_read(0);
i2c_stop();
temp = temp+ret;
return temp;
}
//Get the pressure values
float32 GetPressure(){
GetD1D2();
float32 TOP = GetTopCoef();
float32 BOT = GetBOTCoef();
float32 Y = TOP/BOT;
float32 P = Y*(1-C2*pow(2,Q[2])/pow(2,24))+(C2*pow(2,Q[2])/pow(2,24))*pow(Y,2);
float32 Pressure = ((P-0.1)/0.8)*(6-0);
return Pressure;
}
float32 GetTopCoef(){
float32 TOP = D1+C0*pow(2,Q[0]) + C3*pow(2, Q[3]) * (D2/pow(2, 24)) + C4*pow(2, Q[4]) * pow((D2/pow(2, 24)),2);
printf("TOP %f\r", TOP);
return TOP;
}
float32 GetBOTCoef(){
float32 BOT = C1 * pow(2, Q[1]) + C5 * pow(2, Q[5]) * (D2/pow(2,24)) + C6*pow(2, Q[6])*pow((D2/pow(2,24)),2);
printf("BOT %f\r", BOT);
return BOT;
}
void GetD1D2(){
D2=cmd_adc(CMD_ADC_D2+CMD_ADC_4096); // read D2
D1=cmd_adc(CMD_ADC_D1+CMD_ADC_4096); // read D1
printf("D1: %ld\r\n", D1);
printf("D2: %ld\r\n", D2);
} |
|
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Wed Dec 16, 2015 4:19 pm |
|
|
demedeiros wrote: |
TOP should evaluate to 1152346.526 and BOT should evaluate to
4969073.057. They are currently evaluating to -18441256.94 and
-15428157.40 respectively.
|
I took your code and made it into a test program and it works OK.
I compiled it with CCS vs. 5.051 and ran it in MPLAB vs. 8.92 simulator.
I got the following results in the MPLAB Output window:
Quote: |
TOP 1152346.08
BOT 4969073.60
|
Test program:
Code: |
#include <16F1829.h>
#fuses INTRC_IO, NOWDT, NOMCLR
#use delay(clock=4M)
#use rs232(baud=9600, UART1, ERRORS)
#include <math.h>
unsigned int16 C[8];
float C0=0, C1=0, C2=0, C3=0, C4=0, C5=0, C6=0;
int16 SENA0=0, SENA1=0, SENA2=0;
float Q[7] = {9, 11, 9, 15, 15, 16 ,16};
unsigned int32 D1, D2;
float32 P,TOP, BOT;
float32 GetTopCoef();
float32 GetBOTCoef();
//=================================
void main()
{
float top_result;
float bot_result;
D1 = 4098692;
D2 = 2940222;
C0 = -4742;
C1 = 3088;
C2 = 99;
C3 = -122;
C4 = 181;
C5 = -142;
C6 = 137;
top_result = GetTopCoef();
bot_result = GetBOTCoef();
while(TRUE);
}
//----------------------------------
float32 GetTopCoef(){
float32 TOP = D1+C0*pow(2,Q[0]) + C3*pow(2, Q[3]) * (D2/pow(2, 24)) + C4*pow(2, Q[4]) * pow((D2/pow(2, 24)),2);
printf("TOP %f\r", TOP);
return TOP;
}
//-----------------------------
float32 GetBOTCoef(){
float32 BOT = C1 * pow(2, Q[1]) + C5 * pow(2, Q[5]) * (D2/pow(2,24)) + C6*pow(2, Q[6])*pow((D2/pow(2,24)),2);
printf("BOT %f\r", BOT);
return BOT;
} |
|
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19552
|
|
Posted: Thu Dec 17, 2015 6:01 am |
|
|
PCM_programmer, has shown that in fact it does work OK.
His code, without doing anything else though, uses 58% of the ROM.
Lets see if we can improve things.
There are several things being 'missed'. Most of the 'pow' calls are being used to evaluate _integer_ 2^x calls. On a simple int32, this is equivalent to a rotation, that can be performed in a handful of instructions, instead of a pow call (hundreds of instructions). The remaining pow calls are used to evaluate squares, which is much more efficiently done as x*x.
Then there are two terms that are evaluated twice in each routine. Much more efficient to solve these once, and store the result.
Then the coefficients for the rotations are always integers. No point in storing these as floats.
So:
Code: |
#include <16F1829.h>
#fuses INTRC_IO, NOWDT, NOMCLR
#use delay(clock=4M)
#use rs232(baud=9600, UART1, ERRORS)
unsigned int16 C[8];
float C0=0, C1=0, C2=0, C3=0, C4=0, C5=0, C6=0;
int16 SENA0=0, SENA1=0, SENA2=0;
int Q[7] = {9, 11, 9, 15, 15, 16 ,16}; //These factors are always integer
unsigned int32 D1, D2;
float32 P,TOP, BOT;
float32 GetTopCoef();
float32 GetBOTCoef();
float32 twoto(int factor) //Efficiently return integer 2^factor
{
int32 res=1;
res<<=(factor);
return (float32)res;
}
#define SQUARE(x) ((x)*(x)) //efficient square function
//=================================
void main()
{
float top_result;
float bot_result;
float result;
D1 = 4098692;
D2 = 2940222;
C0 = -4742;
C1 = 3088;
C2 = 99;
C3 = -122;
C4 = 181;
C5 = -142;
C6 = 137;
top_result = GetTopCoef();
bot_result = GetBOTCoef();
result=top_result/bot_result;
printf("result %7.4f\r", result);
while(TRUE);
}
//----------------------------------
float32 GetTopCoef()
{
float32 recip;
float32 TOP;
recip=D2/twoto(24); //save calculation - do this once
TOP = D1 + C0*twoto(Q[0]) + (C3*twoto(Q[3]) * recip) + (C4*twoto(Q[4]) * (SQUARE(recip)));
printf("TOP %f\r", TOP);
return TOP;
}
//-----------------------------
float32 GetBOTCoef()
{
//similar recvoding for the bottom
float32 recip;
float32 BOT;
recip=D2/twoto(24);
BOT = C1*twoto(Q[1]) + (C5*twoto(Q[5]) * recip) + (C6*twoto(Q[6]) * (SQUARE(recip)));
printf("BOT %f\r", BOT);
return BOT;
}
|
I've added code to generate the final result (since I wanted to test with the values in the data sheet, and verify that I did get a result to match the example (got 0.7672). This uses less than half the ROM, and takes 1/10th the time (removing the prints, takes 17mSec against 171mSec.....). The result is also slightly more accurate (avoids some of the float rounding errors...).
Hopefully this will give the space needed, and bring the speed to a more useful level as well as emphasising the point I often make here to avoid float except where they must be used.
Just edited:
Just to add one thing, if you are not getting the right numbers, how are you testing these?. If it is in a debugger, like MPLAB, make sure you are selecting the correct number format (Microchip, not IEEE). Would explain silly results... |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19552
|
|
Posted: Thu Dec 17, 2015 6:42 am |
|
|
as a further comment, your conversions are 'dubious'.
You have a value that is potentially _signed_. It is going to need to be returned as a signed number to work. The conversion is relatively simple. It is called 'sign extending'. For a 10bit value (for example):
Code: |
signed int16 convert10(int16 source_val)
{
signed int16 rval;
rval=source_val & 0x1FF; //low 9 bits
if (bit_test(source_val,9))
{
rval |= 0xFE00); //set top 7 bits
}
return rval;
}
|
All you do is test the tenth bit (9, since they are numbered from 0), and set the top 7 bits in the result if this is set. So the 'sign' bit, is 'extended' into the result value.
The same technique will work for the other conversions.
Last edited by Ttelmah on Thu Dec 17, 2015 8:06 am; edited 1 time in total |
|
|
demedeiros
Joined: 27 Dec 2013 Posts: 71
|
|
Posted: Thu Dec 17, 2015 7:49 am |
|
|
Man, you guys are incredible!
Optimizing is still something I need more experience with, thank you for providing the examples on pow() and the twoto() function. That is incredibly helpful!
After banging my head against my desk for a few more hours, I ended up determining that my error was where I perform the two's complement conversion on some of the C constants. Specifically, these two functions:
Code: | //Convert from twos complement if required
int16 TenBitConvertion( int16 Value){
int16 Converted = Value;
if(Value > 512){
Converted = ((Value - 512 -1)^511)*-1;
}
return Converted;
}
//Convert from twos complement if required
int16 FourteenBitConversion( int16 Value){
int16 Converted = Value;
if(Value > 8192){
Converted = ((Value - 8192 -1)^8191)*-1;
}
return Converted;
}
|
I changed the function type and casted "Converted" to float32, all seems well now!
Code: |
//Convert from twos complement if required
float32 TenBitConvertion( int16 Value){
float32 Converted = Value;
if(Value > 512){
Converted = ((Value - 512 -1)^511)*-1;
}
//printf("Converted: %ld\r\n", Converted);
return (float32)Converted;
}
//Convert from twos complement if required
float32 FourteenBitConversion( int16 Value){
float32 Converted = Value;
if(Value > 8192){
Converted = ((Value - 8192 -1)^8191)*-1;
}
// printf("Converted: %ld\r\n", Converted);
return (float32)Converted;
} |
One thing I am not understanding though, is why this does not work as an int16? This numbers (C) are all integers (pos and negative). Even if I declared all the constants as int16 as opposed to float32, they still didnt work.
Thanks again!!! |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19552
|
|
Posted: Thu Dec 17, 2015 8:05 am |
|
|
See the comment on how to do the conversions already posted....
_signed_ int16.
You are not returning the sign. |
|
|
demedeiros
Joined: 27 Dec 2013 Posts: 71
|
|
Posted: Thu Dec 17, 2015 8:06 am |
|
|
Sorry! I hadn't seen that most recent reply! Thanks again!! |
|
|
asmboy
Joined: 20 Nov 2007 Posts: 2128 Location: albany ny
|
|
Posted: Thu Dec 17, 2015 6:02 pm |
|
|
MR. T has given an excellent summary of your problem but there is a generic message that can't be repeated enough to all who read this:
When dealing with a complicated bit of math in the PIC -
1- always reduce the expression you want to solve by pre-calculating all the constants you can combine. CONSOLIDATE constant values !
2-factor multiplications and divisions into shifts EG: /96 is really /3 then>>5
3- work with scaled INTS where ever possible.
4- store intermediate values that will be needed again. Clever use of ram saves cycles.
5-avoid POW , SQR , trig-fns, transcendental math // floats like the processor plague they are.
6- Avoid all but the simplest math , and especially floats inside an ISR handler. |
|
|
Gabriel
Joined: 03 Aug 2009 Posts: 1067 Location: Panama
|
|
Posted: Mon Dec 21, 2015 5:57 am |
|
|
Ive read this briefly, but just wanted to suggest a lookup table.
Use an Excel sheet to precalculate the values.
Ive done this to linearize a thermocouple, which seems very similar to what you are doing.
G. _________________ CCS PCM 5.078 & CCS PCH 5.093 |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19552
|
|
Posted: Mon Dec 21, 2015 8:12 am |
|
|
I'ts 'eeny meeny' on that.
The maths is directly from the formulae given by the manufacturer, and with the tweaks already given, is not too bad on size. Problem is that a look up might well end up larger....
In this case the coefficients are being read from the chip itself, so for general use (as opposed to coding for a single chip), the maths has to be done.
If there were 'log' terms higher powers, or other functions like this, that wouldn't simplify, and the coefficients were fixed, then definitely worth considering.
Now where I really strongly 'might' recommend this, is on the other thread running at the moment about variable width PWM. However even on this, with any reasonable number of terms, the poster may well run into size problems on his PIC.
An approach that always must be remembered. |
|
|
|
|
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
|