Subversion Repositories Code-Repo

Rev

Rev 263 | Blame | Last modification | View Log | RSS feed

#include "defines.h"
#include "CUBE.h"
#include "SPI1.h"
#include "glcdfont.h"
#include "UART1.h"
#include "ETHERNET.h"
#include "TIMER4.h"

static CUBE_DATA *cube_data_ptr;

inline void Cube_Delay() {
    // Small delay to ensure that latch speeds are < 30Mhz
    Nop();
    Nop();
    Nop();
}

void Cube_Init(CUBE_DATA *data, uint8_t BC) {
    cube_data_ptr = data;
    cube_data_ptr->current_layer = 0;
    cube_data_ptr->rotation_counter = 0;
    cube_data_ptr->frame_state = IDLE;
    cube_data_ptr->frame_escape = 0;

    SFT_D = 0;
    SFT_S = 0;
    SFT_K = 0;
    SFT_R = 0;
    GSLAT = 0;
    XBLNK = 0;
    
    SFT_D_TRIS = 0;
    SFT_S_TRIS = 0;
    SFT_K_TRIS = 0;
    SFT_R_TRIS = 0;
    GSLAT_TRIS = 0;
    XBLNK_TRIS = 0;

    // Clear the shift register
    Cube_Delay();
    SFT_K = 1;
    Cube_Delay();
    SFT_K = 0;
    Cube_Delay();
    SFT_S = 1;
    Cube_Delay();
    SFT_S = 0;
    Cube_Delay();
    SFT_R = 1;

    Cube_Write_DCS(BC);
    Cube_Clear();
    Cube_Overlay_Clear();
}

void Cube_Timer_Interrupt(void) {
    // OR values in the overlay array with the display array
    uint8_t i;
    uint16_t j;
    for (i = 0; i < CUBE_LAYER_COUNT; i++) {
        for (j = 0; j < GCS_LAYER_SIZE; j++) {
            cube_data_ptr->GCS_WRITE[i][j] = cube_data_ptr->GCS[i][j] | cube_data_ptr->GCS_OVERLAY[i][j];
        }
    }
    // Write to the GCS register
    SPI1_Write(cube_data_ptr->GCS_WRITE[cube_data_ptr->current_layer], GCS_LAYER_SIZE, &Cube_GCS_Write_Callback);
}

////////////////////////
// Callback functions //
////////////////////////

void Cube_DCS_Write_Callback(void) {
    // GSLAT must be >7ms after DCS write
    Delay_MS(7);
    GSLAT = 0;
    Cube_Delay();
    GSLAT = 1;
    Cube_Delay();
    GSLAT = 0;
}

void Cube_GCS_Write_Callback(void) {
    // Disable LED output and latch in written data to GCS
    XBLNK = 0;
    Cube_Delay();
    GSLAT = 1;
    // Set the shift register to turn on the current layer
    uint8_t i;
    for (i = 0; i < CUBE_LAYER_COUNT; i++) {
        Cube_Delay();
        SFT_D = (i == CUBE_LAYER_COUNT - cube_data_ptr->current_layer - 1) ? 1 : 0;
        Cube_Delay();
        SFT_K = 1;
        Cube_Delay();
        SFT_K = 0;
    }
    Cube_Delay();
    SFT_S = 1;
    Cube_Delay();
    SFT_S = 0;
    Cube_Delay();
    // Enable LED output
    XBLNK = 1;
    Cube_Delay();
    GSLAT = 0;

    cube_data_ptr->current_layer = (cube_data_ptr->current_layer == CUBE_LAYER_COUNT-1)
            ? 0 : cube_data_ptr->current_layer + 1;
}

////////////////////////////
// Cube control functions //
////////////////////////////

void Cube_Write_DCS(uint8_t BC) {
    XBLNK = 0;
    uint8_t i,j;
    // Write configuration data to the DC/BC/FC/UD registers
    uint8_t DCS[GCS_LAYER_SIZE] = {0};

    for (i = 0; i < 8; i++) {
        uint16_t offset = i * GCS_REG_SIZE;

        for (j = 0; j < 21; j++) {
            DCS[offset + j] = 0xFF; // Dot correction
        }

        // Warning: do not set BC > 0x6F ?? NEED TO VERIFY THIS !!
        DCS[offset + 21] = BC; // Global red brightness
        DCS[offset + 22] = BC; // Global green brightness
        DCS[offset + 23] = BC; // Global blue brightness

        // DC low range, auto repeat, no timing reset, 8 bit counter mode
        DCS[offset + 24] = 0x68; // 0110 1000
    }
    
    GSLAT = 1;
    SPI1_Write(DCS, GCS_LAYER_SIZE, &Cube_DCS_Write_Callback);
    Delay_MS(10); // Delay until the entire DCS write is finished
}

void Cube_Clear(void) {
    uint8_t i;
    uint16_t j;
    for (i = 0; i < CUBE_LAYER_COUNT; i++)
        for (j = 0; j < GCS_LAYER_SIZE; j++)
            cube_data_ptr->GCS[i][j] = 0x00;
}

void Cube_Set_All(uint16_t R, uint16_t G, uint16_t B) {
    // Set all pixels in the cube to the given color
    R &= 0x0FFF;
    G &= 0x0FFF;
    B &= 0x0FFF;
    uint8_t i,j,k;
    for (i = 0; i < CUBE_LAYER_COUNT; i++) {
        for (j = 0; j < CUBE_ROW_COUNT; j++) {
            uint16_t j_var = j * GCS_REG_SIZE;
            for (k = 0; k < 4; k++) {
                uint16_t k_var = j_var + (k * 9);
                cube_data_ptr->GCS[i][k_var+0] = R & 0xFF;;
                cube_data_ptr->GCS[i][k_var+1] = (G << 4) | (R >> 8);
                cube_data_ptr->GCS[i][k_var+2] = G >> 4;
                cube_data_ptr->GCS[i][k_var+3] = B & 0xFF;
                cube_data_ptr->GCS[i][k_var+4] = (R << 4) | (B >> 8);
                cube_data_ptr->GCS[i][k_var+5] = R >> 4;
                cube_data_ptr->GCS[i][k_var+6] = G & 0xFF;
                cube_data_ptr->GCS[i][k_var+7] = (B << 4) | (G >> 8);
                cube_data_ptr->GCS[i][k_var+8] = B >> 4;
            }
        }
    }
}

void Cube_Set_Layer(uint8_t layer, uint16_t R, uint16_t G, uint16_t B) {
    // Set all pixels in the specified layer to the given color
    R &= 0x0FFF;
    G &= 0x0FFF;
    B &= 0x0FFF;
    uint8_t i,j;
    for (i = 0; i < CUBE_ROW_COUNT; i++) {
        uint16_t i_var = i * GCS_REG_SIZE;
        for (j = 0; j < 4; j++) {
            uint16_t j_var = i_var + (j * 9);
            cube_data_ptr->GCS[layer][j_var+0] = R & 0xFF;;
            cube_data_ptr->GCS[layer][j_var+1] = (G << 4) | (R >> 8);
            cube_data_ptr->GCS[layer][j_var+2] = G >> 4;
            cube_data_ptr->GCS[layer][j_var+3] = B & 0xFF;
            cube_data_ptr->GCS[layer][j_var+4] = (R << 4) | (B >> 8);
            cube_data_ptr->GCS[layer][j_var+5] = R >> 4;
            cube_data_ptr->GCS[layer][j_var+6] = G & 0xFF;
            cube_data_ptr->GCS[layer][j_var+7] = (B << 4) | (G >> 8);
            cube_data_ptr->GCS[layer][j_var+8] = B >> 4;
        }
    }
}

void Cube_Set_Row(uint8_t row, uint16_t R, uint16_t G, uint16_t B) {
    // Set the specified row to the given color
    R &= 0x0FFF;
    G &= 0x0FFF;
    B &= 0x0FFF;
    uint8_t column, layer;
    for (layer = 0; layer < CUBE_LAYER_COUNT; layer++) {
        for (column = 0; column < CUBE_COLUMN_COUNT; column++) {
            uint16_t var = row * GCS_REG_SIZE + (column / 2 * 9);
            switch (column % 2) {
                case 0:
                    cube_data_ptr->GCS[layer][var+0] = R & 0xFF;
                    cube_data_ptr->GCS[layer][var+1] = (G << 4) | (R >> 8);
                    cube_data_ptr->GCS[layer][var+2] = G >> 4;
                    cube_data_ptr->GCS[layer][var+3] = B & 0xFF;
                    cube_data_ptr->GCS[layer][var+4] = (cube_data_ptr->GCS[layer][var+4] & 0xF0) | (B >> 8);
                    break;
                case 1:
                    cube_data_ptr->GCS[layer][var+4] = (cube_data_ptr->GCS[layer][var+4] & 0x0F) | (R << 4);
                    cube_data_ptr->GCS[layer][var+5] = R >> 4;
                    cube_data_ptr->GCS[layer][var+6] = G & 0xFF;
                    cube_data_ptr->GCS[layer][var+7] = (B << 4) | (G >> 8);
                    cube_data_ptr->GCS[layer][var+8] = B >> 4;
                    break;
            }       
        }
    }
}

void Cube_Set_Column(uint8_t column, uint16_t R, uint16_t G, uint16_t B) {
    // Set the specified row to the given color
    R &= 0x0FFF;
    G &= 0x0FFF;
    B &= 0x0FFF;
    uint8_t row, layer;
    for (layer = 0; layer < CUBE_LAYER_COUNT; layer++) {
        for (row = 0; row < CUBE_COLUMN_COUNT; row++) {
            uint16_t var = row * GCS_REG_SIZE + (column / 2 * 9);
            switch (column % 2) {
                case 0:
                    cube_data_ptr->GCS[layer][var+0] = R & 0xFF;
                    cube_data_ptr->GCS[layer][var+1] = (G << 4) | (R >> 8);
                    cube_data_ptr->GCS[layer][var+2] = G >> 4;
                    cube_data_ptr->GCS[layer][var+3] = B & 0xFF;
                    cube_data_ptr->GCS[layer][var+4] = (cube_data_ptr->GCS[layer][var+4] & 0xF0) | (B >> 8);
                    break;
                case 1:
                    cube_data_ptr->GCS[layer][var+4] = (cube_data_ptr->GCS[layer][var+4] & 0x0F) | (R << 4);
                    cube_data_ptr->GCS[layer][var+5] = R >> 4;
                    cube_data_ptr->GCS[layer][var+6] = G & 0xFF;
                    cube_data_ptr->GCS[layer][var+7] = (B << 4) | (G >> 8);
                    cube_data_ptr->GCS[layer][var+8] = B >> 4;
                    break;
            }
        }
    }
}

void Cube_Set_Pixel(uint8_t layer, uint8_t row, uint8_t column, uint16_t R, uint16_t G, uint16_t B) {
    // Set the specified pixel to the given color
    R &= 0x0FFF;
    G &= 0x0FFF;
    B &= 0x0FFF;
    uint16_t var = row * GCS_REG_SIZE + (column / 2 * 9);
    switch (column % 2) {
        case 0:
            cube_data_ptr->GCS[layer][var+0] = R & 0xFF;
            cube_data_ptr->GCS[layer][var+1] = (G << 4) | (R >> 8);
            cube_data_ptr->GCS[layer][var+2] = G >> 4;
            cube_data_ptr->GCS[layer][var+3] = B & 0xFF;
            cube_data_ptr->GCS[layer][var+4] = (cube_data_ptr->GCS[layer][var+4] & 0xF0) | (B >> 8);
            break;
        case 1:
            cube_data_ptr->GCS[layer][var+4] = (cube_data_ptr->GCS[layer][var+4] & 0x0F) | (R << 4);
            cube_data_ptr->GCS[layer][var+5] = R >> 4;
            cube_data_ptr->GCS[layer][var+6] = G & 0xFF;
            cube_data_ptr->GCS[layer][var+7] = (B << 4) | (G >> 8);
            cube_data_ptr->GCS[layer][var+8] = B >> 4;
            break;
    }
}

void Cube_Get_Pixel(uint8_t layer, uint8_t row, uint8_t column, uint16_t* R, uint16_t* G, uint16_t* B) {
    uint16_t var = row * GCS_REG_SIZE + (column / 2 * 9);
    switch (column % 2) {
        // Concatenate lower byte and upper byte of each color channel
        case 0:
            *R = cube_data_ptr->GCS[layer][var+0] | ((cube_data_ptr->GCS[layer][var+1] & 0x0F) << 8);
            *G = (cube_data_ptr->GCS[layer][var+1] >> 4) | (cube_data_ptr->GCS[layer][var+2] << 4);
            *B = cube_data_ptr->GCS[layer][var+3] | ((cube_data_ptr->GCS[layer][var+4] & 0x0F) << 8);
            break;
        case 1:
            *R = (cube_data_ptr->GCS[layer][var+4] >> 4) | (cube_data_ptr->GCS[layer][var+5] << 4);
            *G = cube_data_ptr->GCS[layer][var+6] | ((cube_data_ptr->GCS[layer][var+7] & 0x0F) << 8);
            *B = (cube_data_ptr->GCS[layer][var+7] >> 4) | (cube_data_ptr->GCS[layer][var+8] << 4);
            break;
    }
}

void Cube_Move_Pixel(uint8_t layer1, uint8_t row1, uint8_t column1, uint8_t layer2, uint8_t row2, uint8_t column2) {
    // Copies data from pixel 1 to pixel 2
    // Note: destination pixel value is overwritten
    uint16_t prev_R, prev_G, prev_B;
    Cube_Get_Pixel(layer1, row1, column1, &prev_R, &prev_G, &prev_B);
    Cube_Set_Pixel(layer2, row2, column2, prev_R, prev_G, prev_B);
}

