/*-----------------------------------------------------------------------
  WS2812Ctrl.c

  Control of LED Strip with WS2812B driver
  -----------------------------------------------------------------------
  Last committed:     $Revision: 05 $
  Last changed by:    $Author: Thomas.Stief@web.de$
  Last changed date:  $Date:  04.05.2015 $
  ID:                 $Id: WS2812Ctrl $

  -----------------------------------------------------------------------
  Resources
  -----------------------------------------------------------------------
	* µC:			STM32F103C8
		- GPIO:		A0
		- Timer:	TIM2
		- DMA:		DMA1 Channel 2
		- IRQ:		DMA1_Channel2_IRQHandler

  -----------------------------------------------------------------------
  Revision History
  -----------------------------------------------------------------------
  01   - Created
  02   - Interface adjusted
  03   - Calculation of psychophysic perception included
       - additional coding value in COLOR struct included -> different coding
         possible for individual LED
  04   - Remove relics of Standard peripheral library
       - Change extension to CPP - use within C++ projects
  05   - Remove SPL completely, declare ISR as extern "C"

  -----------------------------------------------------------------------
  ToDo
  -----------------------------------------------------------------------
  -
*/

#include "stm32f10x.h"
#include "WS2812Ctrl\WS2812Ctrl.h"
#include "HSV2RGB\hsv2rgb.h"

// Define internal Functions
void fillDMABuffer(uint8_t nPos);

// Define internal variables

// PWM table
const uint8_t u8PWMValue[64] = {	  0,   0,   1,   1,   1,   1,   2,   2,
									  3,   3,   4,   5,   5,   6,   7,   8,
									  9,  11,  12,  13,  15,  17,  19,  20,
									 23,  25,  27,  30,  32,  35,  38,  41,
									 45,  48,  52,  56,  60,  64,  68,  73,
									 78,  83,  88,  93,  99, 105, 111, 117,
									124, 131, 138, 145, 153, 161, 169, 177,
									186, 195, 204, 214, 224, 234, 244, 255
 };

// DMA Buffer
uint8_t DMABuffer[48];

// Data Pointer
COLOR *pLEDData;
// Number of LEDs stored in pData
uint16_t nLED=0;
// Counter LEDs transfered
volatile uint16_t iLED=0;

void initWS2812(void)
{
    // Init GPIO
    // --------------------------------------------------------------
    RCC->APB2ENR|= RCC_APB2ENR_IOPAEN;					// GPIOA: Clock enable

	GPIOA->CRL &= ~GPIO_CRL_MODE0 & ~GPIO_CRL_CNF0;		// initialize GPIOA->CRL Pin0
	GPIOA->CRL |= GPIO_CRL_CNF0_1 |						// GPIOA.0 -> Alternative function, Push-Pull
				  GPIO_CRL_MODE0_1;						// GPIOA.0 -> Mode Output, 2MHz

    // Init DMA
    // --------------------------------------------------------------
    RCC->AHBENR |= RCC_AHBENR_DMA1EN;					// DMA1: Clock enable

	DMA1_Channel2->CCR &=~DMA_CCR2_EN;					// disable DMA1 Channel 2

	DMA1_Channel2->CPAR = (uint32_t)&(TIM2->CCR1);		// Set Base-Address Peripheral
	DMA1_Channel2->CMAR = (uint32_t)DMABuffer;			// Set Base-Address Memory
	DMA1_Channel2->CNDTR = 48;							// Buffer len

	DMA1_Channel2->CCR = DMA_CCR2_TCIE |				// Transfer Complete IRQ enable
						 DMA_CCR2_HTIE | 				// Half Transfer IRQ enable
						 DMA_CCR2_DIR |					// Memory -> Peripheral
						 DMA_CCR2_CIRC |				// Circular Mode enable
					/*	 DMA_CCR2_PINC |		*/		// Memory increment disable
						 DMA_CCR2_MINC |				// Memory increment enable
						 DMA_CCR2_PSIZE_0 |				// PSIZE[1:0] = 01 -> Peripheral = 16bit
					/*	 DMA_CCR2_MSIZE_0 |		*/		// MSIZE[1:0] = 00 -> Memory = 8bit
						 DMA_CCR2_PL_1 ;				// PL[1:0] = 10 -> Priority = high
					/*	 DMA_CCR2_MEM2MEM		*/		// MEM2MEM disable

    // Init TIM2
    // --------------------------------------------------------------
	RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;

    // Basic Init
    TIM2->PSC = 0;										// no prescale -> clock TIM2 = 72MHz
	TIM2->ARR = 90;										// Counting 0 ... 89 @ 72MHz = 800kHz (DT = 1,25µs)
	TIM2->CCR1 = 0;										// Capture Compare = 0 -> register will be modified by DMA-Transfer
	TIM2->CR1 &=~TIM_CR1_DIR &							// Upward counting
				~TIM_CR1_CMS;							// Edge aligned mode
	TIM2->CCMR1 = TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1M_1;	// PWM Mode 1: active @ CNT < CCR1 / not active @ CNT > CCR1   / PWM Channel 2 disabled
	TIM2->CCMR2 = 0;									// PWM Channel 3 and 4 disabled
	TIM2->CCER |= TIM_CCER_CC1E;						// Channel configured as output
			/*  &~TIM_CCER_CC1P */						// Channel active high

	// Activate DMA Transfer
	TIM2->DIER |=  TIM_DIER_UDE;						// DMA Transfer Update cmd. enable

    // Init Core
    // --------------------------------------------------------------
	NVIC_SetPriority(DMA1_Channel2_IRQn,0);				// Set Priority of DMA-IRQ to high
	DBGMCU->CR |=  DBGMCU_CR_DBG_TIM2_STOP;				// Timer stop @ break

}

