6
\$\begingroup\$

I'm writing code for a PIC18F46K22 using the C18 compiler. I want to write the value of an integer \$n\$ in ASCII over the USART to my PC.

For \$n<10\$, it's easy:

Write1USART(n + 0x30); // 0x30 = '0'

This would work for \$10\le{}n\le100\$:

Write1USART((n/10) + 0x30);
Write1USART((n%10) + 0x30);

But this isn't the fastest possible way, probably.

So is there a built-in function or a function somewhere out there that I could just use instead of rolling my own?

\$\endgroup\$
  • \$\begingroup\$ Do you mean something that could reach the full size of an int? \$\endgroup\$ – Kortuk Apr 13 '13 at 15:08
  • \$\begingroup\$ @Kortuk yes, or even better, an unsigned long. \$\endgroup\$ – user17592 Apr 13 '13 at 15:09
  • 1
    \$\begingroup\$ Just a suggestion: instead of n + 0x30 with a comment that 0x30 = '0', use n + '0'. Both C and C++ require the digits 0 - 9 to have adjacent increasing values, so n + '0' always works, and is clearer. \$\endgroup\$ – Pete Becker Apr 14 '13 at 13:05
9
\$\begingroup\$

The C18 compiler supports the number-to-ascii family of standard C functions in stdlib.h: itoa(), ltoa(), ultoa() et cetera.

Depending on which compiler / stdlib.h you have, the relevant function prototype would be:

extern char *   itoa(char * buf, int val, int base); // signed int
extern char *   utoa(char * buf, unsigned val, int base); // unsigned int

or

extern char *   itoa(char * buf, int val); // signed int
extern char *   utoa(char * buf, unsigned val); // unsigned int

If you were looking for a relatively robust built-in "standard" C way for converting your numbers to ASCII strings, these xtoa() functions would be the ones to use.

If on the other hand you are constrained to squeeze a few extra cycles or bytes of memory out of the final code, then several of the other answers to your question are the way to go.

\$\endgroup\$
  • \$\begingroup\$ Those functions aren't standard C. itoa might have originated in ancient Borland C, but it never made it into ISO C. Doesn't matter though; if they are there, use them. \$\endgroup\$ – Kaz Apr 14 '13 at 0:48
3
\$\begingroup\$

You could try a function that uses brute-force method to convert to string. Below function does not use modulus operator nor multiplication. It returns a string.

/*
 *  Create a function that will return a string.
 *  It accepts 'inputValue' that is up to 255, but you can make it an int or longint...
 *  ...after you make some edits in the function.
 *  inputValue:   5       7       6
 *  Digits:      1st     2nd     3rd
 */
unsigned char* returnString(unsigned char inputValue)
{
    static unsigned char processedString[4]; // Return a string of 3 digits.
    unsigned char firstDigitCounter = 0; // Brute-force counter for first digit.
    unsigned char secondDigitCounter = 0; // Brute-force counter for second digit.
    if (inputValue > 99) // If we have a 3 digit number,
    {
        while (inputValue > 99) // Until our number is 3 digits, i.e. bigger than 99,
        {
            inputValue -= 100; // Subtract 100 and..
            firstDigitCounter++; //.. increment first digit.
        }
        while (inputValue > 9) // Until our number is 3 digits, i.e. bigger than 9,
        {
            inputValue -= 10; // Subtract 10 and..
            secondDigitCounter++; //.. increment second digit.
        }

        // Now, we have left the 'inputValue' as a single digit.

        processedString[0] = firstDigitCounter + 0x30; // First digit
        processedString[1] = secondDigitCounter + 0x30; // Second digit
        processedString[2] = inputValue + 0x30; // Third digit
        processedString[3] = '\0'; // String terminator.
    }
    else // If we have a 2 digit number,
    {
        while (inputValue > 9) // Until our number is 3 digits, i.e. bigger than 99,
        {
            inputValue -= 10; // Subtract 10 and..
            secondDigitCounter++; //.. increment second digit.
        }
        processedString[0] = secondDigitCounter + 0x30; // Second digit
        processedString[1] = inputValue + 0x30; // Third digit
        processedString[2] = '\0'; // String terminator.
    }
    return processedString; // Return the processed string.
}

Pastebin of the above code.

\$\endgroup\$
  • \$\begingroup\$ I am stuck with a dinosaur that has only assembler, and gave up on the problem, sending data in hex. So +1 for a procedure that does not use multiplications or more importantly divisions. But it does not scale easily for 4 or 5 digit numbers. Any ideas on how to scale it to larger numbers? \$\endgroup\$ – Bobbi Bennett Apr 13 '13 at 15:05
  • \$\begingroup\$ @BobbiBennett I generally use hex on my devices, if I want the output to look pretty I let my computer do that. I have also used microcomputers that could not support multiply directly, a divide would take more then a millisecond, in that case this is going to be the only way. \$\endgroup\$ – Kortuk Apr 13 '13 at 15:17
3
\$\begingroup\$

I have used sprintf(); before. Apart from it being convenient with formatting, I'm not entirely sure if it is fast and has a small footprint. It comes with .

#include <stdio.h>
const uint8_t stringLength = 16;
char string[ stringLength ] = { 0 };

volatile uint32_t measurement = 12345;
sprintf( string , "Measured: %lu milliseconds\n" , measurement );

uint8_t charCounter = 0;
while ( ( charCounter < stringLength ) and ( string[ charCounter ] != 0x00 ) ) {
    serialByteOut( string[ charCounter ] );                         // Send a single character
    charCounter++;
}

Where measurement is a 32bit integer updated in an ISR, which I want to print and string is the output buffer. %lu indicates a long unsigned integer is to be printed and \n is a newline.

The use is largely the same as for printf(); Documentation is extensive and can easily be found on Internet on various websites: http://linux.die.net/man/3/sprintf

\$\endgroup\$
  • 2
    \$\begingroup\$ I am on this stack to learn, even if it is from my own mistakes. Why the down vote? \$\endgroup\$ – jippie Apr 13 '13 at 9:14
  • 2
    \$\begingroup\$ I normally use sprintf() as well, however it's by no means small footprint. I don't remember actual numbers, but you will notice a significant increase in code size. But silicon is cheap :-) \$\endgroup\$ – lyndon Apr 13 '13 at 14:35
3
\$\begingroup\$

I have made a function myself:

void writeInteger(unsigned long input) {
    unsigned long start = 1;
    unsigned long counter;
    while (start*10 <= input)
        start *= 10;
    for (counter = start; counter >= 1; counter /= 10)
        Write1USART(((input / counter) % 10) + 0x30);
}
\$\endgroup\$
  • \$\begingroup\$ This code is being discussed here. \$\endgroup\$ – user17592 Apr 13 '13 at 9:23
1
\$\begingroup\$

You could try this:

void writeInteger(unsigned i)
{
   if (i > 9)
     writeInteger(i/10);
   write1USART(i % 10 + '0');
}
\$\endgroup\$
  • 4
    \$\begingroup\$ This code works (in ASCII), but is unnecessarily cute in two regards: (1) using “| ‘0’” instead of “+ ‘0’” obfuscates the operation that needs to be performed (even though it works for ASCII ‘0’). (2) the recursive call is not ideal on microcontrollers, which often work with very small stack sizes. A caller might be in for a rude surprise if they end up with 9 levels of recursion piled on their stack. \$\endgroup\$ – microtherion Apr 13 '13 at 12:24
  • 1
    \$\begingroup\$ @microtherion ASCII was specified in the question, but using '0' is not 'cute', it is an accepted way of isolating character set differences. If the compiler used BCD the code would work in BCD too. It is 0x30 that only works in ASCII. Using | instead of + expresses the fact that we are setting the zone bit, not performing a magical arithmetic calculation. The recursion can't recur more than ten times unless an unsigned int has 64 bits, which takes us out of the realms of microprocessors altogether, and the ten times don't use significantly more memory than other solutions here. \$\endgroup\$ – user207421 Apr 13 '13 at 12:36
  • \$\begingroup\$ @EJP, it’s not the ‘0’ that I object to, it’s the |. The operation we’re expressing is “map a digit into a range of contiguous characters”, so + is perfectly clear for this, and it works in cases where the LSBs of the “zero” of that range are not 0 (e.g. for some of the numeric representations in Unicode). | is less clear and less general. \$\endgroup\$ – microtherion Apr 13 '13 at 23:19
  • \$\begingroup\$ As for the recursion not using “significantly” more memory, for a 32 bit unsigned, 10x recursion probably uses 60-80 bytes of RAM, depending on the address size of the microcontroller. An iterative solution uses <20 bytes. With some MCUs equipped with just 128bytes of RAM, wasting 40 bytes can be significant. \$\endgroup\$ – microtherion Apr 13 '13 at 23:24
  • 1
    \$\begingroup\$ @microtherion I suppose it should really be either + '0' or | 0x30. The recursion cost is balanced to an extent by the method's own tiny size, but limits are limits and must be observed for sure. \$\endgroup\$ – user207421 Apr 13 '13 at 23:41

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy