/**
  ******************************************************************************
  * File Name          : main.c
  * Description        : Main program body
  ******************************************************************************
  *
  * Copyright (c) 2019 STMicroelectronics International N.V. 
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without 
  * modification, are permitted, provided that the following conditions are met:
  *
  * 1. Redistribution of source code must retain the above copyright notice, 
  *    this list of conditions and the following disclaimer.
  * 2. Redistributions in binary form must reproduce the above copyright notice,
  *    this list of conditions and the following disclaimer in the documentation
  *    and/or other materials provided with the distribution.
  * 3. Neither the name of STMicroelectronics nor the names of other 
  *    contributors to this software may be used to endorse or promote products 
  *    derived from this software without specific written permission.
  * 4. This software, including modifications and/or derivative works of this 
  *    software, must execute solely and exclusively on microcontroller or
  *    microprocessor devices manufactured by or for STMicroelectronics.
  * 5. Redistribution and use of this software other than as permitted under 
  *    this license is void and will automatically terminate your rights under 
  *    this license. 
  *
  * THIS SOFTWARE IS PROVIDED BY STMICROELECTRONICS AND CONTRIBUTORS "AS IS" 
  * AND ANY EXPRESS, IMPLIED OR STATUTORY WARRANTIES, INCLUDING, BUT NOT 
  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 
  * PARTICULAR PURPOSE AND NON-INFRINGEMENT OF THIRD PARTY INTELLECTUAL PROPERTY
  * RIGHTS ARE DISCLAIMED TO THE FULLEST EXTENT PERMITTED BY LAW. IN NO EVENT 
  * SHALL STMICROELECTRONICS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 
  * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
  * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *
  ******************************************************************************
  */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "stm32f4xx_hal.h"
#include "usb_host.h"

/* USER CODE BEGIN Includes */
#include <stdio.h>						// printf()
#include <sys/time.h>					// time()
#include <errno.h>						//
#include <string.h>						//
#include "bt_a2dp_sink.h"				// BT_A2DP_SINK_BUFFER_NUM
#include "lcd_draw.h"					// LCM
#include "lcd_gui.h"					// LCM
#include "bt.h"							// Bluetooth
/* USER CODE END Includes */

/* Private variables ---------------------------------------------------------*/
ADC_HandleTypeDef hadc1;
DMA_HandleTypeDef hdma_adc1;

TIM_HandleTypeDef htim1;
TIM_HandleTypeDef htim2;
TIM_HandleTypeDef htim3;
TIM_HandleTypeDef htim7;
DMA_HandleTypeDef hdma_tim1_up;

UART_HandleTypeDef huart2;

/* USER CODE BEGIN PV */
//=============================================================================
//
//	Audio amplifier mini 0.2
//		Created by Toyohiko Togashi 2019
//
//	2019.02.07 1st fix version
//	2019.02.24 Enable PWM3B
//
//=============================================================================

/* Configuration ---------------------------------------------------------*/
#define SOUND_FRAME_SIZE	1024
#define ADC_CHANNEL_L1		2		// PC0 Analog port channel
#define ADC_CHANNEL_R1		3		// PC1
#define ADC_CHANNEL_L2		0		// PA0
#define ADC_CHANNEL_R2		1		// PA1
#define ADC_CHANNEL_MAX		4		//
#define MIXER_CHANNEL_AUX1	0		// Logical numbers
#define MIXER_CHANNEL_AUX2	1		//
#define MIXER_CHANNEL_BT	2		//
#define MIXER_CHANNEL_MAX	3		//

// Speaker drive <PWM mode>         // Circuit      Control   STM32CubeMX Configuration TIM1 CH Polarity
//#define PWM2						// Half-bridge  2-value   CH1=High CH2=N/A  CH3=High CH4=N/A
//#define PWM2_FULL					// Full-bridge  2-value   CH1=High CH2=Low  CH3=High CH4=Low
//#define PWM3A						// Full-bridge  3-value   CH1=Low  CH2=Low  CH3=Low  CH4=Low
  #define PWM3B						// Full-bridge  3-value   CH1=Low  CH2=Low  CH3=Low  CH4=Low

#define LEVEL_METER_RATE		50		// [ms]
#define LEVEL_METER_SAMPLE_TIME	16		// In the sound frame
#define PEAK_METER_RATE			200		// [ms]
#define PEAK_METER_STEP			0.002	// 0.2%
#define B1_BUTTON_TIME_OUT		10000	// [ms]
#define B1_BUTTON_SCAN_RATE		10		// [ms]
#define VOLUME_MAX				2.0		// 200%
#define VOLUME_STEP				0.03	// 3%
#define BALANCE_STEP			0.05	// 5%
//#define SPK_NO_SIGNAL_LEVEL	202		// [PWM=0..4081/2] 5%
#define NO_SIGNAL_LEVEL			1638	// [PCM16=0..32767/2] 5%
#define STAND_BY_DELAY			180		// [sec]
#define AUX1_NO_SIGNAL_LEVEL	102		// [ADC12=0..4096/2] 5%
#define AUX2_NO_SIGNAL_LEVEL	102		// [ADC12=0..4096/2] 5%
#define AUX1_CUT_OFF_DELAY		300		// [sec]
#define AUX2_CUT_OFF_DELAY		300		// [sec]

/* Private variables ---------------------------------------------------------*/
unsigned short	adcBuff[2][SOUND_FRAME_SIZE][ADC_CHANNEL_MAX];	// Analog input
int				adcBuffFlag;
unsigned short	pwmBuff[2][SOUND_FRAME_SIZE][4];				// Speaker out [L+, L-, R+, R-]
int				pwmBuffFlag;
int				speakerGain = 13;		// 128=100%
int				standByDelay;			// [sec]

struct audioChannel {
	float	volume;				//  1.0 == 100%
	float	balance;			// -1.0:(L=200%,R=0%) 0.0:(L=100%,R=100%) 1.0:(L=0%,R=200%)
	int		leftGain;			// Calculated from the volume/balance, 128=100%
	int		rightGain;			// Calculated from the volume/balance, 128=100%
};
struct audioChannel	mixerSetting[MIXER_CHANNEL_MAX] = {
		{1.0, 0.0, 128, 128},	// AUX1 Volume=100% Balance=Center
		{1.0, 0.0, 128, 128},	// AUX2 Volume=100% Balance=Center
		{1.0, 0.0, 128, 128},	// BT   Volume=100% Balance=Center
};
void recalcVolume(int ch){
	int	i,f,t;

	if (ch < 0) {
		f = 0;
		t = MIXER_CHANNEL_MAX;
	} else {
		f = ch;
		t = ch + 1;
	}
	for(i = f; i < t; i++){
		mixerSetting[i].leftGain  = mixerSetting[i].volume * (1.0 - mixerSetting[i].balance) * 128;
		mixerSetting[i].rightGain = mixerSetting[i].volume * (1.0 + mixerSetting[i].balance) * 128;
	}
	return;
}
/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
void Error_Handler(void);
static void MX_GPIO_Init(void);
static void MX_DMA_Init(void);
static void MX_USART2_UART_Init(void);
static void MX_ADC1_Init(void);
static void MX_TIM1_Init(void);
static void MX_TIM2_Init(void);
static void MX_TIM3_Init(void);
static void MX_TIM7_Init(void);
static void MX_NVIC_Init(void);
void MX_USB_HOST_Process(void);

void HAL_TIM_MspPostInit(TIM_HandleTypeDef *htim);
                                

/* USER CODE BEGIN PFP */
/* Private function prototypes -----------------------------------------------*/
#define MAX(a,b)	((a)>(b) ? (a) : (b))
#define MIN(a,b)	((a)<(b) ? (a) : (b))
#define ABS(a)		((a)<0 ?   (-a) : (a))
/* USER CODE END PFP */

/* USER CODE BEGIN 0 */
//=============================================================================
//
//	Standard subroutines
//
//=============================================================================
#ifndef DEL
//
//	DEBUG message to host machine(/dev/cu.usbmodem*)
//
int __io_putchar(int ch) {
	HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1, 0xFFFF);
	return ch;
}
int _write(int file, char *ptr, int len){
	int DataIdx;

	for (DataIdx = 0; DataIdx < len; DataIdx++){
		__io_putchar(*ptr++);
	}
	return len;
}
#endif

