/* Project L476_ats_blink for STM32L476 mounted on Nucleo board:
 * the user LED (mounted on pin PA-5) is flashed every second for 50 ms.
 * The time base is provided by Systick (1000 ticks per second).
 * The clock configuration is the default one (Sysclk = 80 MHz, derived from MSI and PLL).
 */

/* Includes ------------------------------------------------------------------*/
#include "main.h"
// #if defined(USE_FULL_ASSERT)
// #include "stm32_assert.h"
// #endif /* USE_FULL_ASSERT */

#include "gpio.h"

// systick interrupt handler
volatile uint32_t msTicks = 0;
volatile uint8_t expe = 0;
volatile uint8_t blue_mode = 0;

void SysTick_Handler()	//occurs every 10ms
{
	if	( BLUE_BUTTON() ){	//switch to blue mode if blue button was pressed
		blue_mode = 1 ;
	}

	msTicks++;					//increment time counter
	if (msTicks == 5 * expe){	//turn off led after 50ms*expe
		LED_GREEN(0);
	}else if(msTicks >= 200){	//turn on led every 2 seconds
		msTicks = 0;
		LED_GREEN(1);
	}
	if(expe == 2 || expe == 4){	//output a 50Hz clock on PC10 to evaluate frequency stability for expe 2 & 4
		CLK_TOGGLE();
	}
}




int main(void)
{


	// config GPIO
	GPIO_init();

//	if (RCC->BDCR & RCC_BDCR_LSEON) {
		LL_APB1_GRP1_EnableClock( LL_APB1_GRP1_PERIPH_PWR );
		LL_PWR_EnableBkUpAccess();

		//expe = register RTC
		expe = RTC->BKP0R;		//Load expe number from backup register
		if (expe == 0) {		//if cold start : setup clock, LSE & set expe to 1
			SystemClock_Config_24M_LSE();
			expe = 1;
			LL_APB1_GRP1_EnableClock( LL_APB1_GRP1_PERIPH_PWR );
			LL_PWR_EnableBkUpAccess();
			RTC->BKP0R = expe;
		}

		if (BLUE_BUTTON()){	//if blue_button is pressed at reset, switch to next expe

			expe ++;
			blue_mode = 0;
			if (expe > 8) expe = 1;	//rollover to expe 1
			RTC->BKP0R = expe;
		}
//	}else{

//	}
	LL_PWR_DisableBkUpAccess();
	switch(expe){
	case 1:
		/* Configure the system clock */
		SystemClock_Config_80M();
		//Setup Sleep mode
		LL_LPM_EnableSleep();
		break;
	case 2:
		/* Configure the system clock */
		SystemClock_Config_24M_LSE();
		break;
	case 3:
		SystemClock_Config_24M_LSE_FL3_VS2();
		LL_LPM_EnableSleep();
		break;
	case 4:
		SystemClock_Config_24M_LSE_FL3_VS2();
	case 5:
		SystemClock_Config_24M_LSE_FL3_VS2();
		LL_PWR_SetPowerMode(LL_PWR_MODE_STOP0);
		break;
	case 6:
		SystemClock_Config_24M_LSE_FL3_VS2();
		LL_PWR_SetPowerMode(LL_PWR_MODE_STOP1);
		break;
	case 7:
		SystemClock_Config_24M_LSE_FL3_VS2();
		LL_PWR_SetPowerMode(LL_PWR_MODE_STOP2);
		break;
	case 8:
		SystemClock_Config_24M_LSE_FL3_VS2();
		LL_PWR_SetPowerMode(LL_PWR_MODE_SHUTDOWN);
		break;
	}


	// init systick timer (tick period at 10 ms)
	LL_Init1msTick( SystemCoreClock );
	LL_SYSTICK_EnableIT();


	//LL_LPM_EnableSleepOnExit();

	while (1)  {
		if (blue_mode){
			switch(expe){
			case 5:											//cases 5-7 : start stop mode, and reconfigure at wakeup
			case 6:
			case 7:
				LL_LPM_EnableDeepSleep();
				RTC_wakeup_init_from_stop(20);
				__WFI();
				blue_mode = 0;

				SystemClock_Config_24M_LSE_FL3_VS2();
				LL_Init1msTick( SystemCoreClock );
				LL_SYSTICK_EnableIT();
				break;
			case 8:
				LL_LPM_EnableDeepSleep();					//case 8 : shutdown (wakeup=>reset)
				RTC_wakeup_init_from_standby_or_shutdown(10);
			case 1:
			case 3:
				__WFI();
				break;
			case 2:
			case 4:
				LL_RCC_MSI_EnablePLLMode();					//case 4 : enable MSI calibration
				break;
			}
		}else{
				if (expe > 4) {								//expe 5-8 : sleep as normal mode
					LL_LPM_EnableSleep();
					__WFI();
				}
		}
	}
}
/**
 * @brief System Clock Configuration
 * @retval None
 * 24Mhz + RTC + LSE + Flash latency 3 + Voltage scaling 2
 */
