STM32F103C8T6でオーディオスペクトルアナライザ

# はじめに
通称Blue PillとよばれているSTM32F103C8T6ボードと0.96inch OLEDモジュールでオーディオスペクトルアナライザを作ってみました。
[STM32F103C8T6ボードと開発環境につにてはこちらを参照ください](https://qiita.com/tlab/items/6f33d4ed22d675cbad28)。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211703/b9d8a894-51a1-940d-a06f-47ea56018dcd.png)

# 用意するもの
## ハードウェア
* STM32F103C8T6 x1
* 0.96inch OLEDモジュール
* オペアンプ 2回路入り x1
* 0.1uF MLCC x3
* 抵抗10kΩ x1
* トリマ 5kΩ x2
* トリマ 50kΩ x2
* ケース、ユニバーサル基板、配線材、DCソケット、3.5mmソケット
* その他、はんだ、はんだごて、工具等

###オペアンプ
単電源動作するオペアンプなら何でも良いと思います。私はLM358を使用しました。
STM32F103のADCは0～3.3Vであるため、オペアンプにレールtoレールの特性は必要ありません。3.3Vまで出力出来ればOKです。今回は5V駆動で出力はV(+) -1.5Vであるため、3.5Vとなり、十分な振幅を得られます。

###MLCC
オペアンプのデカップに1つ。
L, Rchの入力に1つずつで、計3つです。フィルムを使えという意見はあるでしょうが、省スペースを優先してMLCCにしました。

##ソフトウェア
* STM32CubeMX
* Atollic TrueSTUDIO for STM32
* STM32 ST-Link Utility
* STM32F10X DSP Library

[STM32F103C8T6ボードと開発環境につにてはこちらを参照ください](https://qiita.com/tlab/items/6f33d4ed22d675cbad28)とは開発環境が異なりますが、使い方は一緒です。TrueSTUDIOのほうが良さそうです。

#ハードウェア作成
##オペアンプ
ゼロ点シフトして、信号増幅する回路です。R3, R5がゼロ点調整、R4, R6が増幅調整用のトリマです。
![コメント 2019-05-09 204504.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211703/0e2cd3fa-a96e-acc7-eb64-12ef0d837c10.png)

##STM32F103C8T6とOLEDモジュール
| STM | --- | OLD |
|:-:|:-:|:-:|
| B6  | ---  | CLK  |
| B7  | ---  | SDA  |
B6, B7がI2C通信のラインになります。

| STM | CONNECT |
|:-:|:-:|
| A0 | OPA Lch |
| A1 | OPA Rch |
| 5V | 5V |
| GND | GND |
| B12 | -- Switch -- GND  |
電源供給は5Vピンに5Vを供給します。
A0、A1にオペアンプの出力を入力。
B12にタクトスイッチを接続し、スイッチONでGNDに落ちるように接続。

WxHxL=50x20x80mmのケースにこんな感じに組み込みました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211703/8db1a472-1c65-3cb2-3a6b-6bc90dca0496.png)

#ソフトウェア
##CubeMX
設定のみ記述します。
###System Core
####GPIO
PB12をプルアップの入力に設定。P13は今回は使用していません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211703/b91bf979-929a-8fe4-0648-c3ddd993989f.png)

####RCC
High Speed Clock(HSE)を"Crystal/Ceramic Resonator"に設定
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211703/f8573a1c-ae01-2894-0fc8-35cf8f30d8f6.png)

###Analog
####ADC1
IN0を選択し、Continuous Conversion ModeをEnableに。これを入れないと連続でADCできません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211703/be4011c4-f097-8539-975b-b4d28480630f.png)
####ADC2
同様に、IN1を選択し、Continuous Conversion ModeをEnableに。

###Timers
変更なし

###Connectivity
I2Cを選択し、Speed ModeをFast Modeに、Clockを40,000Hzにします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211703/7cd9c47f-5c66-7a51-0fd0-ebb017992eec.png)

###Clock Configuration
FFTはパワーが必要なのでClock Configurationはこんな感じに最高速で回します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211703/96f56069-f489-1c24-2b9d-4bdbd48ecff3.png)

##STM32F10X DSP Library
STMF10X用のDSPライブラリを入手します。**これフリーなのか有料なのか調べてもわかりませんでした。自己責任で入手してください。**[STMのコミュニティにダウンロードリンクはありましたが](https://community.st.com/s/question/0D50X00009XkYCwSAN/stm32f10x-dsp-library)どういうライセンスなのか良くわかりません。

###ライブラリのインポート
####DSPライブラリ
この構成でインポートします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211703/dfc105ce-1309-83d1-c236-f74ffb2e7b55.png)

####g8uグラフィックライブラリ
[AtmelStudioでu8glibを使用した開発環境を作る](https://qiita.com/tlab/items/64b05074008efa9e0404)を参考にして、STM用のライブラリをダウンロードして、インポートします。
ソースの下にはライブラリで一杯になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211703/eafd4771-bd09-7308-f11c-8c6ec928f452.png)

**注意: fontデータは使用するものだけを残して、他は削除してください**。ArduinoやAtMelStudioと違って、使用していないデータもコンパイルされます。

####0.96inch OLEDへの対応
STM用のu8gライブラリではOLEDに対応していませんでした。
[こちらのサイトのファイルを追加します](http://elastic-notes.blogspot.com/p/stm32-i2c-oled-ssd.html)

```C:u8g_arm.h
#ifndef U8G_ARM_H_
#define U8G_ARM_H_

#include "u8g.h"
#include "stm32f1xx_hal.h"


#define DATA_BUFFER_SIZE 1000
#define I2C_TIMEOUT 100	// change to 100 from 10000
#define DEVICE_ADDRESS 0x78 //device address is written on back side of your display
#define I2C_HANDLER hi2c1

extern I2C_HandleTypeDef hi2c1; // use your i2c handler

uint8_t u8g_com_hw_i2c_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, void *arg_ptr);

#endif /* U8G_ARM_H_ */
  
```

```C:u8g_arm.c
#include "u8g_arm.h"

static uint8_t control = 0;
void u8g_Delay(uint16_t val)
{
	HAL_Delay(val);
}

void u8g_MicroDelay(void)
{
 int i;
 for (i = 0; i < 1000; i++);
}

void u8g_10MicroDelay(void)
{
		 int i;
		 for (i = 0; i < 10000; i++);
}


uint8_t u8g_com_hw_i2c_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, void *arg_ptr)
{
	switch(msg)
	{
		case U8G_COM_MSG_STOP:
			break;

		case U8G_COM_MSG_INIT:
			u8g_MicroDelay();
			break;

		case U8G_COM_MSG_ADDRESS:					 /* define cmd (arg_val = 0) or data mode (arg_val = 1) */
			u8g_10MicroDelay();
			if (arg_val == 0)
			{
				control = 0;
			}
			else
			{
				control = 0x40;
			}
			break;

		case U8G_COM_MSG_WRITE_BYTE:
		{
			uint8_t buffer[2];
			buffer[0] = control;
			buffer[1] = arg_val;
			HAL_I2C_Master_Transmit(&hi2c1, DEVICE_ADDRESS, (uint8_t*) buffer, 2, I2C_TIMEOUT);
		}
			break;

		case U8G_COM_MSG_WRITE_SEQ:
		case U8G_COM_MSG_WRITE_SEQ_P:
		{
			 uint8_t buffer[DATA_BUFFER_SIZE];
			uint8_t *ptr = arg_ptr;
			buffer[0] = control;
			for (int i = 1; i <= arg_val; i++)
			{
				 buffer[i] = *(ptr++);
			}
			HAL_I2C_Master_Transmit(&hi2c1, DEVICE_ADDRESS, (uint8_t *)buffer, arg_val, I2C_TIMEOUT);
		}
		break;
	}
	return 1;
}
```

プロジェクトにインポートすると、使用できるようになります。

###プログラム

```C:main.c
/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * <h2><center>&copy; Copyright (c) 2019 STMicroelectronics.
  * All rights reserved.</center></h2>
  *
  * This software component is licensed by ST under BSD 3-Clause license,
  * the "License"; You may not use this file except in compliance with the
  * License. You may obtain a copy of the License at:
  *                        opensource.org/licenses/BSD-3-Clause
  *
  ******************************************************************************
  */
/* USER CODE END Header */

/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
 #include "u8g_arm.h"
#include "stm32f1xx_hal.h"	// GPIO access
#include <math.h>
#include <stdbool.h>
#include ".\\inc\\stm32_dsp.h"
#include ".\\inc\\table_fft.h"
/* USER CODE END Includes */

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

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define PI2  6.28318530717959

#define N_FFT 64		/* NPT = No of FFT point*/
#define DISPLAY_RIGHT 127 /* 224 for centered, 319 for right-aligned */
#define DISPLAY_LEFT 1  /* 224 for centered, 319 for right-aligned */

/* USER CODE END PD */

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

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
ADC_HandleTypeDef hadc1;
ADC_HandleTypeDef hadc2;

I2C_HandleTypeDef hi2c1;

/* USER CODE BEGIN PV */
static u8g_t u8g;

// for FFT
extern uint16_t TableFFT[];
extern volatile uint32_t TimingDelay ;
long l_L_BUFIN[N_FFT];         /* Complex input vector */
long l_R_BUFIN[N_FFT];         /* Complex input vector */
long l_L_BUFOUT[N_FFT];        /* Complex output vector */
long l_R_BUFOUT[N_FFT];        /* Complex output vector */
long l_L_BUFMAG[N_FFT + N_FFT/2];/* Magnitude vector */
long l_R_BUFMAG[N_FFT + N_FFT/2];/* Magnitude vector */
long l_L_RawData[N_FFT];
long l_R_RawData[N_FFT];
uint8_t	ucL_Peak[N_FFT];	// Peak hold
uint8_t	ucR_Peak[N_FFT];

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_ADC1_Init(void);
static void MX_ADC2_Init(void);
static void MX_I2C1_Init(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
uint16_t	GetADCValue(int Ch);
void DrawWaveForm(long *plData, int numOfData, int drawPosX);
void DrawFrame(int style);
void DrawInfo();
void DrawFFT(long *plData, int numOfData, int drawPosX, bool bPeakHold,uint8_t *pucPeak);
void DrawFFT2(long *plData, int numOfData, int drawPosX);
void DrawFFT3(long *plData, int numOfData, int drawPosX);
void DrawFFT4(long *plData, int numOfData, int drawPosX);
void DrawVU(long *plData, int numOfData, int drawPosY);
int GetXpos(uint16_t freq, unsigned char bLog);
int GetYpos(uint16_t power , unsigned char bLog);

uint32_t globalcounter = 0;
uint8_t btn_sts = 1;
uint8_t wave_mode = 0;

void powerMag(long *lbufout, long *lbufmag, long nfill, char* strPara);
void Setup_u8g();
void setup_ADC();
void GetFFTData(long *lbufin, int ch);
//void DrawVU(long *plData, int numOfData, int drawPosY);

/**
  * @brief  : Draw Debugging data
  * @param	: data : Soowing data
  * @retval : NIL
  */
void DrawInfo()
{
	char text[32];
	sprintf(text, "%ld",globalcounter);
	u8g_DrawStr(&u8g, 2, 16, text);

}
/**
  * @brief  : Draw Rectangle Frame
  * @param	: int style 0: horizontal, 1 : Vertical separation.
  * @retval : NIL
  */
void DrawFrame(int style)
{
	u8g_DrawLine(&u8g, 1, 1, 127, 1);
	u8g_DrawLine(&u8g, 1, 63, 1, 1);
	u8g_DrawLine(&u8g, 126, 63, 126, 1);
	u8g_DrawLine(&u8g, 1, 63, 127, 63);
	if( 0 == style )
	{
		u8g_DrawLine(&u8g, 64, 1, 64, 63 );	// Center line
	}
	else
	{
		u8g_DrawLine(&u8g, 1, 40, 127, 40 );	// Center line
	}

}

/**
  * @brief  : Draw Wave form
  * @param	: plData
  * 		: numOfData : N_FFT
  * @retval : NIL
  */
void DrawWaveForm(long *plData, int numOfData, int drawPosX)
{
	//uint8_t aScale;
	// lBUFMAG[]
//	u8g_DrawLine(&u8g, 64 , 32, 127, 32 );
	int xpos = drawPosX ;
	for(int i = 1 ; i < numOfData ; i+= numOfData/64 )
	{
//		u8g_DrawLine(&u8g, i + 64 -1, 32 - (plData[i-1])/10, i + 64, 32 - (plData[i])/10 );

		u8g_DrawLine(&u8g, xpos -1, 32 - (plData[i-1])/10, xpos, 32 - (plData[i])/10 );
		xpos ++;
	}
}

/**
  * @brief  : Draw FFT data
  * @param	: plData
  * 		: numOfData : N_FFT
  * @retval : NIL
  */
void DrawFFT(long *plData, int numOfData, int drawPosX, bool bPeakHold, uint8_t *pucPeak)
{
	int16_t aScale;
	// lBUFMAG[]
	int xpos = drawPosX + 2;
	int i;
	for( i = 1 ; i < numOfData/2 ; i ++ )
	{
/*		aScale = GetYpos(plData[i],1);
		aScale += GetYpos(plData[i+1],1);
		aScale /= 2;
		// 非理論的補正 見栄え補正 ただしスイープすると変になる
		aScale -= 25;
		if(aScale < 0) aScale *= -1;
		aScale *= 2 ;
		// 見栄えオワリ
*/
		aScale = plData[i-1]>>7;
		aScale += plData[i+0]>>7;
		aScale /= 2;
		if( aScale > 60 ) aScale = 60;
		if( aScale <= 0 ) aScale = 0;

		if( bPeakHold && NULL != pucPeak)
		{
			if( pucPeak[i] <= aScale )
				pucPeak[i] = aScale;
			else
			{
				if( pucPeak > 0 )
					pucPeak[i] -= 1;
			}
		}
		u8g_DrawLine(&u8g, xpos, 62, xpos, 62-aScale );
		u8g_DrawLine(&u8g, xpos+1, 62, xpos+1, 62-aScale );

		if( bPeakHold && NULL != pucPeak)
		{
			uint8_t peak;
			peak = 62-pucPeak[i];
			u8g_DrawLine(&u8g, xpos, peak, xpos+1, peak );
		}
		xpos += 4 ;

/*		double db = log10((double)plData[i]);
		aScale = (uint16_t)(db * 6.);
		u8g_DrawLine(&u8g, xpos, 64, xpos, 64-aScale );
		u8g_DrawLine(&u8g, xpos+1, 64, xpos+1, 64-aScale );
		xpos +=4 ;
*/	}
}

void DrawFFT2(long *plData, int numOfData, int drawPosX)
{
	uint16_t aScale;
	// lBUFMAG[]
	int xpos = drawPosX + 2;
	int i;
	for( i = 0 ; i < numOfData /2 ; i ++ )
	{
		aScale = plData[i] >> 6;
		//aScale += plData[i+1] >> 8;
		//aScale /= 2;
		u8g_DrawLine(&u8g, xpos, 62, xpos, 62-aScale );
		//u8g_DrawLine(&u8g, xpos+1, 62, xpos+1, 62-aScale );
		xpos += 2 ;

/*		double db = log10((double)plData[i]);
		aScale = (uint16_t)(db * 6.);
		u8g_DrawLine(&u8g, xpos, 64, xpos, 64-aScale );
		u8g_DrawLine(&u8g, xpos+1, 64, xpos+1, 64-aScale );
		xpos +=4 ;
*/	}
}
// LINE
void DrawFFT3(long *plData, int numOfData, int drawPosX)
{
	uint16_t aScale1, aScale2;
	// lBUFMAG[]
	int xpos = drawPosX + 2;
	int i;
	for( i = 1 ; i < numOfData /2 ; i ++ )
	{
		aScale1 = plData[i-1] >> 6;
		aScale2 = plData[i] >> 6;
		u8g_DrawLine(&u8g, xpos - 1, 62 - aScale1, xpos, 62-aScale2 );
		xpos += 2 ;

/*		double db = log10((double)plData[i]);
		aScale = (uint16_t)(db * 6.);
		u8g_DrawLine(&u8g, xpos, 64, xpos, 64-aScale );
		u8g_DrawLine(&u8g, xpos+1, 64, xpos+1, 64-aScale );
		xpos +=4 ;
*/	}
}
// LINE
void DrawFFT4(long *plData, int numOfData, int drawPosX)
{
	uint16_t aScale1, aScale2;
	double Freq;
	// lBUFMAG[]
	int xpos = drawPosX + 2;
	int i;
	for( i = 1 ; i < numOfData /2 ; i ++ )
	{
		aScale1 = plData[i-1] >> 6;
		aScale2 = plData[i] >> 6;
		u8g_DrawLine(&u8g, xpos + GetXpos( (i-1), 1) , 62 - aScale1, xpos + GetXpos( i, 1), 62-aScale2 );

/*		double db = log10((double)plData[i]);
		aScale = (uint16_t)(db * 6.);
		u8g_DrawLine(&u8g, xpos, 64, xpos, 64-aScale );
		u8g_DrawLine(&u8g, xpos+1, 64, xpos+1, 64-aScale );
		xpos +=4 ;
*/	}
}
/*
 * 周波数に対するXposを返す
 */
int GetXpos(uint16_t freq, unsigned char bLog)
{
	// log10(20000) == 4.3
	double f = (double)freq * 312.4;	// (20000. / 64.)
//	double f = (double)freq * 78.125;	// (20000. / 256.)
//	double f = (double)freq * 19.53;	// (20000. / 1024.)

	if( bLog == 1 )
	{
		f = log10(f * 2);	// *2(N_FFTの半分しか表示しないので)
				f *= 14.88;	// = 64./4.3
		//		f *= 29.767;	// = 128./4.3
	}

	return (int)f;
}
int GetYpos(uint16_t power , unsigned char bLog)
{
	// log10(32767) == 4.5154
	double f = (double)power ;

	if( bLog == 1 )
	{
		f = log10(f);
		f *= 14.3 ;	// = 64./4.5154
	}
	return (int)f;
}
/**
  * @brief  : Draw VU
  * @param	: plData
  * 		: numOfData : N_FFT
  * @retval : NIL
  */
void DrawVU(long *plData, int numOfData, int drawPosY)
{
	uint16_t aScale;
	uint16_t temp = 0;
	int i;
	for( i = 1 ; i < numOfData  ; i ++ )
	{
		aScale = plData[i] >> 7;
		temp += (long)aScale ;
	}
	temp = temp / ( numOfData / 8 );
//	temp = log10((double)temp) *10.0;
	u8g_DrawLine(&u8g, 2, drawPosY, temp, drawPosY);
	u8g_DrawLine(&u8g, 2, drawPosY + 1, temp, drawPosY + 1);
	u8g_DrawLine(&u8g, 2, drawPosY + 2, temp, drawPosY + 2 );
}


/**
  * @brief  Get ADC value
  * @param Ch : ADC channel
  * @retval : ADC value 0 - 4096
  */
uint16_t GetADCValue(int Ch)
{
	ADC_HandleTypeDef *hadc;

	uint16_t ReturnValue = 0;
	hadc = (Ch==0)?&hadc1:&hadc2;

	if (HAL_ADC_PollForConversion(hadc , 100) != HAL_OK)
	{
		/* End Of Conversion flag not set on time */
		Error_Handler();
		ReturnValue = -1;
	}
	else
	{
		/* ADC conversion completed */
		/*##-5- Get the converted value of regular channel  ######################*/
		ReturnValue = HAL_ADC_GetValue( hadc );
	}


	return ReturnValue;
}

/**
  * @brief  Micro second delay
  * @param i : delay time (usec)
  * @retval :
  */
void delay_us(uint32_t delay){
    while(delay>0){
        //NOP72回@72MHz = 1us
        //実際はwhile文、iの減算の処理がある。実測でNOP90回が1usに相当する。(@100MHz) = 10 clockつかう -> Nop 10個減算
        asm("NOP"); asm("NOP"); asm("NOP"); asm("NOP"); asm("NOP"); asm("NOP"); asm("NOP"); asm("NOP"); asm("NOP"); asm("NOP");
        asm("NOP"); asm("NOP"); asm("NOP"); asm("NOP"); asm("NOP"); asm("NOP"); asm("NOP"); asm("NOP"); asm("NOP"); asm("NOP");
        asm("NOP"); asm("NOP"); asm("NOP"); asm("NOP"); asm("NOP"); asm("NOP"); asm("NOP"); asm("NOP"); asm("NOP"); asm("NOP");
        asm("NOP"); asm("NOP"); asm("NOP"); asm("NOP"); asm("NOP"); asm("NOP"); asm("NOP"); asm("NOP"); asm("NOP"); asm("NOP");
        asm("NOP"); asm("NOP"); asm("NOP"); asm("NOP"); asm("NOP"); asm("NOP"); asm("NOP"); asm("NOP"); asm("NOP"); asm("NOP");
        asm("NOP"); asm("NOP"); asm("NOP"); asm("NOP"); asm("NOP"); asm("NOP"); asm("NOP"); asm("NOP"); asm("NOP"); asm("NOP");
        asm("NOP"); asm("NOP");
        delay--;
    }
}

/**
  * @brief  : Initialize for u8g
  * @param	: NIL
  * @retval : NIL
  */
void Setup_u8g()
{
	u8g_InitComFn(&u8g, &u8g_dev_ssd1306_128x64_i2c, u8g_com_hw_i2c_fn); //here we init our u8glib driver

	// flip screen, if required
	// u8g_SetRot180(&u8g)
	u8g_SetContrast(&u8g, 0);                  // 液晶のコントラスト設定 0:暗い, 255:明るい(あんまり変らない)

	// set SPI backup if required
	// u8g_SetHardwareBackup(&u8g, u8g_backup_avr_spi);
	// assign default color value
	if ( u8g_GetMode(&u8g) == U8G_MODE_R3G3B2 ) {
		u8g_SetColorIndex(&u8g, 255);     // white
	}
	else if ( u8g_GetMode(&u8g) == U8G_MODE_GRAY2BIT ) {
		u8g_SetColorIndex(&u8g,3);         // max intensity
	}
	else if ( u8g_GetMode(&u8g) == U8G_MODE_BW ) {
		u8g_SetColorIndex(&u8g,1);         // pixel on
	}
	else if ( u8g_GetMode(&u8g) == U8G_MODE_HICOLOR ) {
		u8g_SetHiColorByRGB(&u8g, 255, 255, 255);
	}
	// Font set
	u8g_SetFont(&u8g,u8g_font_helvR10);
}

/**
  * @brief  : Initialize for ADC
  * @param	: NIL
  * @retval : NIL
  */
void setup_ADC()
{
	// CH0
	if (HAL_ADC_Init(&hadc1) != HAL_OK)
	{
	   /* ADC initialization Error */
	   Error_Handler();
	}

	/* Run the ADC calibration in single-ended mode */
	if (HAL_ADCEx_Calibration_Start(&hadc1 ) != HAL_OK)
	{
	   /* Calibration Error */
	   Error_Handler();
	}
	/*##-3- Start the conversion process #######################################*/
	if (HAL_ADC_Start(&hadc1) != HAL_OK)
	{
	  /* Start Conversation Error */
	  Error_Handler();
	}

	// CH1
	if (HAL_ADC_Init(&hadc2) != HAL_OK)
	{
	   /* ADC initialization Error */
	   Error_Handler();
	}

	/* Run the ADC calibration in single-ended mode */
	if (HAL_ADCEx_Calibration_Start(&hadc2 ) != HAL_OK)
	{
	   /* Calibration Error */
	   Error_Handler();
	}
	/*##-3- Start the conversion process #######################################*/
	if (HAL_ADC_Start(&hadc2) != HAL_OK)
	{
	  /* Start Conversation Error */
	  Error_Handler();
	}
}
/**
  * @brief  : Get ADC data for FFT
  * @param	: NIL
  * @retval : set data to lBUFIN[]
  */
void GetFFTData(long *lbufin, int ch)
{
	int i;
	for(i = 0; i < N_FFT ; i++)
	{
		//puAdcData[i] = GetADCValue(0);
		lbufin[i] = (long)(GetADCValue(ch)) - 2048L;		// Data store
		if( ch == 0 )
			l_L_RawData[i] = lbufin[i] ;
		else
			l_R_RawData[i] = lbufin[i];
		lbufin[i] *= 1048544; // 2147418112 / 2048
		//HAL_Delay(1);
		delay_us(25);	// 25usec = 40khz sample rate
	}

}
/* 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();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_ADC1_Init();
  MX_ADC2_Init();
  MX_I2C1_Init();
  /* USER CODE BEGIN 2 */
  globalcounter = 0;

  // Setup for u8g
  Setup_u8g();

  // Setup for ADC
  setup_ADC();

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */

	  // Get FFT data
	  GetFFTData(l_L_BUFIN, 0);
	  GetFFTData(l_R_BUFIN, 1);


	  // FFT
	  if( GPIO_PIN_RESET != HAL_GPIO_ReadPin(GPIOB, WaveFormDisp_Pin ) 	// PB12 Button OFF
			  || GPIO_PIN_RESET != HAL_GPIO_ReadPin(GPIOB, VUDisp_Pin) )	// PB 12 Button OFF
//	  if(wave_mode == 1)
	  {
		  cr4_fft_64_stm32(l_L_BUFOUT, l_L_BUFIN, N_FFT);
		  cr4_fft_64_stm32(l_R_BUFOUT, l_R_BUFIN, N_FFT);
		  powerMag(l_L_BUFOUT, l_L_BUFMAG, N_FFT,"2SIDED");	// データはlBUFMAGにセット
		  powerMag(l_R_BUFOUT, l_R_BUFMAG, N_FFT,"2SIDED");	// データはlBUFMAGにセット
	  }
/* USER CODE END WHILE */

//	  if( 0 == globalcounter%10)
//	  {
	  u8g_FirstPage(&u8g);
	  do
	  {
		  //DrawInfo();
		  // FFT
		  if( GPIO_PIN_RESET != HAL_GPIO_ReadPin(GPIOB, WaveFormDisp_Pin) )	// PB12 Button OFF
		  {
			  DrawFrame(0);
			  //DrawFFT2(l_L_BUFMAG, N_FFT, 1 );
			  //DrawFFT4(l_R_BUFMAG, N_FFT, 64 );
			  DrawFFT(l_L_BUFMAG, N_FFT/2, 1, true, ucL_Peak);
			  DrawFFT(l_R_BUFMAG, N_FFT/2, 64, true, ucR_Peak);
		  }
		  // VU
/*		  else if( GPIO_PIN_RESET == HAL_GPIO_ReadPin(GPIOB, VUDisp_Pin ) ) //PB13 Button ON
		  {
			  DrawFrame(1);
			  DrawVU(l_L_BUFMAG, N_FFT/2, 30);
			  DrawVU(l_R_BUFMAG, N_FFT/2, 50);
		  }	*/
		  // Wave Form
		  else
		  {
			  DrawFrame(0);
			  DrawWaveForm(l_L_RawData, N_FFT, 1);
			  DrawWaveForm(l_R_RawData, N_FFT, 64);
		  }
	  } while ( u8g_NextPage(&u8g) );
	  u8g_Delay(50);
//	  }
	  globalcounter++;
	  btn_sts == HAL_GPIO_ReadPin(GPIOB, WaveFormDisp_Pin );	// ボタン状態保存

  }
  /* USER CODE END 3 */
}

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

  /** Initializes the CPU, AHB and APB busses clocks 
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != 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_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV4;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }
  PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_ADC;
  PeriphClkInit.AdcClockSelection = RCC_ADCPCLK2_DIV2;
  if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
  {
    Error_Handler();
  }
}

/**
  * @brief ADC1 Initialization Function
  * @param None
  * @retval None
  */
static void MX_ADC1_Init(void)
{

  /* USER CODE BEGIN ADC1_Init 0 */

  /* USER CODE END ADC1_Init 0 */

  ADC_ChannelConfTypeDef sConfig = {0};

  /* USER CODE BEGIN ADC1_Init 1 */

  /* USER CODE END ADC1_Init 1 */
  /** Common config 
  */
  hadc1.Instance = ADC1;
  hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
  hadc1.Init.ContinuousConvMode = ENABLE;
  hadc1.Init.DiscontinuousConvMode = DISABLE;
  hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
  hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc1.Init.NbrOfConversion = 1;
  if (HAL_ADC_Init(&hadc1) != HAL_OK)
  {
    Error_Handler();
  }
  /** Configure Regular Channel 
  */
  sConfig.Channel = ADC_CHANNEL_0;
  sConfig.Rank = ADC_REGULAR_RANK_1;
  sConfig.SamplingTime = ADC_SAMPLETIME_1CYCLE_5;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN ADC1_Init 2 */

  /* USER CODE END ADC1_Init 2 */

}

/**
  * @brief ADC2 Initialization Function
  * @param None
  * @retval None
  */
static void MX_ADC2_Init(void)
{

  /* USER CODE BEGIN ADC2_Init 0 */

  /* USER CODE END ADC2_Init 0 */

  ADC_ChannelConfTypeDef sConfig = {0};

  /* USER CODE BEGIN ADC2_Init 1 */

  /* USER CODE END ADC2_Init 1 */
  /** Common config 
  */
  hadc2.Instance = ADC2;
  hadc2.Init.ScanConvMode = ADC_SCAN_DISABLE;
  hadc2.Init.ContinuousConvMode = ENABLE;
  hadc2.Init.DiscontinuousConvMode = DISABLE;
  hadc2.Init.ExternalTrigConv = ADC_SOFTWARE_START;
  hadc2.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc2.Init.NbrOfConversion = 1;
  if (HAL_ADC_Init(&hadc2) != HAL_OK)
  {
    Error_Handler();
  }
  /** Configure Regular Channel 
  */
  sConfig.Channel = ADC_CHANNEL_1;
  sConfig.Rank = ADC_REGULAR_RANK_1;
  sConfig.SamplingTime = ADC_SAMPLETIME_1CYCLE_5;
  if (HAL_ADC_ConfigChannel(&hadc2, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN ADC2_Init 2 */

  /* USER CODE END ADC2_Init 2 */

}

/**
  * @brief I2C1 Initialization Function
  * @param None
  * @retval None
  */
static void MX_I2C1_Init(void)
{

  /* USER CODE BEGIN I2C1_Init 0 */

  /* USER CODE END I2C1_Init 0 */

  /* USER CODE BEGIN I2C1_Init 1 */

  /* USER CODE END I2C1_Init 1 */
  hi2c1.Instance = I2C1;
  hi2c1.Init.ClockSpeed = 400000;
  hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
  hi2c1.Init.OwnAddress1 = 0;
  hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
  hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
  hi2c1.Init.OwnAddress2 = 0;
  hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
  hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
  if (HAL_I2C_Init(&hi2c1) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN I2C1_Init 2 */

  /* USER CODE END I2C1_Init 2 */

}

/**
  * @brief GPIO Initialization Function
  * @param None
  * @retval None
  */
static void MX_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOD_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();
  __HAL_RCC_GPIOB_CLK_ENABLE();

  /*Configure GPIO pins : WaveFormDisp_Pin VUDisp_Pin */
  GPIO_InitStruct.Pin = WaveFormDisp_Pin|VUDisp_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
  GPIO_InitStruct.Pull = GPIO_PULLUP;
  HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

}

/* USER CODE BEGIN 4 */




/**
  * @brief  Compute power magnitude of the FFT transform
  * @param ill: length of the array holding power mag
  *   : strPara: if set to "1SIDED", removes aliases part of spectrum (not tested)
  * @retval : None
  */
void powerMag(long *lbufout, long *lbufmag, long nfill, char* strPara)
{
  int32_t lX,lY;
  uint32_t i;

  for (i=0; i < nfill; i++)
  {
    lX= (lbufout[i]<<16)>>16; /* sine_cosine --> cos */
    lY= (lbufout[i] >> 16);   /* sine_cosine --> sin */
    {
      float X=  64*((float)lX)/32768;
      float Y = 64*((float)lY)/32768;
      float Mag = sqrt(X*X+ Y*Y)/nfill;
      lbufmag[i] = (uint32_t)(Mag*65536);
    }
  }
  //if (strPara == "1SIDED") onesided(nfill);
}




/* 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 */

  /* 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,
     tex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
```
いろいろツッコミはあるかと思いますが、取り敢えず動作しているプログラムをアップしておきます。


#アナログ部分の調整
ソフトが完成したら、アナログの調整します。
B12をGNDに落すと入力波形データが表示されます。
##オフセット調整
無入力状態にて、R3, R5で波形を画面の真ん中に持っていきます。
##振幅調整
WaveGene等の波形出力ソフトなどを使用して、0dBの波形を入力し、振幅がオーバーしないように、R4, R6を調整します。

B12をオープンにするとFFTが表示されます。

![Burst_Cover_GIF_Action_20190509220555.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211703/8a63c4a2-0742-17cb-2cd7-34ab05dd5e03.gif)

#おわりに
STM32F103C8T6はU$1.5くらいで購入できるのですが、64点2chのFFTを行って、10fpsで表示できるというのは驚きました。10fpsはOLEDの表示更新のボトルネックです。
小さい部品を合せても1000円以下でオーディオスペアナが完成できます。スゴイ!

だいぶ遅くなりますが256点、1024点のFFTも可能です。