views:

263

answers:

5

I am working a C firmware program for an embedded device. I want to send an array of hex char values over the serial port. Is there a simple way to convert a value to ASCII hex?

For example if the array contains 0xFF, I want to send out the ASCII string "FF", or for a hex value of 0x3B I want to send out "3B".

How is this typically done?

I already have the serial send functions in place so that I can do this...

char msg[] = "Send this message";
SendString(msg);

and the SendString function calls this function for each element in the passed array:

// This function sends out a single character over the UART
int SendU( int c)
{
    while(U1STAbits.UTXBF);
    U1TXREG = c;
    return c;
}

I am looking for a function that will allow me to do this...

char HexArray[5] = {0x4D, 0xFF, 0xE3, 0xAA, 0xC4};
SendHexArray(HexArray);

//Output "4D, FF, E3, AA, C4"
+16  A: 

Write the number to a string using sprintf and then use your existing SendString function to send that over the UART. You can, of course, do this one number at a time:

char num_str[3];
sprintf( num_str, "%02X", 0xFF );
SendString(num_str);

%02X is a format string for the printf family of functions, it says pad the element with 0s until width 2 and format the element as a hexadecimal number.

The 02 part ensures that when you want to print 0x0F that you get 0F instead of just F in the output stream. If you use a lowercase x you'll get lowercase characters (e.g. ff instead of FF).

Mark E
Can you explain the %02X part?
Jordan S
`%02X` write the `int` value in hexadecimal representation using uppercase letters (`X`), using at least 2 spaces and padding with '0' on the left.
pmg
This is fine, but check the link map and see if you still have room for the rest of your application in ROM if this is the first call to any of the huge family of stdio functions. Even if you are using them now, check your link map and see if replacing them might save you room for new features next month.
RBerteig
I agree with RBerteig. Depending in your meaning of embedded, sprintf is like taking a nuclear bomb to crack a nut. sprintf might add 10k bytes to the size of your executable image.
Andrew Bainbridge
It might, but it may not. Especially if you are already using standard formatted I/O; you may have already taken that overhead hit. Of greater concern often is the amount of stack space required by formatted I/O operations; about 4K would not be unusual.
Clifford
+2  A: 

sprintf will do it.

sprintf (dest, "%X", src);

Nathon
The `%X` will output a variable number of characters, because it suppresses leading zeros. You want `%02X` to meet the OP's spec.
RBerteig
@RBerteig, I don't see anything in the question that explicitly requires 2-byte strings. (Or 3 with the null terminator.)
Nathon
+5  A: 

I'd start with an array lookup.

char *asciihex[] = {
    "00", "01", "02", ..., "0F",
    "10", "11", "12", ..., "1F",
    ...,
    "F0", "F1", "F2", ..., "FF"
};

and then simply look it up ...

SendString(asciihex[val]);

Edit

Incorporating Dysaster's nibbles idea:

void SendString(const char *msg) {
    static const char nibble[] = {'0', '1', '2', ..., 'F'};
    while (*msg) {
        /* cast to unsigned char before bit operations (thanks RBerteig) */
        SendU(nibble[(((unsigned char)*msg)&0xf0)>>4]); /* mask 4 top bits too, in case CHAR_BIT > 8 */
        SendU(nibble[((unsigned char)*msg)&0x0f]);
        msg++;
    }
}
pmg
I don't know why this was down voted. +1. This would be my accepted answer. It is very straight forward and super fast. Further, this table can be implemented in a small special partition of the cheap flash ROM
mmonem
Dysaster
Maybe the `...` were the reason for the downvote. I like the nibbles idea.
pmg
With Dysaster's nibbles approach you may even tranform this into a switch statement. Then the compiler would decide if it should be realized with a table lookup, with a conditional store or a conditional jump. Usually the compiler is better with this than a first optimizing approach by a programmer.
Jens Gustedt
I choose this as the solution because I was not using the stdio library anywhere else in my code and to use it added over four times as much program memory as this solution. I am not sure which is faster but I would guess this solution is faster as well. Thanks guys
Jordan S
unless the code needs to be out and out fast surely sprintf is the way to code - being 1 line of code....
Tony Lambert
@Tony, in embedded systems stdio in general and `printf` specifically is something generally avoided because of the huge amount of code that touching any part of stdio drags in. Even `sprintf` brings in nearly all of the `FILE *` machinery because it is often implemented by making a fake `FILE *` where the file's buffer is the destination buffer. If he isn't already using stdio, I understand avoiding it just to get a cheap conversion from binary to hex.
RBerteig
I usually write the nybble table as `"0123456890ABCDEF"`. Just be sure to notice that I accidentally left out a character there *before* cutting and pasting blindly ;-) It gets embarrassing otherwise.
RBerteig
Incidentally, if `char` is signed, then `(*msg)>>4` might be negative...
RBerteig
Nicely spotted RBerteig! Thanks, code updated.
pmg
@pmg, I got bit by that one once or twice. It took a long time to recognize what was happening.... so I've learned to head it off at the pass.
RBerteig
You might want to make nibble[] "const" so that it can be ROM'ed. I believe in the current implementation the 16 byte sequence will be copied from ROM into read/write (usually .data) space in the C runtime startup code.
Dan
Thanks Dan. Code updated.
pmg
Why are you using a lookup table? ASCII values are usefully in order, so you can just do `ascii = nybble < 10 ? nybble + '0' : nybble + 'A' - 10;`.
Brooks Moses
And EBCDIC is in order for 'A'-'F' too :) But I like my code to run on the DS9K ( http://dspace.dial.pipex.com/town/green/gfd34/art/ )
pmg
@RBerteig When I used to code firmware sometimes the rom was only 256 bytes, if you were lucky 4k. C wasn't an option. I'm surprised that a little Std Library is an issue. I'm sure a printf ("hello world\n"); comes in at under 10k?
Tony Lambert
@Tony, it depends strongly on the tool chain, and specifically its C run time library. On one implementation I used, the first call to `sprintf()` brought along most of `fopen()`, `malloc()`, `free()`, and all the support machinery for a `FILE*` and its buffer, none of which had been in the map before. I especially objected to `malloc()`, because I wasn't using it and had no heap space allocated for it. This happened because an implementer noticed that if you pretended that the destination string was the buffer of a `FILE`...
RBerteig
A: 

If your compiler supports it, you can use itoa. Otherwise, I'd use sprintf as in Nathan's & Mark's answers. If itoa is supported and performance is an issue, try some testing to determine which is faster (past experience leads me to expect itoa to be faster, but YMMV).

GreenMatt
I was going to say "itoa doesn't output in base 16 eg. hex." but I see that it does these days.... I must be getting old. I'm sure it didn't use to.
Tony Lambert
+8  A: 

The classic trick from the 8-bit micro in assembly language era is to break the conversion of a nybble into two segments. The value from 0 to 9 and the value from 10 to 15. Then simple arithmetic saves the 16-byte lookup table.

void SendDigit(int c) {
    c &= 0x0f;
    c += (c <= 9) ? '0' : 'A'-10;
    SendU(c);
}

void SendArray(const unsigned char *msg, size_t len) {
    while (len--) {
        unsigned char c = *msg++;
        SendDigit(c>>4);
        SendDigit(c);
    }
}

A couple of side notes are in order. First, this works because the digits and letters are each in contiguous spans in ASCII. If you are unfortunate enough to want EBCDIC, this still works as the letters 'A' through 'F' are contiguous there as well (but the rest of the alphabet is broken into three spans).

Second, I've changed the signature of SendArray(). My version is careful to make the buffer be unsigned, which is generally safer when planning to promote the bytes to some larger integral type to do arithmetic. If they are signed, then code like nibble[(*msg)>>4] might try to use a negative index into the array and the result won't be at all useful.

Finally, I added a length parameter. For a general binary dump, you probably don't have any byte value that makes sense to use as an end sentinel. For that, a count is much more effective.

Edit: Fixed a bug: for digits over 10, the arithmetic is c + 'A' - 10, not c + 'A'. Thanks to Brooks Moses for catching that.

RBerteig
This is the solution I'd chose, supposed no sprintf +1
ziggystar
I am actually using a 16 bit micro (PIC24F series) would that change your decision?
Jordan S
Actually, you need that to be `'A' - 10`, not `'A'`.
Brooks Moses
@Brooks, that is why test cases are important, especially for the trivial code!
RBerteig
@Jordan S, This or the digit table lookup are both good candidates. The lookup is easier to get right, assuming you don't typo the table, and is likely to be clearer to a code reviewer (probably you, five years later). Otherwise, compile both, and benchmark against size or execution time depending on what constraints you are living with.
RBerteig
@starblue, Thanks for noticing that I still hadn't passed this fragment through a compiler. It looks better now ;-)
RBerteig