• AVR Freaks

AnsweredHot!16LF18345 itoa returning unexpected values.

Author
phuetson
New Member
  • Total Posts : 2
  • Reward points : 0
  • Joined: 2020/01/03 07:08:34
  • Location: Tennessee
  • Status: offline
2020/01/07 06:49:00 (permalink)
0

16LF18345 itoa returning unexpected values.

I am using a 16LF18345 to read values from a SHT35 via I2C, convert the values from uint16_t to float, from float to int then attempting to cast the int into a char* using itoa, this code snippet is where the issue lies. 
 

    uns16 temp_T; // Raw uint16_t from I2C read
    float final_F;    // Temp returned after converting from Uint16_t
    char *out_F;   // Char pointer for LCD_puts()
    int final_FI;    // final 2 digit int from float 
    
    while (1)
    {
        
        i2c_Write( 0x44, 0x2C06 ); // Top SHT35, Clock Stretch, High Repeat
        temp_T = i2c_Read ( 0x44 );
    
        final_F = getFahrenheit( temp_T );
        final_FI = (int)final_F;
        itoa(final_FI, out_F, 10);

 
every varaible returns values as expected up until itoa in where (out_F) is not the ascii value for int 67, I have confirmed with a logic analyzer and by debugging the micro and watching variable values, the values below are what is returned by the debugger. 
 

 
am i using iota wrong? I would like to avoid sprintf if possible due to the size it adds to the project. 
#1
1and0
Access is Denied
  • Total Posts : 11337
  • Reward points : 0
  • Joined: 2007/05/06 12:03:20
  • Location: Harry's Gray Matter
  • Status: offline
Re: 16LF18345 itoa returning unexpected values. 2020/01/07 08:48:17 (permalink) ☼ Best Answerby phuetson 2020/01/07 09:18:08
+2 (2)
phuetson
I am using a 16LF18345 to read values from a SHT35 via I2C, convert the values from uint16_t to float, from float to int then attempting to cast the int into a char* using itoa, this code snippet is where the issue lies. 

 
    char *out_F;   // Char pointer for LCD_puts()
    int final_FI;    // final 2 digit int from float 
    
        itoa(final_FI, out_F, 10);

every varaible returns values as expected up until itoa in where (out_F) is not the ascii value for int 67, I have confirmed with a logic analyzer and by debugging the micro and watching variable values, the values below are what is returned by the debugger. 
 

 
am i using iota wrong? I would like to avoid sprintf if possible due to the size it adds to the project. 



The first two parameters of your itoa() call are swapped.
#2
mbrowning
USNA79
  • Total Posts : 1811
  • Reward points : 0
  • Joined: 2005/03/16 14:32:56
  • Location: Melbourne, FL
  • Status: online
Re: 16LF18345 itoa returning unexpected values. 2020/01/07 09:01:00 (permalink) ☄ Helpfulby phuetson 2020/01/07 09:11:16
+4 (4)
Also, "char *out_F" allocates space for a pointer, not data. You should change the declaration to "char out_F[7]" to allocate space for an ascii string representation of any conceivable 16b (int) number.
 
#3
1and0
Access is Denied
  • Total Posts : 11337
  • Reward points : 0
  • Joined: 2007/05/06 12:03:20
  • Location: Harry's Gray Matter
  • Status: offline
Re: 16LF18345 itoa returning unexpected values. 2020/01/07 09:08:13 (permalink) ☄ Helpfulby phuetson 2020/01/07 09:11:13
+1 (1)
mbrowning
Also, "char *out_F" allocates space for a pointer, not data. You should change the declaration to "char out_F[7]" to allocate space for an ascii string representation of any conceivable 16b (int) number.

I noticed that too, but what is the + box next to "out_F" in the Watches window?
 
Edit: Now I see the + expands to show the content of *out_F.  At first glance, I was thinking it's an array. ;)
 
post edited by 1and0 - 2020/01/07 09:24:57
#4
phuetson
New Member
  • Total Posts : 2
  • Reward points : 0
  • Joined: 2020/01/03 07:08:34
  • Location: Tennessee
  • Status: offline
Re: 16LF18345 itoa returning unexpected values. 2020/01/07 09:15:42 (permalink)
0
1and0, mbrowning thanks for the help you both solved it!
 
I feel like an idiot, I just assumed itoa() took its parameters the same as itoa for c++ (int, char*, base), not the case I see. 
#5
1and0
Access is Denied
  • Total Posts : 11337
  • Reward points : 0
  • Joined: 2007/05/06 12:03:20
  • Location: Harry's Gray Matter
  • Status: offline
Re: 16LF18345 itoa returning unexpected values. 2020/01/07 09:22:59 (permalink)
+3 (3)
phuetson
I feel like an idiot, I just assumed itoa() took its parameters the same as itoa for c++ (int, char*, base), not the case I see. 

itoa() and its siblings are NOT standard functions, so the order and number of parameters vary from compiler to compilers.
#6
markm23
Starting Member
  • Total Posts : 28
  • Reward points : 0
  • Joined: 2019/11/21 12:51:59
  • Location: 0
  • Status: offline
Re: 16LF18345 itoa returning unexpected values. 2020/01/14 12:29:44 (permalink)
0
Please tell me which C compiler you are using and which library contains itoa? I am using XC8, version 2.10, free license, and the documentation with it does not list itoa in any library. (I searched the whole PDF:
 
MPLAB® XC8 C Compiler
User’s Guide for PIC® MCU
 
for "itoa", no hits.)
 
... I used snprint, but it not only takes a lot of space, but gives me a stack overflow warning, saying the stack may go 18 levels deep. (It's called from 4 or 5 levels deep, in a PIC16F15356 with a 16-level stack.) This warning might be bogus - there have been no stack overflows - but I can't be sure that some particular value won't send it all the way down
#7
NKurzman
A Guy on the Net
  • Total Posts : 18976
  • Reward points : 0
  • Joined: 2008/01/16 19:33:48
  • Location: 0
  • Status: online
Re: 16LF18345 itoa returning unexpected values. 2020/01/14 12:33:45 (permalink)
+1 (1)
if C90 mode is enabled, you may have itoa().  I think it is no longer in C99 mode.
#8
1and0
Access is Denied
  • Total Posts : 11337
  • Reward points : 0
  • Joined: 2007/05/06 12:03:20
  • Location: Harry's Gray Matter
  • Status: offline
Re: 16LF18345 itoa returning unexpected values. 2020/01/14 12:50:47 (permalink)
0
For C99 mode, go to your project properties and select to link in the C90 library.
 
#9
ric
Super Member
  • Total Posts : 28671
  • Reward points : 0
  • Joined: 2003/11/07 12:41:26
  • Location: Australia, Melbourne
  • Status: offline
Re: 16LF18345 itoa returning unexpected values. 2020/01/14 16:40:49 (permalink)
0
The C90 library comes with the source code.
 
C:\Program Files (x86)\Microchip\xc8\v2.10\pic\sources\c90\common\itoa.c
 
Here it is:
#include    <stdlib.h>
    
char *
itoa(char * buf, int val, int base)
{
    char *    cp = buf;

    if(val < 0) {
        *buf++ = '-';
        val = -val;
    }
    utoa(buf, val, base);
    return cp;
}

char *
utoa(char * buf, unsigned val, int base)
{
    unsigned    v;
    char        c;

    v = val;
    do {
        v /= base;
        buf++;
    } while(v != 0);
    *buf-- = 0;
    do {
        c = val % base;
        val /= base;
        if(c >= 10)
            c += 'A'-'0'-10;
        c += '0';
        *buf-- = c;
    } while(val != 0);
    return ++buf;
}


I also post at: PicForum
Links to useful PIC information: http://picforum.ric323.co...opic.php?f=59&t=15
NEW USERS: Posting images, links and code - workaround for restrictions.
To get a useful answer, always state which PIC you are using!
#10
markm23
Starting Member
  • Total Posts : 28
  • Reward points : 0
  • Joined: 2019/11/21 12:51:59
  • Location: 0
  • Status: offline
Re: 16LF18345 itoa returning unexpected values. 2020/01/15 10:03:03 (permalink)
0
Thank you. I have too much code written for C99 to consider switching to C90 now, but I see no reason Ric's code won't work in C99. Any idea why Microchip dropped itoa() and uitoa() from the C99 library?
 
It is going to be slow. PIC16's don't have multiply or divide instructions, so you have to do this in software, and that gets expensive. A dumb compiler would give three divisions per digit - one in the first loop to find out how many digits, and two in the second loop (% also requires a division). A very good compiler or an assembly hand-coder would combine the % and / operations into one operation giving both results. 
 
I found a different algorithm by googling, and it seems to work:
 
/*** Convert Unsigned Integer to Decimal String ------------------------------*/

void uitoa(uint16_t Value, char *Buffer)
/* Radix fixed at 10. Buffer dimension must be at least 6 (5 digits and '\0').
 * Suppresses leading zeroes. Conversion proceeds left to right.
 * Probably pretty slow for a PIC16. Possible optimization: replace
 * "Digit = Value/Divisor;" with multiply by 2^32/10000.
 * Adapted from code on the Microchip forums, attributed to Microsoft. */
 {
  uint8_t i;
  uint16_t Digit;
  uint16_t Divisor;
  bool Printed = false;
 
  if(Value) // Check that it's not zero
  {
   for(i = 0, Divisor = 10000; i < 5u; i++)
   {
    Digit = Value/Divisor; // Get the first/next digit
                                    //(truncate result to whole number)
    if(Digit || Printed) // Don't "print" leading zeroes
    {
     *Buffer++ = '0' + Digit; // Add digit to the buffer
     Value -= Digit*Divisor; // Remove value of the digit
     Printed = true; // Flag to end leading-zero suppression
    }
    Divisor /= 10; // Adjust divisor for the next digit
   }
  }
  else // Handle the case of Value == 0 initially - output "0"
  {
   *Buffer++ = '0';
  }
 
  *Buffer = '\0'; // Terminate the string
 }

 
This generates the digits left to right, while Ric's goes right to left. One advantage of the right-to-left approach is that it only loops for the actual number of digits, while the left-to-right method loops (and divides, twice) for the maximum possible number of digits. It also has another branch (if) in the middle of the loop. If the right-to-left code is optimized to combine '%' and '/' into one division with remainder, it will always run faster, and be much faster for small numbers. 
 
The run-time doesn't matter where I am using this, so I didn't try any optimization. If speed mattered, the left-to-right method might be improved by using an array to look up Divisor instead of calculating it by dividing:
 
static uint16_t Divisor[5] = {10000, 1000, 100, 10, 1};
uint16_t *divPointer = Divisor;
...
           Digit = Value / *divPointer++;
 
In the right-to-left method, you could replace the divisions in the first loop with looking up the Value in the array. (Both of these optimizations are only for 8-bitters and chips without a divide instruction; if divide is just one instruction, it's about as fast as stepping through the array.) 
 
#11
NKurzman
A Guy on the Net
  • Total Posts : 18976
  • Reward points : 0
  • Joined: 2008/01/16 19:33:48
  • Location: 0
  • Status: online
Re: 16LF18345 itoa returning unexpected values. 2020/01/15 10:22:43 (permalink)
0
"It is going to be slow. PIC16's don't have multiply or divide instructions"  And itoa() could not do it any differently.  is has the same instruction set.  if you want to go crazy you can divide with shifts and subtractions if you are always dividing by 10.
 
div() will give you the quotient and remainder.  You would need to see how efficiently it was coded in XC8.
 
left to right or right to left can also matter if you use the function to send directly without buffering it.
#12
markm23
Starting Member
  • Total Posts : 28
  • Reward points : 0
  • Joined: 2019/11/21 12:51:59
  • Location: 0
  • Status: offline
Re: 16LF18345 itoa returning unexpected values. 2020/01/15 11:44:22 (permalink)
0
Yes, (u)itoa or printf, it always comes down to the same loop, with minor variations. If I had to write a printf function from scratch, I'd start by writing (x)toa and ato(x) functions for each integer size and radix that's supported, and call them when needed. (The _hard_ part of the job might be the non-numeric formatting...) 
 
Short of trivial cases like binary to hexadecimal, base conversions always require shifting and subtracting or multiplying, several times. IIRC, decimal to binary is done with shift and multiply. Binary to decimal requires division (unless you do the arithmetic in decimal!), and division tends to be slower and is more tricky to optimize. If you have a divide instruction, the repetition is hidden in the implementation of the instruction, but in Pentiums and other IC's that were built to do arithmetic well, often it's been optimized in hardware to where the whole 16-step division runs as fast as the operands can be fetched from RAM. In a PIC 16 without division hardware, a 16 bit divide probably requires hundreds of instruction cycles, and the clock is slow enough that someone might notice. 
 
And a conversion function with software division could be optimized by replacing the generic division function by one optimized to divide only by 0b1010. That might require under two dozen instruction cycles per division. (The optimization for multiply by ten is very easy: shift one bit, shift two bits, and add those two results.) 
 
But the most effective optimization is to minimize the number of software divides or multiplies. You have to do this once for each decimal digit, but you can code to avoid doing it twice, you might be able to code to avoid doing it for leading zeroes, and you can replace part of the arithmetic with pre-computed numbers. 
#13
pcbbc
Super Member
  • Total Posts : 1703
  • Reward points : 0
  • Joined: 2014/03/27 07:04:41
  • Location: 0
  • Status: offline
Re: 16LF18345 itoa returning unexpected values. 2020/01/15 12:18:24 (permalink)
0
Binary to decimal requires division (unless you do the arithmetic in decimal!)

You forget that divide by a constant can be replaced by a double precision multiply by a magic number (and sometimes a shift) in almost all cases.
So if you have a hardware multiply unit, then that is probably your best bet. Of course most 8-bit PICs don’t even have that.
 
If you want the most space efficient method it’s probably double dabble. That can divide by a given radix to yield the quotient and remainder. Just repeat until zero and reverse the results. It’s not at all efficient if your out to minimise execution time though.
post edited by pcbbc - 2020/01/15 12:25:16
#14
pcbbc
Super Member
  • Total Posts : 1703
  • Reward points : 0
  • Joined: 2014/03/27 07:04:41
  • Location: 0
  • Status: offline
Re: 16LF18345 itoa returning unexpected values. 2020/01/15 15:38:16 (permalink)
0
void uint16toa(char * buf, uint16_t val)
{
    uint8_t i = 0;
    char temp[5];
    
    // do: while digits remaining
    do {
        uint8_t d = 0;
        uint8_t n = 16; //n = number of bits in val
        // in place divide val by 10, remainder to d
        do {
            // double...
            d <<= 1;
            if (val & 0x8000) d |= 1;
            val <<= 1;
            // dabble... (10 = base value, other bases work too)
            if (d >= 10) {
                d -= 10;
                val |= 1;
            }
        } while (--n);
        // save digit
        temp[i++] = '0' + d;
    } while (val);

    // output result
    do {
        *buf++ = temp[--i];
    } while (i);

    *buf = 0;
}

Very easy to:
Extend to other bases - just change the two constant 10 values to the base you require.  For bases less than 10 you will need some more temp space as well.
Extend to uint24 or uint32 - change n to the number of bits (i.e. 24 or 32) and increase the temp space accordingly.
#15
ric
Super Member
  • Total Posts : 28671
  • Reward points : 0
  • Joined: 2003/11/07 12:41:26
  • Location: Australia, Melbourne
  • Status: offline
Re: 16LF18345 itoa returning unexpected values. 2020/01/15 15:54:09 (permalink)
0
That's not handling the digit values greater than 9, so that 10 should give 'A', 11 gives 'B' and so on.
 
Where you have
        temp[i++] = '0' + d;

the library code has
        if(c >= 10)
            c += 'A'-'0'-10;
        c += '0';

post edited by ric - 2020/01/15 15:55:56

I also post at: PicForum
Links to useful PIC information: http://picforum.ric323.co...opic.php?f=59&t=15
NEW USERS: Posting images, links and code - workaround for restrictions.
To get a useful answer, always state which PIC you are using!
#16
pcbbc
Super Member
  • Total Posts : 1703
  • Reward points : 0
  • Joined: 2014/03/27 07:04:41
  • Location: 0
  • Status: offline
Re: 16LF18345 itoa returning unexpected values. 2020/01/15 23:25:36 (permalink)
0
ricThat's not handling the digit values greater than 9, so that 10 should give 'A', 11 gives 'B' and so on.

It doesn’t have to, it’s hard coded to base 10.
You are of course right that this is another thing that needs adding if you extend/modify it for bases over 10.
#17
ric
Super Member
  • Total Posts : 28671
  • Reward points : 0
  • Joined: 2003/11/07 12:41:26
  • Location: Australia, Melbourne
  • Status: offline
Re: 16LF18345 itoa returning unexpected values. 2020/01/15 23:42:18 (permalink)
0
Yes, I meant if you allow bases bigger than 10.
 

I also post at: PicForum
Links to useful PIC information: http://picforum.ric323.co...opic.php?f=59&t=15
NEW USERS: Posting images, links and code - workaround for restrictions.
To get a useful answer, always state which PIC you are using!
#18
NKurzman
A Guy on the Net
  • Total Posts : 18976
  • Reward points : 0
  • Joined: 2008/01/16 19:33:48
  • Location: 0
  • Status: online
Re: 16LF18345 itoa returning unexpected values. 2020/01/16 06:27:10 (permalink)
0
Hard coded can give smaller faster code.
Flexibility has its cost.
Which is best it’s up to the engineer to choose for any given project.
#19
Jump to:
© 2020 APG vNext Commercial Version 4.5