uint8_t doTransferWS2812(COLOR *_pLEDData, uint16_t _nLED)
{
	// initialize internal Data and store parameters
    // --------------------------------------------------------------
	if(iLED > 0)										// if iLED > 0 -> transfer in progress
		return 0;										// return with error

	pLEDData=_pLEDData;									// store data pointer
	nLED=_nLED;											// store count LED-data to be transfered

	// fill DMABuffer
    // --------------------------------------------------------------
	fillDMABuffer(0);									// fill lower part of DMA-Buffer with data of first LED
	fillDMABuffer(24);									// fill higher part of DMA-Buffer with data of second LED

    // enable transfer to WS2812-LEDs
    // --------------------------------------------------------------
    NVIC_EnableIRQ(DMA1_Channel2_IRQn);					// enable DMA-IRQ

	DMA1_Channel2->CNDTR = 48;							// init DMA: set pending transfers to 48
	DMA1_Channel2->CCR|= DMA_CCR1_EN;					// enable DMA1 Channel 2

	TIM2->CNT = 0;										// Set timer to 0 -> timer start at the
	TIM2->CR1 |= TIM_CR1_CEN;							// enable timer

	return 1;		// return without error
}

extern "C" void DMA1_Channel2_IRQHandler(void)
{
	if((DMA1->ISR & DMA_ISR_HTIF2) != 0)				// check if half transfer flag is set
	{
		DMA1->IFCR = DMA_IFCR_CHTIF2;					// clear half transfer flag
		fillDMABuffer(0);								// fill lower part of DMA buffer with next LED data
	}

	// Transfer complete -> refill upper DMABuffer
	if((DMA1->ISR & DMA_ISR_TCIF2) != 0	)				// check if transfer complete flag is set
	{
		DMA1->IFCR = DMA_IFCR_CTCIF2;					// clear transfer complete flag
		fillDMABuffer(24);								// fill upper part of DMA buffer with next LED data
	}

	// if iLED < nLED -> fillDMABuffer will fill the next LED data into the Buffer
	// if iLED >= nLED -> fill DMABuffer will fill Zeros into the Buffer results in pin @ low -> LED-Bus reset
	if(iLED > nLED+2)									// after 2 Transfers with Zeros => deactivate Timer, DMA and IRQs
	{
		TIM2->CR1 &=~TIM_CR1_CEN;						// disable Timer 2
		DMA1_Channel2->CCR &=~DMA_CCR1_EN;				// disable DMA1 Channel 2
		NVIC_DisableIRQ(DMA1_Channel2_IRQn);			// disable DMA-IRQ

		iLED = 0;										// counter iLED = 0 -> flag for "transfer complete"
	}
}

void fillDMABuffer(uint8_t nPos)
{
	uint8_t i,r,g,b;

	if(iLED<nLED)										// if iLED < nLED -> fill DMABuffer with next LED data
	{
		switch(pLEDData[iLED].Coding)
		{
		case COLOR_CODING_HSV:							// LED Data stored as HSV-Value in pLEDData
			translateHSVtoRGB(pLEDData[iLED].h,pLEDData[iLED].s,pLEDData[iLED].v,&r,&g,&b);
														// translate next LED data (HSV-Value) into RGB value and store this in r,g,b
			// Adjust RGB-values accordingly psychophysic perception
			r= u8PWMValue[r];		// red
			g= u8PWMValue[g];		// green
			b= u8PWMValue[b];		// blue
			break;
		case COLOR_CODING_RGB:
			// Adjust RGB-values accordingly psychophysic perception
			r=pLEDData[iLED].r < 64?u8PWMValue[pLEDData[iLED].r]:255;	// red
			g=pLEDData[iLED].g < 64?u8PWMValue[pLEDData[iLED].g]:255;	// green
			b=pLEDData[iLED].b < 64?u8PWMValue[pLEDData[iLED].b]:255;	// blue
			break;
		case COLOR_CODING_RGB_DIRECT:
		default:
			r=pLEDData[iLED].r;		// red
			g=pLEDData[iLED].g;		// green
			b=pLEDData[iLED].b;		// blue
			break;
		}

		for(i=0;i<8;i++)								// for all bits in r,g and b:
														// if highest bit in r, g or b
		{												// is set -> corresponding DMABuffer byte is set to 60 => 0,833µs high and 0,416µs low
														// if not -> corresponding DMABuffer byte is set to 30 => 0,416µs high and 0,833µs low
			DMABuffer[nPos+i]=(g&0x80)!=0?60:30;		// first 8 byte of DMA-Buffer = green
			DMABuffer[nPos+i+8]=(r&0x80)!=0?60:30;		// second 8 bytes of DMA-Buffer = red
			DMABuffer[nPos+i+16]=(b&0x80)!=0?60:30;		// last 8 byte of DMA-Buffer = blue
			r<<=1;										//
			g<<=1;										// shift r,g,b one bit to the left -> next test will check the one lower bit
			b<<=1;										//
		}
		iLED++;											// increase iLED -> net time function called, next LED data is transfered
	}
	else												// if iLED >= nLED
	{
		for(i=0;i<24;i++)
			DMABuffer[nPos+i]=0;						// fill all 24 bytes of DMA-Buffer with Zero => pin is held low => LED-bus reset
		iLED++;											// increase iLED
	}
}