//
//	time() implement / TIM7 version
//
volatile time_t		rtc_time;
// Setup CubeMX TIM7: Prescaler=8999,Period=10000 and NVIC enable
// Append a source code to stm32f4xx_it.c: TIM7_IRQHandler()
//		/* USER CODE BEGIN TIM7_IRQn 0 */
//		#include <time.h>
//		extern volatile time_t	rtc_time;
//			rtc_time++;
//		/* USER CODE END TIM7_IRQn 0 */
time_t time(time_t *t) {
	if (t != NULL) {
		*t = rtc_time;
	}
	return(rtc_time);
}
int settimeofday(const struct timeval *tv, const struct timezone *tz){
	int	r;

	if (tv != NULL) {
		rtc_time = tv->tv_sec;
		__HAL_TIM_SET_COUNTER(&htim7, tv->tv_usec / 100);
		r = 0;
	} else {
		r = -1;
		errno = EFAULT;
	}
	return(r);
}
int gettimeofday(struct timeval *tp, void *tzp){
	struct timezone	*tz;

	tz = tzp;
	if (tp != NULL) {
		tp->tv_sec  = rtc_time;
		tp->tv_usec = __HAL_TIM_GET_COUNTER(&htim7) * 100;
	}
	if (tz != NULL) {
		tz->tz_minuteswest = 0;
		tz->tz_dsttime     = 0;
	}
	return(0);
}

//=============================================================================
//
//	Core tasks
//
//=============================================================================

//
//	ADC automation task (Very high priority, light weight process)
//
void adcToPwm(void) {
	static unsigned short	zeroL1;		// Center level (ADC value)
	static unsigned short	zeroR1;
	static unsigned short	zeroL2;
	static unsigned short	zeroR2;
	static int				zeroTim;	// Re examination timing
	static int				k;
	int	i;
	int j;
	int	l, r;							// PCM(Signed 16bit)
	int	l1, r1;
	int	l2, r2;
	int	lb, rb;
	int x, y;


	// Initial values
	if (k == 0) {			// Maximum number
		k = htim1.Init.Period / 2 + 1;
	}
	if (zeroL1 == 0) {		// ADC Zero center level
		// Get a zero center level
		long	sumL1, sumR1;
		long	sumL2, sumR2;

		// Center level is average
		sumL1   = 0;
		sumR1   = 0;
		sumL2   = 0;
		sumR2   = 0;
		for(i = 0; i < SOUND_FRAME_SIZE; i++) {
			sumL1   += adcBuff[adcBuffFlag][i][ADC_CHANNEL_L1];
			sumR1   += adcBuff[adcBuffFlag][i][ADC_CHANNEL_R1];
			sumL2   += adcBuff[adcBuffFlag][i][ADC_CHANNEL_L2];
			sumR2   += adcBuff[adcBuffFlag][i][ADC_CHANNEL_R2];
	    }
		zeroL1   = sumL1   / SOUND_FRAME_SIZE;
		zeroR1   = sumR1   / SOUND_FRAME_SIZE;
		zeroL2   = sumL2   / SOUND_FRAME_SIZE;
		zeroR2   = sumR2   / SOUND_FRAME_SIZE;
//		printf("Ave=%d,%d %d,%d\n", zeroL1, zeroR1, zeroL2, zeroR2);
	}

	// Find a Bluetooth buffer
	j = -1;
	if (bt_a2dp_endPoint[0].pcmReadPos < bt_a2dp_endPoint[0].pcmWritePos) {
		if ((bt_a2dp_endPoint[0].pcmWritePos - bt_a2dp_endPoint[0].pcmReadPos) >= (BT_A2DP_SINK_BUFFER_NUM-1)) {
			bt_a2dp_endPoint[0].pcmReadPos = bt_a2dp_endPoint[0].pcmWritePos - (BT_A2DP_SINK_BUFFER_NUM-1);
		}
		j = bt_a2dp_endPoint[0].pcmReadPos % BT_A2DP_SINK_BUFFER_NUM;
		(bt_a2dp_endPoint[0].pcmReadPos)++;
	}

	// Convert and mixing
    for(i = 0; i < SOUND_FRAME_SIZE; i++) {
		// Input: AUX1 ADC raw data	(12bit -> 16bit)
		l1 = ((adcBuff[adcBuffFlag][i][ADC_CHANNEL_L1] - zeroL1) * mixerSetting[MIXER_CHANNEL_AUX1].leftGain ) >> 3;
		r1 = ((adcBuff[adcBuffFlag][i][ADC_CHANNEL_R1] - zeroR1) * mixerSetting[MIXER_CHANNEL_AUX1].rightGain) >> 3;

		// Input: AUX2 ADC raw data	(12bit -> 16bit)
		l2 = ((adcBuff[adcBuffFlag][i][ADC_CHANNEL_L2] - zeroL2) * mixerSetting[MIXER_CHANNEL_AUX2].leftGain ) >> 3;
		r2 = ((adcBuff[adcBuffFlag][i][ADC_CHANNEL_R2] - zeroR2) * mixerSetting[MIXER_CHANNEL_AUX2].rightGain) >> 3;

		// Input: Bluetooth PCM data
		if ((j >= 0) && (i < bt_a2dp_endPoint[0].pcmLen[j])) {
			lb = (bt_a2dp_endPoint[0].pcmBuffer[j][i][0] * mixerSetting[MIXER_CHANNEL_BT].leftGain ) >> 7;
			rb = (bt_a2dp_endPoint[0].pcmBuffer[j][i][1] * mixerSetting[MIXER_CHANNEL_BT].rightGain) >> 7;
		} else {
			lb = 0;
			rb = 0;
		}

		// Mixing
		l = (l1 + l2 + lb) * speakerGain >> 7;
		r = (r1 + r2 + rb) * speakerGain >> 7;
		l = MAX(-32768, MIN(32767, l));		// Peak cut on PCM16
		r = MAX(-32768, MIN(32767, r));		//

#if defined(PWM2)
		// Output: Speaker PWM2
		x = ((l * k) >> 15) + k;
		y = ((r * k) >> 15) + k;
		pwmBuff[pwmBuffFlag][i][0] = x;
		pwmBuff[pwmBuffFlag][i][1] = 0;
		pwmBuff[pwmBuffFlag][i][2] = y;
		pwmBuff[pwmBuffFlag][i][3] = 0;
#elif defined(PWM2_FULL)
		// Output: Speaker PWM2: full bridge and normal drive
		x = ((l * k) >> 15) + k;
		y = ((r * k) >> 15) + k;
		pwmBuff[pwmBuffFlag][i][0] = x;
		pwmBuff[pwmBuffFlag][i][1] = x;
		pwmBuff[pwmBuffFlag][i][2] = y;
		pwmBuff[pwmBuffFlag][i][3] = y;
#elif defined(PWM3A)
		// Output: Speaker PWM3: full bridge and 3 phase drive
		x = (l * k) >> 14;
		y = (r * k) >> 14;		// k is period/2
		if (x > 0) {
			pwmBuff[pwmBuffFlag][i][0] = x;
			pwmBuff[pwmBuffFlag][i][1] = 0;
		} else {
			pwmBuff[pwmBuffFlag][i][0] = 0;
			pwmBuff[pwmBuffFlag][i][1] = -x;
		}
		if (y > 0) {
			pwmBuff[pwmBuffFlag][i][2] = y;
			pwmBuff[pwmBuffFlag][i][3] = 0;
		} else {
			pwmBuff[pwmBuffFlag][i][2] = 0;
			pwmBuff[pwmBuffFlag][i][3] = -y;
		}
#elif defined(PWM3B)
		// Output: Speaker PWM3: full bridge and 3 phase drive
		x = (l * k) >> 15;
		y = (r * k) >> 15;
		pwmBuff[pwmBuffFlag][i][0] = k + x;
		pwmBuff[pwmBuffFlag][i][1] = k - x;
		pwmBuff[pwmBuffFlag][i][2] = k + y;
		pwmBuff[pwmBuffFlag][i][3] = k - y;
#else
#error "Not defined PWM mode"
#endif
	}

    // Checking of quiet
#ifdef SPK_NO_SIGNAL_LEVEL
	if ((l > SPK_NO_SIGNAL_LEVEL) || (r > SPK_NO_SIGNAL_LEVEL)) {
		standByDelay = 0;
	}
#endif
#ifdef NO_SIGNAL_LEVEL
	if ((l1 > NO_SIGNAL_LEVEL) || (r1 > NO_SIGNAL_LEVEL) ||
		(l2 > NO_SIGNAL_LEVEL) || (r2 > NO_SIGNAL_LEVEL) ||
		(lb > NO_SIGNAL_LEVEL) || (rb > NO_SIGNAL_LEVEL)) {
		standByDelay = 0;
	}
#endif

    // Re examination request
	if (--zeroTim <= 0) {
		zeroTim = 1000;		// About 23sec
		zeroL1  = 0;
	}

	return;
}

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *handle) {
	adcBuffFlag = 1;
	adcToPwm();
	return;
}
void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef *handle) {
	adcBuffFlag = 0;
	adcToPwm();
	return;
}

