divided by two has no remainder (equals 0), while any
odd number divided by two will have a remainder. We
don’t care what the remainder is, only that it exists. So, we
can use an if statement which will be true if there is a
remainder and false if not:
Finally, we place the temp byte back in the array.
Unpacking the nibble from the virtual nibble array is
similar:
temp &= 0xF0; // clear the low nibble
temp += value;
uint8_t unpackNibble(uint8_t array, uint16_t
nibble_index)
{
// We are storing two nibbles in each byte of
// the array so the array index is 1/2 the
// nibble index
uint16_t array_index = nibble_index/2;
if ((nibble_index 2) == 0) {
// number is even
}
else {
// number is odd
}
// Get the array byte to unpack the nibble
uint8_t temp = array[array_index];
Now that we can determine if the nibble index is even
or odd, we will use the & and the >> and << operators to
pack and unpack the nibble in the correct position in the
byte. Let’s look at the packNibble function to see how
this works:
void packNibble(uint8_t array, uint16_t
nibble_index, uint8_t value)
{
// the actual array position is 1/2 the
// nibble position
uint16_t array_index = nibble_index/2;
// Determine which nibble we want and return it
if ((nibble_index 2) == 0) {
// number is even
return ((temp & 0xF0) >> 4);
}
else {
// number is odd
return(temp & 0x0F);
}
}
uint8_t temp = array[array_index];
As before, we decide if the virtual nibble index is even
or odd. If it is even, we AND the byte with 0xF0, shift it
four positions right, and then return that value:
if ((nibble_index 2) == 0) {
// number is even
temp &= 0x0F; // clear the high nibble
temp += (value << 4); // add to the high
// nibble
}
else {
// number is odd
temp &= 0xF0; // clear the low nibble
temp += value;
}
array[array_index] = temp;
}
return ((temp & 0xF0) >> 4);
If it is odd, we just mask off the high nibble and return
the value:
return(temp & 0x0F);
The packNibble function takes a pointer to the byte
array that we use to store data in SRAM — the
nibble_index — and the nibble value. First, we get the array
index by dividing the nibble index by two. Thus, our
nibble index is 256 and our array index will be 128.
How simple is that? Well, not so simple really. Having
a working program to demonstrate a complex principle is
always a help. So, I’ve written a test program that
demonstrates these principles (nibble_test) that you can
download at the article link. The output to the serial
monitor from running the test is shown in Figure 5.
Next, we get the value already stored in the array at
that index value and store it in a temporary byte. Then, we
decide if the nibble index is odd or even. If it is odd, we
clear the high nibble in the array byte by using the &
operator with 0x0F to mask the high nibble to 0. Then, we
move the nibble value four binary positions to the left to
put it in the high nibble:
Using the EEPROM
The Arduino (with the ATmega328P) has 1,024 bytes
of EEPROM that was designed to store data when the
device is turned off. Since we decided to dedicate
512 bytes of SRAM for our data array, we can use the
temp &= 0x0F; // clear the high nibble
temp += (value << 4); // add to the high nibble
EEPROM to dump our SRAM in two blocks which triples
the total amount of data we can log. This gives us 1,536
bytes which (with nibble packing) can theoretically store
3,072 data samples.
Or, if it is the even nibble, we clear the low nibble by
ANDing (&) 0xF0 with the value to mask the low nibble to
0. Then, we add the value to the byte without needing to
shift it since it is already in the correct position:
I say theoretically because we do have to store some
full bytes for the origin bytes. Still, this is not a lot of data
from some perspectives, but it is much better than the
512 bytes we were allowing ourselves with the SRAM and
no data packing. We are very fortunate to have an
Arduino library for EEPROMs that we use by including
EEPROM.h in our code. Then, we can read and write to
56 November 2013