void SystemClock_Config_24M_LSE_FL3_VS2(void){
	LL_APB1_GRP1_EnableClock( LL_APB1_GRP1_PERIPH_PWR );
	LL_FLASH_SetLatency(LL_FLASH_LATENCY_3);
	while(LL_FLASH_GetLatency()!= LL_FLASH_LATENCY_3)
	{
	}
	LL_PWR_SetRegulVoltageScaling(LL_PWR_REGU_VOLTAGE_SCALE2);
	LL_RCC_MSI_Enable();

	/* Wait till MSI is ready */
	while(LL_RCC_MSI_IsReady() != 1)
	{

	}

	LL_PWR_EnableBkUpAccess();
//	LL_RCC_ForceBackupDomainReset();
	LL_RCC_ReleaseBackupDomainReset();
	LL_RCC_LSE_SetDriveCapability(LL_RCC_LSEDRIVE_LOW);

	LL_RCC_MSI_EnableRangeSelection();
	LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_6);
	LL_RCC_MSI_SetCalibTrimming(0);
	//  LL_RCC_MSI_EnablePLLMode();

	LL_RCC_LSE_Enable();

	/* Wait till LSE is ready */
	while(LL_RCC_LSE_IsReady() != 1)
	{

	}
	LL_RCC_SetRTCClockSource(LL_RCC_RTC_CLKSOURCE_LSE);
	LL_RCC_EnableRTC();

	LL_RCC_PLL_ConfigDomain_SYS(LL_RCC_PLLSOURCE_MSI, LL_RCC_PLLM_DIV_1, 24, LL_RCC_PLLR_DIV_4);
	LL_RCC_PLL_EnableDomain_SYS();
	LL_RCC_PLL_Enable();

	/* Wait till PLL is ready */
	while(LL_RCC_PLL_IsReady() != 1)
	{

	}
	LL_RCC_SetSysClkSource(LL_RCC_SYS_CLKSOURCE_PLL);

	/* Wait till System clock is ready */
	while(LL_RCC_GetSysClkSource() != LL_RCC_SYS_CLKSOURCE_STATUS_PLL)
	{

	}
	LL_RCC_SetAHBPrescaler(LL_RCC_SYSCLK_DIV_1);
	LL_RCC_SetAPB1Prescaler(LL_RCC_APB1_DIV_1);
	LL_RCC_SetAPB2Prescaler(LL_RCC_APB2_DIV_1);
	LL_SetSystemCoreClock(24000000);
}

/**
 * @brief System Clock Configuration
 * @retval None
 * 24Mhz + RTC + LSE
 */
void SystemClock_Config_24M_LSE(void)
{
	LL_APB1_GRP1_EnableClock( LL_APB1_GRP1_PERIPH_PWR );
	LL_FLASH_SetLatency(LL_FLASH_LATENCY_1);
	while(LL_FLASH_GetLatency()!= LL_FLASH_LATENCY_1)
	{
	}
	LL_PWR_SetRegulVoltageScaling(LL_PWR_REGU_VOLTAGE_SCALE1);
	LL_RCC_MSI_Enable();

	/* Wait till MSI is ready */
	while(LL_RCC_MSI_IsReady() != 1)
	{

	}

	LL_PWR_EnableBkUpAccess();
//	LL_RCC_ForceBackupDomainReset();
	LL_RCC_ReleaseBackupDomainReset();
	LL_RCC_LSE_SetDriveCapability(LL_RCC_LSEDRIVE_LOW);

	LL_RCC_MSI_EnableRangeSelection();
	LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_6);
	LL_RCC_MSI_SetCalibTrimming(0);
	//  LL_RCC_MSI_EnablePLLMode();

	LL_RCC_LSE_Enable();

	/* Wait till LSE is ready */
	while(LL_RCC_LSE_IsReady() != 1)
	{

	}
	LL_RCC_SetRTCClockSource(LL_RCC_RTC_CLKSOURCE_LSE);
	LL_RCC_EnableRTC();

	LL_RCC_PLL_ConfigDomain_SYS(LL_RCC_PLLSOURCE_MSI, LL_RCC_PLLM_DIV_1, 24, LL_RCC_PLLR_DIV_4);
	LL_RCC_PLL_EnableDomain_SYS();
	LL_RCC_PLL_Enable();

	/* Wait till PLL is ready */
	while(LL_RCC_PLL_IsReady() != 1)
	{

	}
	LL_RCC_SetSysClkSource(LL_RCC_SYS_CLKSOURCE_PLL);

	/* Wait till System clock is ready */
	while(LL_RCC_GetSysClkSource() != LL_RCC_SYS_CLKSOURCE_STATUS_PLL)
	{

	}
	LL_RCC_SetAHBPrescaler(LL_RCC_SYSCLK_DIV_1);
	LL_RCC_SetAPB1Prescaler(LL_RCC_APB1_DIV_1);
	LL_RCC_SetAPB2Prescaler(LL_RCC_APB2_DIV_1);
	LL_SetSystemCoreClock(24000000);
}

