/* USER CODE BEGIN Header */
/**
 ******************************************************************************
 * @file           : main.c
 * @brief          : Main program body
 ******************************************************************************
 * @attention
 *
 * Copyright (c) 2022 STMicroelectronics.
 * All rights reserved.
 *
 * This software is licensed under terms that can be found in the LICENSE file
 * in the root directory of this software component.
 * If no LICENSE file comes with this software, it is provided AS-IS.
 *
 ******************************************************************************
 */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "adc.h"
#include "dma.h"
#include "i2c.h"
#include "usart.h"
#include "rng.h"
#include "sai.h"
#include "spi.h"
#include "tim.h"
#include "usb_otg.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "ILI9341_STM32_Driver.h"
#include "ILI9341_GFX.h"

#include "snow_tiger.h"
#include "image.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "ssd1306.h"
#include "ssd1306_tests.h"

#include "arm_math.h"

#include <math.h>
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define FFT_SAMPLES 2048
#define SPEED_OF_FALL 4

#define BAR_SCREEN_OFFSET 10
#define BAR_SCREEN_WIDTH 27
#define BAR_SCREEN_SPACING 3
#define DROPPING_LINE_THICKNESS 3

#define SAI_SAMPLES 2048

#define ANALOG_MIC 0
#define DIGITAL_PCM_MIC 1

#if ANALOG_MIC
	#define MIC_OFFSET 55 //ca. 30 for raw mic and ca. 50 for MAX9814 with gain set at 40dB and windowing
	#define SCALING_FACTOR 4.0f
#endif

#if DIGITAL_PCM_MIC
#define MIC_OFFSET 40
#define SCALING_FACTOR 2.0f
#endif

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */
uint16_t AdcInjected[FFT_SAMPLES];

uint32_t SaiPcmMicrophone[FFT_SAMPLES];

float32_t FFTInBuffer[FFT_SAMPLES];
float32_t FFTInBufferNoWindow[FFT_SAMPLES];
float32_t FFTOutBuffer[FFT_SAMPLES];
float32_t FFTWindow[FFT_SAMPLES];
float32_t SumOfAllSamples;
float32_t AvgOfAllSamples;

uint32_t PcmBuffer[SAI_SAMPLES];

arm_rfft_fast_instance_f32 FFTHandler;

volatile uint8_t AdcSamplesReady;
volatile uint8_t SaiSamplesReady;

float32_t OutFreqArray[11];

float32_t DroppingBarArray[11];

float32_t PreviousOutFreqArray[11];

float32_t PreviousDroppingBarArray[11];

const uint32_t BandDivisionPoints[11] =
{ 0, 1, 3, 7, 14, 29, 62, 132, 218, 594, (FFT_SAMPLES / 2 - 1) }; //see the attached .xlsx (log scale)

uint32_t SoftTimerOled;
uint16_t i, k;
/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
void PeriphCommonClock_Config(void);
/* USER CODE BEGIN PFP */
void CalculateFFT(void);
float32_t HanningWindow(uint16_t i, uint16_t nn);
float32_t ParzenWindow(uint16_t i, uint16_t nn);
void DrawSpectrumMainBars(float32_t *PreviousOutFreqArray,
		float32_t *OutFreqArray);
void DrawSpectrumDroppingBars(float32_t *PreviousDroppingBarArray,
		float32_t *DroppingBarArray, float32_t *OutFreqArray);
/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
 * @brief  The application entry point.
 * @retval int
 */
int main(void)
{
	/* USER CODE BEGIN 1 */

	/* USER CODE END 1 */

	/* MCU Configuration--------------------------------------------------------*/

	/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
	HAL_Init();

	/* USER CODE BEGIN Init */

	/* USER CODE END Init */

	/* Configure the system clock */
	SystemClock_Config();

	/* Configure the peripherals common clocks */
	PeriphCommonClock_Config();

	/* USER CODE BEGIN SysInit */

	/* USER CODE END SysInit */

	/* Initialize all configured peripherals */
	MX_GPIO_Init();
	MX_DMA_Init();
	MX_LPUART1_UART_Init();
	MX_USART3_UART_Init();
	MX_USB_OTG_FS_PCD_Init();
	MX_SPI1_Init();
	MX_TIM1_Init();
	MX_RNG_Init();
	MX_ADC1_Init();
	MX_I2C1_Init();
	MX_TIM2_Init();
	MX_SAI1_Init();
	/* USER CODE BEGIN 2 */
	//HAL_ADCEx_Calibration_Start(&hadc1, ADC_SINGLE_ENDED);
	ILI9341_Init();
	ILI9341_Set_Rotation(SCREEN_HORIZONTAL_2);
	ILI9341_Fill_Screen(BLACK);
	ILI9341_Draw_Logo((ILI9341_SCREEN_WIDTH - bmBULogo.xSize),
			(ILI9341_SCREEN_HEIGHT - bmBULogo.ySize) * 0, &bmBULogo);

	ssd1306_Init();

	ssd1306_Fill(Black);

	ssd1306_SetCursor(50, 0);
	ssd1306_WriteString("Audio", Font_6x8, White);
	ssd1306_SetCursor(15, 10);
	ssd1306_WriteString("frequency analyzer", Font_6x8, White);
	ssd1306_SetCursor(30, 20);
	ssd1306_WriteString("with windowing", Font_6x8, White);
	ssd1306_SetCursor(10, 35);
	ssd1306_WriteString("Inspired by:", Font_6x8, White);
	ssd1306_SetCursor(35, 45);
	ssd1306_WriteString("msalamon.pl", Font_6x8, White);
	ssd1306_SetCursor(35, 55);
	ssd1306_WriteString("kursstm32.pl", Font_6x8, White);
	ssd1306_UpdateScreen();

	HAL_TIM_Base_Start(&htim2);
	HAL_ADCEx_InjectedStart_IT(&hadc1);

	arm_rfft_fast_init_f32(&FFTHandler, FFT_SAMPLES);
	SoftTimerOled = HAL_GetTick();

	for (uint16_t i = 0; i < FFT_SAMPLES; i++) // windowing
	{
		FFTWindow[i] = HanningWindow(i, FFT_SAMPLES);
		//FFTWindow[i] = ParzenWindow(i, FFT_SAMPLES);
		//FFTWindow[i] = 1.0f;
	}
	/* USER CODE END 2 */

	/* Infinite loop */
	/* USER CODE BEGIN WHILE */

	//__HAL_SAI_ENABLE(&hsai_BlockA1);
	HAL_SAI_Receive_DMA(&hsai_BlockA1, (uint8_t*) PcmBuffer, SAI_SAMPLES);

	while (1)
	{

		if (((AdcSamplesReady == 1) && ANALOG_MIC)
				|| ((SaiSamplesReady == 1) && DIGITAL_PCM_MIC))
		{

			SumOfAllSamples = 0;

			if (ANALOG_MIC)
			{
				AdcSamplesReady = 0;
				for (i = 0; i < FFT_SAMPLES; i++)
				{
					FFTInBufferNoWindow[i] = ((float32_t) AdcInjected[i]);
					SumOfAllSamples += FFTInBufferNoWindow[i];
				}
			}
			else if (DIGITAL_PCM_MIC)
			{
				SaiSamplesReady = 0;
				for (i = 0; i < FFT_SAMPLES; i++)
				{
					FFTInBufferNoWindow[i] = ((float32_t) SaiPcmMicrophone[i]);
					SumOfAllSamples += FFTInBufferNoWindow[i];
				}

			}

			AvgOfAllSamples = SumOfAllSamples / (float32_t) FFT_SAMPLES;
			for (i = 0; i < FFT_SAMPLES; i++) // DC removal required for windowing
			{
				FFTInBufferNoWindow[i] -= AvgOfAllSamples;
			}

			arm_mult_f32(FFTInBufferNoWindow, FFTWindow, FFTInBuffer,
			FFT_SAMPLES);	// windowing

			CalculateFFT();

		}

		if ((HAL_GetTick() - SoftTimerOled) > 50)// ca. 40 kHz audio and ca. 2 k samples gives 20 Hz refresh rate --> the limit is then not on the screen side
		{
			SoftTimerOled = HAL_GetTick();

			for (uint8_t i = 0; i < 11; i++)
			{
				if (OutFreqArray[i] > ILI9341_SCREEN_HEIGHT - 1)
				{
					OutFreqArray[i] = ILI9341_SCREEN_HEIGHT - 1;
				}
			}

			DrawSpectrumMainBars(PreviousOutFreqArray, OutFreqArray);

			DrawSpectrumDroppingBars(PreviousDroppingBarArray, DroppingBarArray,
					OutFreqArray);

			for (uint8_t i = 1; i < 11; i++) // Drop bars
			{
				if (DroppingBarArray[i] > 0)
				{
					DroppingBarArray[i] = DroppingBarArray[i] - SPEED_OF_FALL;
				}
			}
		}

		/* USER CODE END WHILE */

		/* USER CODE BEGIN 3 */
	}
	/* USER CODE END 3 */
}

/**
 * @brief System Clock Configuration
 * @retval None
 */
void SystemClock_Config(void)
{
	RCC_OscInitTypeDef RCC_OscInitStruct =
	{ 0 };
	RCC_ClkInitTypeDef RCC_ClkInitStruct =
	{ 0 };

	/** Configure the main internal regulator output voltage
	 */
	if (HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1_BOOST)
			!= HAL_OK)
	{
		Error_Handler();
	}

	/** Initializes the RCC Oscillators according to the specified parameters
	 * in the RCC_OscInitTypeDef structure.
	 */
	RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI48
			| RCC_OSCILLATORTYPE_HSI;
	RCC_OscInitStruct.HSIState = RCC_HSI_ON;
	RCC_OscInitStruct.HSI48State = RCC_HSI48_ON;
	RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
	RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
	RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
	RCC_OscInitStruct.PLL.PLLM = 2;
	RCC_OscInitStruct.PLL.PLLN = 30;
	RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
	RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
	RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
	if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
	{
		Error_Handler();
	}

	/** Initializes the CPU, AHB and APB buses clocks
	 */
	RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK
			| RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
	RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
	RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
	RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
	RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

	if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
	{
		Error_Handler();
	}
}

/**
 * @brief Peripherals Common Clock Configuration
 * @retval None
 */
void PeriphCommonClock_Config(void)
{
	RCC_PeriphCLKInitTypeDef PeriphClkInit =
	{ 0 };

	/** Initializes the peripherals clock
	 */
	PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_SAI1 | RCC_PERIPHCLK_ADC;
	PeriphClkInit.Sai1ClockSelection = RCC_SAI1CLKSOURCE_PLLSAI1;
	PeriphClkInit.AdcClockSelection = RCC_ADCCLKSOURCE_PLLSAI1;
	PeriphClkInit.PLLSAI1.PLLSAI1Source = RCC_PLLSOURCE_HSI;
	PeriphClkInit.PLLSAI1.PLLSAI1M = 2;
	PeriphClkInit.PLLSAI1.PLLSAI1N = 12;
	PeriphClkInit.PLLSAI1.PLLSAI1P = RCC_PLLP_DIV2;
	PeriphClkInit.PLLSAI1.PLLSAI1Q = RCC_PLLQ_DIV2;
	PeriphClkInit.PLLSAI1.PLLSAI1R = RCC_PLLR_DIV2;
	PeriphClkInit.PLLSAI1.PLLSAI1ClockOut = RCC_PLLSAI1_SAI1CLK
			| RCC_PLLSAI1_ADC1CLK;
	if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
	{
		Error_Handler();
	}
}

/* USER CODE BEGIN 4 */

//// for circular DMA
//void HAL_SAI_RxCpltCallback(SAI_HandleTypeDef *hsai)
//{
//
//	static uint16_t i = 0;
//
//	if (hsai->Instance == SAI1_Block_A)
//	{
//		HAL_SAI_DMAPause(hsai);
//		for (i = 0; i < FFT_SAMPLES; i++)
//		{
//			SaiPcmMicrophone[i] = ((float32_t) (pcmBuffer[i] >> 14));
//		}
//		SaiSamplesReady = 1;
//		HAL_SAI_DMAResume(hsai);
//	}
//}

void HAL_SAI_RxCpltCallback(SAI_HandleTypeDef *hsai)
{

	static uint16_t i = 0;

	if (hsai->Instance == SAI1_Block_A)
	{
		for (i = 0; i < FFT_SAMPLES; i++)
		{
			SaiPcmMicrophone[i] = ((float32_t) (PcmBuffer[i] >> 14));
		}
		SaiSamplesReady = 1;
		HAL_SAI_Receive_DMA(&hsai_BlockA1, (uint8_t*) PcmBuffer, SAI_SAMPLES);
	}
}

void HAL_ADCEx_InjectedConvCpltCallback(ADC_HandleTypeDef *hadc)
{
	static uint16_t i = 0;

	if (hadc->Instance == ADC1)
	{
		AdcInjected[i] = HAL_ADCEx_InjectedGetValue(&hadc1,
		ADC_INJECTED_RANK_1);
		i++;

		if (i == FFT_SAMPLES)
		{
			i = 0;
			AdcSamplesReady = 1;
		}
	}
}

float32_t complexABS(float32_t real, float32_t compl)
{
	return sqrtf(real * real + compl * compl);
}

// https://github.com/kichiki/WaoN/blob/master/fft.c
float32_t HanningWindow(uint16_t i, uint16_t nn)
{
	return (0.5f
			* (1.0f
					- cosf(
							2.0f * 3.14159265f * ((float32_t) i)
									/ ((float32_t) (nn - 1)))));

}

