Convert Float To Hexadecimal String In C For PIC18F45K50 With Minimal Processing

Published Oct 17, 2025
 1 hours to build
 Beginner

Using pointers, the hexadecimal code of a float variable can be read directly avoiding the standard conversion algorithm and loss of accuracy. A short algorithm is still needed to convert the hexadecimal number to string.

display image

Components Used

PIC18F45k50
8-bit microcontroller, but this project is more about the C code than the platform.
1
Description

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

Comments
Ad