For the timer I'd used the 16 bit timer1. This timer is then initialized according to the user defined refresh frequency. As seen in the code below (
void initInterrupt(void)) we first calculate the period of the frequency. This period will be used as parameter for the function
calcPrescaler() to calculate the smallest needed prescaler. To be able to calculate the prescaler we actually need to know the value of the timer output compare register. However this value is usually calculated
after the prescaler is known. Luckily we do know the maximum value of the output compare register, because we know that timer1 is a 16 bit timer. This means the maximum possible value of the output compare register is 65535. Based on this knowledge we can calculate a prescaler that will be large enough to limit the output compare register value to a maximum of 65535. Overall, when we multiply the calculated period with the microcontroller its oscillating frequency (F_CPU) and divide that value with 65535, we get an exact number of the needed prescaler to limit the output compare timer register to its maximum possible value. Because it ain't possible to use such exact prescaler we need to round up that value to the closest and greatest available prescaler. Both the microcontroller its prescaler values and select bits are defined in an array of the struct type Prescaler. Eventually the
calcPrescaler() will return a single struct item of this array. This prescaler is then further used to calculate the needed output compare register value to match the desired refresh frequency and to set the prescaler bits of the TCCR1B register as can be seen in the code below.
typedef struct
{
uint16_t value;
uint8_t selectBits;
} Prescaler;
const Prescaler LedCube::_prescalers[] = { {1, 0b001}, {8, 0b010}, {64, 0b011}, {256, 0b100}, {1024, 0b101} }; // Largest prescaler must be the last item.
// Initialize timer interrupt
void LedCube::initInterrupt(void)
{
// Calculate prescaler and timer counter
double period = 1.0 / _refreshFrequency;
Prescaler prescaler = calcPrescaler(period);
uint16_t timerCounter = period / ((double)prescaler.value / F_CPU);
// Set timer parameters
TCNT1 = 0;
TCCR1A = 0;
TCCR1B = prescaler.selectBits;
OCR1A = timerCounter;
TCCR1B |= (1 << WGM12);
TIMSK1 |= (1 << OCIE1A);
}
// Prescaler calculation
Prescaler LedCube::calcPrescaler(double period)
{
uint16_t pr = ceil((F_CPU * period) / 65535.0);
for (uint8_t i = 0; i < PRESCALER_ARR_SIZE; i++)
{
if (pr <= _prescalers[i].value)
return _prescalers[i];
}
// At this point return the largest available prescaler
return _prescalers[PRESCALER_ARR_SIZE - 1];
}
To map all the states of the LEDs I use a single 1 dimensional array of bytes. Because the size isn't known at compile we need to allocate the memory at run time. To determine the total size of this array we need to know how many bytes are needed to store a single layer of the LED cube and multiply this with the total number of layers in the cube. For example, we have a 5x5x5 LED cube. We know that one layer of this cube contains 25 LEDs, thus 25 bits need to be stored. To store 25 bits we have to use atleast 4 bytes of data. To store all the 5 layers we then need 4 * 5 = 20 bytes of data. This memory is allocated (and initialized with zeros) with the code below. The manipulating of the bits of this array is discussed later in this paragraph.
// Allocate memory needed to map the voxels of the cube
void LedCube::allocateVoxelMapping(void)
{
if (_allocated)
return;
// Allocate 1 dimensional array
uint8_t size = _layerSize * _cubeSize; // Calculate max. needed bytes
_voxelMapping = (uint8_t *)calloc(size, sizeof(uint8_t)); // Allocate memory and initialize it with zeros
if (_voxelMapping == NULL)
abort();
_allocated = true; // At this point the memory is allocated properly
}
END// Stop displaying animations on the cube
void LedCube::end(void)
{
TIMSK1 &= ~(1 << OCIE1A); // Disable timer
SPI.end();
digitalWriteFast(_clearPin, HIGH); // Clear all data
// Free allocated memory
if (_allocated)
{
free(_voxelMapping);
_allocated = false;
}
}
To stop refreshing the cube and to free the SPI, timer1 and memory you can call the function end(). There's not much to explain about this code so lets go to next paragraph

.
VOXEL// Set voxel (z,y,x) state
void LedCube::voxel(uint8_t z, uint8_t x, uint8_t y, bool state)
{
VoxelCoordinate currVoxel;
// Make sure given input isn't greater than maximum cube size
if (z >= _cubeSize)
currVoxel.z = _cubeSize - 1;
else
currVoxel.z = z;
if (x >= _cubeSize)
currVoxel.x = _cubeSize - 1;
else
currVoxel.x = x;
if (y >= _cubeSize)
currVoxel.y = _cubeSize - 1;
else
currVoxel.y = y;
setVoxel(currVoxel, state); // Set actual voxel value
}
To turn a LED on or off we can call the voxel(). This function accepts 4 parameters: z, x, y and state. The position is zero index based, so if you have a 5x5x5 LED cube the values can range from 0 to 4. In the code above we make sure you do not exceed the maximum value, the actual setting or clearing of a voxel happens in the setVoxel() function. This function accepts a instance of the struct type VoxelCoordinate and a state. As previously mentioned I use a byte array to store each LED state value. This byte array can be visually represented in the image below.

Now lets take focus on one single block of bytes. As you can see each block contains 4 bytes. This is the minimum to store 25 LED states (one single layer of a 5x5x5 cube). Let's say that the columns of a block can be defined as the x positions and the rows as the y positions. Then every new block (layer 0, layer 1, layer 2, etc....) can be defined as the z position. With that we only need to know which byte and bit of that byte we need to manipulate to turn on/off a voxel. For example, we want to turn on a voxel with the coordinate z = 0, x = 2 and y = 3. That means that bit 5 of the lighter green column of layer 0 should be set to a '1'. So to manipulate this particular value we need to know which byte of the voxel mapping array it is located in. We also need to know how many positions we need to shift the '1' to the left. To do this we start looking at the led position from a single layer. The led position in any given layer will be
position = y-coordinate + x-coordinate * (1 dimensional cube size) = 3 + 2 * 5 = 13. With this value we can calculate both which byte we need to address and the number of positions we need to shift the '1' bit to the left. So,
array index = position / byte-size = 13 / 8 = 1.625 = 1 (integer value) and
array shift = position % byte-size = 13 % 8 = 5. And this are exactly the values (zero based) we need to know to address bit 5 of the lighter green column of the image. To correct for the z coordinate we simply add an offset to the calculated array index value. This means, for a 5x5x5 cube, that each layer adds an offset of 4 bytes.
typedef struct
{
uint8_t z;
uint8_t x;
uint8_t y;
} VoxelCoordinate;
inline void LedCube::setVoxel(VoxelCoordinate voxelCoordinate, bool state)
{
// Return if memory isn't allocated
if (!_allocated)
return;
uint16_t voxelPos = voxelCoordinate.y + voxelCoordinate.x * _cubeSize; // Calculate the position of the voxel from a single layer
uint8_t arrPos = voxelPos / 8 + voxelCoordinate.z * _layerSize; // Calculate index of the voxelmapping array based on the position of the voxel
uint8_t arrShift = voxelPos % 8; // Calculate the needed shift to set the correct bit
if (state)
_voxelMapping[arrPos] |= (1 << arrShift); // Set bit to '1'
else
_voxelMapping[arrPos] &= ~(1 << arrShift); // Set bit to '0'
}
REFRESH DATAvoid LedCube::refreshData(void)
{
digitalWriteFast(_clearPin, HIGH); // Clear all data
for (uint8_t i = 0; i < _layerSize; i++)
SPI.transfer(_voxelMapping[i + _zPositionCounter * _layerSize]);
SPI.transfer((1 << _zPositionCounter)); // Current layer byte
// Latch the data
digitalWriteFast(_latchPin, HIGH);
digitalWriteFast(_latchPin, LOW);
digitalWriteFast(_clearPin, LOW);
_zPositionCounter++; // Increment counter to the next layer
if (_zPositionCounter >= _cubeSize)
_zPositionCounter = 0;
}