// https://github.com/kichiki/WaoN/blob/master/fft.c
float32_t ParzenWindow(uint16_t i, uint16_t nn)
{
	return (1.0f
			- fabsf(
					((float32_t) i - 0.5 * (float32_t) (nn - 1))
							/ (0.5f * (float32_t) (nn + 1))));
}

void CalculateFFT(void)
{
	arm_rfft_fast_f32(&FFTHandler, FFTInBuffer, FFTOutBuffer, 0);

	float Freqs[FFT_SAMPLES];
	//uint32_t FreqPoint = 0;
	int32_t Offset = MIC_OFFSET; // variable noise floor offset

	// calculate abs values and linear-to-dB
	for (uint16_t i = 0; i < FFT_SAMPLES; i = i + 2)
	{
		//Freqs[FreqPoint] = (int)(20*log10f(complexABS(FFTOutBuffer[i], FFTOutBuffer[i+1]))) - Offset;
		Freqs[i >> 1] = complexABS(FFTOutBuffer[i], FFTOutBuffer[i + 1]);
	}

	OutFreqArray[0] = Freqs[0]; // dc

	for (uint8_t i = 1; i <= 10; i++) // prepare for accumulation
	{
		OutFreqArray[i] = 0;
	}

	for (uint8_t i = 1; i <= 10; i++) // lumping harmonics together into bands (loglog scale)
	{
		for (uint16_t j = BandDivisionPoints[i - 1] + 1;
				j <= BandDivisionPoints[i]; j++)
		{
			OutFreqArray[i] += Freqs[j];
		}
		OutFreqArray[i] = OutFreqArray[i]
				/ (BandDivisionPoints[i] - (BandDivisionPoints[i - 1] + 1) + 1);
		OutFreqArray[i] = ((20 * log10f(OutFreqArray[i])) - Offset)
				* SCALING_FACTOR;
		if (OutFreqArray[i] < 0)
		{
			OutFreqArray[i] = 0;
		}

	}
}

void DrawSpectrumMainBars(float32_t *PreviousOutFreqArray,
		float32_t *OutFreqArray)	// Draw or erase only the difference
{
	for (uint8_t i = 1; i < 11; i++) // Each frequency
	{
		//			ILI9341_Draw_Filled_Rectangle_Coord(BAR_SCREEN_OFFSET + ((i - 1) * (BAR_SCREEN_WIDTH + BAR_SCREEN_SPACING)), ILI9341_SCREEN_HEIGHT,
		//					BAR_SCREEN_OFFSET + ((i - 1) * (BAR_SCREEN_WIDTH + BAR_SCREEN_SPACING)) + BAR_SCREEN_WIDTH, ILI9341_SCREEN_HEIGHT - ((uint16_t)OutFreqArray[i]), WHITE);

		// Draw or erase only the difference
		if ((uint16_t) OutFreqArray[i]
				== (uint16_t) PreviousOutFreqArray[i] + 1)
		{
			ILI9341_Draw_Horizontal_Line(
					BAR_SCREEN_OFFSET
							+ ((i - 1) * (BAR_SCREEN_WIDTH + BAR_SCREEN_SPACING)),
					ILI9341_SCREEN_HEIGHT - ((uint16_t) OutFreqArray[i]),
					BAR_SCREEN_WIDTH, WHITE);
		}
		else if ((uint16_t) OutFreqArray[i]
				== (uint16_t) PreviousOutFreqArray[i] - 1)
		{
			ILI9341_Draw_Horizontal_Line(
					BAR_SCREEN_OFFSET
							+ ((i - 1) * (BAR_SCREEN_WIDTH + BAR_SCREEN_SPACING)),
					ILI9341_SCREEN_HEIGHT
							- ((uint16_t) PreviousOutFreqArray[i] + 1),
					BAR_SCREEN_WIDTH, BLACK);
		}
		else if ((uint16_t) OutFreqArray[i]
				> (uint16_t) PreviousOutFreqArray[i] + 1)
		{
			ILI9341_Draw_Filled_Rectangle_Coord(
					BAR_SCREEN_OFFSET
							+ ((i - 1) * (BAR_SCREEN_WIDTH + BAR_SCREEN_SPACING)),
					ILI9341_SCREEN_HEIGHT
							- ((uint16_t) PreviousOutFreqArray[i]),
					BAR_SCREEN_OFFSET
							+ ((i - 1) * (BAR_SCREEN_WIDTH + BAR_SCREEN_SPACING))
							+ BAR_SCREEN_WIDTH,
					ILI9341_SCREEN_HEIGHT - ((uint16_t) OutFreqArray[i] + 1),
					WHITE);
		}
		else if ((uint16_t) OutFreqArray[i]
				< (uint16_t) PreviousOutFreqArray[i] - 1)
		{
			ILI9341_Draw_Filled_Rectangle_Coord(
					BAR_SCREEN_OFFSET
							+ ((i - 1) * (BAR_SCREEN_WIDTH + BAR_SCREEN_SPACING)),
					ILI9341_SCREEN_HEIGHT - ((uint16_t) OutFreqArray[i]),
					BAR_SCREEN_OFFSET
							+ ((i - 1) * (BAR_SCREEN_WIDTH + BAR_SCREEN_SPACING))
							+ BAR_SCREEN_WIDTH,
					ILI9341_SCREEN_HEIGHT
							- ((uint16_t) PreviousOutFreqArray[i] + 1),
					BLACK);
		}
		else
		{
			;
		}

	}

	for (i = 0; i < 11; i++)
	{
		PreviousOutFreqArray[i] = OutFreqArray[i];
	}
}

