|Terms of service|
|Let's keep it simple
Pointers and floats - Improve your Arduino programming skills
In this video I will try to explain as simple as possible the concept of pointers and how you can use them in a practical manner. For example, have a look at this line. Looks complicated doesn’t it?
At the end of the video you will understand what this line does and how you can use it in your projects. So if you’re interested in learning something new you might as well keep watching.
Ok, the easiest way to explain pointers is by using an example.
Let’s say that I have built a balancing robot with the ATmega328p, that is the microcontroller used on various Arduino boards. And I want to store some variables in the EEPROM of the microcontroller.
The EEPROM is a non-volatile memory that will remember the data even after the power has been turned off.
To do this I will use an Arduino Uno that will run my example programs and the EEPROM library that is part of the standard Arduino IDE.
In my example program I use a byte and a float that I want to store in EEPROM and later read it back to the check variables.
On the EEPROM write reference page we can find the syntax of the EEPROM write function. This function will write data to the EEPROM of the ATmega328P. All we need is a memory address represented as an integer and the value that we want to store in the EEPROM in the form of a byte.
And for those who forgot, one byte holds 8 bits and gives us 256 possible combinations varying between 0 and 255.
For your information the EEPROM in the ATmega328P only has 1024 memory locations. Meaning that we can only store 1024 bytes and that the memory address must be between 0 and 1023 as we start counting from 0.
Ok, that sounds relatively easy. If I want to store my byte variable I can simply use this line. The value of the byte variable will be stored in the EEPROM memory location 0.
As a conformation I will read back the value from the EEPROM and print the check byte variable on the serial output. Needless to say but during the read back the EEPROM memory location needs to be the same.
So, if I now run the program you can see that both variables have the same value. Meaning that the write-read cycle worked.
Well, that wasn’t so hard. Now let’s have a look at the float variable.
And you might already guessed it, a float is not a byte. However, all data in the microcontroller is stored in the form of bytes. And floats are no exception.
To better understand of what is going on we need to understand the concept of floats.
On the float reference page we can see that a float is stored as a 4 byte value when we use the ATmega328P. Meaning that in the static random access memory, or SRAM of the microcontroller, 4 bytes are used to store the float variable.
Simplified it looks like this. This cell represents the first byte in the SRAM memory. As it is a byte it holds 8 bits. With every cell the address is incremented by one. Because the ATmega328P has 2k of SRAM there are 2048 memory address locations.
In the datasheet of the ATmega328P we can find the start address of the SRAM memory, in this case 0x0100 in hexadecimal form. And it ends at address 0x08FF, also in hexadecimal form.
So basically all I want to do is to copy these 4 bytes individually to the EEPROM and later write them back to the check float variable.
And as you probably guessed it, we will use pointers to get this to work. Ok, first things first, what is a pointer?
A pointer makes it possible to create another object or variable that points, or is coupled to a certain memory location. The memory location where the pointers is coupled to is stored inside the pointer itself.
As an example, here I have declared a float variable that is located over 4 bytes somewhere in the SRAM memory.
I will also declare a float pointer that you can recognize by the asterisk. I can now connect the pointer to the float address via this line. Because I’m changing the pointer address the leading asterisk is removed.
The ampersand is the address-of operator and will return the address location of the float variable as a 16 bit integer.
I will also add some extra lines so we can see the various outputs on the serial monitor.
First I will output the hexadecimal address of the float variable. Now, it’s important to use the correct datatype as the result of the address-of operator is an 16 bit integer. However, the variable itself is a float.
If I compile this code the compiler returns an error and tells me that it can choose between various options.
To make it clear that the compiler has to handle it as an integer I will cast or force the result of the address-of operator to the datatype integer.
Next the value of the float variable and the value of the pointer is printed on the serial monitor.
Because I want to read the coupled memory location of the pointer I add the asterisk in front of the pointer.
When I want to read the address where the pointer is coupled to the asterisk is removed.
When I run this program we can see that the start address of the float variable is 0x100 in hexadecimal form. And that both the float variable and the pointer hold the same value. This is as expected because the pointer is coupled to the same memory address of the float variable.
Fun fact, the 0x100 address is the first address of the SRAM memory as you can see here in the datasheet. So we know that it’s a valid memory address.
Ok, this makes sense so far as it is pretty straight forward. If not, please watch this part again until it makes sense.
Now it’s time to use the real power of pointers. In our program we declared the pointer as a float because that is what we wanted to access. However it is also possible to use other datatypes. Because we need to write single bytes to the EEPROM I will focus on that.
So, I will change the datatype of the pointer to a byte. To connect the pointer to the address location of the float I have to explain to the compiler that it has to connect it as a byte and not a float. To do this I need to cast the datatype of the pointer address location to a byte like this.
Now the compiler will handle this pointer as a byte instead of a float. And this makes it possible to print the first byte of the float variable like this.
To print the second byte of the float the pointer address has to be incremented by one. And we can simply do this by adding the increment operator to the pointer. When we do this the memory address of the pointer is incremented after the read.
So, it will read the address location and after that it will automatically increment to the next memory location. This way I can add the remaining three bytes like this.
And when I now run the program we get the individual hexadecimal values of the 4 bytes that hold the float value.
And to make sure that the code is working correctly I can calculate the original float value from the 4 bytes that we have extracted from the SRAM.
First I will convert the hexadecimal values to bits.
The first bit is our sing bit. When one the float value is negative. When zero the value is positive. So we already know that it will be a positive value.
The next 8 bits represent the exponent that we will use later. We can calculate the value like any other unsigned byte value. The first bit represents a 1, the second a 2, the third a 4 and so on. By adding the values of the bits that are one we get the value of 130.
As the exponent can be positive and negative we need to subtract 127 from the result. The 127 is a given number that we simply have to use. Resulting in an exponent of 3 that we will use later.
These last 23 bits are the fraction bits and represent the actual value of the float. Because the way this works I will add a leading one and get a 24 bit value. The first bit of the 24 bit value is always a 1.
In contrast to a normal byte the bit values of the fraction work a little different.
The first bit represents a 1. The second bit represents the half of the previous bit. So 1 divided by 2 equals 0.5. The next bit also represents the half value of the previous bit and equals 0.25. And this goes on up to the last bit.
To make this more convenient I will use an Excel sheet where the rows represent the 24 bits. So first bit represents a 1, the second bit is the first bit divided by 2, and so on.
Column B represent my 24 bit value and whenever a bit is one the representation of the bit is copied to column C. The result of column C is printed here.
Now I can fill in the individual bits 1010 0010 0001 0100 0111 1011
And we have a result of 1.26625 and so on. Last thing that we need to do is multiply the outcome by the exponent 3 that we calculated earlier.
So 2 to the power of 3 multiplied by the result will give us a value that will come close to the original 10.13 that we originally stored in the float variable.
It’s not spot on because that is simply how a float works. For example, if I change the last bit you can see that the result is now smaller then 10.13. So there is now way to get a perfect 10.13 value with a 4 byte float.
In short, the pointer method is working and we can now store the individual bytes in the EEPROM.
The same pointer method can be used to restore the check float value from the EEPROM as you can see here. When I now run the code you can see that the both the original float value and the check float value are the same.
And basically you can use this method for all datatypes as long as you know the number of bytes that are used by the specific data type.
Remember that line that I showed in the beginning of the video? This line makes it possible to directly change or access any byte in the SRAM memory.
So it uses a pointer to address 0x100 in hexadecimal form. Because the pointer is cast to a byte it’s possible to change the value of that specific byte to 0xFF in hexadecimal form This way you can read and write any individual byte in the SRAM memory. And that is one of the powerful things that you can do with pointers.
And that wraps it up for this video. As always, I hope you learned something new and that you can use this knowledge in your future projects.
Thank you for watching and see you next time.