EmbeddedRelated.com
Forums

C18 calculation question - novice...

Started by Dennis August 3, 2011
I'm trying to interface a SHT21 humidity sensor to a PIC (and learning 
C18 along the way, MPLAB, C18, PIC18LF44K22).

A 16 bit serial value, S_RH, is read from the sensor. To convert this 
value into relative humidity, RH, it needs to be inserted into an equation.

RH = (125 x S_RH)/ 2^16  - 6

The data sheet gives a test value of S_RH = 25424 giving  RH = 42.5% 
which works nicely.


I want the result in a from that I can easily convert to 
hundreds/tens/units/tenths etc for outputing from the uC.  To achieve 
this I want to multiply the above formula by 100 so the result 
represents tens/units/tenths/hundredths with the decimal point shifted 
right two places.

So the formula will become:

RH = (100 x 125 x S_RH)/2^16 - 600


Now for my problem.....

I'm having problems with what number types to use.

The maximum value for RH DURING the calulation should be;
100 x 125 x 65536  = 81987500   which is Ox30D3CF2C

This needs 32 bits so I define RH as unsigned long but it appears to 
overflow corrupting the result when I run the code in the simulator





volatile unsigned int	S_RH;	//raw serial rh value, 16 bits
volatile unsigned long RH;	//RH value, 32 bits

#pragma code    // declare executable instructions
void main (void)
{

//load test serial data
S_RH = 25424;

RH = 125*(S_RH);	//65536;

.....}


Even with the calculation reduced to 125 x S_RH to limit the size of the 
result I can see that the result gets clipped with RH register 
containing 0x00007E10 after the above calculation.
The correct value which would be 0X00307E10. So it appears the RH is 
being treated as a 16 bit variable.

Apologies for the long post, any advice gratefully received.

Thanks.
Dennis <user@example.net> writes:

> I'm trying to interface a SHT21 humidity sensor to a PIC (and learning > C18 along the way, MPLAB, C18, PIC18LF44K22). > > A 16 bit serial value, S_RH, is read from the sensor. To convert this > value into relative humidity, RH, it needs to be inserted into an > equation. > > RH = (125 x S_RH)/ 2^16 - 6 > > The data sheet gives a test value of S_RH = 25424 giving RH = 42.5% > which works nicely. > > > I want the result in a from that I can easily convert to > hundreds/tens/units/tenths etc for outputing from the uC. To achieve > this I want to multiply the above formula by 100 so the result > represents tens/units/tenths/hundredths with the decimal point shifted > right two places. > > So the formula will become: > > RH = (100 x 125 x S_RH)/2^16 - 600 > > > Now for my problem..... > > I'm having problems with what number types to use. > > The maximum value for RH DURING the calulation should be; > 100 x 125 x 65536 = 81987500 which is Ox30D3CF2C > > This needs 32 bits so I define RH as unsigned long but it appears to > overflow corrupting the result when I run the code in the simulator > > > > > > volatile unsigned int S_RH; //raw serial rh value, 16 bits > volatile unsigned long RH; //RH value, 32 bits > > #pragma code // declare executable instructions > void main (void) > { > > //load test serial data > S_RH = 25424; > > RH = 125*(S_RH); //65536;
This only converts to a long *after* the 125xS_RH has been performed. So it is overflowing before being assigned to RH. Try RH = 125L * S_RH; The L specifies a "long literal" value, this forces the entire calculation to be done in "long". Other ways would be: RH = 125 * (long)S_RH; or RH = S_RH; RH *= 125;
> Even with the calculation reduced to 125 x S_RH to limit the size of > the result I can see that the result gets clipped with RH register > containing 0x00007E10 after the above calculation. > The correct value which would be 0X00307E10. So it appears the RH is > being treated as a 16 bit variable.
It is S_RH and the value 125 that are 16 bit. You need to "coerce" one of them to 32 bit in order to do 32 bit math. [...] -- John Devereux
John Devereux <john@devereux.me.uk> writes:

> Dennis <user@example.net> writes: > >> I'm trying to interface a SHT21 humidity sensor to a PIC (and learning >> C18 along the way, MPLAB, C18, PIC18LF44K22). >> >> A 16 bit serial value, S_RH, is read from the sensor. To convert this >> value into relative humidity, RH, it needs to be inserted into an >> equation. >> >> RH = (125 x S_RH)/ 2^16 - 6 >> >> The data sheet gives a test value of S_RH = 25424 giving RH = 42.5% >> which works nicely. >> >> >> I want the result in a from that I can easily convert to >> hundreds/tens/units/tenths etc for outputing from the uC. To achieve >> this I want to multiply the above formula by 100 so the result >> represents tens/units/tenths/hundredths with the decimal point shifted >> right two places. >> >> So the formula will become: >> >> RH = (100 x 125 x S_RH)/2^16 - 600 >> >> >> Now for my problem..... >> >> I'm having problems with what number types to use. >> >> The maximum value for RH DURING the calulation should be; >> 100 x 125 x 65536 = 81987500 which is Ox30D3CF2C >> >> This needs 32 bits so I define RH as unsigned long but it appears to >> overflow corrupting the result when I run the code in the simulator >> >> >> >> >> >> volatile unsigned int S_RH; //raw serial rh value, 16 bits >> volatile unsigned long RH; //RH value, 32 bits >> >> #pragma code // declare executable instructions >> void main (void) >> { >> >> //load test serial data >> S_RH = 25424; >> >> RH = 125*(S_RH); //65536; > > This only converts to a long *after* the 125xS_RH has been performed. So > it is overflowing before being assigned to RH. Try > > RH = 125L * S_RH; > > The L specifies a "long literal" value, this forces the entire > calculation to be done in "long". > > Other ways would be: > > RH = 125 * (long)S_RH; > > or > > RH = S_RH; > RH *= 125; > >> Even with the calculation reduced to 125 x S_RH to limit the size of >> the result I can see that the result gets clipped with RH register >> containing 0x00007E10 after the above calculation. >> The correct value which would be 0X00307E10. So it appears the RH is >> being treated as a 16 bit variable. > > It is S_RH and the value 125 that are 16 bit. You need to "coerce" one of > them to 32 bit in order to do 32 bit math. > > > [...]
I meant to add you should google "usual conversions" for all the rules here. -- John Devereux
On 3/08/2011 3:16 PM, John Devereux wrote:
> John Devereux<john@devereux.me.uk> writes:
[.........]
>> >> This only converts to a long *after* the 125xS_RH has been performed. So >> it is overflowing before being assigned to RH. Try >> >> RH = 125L * S_RH; >> >> The L specifies a "long literal" value, this forces the entire >> calculation to be done in "long". >> >> Other ways would be: >> >> RH = 125 * (long)S_RH; >> >> or >> >> RH = S_RH; >> RH *= 125; >>
[...]
>
> I meant to add you should google "usual conversions" for all the rules > here. >
Fantastic John, thank you. The 125L solved the problem. I'll further google your "usual conversions" suggestion and no doubt add another few pages to my cheat notes. Thanks again, your help is much appreciated. D.
On Wed, 03 Aug 2011 14:02:06 +0800, Dennis wrote:

> I'm trying to interface a SHT21 humidity sensor to a PIC (and learning > C18 along the way, MPLAB, C18, PIC18LF44K22). > > A 16 bit serial value, S_RH, is read from the sensor. To convert this > value into relative humidity, RH, it needs to be inserted into an > equation. > > RH = (125 x S_RH)/ 2^16 - 6 > > The data sheet gives a test value of S_RH = 25424 giving RH = 42.5% > which works nicely. > > > I want the result in a from that I can easily convert to > hundreds/tens/units/tenths etc for outputing from the uC. To achieve > this I want to multiply the above formula by 100 so the result > represents tens/units/tenths/hundredths with the decimal point shifted > right two places. > > So the formula will become: > > RH = (100 x 125 x S_RH)/2^16 - 600 > > > Now for my problem..... > > I'm having problems with what number types to use. > > The maximum value for RH DURING the calulation should be; 100 x 125 x > 65536 = 81987500 which is Ox30D3CF2C > > This needs 32 bits so I define RH as unsigned long but it appears to > overflow corrupting the result when I run the code in the simulator > > > > > > volatile unsigned int S_RH; //raw serial rh value, 16 bits volatile > unsigned long RH; //RH value, 32 bits > > #pragma code // declare executable instructions void main (void) > { > > //load test serial data > S_RH = 25424; > > RH = 125*(S_RH); //65536; > > .....} > > > Even with the calculation reduced to 125 x S_RH to limit the size of the > result I can see that the result gets clipped with RH register > containing 0x00007E10 after the above calculation. The correct value > which would be 0X00307E10. So it appears the RH is being treated as a 16 > bit variable. > > Apologies for the long post, any advice gratefully received.
At one place I worked we had a long drawn out interview question (it could take an hour to explore all the ramifications). You're already a lot further along that many. -- Tim Wescott Control system and signal processing consulting www.wescottdesign.com
On Wed, 03 Aug 2011 08:08:12 +0100, John Devereux wrote:

> Dennis <user@example.net> writes: > >> I'm trying to interface a SHT21 humidity sensor to a PIC (and learning >> C18 along the way, MPLAB, C18, PIC18LF44K22). >> >> A 16 bit serial value, S_RH, is read from the sensor. To convert this >> value into relative humidity, RH, it needs to be inserted into an >> equation. >> >> RH = (125 x S_RH)/ 2^16 - 6 >> >> The data sheet gives a test value of S_RH = 25424 giving RH = 42.5% >> which works nicely. >> >> >> I want the result in a from that I can easily convert to >> hundreds/tens/units/tenths etc for outputing from the uC. To achieve >> this I want to multiply the above formula by 100 so the result >> represents tens/units/tenths/hundredths with the decimal point shifted >> right two places. >> >> So the formula will become: >> >> RH = (100 x 125 x S_RH)/2^16 - 600 >> >> >> Now for my problem..... >> >> I'm having problems with what number types to use. >> >> The maximum value for RH DURING the calulation should be; 100 x 125 x >> 65536 = 81987500 which is Ox30D3CF2C >> >> This needs 32 bits so I define RH as unsigned long but it appears to >> overflow corrupting the result when I run the code in the simulator >> >> >> >> >> >> volatile unsigned int S_RH; //raw serial rh value, 16 bits
volatile
>> unsigned long RH; //RH value, 32 bits >> >> #pragma code // declare executable instructions void main (void) >> { >> >> //load test serial data >> S_RH = 25424; >> >> RH = 125*(S_RH); //65536; > > This only converts to a long *after* the 125xS_RH has been performed. So > it is overflowing before being assigned to RH. Try > > RH = 125L * S_RH; > > The L specifies a "long literal" value, this forces the entire > calculation to be done in "long". > > Other ways would be: > > RH = 125 * (long)S_RH; > > or > > RH = S_RH; > RH *= 125; > >> Even with the calculation reduced to 125 x S_RH to limit the size of >> the result I can see that the result gets clipped with RH register >> containing 0x00007E10 after the above calculation. The correct value >> which would be 0X00307E10. So it appears the RH is being treated as a >> 16 bit variable. > > It is S_RH and the value 125 that are 16 bit. You need to "coerce" one > of them to 32 bit in order to do 32 bit math.
I have run into aggressively optimizing compilers that need to have _both_ arguments cast to long to reliably do the calculation right, in spite of the C standard. Saying 125L * (long)(S_RH) only uses up a few more characters, doesn't change what a correct compiler will do, and saves your code from the edge cases where a compiler _doesn't_ do its job. -- Tim Wescott Control system and signal processing consulting www.wescottdesign.com
Tim <tim@seemywebsite.please> writes:

> On Wed, 03 Aug 2011 08:08:12 +0100, John Devereux wrote: > >> Dennis <user@example.net> writes: >>
[...]
>>> >>> volatile unsigned int S_RH; //raw serial rh value, 16 bits > volatile >>> unsigned long RH; //RH value, 32 bits >>> >>> #pragma code // declare executable instructions void main (void) >>> { >>> >>> //load test serial data >>> S_RH = 25424; >>> >>> RH = 125*(S_RH); //65536; >> >> This only converts to a long *after* the 125xS_RH has been performed. So >> it is overflowing before being assigned to RH. Try >> >> RH = 125L * S_RH; >> >> The L specifies a "long literal" value, this forces the entire >> calculation to be done in "long". >> >> Other ways would be: >> >> RH = 125 * (long)S_RH; >> >> or >> >> RH = S_RH; >> RH *= 125; >> >>> Even with the calculation reduced to 125 x S_RH to limit the size of >>> the result I can see that the result gets clipped with RH register >>> containing 0x00007E10 after the above calculation. The correct value >>> which would be 0X00307E10. So it appears the RH is being treated as a >>> 16 bit variable. >> >> It is S_RH and the value 125 that are 16 bit. You need to "coerce" one >> of them to 32 bit in order to do 32 bit math. > > I have run into aggressively optimizing compilers that need to have > _both_ arguments cast to long to reliably do the calculation right, in > spite of the C standard. > > Saying 125L * (long)(S_RH) only uses up a few more characters, doesn't > change what a correct compiler will do, and saves your code from the edge > cases where a compiler _doesn't_ do its job.
That is the sort of bridge that I will cross when I come to it - if a compiler gets its fundamental arithmetic operations wrong it is not to be trusted for anything else whatsoever. May as well use assembler 'cause I'm going to have to check each instruction anyway. Having said that I have never used a PIC compiler, and I gather they are all notoriously crap. So you have a point :) Everything I do now seems to be ARM and gcc luckily. In the past I used AVR which also has a nice gcc solution. -- John Devereux
On 04/08/11 08:23, John Devereux wrote:
> Tim<tim@seemywebsite.please> writes: > >> On Wed, 03 Aug 2011 08:08:12 +0100, John Devereux wrote: >> >>> Dennis<user@example.net> writes: >>> > > [...] > > >>>> >>>> volatile unsigned int S_RH; //raw serial rh value, 16 bits >> volatile >>>> unsigned long RH; //RH value, 32 bits >>>> >>>> #pragma code // declare executable instructions void main (void) >>>> { >>>> >>>> //load test serial data >>>> S_RH = 25424; >>>> >>>> RH = 125*(S_RH); //65536; >>> >>> This only converts to a long *after* the 125xS_RH has been performed. So >>> it is overflowing before being assigned to RH. Try >>> >>> RH = 125L * S_RH; >>> >>> The L specifies a "long literal" value, this forces the entire >>> calculation to be done in "long". >>> >>> Other ways would be: >>> >>> RH = 125 * (long)S_RH; >>> >>> or >>> >>> RH = S_RH; >>> RH *= 125; >>> >>>> Even with the calculation reduced to 125 x S_RH to limit the size of >>>> the result I can see that the result gets clipped with RH register >>>> containing 0x00007E10 after the above calculation. The correct value >>>> which would be 0X00307E10. So it appears the RH is being treated as a >>>> 16 bit variable. >>> >>> It is S_RH and the value 125 that are 16 bit. You need to "coerce" one >>> of them to 32 bit in order to do 32 bit math. >> >> I have run into aggressively optimizing compilers that need to have >> _both_ arguments cast to long to reliably do the calculation right, in >> spite of the C standard. >> >> Saying 125L * (long)(S_RH) only uses up a few more characters, doesn't >> change what a correct compiler will do, and saves your code from the edge >> cases where a compiler _doesn't_ do its job. >
I would also say that "125 * (long)(S_RH)" is clearer, and therefore a better choice. Even better, however, is "125 * (uint32_t)(S_RH)", since that makes it entirely clear that you want 32-bit arithmetic here. The use of "int", "long", etc., should be deprecated in most embedded code. Compilers (and their libraries) often have faster code for unsigned multiplication or division, so it is worth making sure you use unsigned explicitly unless you really need signed arithmetic. It is conceivable that a compiler will generate better code in this case - it may note that "125" fits in 8 bits, and make a call to an 8x32 bit multiply routine. Sometimes compilers generate different code even when the meaning (according to the C standards) are identical.
> That is the sort of bridge that I will cross when I come to it - if a > compiler gets its fundamental arithmetic operations wrong it is not to > be trusted for anything else whatsoever. May as well use assembler > 'cause I'm going to have to check each instruction anyway. > > Having said that I have never used a PIC compiler, and I gather they are > all notoriously crap. So you have a point :) > > Everything I do now seems to be ARM and gcc luckily. In the past I used > AVR which also has a nice gcc solution. >
David Brown <david.brown@removethis.hesbynett.no> writes:

> On 04/08/11 08:23, John Devereux wrote: >> Tim<tim@seemywebsite.please> writes: >> >>> On Wed, 03 Aug 2011 08:08:12 +0100, John Devereux wrote: >>> >>>> Dennis<user@example.net> writes: >>>> >> >> [...] >> >> >>>>> >>>>> volatile unsigned int S_RH; //raw serial rh value, 16 bits >>> volatile >>>>> unsigned long RH; //RH value, 32 bits >>>>> >>>>> #pragma code // declare executable instructions void main (void) >>>>> { >>>>> >>>>> //load test serial data >>>>> S_RH = 25424; >>>>> >>>>> RH = 125*(S_RH); //65536; >>>> >>>> This only converts to a long *after* the 125xS_RH has been performed. So >>>> it is overflowing before being assigned to RH. Try >>>> >>>> RH = 125L * S_RH; >>>> >>>> The L specifies a "long literal" value, this forces the entire >>>> calculation to be done in "long". >>>> >>>> Other ways would be: >>>> >>>> RH = 125 * (long)S_RH; >>>> >>>> or >>>> >>>> RH = S_RH; >>>> RH *= 125; >>>> >>>>> Even with the calculation reduced to 125 x S_RH to limit the size of >>>>> the result I can see that the result gets clipped with RH register >>>>> containing 0x00007E10 after the above calculation. The correct value >>>>> which would be 0X00307E10. So it appears the RH is being treated as a >>>>> 16 bit variable. >>>> >>>> It is S_RH and the value 125 that are 16 bit. You need to "coerce" one >>>> of them to 32 bit in order to do 32 bit math. >>> >>> I have run into aggressively optimizing compilers that need to have >>> _both_ arguments cast to long to reliably do the calculation right, in >>> spite of the C standard. >>> >>> Saying 125L * (long)(S_RH) only uses up a few more characters, doesn't >>> change what a correct compiler will do, and saves your code from the edge >>> cases where a compiler _doesn't_ do its job. >> > > I would also say that "125 * (long)(S_RH)" is clearer, and therefore a > better choice. Even better, however, is "125 * (uint32_t)(S_RH)", > since that makes it entirely clear that you want 32-bit arithmetic > here.
not RH = (uint32_t)((uint32_t)125 * (uint32_t)(S_RH)) ...just to be really sure? :)
> The use of "int", "long", etc., should be deprecated in most > embedded code.
I know I'm supposed to do it, but I find uint32_t et al ugly. I did have a phase of using them, I think I ran into trouble when my code had to interface to newlib. Can't remember now, anyway I went back to shorts, ints and longs as god intended - and the rules of C. My rationale is that it is only things like hardware accesses that require precise widths, and this code will be inherently non-portable anyway (we are talking microcontroller contexts here).
> Compilers (and their libraries) often have faster code for unsigned > multiplication or division, so it is worth making sure you use > unsigned explicitly unless you really need signed arithmetic. > > It is conceivable that a compiler will generate better code in this > case - it may note that "125" fits in 8 bits, and make a call to an > 8x32 bit multiply routine. Sometimes compilers generate different > code even when the meaning (according to the C standards) are > identical. >
[...] -- John Devereux
On 2011-08-03, Dennis <user@example.net> wrote:
> I'm trying to interface a SHT21 humidity sensor to a PIC (and learning > C18 along the way, MPLAB, C18, PIC18LF44K22).
[...]
> So the formula will become: > > RH = (100 x 125 x S_RH)/2^16 - 600
Keep in mind that C18 does not do integer promotions by default, so an expression like "100 * 125" will overflow. You can enable integer promotions if you want to have standard C behavior. -- John W. Temples, III