//
//	PWM automation task
//
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){
	if (htim == &htim1) {
		pwmBuffFlag = 1;
	}
	return;
}
void HAL_TIM_PeriodHalfElapsedCallback(TIM_HandleTypeDef *htim){
	if (htim == &htim1) {
		pwmBuffFlag = 0;
	}
	return;
}
void TIM_DMAPeriodElapsedCplt2(DMA_HandleTypeDef *hdma) {
  TIM_HandleTypeDef *htim = (TIM_HandleTypeDef *)((DMA_HandleTypeDef *)hdma)->Parent;
  htim->State= HAL_TIM_STATE_READY;
  HAL_TIM_PeriodElapsedCallback(htim);
}
void TIM_DMAPeriodHalfElapsedCplt2(DMA_HandleTypeDef *hdma) {
  TIM_HandleTypeDef *htim = (TIM_HandleTypeDef *)((DMA_HandleTypeDef *)hdma)->Parent;
  htim->State= HAL_TIM_STATE_READY;
  HAL_TIM_PeriodHalfElapsedCallback(htim);
}
void TIM_DMATriggerCplt2(DMA_HandleTypeDef *hdma) {
  TIM_HandleTypeDef *htim = (TIM_HandleTypeDef *)((DMA_HandleTypeDef *)hdma)->Parent;
  htim->State= HAL_TIM_STATE_READY;
  HAL_TIM_TriggerCallback(htim);
}
// Custom HAL_TIM_DMABurst_WriteStart()
HAL_StatusTypeDef HAL_TIM_DMABurst_WriteStart2(TIM_HandleTypeDef *htim, uint32_t BurstBaseAddress, uint32_t BurstRequestSrc, uint32_t* BurstBuffer, uint32_t  BurstLength, uint32_t  BufferLength) {
	if((htim->State == HAL_TIM_STATE_BUSY)) {
		return HAL_BUSY;
	} else if((htim->State == HAL_TIM_STATE_READY)) {
		if((BurstBuffer == 0U) && (BurstLength > 0U)) {
			return HAL_ERROR;
		} else {
			htim->State = HAL_TIM_STATE_BUSY;
		}
	}
	switch(BurstRequestSrc) {
	case TIM_DMA_UPDATE:
		htim->hdma[TIM_DMA_ID_UPDATE]->XferCpltCallback     = TIM_DMAPeriodElapsedCplt2;
		htim->hdma[TIM_DMA_ID_UPDATE]->XferHalfCpltCallback = TIM_DMAPeriodHalfElapsedCplt2;
		htim->hdma[TIM_DMA_ID_UPDATE]->XferErrorCallback    = TIM_DMAError;
		HAL_DMA_Start_IT(htim->hdma[TIM_DMA_ID_UPDATE], (uint32_t)BurstBuffer, (uint32_t)&htim->Instance->DMAR, BufferLength);
		break;
	case TIM_DMA_CC1:
		htim->hdma[TIM_DMA_ID_CC1]->XferCpltCallback     = TIM_DMADelayPulseCplt;
		htim->hdma[TIM_DMA_ID_CC1]->XferHalfCpltCallback = TIM_DMAPeriodHalfElapsedCplt2;
		htim->hdma[TIM_DMA_ID_CC1]->XferErrorCallback    = TIM_DMAError;
		HAL_DMA_Start_IT(htim->hdma[TIM_DMA_ID_CC1], (uint32_t)BurstBuffer, (uint32_t)&htim->Instance->DMAR, BufferLength);
		break;
	case TIM_DMA_CC2:
		htim->hdma[TIM_DMA_ID_CC2]->XferCpltCallback     = TIM_DMADelayPulseCplt;
		htim->hdma[TIM_DMA_ID_CC2]->XferHalfCpltCallback = TIM_DMAPeriodHalfElapsedCplt2;
		htim->hdma[TIM_DMA_ID_CC2]->XferErrorCallback    = TIM_DMAError;
		HAL_DMA_Start_IT(htim->hdma[TIM_DMA_ID_CC2], (uint32_t)BurstBuffer, (uint32_t)&htim->Instance->DMAR, BufferLength);
		break;
	case TIM_DMA_CC3:
		htim->hdma[TIM_DMA_ID_CC3]->XferCpltCallback     = TIM_DMADelayPulseCplt;
		htim->hdma[TIM_DMA_ID_CC3]->XferHalfCpltCallback = TIM_DMAPeriodHalfElapsedCplt2;
		htim->hdma[TIM_DMA_ID_CC3]->XferErrorCallback    = TIM_DMAError;
		HAL_DMA_Start_IT(htim->hdma[TIM_DMA_ID_CC3], (uint32_t)BurstBuffer, (uint32_t)&htim->Instance->DMAR, BufferLength);
		break;
	case TIM_DMA_CC4:
		htim->hdma[TIM_DMA_ID_CC4]->XferCpltCallback     = TIM_DMADelayPulseCplt;
		htim->hdma[TIM_DMA_ID_CC4]->XferHalfCpltCallback = TIM_DMAPeriodHalfElapsedCplt2;
		htim->hdma[TIM_DMA_ID_CC4]->XferErrorCallback    = TIM_DMAError;
		HAL_DMA_Start_IT(htim->hdma[TIM_DMA_ID_CC4], (uint32_t)BurstBuffer, (uint32_t)&htim->Instance->DMAR, BufferLength);
		break;
	case TIM_DMA_COM:
		htim->hdma[TIM_DMA_ID_COMMUTATION]->XferCpltCallback     = TIMEx_DMACommutationCplt;
		htim->hdma[TIM_DMA_ID_COMMUTATION]->XferHalfCpltCallback = TIM_DMAPeriodHalfElapsedCplt2;
		htim->hdma[TIM_DMA_ID_COMMUTATION]->XferErrorCallback    = TIM_DMAError;
		HAL_DMA_Start_IT(htim->hdma[TIM_DMA_ID_COMMUTATION], (uint32_t)BurstBuffer, (uint32_t)&htim->Instance->DMAR, BufferLength);
		break;
	case TIM_DMA_TRIGGER:
		htim->hdma[TIM_DMA_ID_TRIGGER]->XferCpltCallback     = TIM_DMATriggerCplt2;
		htim->hdma[TIM_DMA_ID_TRIGGER]->XferHalfCpltCallback = TIM_DMAPeriodHalfElapsedCplt2;
		htim->hdma[TIM_DMA_ID_TRIGGER]->XferErrorCallback    = TIM_DMAError;
		HAL_DMA_Start_IT(htim->hdma[TIM_DMA_ID_TRIGGER], (uint32_t)BurstBuffer, (uint32_t)&htim->Instance->DMAR, BufferLength);
		break;
	default:
		break;
	}
	htim->Instance->DCR = BurstBaseAddress | BurstLength;
	__HAL_TIM_ENABLE_DMA(htim, BurstRequestSrc);
	htim->State = HAL_TIM_STATE_READY;
	return HAL_OK;
};
void HardFault_Handler(void){
	printf("HardFault_Handler\n");
	return;
}
void MemManage_Handler(void){
	printf("MemManage_Handler\n");
	return;
}
void BusFault_Handler(void){
	printf("BusFault_Handler\n");
	return;
}
void UsageFault_Handler(void){
	printf("UsageFault_Handler\n");
	return;
}
void WWDG_IRQHandler(void){
	printf("WWDG_IRQHandler\n");
	return;
}

