Sunday, January 29

Mikro C floating point from Microchip to PC

Many people out there have done this in many ways, I'd like to show how I have implemented it in Mikro C Pro.

The IEEE754 standard says:
1 bit sign
8 bit exponent
23 bit mantissa

Microchip variation:
8 bit exponent
1 bit sign
23 bit mantissa

so a number like -4.4 in Microchip hexadecimal representation would look like:

81 8C CC CD 

which in memory is stored CD CC 8C 81 as you can see below starting at location 0x45 from a RAM Window in Mikro C Pro debugger.


Which in binary would be:

10000001 10001100 11001100 11001101

Now if you are not extremely familiar with how to convert floating point numbers to binary and back, you might like to use this tool from Harald Schmidt which will do it for you.
According to the standard -4.4 in binary is

11000000 10001100 11001100 11001101 

and in Hex: 

C0 8C CC CD


The first thing to notice (quite obviously really) is that the last two bytes are the same in both representations CC CD hence we don't need to touch them, great! Now let's see what we would need to do in order to "adjust" the other two bytes to match the IEEE754 standard.
The green digit needs to "fly" in front of the blues which in turn need to move down the bench and the reds do not move at all! This is it.

Now let's see how could this be done in Mikro C code.
Assume we have declared

float number = -4.4;

we would need to be able to access each of the four bytes representing the number so to be able to manipulate them. There are essentially two ways I can think of:
Use the built in routines Lo, Hi, Higher, Highest or use a pointer "creatively".
I like the second better. 

For option 1 you need to remember to

#include "built_in.h"

then declare four byte-size variables

unsigned char one, two, three, four;

after which you can distribute the 4 bytes of floating point number. Three and four will not be needed for this example but it might be handy to have them for example depending on how we intend to get the floating number to the PC (e.g over a serial line).

one =Lo(number); 0x81 0b10000001
two = Hi(number); 0x8C 0b10001100
three = Higher(number); 0xCC 0b11001100
four = Highest(number); 0xCD 0b11001101

Option 2 instead needs a pointer like

unsigned char *numberPoint = &number;

Now the compiler will warn "Suspicious pointer conversion", but doing so will allow us to "scan" the floating point bytes as in

one = *(numberPoint + 3); 0x81 0b10000001
two = *(numberPoint + 2); 0x8C 0b10001100
three = *(numberPoint + 1); 0xCC 0b11001100
four = *numberPoint; 0xCD 0b11001101

looking at the RAM window above, if we had defined the pointer as float *(numberPoint + 1) it would have returned the value BC at location 0x49. That is because 1 in this case is 1 size of floating point numbers, in fact 4 bytes! By declaring our pointer of a type char, which is stored in one byte, we gain the necessary granularity we need to move one byte at a time in the floating point binary representation.

All right, we haven't finished yet but we are nearly there. Now we need to do our flying and shifting of bits.
A really useful thing that you can do in C is that it can embed Assembler code in your program.
Now, whatever the CPU architecture. you will always have Assembler instructions for shifting, rotating or both shifting and rotating. They are very similar commands and essentially they tell the CPU to "move" all the bits to the right or to the left. What happens to the leftmost bit when rotating to the left? It "moves" to a particular location called the carry flag (CF), if you then rotate to the right the carry flag gets back on the leftmost bit of the byte. This is what we need to know. If you really really want to know more, there is just about a ton of stuff on the Internet, just Google it.

So coming back to our bytes, we would need to rotate to the left (rlf) byte two, push the CF bit back into byte one by rotating to the right (rrf) and finally rrf the new CF to byte two. That's it!

rlf two 10001100 CF=1 two=0001100x (the x because it does not matter what was in the CF)
rrf one 10000001 CF=1 one=11000000
rrf two 0001100x CF=x two=10001100

this is how you write it in Assembler in your Mikro C editor

asm {
          bsf STATUS, C
          rlf _two, 1
          rrf _one, 1
          rrf _two, 1
      }

so now we have our 11000000 10001100 11001100 11001101 hurrah!  

I hope you found this useful, it turns out I did not need to do this for the project I was working on and that generally there are better ways to get values that are represented with floating point numbers to a PC but maybe this is for another post.  
     


No comments: