Normally to convert a floating point number to a hexadecimal code an algorithm is used that can loose accuracy and cost processing time. However, the value is already stored in the microcontroller’s (or PC’s) memory in a hexadecimal form, which can be read directly using pointers and then converted to a string.
This concept is intended for microcontroller projects that need to send decimal numbers over RS232, reducing inaccuracy and computation time.
Code:
#include <xc.h>
#include <stdio.h>
char _4bitsToHex(uint8_t x){
char outputChar;
if(x > 9){
outputChar = x + 55;
// 'A' -> 65 (decimal)
// 0x0A + 'A' - 10 = 0x0A + 65 - 10 = 0x0A + 55 -> 'A'
// 0x0B + 55 -> 'B'
// ...
}else{
outputChar = x + '0';
}
return outputChar;
};
void main(void) {
char outputString[8];
uint8_t x_h, x_l;
//example
float ex = 1.9892; //0x3ffe9e1b
// float ex = -1.9892; //0xbffe9e1b
// float ex = 12.0092; //0x414025af
// float ex = -111.9892; //0xc2dffa78
uint8_t* p_ex; // to read 1 byte at a time use a data type 1 byte long for pointer
// data type with 2 bytes and 4 bytes read 2 and 4 bytes at a time respectively (see example below)
p_ex = &ex;
uint8_t xx[4] ={0, 0, 0, 0};
xx[0] = *p_ex; // lower byte
xx[1] = *(p_ex+1);
xx[2] = *(p_ex+2);
xx[3] = *(p_ex+3);
/* code to read 2 bytes at a time
uint16_t* p_ex;
p_ex = &ex;
//long int xx = 0;
uint16_t xx[2] ={0, 0};
xx[0] = *p_ex;
xx[1] = *(p_ex+1);
//*/
// convert to string
int i = 0;
for(i=3; i >= 0; i--){
x_h = xx[i]; // temporary store in x_h
x_l = x_h & 0b00001111; // lower 4 bits ex: 0x3F -> 0x0F
x_h = x_h >> 4; // upper 4 bits ex: 0x3F -> 0x03
outputString[6 - 2*i] = _4bitsToHex(x_h);
outputString[7 - 2*i] = _4bitsToHex(x_l);
}
while(1);
return;
}
Reading Data
After some experimentation, it was noticed that C will read 1 byte if the pointer is for a data type 1 byte long, 2 bytes if pointer is 2 bytes ... etc. Example:
- A
uint8_t*
pointer reads 1 byte at a time. - A
uint16_t*
pointer reads 2 bytes at a time.
Note: uint8_t*
is not the actual length of the pointer.
By reading 1 byte at a time, fewer shift operations are required in the byte-to-hex conversion section (further explanation in the next section).
uint8_t* p_ex; // to read 1 byte at a time use a data type 1 byte long for pointer
// data type with 2 bytes and 4 bytes read 2 and 4 bytes at a time respectively (see example below)
p_ex = &ex;
uint8_t xx[4] ={0, 0, 0, 0};
xx[0] = *p_ex; // lower byte
xx[1] = *(p_ex+1);
xx[2] = *(p_ex+2);
xx[3] = *(p_ex+3);
Each entry in the array xx
holds a single byte. The pointer p_ex
points to the lowest byte of the floating point number ex
.

Note: uint8_t* p_ex
defines a pointer to an 8-bit variable. The pointer itself is not 8-bits long—it can point to any address in the microcontroller.
Converting to a Character Array (String)
for(i=3; i >= 0; i--){
x_h = xx[i]; // temporary store in x_h
x_l = x_h & 0b00001111; // lower 4 bits ex: 0x3F -> 0x0F
x_h = x_h >> 4; // upper 4 bits ex: 0x3F -> 0x03
outputString[6 - 2*i] = _4bitsToHex(x_h);
outputString[7 - 2*i] = _4bitsToHex(x_l);
}
The for loop converts the 1-byte entries in the array xx
into characters and stores them in the array outputString
starting with the most significant byte (i.e. outputString[0]
= most significant its).
The byte is temporarily stored in the variable x_h
. x_l
holds the lower 4 bits hence the mask 0b00001111
is used to remove the higher bits. A right shift is used on x_h
to remove the lower bytes and move the upper 4 bytes down. Example:
if xx[i]
= 0x1b then x_h
= 0x01, x_l
= 0x0b
The function _4bitsToHex()
converts them to characters.
The reason why I chose to separate the floating point variable into single bytes instead of storing it whole in a 4-byte variable is to reduce the number of right shifts. A 4-byte variable would need a right shift 3 times and masking for every 4-bits. I assume that this would require more computation than separating the float.
_4bitsToHex()
char _4bitsToHex(uint8_t x){
char outputChar;
if(x > 9){
outputChar = x + 55;
// 'A' -> 65 (decimal)
// 0x0A + 'A' - 10 = 0x0A + 65 - 10 = 0x0A + 55 -> 'A'
// 0x0B + 55 -> 'B'
// ...
}else{
outputChar = x + '0';
}
return outputChar;
};
This function maps numbers from 0 to 9 to characters '0' to '9', and maps numbers from 10 (0xA) to 15 (0xF) to characters 'A' (ASCII 65 = 10 + 55) to 'F' (70 = 15 + 55).