/* Includes ------------------------------------------------------------------*/
#include "stm32f4xx_hal.h"
#include "globals.h"
/* USER CODE BEGIN Includes */
#include "led_driver.h"
/* USER CODE END Includes */

// Format Image data so that it can be pushed as is by DMA toward the GPIO
// Starting from image in raw format RGB.
// lines from left to right and top to bottom

// NUCLEO 64 avec 446
// to be put in port B0..10,12..15 
// and PC0..PC4


// Sequence de pilotage des leds
// 8 bits G7..0, 8 bits R7..0, 8 bits B7..0

// Encodage des bits
// Bit 0 : 100
// Bit 1 : 110



// To be called just once
// This is actually supposed to be done at the end, but that's ok this way
void initLedResetCodeInMemoryForDMA2(uint16_t *portbmem, uint16_t *portcmem)
{
	uint32_t i;
	
	// pre fill never changing parts
	for (i=0;i<NB_LEDS_PER_STRIP*8*3;i++)
	{
		*portbmem++=0xFFFF;// pre fill, never changes
		*portcmem++=0xFFFF;// pre fill, never changes
		*portbmem++=0x0000;// will be overwritten
		*portcmem++=0x0000;// will be overwritten
		*portbmem++=0x0000;// pre fill, never changes
		*portcmem++=0x0000;// pre fill, never changes
	}

	for (i=0;i<LED_RESET_DELAY;i++)
	{
		*portbmem++ = 0;
		*portcmem++ = 0;
	}
	
}

/*
LED0 PC4
LED1 PB1
LED2 PB2
LED3 PB3
LED4 PB5
LED5 PB4
LED6 PB6
LED7 PC8 (R3)

LED8 PC6 (R1)
LED9 PB9
LED10 PB12
LED11 PB8
LED12 PB15
LED13 PB10
LED14 PB14
LED15 PB13

LED16 PC3
LED17 PC2
LED18 PC1
LED19 PB0
LED20 PC0
LED21 PC9 (R4)
LED22 PC5
LED23 PC7 (R2)

LED24 PC11 (R7)
LED25 PC10 (R5)
LED26 PC12 (R8)
LED27 PB7
LED28 PC13 (R9)
LED29 PC14 (R10)
LED30 PC15 (R47)
LED31 PD2 ou OPT_PB11 (on other boards)
*/

// Encodes GPIO port bit numbers as a function of LED number according to ^
uint8_t btable[32] = 
{
  4,1,2,3,5,4,6,8,
	6,9,12,8,15,10,14,13,
	3,2,1,0,0,9,5,7,
	11,10,12,7,13,14,15,11
};

__attribute__((section("dtcmram")))
uint16_t bbintable[32];

// 0 for PORTB,
// 1 for PORTC, 
// 2 for PORTD if you want to use PD2 LED31 instead of PB11, you will have to treat that special case yourself by adding the code in formatMemoryForDMA just for PD2
// + dma code... for just one bit...
// Encodes GPIO port as a function of LED number according to ^^

__attribute__((section("dtcmram")))
uint8_t ptable[32] = 
{
	1,0,0,0,0,0,0,1,
	1,0,0,0,0,0,0,0,
	1,1,1,0,1,1,1,1,
	1,1,1,0,1,1,1,0
};


// 0 <= integer_offset < NB_LEDS_PER_STRIP required
// Version rerouting bits
// Une part de la compéxité de ce code vient de ce qu'il réalise un offset entier integer_offset horizontal sur l'image, il doit gérer le rebouclage image

// R,G,B,R,G,B,R,G,B : raw image 
// G,R,B,G,R,B,G,R,B : how it must be sent to WS2812B
__attribute__((section("dtcmram")))
int8_t rgbtrip[3] = {-1,2,2}; // starts with G then R (-1) then B (+2) then again +2 to reach next G 

__attribute__((section("itcmram")))
void formatMemoryForDMA2(uint8_t *raw, uint16_t *portbmem, uint16_t *portcmem, uint8_t integer_offset)
{
		uint8_t line,column,rgb_bit;
		uint8_t *rgb[NB_LINES];
		uint16_t tmpmem[2];
		uint8_t rgbsequence;
	
	  static uint8_t initialized = 0;

		if (initialized==0)
		{
			for (line=0;line<32;line++)
			{
				bbintable[line] = 1 << btable[line];
			}
			initialized = 1;
		}

		// calcul pour chaque ligne l'adresse de début
		for (line=0;line<NB_LINES;line++)
		{
			rgb[line] = raw+1+integer_offset*3; // Starts with G, hence the +1
			// Go to next Line
			raw+=NB_LEDS_PER_STRIP*3;
		}

		portbmem++; // skip first pre filled =0xFFFF;
		portcmem++; // skip first pre filled =0xFFFF;
		
		for (column=0;column<NB_LEDS_PER_STRIP;column++)
		{
			// avec l'integer_offset, si on arrive au bout de la bande, il faut revenir au début
			// seulement une fois. 
			// si integer_offset==1, ça se fait juste à la dernière colonne : column==NB_LEDS_PER_STRIP-1
			if (column+integer_offset==NB_LEDS_PER_STRIP)
			{
				for (line=0;line<NB_LINES;line++)
				{
					rgb[line]-=NB_LEDS_PER_STRIP*3; 
				}				
			}			
			
			// Send the 3 colors for the column
			for (rgbsequence=0;rgbsequence<3;rgbsequence++)
			{
				// 3x G then R then B
				rgb_bit = 0x80;
				while(rgb_bit)
				{
					// PORTB&C
//					portbmem++; // skip =0xFFFF;, already done
//					portcmem++; // skip =0xFFFF;, already done
					tmpmem[0] = 0;
					tmpmem[1] = 0;
					// PORTB&C 0..NB_LINES
					for (line=0;line<NB_LINES;line++)
					{
						if ((*rgb[line])&rgb_bit)
						{
							tmpmem[ptable[line]] |= bbintable[line]; // routing according to printed circuit board
						}
					}
					*portbmem = tmpmem[0];
					portbmem+=3; // skip =0x0000, 0xFFFF;
					*portcmem = tmpmem[1];
					portcmem+=3; // skip =0x0000, 0xFFFF;
					rgb_bit = rgb_bit >> 1; // Bit 7 à Bit 0.
				}
			
				for (line=0;line<NB_LINES;line++)
				{
					rgb[line]+=rgbtrip[rgbsequence]; // next is R, next time +=2, then +=2
				}
			}
			
		}
		
}