void Cube_Set_Sphere(uint8_t layer, uint8_t R, uint8_t G, uint8_t B) {
    // Super code inefficient (or is it?) lookup table
    switch (layer) {
        case 0:
            Cube_Set_Pixel(3, 3, 3, R, G, B);
            Cube_Set_Pixel(3, 3, 4, R, G, B);
            Cube_Set_Pixel(3, 4, 3, R, G, B);
            Cube_Set_Pixel(3, 4, 4, R, G, B);
            Cube_Set_Pixel(4, 3, 3, R, G, B);
            Cube_Set_Pixel(4, 3, 4, R, G, B);
            Cube_Set_Pixel(4, 4, 3, R, G, B);
            Cube_Set_Pixel(4, 4, 4, R, G, B);
            break;
        case 1:
            Cube_Set_Pixel(2, 3, 3, R, G, B);
            Cube_Set_Pixel(2, 3, 4, R, G, B);
            Cube_Set_Pixel(2, 4, 3, R, G, B);
            Cube_Set_Pixel(2, 4, 4, R, G, B);

            Cube_Set_Pixel(5, 3, 3, R, G, B);
            Cube_Set_Pixel(5, 3, 4, R, G, B);
            Cube_Set_Pixel(5, 4, 3, R, G, B);
            Cube_Set_Pixel(5, 4, 4, R, G, B);

            Cube_Set_Pixel(3, 2, 3, R, G, B);
            Cube_Set_Pixel(3, 2, 4, R, G, B);
            Cube_Set_Pixel(3, 5, 3, R, G, B);
            Cube_Set_Pixel(3, 5, 4, R, G, B);
            Cube_Set_Pixel(3, 3, 2, R, G, B);
            Cube_Set_Pixel(3, 4, 2, R, G, B);
            Cube_Set_Pixel(3, 3, 5, R, G, B);
            Cube_Set_Pixel(3, 4, 5, R, G, B);

            Cube_Set_Pixel(4, 2, 3, R, G, B);
            Cube_Set_Pixel(4, 2, 4, R, G, B);
            Cube_Set_Pixel(4, 5, 3, R, G, B);
            Cube_Set_Pixel(4, 5, 4, R, G, B);
            Cube_Set_Pixel(4, 3, 2, R, G, B);
            Cube_Set_Pixel(4, 4, 2, R, G, B);
            Cube_Set_Pixel(4, 3, 5, R, G, B);
            Cube_Set_Pixel(4, 4, 5, R, G, B);
            break;
        case 2:
            Cube_Set_Pixel(1, 3, 3, R, G, B);
            Cube_Set_Pixel(1, 3, 4, R, G, B);
            Cube_Set_Pixel(1, 4, 3, R, G, B);
            Cube_Set_Pixel(1, 4, 4, R, G, B);
            
            Cube_Set_Pixel(6, 3, 3, R, G, B);
            Cube_Set_Pixel(6, 3, 4, R, G, B);
            Cube_Set_Pixel(6, 4, 3, R, G, B);
            Cube_Set_Pixel(6, 4, 4, R, G, B);

            Cube_Set_Pixel(3, 1, 3, R, G, B);
            Cube_Set_Pixel(3, 1, 4, R, G, B);
            Cube_Set_Pixel(3, 6, 3, R, G, B);
            Cube_Set_Pixel(3, 6, 4, R, G, B);
            Cube_Set_Pixel(3, 3, 1, R, G, B);
            Cube_Set_Pixel(3, 4, 1, R, G, B);
            Cube_Set_Pixel(3, 3, 6, R, G, B);
            Cube_Set_Pixel(3, 4, 6, R, G, B);

            Cube_Set_Pixel(3, 2, 2, R, G, B);
            Cube_Set_Pixel(3, 2, 5, R, G, B);
            Cube_Set_Pixel(3, 5, 5, R, G, B);
            Cube_Set_Pixel(3, 5, 2, R, G, B);

            Cube_Set_Pixel(4, 1, 3, R, G, B);
            Cube_Set_Pixel(4, 1, 4, R, G, B);
            Cube_Set_Pixel(4, 6, 3, R, G, B);
            Cube_Set_Pixel(4, 6, 4, R, G, B);
            Cube_Set_Pixel(4, 3, 1, R, G, B);
            Cube_Set_Pixel(4, 4, 1, R, G, B);
            Cube_Set_Pixel(4, 3, 6, R, G, B);
            Cube_Set_Pixel(4, 4, 6, R, G, B);

            Cube_Set_Pixel(4, 2, 2, R, G, B);
            Cube_Set_Pixel(4, 2, 5, R, G, B);
            Cube_Set_Pixel(4, 5, 5, R, G, B);
            Cube_Set_Pixel(4, 5, 2, R, G, B);

            Cube_Set_Pixel(2, 3, 2, R, G, B);
            Cube_Set_Pixel(2, 4, 2, R, G, B);
            Cube_Set_Pixel(2, 3, 5, R, G, B);
            Cube_Set_Pixel(2, 4, 5, R, G, B);
            Cube_Set_Pixel(2, 2, 3, R, G, B);
            Cube_Set_Pixel(2, 2, 4, R, G, B);
            Cube_Set_Pixel(2, 5, 3, R, G, B);
            Cube_Set_Pixel(2, 5, 4, R, G, B);

            Cube_Set_Pixel(2, 2, 2, R, G, B);
            Cube_Set_Pixel(2, 2, 5, R, G, B);
            Cube_Set_Pixel(2, 5, 2, R, G, B);
            Cube_Set_Pixel(2, 5, 5, R, G, B);

            Cube_Set_Pixel(5, 3, 2, R, G, B);
            Cube_Set_Pixel(5, 4, 2, R, G, B);
            Cube_Set_Pixel(5, 3, 5, R, G, B);
            Cube_Set_Pixel(5, 4, 5, R, G, B);
            Cube_Set_Pixel(5, 2, 3, R, G, B);
            Cube_Set_Pixel(5, 2, 4, R, G, B);
            Cube_Set_Pixel(5, 5, 3, R, G, B);
            Cube_Set_Pixel(5, 5, 4, R, G, B);

            Cube_Set_Pixel(5, 2, 2, R, G, B);
            Cube_Set_Pixel(5, 2, 5, R, G, B);
            Cube_Set_Pixel(5, 5, 2, R, G, B);
            Cube_Set_Pixel(5, 5, 5, R, G, B);
            break;
        case 3:
            Cube_Set_Pixel(0, 3, 3, R, G, B);
            Cube_Set_Pixel(0, 3, 4, R, G, B);
            Cube_Set_Pixel(0, 4, 3, R, G, B);
            Cube_Set_Pixel(0, 4, 4, R, G, B);
            Cube_Set_Pixel(7, 3, 3, R, G, B);
            Cube_Set_Pixel(7, 3, 4, R, G, B);
            Cube_Set_Pixel(7, 4, 3, R, G, B);
            Cube_Set_Pixel(7, 4, 4, R, G, B);

            Cube_Set_Pixel(3, 0, 3, R, G, B);
            Cube_Set_Pixel(3, 0, 4, R, G, B);
            Cube_Set_Pixel(3, 7, 3, R, G, B);
            Cube_Set_Pixel(3, 7, 4, R, G, B);
            Cube_Set_Pixel(3, 3, 0, R, G, B);
            Cube_Set_Pixel(3, 4, 0, R, G, B);
            Cube_Set_Pixel(3, 3, 7, R, G, B);
            Cube_Set_Pixel(3, 4, 7, R, G, B);

            Cube_Set_Pixel(3, 2, 6, R, G, B);
            Cube_Set_Pixel(3, 1, 5, R, G, B);
            Cube_Set_Pixel(3, 6, 2, R, G, B);
            Cube_Set_Pixel(3, 5, 1, R, G, B);
            Cube_Set_Pixel(3, 1, 2, R, G, B);
            Cube_Set_Pixel(3, 2, 1, R, G, B);
            Cube_Set_Pixel(3, 6, 5, R, G, B);
            Cube_Set_Pixel(3, 5, 6, R, G, B);

            Cube_Set_Pixel(4, 0, 3, R, G, B);
            Cube_Set_Pixel(4, 0, 4, R, G, B);
            Cube_Set_Pixel(4, 7, 3, R, G, B);
            Cube_Set_Pixel(4, 7, 4, R, G, B);
            Cube_Set_Pixel(4, 3, 0, R, G, B);
            Cube_Set_Pixel(4, 4, 0, R, G, B);
            Cube_Set_Pixel(4, 3, 7, R, G, B);
            Cube_Set_Pixel(4, 4, 7, R, G, B);

            Cube_Set_Pixel(4, 2, 6, R, G, B);
            Cube_Set_Pixel(4, 1, 5, R, G, B);
            Cube_Set_Pixel(4, 6, 2, R, G, B);
            Cube_Set_Pixel(4, 5, 1, R, G, B);
            Cube_Set_Pixel(4, 1, 2, R, G, B);
            Cube_Set_Pixel(4, 2, 1, R, G, B);
            Cube_Set_Pixel(4, 6, 5, R, G, B);
            Cube_Set_Pixel(4, 5, 6, R, G, B);

            Cube_Set_Pixel(1, 2, 5, R, G, B);
            Cube_Set_Pixel(1, 2, 4, R, G, B);
            Cube_Set_Pixel(1, 2, 3, R, G, B);
            Cube_Set_Pixel(1, 2, 2, R, G, B);
            Cube_Set_Pixel(1, 3, 5, R, G, B);
            Cube_Set_Pixel(1, 3, 2, R, G, B);
            Cube_Set_Pixel(1, 4, 5, R, G, B);
            Cube_Set_Pixel(1, 4, 2, R, G, B);
            Cube_Set_Pixel(1, 5, 5, R, G, B);
            Cube_Set_Pixel(1, 5, 4, R, G, B);
            Cube_Set_Pixel(1, 5, 3, R, G, B);
            Cube_Set_Pixel(1, 5, 2, R, G, B);

            Cube_Set_Pixel(2, 1, 5, R, G, B);
            Cube_Set_Pixel(2, 1, 4, R, G, B);
            Cube_Set_Pixel(2, 1, 3, R, G, B);
            Cube_Set_Pixel(2, 1, 2, R, G, B);
            Cube_Set_Pixel(2, 6, 5, R, G, B);
            Cube_Set_Pixel(2, 6, 4, R, G, B);
            Cube_Set_Pixel(2, 6, 3, R, G, B);
            Cube_Set_Pixel(2, 6, 2, R, G, B);
            Cube_Set_Pixel(2, 2, 6, R, G, B);
            Cube_Set_Pixel(2, 3, 6, R, G, B);
            Cube_Set_Pixel(2, 4, 6, R, G, B);
            Cube_Set_Pixel(2, 5, 6, R, G, B);
            Cube_Set_Pixel(2, 2, 1, R, G, B);
            Cube_Set_Pixel(2, 3, 1, R, G, B);
            Cube_Set_Pixel(2, 4, 1, R, G, B);
            Cube_Set_Pixel(2, 5, 1, R, G, B);

            Cube_Set_Pixel(5, 1, 5, R, G, B);
            Cube_Set_Pixel(5, 1, 4, R, G, B);
            Cube_Set_Pixel(5, 1, 3, R, G, B);
            Cube_Set_Pixel(5, 1, 2, R, G, B);
            Cube_Set_Pixel(5, 6, 5, R, G, B);
            Cube_Set_Pixel(5, 6, 4, R, G, B);
            Cube_Set_Pixel(5, 6, 3, R, G, B);
            Cube_Set_Pixel(5, 6, 2, R, G, B);
            Cube_Set_Pixel(5, 2, 6, R, G, B);
            Cube_Set_Pixel(5, 3, 6, R, G, B);
            Cube_Set_Pixel(5, 4, 6, R, G, B);
            Cube_Set_Pixel(5, 5, 6, R, G, B);
            Cube_Set_Pixel(5, 2, 1, R, G, B);
            Cube_Set_Pixel(5, 3, 1, R, G, B);
            Cube_Set_Pixel(5, 4, 1, R, G, B);
            Cube_Set_Pixel(5, 5, 1, R, G, B);

            Cube_Set_Pixel(6, 2, 5, R, G, B);
            Cube_Set_Pixel(6, 2, 4, R, G, B);
            Cube_Set_Pixel(6, 2, 3, R, G, B);
            Cube_Set_Pixel(6, 2, 2, R, G, B);
            Cube_Set_Pixel(6, 3, 5, R, G, B);
            Cube_Set_Pixel(6, 3, 2, R, G, B);
            Cube_Set_Pixel(6, 4, 5, R, G, B);
            Cube_Set_Pixel(6, 4, 2, R, G, B);
            Cube_Set_Pixel(6, 5, 5, R, G, B);
            Cube_Set_Pixel(6, 5, 4, R, G, B);
            Cube_Set_Pixel(6, 5, 3, R, G, B);
            Cube_Set_Pixel(6, 5, 2, R, G, B);
            break;
        case 4:
            Cube_Set_Pixel(0, 2, 5, R, G, B);
            Cube_Set_Pixel(0, 2, 4, R, G, B);
            Cube_Set_Pixel(0, 2, 3, R, G, B);
            Cube_Set_Pixel(0, 2, 2, R, G, B);
            Cube_Set_Pixel(0, 3, 5, R, G, B);
            Cube_Set_Pixel(0, 3, 2, R, G, B);
            Cube_Set_Pixel(0, 4, 5, R, G, B);
            Cube_Set_Pixel(0, 4, 2, R, G, B);
            Cube_Set_Pixel(0, 5, 5, R, G, B);
            Cube_Set_Pixel(0, 5, 4, R, G, B);
            Cube_Set_Pixel(0, 5, 3, R, G, B);
            Cube_Set_Pixel(0, 5, 2, R, G, B);

            Cube_Set_Pixel(7, 2, 5, R, G, B);
            Cube_Set_Pixel(7, 2, 4, R, G, B);
            Cube_Set_Pixel(7, 2, 3, R, G, B);
            Cube_Set_Pixel(7, 2, 2, R, G, B);
            Cube_Set_Pixel(7, 3, 5, R, G, B);
            Cube_Set_Pixel(7, 3, 2, R, G, B);
            Cube_Set_Pixel(7, 4, 5, R, G, B);
            Cube_Set_Pixel(7, 4, 2, R, G, B);
            Cube_Set_Pixel(7, 5, 5, R, G, B);
            Cube_Set_Pixel(7, 5, 4, R, G, B);
            Cube_Set_Pixel(7, 5, 3, R, G, B);
            Cube_Set_Pixel(7, 5, 2, R, G, B);

            Cube_Set_Pixel(1, 1, 5, R, G, B);
            Cube_Set_Pixel(1, 1, 4, R, G, B);
            Cube_Set_Pixel(1, 1, 3, R, G, B);
            Cube_Set_Pixel(1, 1, 2, R, G, B);
            Cube_Set_Pixel(1, 6, 5, R, G, B);
            Cube_Set_Pixel(1, 6, 4, R, G, B);
            Cube_Set_Pixel(1, 6, 3, R, G, B);
            Cube_Set_Pixel(1, 6, 2, R, G, B);
            Cube_Set_Pixel(1, 2, 6, R, G, B);
            Cube_Set_Pixel(1, 3, 6, R, G, B);
            Cube_Set_Pixel(1, 4, 6, R, G, B);
            Cube_Set_Pixel(1, 5, 6, R, G, B);
            Cube_Set_Pixel(1, 2, 1, R, G, B);
            Cube_Set_Pixel(1, 3, 1, R, G, B);
            Cube_Set_Pixel(1, 4, 1, R, G, B);
            Cube_Set_Pixel(1, 5, 1, R, G, B);

            Cube_Set_Pixel(6, 1, 5, R, G, B);
            Cube_Set_Pixel(6, 1, 4, R, G, B);
            Cube_Set_Pixel(6, 1, 3, R, G, B);
            Cube_Set_Pixel(6, 1, 2, R, G, B);
            Cube_Set_Pixel(6, 6, 5, R, G, B);
            Cube_Set_Pixel(6, 6, 4, R, G, B);
            Cube_Set_Pixel(6, 6, 3, R, G, B);
            Cube_Set_Pixel(6, 6, 2, R, G, B);
            Cube_Set_Pixel(6, 2, 6, R, G, B);
            Cube_Set_Pixel(6, 3, 6, R, G, B);
            Cube_Set_Pixel(6, 4, 6, R, G, B);
            Cube_Set_Pixel(6, 5, 6, R, G, B);
            Cube_Set_Pixel(6, 2, 1, R, G, B);
            Cube_Set_Pixel(6, 3, 1, R, G, B);
            Cube_Set_Pixel(6, 4, 1, R, G, B);
            Cube_Set_Pixel(6, 5, 1, R, G, B);

            Cube_Set_Pixel(2, 0, 5, R, G, B);
            Cube_Set_Pixel(2, 0, 4, R, G, B);
            Cube_Set_Pixel(2, 0, 3, R, G, B);
            Cube_Set_Pixel(2, 0, 2, R, G, B);
            Cube_Set_Pixel(2, 7, 5, R, G, B);
            Cube_Set_Pixel(2, 7, 4, R, G, B);
            Cube_Set_Pixel(2, 7, 3, R, G, B);
            Cube_Set_Pixel(2, 7, 2, R, G, B);
            Cube_Set_Pixel(2, 5, 0, R, G, B);
            Cube_Set_Pixel(2, 4, 0, R, G, B);
            Cube_Set_Pixel(2, 3, 0, R, G, B);
            Cube_Set_Pixel(2, 2, 0, R, G, B);
            Cube_Set_Pixel(2, 5, 7, R, G, B);
            Cube_Set_Pixel(2, 4, 7, R, G, B);
            Cube_Set_Pixel(2, 3, 7, R, G, B);
            Cube_Set_Pixel(2, 2, 7, R, G, B);
            Cube_Set_Pixel(2, 1, 1, R, G, B);
            Cube_Set_Pixel(2, 1, 6, R, G, B);
            Cube_Set_Pixel(2, 6, 1, R, G, B);
            Cube_Set_Pixel(2, 6, 6, R, G, B);

            Cube_Set_Pixel(5, 0, 5, R, G, B);
            Cube_Set_Pixel(5, 0, 4, R, G, B);
            Cube_Set_Pixel(5, 0, 3, R, G, B);
            Cube_Set_Pixel(5, 0, 2, R, G, B);
            Cube_Set_Pixel(5, 7, 5, R, G, B);
            Cube_Set_Pixel(5, 7, 4, R, G, B);
            Cube_Set_Pixel(5, 7, 3, R, G, B);
            Cube_Set_Pixel(5, 7, 2, R, G, B);
            Cube_Set_Pixel(5, 5, 0, R, G, B);
            Cube_Set_Pixel(5, 4, 0, R, G, B);
            Cube_Set_Pixel(5, 3, 0, R, G, B);
            Cube_Set_Pixel(5, 2, 0, R, G, B);
            Cube_Set_Pixel(5, 5, 7, R, G, B);
            Cube_Set_Pixel(5, 4, 7, R, G, B);
            Cube_Set_Pixel(5, 3, 7, R, G, B);
            Cube_Set_Pixel(5, 2, 7, R, G, B);
            Cube_Set_Pixel(5, 1, 1, R, G, B);
            Cube_Set_Pixel(5, 1, 6, R, G, B);
            Cube_Set_Pixel(5, 6, 1, R, G, B);
            Cube_Set_Pixel(5, 6, 6, R, G, B);

            Cube_Set_Pixel(3, 0, 2, R, G, B);
            Cube_Set_Pixel(3, 0, 5, R, G, B);
            Cube_Set_Pixel(3, 2, 0, R, G, B);
            Cube_Set_Pixel(3, 5, 0, R, G, B);
            Cube_Set_Pixel(3, 7, 2, R, G, B);
            Cube_Set_Pixel(3, 7, 5, R, G, B);
            Cube_Set_Pixel(3, 2, 7, R, G, B);
            Cube_Set_Pixel(3, 5, 7, R, G, B);
            Cube_Set_Pixel(3, 1, 1, R, G, B);
            Cube_Set_Pixel(3, 1, 6, R, G, B);
            Cube_Set_Pixel(3, 6, 1, R, G, B);
            Cube_Set_Pixel(3, 6, 6, R, G, B);

            Cube_Set_Pixel(4, 0, 2, R, G, B);
            Cube_Set_Pixel(4, 0, 5, R, G, B);
            Cube_Set_Pixel(4, 2, 0, R, G, B);
            Cube_Set_Pixel(4, 5, 0, R, G, B);
            Cube_Set_Pixel(4, 7, 2, R, G, B);
            Cube_Set_Pixel(4, 7, 5, R, G, B);
            Cube_Set_Pixel(4, 2, 7, R, G, B);
            Cube_Set_Pixel(4, 5, 7, R, G, B);
            Cube_Set_Pixel(4, 1, 1, R, G, B);
            Cube_Set_Pixel(4, 1, 6, R, G, B);
            Cube_Set_Pixel(4, 6, 1, R, G, B);
            Cube_Set_Pixel(4, 6, 6, R, G, B);
            break;
        case 5:
            Cube_Set_Pixel(0, 1, 5, R, G, B);
            Cube_Set_Pixel(0, 1, 4, R, G, B);
            Cube_Set_Pixel(0, 1, 3, R, G, B);
            Cube_Set_Pixel(0, 1, 2, R, G, B);
            Cube_Set_Pixel(0, 6, 5, R, G, B);
            Cube_Set_Pixel(0, 6, 4, R, G, B);
            Cube_Set_Pixel(0, 6, 3, R, G, B);
            Cube_Set_Pixel(0, 6, 2, R, G, B);
            Cube_Set_Pixel(0, 2, 6, R, G, B);
            Cube_Set_Pixel(0, 3, 6, R, G, B);
            Cube_Set_Pixel(0, 4, 6, R, G, B);
            Cube_Set_Pixel(0, 5, 6, R, G, B);
            Cube_Set_Pixel(0, 2, 1, R, G, B);
            Cube_Set_Pixel(0, 3, 1, R, G, B);
            Cube_Set_Pixel(0, 4, 1, R, G, B);
            Cube_Set_Pixel(0, 5, 1, R, G, B);

            Cube_Set_Pixel(1, 0, 2, R, G, B);
            Cube_Set_Pixel(1, 0, 3, R, G, B);
            Cube_Set_Pixel(1, 0, 4, R, G, B);
            Cube_Set_Pixel(1, 0, 5, R, G, B);
            Cube_Set_Pixel(1, 1, 6, R, G, B);
            Cube_Set_Pixel(1, 2, 7, R, G, B);
            Cube_Set_Pixel(1, 3, 7, R, G, B);
            Cube_Set_Pixel(1, 4, 7, R, G, B);
            Cube_Set_Pixel(1, 5, 7, R, G, B);
            Cube_Set_Pixel(1, 6, 6, R, G, B);
            Cube_Set_Pixel(1, 7, 5, R, G, B);
            Cube_Set_Pixel(1, 7, 4, R, G, B);
            Cube_Set_Pixel(1, 7, 3, R, G, B);
            Cube_Set_Pixel(1, 7, 2, R, G, B);
            Cube_Set_Pixel(1, 6, 1, R, G, B);
            Cube_Set_Pixel(1, 5, 0, R, G, B);
            Cube_Set_Pixel(1, 4, 0, R, G, B);
            Cube_Set_Pixel(1, 3, 0, R, G, B);
            Cube_Set_Pixel(1, 2, 0, R, G, B);
            Cube_Set_Pixel(1, 1, 1, R, G, B);

            Cube_Set_Pixel(2, 0, 1, R, G, B);
            Cube_Set_Pixel(2, 1, 0, R, G, B);
            Cube_Set_Pixel(2, 0, 6, R, G, B);
            Cube_Set_Pixel(2, 1, 7, R, G, B);
            Cube_Set_Pixel(2, 6, 7, R, G, B);
            Cube_Set_Pixel(2, 7, 6, R, G, B);
            Cube_Set_Pixel(2, 6, 0, R, G, B);
            Cube_Set_Pixel(2, 7, 1, R, G, B);

            Cube_Set_Pixel(3, 0, 1, R, G, B);
            Cube_Set_Pixel(3, 1, 0, R, G, B);
            Cube_Set_Pixel(3, 0, 6, R, G, B);
            Cube_Set_Pixel(3, 1, 7, R, G, B);
            Cube_Set_Pixel(3, 6, 7, R, G, B);
            Cube_Set_Pixel(3, 7, 6, R, G, B);
            Cube_Set_Pixel(3, 6, 0, R, G, B);
            Cube_Set_Pixel(3, 7, 1, R, G, B);

            Cube_Set_Pixel(4, 0, 1, R, G, B);
            Cube_Set_Pixel(4, 1, 0, R, G, B);
            Cube_Set_Pixel(4, 0, 6, R, G, B);
            Cube_Set_Pixel(4, 1, 7, R, G, B);
            Cube_Set_Pixel(4, 6, 7, R, G, B);
            Cube_Set_Pixel(4, 7, 6, R, G, B);
            Cube_Set_Pixel(4, 6, 0, R, G, B);
            Cube_Set_Pixel(4, 7, 1, R, G, B);

            Cube_Set_Pixel(5, 0, 1, R, G, B);
            Cube_Set_Pixel(5, 1, 0, R, G, B);
            Cube_Set_Pixel(5, 0, 6, R, G, B);
            Cube_Set_Pixel(5, 1, 7, R, G, B);
            Cube_Set_Pixel(5, 6, 7, R, G, B);
            Cube_Set_Pixel(5, 7, 6, R, G, B);
            Cube_Set_Pixel(5, 6, 0, R, G, B);
            Cube_Set_Pixel(5, 7, 1, R, G, B);


            Cube_Set_Pixel(6, 0, 2, R, G, B);
            Cube_Set_Pixel(6, 0, 3, R, G, B);
            Cube_Set_Pixel(6, 0, 4, R, G, B);
            Cube_Set_Pixel(6, 0, 5, R, G, B);
            Cube_Set_Pixel(6, 1, 6, R, G, B);
            Cube_Set_Pixel(6, 2, 7, R, G, B);
            Cube_Set_Pixel(6, 3, 7, R, G, B);
            Cube_Set_Pixel(6, 4, 7, R, G, B);
            Cube_Set_Pixel(6, 5, 7, R, G, B);
            Cube_Set_Pixel(6, 6, 6, R, G, B);
            Cube_Set_Pixel(6, 7, 5, R, G, B);
            Cube_Set_Pixel(6, 7, 4, R, G, B);
            Cube_Set_Pixel(6, 7, 3, R, G, B);
            Cube_Set_Pixel(6, 7, 2, R, G, B);
            Cube_Set_Pixel(6, 6, 1, R, G, B);
            Cube_Set_Pixel(6, 5, 0, R, G, B);
            Cube_Set_Pixel(6, 4, 0, R, G, B);
            Cube_Set_Pixel(6, 3, 0, R, G, B);
            Cube_Set_Pixel(6, 2, 0, R, G, B);
            Cube_Set_Pixel(6, 1, 1, R, G, B);

            Cube_Set_Pixel(7, 1, 5, R, G, B);
            Cube_Set_Pixel(7, 1, 4, R, G, B);
            Cube_Set_Pixel(7, 1, 3, R, G, B);
            Cube_Set_Pixel(7, 1, 2, R, G, B);
            Cube_Set_Pixel(7, 6, 5, R, G, B);
            Cube_Set_Pixel(7, 6, 4, R, G, B);
            Cube_Set_Pixel(7, 6, 3, R, G, B);
            Cube_Set_Pixel(7, 6, 2, R, G, B);
            Cube_Set_Pixel(7, 2, 6, R, G, B);
            Cube_Set_Pixel(7, 3, 6, R, G, B);
            Cube_Set_Pixel(7, 4, 6, R, G, B);
            Cube_Set_Pixel(7, 5, 6, R, G, B);
            Cube_Set_Pixel(7, 2, 1, R, G, B);
            Cube_Set_Pixel(7, 3, 1, R, G, B);
            Cube_Set_Pixel(7, 4, 1, R, G, B);
            Cube_Set_Pixel(7, 5, 1, R, G, B);
            break;
        case 6:
            Cube_Set_Pixel(0, 0, 2, R, G, B);
            Cube_Set_Pixel(0, 0, 3, R, G, B);
            Cube_Set_Pixel(0, 0, 4, R, G, B);
            Cube_Set_Pixel(0, 0, 5, R, G, B);
            Cube_Set_Pixel(0, 1, 1, R, G, B);
            Cube_Set_Pixel(0, 1, 6, R, G, B);
            Cube_Set_Pixel(0, 2, 0, R, G, B);
            Cube_Set_Pixel(0, 2, 7, R, G, B);
            Cube_Set_Pixel(0, 3, 0, R, G, B);
            Cube_Set_Pixel(0, 3, 7, R, G, B);
            Cube_Set_Pixel(0, 4, 0, R, G, B);
            Cube_Set_Pixel(0, 4, 7, R, G, B);
            Cube_Set_Pixel(0, 5, 0, R, G, B);
            Cube_Set_Pixel(0, 5, 7, R, G, B);
            Cube_Set_Pixel(0, 6, 6, R, G, B);
            Cube_Set_Pixel(0, 6, 1, R, G, B);
            Cube_Set_Pixel(0, 7, 2, R, G, B);
            Cube_Set_Pixel(0, 7, 3, R, G, B);
            Cube_Set_Pixel(0, 7, 4, R, G, B);
            Cube_Set_Pixel(0, 7, 5, R, G, B);

            Cube_Set_Pixel(1, 0, 1, R, G, B);
            Cube_Set_Pixel(1, 1, 0, R, G, B);
            Cube_Set_Pixel(1, 0, 6, R, G, B);
            Cube_Set_Pixel(1, 1, 7, R, G, B);
            Cube_Set_Pixel(1, 6, 7, R, G, B);
            Cube_Set_Pixel(1, 7, 6, R, G, B);
            Cube_Set_Pixel(1, 6, 0, R, G, B);
            Cube_Set_Pixel(1, 7, 1, R, G, B);

            Cube_Set_Pixel(2, 0, 0, R, G, B);
            Cube_Set_Pixel(2, 0, 7, R, G, B);
            Cube_Set_Pixel(2, 7, 7, R, G, B);
            Cube_Set_Pixel(2, 7, 0, R, G, B);

            Cube_Set_Pixel(3, 0, 0, R, G, B);
            Cube_Set_Pixel(3, 0, 7, R, G, B);
            Cube_Set_Pixel(3, 7, 7, R, G, B);
            Cube_Set_Pixel(3, 7, 0, R, G, B);

            Cube_Set_Pixel(4, 0, 0, R, G, B);
            Cube_Set_Pixel(4, 0, 7, R, G, B);
            Cube_Set_Pixel(4, 7, 7, R, G, B);
            Cube_Set_Pixel(4, 7, 0, R, G, B);

            Cube_Set_Pixel(5, 0, 0, R, G, B);
            Cube_Set_Pixel(5, 0, 7, R, G, B);
            Cube_Set_Pixel(5, 7, 7, R, G, B);
            Cube_Set_Pixel(5, 7, 0, R, G, B);

            Cube_Set_Pixel(6, 0, 1, R, G, B);
            Cube_Set_Pixel(6, 1, 0, R, G, B);
            Cube_Set_Pixel(6, 0, 6, R, G, B);
            Cube_Set_Pixel(6, 1, 7, R, G, B);
            Cube_Set_Pixel(6, 6, 7, R, G, B);
            Cube_Set_Pixel(6, 7, 6, R, G, B);
            Cube_Set_Pixel(6, 6, 0, R, G, B);
            Cube_Set_Pixel(6, 7, 1, R, G, B);

            Cube_Set_Pixel(7, 0, 2, R, G, B);
            Cube_Set_Pixel(7, 0, 3, R, G, B);
            Cube_Set_Pixel(7, 0, 4, R, G, B);
            Cube_Set_Pixel(7, 0, 5, R, G, B);
            Cube_Set_Pixel(7, 1, 1, R, G, B);
            Cube_Set_Pixel(7, 1, 6, R, G, B);
            Cube_Set_Pixel(7, 2, 0, R, G, B);
            Cube_Set_Pixel(7, 2, 7, R, G, B);
            Cube_Set_Pixel(7, 3, 0, R, G, B);
            Cube_Set_Pixel(7, 3, 7, R, G, B);
            Cube_Set_Pixel(7, 4, 0, R, G, B);
            Cube_Set_Pixel(7, 4, 7, R, G, B);
            Cube_Set_Pixel(7, 5, 0, R, G, B);
            Cube_Set_Pixel(7, 5, 7, R, G, B);
            Cube_Set_Pixel(7, 6, 6, R, G, B);
            Cube_Set_Pixel(7, 6, 1, R, G, B);
            Cube_Set_Pixel(7, 7, 2, R, G, B);
            Cube_Set_Pixel(7, 7, 3, R, G, B);
            Cube_Set_Pixel(7, 7, 4, R, G, B);
            Cube_Set_Pixel(7, 7, 5, R, G, B);
            break;
        case 7:
            Cube_Set_Pixel(0, 0, 1, R, G, B);
            Cube_Set_Pixel(0, 1, 0, R, G, B);
            Cube_Set_Pixel(0, 0, 6, R, G, B);
            Cube_Set_Pixel(0, 1, 7, R, G, B);
            Cube_Set_Pixel(0, 6, 7, R, G, B);
            Cube_Set_Pixel(0, 7, 6, R, G, B);
            Cube_Set_Pixel(0, 7, 1, R, G, B);
            Cube_Set_Pixel(0, 6, 0, R, G, B);

            Cube_Set_Pixel(1, 0, 0, R, G, B);
            Cube_Set_Pixel(1, 0, 7, R, G, B);
            Cube_Set_Pixel(1, 7, 7, R, G, B);
            Cube_Set_Pixel(1, 7, 0, R, G, B);

            Cube_Set_Pixel(6, 0, 0, R, G, B);
            Cube_Set_Pixel(6, 0, 7, R, G, B);
            Cube_Set_Pixel(6, 7, 7, R, G, B);
            Cube_Set_Pixel(6, 7, 0, R, G, B);

            Cube_Set_Pixel(7, 0, 1, R, G, B);
            Cube_Set_Pixel(7, 1, 0, R, G, B);
            Cube_Set_Pixel(7, 0, 6, R, G, B);
            Cube_Set_Pixel(7, 1, 7, R, G, B);
            Cube_Set_Pixel(7, 6, 7, R, G, B);
            Cube_Set_Pixel(7, 7, 6, R, G, B);
            Cube_Set_Pixel(7, 7, 1, R, G, B);
            Cube_Set_Pixel(7, 6, 0, R, G, B);
            break;
        case 8:
            Cube_Set_Pixel(0, 0, 0, R, G, B);
            Cube_Set_Pixel(0, 0, 7, R, G, B);
            Cube_Set_Pixel(0, 7, 7, R, G, B);
            Cube_Set_Pixel(0, 7, 0, R, G, B);

            Cube_Set_Pixel(7, 0, 0, R, G, B);
            Cube_Set_Pixel(7, 0, 7, R, G, B);
            Cube_Set_Pixel(7, 7, 7, R, G, B);
            Cube_Set_Pixel(7, 7, 0, R, G, B);
            break;
        default:
            break;
    }
}