//=============================================================================
//
//	Man machine interface
//
//=============================================================================
// Screen layout
// +----------------+------+
// |                |      |
// |     MIXER      |RENDER|
// |                |      |
// +----------------+------+
//
// Display elements
//
struct {
	struct lcd_guiRoot 		*root;
	struct {
		struct lcd_guiGroup	*group;
		struct lcd_guiText	*label;
		struct lcd_guiGage	*outputLevel;
		struct lcd_guiText	*label2;
		char				labelName[8];
		char				labelName2[8];
	}	render;

	struct {
		struct lcd_guiGroup	*group;
		struct {
			struct lcd_guiGroup		*group;
			struct lcd_guiText		*label;
			char					labelName[8];
			struct lcd_guiGage		*levelLeft;
			struct lcd_guiGage		*levelRight;
			struct lcd_guiBarChart	*peakLeft;
			unsigned short			peakLeftColor[2];
			float					peakLeftValue[2];
			struct lcd_guiBarChart	*peakRight;
			unsigned short			peakRightColor[2];
			float					peakRightValue[2];
			struct lcd_guiBarChart	*volume;
			unsigned short			volumeColor[2];
			float					volumeValue[2];
			struct lcd_guiBarChart	*balance;
			unsigned short			balanceColor[2];
			float					balanceValue[2];
		}	ch[MIXER_CHANNEL_MAX];
	}	mixer;

}	display;

void displayInit(void) {
	int	i;

	lcd_drawOpen();
	display.root = lcd_guiNewRoot(LCD_COLOR_BLACK, NULL, NULL);
	display.render.group        = lcd_guiNewGroup(display.root, 135, 0, 25, 128, LCD_COLOR_BLUE, NULL, NULL);
	display.render.label        = lcd_guiNewText(display.render.group,  1,   1, 23,  8, LCD_COLOR_WHITE, LCD_COLOR_DARKBLUE, LCD_GUI_TEXT_ALIGN_CENTER, LCD_GUI_TEXT_ALIGN_CENTER, display.render.labelName, LCD_FONT_6x8);
	display.render.outputLevel  = lcd_guiNewGage(display.render.group,  1,  10, 23,108, LCD_COLOR_CYAN,  LCD_COLOR_BLACK,    1, 0.0);
	display.render.label2       = lcd_guiNewText(display.render.group,  1, 119, 23,  8, LCD_COLOR_WHITE, LCD_COLOR_DARKBLUE, LCD_GUI_TEXT_ALIGN_CENTER, LCD_GUI_TEXT_ALIGN_CENTER, display.render.labelName2, LCD_FONT_6x8);
	strcpy(display.render.labelName, "Vol");
	strcpy(display.render.labelName2, "--%");

	display.mixer.group = lcd_guiNewGroup(display.root, 0, 0, 135, 128, LCD_COLOR_BLUE, NULL, NULL);
	display.mixer.ch[MIXER_CHANNEL_AUX1].group = lcd_guiNewGroup(display.mixer.group,   0,  0, 45, 128, LCD_COLOR_BLUE, NULL, NULL);
	display.mixer.ch[MIXER_CHANNEL_AUX2].group = lcd_guiNewGroup(display.mixer.group,  45,  0, 45, 128, LCD_COLOR_BLUE, NULL, NULL);
	display.mixer.ch[MIXER_CHANNEL_BT  ].group = lcd_guiNewGroup(display.mixer.group,  90,  0, 45, 128, LCD_COLOR_BLUE, NULL, NULL);
	strcpy(display.mixer.ch[MIXER_CHANNEL_AUX1].labelName, "A");
	strcpy(display.mixer.ch[MIXER_CHANNEL_AUX2].labelName, "B");
	strcpy(display.mixer.ch[MIXER_CHANNEL_BT  ].labelName, "Bt");
	for(i = 0; i < MIXER_CHANNEL_MAX; i++) {
		display.mixer.ch[i].label           = lcd_guiNewText(    display.mixer.ch[i].group,  1,   1, 45, 8, LCD_COLOR_WHITE, LCD_COLOR_DARKBLUE, LCD_GUI_TEXT_ALIGN_CENTER, LCD_GUI_TEXT_ALIGN_CENTER, display.mixer.ch[i].labelName, LCD_FONT_6x8);
		display.mixer.ch[i].levelLeft       = lcd_guiNewGage(    display.mixer.ch[i].group,  2, 10, 16,108, LCD_COLOR_LIME, LCD_COLOR_BLACK, 1, 0.0);
		display.mixer.ch[i].levelRight      = lcd_guiNewGage(    display.mixer.ch[i].group, 18, 10, 16,108, LCD_COLOR_LIME, LCD_COLOR_BLACK, 1, 0.0);
		display.mixer.ch[i].peakLeft        = lcd_guiNewBarChart(display.mixer.ch[i].group,  5, 10, 10,108, LCD_COLOR_BLACK, 1, 2, display.mixer.ch[i].peakLeftValue, display.mixer.ch[i].peakLeftColor);
		display.mixer.ch[i].peakRight       = lcd_guiNewBarChart(display.mixer.ch[i].group, 21, 10, 10,108, LCD_COLOR_BLACK, 1, 2, display.mixer.ch[i].peakRightValue, display.mixer.ch[i].peakRightColor);
		display.mixer.ch[i].volume          = lcd_guiNewBarChart(display.mixer.ch[i].group, 34, 10, 10,108, LCD_COLOR_DIMGRAY, 1, 2, display.mixer.ch[i].volumeValue, display.mixer.ch[i].volumeColor);
		display.mixer.ch[i].balance         = lcd_guiNewBarChart(display.mixer.ch[i].group,  2,119, 32,  8, LCD_COLOR_DIMGRAY, 0, 2, display.mixer.ch[i].balanceValue, display.mixer.ch[i].balanceColor);

		display.mixer.ch[i].peakLeftColor[0]  = LCD_COLOR_TRANSPARENT;
		display.mixer.ch[i].peakLeftColor[1]  = LCD_COLOR_YELLOW;
		display.mixer.ch[i].peakLeftValue[0]  = 0.0;
		display.mixer.ch[i].peakLeftValue[1]  = 0.01;
		display.mixer.ch[i].peakRightColor[0] = LCD_COLOR_TRANSPARENT;
		display.mixer.ch[i].peakRightColor[1] = LCD_COLOR_YELLOW;
		display.mixer.ch[i].peakRightValue[0] = 0.0;
		display.mixer.ch[i].peakRightValue[1] = 0.01;
		display.mixer.ch[i].volumeColor[0]    = LCD_COLOR_DIMGRAY;
		display.mixer.ch[i].volumeColor[1]    = LCD_COLOR_YELLOW;
		display.mixer.ch[i].balanceColor[0]   = LCD_COLOR_DIMGRAY;
		display.mixer.ch[i].balanceColor[1]   = LCD_COLOR_YELLOW;
		display.mixer.ch[i].volumeValue[0]    = 0.48;
		display.mixer.ch[i].volumeValue[1]    = 0.04;
		display.mixer.ch[i].balanceValue[0]   = 0.45;
		display.mixer.ch[i].balanceValue[1]   = 0.10;
	}
	return;
}