/**
 * @brief System Clock Configuration
 * @retval None
 * 80MHz using PLL,default flash latency and voltage scaling
 */
void SystemClock_Config_80M(void)
{
	LL_FLASH_SetLatency(LL_FLASH_LATENCY_4);
	while(LL_FLASH_GetLatency()!= LL_FLASH_LATENCY_4)
	{
	}
	LL_PWR_SetRegulVoltageScaling(LL_PWR_REGU_VOLTAGE_SCALE1);
	LL_RCC_MSI_Enable();

	/* Wait till MSI is ready */
	while(LL_RCC_MSI_IsReady() != 1)
	{

	}
	LL_RCC_MSI_EnableRangeSelection();
	LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_6);
	LL_RCC_MSI_SetCalibTrimming(0);
	LL_RCC_PLL_ConfigDomain_SYS(LL_RCC_PLLSOURCE_MSI, LL_RCC_PLLM_DIV_1, 40, LL_RCC_PLLR_DIV_2);
	LL_RCC_PLL_EnableDomain_SYS();
	LL_RCC_PLL_Enable();

	/* Wait till PLL is ready */
	while(LL_RCC_PLL_IsReady() != 1)
	{

	}
	LL_RCC_SetSysClkSource(LL_RCC_SYS_CLKSOURCE_PLL);

	/* Wait till System clock is ready */
	while(LL_RCC_GetSysClkSource() != LL_RCC_SYS_CLKSOURCE_STATUS_PLL)
	{

	}
	LL_RCC_SetAHBPrescaler(LL_RCC_SYSCLK_DIV_1);
	LL_RCC_SetAPB1Prescaler(LL_RCC_APB1_DIV_1);
	LL_RCC_SetAPB2Prescaler(LL_RCC_APB2_DIV_1);
	LL_SetSystemCoreClock(80000000);
}

// partie commune a toutes les utilisations du wakeup timer
static void RTC_wakeup_init( int delay )
{
LL_RTC_DisableWriteProtection( RTC );
LL_RTC_WAKEUP_Disable( RTC );
while	( !LL_RTC_IsActiveFlag_WUTW( RTC ) )
	{ }
// connecter le timer a l'horloge 1Hz de la RTC
LL_RTC_WAKEUP_SetClock( RTC, LL_RTC_WAKEUPCLOCK_CKSPRE );
// fixer la duree de temporisation
LL_RTC_WAKEUP_SetAutoReload( RTC, delay );	// 16 bits
LL_RTC_ClearFlag_WUT(RTC);
LL_RTC_EnableIT_WUT(RTC);
LL_RTC_WAKEUP_Enable(RTC);
LL_RTC_EnableWriteProtection(RTC);
}

// Dans le cas des modes STANDBY et SHUTDOWN, le MPU sera reveille par reset
// causé par 1 wakeup line (interne ou externe) (le NVIC n'est plus alimenté)
void RTC_wakeup_init_from_standby_or_shutdown( int delay )
{
RTC_wakeup_init( delay );
// enable the Internal Wake-up line
LL_PWR_EnableInternWU();	// ceci ne concerne que Standby et Shutdown, pas STOPx
}

// Dans le cas des modes STOPx, le MPU sera reveille par interruption
// le module EXTI et une partie du NVIC sont encore alimentes
// le contenu de la RAM et des registres étant préservé, le MPU
// reprend l'execution après l'instruction WFI
void RTC_wakeup_init_from_stop( int delay )
{
RTC_wakeup_init( delay );
// valider l'interrupt par la ligne 20 du module EXTI, qui est réservée au wakeup timer
LL_EXTI_EnableIT_0_31( LL_EXTI_LINE_20 );
LL_EXTI_EnableRisingTrig_0_31( LL_EXTI_LINE_20 );
// valider l'interrupt chez NVIC
NVIC_SetPriority( RTC_WKUP_IRQn, 1 );
NVIC_EnableIRQ( RTC_WKUP_IRQn );
}

// wakeup timer interrupt Handler (inutile mais doit etre defini)
void RTC_WKUP_IRQHandler()
{
LL_EXTI_ClearFlag_0_31( LL_EXTI_LINE_20 );
}