void Cube_Set_Shell(uint8_t layer, uint8_t R, uint8_t G, uint8_t B) {
    // Sets the specified shell to the specific color
    // Shell 0 is the outermost layer, 3 is the innermost cube of pixels
    uint8_t i, j, k;
    
    for (i = 0; i < CUBE_LAYER_COUNT; i++) {
        if ((layer == 0 || layer == 4)&&(i == 0 || i == 7)) {
            Cube_Set_Layer(i,R,G,B);
        } else if ((layer == 1 || layer == 4)&&(i == 1 || i == 6)) {
            for (j = 1; j < CUBE_ROW_COUNT-1; j++)
                for (k = 1; k < CUBE_COLUMN_COUNT-1; k++)
                    Cube_Set_Pixel(i,j,k,R,G,B);
        } else if ((layer == 2 || layer == 4)&&(i == 2 || i == 5)) {
            for (j = 2; j < CUBE_ROW_COUNT-2; j++)
                for (k = 2; k < CUBE_COLUMN_COUNT-2; k++)
                    Cube_Set_Pixel(i,j,k,R,G,B);
        } else if ((layer == 3 || layer == 4)&&(i == 3 || i == 4)) {
            for (j = 3; j < CUBE_ROW_COUNT-3; j++)
                for (k = 3; k < CUBE_COLUMN_COUNT-3; k++)
                    Cube_Set_Pixel(i,j,k,R,G,B);
        }

        if ((layer == 0 || layer == 4)&&(i > 0 && i < 8)) {
            for (j = 0; j < 8; j++) {
                Cube_Set_Pixel(i,j,0,R,G,B);
                Cube_Set_Pixel(i,j,7,R,G,B);
                Cube_Set_Pixel(i,0,j,R,G,B);
                Cube_Set_Pixel(i,7,j,R,G,B);
            }
        }
        if ((layer == 1 || layer == 4)&&(i > 1 && i < 7)) {
            for (j = 1; j < 7; j++) {
                Cube_Set_Pixel(i,j,1,R,G,B);
                Cube_Set_Pixel(i,j,6,R,G,B);
                Cube_Set_Pixel(i,1,j,R,G,B);
                Cube_Set_Pixel(i,6,j,R,G,B);
            }
        }
        if ((layer == 2 || layer == 4)&&(i > 2 && i < 6)) {
            for (j = 2; j < 6; j++) {
                Cube_Set_Pixel(i,j,2,R,G,B);
                Cube_Set_Pixel(i,j,5,R,G,B);
                Cube_Set_Pixel(i,2,j,R,G,B);
                Cube_Set_Pixel(i,5,j,R,G,B);
            }
        }
    }
}

void Cube_Rotate_Shell(uint8_t shell, uint8_t direction) {
    // Shell is the layer to rotate, with the outermost being 0
    uint8_t layer;
    uint16_t origin_R, origin_G, origin_B;
    for (layer = 0; layer < CUBE_LAYER_COUNT; layer++) {
        if (direction) {
            switch(shell) {
                case 0:
                    // Rotate outermost layer
                    Cube_Get_Pixel(layer, 0, 0, &origin_R, &origin_G, &origin_B);
                    Cube_Move_Pixel(layer, 0, 1, layer, 0, 0);
                    Cube_Move_Pixel(layer, 0, 2, layer, 0, 1);
                    Cube_Move_Pixel(layer, 0, 3, layer, 0, 2);
                    Cube_Move_Pixel(layer, 0, 4, layer, 0, 3);
                    Cube_Move_Pixel(layer, 0, 5, layer, 0, 4);
                    Cube_Move_Pixel(layer, 0, 6, layer, 0, 5);
                    Cube_Move_Pixel(layer, 0, 7, layer, 0, 6);
                    Cube_Move_Pixel(layer, 1, 7, layer, 0, 7);
                    Cube_Move_Pixel(layer, 2, 7, layer, 1, 7);
                    Cube_Move_Pixel(layer, 3, 7, layer, 2, 7);
                    Cube_Move_Pixel(layer, 4, 7, layer, 3, 7);
                    Cube_Move_Pixel(layer, 5, 7, layer, 4, 7);
                    Cube_Move_Pixel(layer, 6, 7, layer, 5, 7);
                    Cube_Move_Pixel(layer, 7, 7, layer, 6, 7);
                    Cube_Move_Pixel(layer, 7, 6, layer, 7, 7);
                    Cube_Move_Pixel(layer, 7, 5, layer, 7, 6);
                    Cube_Move_Pixel(layer, 7, 4, layer, 7, 5);
                    Cube_Move_Pixel(layer, 7, 3, layer, 7, 4);
                    Cube_Move_Pixel(layer, 7, 2, layer, 7, 3);
                    Cube_Move_Pixel(layer, 7, 1, layer, 7, 2);
                    Cube_Move_Pixel(layer, 7, 0, layer, 7, 1);
                    Cube_Move_Pixel(layer, 6, 0, layer, 7, 0);
                    Cube_Move_Pixel(layer, 5, 0, layer, 6, 0);
                    Cube_Move_Pixel(layer, 4, 0, layer, 5, 0);
                    Cube_Move_Pixel(layer, 3, 0, layer, 4, 0);
                    Cube_Move_Pixel(layer, 2, 0, layer, 3, 0);
                    Cube_Move_Pixel(layer, 1, 0, layer, 2, 0);
                    Cube_Set_Pixel(layer, 1, 0, origin_R, origin_G, origin_B);
                    break;
                case 1:
                    // Rotate second to outermost layer
                    Cube_Get_Pixel(layer, 1, 1, &origin_R, &origin_G, &origin_B);
                    Cube_Move_Pixel(layer, 1, 2, layer, 1, 1);
                    Cube_Move_Pixel(layer, 1, 3, layer, 1, 2);
                    Cube_Move_Pixel(layer, 1, 4, layer, 1, 3);
                    Cube_Move_Pixel(layer, 1, 5, layer, 1, 4);
                    Cube_Move_Pixel(layer, 1, 6, layer, 1, 5);
                    Cube_Move_Pixel(layer, 2, 6, layer, 1, 6);
                    Cube_Move_Pixel(layer, 3, 6, layer, 2, 6);
                    Cube_Move_Pixel(layer, 4, 6, layer, 3, 6);
                    Cube_Move_Pixel(layer, 5, 6, layer, 4, 6);
                    Cube_Move_Pixel(layer, 6, 6, layer, 5, 6);
                    Cube_Move_Pixel(layer, 6, 5, layer, 6, 6);
                    Cube_Move_Pixel(layer, 6, 4, layer, 6, 5);
                    Cube_Move_Pixel(layer, 6, 3, layer, 6, 4);
                    Cube_Move_Pixel(layer, 6, 2, layer, 6, 3);
                    Cube_Move_Pixel(layer, 6, 1, layer, 6, 2);
                    Cube_Move_Pixel(layer, 5, 1, layer, 6, 1);
                    Cube_Move_Pixel(layer, 4, 1, layer, 5, 1);
                    Cube_Move_Pixel(layer, 3, 1, layer, 4, 1);
                    Cube_Move_Pixel(layer, 2, 1, layer, 3, 1);
                    Cube_Set_Pixel(layer, 2, 1, origin_R, origin_G, origin_B);
                    break;
                case 2:
                    // Rotate second to innermost layer
                    Cube_Get_Pixel(layer, 2, 2, &origin_R, &origin_G, &origin_B);
                    Cube_Move_Pixel(layer, 2, 3, layer, 2, 2);
                    Cube_Move_Pixel(layer, 2, 4, layer, 2, 3);
                    Cube_Move_Pixel(layer, 2, 5, layer, 2, 4);
                    Cube_Move_Pixel(layer, 3, 5, layer, 2, 5);
                    Cube_Move_Pixel(layer, 4, 5, layer, 3, 5);
                    Cube_Move_Pixel(layer, 5, 5, layer, 4, 5);
                    Cube_Move_Pixel(layer, 5, 4, layer, 5, 5);
                    Cube_Move_Pixel(layer, 5, 3, layer, 5, 4);
                    Cube_Move_Pixel(layer, 5, 2, layer, 5, 3);
                    Cube_Move_Pixel(layer, 4, 2, layer, 5, 2);
                    Cube_Move_Pixel(layer, 3, 2, layer, 4, 2);
                    Cube_Set_Pixel(layer, 3, 2, origin_R, origin_G, origin_B);
                    break;
                case 3:
                    // Rotate innermost layer
                    Cube_Get_Pixel(layer, 3, 3, &origin_R, &origin_G, &origin_B);
                    Cube_Move_Pixel(layer, 3, 4, layer, 3, 3);
                    Cube_Move_Pixel(layer, 4, 4, layer, 3, 4);
                    Cube_Move_Pixel(layer, 4, 3, layer, 4, 4);
                    Cube_Set_Pixel(layer, 4, 3, origin_R, origin_G, origin_B);
                    break;
            }
        } else {
            switch(shell) {
                case 0:
                    // Rotate outermost layer
                    Cube_Get_Pixel(layer, 0, 0, &origin_R, &origin_G, &origin_B);
                    Cube_Move_Pixel(layer, 1, 0, layer, 0, 0);
                    Cube_Move_Pixel(layer, 2, 0, layer, 1, 0);
                    Cube_Move_Pixel(layer, 3, 0, layer, 2, 0);
                    Cube_Move_Pixel(layer, 4, 0, layer, 3, 0);
                    Cube_Move_Pixel(layer, 5, 0, layer, 4, 0);
                    Cube_Move_Pixel(layer, 6, 0, layer, 5, 0);
                    Cube_Move_Pixel(layer, 7, 0, layer, 6, 0);
                    Cube_Move_Pixel(layer, 7, 1, layer, 7, 0);
                    Cube_Move_Pixel(layer, 7, 2, layer, 7, 1);
                    Cube_Move_Pixel(layer, 7, 3, layer, 7, 2);
                    Cube_Move_Pixel(layer, 7, 4, layer, 7, 3);
                    Cube_Move_Pixel(layer, 7, 5, layer, 7, 4);
                    Cube_Move_Pixel(layer, 7, 6, layer, 7, 5);
                    Cube_Move_Pixel(layer, 7, 7, layer, 7, 6);
                    Cube_Move_Pixel(layer, 6, 7, layer, 7, 7);
                    Cube_Move_Pixel(layer, 5, 7, layer, 6, 7);
                    Cube_Move_Pixel(layer, 4, 7, layer, 5, 7);
                    Cube_Move_Pixel(layer, 3, 7, layer, 4, 7);
                    Cube_Move_Pixel(layer, 2, 7, layer, 3, 7);
                    Cube_Move_Pixel(layer, 1, 7, layer, 2, 7);
                    Cube_Move_Pixel(layer, 0, 7, layer, 1, 7);
                    Cube_Move_Pixel(layer, 0, 6, layer, 0, 7);
                    Cube_Move_Pixel(layer, 0, 5, layer, 0, 6);
                    Cube_Move_Pixel(layer, 0, 4, layer, 0, 5);
                    Cube_Move_Pixel(layer, 0, 3, layer, 0, 4);
                    Cube_Move_Pixel(layer, 0, 2, layer, 0, 3);
                    Cube_Move_Pixel(layer, 0, 1, layer, 0, 2);
                    Cube_Set_Pixel(layer, 0, 1, origin_R, origin_G, origin_B);
                    break;
                case 1:
                    // Rotate second to outermost layer
                    Cube_Get_Pixel(layer, 1, 1, &origin_R, &origin_G, &origin_B);
                    Cube_Move_Pixel(layer, 2, 1, layer, 1, 1);
                    Cube_Move_Pixel(layer, 3, 1, layer, 2, 1);
                    Cube_Move_Pixel(layer, 4, 1, layer, 3, 1);
                    Cube_Move_Pixel(layer, 5, 1, layer, 4, 1);
                    Cube_Move_Pixel(layer, 6, 1, layer, 5, 1);
                    Cube_Move_Pixel(layer, 6, 2, layer, 6, 1);
                    Cube_Move_Pixel(layer, 6, 3, layer, 6, 2);
                    Cube_Move_Pixel(layer, 6, 4, layer, 6, 3);
                    Cube_Move_Pixel(layer, 6, 5, layer, 6, 4);
                    Cube_Move_Pixel(layer, 6, 6, layer, 6, 5);
                    Cube_Move_Pixel(layer, 5, 6, layer, 6, 6);
                    Cube_Move_Pixel(layer, 4, 6, layer, 5, 6);
                    Cube_Move_Pixel(layer, 3, 6, layer, 4, 6);
                    Cube_Move_Pixel(layer, 2, 6, layer, 3, 6);
                    Cube_Move_Pixel(layer, 1, 6, layer, 2, 6);
                    Cube_Move_Pixel(layer, 1, 5, layer, 1, 6);
                    Cube_Move_Pixel(layer, 1, 4, layer, 1, 5);
                    Cube_Move_Pixel(layer, 1, 3, layer, 1, 4);
                    Cube_Move_Pixel(layer, 1, 2, layer, 1, 3);
                    Cube_Set_Pixel(layer, 1, 2, origin_R, origin_G, origin_B);
                    break;
                case 2:
                    // Rotate second to innermost layer
                    Cube_Get_Pixel(layer, 2, 2, &origin_R, &origin_G, &origin_B);
                    Cube_Move_Pixel(layer, 3, 2, layer, 2, 2);
                    Cube_Move_Pixel(layer, 4, 2, layer, 3, 2);
                    Cube_Move_Pixel(layer, 5, 2, layer, 4, 2);
                    Cube_Move_Pixel(layer, 5, 3, layer, 5, 2);
                    Cube_Move_Pixel(layer, 5, 4, layer, 5, 3);
                    Cube_Move_Pixel(layer, 5, 5, layer, 5, 4);
                    Cube_Move_Pixel(layer, 4, 5, layer, 5, 5);
                    Cube_Move_Pixel(layer, 3, 5, layer, 4, 5);
                    Cube_Move_Pixel(layer, 2, 5, layer, 3, 5);
                    Cube_Move_Pixel(layer, 2, 4, layer, 2, 5);
                    Cube_Move_Pixel(layer, 2, 3, layer, 2, 4);
                    Cube_Set_Pixel(layer, 2, 3, origin_R, origin_G, origin_B);
                    break;
                case 3:
                    // Rotate innermost layer
                    Cube_Get_Pixel(layer, 3, 3, &origin_R, &origin_G, &origin_B);
                    Cube_Move_Pixel(layer, 4, 3, layer, 3, 3);
                    Cube_Move_Pixel(layer, 4, 4, layer, 4, 3);
                    Cube_Move_Pixel(layer, 3, 4, layer, 4, 4);
                    Cube_Set_Pixel(layer, 3, 4, origin_R, origin_G, origin_B);
                    break;
            }
        }
    }
}

void Cube_Rotate(uint8_t direction) {
    // Rotate outermost layer
    Cube_Rotate_Shell(0, direction);
    // Rotate second to outermost layer
    if ((cube_data_ptr->rotation_counter != 1) && (cube_data_ptr->rotation_counter != 5)) {
        Cube_Rotate_Shell(1, direction);
    }
    // Rotate second to innermost layer
    if ((cube_data_ptr->rotation_counter != 0) && (cube_data_ptr->rotation_counter != 2) &&
        (cube_data_ptr->rotation_counter != 4) && (cube_data_ptr->rotation_counter != 6)) {
        Cube_Rotate_Shell(2, direction);
    }
    // Rotate innermost layer
    if ((cube_data_ptr->rotation_counter == 3) || (cube_data_ptr->rotation_counter == 7)) {
        Cube_Rotate_Shell(3, direction);
    }

    if (direction == 0) {
        cube_data_ptr->rotation_counter = (cube_data_ptr->rotation_counter == CUBE_ROTATIONS - 1)
                ? 0 : cube_data_ptr->rotation_counter + 1;
    } else {
        cube_data_ptr->rotation_counter = (cube_data_ptr->rotation_counter == 0)
                ? CUBE_ROTATIONS - 1 : cube_data_ptr->rotation_counter - 1;
    }
}

void Cube_Shift_Row(uint8_t direction) {
    // Shifts the display by an entire row
    int i, j, k;
    if (direction) {
        // Shift values in each row by one
        for (i = CUBE_ROW_COUNT - 1; i >= 0; i--) {   // Row
            for (j = 0; j < CUBE_COLUMN_COUNT; j++) {
                for (k = 0; k < CUBE_LAYER_COUNT; k++) {
                    Cube_Move_Pixel(k, i - 1, j, k, i, j);
                }
            }
        }
    } else {
        for (i = 0; i < CUBE_ROW_COUNT - 1; i++) {
            for (j = 0; j < CUBE_COLUMN_COUNT; j++) {
                for (k = 0; k < CUBE_LAYER_COUNT; k++) {
                    Cube_Move_Pixel(k, i + 1, j, k, i, j);
                }
            }
        }
    }
}

void Cube_Shift_Waterfall(uint8_t *values) {
    // Takes an array of 8 values and sets them to the column height
    // Each column is set to a manually specified color
    uint8_t i, j;
    uint8_t update_row = CUBE_ROW_COUNT - 1;

    // First shift the rows
    Cube_Shift_Row(0);

    // Then update the empty row
    for (i = 0; i < CUBE_COLUMN_COUNT; i++) {
        for (j = 0; j < CUBE_LAYER_COUNT; j++) {
            if (j < values[i]) {
                // Specify the color for each column
                if (i == 0)
                    Cube_Set_Pixel(j, update_row, i, RED);
                else if (i == 1)
                    Cube_Set_Pixel(j, update_row, i, ORANGE);
                else if (i == 2)
                    Cube_Set_Pixel(j, update_row, i, YELLOW);
                else if (i == 3)
                    Cube_Set_Pixel(j, update_row, i, GREEN);
                else if (i == 4)
                    Cube_Set_Pixel(j, update_row, i, TEAL);
                else if (i == 5)
                    Cube_Set_Pixel(j, update_row, i, BLUE);
                else if (i == 6)
                    Cube_Set_Pixel(j, update_row, i, PURPLE);
                else
                    Cube_Set_Pixel(j, update_row, i, WHITE);
            } else {
                Cube_Set_Pixel(j, update_row, i, CLEAR);
            }
        }
    }
}

void Cube_Shift_Waterfall2(uint8_t *values) {
    // Takes an array of 8 values and sets them to the column height
    // Each layer is set to a manually specified color
    uint8_t i, j;
    uint8_t update_row = CUBE_ROW_COUNT - 1;

    // First shift the rows
    Cube_Shift_Row(0);

    // Then update the empty row
    for (i = 0; i < CUBE_COLUMN_COUNT; i++) {
        for (j = 0; j < CUBE_LAYER_COUNT; j++) {
            if (j < values[i]) {
                // Specify the color for each layer
                if (j == 7)
                    Cube_Set_Pixel(j, update_row, i, RED);
                else if (j == 6)
                    Cube_Set_Pixel(j, update_row, i, ORANGE);
                else if (j == 5)
                    Cube_Set_Pixel(j, update_row, i, YELLOW);
                else if (j == 4)
                    Cube_Set_Pixel(j, update_row, i, GREEN);
                else if (j == 3)
                    Cube_Set_Pixel(j, update_row, i, TEAL);
                else if (j == 2)
                    Cube_Set_Pixel(j, update_row, i, BLUE);
                else if (j == 1)
                    Cube_Set_Pixel(j, update_row, i, PURPLE);
                else
                    Cube_Set_Pixel(j, update_row, i, WHITE);
            } else {
                Cube_Set_Pixel(j, update_row, i, CLEAR);
            }
        }
    }
}

///////////////////////////////
// Overlay control functions //
///////////////////////////////

void Cube_Overlay_Clear(void) {
    uint16_t i,j;
    for (i = 0; i < CUBE_LAYER_COUNT; i++) {
        for (j = 0; j < GCS_LAYER_SIZE; j++) {
            cube_data_ptr->GCS_OVERLAY[i][j] = 0x00;
        }
    }
}

void Cube_Overlay_Set_Pixel(uint8_t layer, uint8_t row, uint8_t column, uint16_t R, uint16_t G, uint16_t B) {
    // Set the specified pixel to the given color
    R &= 0x0FFF;
    G &= 0x0FFF;
    B &= 0x0FFF;
    uint16_t var = row * GCS_REG_SIZE + (column / 2 * 9);
    switch (column % 2) {
        case 0:
            cube_data_ptr->GCS_OVERLAY[layer][var+0] = R & 0xFF;
            cube_data_ptr->GCS_OVERLAY[layer][var+1] = (G << 4) | (R >> 8);
            cube_data_ptr->GCS_OVERLAY[layer][var+2] = G >> 4;
            cube_data_ptr->GCS_OVERLAY[layer][var+3] = B & 0xFF;
            cube_data_ptr->GCS_OVERLAY[layer][var+4] = (cube_data_ptr->GCS_OVERLAY[layer][var+4] & 0xF0) | (B >> 8);
            break;
        case 1:
            cube_data_ptr->GCS_OVERLAY[layer][var+4] = (cube_data_ptr->GCS_OVERLAY[layer][var+4] & 0x0F) | (R << 4);
            cube_data_ptr->GCS_OVERLAY[layer][var+5] = R >> 4;
            cube_data_ptr->GCS_OVERLAY[layer][var+6] = G & 0xFF;
            cube_data_ptr->GCS_OVERLAY[layer][var+7] = (B << 4) | (G >> 8);
            cube_data_ptr->GCS_OVERLAY[layer][var+8] = B >> 4;
            break;
    }
}

void Cube_Overlay_Get_Pixel(uint8_t layer, uint8_t row, uint8_t column, uint16_t* R, uint16_t* G, uint16_t* B) {
    uint16_t var = row * GCS_REG_SIZE + (column / 2 * 9);
    switch (column % 2) {
        // Concatenate lower byte and upper byte of each color channel
        case 0:
            *R = cube_data_ptr->GCS_OVERLAY[layer][var+0] | ((cube_data_ptr->GCS_OVERLAY[layer][var+1] & 0x0F) << 8);
            *G = (cube_data_ptr->GCS_OVERLAY[layer][var+1] >> 4) | (cube_data_ptr->GCS_OVERLAY[layer][var+2] << 4);
            *B = cube_data_ptr->GCS_OVERLAY[layer][var+3] | ((cube_data_ptr->GCS_OVERLAY[layer][var+4] & 0x0F) << 8);
            break;
        case 1:
            *R = (cube_data_ptr->GCS_OVERLAY[layer][var+4] >> 4) | (cube_data_ptr->GCS_OVERLAY[layer][var+5] << 4);
            *G = cube_data_ptr->GCS_OVERLAY[layer][var+6] | ((cube_data_ptr->GCS_OVERLAY[layer][var+7] & 0x0F) << 8);
            *B = (cube_data_ptr->GCS_OVERLAY[layer][var+7] >> 4) | (cube_data_ptr->GCS_OVERLAY[layer][var+8] << 4);
            break;
    }
}

void Cube_Overlay_Move_Pixel(uint8_t layer1, uint8_t row1, uint8_t column1, uint8_t layer2, uint8_t row2, uint8_t column2) {
    // Copies data from pixel 1 to pixel 2
    // Note: destination pixel value is overwritten
    uint16_t prev_R, prev_G, prev_B;
    Cube_Overlay_Get_Pixel(layer1, row1, column1, &prev_R, &prev_G, &prev_B);
    Cube_Overlay_Set_Pixel(layer2, row2, column2, prev_R, prev_G, prev_B);
}

void Cube_Overlay_Rotate_Shell(uint8_t shell, uint8_t direction) {
    // Shell is the layer to rotate, with the outermost being 0
    uint8_t layer;
    uint16_t origin_R, origin_G, origin_B;;
    for (layer = 0; layer < CUBE_LAYER_COUNT; layer++) {
        if (direction) {
            switch(shell) {
                case 0:
                    // Rotate outermost layer
                    Cube_Overlay_Get_Pixel(layer, 0, 0, &origin_R, &origin_G, &origin_B);
                    Cube_Overlay_Move_Pixel(layer, 0, 1, layer, 0, 0);
                    Cube_Overlay_Move_Pixel(layer, 0, 2, layer, 0, 1);
                    Cube_Overlay_Move_Pixel(layer, 0, 3, layer, 0, 2);
                    Cube_Overlay_Move_Pixel(layer, 0, 4, layer, 0, 3);
                    Cube_Overlay_Move_Pixel(layer, 0, 5, layer, 0, 4);
                    Cube_Overlay_Move_Pixel(layer, 0, 6, layer, 0, 5);
                    Cube_Overlay_Move_Pixel(layer, 0, 7, layer, 0, 6);
                    Cube_Overlay_Move_Pixel(layer, 1, 7, layer, 0, 7);
                    Cube_Overlay_Move_Pixel(layer, 2, 7, layer, 1, 7);
                    Cube_Overlay_Move_Pixel(layer, 3, 7, layer, 2, 7);
                    Cube_Overlay_Move_Pixel(layer, 4, 7, layer, 3, 7);
                    Cube_Overlay_Move_Pixel(layer, 5, 7, layer, 4, 7);
                    Cube_Overlay_Move_Pixel(layer, 6, 7, layer, 5, 7);
                    Cube_Overlay_Move_Pixel(layer, 7, 7, layer, 6, 7);
                    Cube_Overlay_Move_Pixel(layer, 7, 6, layer, 7, 7);
                    Cube_Overlay_Move_Pixel(layer, 7, 5, layer, 7, 6);
                    Cube_Overlay_Move_Pixel(layer, 7, 4, layer, 7, 5);
                    Cube_Overlay_Move_Pixel(layer, 7, 3, layer, 7, 4);
                    Cube_Overlay_Move_Pixel(layer, 7, 2, layer, 7, 3);
                    Cube_Overlay_Move_Pixel(layer, 7, 1, layer, 7, 2);
                    Cube_Overlay_Move_Pixel(layer, 7, 0, layer, 7, 1);
                    Cube_Overlay_Move_Pixel(layer, 6, 0, layer, 7, 0);
                    Cube_Overlay_Move_Pixel(layer, 5, 0, layer, 6, 0);
                    Cube_Overlay_Move_Pixel(layer, 4, 0, layer, 5, 0);
                    Cube_Overlay_Move_Pixel(layer, 3, 0, layer, 4, 0);
                    Cube_Overlay_Move_Pixel(layer, 2, 0, layer, 3, 0);
                    Cube_Overlay_Move_Pixel(layer, 1, 0, layer, 2, 0);
                    Cube_Overlay_Set_Pixel(layer, 1, 0, origin_R, origin_G, origin_B);
                    break;
                case 1:
                    // Rotate second to outermost layer
                    Cube_Overlay_Get_Pixel(layer, 1, 1, &origin_R, &origin_G, &origin_B);
                    Cube_Overlay_Move_Pixel(layer, 1, 2, layer, 1, 1);
                    Cube_Overlay_Move_Pixel(layer, 1, 3, layer, 1, 2);
                    Cube_Overlay_Move_Pixel(layer, 1, 4, layer, 1, 3);
                    Cube_Overlay_Move_Pixel(layer, 1, 5, layer, 1, 4);
                    Cube_Overlay_Move_Pixel(layer, 1, 6, layer, 1, 5);
                    Cube_Overlay_Move_Pixel(layer, 2, 6, layer, 1, 6);
                    Cube_Overlay_Move_Pixel(layer, 3, 6, layer, 2, 6);
                    Cube_Overlay_Move_Pixel(layer, 4, 6, layer, 3, 6);
                    Cube_Overlay_Move_Pixel(layer, 5, 6, layer, 4, 6);
                    Cube_Overlay_Move_Pixel(layer, 6, 6, layer, 5, 6);
                    Cube_Overlay_Move_Pixel(layer, 6, 5, layer, 6, 6);
                    Cube_Overlay_Move_Pixel(layer, 6, 4, layer, 6, 5);
                    Cube_Overlay_Move_Pixel(layer, 6, 3, layer, 6, 4);
                    Cube_Overlay_Move_Pixel(layer, 6, 2, layer, 6, 3);
                    Cube_Overlay_Move_Pixel(layer, 6, 1, layer, 6, 2);
                    Cube_Overlay_Move_Pixel(layer, 5, 1, layer, 6, 1);
                    Cube_Overlay_Move_Pixel(layer, 4, 1, layer, 5, 1);
                    Cube_Overlay_Move_Pixel(layer, 3, 1, layer, 4, 1);
                    Cube_Overlay_Move_Pixel(layer, 2, 1, layer, 3, 1);
                    Cube_Overlay_Set_Pixel(layer, 2, 1, origin_R, origin_G, origin_B);
                    break;
                case 2:
                    // Rotate second to innermost layer
                    Cube_Overlay_Get_Pixel(layer, 2, 2, &origin_R, &origin_G, &origin_B);
                    Cube_Overlay_Move_Pixel(layer, 2, 3, layer, 2, 2);
                    Cube_Overlay_Move_Pixel(layer, 2, 4, layer, 2, 3);
                    Cube_Overlay_Move_Pixel(layer, 2, 5, layer, 2, 4);
                    Cube_Overlay_Move_Pixel(layer, 3, 5, layer, 2, 5);
                    Cube_Overlay_Move_Pixel(layer, 4, 5, layer, 3, 5);
                    Cube_Overlay_Move_Pixel(layer, 5, 5, layer, 4, 5);
                    Cube_Overlay_Move_Pixel(layer, 5, 4, layer, 5, 5);
                    Cube_Overlay_Move_Pixel(layer, 5, 3, layer, 5, 4);
                    Cube_Overlay_Move_Pixel(layer, 5, 2, layer, 5, 3);
                    Cube_Overlay_Move_Pixel(layer, 4, 2, layer, 5, 2);
                    Cube_Overlay_Move_Pixel(layer, 3, 2, layer, 4, 2);
                    Cube_Overlay_Set_Pixel(layer, 3, 2, origin_R, origin_G, origin_B);
                    break;
                case 3:
                    // Rotate innermost layer
                    Cube_Overlay_Get_Pixel(layer, 3, 3, &origin_R, &origin_G, &origin_B);
                    Cube_Overlay_Move_Pixel(layer, 3, 4, layer, 3, 3);
                    Cube_Overlay_Move_Pixel(layer, 4, 4, layer, 3, 4);
                    Cube_Overlay_Move_Pixel(layer, 4, 3, layer, 4, 4);
                    Cube_Overlay_Set_Pixel(layer, 4, 3, origin_R, origin_G, origin_B);
                    break;
            }
        } else {
            switch(shell) {
                case 0:
                    // Rotate outermost layer
                    Cube_Overlay_Get_Pixel(layer, 0, 0, &origin_R, &origin_G, &origin_B);
                    Cube_Overlay_Move_Pixel(layer, 1, 0, layer, 0, 0);
                    Cube_Overlay_Move_Pixel(layer, 2, 0, layer, 1, 0);
                    Cube_Overlay_Move_Pixel(layer, 3, 0, layer, 2, 0);
                    Cube_Overlay_Move_Pixel(layer, 4, 0, layer, 3, 0);
                    Cube_Overlay_Move_Pixel(layer, 5, 0, layer, 4, 0);
                    Cube_Overlay_Move_Pixel(layer, 6, 0, layer, 5, 0);
                    Cube_Overlay_Move_Pixel(layer, 7, 0, layer, 6, 0);
                    Cube_Overlay_Move_Pixel(layer, 7, 1, layer, 7, 0);
                    Cube_Overlay_Move_Pixel(layer, 7, 2, layer, 7, 1);
                    Cube_Overlay_Move_Pixel(layer, 7, 3, layer, 7, 2);
                    Cube_Overlay_Move_Pixel(layer, 7, 4, layer, 7, 3);
                    Cube_Overlay_Move_Pixel(layer, 7, 5, layer, 7, 4);
                    Cube_Overlay_Move_Pixel(layer, 7, 6, layer, 7, 5);
                    Cube_Overlay_Move_Pixel(layer, 7, 7, layer, 7, 6);
                    Cube_Overlay_Move_Pixel(layer, 6, 7, layer, 7, 7);
                    Cube_Overlay_Move_Pixel(layer, 5, 7, layer, 6, 7);
                    Cube_Overlay_Move_Pixel(layer, 4, 7, layer, 5, 7);
                    Cube_Overlay_Move_Pixel(layer, 3, 7, layer, 4, 7);
                    Cube_Overlay_Move_Pixel(layer, 2, 7, layer, 3, 7);
                    Cube_Overlay_Move_Pixel(layer, 1, 7, layer, 2, 7);
                    Cube_Overlay_Move_Pixel(layer, 0, 7, layer, 1, 7);
                    Cube_Overlay_Move_Pixel(layer, 0, 6, layer, 0, 7);
                    Cube_Overlay_Move_Pixel(layer, 0, 5, layer, 0, 6);
                    Cube_Overlay_Move_Pixel(layer, 0, 4, layer, 0, 5);
                    Cube_Overlay_Move_Pixel(layer, 0, 3, layer, 0, 4);
                    Cube_Overlay_Move_Pixel(layer, 0, 2, layer, 0, 3);
                    Cube_Overlay_Move_Pixel(layer, 0, 1, layer, 0, 2);
                    Cube_Overlay_Set_Pixel(layer, 0, 1, origin_R, origin_G, origin_B);
                    break;
                case 1:
                    // Rotate second to outermost layer
                    Cube_Overlay_Get_Pixel(layer, 1, 1, &origin_R, &origin_G, &origin_B);
                    Cube_Overlay_Move_Pixel(layer, 2, 1, layer, 1, 1);
                    Cube_Overlay_Move_Pixel(layer, 3, 1, layer, 2, 1);
                    Cube_Overlay_Move_Pixel(layer, 4, 1, layer, 3, 1);
                    Cube_Overlay_Move_Pixel(layer, 5, 1, layer, 4, 1);
                    Cube_Overlay_Move_Pixel(layer, 6, 1, layer, 5, 1);
                    Cube_Overlay_Move_Pixel(layer, 6, 2, layer, 6, 1);
                    Cube_Overlay_Move_Pixel(layer, 6, 3, layer, 6, 2);
                    Cube_Overlay_Move_Pixel(layer, 6, 4, layer, 6, 3);
                    Cube_Overlay_Move_Pixel(layer, 6, 5, layer, 6, 4);
                    Cube_Overlay_Move_Pixel(layer, 6, 6, layer, 6, 5);
                    Cube_Overlay_Move_Pixel(layer, 5, 6, layer, 6, 6);
                    Cube_Overlay_Move_Pixel(layer, 4, 6, layer, 5, 6);
                    Cube_Overlay_Move_Pixel(layer, 3, 6, layer, 4, 6);
                    Cube_Overlay_Move_Pixel(layer, 2, 6, layer, 3, 6);
                    Cube_Overlay_Move_Pixel(layer, 1, 6, layer, 2, 6);
                    Cube_Overlay_Move_Pixel(layer, 1, 5, layer, 1, 6);
                    Cube_Overlay_Move_Pixel(layer, 1, 4, layer, 1, 5);
                    Cube_Overlay_Move_Pixel(layer, 1, 3, layer, 1, 4);
                    Cube_Overlay_Move_Pixel(layer, 1, 2, layer, 1, 3);
                    Cube_Overlay_Set_Pixel(layer, 1, 2, origin_R, origin_G, origin_B);
                    break;
                case 2:
                    // Rotate second to innermost layer
                    Cube_Overlay_Get_Pixel(layer, 2, 2, &origin_R, &origin_G, &origin_B);
                    Cube_Overlay_Move_Pixel(layer, 3, 2, layer, 2, 2);
                    Cube_Overlay_Move_Pixel(layer, 4, 2, layer, 3, 2);
                    Cube_Overlay_Move_Pixel(layer, 5, 2, layer, 4, 2);
                    Cube_Overlay_Move_Pixel(layer, 5, 3, layer, 5, 2);
                    Cube_Overlay_Move_Pixel(layer, 5, 4, layer, 5, 3);
                    Cube_Overlay_Move_Pixel(layer, 5, 5, layer, 5, 4);
                    Cube_Overlay_Move_Pixel(layer, 4, 5, layer, 5, 5);
                    Cube_Overlay_Move_Pixel(layer, 3, 5, layer, 4, 5);
                    Cube_Overlay_Move_Pixel(layer, 2, 5, layer, 3, 5);
                    Cube_Overlay_Move_Pixel(layer, 2, 4, layer, 2, 5);
                    Cube_Overlay_Move_Pixel(layer, 2, 3, layer, 2, 4);
                    Cube_Overlay_Set_Pixel(layer, 2, 3, origin_R, origin_G, origin_B);
                    break;
                case 3:
                    // Rotate innermost layer
                    Cube_Overlay_Get_Pixel(layer, 3, 3, &origin_R, &origin_G, &origin_B);
                    Cube_Overlay_Move_Pixel(layer, 4, 3, layer, 3, 3);
                    Cube_Overlay_Move_Pixel(layer, 4, 4, layer, 4, 3);
                    Cube_Overlay_Move_Pixel(layer, 3, 4, layer, 4, 4);
                    Cube_Overlay_Set_Pixel(layer, 3, 4, origin_R, origin_G, origin_B);
                    break;
            }
        }
    }
}

////////////////////////////
// Text control functions //
////////////////////////////

void Cube_Text_Init(uint8_t *string, uint8_t length, uint16_t R, uint16_t G, uint16_t B) {
    // Ensure that the length of the string does not exceed the storage buffer
    if (length > CUBE_STRING_MAX_LENGTH) length = CUBE_STRING_MAX_LENGTH;

    Cube_Overlay_Clear();

    // Copy the passed data into the buffer
    uint8_t i;
    for (i = 0; i < length; i++)
        cube_data_ptr->string[i] = string[i];
    cube_data_ptr->string_length = length;
    cube_data_ptr->string_index = 0;
    cube_data_ptr->string_line = 0;
    cube_data_ptr->string_R = R;
    cube_data_ptr->string_G = G;
    cube_data_ptr->string_B = B;
}

void Cube_Text_Interrupt(void) {
    uint8_t layer;
    uint16_t line;

    // Rotate before drawing the new line at (0,0)
    Cube_Overlay_Rotate_Shell(0, 0);

    // Get the next vertical line of the int8_tacter currently being drawn
    if (cube_data_ptr->string_line == 5) {
        line = 0x0; // Leave a space between int8_tacters
    } else {
        line = font[(cube_data_ptr->string[cube_data_ptr->string_index] * 5)
                + cube_data_ptr->string_line];
    }

    // Draw the line onto (0,0) using the specified color
    for (layer = 8; layer != 0; layer--) {
        if (line & 0x1) {
            Cube_Overlay_Set_Pixel(layer-1, 0, 0, cube_data_ptr->string_R,
                    cube_data_ptr->string_G, cube_data_ptr->string_B);
        } else {
            Cube_Overlay_Set_Pixel(layer-1, 0, 0, 0x00, 0x00, 0x00);
        }
        line >>= 1;
    }

    // Increment the vertical line and the int8_tacter as needed
    if (cube_data_ptr->string_line == 5) {
        cube_data_ptr->string_line = 0;
        if (cube_data_ptr->string_index == cube_data_ptr->string_length-1) {
            cube_data_ptr->string_index = 0;
        } else {
            cube_data_ptr->string_index += 1;
        }
    } else {
        cube_data_ptr->string_line += 1;
    }
}

/////////////////////////////////////////////
// Functions for processing streaming data //
/////////////////////////////////////////////

void Cube_Data_In(uint8_t c) {
    // Reset upon receiving the start int8_t
    if (c == CUBE_START_CHAR) {
        cube_data_ptr->frame_length = 0;
        cube_data_ptr->frame_index = 0;
        cube_data_ptr->frame_checksum = 0;
        cube_data_ptr->frame_command = 0;
        cube_data_ptr->frame_escape = 0;
        cube_data_ptr->frame_state = READ_LENGTH_MSB;
        return;
    }
    // If the input is the escape int8_t, XOR the next int8_t received
    if (c == CUBE_ESCAPE_CHAR) {
        cube_data_ptr->frame_escape = 1;
        return;
    }
    // XOR the input int8_t if needed
    if (cube_data_ptr->frame_escape) {
        c ^= CUBE_ESCAPE_XOR;
        cube_data_ptr->frame_escape = 0;
    }
    // Process data
    switch (cube_data_ptr->frame_state) {
        case IDLE:
            // Reflect the int8_tacter back to the transmitter
            UART1_Write(&c, 1);
            break;
        case READ_LENGTH_MSB: // Save MSB of length
            cube_data_ptr->frame_length |= (c << 8);
            cube_data_ptr->frame_state = READ_LENGTH_LSB;
            break;
        case READ_LENGTH_LSB: // Save LSB of length
            cube_data_ptr->frame_length |= c;
            cube_data_ptr->frame_state = READ_COMMAND;
            break;
        case READ_COMMAND: // Store the command byte
            cube_data_ptr->frame_checksum += c;
            cube_data_ptr->frame_command = c;
            if (cube_data_ptr->frame_length == 1)
                cube_data_ptr->frame_state = READ_CHECKSUM;
            else
                cube_data_ptr->frame_state = READ_DATA;
            break;
        case READ_DATA: // Read the passed data into the buffer
            cube_data_ptr->frame_checksum += c;
            cube_data_ptr->frame_buffer[cube_data_ptr->frame_index] = c;
            cube_data_ptr->frame_index++;
            if (cube_data_ptr->frame_index == cube_data_ptr->frame_length - 1)
                cube_data_ptr->frame_state = READ_CHECKSUM;
            break;
        case READ_CHECKSUM: // Process frame if checksum is valid
            cube_data_ptr->frame_checksum = 0xFF - cube_data_ptr->frame_checksum;
            if (cube_data_ptr->frame_checksum == c) {
                Cube_Data_In_Process_Frame();
            }
            cube_data_ptr->frame_state = IDLE;
            cube_data_ptr->frame_index = 0;
            cube_data_ptr->frame_length = 0;
            break;
        default:
            break;
    }
}

void Cube_Data_In_Process_Frame(void) {
    // Here we process received frames depending on the command
    uint8_t *frame = cube_data_ptr->frame_buffer;
    switch (cube_data_ptr->frame_command) {
        case CUBE_COMMAND_SET_BC:
            TIMER5_Stop();
            Delay_MS(1); // Need to wait for all SPI writes to complete
            Cube_Write_DCS(frame[0]);
            TIMER5_Start();
            break;
        case CUBE_COMMAND_CLEAR:
            Cube_Clear();
            break;
        case CUBE_COMMAND_SET_PIXEL:
            Cube_Set_Pixel(frame[0], frame[1], frame[2], frame[3], frame[4], frame[5]);
            break;
        case CUBE_COMMAND_SET_ALL:
            Cube_Data_Direct_Write_All(&frame[0]);
            break;
        case CUBE_COMMAND_START_TEXT:
            Cube_Text_Init(&frame[3], cube_data_ptr->frame_length - 4, frame[0], frame[1], frame[2]);
            TIMER4_Start();
            break;
        case CUBE_COMMAND_STOP_TEXT:
            TIMER4_Stop();
            Cube_Overlay_Clear();
            break;
        default:
            break;
    }
}

void Cube_Data_Direct_Write_All(uint8_t *buffer) {
    memcpy(cube_data_ptr->GCS, buffer, CUBE_LAYER_COUNT * GCS_LAYER_SIZE);
}

void Cube_Ethernet_Frame_In(void) {
    uint8_t i,j,k;
    uint8_t buffer[2048] = {0};
    uint16_t length;

    // Read and process the ethernet packet
    if (!ETH_Read_Packet(buffer, &length)) {
        // Check the opcode (first byte) to determine what to do

        if (buffer[0] == CUBE_ETH_RESET) {  // 0x1 - Reset into Ethernet mode
            Reset_Board(BOARD_MODE_ETHERNET);
        } else if (Get_Board_State() == BOARD_MODE_ETHERNET) {
            if (buffer[0] == CUBE_EHT_IDLE) {   // 0x2 - Reset back to idle mode
                Reset_Board(BOARD_MODE_IDLE);
            } else if (buffer[0] == CUBE_ETH_CLEAR) {   // 0xA
                Cube_Clear();
            } else if (buffer[0] == CUBE_ETH_DCS) {     // 0xB
                // Byte 1 = global brightness value
                Cube_Write_DCS(buffer[1]);
            } else if (buffer[0] == CUBE_ETH_ROTATE) {  // 0xC
                // Byte 1 = directon to rotate
                ClearWDT();
                Cube_Rotate(buffer[1]);
            } else if (buffer[0] == CUBE_ETH_ROTATE_LAYER) {    // 0xD
                // Byte 1 = layer to rotate
                // Byte 2 = direction to rotate
                ClearWDT();
                Cube_Rotate_Shell(buffer[1], buffer[2]);
            } else if (buffer[0] == CUBE_ETH_WRITE_ALL) {       // 0x10
                // Byte 1+ = pixel color data (R/G/B)
                if (length == 0x0601) {
                    ClearWDT();
                    uint16_t index = 1;
                    for (i = 0; i < CUBE_LAYER_COUNT; i++) {
                        for (j = 0; j < CUBE_COLUMN_COUNT; j++) {
                            for (k = 0; k < CUBE_ROW_COUNT; k++) {
                                Cube_Set_Pixel(i, k, j, buffer[index], buffer[index+1], buffer[index+2]);
                                index = index + 3;
                            }
                        }
                    }
                }
            } else if (buffer[0] == CUBE_ETH_WRITE_PIXEL) {     // 0x11
                // Byte 1 = row index
                // Byte 2 = column index
                // Byte 3 = layer index
                // Byte 4 = red channel
                // Byte 5 = green channel
                // Byte 6 = blue channel
                ClearWDT();
                Cube_Set_Pixel(buffer[3], buffer[1], buffer[2], buffer[4], buffer[5], buffer[6]);
            } else if (buffer[0] == CUBE_ETH_WRITE_CHANNEL) {   // 0x12
                // Byte 1 = color channel, 0 = red, 1 = green, 2 = blue
                // Byte 2+ = color data
                uint16_t r, g, b;
                uint16_t index = 2;
                ClearWDT();
                if (buffer[1] % 3 == 0) {
                    for (i = 0; i < CUBE_LAYER_COUNT; i++) {
                        for (j = 0; j < CUBE_ROW_COUNT; j++) {
                            for (k = 0; k < CUBE_COLUMN_COUNT; k++) {
//                                Cube_Get_Pixel(i, j, k, &r, &g, &b);
                                Cube_Set_Pixel(i, j, k, buffer[index], 0x00, 0x00);
                                index++;
                            }
                        }
                    }
                } else if (buffer[1] % 3 == 1) {
                    for (i = 0; i < CUBE_LAYER_COUNT; i++) {
                        for (j = 0; j < CUBE_ROW_COUNT; j++) {
                            for (k = 0; k < CUBE_COLUMN_COUNT; k++) {
                                Cube_Get_Pixel(i, j, k, &r, &g, &b);
                                Cube_Set_Pixel(i, j, k, r, buffer[index], b);
                                index++;
                            }
                        }
                    }
                } else {
                    for (i = 0; i < CUBE_LAYER_COUNT; i++) {
                        for (j = 0; j < CUBE_ROW_COUNT; j++) {
                            for (k = 0; k < CUBE_COLUMN_COUNT; k++) {
                                Cube_Get_Pixel(i, j, k, &r, &g, &b);
                                Cube_Set_Pixel(i, j, k, r, g, buffer[index]);
                                index++;
                            }
                        }
                    }
                }
            } else if (buffer[0] == CUBE_ETH_WRITE_TEXT) {      // 0x20
                // Byte 1 = length of string
                // Byte 2 = red channel
                // Byte 3 = green channel
                // Byte 4 = blue channel
                // Byte 5 = update speed (ms)
                // Byte 6+ = text string
                if (buffer[1] != 0) {
                    Cube_Text_Init(&buffer[6], buffer[1], buffer[2], buffer[3], buffer[4]);

                    TIMER4_Stop();
                    TIMER4_Init(NULL, NULL, &Cube_Text_Interrupt, buffer[5]);
                    TIMER4_Start();
                } else {
                    Cube_Overlay_Clear();
                    TIMER4_Stop();
                }
            } else if (buffer[0] == CUBE_ETH_WATERFALL) {       // 0x30
                // Byte 1 = height of column 0
                // Byte 2 = height of column 1
                // Byte 3 = height of column 2
                // Byte 4 = height of column 3
                // Byte 5 = height of column 4
                // Byte 6 = height of column 5
                // Byte 7 = height of column 6
                // Byte 8 = height of column 7
                Cube_Shift_Waterfall(&buffer[1]);
            } else if (buffer[0] == CUBE_ETH_SPHERE) {          // 0x31
                // Byte 1 = layer (0 = innermost)
                // Byte 2 = red channel
                // Byte 3 = green channel
                // Byte 4 = blue channel
                Cube_Set_Sphere(buffer[1], buffer[2], buffer[3], buffer[4]);
            }
        }
    }
}