Convert raw HEX to char

Thread Starter

FBorges22

Joined Sep 11, 2008
109
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:

kubeek

Joined Sep 20, 2005
5,794
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.
 

Thread Starter

FBorges22

Joined Sep 11, 2008
109
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
 

ErnieM

Joined Apr 24, 2011
8,377
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:

Rich (BB code):
   sprintf (buffer, (const far rom char*) "%#06x", ADRES);
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:

Rich (BB code):
void convert2hex(char* _buffer, unsigned int data)
{
  // we make our string assuming all hex digits are 0 to 9
  // string will be of the form 0xabcd 
  // where a,b,c,d are the individual hex digits
  _buffer[0] = '0';
  _buffer[1] = 'x';
  _buffer[2] = ( (data>>12) & 0x0F) + '0';
  _buffer[3] = ( (data>>8)  & 0x0F) + '0';
  _buffer[4] = ( (data>>4)  & 0x0F) + '0';
  _buffer[5] = ( (data)     & 0x0F) + '0';
  _buffer[6] = 0;  
  
  // now we correct for the case where a digit 
  // is A to F:
  if (_buffer[2] > '9') _buffer[2] += 7;
  if (_buffer[3] > '9') _buffer[3] += 7;
  if (_buffer[4] > '9') _buffer[4] += 7;
  if (_buffer[5] > '9') _buffer[5] += 7;

}
This can be called like so:

Rich (BB code):
#include <p18cxxx.h>
#include <stdio.h>
void convert2hex(char* buffer, unsigned int data);

void main (void)
{
  int i = 0xA12;
  char buffer[10];
  
  
  convert2hex(buffer, 0x1234);
  convert2hex(buffer, 0xFFFF);
  convert2hex(buffer, i);
  
  convert2hex(buffer, ADRES);

  while (1)
    ;
}
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.
 

kubeek

Joined Sep 20, 2005
5,794
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:
Rich (BB code):
char buffer[10];
sprintf(buffer, "%04X %04X",(int*)(ADRESH),(int*)(ADRESL));
 

codehead

Joined Nov 28, 2011
57
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.
 

thatoneguy

Joined Feb 19, 2009
6,359
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.
Nifty, how would 0x be inserted for each number? I am thinking *out++="0x"
followed by your code.
 

Thread Starter

FBorges22

Joined Sep 11, 2008
109
Hello again,

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

Rich (BB code):
void convert2hex(char* _buffer, unsigned int data)
{
    // we make our string assuming all hex digits are 0 to 9
    // string will be of the form 0xabcd 
    // where a,b,c,d are the individual hex digits
    _buffer[0] = ( (data>>12) & 0x0F) + '0';
    _buffer[1] = ( (data>>8)  & 0x0F) + '0';
    _buffer[2] = ( (data>>4)  & 0x0F) + '0';
    _buffer[3] = ( (data)     & 0x0F) + '0';
    _buffer[4] = 0;
    // now we correct for the case where a digit 
    // is A to F:
    if (_buffer[0] > '9') _buffer[0] += 7;
    if (_buffer[1] > '9') _buffer[1] += 7;
    if (_buffer[2] > '9') _buffer[2] += 7;
    if (_buffer[3] > '9') _buffer[3] += 7;
}
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
 

codehead

Joined Nov 28, 2011
57
Hello again,

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

Rich (BB code):
void convert2hex(char* _buffer, unsigned int data)
{
    // we make our string assuming all hex digits are 0 to 9
    // string will be of the form 0xabcd 
    // where a,b,c,d are the individual hex digits
    _buffer[0] = ( (data>>12) & 0x0F) + '0';
    _buffer[1] = ( (data>>8)  & 0x0F) + '0';
    _buffer[2] = ( (data>>4)  & 0x0F) + '0';
    _buffer[3] = ( (data)     & 0x0F) + '0';
    _buffer[4] = 0;
    // now we correct for the case where a digit 
    // is A to F:
    if (_buffer[0] > '9') _buffer[0] += 7;
    if (_buffer[1] > '9') _buffer[1] += 7;
    if (_buffer[2] > '9') _buffer[2] += 7;
    if (_buffer[3] > '9') _buffer[3] += 7;
}
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
Faster, if it matters (and more readable, I think):

Rich (BB code):
void convert2hex(char* _buffer, unsigned int data)
{
	char hexchars[] = "0123456789ABCDEF";

	*_buffer++ = hexchars[data >> 12 & 0x0f];
	*_buffer++ = hexchars[data >> 8 & 0x0f];
	*_buffer++ = hexchars[data >> 4 & 0x0f];
	*_buffer++ = hexchars[data & 0x0f];
	*_buffer = 0;
}
 
Last edited:

Thread Starter

FBorges22

Joined Sep 11, 2008
109
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
 

spinnaker

Joined Oct 29, 2009
7,830
Nifty, how would 0x be inserted for each number? I am thinking *out++="0x"
followed by your code.
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?
 

ErnieM

Joined Apr 24, 2011
8,377
Faster, if it matters (and more readable, I think):

Rich (BB code):
void convert2hex(char* _buffer, unsigned int data)
{
    char hexchars[] = "0123456789ABCDEF";

      *_buffer++ = hexchars[data >> 12 & 0x0f];
    *_buffer++ = hexchars[data >> 8 & 0x0f];
    *_buffer++ = hexchars[data >> 4 & 0x0f];
    *_buffer++ = hexchars[data & 0x0f];
    *_buffer = 0;
}
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:

Rich (BB code):
void convert2hex(char* _buffer, unsigned int data)
{
    char hexchars[] = "0123456789ABCDEF";

    *_buffer++ = '0';
    *_buffer++ = 'x';
    *_buffer++ = hexchars[data >> 12 & 0x0f];
    *_buffer++ = hexchars[data >> 8 & 0x0f];
    *_buffer++ = hexchars[data >> 4 & 0x0f];
    *_buffer++ = hexchars[data & 0x0f];
    *_buffer = 0;
}
 

codehead

Joined Nov 28, 2011
57
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:

Rich (BB code):
void convert2hex(char* _buffer, unsigned int data)
{
    char hexchars[] = "0123456789ABCDEF";

    *_buffer++ = '0';
    *_buffer++ = 'x';
    *_buffer++ = hexchars[data >> 12 & 0x0f];
    *_buffer++ = hexchars[data >> 8 & 0x0f];
    *_buffer++ = hexchars[data >> 4 & 0x0f];
    *_buffer++ = hexchars[data & 0x0f];
    *_buffer = 0;
}
:)

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.)
 

ErnieM

Joined Apr 24, 2011
8,377
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.)
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:

Rich (BB code):
      rom const char hexchars[] = "0123456789ABCDEF";
That puts the string into ROM once at compile time, not in RAM at run time.
 

t06afre

Joined May 11, 2009
5,934
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:

codehead

Joined Nov 28, 2011
57
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:

Rich (BB code):
      rom const char hexchars[] = "0123456789ABCDEF";
That puts the string into ROM once at compile time, not in RAM at run time.
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...
 

ErnieM

Joined Apr 24, 2011
8,377
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...
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.
 
Top