2
\$\begingroup\$

As a result of calculations, I get a float (4 bytes and big endian) which I need to convert to hexadecimal to send it through the UART. How can this conversion be done? I have not found any examples that work for me. Thanks in advance.

EDIT: I have found this method from a library for arduino. Could you do something similar in C?

String Akeru::toHex(float f){
byte * b = (byte*) & f;

String bytes = "";
for (int i=0; i<4; i++)
{
    if (b[i] <= 0xF) // single char
    {
        bytes.concat("0"); // add a "0" to make sure every byte is read correctly
    }
    bytes.concat(String(b[i], 16));
}
return bytes;

}

EDIT_02: I have tried the code suggested by the first post response (by @jonk) and I do not get the expected result. For a float 63.72, the result should be '427ee148' and I get '0000e148 '. Could it be because the microcontroller is 16 bits? This is the tested code:

#include <msp430g2553.h>
#include <stdio.h>

#define STRSIGFOXLENGHT 10

char strSigfox[STRSIGFOXLENGHT];
float f = 63.72;

void toHex( float fv, char * buf );

int main(void) {

    WDTCTL = WDTPW | WDTHOLD;   // Stop watchdog timer

    toHex(f,strSigfox);
    return 0;
}

void toHex( float fv, char * buf ) {
    sprintf( buf, "%08X", *(unsigned long *) &fv );
}

Thanks again.

EDIT_03: This is the code that works fine after the correction of Jonk:

#include <msp430g2553.h>
#include <stdio.h>

#define STRSIGFOXLENGHT 10

char strSigfox[STRSIGFOXLENGHT];
float f = 63.72f;

void toHex( float fv, char * buf );

int main(void) {

    WDTCTL = WDTPW | WDTHOLD;   // Stop watchdog timer

    toHex(f,strSigfox);
    return 0;
}

void toHex( float fv, char * buf ) {
    sprintf( buf, "%08lX", *(unsigned long *) &fv );
}

Thank you very much to all.

\$\endgroup\$
8
  • \$\begingroup\$ Is this a case where you are using a C/C++ compiler's floating point library? \$\endgroup\$
    – jonk
    Commented Oct 8, 2017 at 19:11
  • \$\begingroup\$ Sorry, my English is very basic and I do not know if I understood you correctly. I am not using any library, the float is as a result of implementing a mathematical function to the value provided by a sensor. \$\endgroup\$
    – FranMartin
    Commented Oct 8, 2017 at 19:20
  • 2
    \$\begingroup\$ Then you are using the floating point library (and just don't realize it yet.) Which C compiler are you using? Do you know the size of a "long int" on your compiler? Also, is your goal to convert the hex formatted result back into a "float" value when received at the other end? If so, what device is receiving this message? \$\endgroup\$
    – jonk
    Commented Oct 8, 2017 at 19:36
  • 3
    \$\begingroup\$ Serialization of floats is a bit of a dog's breakfast. You may have to choose the more portable way (eg. XDR) or the simple way (a union). Anyway, this is not the right SE for a pure programming question. \$\endgroup\$ Commented Oct 8, 2017 at 19:50
  • 1
    \$\begingroup\$ His float is already in 4 bytes that he can send out via UART. I think he wants to convert the float to printable characters such as hex which he can then can then convert back to float directly. It's basically just write a binary to hex function and then point it's input to a pointer to the 4 byte float. \$\endgroup\$ Commented Oct 8, 2017 at 21:32

2 Answers 2

3
\$\begingroup\$

This is something worth an answer, as it's too complex otherwise.

The code sample you happened to find may, or may not, be what you want. You found something. But it is obvious you don't really understand it. So it is hard to know if the equivalent in C is really going to do what you need. But let's just assume... for now.

C++ strings use the heap. I don't think you actually want to use the heap for these results because, quite frankly, I don't think you know how to use it. So I'll avoid attempting anything directly similar to C++ on this point and simply direct you to supply a character string buffer that is at least 9 chars long. Also, since the float is 4 bytes, I'll just assume that you pass it, in its entirety, rather than pass a pointer. Finally, I'll assume you are okay using unsafe library functions like sprintf() and don't care about the possible "code-bloat" that hauling in sprintf() may also entail.

char * toHex( float fv, char * buf ) {
    return sprintf( buf, "%08X", *(unsigned long int *) &fv );
}

All this does is "lie to the C compiler." It first asks for a pointer to the passed floating point value (which may force the C compiler to use memory rather than a register to hold it... but that's a compiler detail you don't care about.) It then "casts" that pointer to a different kind of pointer, so that the C compiler thinks it is now something else (a pointer to an unsigned long int, which I suspect is the right size.) Then it "de-references" that pointer, fetching the value there. But now, the C compiler "thinks" that it is an unsigned long int. So that's what it grabs and hands off to sprintf() without doing any conversion. Sprintf() then treats it as an integer and goes off reading the format string to figure out what to do with it.

The one caveat here is that you may require a specific "order" to these ASCII bytes when you talk to your external device. But that is left as a detail for you to worry about.

Just call it like:

\* some code here to compute your float value, 'myFloat' */
{
    char hexBuf[9];
    toHex( myFloat, hexBuf );
    /* some code you must write that uses hexBuf */
    /* and may or may not need to re-order the buffer as it does so */
}
\* other code after all is said and done */

That's about it. It's unsafe to use if you aren't careful with the buffer size, because toHex() doesn't have a way to find out if the given buffer is long enough. If it isn't, then bad things will happen. Also, sprintf() doesn't know about the length of its buffer, either. So it can write over unintended memory, too. The example I provided that uses toHex() provides a buffer that is long enough for the 8 ASCII characters needed, plus one for the string's ending null byte (needed.) Longer is okay. Shorter is NOT okay.

The message length will always have 8 ASCII characters in it. If you wanted shorter when possible, then that's your fault for not saying so.


Try to avoid using floating point numbers in your internal calculations.

Even when there are many orders of magnitude going on, it's often better to do things in the integer domain. For example, one of my commercial applications deals with currents spanning from \$100\:\textrm{fA}\$ all the way up to about \$10\:\mu\textrm{A}\$. That's 8 orders of magnitude!! Seems like a perfect fit for floating point, right? No. I used integers the entire way. My ADCs provided integers. My gain control used integers. My DACs used integers. Everything coming into the system and everything going out of the system was in integer form. Although there was a temptation present, here, I completely avoided the use of floats.

There are some reasons. In floating point, you cannot assume that \$A\cdot\left(B+C\right)== A\cdot B+A\cdot C\$. The distributive property is shot to pieces. Also, you can lose important precision when performing sums. For example, to do things right when working with many orders of magnitude, you almost certainly need to sort the numbers first. But how many times do people really do that? Yet, ignorantly performing \$avg = \frac{1}{N}\sum x_i\$ can get you into trouble (especially if you accidentally start with the largest value.) It gets worse when you are taking the differences of two sums, as you very well might do if and when computing a variance or standard deviation, as the difference might depend heavily on your ability to retain precision in the least significant bits of your sums.

Floating point, useful and powerful as it often is, also is dangerous. Good use often requires serious knowledge about rounding and truncation errors and the skill with numerical methods associated with the use of floating point. Far too few have the training or experience to use floating point wisely. So the basic rule I suggest, until and unless you know what you are doing with it, is to NEVER use float or double for any commercial instrumentation purpose.


Of course, if you are working with instrumentation that requires it and also requires communications using ASCII representations of engineering or scientific notation, or specifies ASCII hex representations, then do that conversion only at the very last possible moment. But all of the internal computation should avoid it, if possible.

\$\endgroup\$
6
  • \$\begingroup\$ Thank you very much for your complete answer. At this moment I can not test your proposed solution, as soon as I can, I will tell you the result and I will evaluate your answer as a solution to my problem. Thank you also for your recommendation on the use of floats. You always learn from your answers. \$\endgroup\$
    – FranMartin
    Commented Oct 8, 2017 at 23:14
  • \$\begingroup\$ Hi Jonk, I have tried your solution and edited again explaining the results. Do you know what I can be doing wrong? Thanks \$\endgroup\$
    – FranMartin
    Commented Oct 9, 2017 at 18:19
  • \$\begingroup\$ Thanks again Jonk. I have seen the values setting a breaking point and debugging. It is right? \$\endgroup\$
    – FranMartin
    Commented Oct 9, 2017 at 18:58
  • \$\begingroup\$ @FranMartin Sorry, I didn't read well enough a moment back. Yes, it's probably due to a 16-bit limitation. My mistake. \$\endgroup\$
    – jonk
    Commented Oct 9, 2017 at 18:58
  • \$\begingroup\$ @FranMartin You will need to change the formatting character I used from "%08X" to "%08lX" I think. Sorry about not reading well enough before. \$\endgroup\$
    – jonk
    Commented Oct 9, 2017 at 18:59
1
\$\begingroup\$

Assuming you are using a C/C++ compiler, I can suggest a practical and widely used float to double word conversion.

Defining a macro (right inside global scope i.e. after #include lines) would be quite useful:

#include "..."
#include <...>

#define F2DW(floatNum)     *((unsigned long *) &floatNum )

int main()
{
    //...

    while(1)
    {
        //main loop
    }

    return 0;
}

Then, use it whenever you want in your code like this:

float fResult= ... //the stuff
unsigned long dwResult = F2DW(fResult);

or like this:

unsigned long dwResult = F2DW(3.1415926f);

If I understood your question correctly, you want to transmit the floating point number (i.e. 4 bytes) through UART. So you need to extract each byte via bitwise (& and |) and shifting (<< and >>) operators before sending through UART:

float fResult= ... //the stuff
unsigned long dwResult = F2DW(fResult);

unsigned char B0, B1, B2, B3;
B0 =  (dwResult & 0xFF); //lowest byte
B1 = ((dwResult >>  8) & 0xFF); 
B2 = ((dwResult >> 16) & 0xFF);
B3 = ((dwResult >> 24) & 0xFF); //highest byte

//Now B0...B3 can be sent through UART.
\$\endgroup\$
4
  • \$\begingroup\$ I am programming the msp430g2553 in C.Thanks Rohat for your answer but I do not understand it, my programming level is quite low. \$\endgroup\$
    – FranMartin
    Commented Oct 8, 2017 at 19:34
  • 1
    \$\begingroup\$ The macro here is obscuring the basic solution. The basic solution is using a pointer to get the bytes that your float result is made of. You can then use the pointer to send the result over UART - most likely by loading one byte after another into the sending register of your UART. Digital memory is binary by nature. Hexadecimal and Decimal are just ways to represent the data. \$\endgroup\$
    – Grebu
    Commented Oct 8, 2017 at 19:51
  • \$\begingroup\$ @Grebu Thanks. Do you mean to do something similar to the code with which I edited my question? \$\endgroup\$
    – FranMartin
    Commented Oct 8, 2017 at 20:43
  • \$\begingroup\$ Thanks Rohat for your clarification. I tried your solution and it works fine but I need to pass B0, B1, B2 and B3 to an 8-position char array. I'm on it. \$\endgroup\$
    – FranMartin
    Commented Oct 9, 2017 at 18:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.