Convert raw HEX to char

Discussion in 'Programmer's Corner' started by FBorges22, Dec 18, 2011.

  1. FBorges22

    Thread Starter Active Member

    Sep 11, 2008
    104
    0
    Greetings,

    I am looking for a C function to convert a raw hex data to a char. Recently I developed a program in PIC18F2550 that reads the data from the ADC and sends to the serial port.

    The program is working all right but I am having difficulties in writing a function that receives the raw hex data and convert to char. It should look something like this:

    unsigned char convert2hex(unsigned char data)

    The ADC and Serial is working all right but I need to convert the ADC data to a readable string to send to serial port o look something like this in the terminal: "0EFF" "00A1" "0012" and etc...

    Also, one import aspect to tell is that the PIC18F2550 have to registers that represents the 10-bits resolution of the ADC. The register ADRESH has the 8 most significant bits of the ADC and the ADRESL register has the 2 less significant bits of the ADC.

    Thanks,
    FBorges22
     
    Last edited: Dec 19, 2011
  2. spinnaker

    AAC Fanatic!

    Oct 29, 2009
    4,887
    1,012
    int a=1234;
    char buffer [10];

    sprintf(buffer,"%04X",a);
     
    Last edited: Dec 18, 2011
  3. kubeek

    AAC Fanatic!

    Sep 20, 2005
    4,670
    804
    I don´t know how fast will the implementation of sprintf be, but you can compare it with an algorithm that takes each nibble and prints the right character, either using a switch table or calculating the right ascii code from the number, but switch should be faster.
     
  4. FBorges22

    Thread Starter Active Member

    Sep 11, 2008
    104
    0
    I tried this in my program:

    sprintf(adcData[0],"%04X",ADRESH);
    sprintf(adcData[1],"%04X",ADRESL);

    However, I am getting an error telling that:

    main.c:82:Warning [2054] suspicious pointer conversion
    main.c:82:Warning [2066] type qualifier mismatch in assignment
    main.c:83:Warning [2054] suspicious pointer conversion
    main.c:83:Warning [2066] type qualifier mismatch in assignment

    Somehow I cannot work directly with ADRESH and ADRESL values. I also tried to put and cast before the ADRES(H and L) registers but it not worked.

    How should I proceed?

    Thanks,
    FBorges22
     
  5. ErnieM

    AAC Fanatic!

    Apr 24, 2011
    7,388
    1,605
    The error you're getting is an obscure point of the C18 compiler. You need to tell it that the constant string you are passing into sprintf() is a constant string. Seems kinda obvious to us but it confuses the compiler.

    A proper statement to use sprintf() would be:

    Code ( (Unknown Language)):
    1.  
    2.    sprintf (buffer, (const far rom char*) "%#06x", ADRES);
    3.  
    Also note here that I used the symbol ADRES which is the entire 10 bit A2D result, which the compiler sees as a int type. So I did the entire conversion in 1 step.

    Now do note that sprintf() is a "heavy duty" function with lots of built in options. Using it I incurred an extra 1,600 instructions over doing a simple direct int to hex routine, like so:

    Code ( (Unknown Language)):
    1.  
    2. void convert2hex(char* _buffer, unsigned int data)
    3. {
    4.   // we make our string assuming all hex digits are 0 to 9
    5.   // string will be of the form 0xabcd
    6.   // where a,b,c,d are the individual hex digits
    7.   _buffer[0] = '0';
    8.   _buffer[1] = 'x';
    9.   _buffer[2] = ( (data>>12) & 0x0F) + '0';
    10.   _buffer[3] = ( (data>>8)  & 0x0F) + '0';
    11.   _buffer[4] = ( (data>>4)  & 0x0F) + '0';
    12.   _buffer[5] = ( (data)     & 0x0F) + '0';
    13.   _buffer[6] = 0;  
    14.  
    15.   // now we correct for the case where a digit
    16.   // is A to F:
    17.   if (_buffer[2] > '9') _buffer[2] += 7;
    18.   if (_buffer[3] > '9') _buffer[3] += 7;
    19.   if (_buffer[4] > '9') _buffer[4] += 7;
    20.   if (_buffer[5] > '9') _buffer[5] += 7;
    21.  
    22. }  
    23.  
    This can be called like so:

    Code ( (Unknown Language)):
    1.  
    2. #include <p18cxxx.h>
    3. #include <stdio.h>
    4. void convert2hex(char* buffer, unsigned int data);
    5.  
    6. void main (void)
    7. {
    8.   int i = 0xA12;
    9.   char buffer[10];
    10.  
    11.  
    12.   convert2hex(buffer, 0x1234);
    13.   convert2hex(buffer, 0xFFFF);
    14.   convert2hex(buffer, i);
    15.  
    16.   convert2hex(buffer, ADRES);
    17.  
    18.   while (1)
    19.     ;
    20. }
    What I also found weird was a "bug" in sprintf() where you pass it low values the "precision" of the converted string (ho0w many characters it uses) may be less then what you wanted (it can change).

    The coded version is always the precision you made it.
     
    FBorges22 likes this.
  6. kubeek

    AAC Fanatic!

    Sep 20, 2005
    4,670
    804
    You cannot put string in a single byte or array member.
    Also, if ADRESH is an absolute memory address, you need to cast it to a pointer.
    The second sprintf overwrites the first one, if want the number one after another it should look like this, assuming ADRESH is adrees of an int:
    AFAIK the correct syntax would be:
    Code ( (Unknown Language)):
    1. char buffer[10];
    2. sprintf(buffer, "%04X %04X",(int*)(ADRESH),(int*)(ADRESL));
    3.  
     
  7. codehead

    Member

    Nov 28, 2011
    56
    11
    Leaving the exact implementation to you, but in a nutshell, you define this once (make a-f capital, if your prefer):

    char hexchars[] = "0123456789abcdef";

    Then your conversion is this (assuming val 0-15):

    char hexVal = hexchars[val];

    That just converts a nibble—if you want to convert a byte, then you need to do some shifting and masking:

    unsigned char data; // data
    unsigned char *out; // pointer to output buffer

    *out++ = hexchars[data >> 4];
    *out++ = hexchars[data & 0x0f];

    No if's, simple and quick.
     
  8. thatoneguy

    AAC Fanatic!

    Feb 19, 2009
    6,357
    718
    Nifty, how would 0x be inserted for each number? I am thinking *out++="0x"
    followed by your code.
     
  9. codehead

    Member

    Nov 28, 2011
    56
    11
    Pretty close, but you'd need to do something like this (or a strcpy), since that's a 2-character string:

    *out++ = '0';
    *out++ = 'x';
    ...
     
  10. FBorges22

    Thread Starter Active Member

    Sep 11, 2008
    104
    0
    Hello again,

    The function posted by ErnieM with some modifications worked in my program. I just removed the "0x" from the string.

    Code ( (Unknown Language)):
    1. void convert2hex(char* _buffer, unsigned int data)
    2. {
    3.     // we make our string assuming all hex digits are 0 to 9
    4.     // string will be of the form 0xabcd
    5.     // where a,b,c,d are the individual hex digits
    6.     _buffer[0] = ( (data>>12) & 0x0F) + '0';
    7.     _buffer[1] = ( (data>>8)  & 0x0F) + '0';
    8.     _buffer[2] = ( (data>>4)  & 0x0F) + '0';
    9.     _buffer[3] = ( (data)     & 0x0F) + '0';
    10.     _buffer[4] = 0;
    11.     // now we correct for the case where a digit
    12.     // is A to F:
    13.     if (_buffer[0] > '9') _buffer[0] += 7;
    14.     if (_buffer[1] > '9') _buffer[1] += 7;
    15.     if (_buffer[2] > '9') _buffer[2] += 7;
    16.     if (_buffer[3] > '9') _buffer[3] += 7;
    17. }
    However, sometimes the program send wierd values to my serial port. For example, when the ADC has 4.999V in his input I was expecting something like this: 03FF and sometimes appears 03?F in the serial terminal.... What could be causing this?

    Generally this happens when the ADRES data contains a hex letter like A,B,C,D,E,F the numbers are not affected. The ADC at least is working ok.
    Any ideas?

    Thanks,
    FBorges22
     
  11. codehead

    Member

    Nov 28, 2011
    56
    11
    Faster, if it matters (and more readable, I think):

    Code ( (Unknown Language)):
    1.  
    2. void convert2hex(char* _buffer, unsigned int data)
    3. {
    4.     char hexchars[] = "0123456789ABCDEF";
    5.  
    6.     *_buffer++ = hexchars[data >> 12 & 0x0f];
    7.     *_buffer++ = hexchars[data >> 8 & 0x0f];
    8.     *_buffer++ = hexchars[data >> 4 & 0x0f];
    9.     *_buffer++ = hexchars[data & 0x0f];
    10.     *_buffer = 0;
    11. }
    12.  
     
    Last edited: Dec 19, 2011
    FBorges22 likes this.
  12. FBorges22

    Thread Starter Active Member

    Sep 11, 2008
    104
    0
    Okay... The code using the tips worked all right! Now the PIC18F data acquisition system prototype using serial port are working all right. :)

    I would like to thank everyone for helping me with this algorithm and also to wish a happy Christmas and new year 2012.

    Thanks,
    FBorges22
     
  13. spinnaker

    AAC Fanatic!

    Oct 29, 2009
    4,887
    1,012
    I like it myself. I will have to add it to my list of utilities.

    Hard to believe sprintf is so code heavy. You wonder what is in there. Perhaps a lot of it is handling the variable length arg list?
     
  14. ErnieM

    AAC Fanatic!

    Apr 24, 2011
    7,388
    1,605
    Good stuff!

    That works, it's even 61 statements shorter then mine (and I like shorter) even when I add in the statements to prefix the string with the "0x" like so:

    Code ( (Unknown Language)):
    1. [FONT=Courier New]void convert2hex(char* _buffer, unsigned int data)
    2. {
    3.     char hexchars[] = "0123456789ABCDEF";
    4.  
    5. [/FONT][FONT=Courier New]    *[/FONT][FONT=Courier New]_buffer++ = '0';
    6.     *_buffer++ = 'x';
    7.     *_buffer++ = hexchars[data >> 12 & 0x0f];
    8.     *_buffer++ = hexchars[data >> 8 & 0x0f];
    9.     *_buffer++ = hexchars[data >> 4 & 0x0f];
    10.     *_buffer++ = hexchars[data & 0x0f];
    11.     *_buffer = 0;
    12. }[/FONT]
     
  15. codehead

    Member

    Nov 28, 2011
    56
    11
    :)

    BTW, in case anyone thinks that the string would be created on each call and add to overhead, it isn't. The compiler simply sets hexchars to the address of the embedded string. (Also, to be technically correct, that line should be "const char hexchars[]...", but I left out "const" because it doesn't matter in practical terms and I didn't want to add potential confusion.)
     
  16. ErnieM

    AAC Fanatic!

    Apr 24, 2011
    7,388
    1,605
    I'm sure that would be true on a Von Neumann architecture but here we have a Harvard architecture where program memory in ROM is separate and distinct and is accessed in a different manor then data memory in RAM (registers).

    Using the MPLAB simulator, if you drop a breakpoint on the line:
    const char hexchars[] = "0123456789ABCDEF";

    Run the code and walk thru the dissasembly listing for that you will find a ton of code that reads "the string would be created on each call and add to overhead." Even using the const attribute has no effect as the same (constant) string is recreated in RAM every time the function is invoked.

    In preparation for moving the string to ROM I moved the hexchars definition to outside and function to make it a global. Surprisingly just this one simple change removed 55 instructions (but no RAM) from the build. It must be using the c start-up code to make the assignments.

    So ROM and RAM usage stay the same for both "char" and "const char" attributes. To get anywhere we give it the "rom const char" attribute and poof, we get 2 less instructions but also 17 fewer RAM bytes.

    So here is how the array is defined:

    Code ( (Unknown Language)):
    1.  
    2.       rom const char hexchars[] = "0123456789ABCDEF";
    That puts the string into ROM once at compile time, not in RAM at run time.
     
  17. t06afre

    AAC Fanatic!

    May 11, 2009
    5,939
    1,222
    You can always define a union. http://www.wellho.net/resources/ex.php4?item=c209/union.c Then you need to "convert" an array of integers to char. Like then you need to send integers using the UART
    EDIT: I see now that this was not your problem. But check out the union anyway. How about a lock up table in the ROM. That should require very few instructions for conversion. And this is also part of the PIC instruction set
     
    Last edited: Dec 20, 2011
  18. codehead

    Member

    Nov 28, 2011
    56
    11
    Haha—I just knew someone would come up with a case where that's not true—thanks ErnieM.

    Yeah, there's very little that you can count on absolutely with a compiler. Whenever I've written code that mattered (which I've done a lot of—especially the DSP stuff), I've sifted through the compiler output to make sure my inlines were getting inlined, that crazy unexpected code wasn't getting thrown in (such as the x87 emulation when rounding as in float to int conversions)...

    I've done a lot of Harvard architecture coding, but it was all at the assembly level...

    BTW, you said you had to move it to global—are you sure you can't just define it as static? I did consider putting that in my original definition, but thought I was making things too complicated for readers...
     
  19. MrChips

    Moderator

    Oct 2, 2009
    12,442
    3,361
    In asm, I would just shift left 4-bits into another register, look up hexchars[ ],
    then do this four times.
     
  20. ErnieM

    AAC Fanatic!

    Apr 24, 2011
    7,388
    1,605
    Sure you can define it as "static char" you get the improvement in instruction count, but pay a penalty in RAM as for some oddball reason C18 seems to put the static string into RAM. So bottom line is stash the string into ROM for the best efficiency.

    Now before you go thinking I did all this just to prove you wrong, what started me on this was looking to remove 1 or 2 bytes in the string definition: char foo[] = "1234"; is not 4 bytes but 5 as the trailing zero is strapped on the string. As in our case we don't need that zero I was looking at making an array: char foo[4] = {'1','2','3','4'};

    What is curious is I can't find that last zero. Compiling as a string or an array leads to the same ROM and RAM usage.

    I'm sure its in there but I'm not going that deep into the disassembly listing.
     
Loading...