extern DMA_HandleTypeDef hdma_tim2_ch1;
extern DMA_HandleTypeDef hdma_tim2_up_ch3;
extern TIM_HandleTypeDef htim2;

extern DMA_HandleTypeDef hdma_tim8_ch1;
extern DMA_HandleTypeDef hdma_tim8_ch3;
extern TIM_HandleTypeDef htim8;

void dmaLedDrive2(uint16_t *portbmem, uint16_t *portcmem)
{

	GPIOB->ODR = GPIOC->ODR = 0;
//	TIM6->CNT = 0;

	HAL_DMA_Start(&hdma_tim8_ch1, (uint32_t) portbmem, (uint32_t) &GPIOB->ODR, NB_DMA_WRITES);
	HAL_DMA_Start(&hdma_tim8_ch3, (uint32_t) portcmem, (uint32_t) &GPIOC->ODR, NB_DMA_WRITES);

	// pourrait s'avérer nécessaire d'interrompre les interrupions entre ces deux instructions pour éviter un déphasage.
	// pas de problème, rien ne se passe avant que le timer 8 ne soit démarré.
  __HAL_TIM_ENABLE_DMA(&htim8, TIM_DMA_CC1);
	__HAL_TIM_ENABLE_DMA(&htim8, TIM_DMA_CC3);

// Appelle pas ces fonction, pour ne pas démarrer le timer avant la fin de la config
//	HAL_TIM_OC_Start(&htim8,TIM_CHANNEL_1);  //Démarre aussi le Timer 8
//	HAL_TIM_OC_Start(&htim8,TIM_CHANNEL_3);

  TIM_CCxChannelCmd(TIM8, TIM_CHANNEL_1, TIM_CCx_ENABLE);
  TIM_CCxChannelCmd(TIM8, TIM_CHANNEL_3, TIM_CCx_ENABLE);
  
  if(IS_TIM_ADVANCED_INSTANCE(TIM8) != RESET)  
  {
    /* Enable the main output */
    __HAL_TIM_MOE_ENABLE(&htim8);
  }
	
//	while(TIM6->CNT<WAIT55US);
  /* Enable the Peripheral */
  __HAL_TIM_ENABLE(&htim8); 
  

}


void dmaPostLedDrive(void)
{
	// Il faut désactiver le CC0 avant le timer lui même. Sinon, __HAL_TIM_DISABLE est non fonctionnel.
  TIM_CCxChannelCmd(TIM8, TIM_CHANNEL_1, TIM_CCx_DISABLE);
  TIM_CCxChannelCmd(TIM8, TIM_CHANNEL_3, TIM_CCx_DISABLE);
	__HAL_TIM_MOE_DISABLE(&htim8);
	__HAL_TIM_DISABLE(&htim8); 
  __HAL_TIM_DISABLE_DMA(&htim8, TIM_DMA_CC1);
	__HAL_TIM_DISABLE_DMA(&htim8, TIM_DMA_CC3);
}

void dmaWait(void)
{
	HAL_StatusTypeDef dma_state;

	dma_state =  HAL_DMA_PollForTransfer(&hdma_tim8_ch1, HAL_DMA_FULL_TRANSFER, 20);
	if (dma_state == HAL_OK)
	{
		dma_state =  HAL_DMA_PollForTransfer(&hdma_tim8_ch3, HAL_DMA_FULL_TRANSFER, 20);
		if (dma_state == HAL_OK)
		{
			dmaPostLedDrive(); // 1µs ou moins
		}
		else
			crash(CC_DMA_TIM8_CH3_FAILED);					
	}				
	else
		crash(CC_DMA_TIM8_CH1_FAILED);
}

// Only one case  : in case of loss of power
void dmaUrgentStop(void)
{
	__HAL_DMA_DISABLE(&hdma_tim8_ch1);
	__HAL_DMA_DISABLE(&hdma_tim8_ch3); // pour gagner un peu de temps
	HAL_DMA_Abort(&hdma_tim8_ch1);
	HAL_DMA_Abort(&hdma_tim8_ch3);
//  dmaPostLedDrive();
}