void displayUpdate(void){
	{	// Level meter
		static uint32_t	t1;
		static uint32_t	t2;
		static uint32_t	t3;
		uint32_t 		t;
		int				i;
		int				j;
		int				maxL, maxR, sumL, sumR;

		t = HAL_GetTick();
		if (t > t1) {
			t1 = t + LEVEL_METER_RATE;
			t2 = t + LEVEL_METER_RATE / 4;
			t3 = t + LEVEL_METER_RATE / 2;
			maxL = maxR = sumL = sumR = 0;
			for(j = 0; j < SOUND_FRAME_SIZE; j += (SOUND_FRAME_SIZE/LEVEL_METER_SAMPLE_TIME)) {
				int	l,r;

				l = adcBuff[0][j][ADC_CHANNEL_L1];
				r = adcBuff[0][j][ADC_CHANNEL_R1];
				maxL = MAX(maxL, l);
				maxR = MAX(maxR, r);
				sumL += l;
				sumR += r;
			}
			display.mixer.ch[MIXER_CHANNEL_AUX1].levelLeft->value  = (maxL - (sumL / LEVEL_METER_SAMPLE_TIME)) * mixerSetting[MIXER_CHANNEL_AUX1].leftGain  / (float)(128 * 4096 / 2);
			display.mixer.ch[MIXER_CHANNEL_AUX1].levelRight->value = (maxR - (sumR / LEVEL_METER_SAMPLE_TIME)) * mixerSetting[MIXER_CHANNEL_AUX1].rightGain / (float)(128 * 4096 / 2);
			lcd_guiSetDirtys(display.mixer.ch[MIXER_CHANNEL_AUX1].levelLeft);
			lcd_guiSetDirtys(display.mixer.ch[MIXER_CHANNEL_AUX1].levelRight);
			display.mixer.ch[MIXER_CHANNEL_AUX1].peakLeftValue[0]  = MAX(display.mixer.ch[MIXER_CHANNEL_AUX1].peakLeftValue[0],  display.mixer.ch[MIXER_CHANNEL_AUX1].levelLeft->value);
			display.mixer.ch[MIXER_CHANNEL_AUX1].peakRightValue[0] = MAX(display.mixer.ch[MIXER_CHANNEL_AUX1].peakRightValue[0], display.mixer.ch[MIXER_CHANNEL_AUX1].levelRight->value);
		} else if (t > t2) {
			t2 = t + LEVEL_METER_RATE;
			maxL = maxR = sumL = sumR = 0;
			for(j = 0; j < SOUND_FRAME_SIZE; j += (SOUND_FRAME_SIZE/LEVEL_METER_SAMPLE_TIME)) {
				int	l,r;

				l = adcBuff[0][j][ADC_CHANNEL_L2];
				r = adcBuff[0][j][ADC_CHANNEL_R2];
				maxL = MAX(maxL, l);
				maxR = MAX(maxR, r);
				sumL += l;
				sumR += r;
			}
			display.mixer.ch[MIXER_CHANNEL_AUX2].levelLeft->value  = (maxL - (sumL / LEVEL_METER_SAMPLE_TIME)) * mixerSetting[MIXER_CHANNEL_AUX2].leftGain  / (float)(128 * 4096 / 2);
			display.mixer.ch[MIXER_CHANNEL_AUX2].levelRight->value = (maxR - (sumR / LEVEL_METER_SAMPLE_TIME)) * mixerSetting[MIXER_CHANNEL_AUX2].rightGain / (float)(128 * 4096 / 2);
			lcd_guiSetDirtys(display.mixer.ch[MIXER_CHANNEL_AUX2].levelLeft);
			lcd_guiSetDirtys(display.mixer.ch[MIXER_CHANNEL_AUX2].levelRight);
			display.mixer.ch[MIXER_CHANNEL_AUX2].peakLeftValue[0]  = MAX(display.mixer.ch[MIXER_CHANNEL_AUX2].peakLeftValue[0],  display.mixer.ch[MIXER_CHANNEL_AUX2].levelLeft->value);
			display.mixer.ch[MIXER_CHANNEL_AUX2].peakRightValue[0] = MAX(display.mixer.ch[MIXER_CHANNEL_AUX2].peakRightValue[0], display.mixer.ch[MIXER_CHANNEL_AUX2].levelRight->value);
		} else if (t > t3) {
			t3 = t + LEVEL_METER_RATE;
			i = bt_a2dp_endPoint[0].pcmReadPos % BT_A2DP_SINK_BUFFER_NUM;		// Target buffer
			maxL = maxR = 0;
			for(j = 0; j < BT_A2DP_SINK_BUFFER_SIZE; j += (BT_A2DP_SINK_BUFFER_SIZE/LEVEL_METER_SAMPLE_TIME)) {
				maxL = MAX(maxL, bt_a2dp_endPoint[0].pcmBuffer[i][j][0]);
				maxR = MAX(maxR, bt_a2dp_endPoint[0].pcmBuffer[i][j][1]);
			}
			display.mixer.ch[MIXER_CHANNEL_BT].levelLeft->value  = maxL * mixerSetting[MIXER_CHANNEL_BT].leftGain  / (float)(128 * 32768);
			display.mixer.ch[MIXER_CHANNEL_BT].levelRight->value = maxR * mixerSetting[MIXER_CHANNEL_BT].rightGain / (float)(128 * 32768);
			lcd_guiSetDirtys(display.mixer.ch[MIXER_CHANNEL_BT].levelLeft);
			lcd_guiSetDirtys(display.mixer.ch[MIXER_CHANNEL_BT].levelRight);
			display.mixer.ch[MIXER_CHANNEL_BT].peakLeftValue[0]  = MAX(display.mixer.ch[MIXER_CHANNEL_BT].peakLeftValue[0],  display.mixer.ch[MIXER_CHANNEL_BT].levelLeft->value);
			display.mixer.ch[MIXER_CHANNEL_BT].peakRightValue[0] = MAX(display.mixer.ch[MIXER_CHANNEL_BT].peakRightValue[0], display.mixer.ch[MIXER_CHANNEL_BT].levelRight->value);
		} else {
			;
		}
	}
	{	// Peak meter
		static uint32_t	t1;
		uint32_t 		t;
		int				i;

		t = HAL_GetTick();
		if (t > t1) {
			t1 = t + PEAK_METER_RATE;
			for(i = 0; i < MIXER_CHANNEL_MAX; i++) {
				if (display.mixer.ch[i].peakLeftValue[0] > 0) {
					display.mixer.ch[i].peakLeftValue[0] -= PEAK_METER_STEP;
					lcd_guiSetDirtys(display.mixer.ch[i].peakLeft);
				}
				if (display.mixer.ch[i].peakRightValue[0] > 0) {
					display.mixer.ch[i].peakRightValue[0] -= PEAK_METER_STEP;
					lcd_guiSetDirtys(display.mixer.ch[i].peakRight);
				}
			}
		}
	}

	lcd_guiHandler(display.root);
	return;
}

//
//	ATT(BLE) interface
//
#define BT_ATT_MAIN_VOLUME_ID	0x1002
#define BT_ATT_AUX1_VOLUME_ID	0x1012
#define BT_ATT_AUX1_BALANCE_ID	0x1022
#define BT_ATT_AUX2_VOLUME_ID	0x1032
#define BT_ATT_AUX2_BALANCE_ID	0x1042
#define BT_ATT_BT_VOLUME_ID		0x1052
#define BT_ATT_BT_BALANCE_ID	0x1062

int bt_attDbNotificationFlag;

void bt_attDbNotification(unsigned short handle, void *value, size_t valueLen) {
	int	a;

	a = ATT_INT16((unsigned char *)value);
	switch(handle) {
	case BT_ATT_MAIN_VOLUME_ID:
		if (!bt_attDbNotificationFlag) {
			speakerGain = (a << 7) / 100;
		}
		display.render.outputLevel->value = a / 100.0f;
		lcd_guiSetDirtys(display.render.outputLevel);
		sprintf(display.render.labelName2, "%d", a);
		lcd_guiSetDirtys(display.render.label2);
		break;
	case BT_ATT_AUX1_VOLUME_ID:
		if (!bt_attDbNotificationFlag) {
			mixerSetting[MIXER_CHANNEL_AUX1].volume = a / 100.0;
			recalcVolume(MIXER_CHANNEL_AUX1);
		}
		display.mixer.ch[MIXER_CHANNEL_AUX1].volumeValue[0] = mixerSetting[MIXER_CHANNEL_AUX1].volume / VOLUME_MAX - (display.mixer.ch[MIXER_CHANNEL_AUX1].volumeValue[1] / 2);
		lcd_guiSetDirtys(display.mixer.ch[MIXER_CHANNEL_AUX1].volume);
		break;
	case BT_ATT_AUX1_BALANCE_ID:
		if (!bt_attDbNotificationFlag) {
			mixerSetting[MIXER_CHANNEL_AUX1].balance = a / 100.0;
			recalcVolume(MIXER_CHANNEL_AUX1);
		}
		display.mixer.ch[MIXER_CHANNEL_AUX1].balanceValue[0] = (mixerSetting[MIXER_CHANNEL_AUX1].balance + 1.0) / 2.0 - (display.mixer.ch[MIXER_CHANNEL_AUX1].balanceValue[1] / 2);
		lcd_guiSetDirtys(display.mixer.ch[MIXER_CHANNEL_AUX1].balance);
		break;
	case BT_ATT_AUX2_VOLUME_ID:
		if (!bt_attDbNotificationFlag) {
			mixerSetting[MIXER_CHANNEL_AUX2].volume = a / 100.0;
			recalcVolume(MIXER_CHANNEL_AUX2);
		}
		display.mixer.ch[MIXER_CHANNEL_AUX2].volumeValue[0] = mixerSetting[MIXER_CHANNEL_AUX2].volume / VOLUME_MAX - (display.mixer.ch[MIXER_CHANNEL_AUX2].volumeValue[1] / 2);
		lcd_guiSetDirtys(display.mixer.ch[MIXER_CHANNEL_AUX2].volume);
		break;
	case BT_ATT_AUX2_BALANCE_ID:
		if (!bt_attDbNotificationFlag) {
			mixerSetting[MIXER_CHANNEL_AUX2].balance = a / 100.0;
			recalcVolume(MIXER_CHANNEL_AUX2);
		}
		display.mixer.ch[MIXER_CHANNEL_AUX2].balanceValue[0] = (mixerSetting[MIXER_CHANNEL_AUX2].balance + 1.0) / 2.0 - (display.mixer.ch[MIXER_CHANNEL_AUX2].balanceValue[1] / 2);
		lcd_guiSetDirtys(display.mixer.ch[MIXER_CHANNEL_AUX2].balance);
		break;
	case BT_ATT_BT_VOLUME_ID:
		if (!bt_attDbNotificationFlag) {
			mixerSetting[MIXER_CHANNEL_BT].volume = a / 100.0;
			recalcVolume(MIXER_CHANNEL_BT);
		}
		display.mixer.ch[MIXER_CHANNEL_BT].volumeValue[0] = mixerSetting[MIXER_CHANNEL_BT].volume / VOLUME_MAX - (display.mixer.ch[MIXER_CHANNEL_BT].volumeValue[1] / 2);
		lcd_guiSetDirtys(display.mixer.ch[MIXER_CHANNEL_BT].volume);
		break;
	case BT_ATT_BT_BALANCE_ID:
		if (!bt_attDbNotificationFlag) {
			mixerSetting[MIXER_CHANNEL_BT].balance = a / 100.0;
			recalcVolume(MIXER_CHANNEL_BT);
		}
		display.mixer.ch[MIXER_CHANNEL_BT].balanceValue[0] = (mixerSetting[MIXER_CHANNEL_BT].balance + 1.0) / 2.0 - (display.mixer.ch[MIXER_CHANNEL_BT].balanceValue[1] / 2);
		lcd_guiSetDirtys(display.mixer.ch[MIXER_CHANNEL_BT].balance);
		break;
	default:
		break;
	}
	if (bt_attDbNotificationFlag) {
		bt_att_serverNotificationQueueAdd(handle);
		bt_attDbNotificationFlag = 0;
	}
	standByDelay = 0;
    return;
}

//
//	Button,RotaryEncoder interface
//
void buttonSwitchInput(void) {
	enum btnFocus {MAIN_VOLUME, VOLUME_1, VOLUME_2, VOLUME_BT, BALANCE_1, BALANCE_2, BALANCE_BT};
	static enum btnFocus	nowFocus;
	static enum btnFocus	beforeFocus;
	static int				btnCount;
	static int				encCount = 9;
	static int				ch[] = {9, MIXER_CHANNEL_AUX1, MIXER_CHANNEL_AUX2, MIXER_CHANNEL_BT,
			   	   	   	   	   	   	   MIXER_CHANNEL_AUX1, MIXER_CHANNEL_AUX2, MIXER_CHANNEL_BT};
	static int				att[] = {BT_ATT_MAIN_VOLUME_ID, BT_ATT_AUX1_VOLUME_ID,  BT_ATT_AUX2_VOLUME_ID,  BT_ATT_BT_VOLUME_ID,
			                                                BT_ATT_AUX1_BALANCE_ID, BT_ATT_AUX2_BALANCE_ID, BT_ATT_BT_BALANCE_ID};
	uint32_t 				t;
	static uint32_t			t1;
	static uint32_t			t2;
	int						a;
	int						d;
	unsigned char			p[2];

	t = HAL_GetTick();
	if (t1 < t) {
		t1 = t + B1_BUTTON_SCAN_RATE;

		// B1 Button is "focus switch"
		if (!HAL_GPIO_ReadPin(B1_GPIO_Port, B1_Pin)) {
			btnCount++;
		} else {
			btnCount = 0;
		}
		if (btnCount == 10) {	// Push
			// Target move
			beforeFocus = nowFocus;
			nowFocus++;
			if (nowFocus > BALANCE_BT) {
				nowFocus = MAIN_VOLUME;
			}
		} else if (btnCount) {	// Pushing...
			t2 = t + B1_BUTTON_TIME_OUT;
			standByDelay = 0;
		} else if ((t > t2) && (nowFocus != MAIN_VOLUME)) {	// Time out
			beforeFocus = nowFocus;
			nowFocus    = MAIN_VOLUME;
		}

		// Rotary encoder
		a = __HAL_TIM_GET_COUNTER(&htim3);
		if (encCount != a) {
			switch(nowFocus) {
			case MAIN_VOLUME:
				d = a - encCount;
				if ((d > 0) && (d <= 8)) {
					speakerGain += (d * d);
					if (speakerGain > 128) {
						speakerGain = 128;
					}
				} else if ((d < 0) && (d >= -8)) {
					speakerGain -= (d * d);
					if (speakerGain < 0) {
						speakerGain = 0;
					}
				}
				ATT_STORE16(p, (speakerGain * 100) >> 7);
				bt_attDbNotificationFlag = 1;
				bt_attDbUpdate(BT_ATT_MAIN_VOLUME_ID, p, sizeof(p));
				break;
			case VOLUME_1:
			case VOLUME_2:
			case VOLUME_BT:
				if (encCount < a) {
					mixerSetting[ch[nowFocus]].volume += VOLUME_STEP;
					if (mixerSetting[ch[nowFocus]].volume > VOLUME_MAX) {
						mixerSetting[ch[nowFocus]].volume = VOLUME_MAX;
					}
				} else {
					mixerSetting[ch[nowFocus]].volume -= VOLUME_STEP;
					if (mixerSetting[ch[nowFocus]].volume < 0.0) {
						mixerSetting[ch[nowFocus]].volume = 0.0;
					}
				}
				recalcVolume(ch[nowFocus]);
				ATT_STORE16(p, (int)(mixerSetting[ch[nowFocus]].volume * 100));
				bt_attDbNotificationFlag = 1;
				bt_attDbUpdate(att[nowFocus], p, sizeof(p));
				break;
			case BALANCE_1:
			case BALANCE_2:
			case BALANCE_BT:
				if (encCount < a) {
					mixerSetting[ch[nowFocus]].balance += BALANCE_STEP;
					if (mixerSetting[ch[nowFocus]].balance > 1.0) {
						mixerSetting[ch[nowFocus]].balance = 1.0;
					}
				} else {
					mixerSetting[ch[nowFocus]].balance -= BALANCE_STEP;
					if (mixerSetting[ch[nowFocus]].balance < -1.0) {
						mixerSetting[ch[nowFocus]].balance = -1.0;
					}
				}
				recalcVolume(ch[nowFocus]);
				ATT_STORE16(p, (int)(mixerSetting[ch[nowFocus]].balance * 100));
				bt_attDbNotificationFlag = 1;
				bt_attDbUpdate(att[nowFocus], p, sizeof(p));
				break;
			default:
				break;
			}
			encCount = a;
			t2 = t + B1_BUTTON_TIME_OUT;
		}

		// Display
		if (beforeFocus != nowFocus) {
			switch(beforeFocus) {
			case VOLUME_1:
			case VOLUME_2:
			case VOLUME_BT:
				display.mixer.ch[ch[beforeFocus]].volumeColor[1] = LCD_COLOR_YELLOW;
				lcd_guiSetDirtys(display.mixer.ch[ch[beforeFocus]].volume);
				break;
			case BALANCE_1:
			case BALANCE_2:
			case BALANCE_BT:
				display.mixer.ch[ch[beforeFocus]].balanceColor[1] = LCD_COLOR_YELLOW;
				lcd_guiSetDirtys(display.mixer.ch[ch[beforeFocus]].balance);
				break;
			default:
				break;
			}
			switch(nowFocus) {
			case VOLUME_1:
			case VOLUME_2:
			case VOLUME_BT:
				display.mixer.ch[ch[nowFocus]].volumeColor[1] = LCD_COLOR_RED;
				lcd_guiSetDirtys(display.mixer.ch[ch[nowFocus]].volume);
				break;
			case BALANCE_1:
			case BALANCE_2:
			case BALANCE_BT:
				display.mixer.ch[ch[nowFocus]].balanceColor[1] = LCD_COLOR_RED;
				lcd_guiSetDirtys(display.mixer.ch[ch[nowFocus]].balance);
				break;
			default:
				break;
			}
			beforeFocus = nowFocus;
		}
#ifdef DEL
		if (nowFocus != MAIN_VOLUME) {
			HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_SET);
		} else {
			HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_RESET);
		}
