#include "sk6805.h"

extern TIM_HandleTypeDef htim2;

uint32_t pwm_data[PWM_BUF_LEN];

SK6805_Color leds[LED_COUNT];

volatile uint8_t sk6805_done = 1;


/*
    Write one SK6805 data bit into pwm_data[]. CCR value 4 or CCR value 10
    It stores PWM compare values that create the correct pulse width.

    PWM mode 1 means Output is high while counter is less than compare value.
      PWM mode 2 (not used) means low while counter is less than compare value
    At the start of each PWM period, the output goes HIGH.
    When the timer counter reaches the compare value, the output goes LOW.
    So the compare value controls how long the pulse stays HIGH.

    idx will point to middle of array since there is a starting and ending RESET_SLOTS
*/
static void SK6805_WriteBit(uint32_t *idx, uint8_t bit)
{
    pwm_data[(*idx)++] = bit ? SK6805_1 : SK6805_0;
}
/*
    Convert one 8-bit color byte into 8 PWM values in pwm_data[].
    SK6805 wants the most-significant bit first (7 read first, then bit 6,...
    Example:  byte = 0b00111011
    writes:
      bit 7 = 0 -> SK6805_0
      bit 6 = 0 -> SK6805_0
      ...
    So pwm_data[] gets:
      4, 4, 10, 10, 10, 4, 10, 10
*/

static void SK6805_WriteByte(uint32_t *idx, uint8_t byte)
{
    for (int8_t bit = 7; bit >= 0; bit--)
    {
        SK6805_WriteBit(idx, (byte >> bit) & 0x01);
    }
}

void SK6805_Clear(void)
{
    for (uint32_t i = 0; i < LED_COUNT; i++)
    {
        leds[i].g = 0;
        leds[i].r = 0;
        leds[i].b = 0;
    }
}

void SK6805_SetLed(uint32_t led_index, uint8_t r, uint8_t g, uint8_t b)
{
    if (led_index >= LED_COUNT)
        return;
    leds[led_index].g = g;
    leds[led_index].r = r;
    leds[led_index].b = b;
}


void SK6805_Show(void)
{
    uint32_t idx = 0;

    while (!sk6805_done)
    {
        // Wait for previous DMA transfer to finish
    }

    // Force output low before starting new frame
    // this was found testing in hardware, first led would flicker green
    __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, 0);
    __HAL_TIM_SET_COUNTER(&htim2, 0);

    // Leading reset/latch low time
    for (uint32_t i = 0; i < RESET_SLOTS; i++){pwm_data[idx++] = 0;}

    // LED data
    for (uint32_t led = 0; led < LED_COUNT; led++)
    {
        SK6805_WriteByte(&idx, leds[led].g);
        SK6805_WriteByte(&idx, leds[led].r);
        SK6805_WriteByte(&idx, leds[led].b);
    }

    // Trailing reset/latch low time
    // while loop seems safer than for loop
    while (idx < PWM_BUF_LEN){pwm_data[idx++] = 0;}

    sk6805_done = 0;

    // Send all values from pwm_data[] to TIM2_CH1.
    // Keep going until PWM_BUF_LEN values have been sent.
    // When the DMA has finished sending the whole buffer, HAL calls HAL_TIM_PWM_PulseFinishedCallback

    if (HAL_TIM_PWM_Start_DMA(&htim2, TIM_CHANNEL_1, pwm_data, PWM_BUF_LEN) != HAL_OK)
    {
        Error_Handler();
    }
}

// for dma interrupt you need to edit the call back template in main.c
// should see somehting like this
// void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim)
// {
//     if (htim->Instance == TIM2)
//     {
//         // Stop PWM DMA on TIM2 channel 1.
//         // Force CCR1 to 0 so PA0 stays low.
//         // Set sk6805_done = 1 so the main code knows it can send again.
//         HAL_TIM_PWM_Stop_DMA(&htim2, TIM_CHANNEL_1);
//         __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, 0);
//         sk6805_done = 1;
//     }
// }



