Reply by Dennis August 5, 20112011-08-05
On 4/08/2011 4:13 AM, Tim wrote:
> 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. >
After 15 years of labouring with assembly before moving to C I hope so! :)
Reply by Dennis August 5, 20112011-08-05
On 5/08/2011 1:21 AM, John Temples wrote:
> 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. >
Thanks John.
Reply by David Brown August 4, 20112011-08-04
On 04/08/11 19:21, John Temples wrote:
> 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. >
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 by David Brown August 4, 20112011-08-04
On 04/08/11 16:05, John Devereux wrote:
> 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? :) >
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.)
>> 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). >
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.
>> 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 by John Temples August 4, 20112011-08-04
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
Reply by John Devereux August 4, 20112011-08-04
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
Reply by David Brown August 4, 20112011-08-04
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. >
Reply by John Devereux August 4, 20112011-08-04
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
Reply by Tim August 3, 20112011-08-03
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
Reply by Tim August 3, 20112011-08-03
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