C18 calculation question - novice...

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.

Reply to
Dennis
Loading thread data ...

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;

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
Reply to
John Devereux

I meant to add you should google "usual conversions" for all the rules here.

--

John Devereux
Reply to
John Devereux
[.........]

[...]

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.

Reply to
Dennis

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
 Click to see the full signature
Reply to
Tim

volatile

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
 Click to see the full signature
Reply to
Tim

[...]

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
Reply to
John Devereux

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.

Reply to
David Brown

not

RH = (uint32_t)((uint32_t)125 * (uint32_t)(S_RH))

...just to be really sure? :)

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).

[...]
--

John Devereux
Reply to
John Devereux

[...]

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
Reply to
John Temples

No - you want enough to be clear to the reader, and to the compiler, without drowning the code. (I know you used a smiley, but some people really write code like that.)

No, there are lots of things that need precise sizes. Most of the time you are happy with "big enough", but you sometimes want to avoid "too big". C doesn't have support for ranged types (like Pascal, Ada, etc.) where you can say what you actually want (helping the writer and other readers, and helping the compiler spot mistakes) - sized types are the nearest it has.

It is in fact in portable code that sized types are most important. Classic cases are code written for 32-bit systems with the assumption that "int" is 32-bit, and then using the same code on 8-bit or 16-bit systems with 16-bit ints. You can well argue that this is a case of making unwarranted assumptions - after all, if you want to be sure of

32-bit with standard C types, you must use "long int" and not "int". But people make such assumptions - being explicit with your types avoids that.

It also avoids problems with the irritating semi-C compilers that have things like 16-bit "long ints".

Some of this is a matter of opinion and style. You may think "uint32_t" is ugly, but personally I hate it when people use "short" or "long" as a type. These are /adjectives/, not types. The types are "short int" and "long int" respectively. It's almost as bad as illiterate code authors who use "char" when they want a number. "char" is short for "character"

- a letter, such as an element in a string. If you want a small number, use "int8_t" or "uint8_t" - that tells people exactly what you are talking about. IMHO, of course.

Reply to
David Brown

A compiler that does not do standard C integer promotions, or treat "100" and "125" as integers, has no business calling itself a "C compiler".

Reply to
David Brown

Thanks John.

Reply to
Dennis

After 15 years of labouring with assembly before moving to C I hope so! :)

Reply to
Dennis

ElectronDepot website is not affiliated with any of the manufacturers or service providers discussed here. All logos and trade names are the property of their respective owners.