#endif
	}
	return;
}

/* USER CODE END 0 */

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();

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

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_USART2_UART_Init();
  MX_ADC1_Init();
  MX_TIM1_Init();
  MX_TIM2_Init();
  MX_USB_HOST_Init();
  MX_TIM3_Init();
  MX_TIM7_Init();

  /* Initialize interrupts */
  MX_NVIC_Init();

  /* USER CODE BEGIN 2 */
  printf("BTSP 0.2\n");
  // time() start
  HAL_TIM_Base_Start_IT(&htim7);
  // Analog input start
  HAL_TIM_Base_Start(&htim2);
  HAL_ADC_Start_DMA(&hadc1, (uint32_t*)&adcBuff, 2*SOUND_FRAME_SIZE*ADC_CHANNEL_MAX);
  // Output start
#if defined(PWM2) || defined(PWM2_FULL)
    htim1.Instance->CCER &= ~TIM_CCER_CC1P;	// CH1=TIM_OCPOLARITY_HIGH
    htim1.Instance->CCER &= ~TIM_CCER_CC3P;	// CH3=TIM_OCPOLARITY_HIGH
#endif
  HAL_TIM_Base_Start(&htim1);
  HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
  HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_2);
  HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_3);
  HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_4);
  HAL_TIM_DMABurst_WriteStart2(&htim1, TIM_DMABASE_CCR1, TIM_DMA_UPDATE,
                    (uint32_t*)pwmBuff, TIM_DMABURSTLENGTH_4TRANSFERS, 2*SOUND_FRAME_SIZE*4);
  // STM Document "DocID027362 Rev 2" STM32F446xC/xE Errata sheet
  PWR->CR |= ((uint32_t)PWR_CR_ADCDC1);		//   2.1.6 Internal noise impacting the ADC accuracy
  	  	  	  	  	  	  	  	  	  	  	//         Option1: Set ADCDC1 bit
  // UI
  HAL_TIM_Encoder_Start(&htim3, TIM_CHANNEL_ALL);
  displayInit();

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
	//
	//	Auto cut off
	//
	{
		static int		aux1before;
		static int		aux1count;
		static int		aux1enable = 1;
		static int		aux2before;
		static int		aux2count;
		static int		aux2enable = 1;
		int				s;
		static time_t	t1;
		time_t			t;

		t = time(NULL);
		if (t1 != t) {	// sec interval
			t1  = t;

			// Silent detect
			s = adcBuff[0][0][ADC_CHANNEL_L1] + adcBuff[0][0][ADC_CHANNEL_R1];
			if ((aux1before - s) > AUX1_NO_SIGNAL_LEVEL) {
				aux1count    = 0;
				standByDelay = 0;
			} else if (standByDelay < STAND_BY_DELAY) {
				aux1count++;
			}
			aux1before = s;
			s = adcBuff[0][0][ADC_CHANNEL_L2] + adcBuff[0][0][ADC_CHANNEL_R2];
			if ((aux2before - s) > AUX2_NO_SIGNAL_LEVEL) {
				aux2count    = 0;
				standByDelay = 0;
			} else if (standByDelay < STAND_BY_DELAY) {
				aux2count++;
			}
			aux2before = s;

			// Cut and resume
			if (aux1count > AUX1_CUT_OFF_DELAY) {		// Silent is over
				if (aux1enable) {
					mixerSetting[MIXER_CHANNEL_AUX1].leftGain  = 0;	// disable
					mixerSetting[MIXER_CHANNEL_AUX1].rightGain = 0;
					display.mixer.ch[MIXER_CHANNEL_AUX1].label->color.face = LCD_COLOR_BLUE;
					lcd_guiSetDirtys(display.mixer.ch[MIXER_CHANNEL_AUX1].label);
					aux1enable = 0;
				}
			} else {									// Active
				if (!aux1enable) {
					recalcVolume(MIXER_CHANNEL_AUX1);	// Resume
					display.mixer.ch[MIXER_CHANNEL_AUX1].label->color.face = LCD_COLOR_WHITE;
					lcd_guiSetDirtys(display.mixer.ch[MIXER_CHANNEL_AUX1].label);
					aux1enable = 1;
				}
			}
			if (aux2count > AUX1_CUT_OFF_DELAY) {		// Silent is over
				if (aux2enable) {
					mixerSetting[MIXER_CHANNEL_AUX2].leftGain  = 0;	// disable
					mixerSetting[MIXER_CHANNEL_AUX2].rightGain = 0;
					display.mixer.ch[MIXER_CHANNEL_AUX2].label->color.face = LCD_COLOR_BLUE;
					lcd_guiSetDirtys(display.mixer.ch[MIXER_CHANNEL_AUX2].label);
					aux2enable = 0;
				}
			} else {									// Active
				if (!aux2enable) {
					recalcVolume(MIXER_CHANNEL_AUX2);	// Resume
					display.mixer.ch[MIXER_CHANNEL_AUX2].label->color.face = LCD_COLOR_WHITE;
					lcd_guiSetDirtys(display.mixer.ch[MIXER_CHANNEL_AUX2].label);
					aux2enable = 1;
				}
			}
		}
	}

	//
	//	Power control
	//
	{
		static int		standByMode;
		static time_t	t1;
		time_t			t;

		t = time(NULL);
		if (t1 != t) {	// sec interval
			t1  = t;
//			printf("Standby=%d\n", standByDelay);
			if (standByDelay >= STAND_BY_DELAY) {
				if (standByMode == 0) {
					printf("Sleeping\n");
					HAL_GPIO_WritePin(LCD_Backlight_GPIO_Port, LCD_Backlight_Pin, GPIO_PIN_RESET);// -0.2W  LCD back light off
					__HAL_TIM_MOE_DISABLE_UNCONDITIONALLY(&htim1);					// -0.4W  Volume=50%
					MODIFY_REG(RCC->CFGR, RCC_CFGR_HPRE, RCC_SYSCLK_DIV8);			// -0.3W  180MHz->22.5MHz
					standByMode = 1;
				}
			} else {
				standByDelay++;
			}
		}
		if (standByDelay == 0) {
			if (standByMode) {
				printf("Wakeup\n");
				MODIFY_REG(RCC->CFGR, RCC_CFGR_HPRE, RCC_SYSCLK_DIV1);			// CPU core is a full speed (180MHz)
				__HAL_TIM_MOE_ENABLE(&htim1);									// Speaker on
				HAL_GPIO_WritePin(LCD_Backlight_GPIO_Port, LCD_Backlight_Pin, GPIO_PIN_SET);	// LCD back light on
				standByMode = 0;
			}
		}
	}

	//
	//	Button and switch control
	//
	buttonSwitchInput();

	//
	//	LCD control
	//
	if (HAL_GPIO_ReadPin(LCD_Backlight_GPIO_Port, LCD_Backlight_Pin) == GPIO_PIN_SET) {
		displayUpdate();
	}

  /* USER CODE END WHILE */
    MX_USB_HOST_Process();

  /* USER CODE BEGIN 3 */

  }
  /* USER CODE END 3 */

}