void DrawSpectrumDroppingBars(float32_t *PreviousDroppingBarArray,
		float32_t *DroppingBarArray, float32_t *OutFreqArray)
{
	for (int i = 0; i <= 10; i++)
	{
		if (OutFreqArray[i] > DroppingBarArray[i])
		{
			DroppingBarArray[i] = OutFreqArray[i];
		}
	}

	for (uint8_t i = 1; i < 11; i++) // Draw dropping bars
	{

		if (PreviousDroppingBarArray[i] > DroppingBarArray[i])
		{
			if (DROPPING_LINE_THICKNESS == 1)
			{
				ILI9341_Draw_Horizontal_Line(
						BAR_SCREEN_OFFSET
								+ ((i - 1)
										* (BAR_SCREEN_WIDTH + BAR_SCREEN_SPACING)),
						ILI9341_SCREEN_HEIGHT
								- ((uint16_t) PreviousDroppingBarArray[i]),
						BAR_SCREEN_WIDTH, BLACK);
			}
			else
			{
				ILI9341_Draw_Filled_Rectangle_Coord(
						BAR_SCREEN_OFFSET
								+ ((i - 1)
										* (BAR_SCREEN_WIDTH + BAR_SCREEN_SPACING)),
						ILI9341_SCREEN_HEIGHT
								- ((uint16_t) PreviousDroppingBarArray[i]),
						BAR_SCREEN_OFFSET
								+ ((i - 1)
										* (BAR_SCREEN_WIDTH + BAR_SCREEN_SPACING))
								+ BAR_SCREEN_WIDTH,
						ILI9341_SCREEN_HEIGHT
								- (((uint16_t) PreviousDroppingBarArray[i])
										+ DROPPING_LINE_THICKNESS),
						BLACK);
			}
		}

		if (DROPPING_LINE_THICKNESS == 1)
		{
			ILI9341_Draw_Horizontal_Line(
					BAR_SCREEN_OFFSET
							+ ((i - 1) * (BAR_SCREEN_WIDTH + BAR_SCREEN_SPACING)),
					ILI9341_SCREEN_HEIGHT - ((uint16_t) DroppingBarArray[i]),
					BAR_SCREEN_WIDTH, WHITE);
		}
		else
		{
			ILI9341_Draw_Filled_Rectangle_Coord(
					BAR_SCREEN_OFFSET
							+ ((i - 1) * (BAR_SCREEN_WIDTH + BAR_SCREEN_SPACING)),
					ILI9341_SCREEN_HEIGHT - ((uint16_t) DroppingBarArray[i]),
					BAR_SCREEN_OFFSET
							+ ((i - 1) * (BAR_SCREEN_WIDTH + BAR_SCREEN_SPACING))
							+ BAR_SCREEN_WIDTH,
					ILI9341_SCREEN_HEIGHT
							- (((uint16_t) DroppingBarArray[i])
									+ DROPPING_LINE_THICKNESS),
					WHITE);
		}
	}

	for (i = 0; i < 11; i++)
	{
		PreviousDroppingBarArray[i] = DroppingBarArray[i];
	}

}

/* USER CODE END 4 */

/**
 * @brief  This function is executed in case of error occurrence.
 * @retval None
 */
void Error_Handler(void)
{
	/* USER CODE BEGIN Error_Handler_Debug */
	/* User can add his own implementation to report the HAL error return state */
	__disable_irq();
	while (1)
	{
	}
	/* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