/** System Clock Configuration
*/
void SystemClock_Config(void)
{

  RCC_OscInitTypeDef RCC_OscInitStruct;
  RCC_ClkInitTypeDef RCC_ClkInitStruct;
  RCC_PeriphCLKInitTypeDef PeriphClkInitStruct;

    /**Configure the main internal regulator output voltage 
    */
  __HAL_RCC_PWR_CLK_ENABLE();

  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);

    /**Initializes the CPU, AHB and APB busses clocks 
    */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLM = 4;
  RCC_OscInitStruct.PLL.PLLN = 180;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = 3;
  RCC_OscInitStruct.PLL.PLLR = 2;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

    /**Activate the Over-Drive mode 
    */
  if (HAL_PWREx_EnableOverDrive() != HAL_OK)
  {
    Error_Handler();
  }

    /**Initializes the CPU, AHB and APB busses 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_DIV4;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;

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

  PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_CLK48;
  PeriphClkInitStruct.PLLSAI.PLLSAIM = 4;
  PeriphClkInitStruct.PLLSAI.PLLSAIN = 144;
  PeriphClkInitStruct.PLLSAI.PLLSAIQ = 2;
  PeriphClkInitStruct.PLLSAI.PLLSAIP = RCC_PLLSAIP_DIV6;
  PeriphClkInitStruct.PLLSAIDivQ = 1;
  PeriphClkInitStruct.Clk48ClockSelection = RCC_CLK48CLKSOURCE_PLLSAIP;
  if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

    /**Configure the Systick interrupt time 
    */
  HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000);

    /**Configure the Systick 
    */
  HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);

  /* SysTick_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
}

/** NVIC Configuration
*/
static void MX_NVIC_Init(void)
{
  /* DMA2_Stream5_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA2_Stream5_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA2_Stream5_IRQn);
  /* DMA2_Stream0_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA2_Stream0_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA2_Stream0_IRQn);
  /* TIM7_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(TIM7_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(TIM7_IRQn);
}

/* ADC1 init function */
static void MX_ADC1_Init(void)
{

  ADC_ChannelConfTypeDef sConfig;

    /**Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion) 
    */
  hadc1.Instance = ADC1;
  hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;
  hadc1.Init.Resolution = ADC_RESOLUTION_12B;
  hadc1.Init.ScanConvMode = ENABLE;
  hadc1.Init.ContinuousConvMode = DISABLE;
  hadc1.Init.DiscontinuousConvMode = DISABLE;
  hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING;
  hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T2_TRGO;
  hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc1.Init.NbrOfConversion = 4;
  hadc1.Init.DMAContinuousRequests = ENABLE;
  hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
  if (HAL_ADC_Init(&hadc1) != HAL_OK)
  {
    Error_Handler();
  }

    /**Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time. 
    */
  sConfig.Channel = ADC_CHANNEL_0;
  sConfig.Rank = 1;
  sConfig.SamplingTime = ADC_SAMPLETIME_28CYCLES;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }

    /**Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time. 
    */
  sConfig.Channel = ADC_CHANNEL_1;
  sConfig.Rank = 2;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }

    /**Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time. 
    */
  sConfig.Channel = ADC_CHANNEL_10;
  sConfig.Rank = 3;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }

    /**Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time. 
    */
  sConfig.Channel = ADC_CHANNEL_11;
  sConfig.Rank = 4;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }

}

/* TIM1 init function */
static void MX_TIM1_Init(void)
{

  TIM_ClockConfigTypeDef sClockSourceConfig;
  TIM_MasterConfigTypeDef sMasterConfig;
  TIM_OC_InitTypeDef sConfigOC;
  TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig;

  htim1.Instance = TIM1;
  htim1.Init.Prescaler = 0;
  htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim1.Init.Period = 582;
  htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim1.Init.RepetitionCounter = 6;
  if (HAL_TIM_Base_Init(&htim1) != HAL_OK)
  {
    Error_Handler();
  }

  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }

  if (HAL_TIM_PWM_Init(&htim1) != HAL_OK)
  {
    Error_Handler();
  }

  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }

  sConfigOC.OCMode = TIM_OCMODE_PWM1;
  sConfigOC.Pulse = 0;
  sConfigOC.OCPolarity = TIM_OCPOLARITY_LOW;
  sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;
  sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
  sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
  sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
  if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
  {
    Error_Handler();
  }

  if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_2) != HAL_OK)
  {
    Error_Handler();
  }

  if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_3) != HAL_OK)
  {
    Error_Handler();
  }

  if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_4) != HAL_OK)
  {
    Error_Handler();
  }

  sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE;
  sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE;
  sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF;
  sBreakDeadTimeConfig.DeadTime = 0;
  sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE;
  sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH;
  sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;
  if (HAL_TIMEx_ConfigBreakDeadTime(&htim1, &sBreakDeadTimeConfig) != HAL_OK)
  {
    Error_Handler();
  }

  HAL_TIM_MspPostInit(&htim1);

}

/* TIM2 init function */
static void MX_TIM2_Init(void)
{

  TIM_ClockConfigTypeDef sClockSourceConfig;
  TIM_MasterConfigTypeDef sMasterConfig;

  htim2.Instance = TIM2;
  htim2.Init.Prescaler = 0;
  htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim2.Init.Period = 2040;
  htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
  {
    Error_Handler();
  }

  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }

  sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }

}

/* TIM3 init function */
static void MX_TIM3_Init(void)
{

  TIM_Encoder_InitTypeDef sConfig;
  TIM_MasterConfigTypeDef sMasterConfig;

  htim3.Instance = TIM3;
  htim3.Init.Prescaler = 0;
  htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim3.Init.Period = 10000;
  htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  sConfig.EncoderMode = TIM_ENCODERMODE_TI12;
  sConfig.IC1Polarity = TIM_ICPOLARITY_RISING;
  sConfig.IC1Selection = TIM_ICSELECTION_DIRECTTI;
  sConfig.IC1Prescaler = TIM_ICPSC_DIV1;
  sConfig.IC1Filter = 3;
  sConfig.IC2Polarity = TIM_ICPOLARITY_RISING;
  sConfig.IC2Selection = TIM_ICSELECTION_DIRECTTI;
  sConfig.IC2Prescaler = TIM_ICPSC_DIV1;
  sConfig.IC2Filter = 0;
  if (HAL_TIM_Encoder_Init(&htim3, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }

  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }

}

/* TIM7 init function */
static void MX_TIM7_Init(void)
{

  TIM_MasterConfigTypeDef sMasterConfig;

  htim7.Instance = TIM7;
  htim7.Init.Prescaler = 8999;
  htim7.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim7.Init.Period = 10000;
  if (HAL_TIM_Base_Init(&htim7) != HAL_OK)
  {
    Error_Handler();
  }

  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim7, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }

}

/* USART2 init function */
static void MX_USART2_UART_Init(void)
{

  huart2.Instance = USART2;
  huart2.Init.BaudRate = 9600;
  huart2.Init.WordLength = UART_WORDLENGTH_8B;
  huart2.Init.StopBits = UART_STOPBITS_1;
  huart2.Init.Parity = UART_PARITY_NONE;
  huart2.Init.Mode = UART_MODE_TX_RX;
  huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart2.Init.OverSampling = UART_OVERSAMPLING_16;
  if (HAL_UART_Init(&huart2) != HAL_OK)
  {
    Error_Handler();
  }

}

/** 
  * Enable DMA controller clock
  */
static void MX_DMA_Init(void) 
{
  /* DMA controller clock enable */
  __HAL_RCC_DMA2_CLK_ENABLE();

}

/** Configure pins as 
        * Analog 
        * Input 
        * Output
        * EVENT_OUT
        * EXTI
*/
static void MX_GPIO_Init(void)
{

  GPIO_InitTypeDef GPIO_InitStruct;

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOC_CLK_ENABLE();
  __HAL_RCC_GPIOH_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();
  __HAL_RCC_GPIOB_CLK_ENABLE();

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_RESET);

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOC, LCD_Backlight_Pin|LCD_CS_Pin, GPIO_PIN_SET);

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOC, LCD_SCK_Pin|LCD_A0_Pin|LCD_SDA_Pin, GPIO_PIN_RESET);

  /*Configure GPIO pin : B1_Pin */
  GPIO_InitStruct.Pin = B1_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  HAL_GPIO_Init(B1_GPIO_Port, &GPIO_InitStruct);

  /*Configure GPIO pin : LD2_Pin */
  GPIO_InitStruct.Pin = LD2_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(LD2_GPIO_Port, &GPIO_InitStruct);

  /*Configure GPIO pin : LCD_Backlight_Pin */
  GPIO_InitStruct.Pin = LCD_Backlight_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(LCD_Backlight_GPIO_Port, &GPIO_InitStruct);

  /*Configure GPIO pins : LCD_CS_Pin LCD_SCK_Pin LCD_A0_Pin LCD_SDA_Pin */
  GPIO_InitStruct.Pin = LCD_CS_Pin|LCD_SCK_Pin|LCD_A0_Pin|LCD_SDA_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
  HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);

}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

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

#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

/**
  * @}
  */ 

/**
  * @}
*/ 

/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
