FFT
oled
STM32F103C8

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


はじめに

通称Blue PillとよばれているSTM32F103C8T6ボードと0.96inch OLEDモジュールでオーディオスペクトルアナライザを作ってみました。

STM32F103C8T6ボードと開発環境につにてはこちらを参照ください

image.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ボードと開発環境につにてはこちらを参照くださいとは開発環境が異なりますが、使い方は一緒です。TrueSTUDIOのほうが良さそうです。


ハードウェア作成


オペアンプ

ゼロ点シフトして、信号増幅する回路です。R3, R5がゼロ点調整、R4, R6が増幅調整用のトリマです。

コメント 2019-05-09 204504.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


ソフトウェア


CubeMX

設定のみ記述します。


System Core


GPIO

PB12をプルアップの入力に設定。P13は今回は使用していません。

image.png


RCC

High Speed Clock(HSE)を"Crystal/Ceramic Resonator"に設定

image.png


Analog


ADC1

IN0を選択し、Continuous Conversion ModeをEnableに。これを入れないと連続でADCできません。

image.png


ADC2

同様に、IN1を選択し、Continuous Conversion ModeをEnableに。


Timers

変更なし


Connectivity

I2Cを選択し、Speed ModeをFast Modeに、Clockを40,000Hzにします。

image.png


Clock Configuration

FFTはパワーが必要なのでClock Configurationはこんな感じに最高速で回します。

image.png


STM32F10X DSP Library

STMF10X用のDSPライブラリを入手します。これフリーなのか有料なのか調べてもわかりませんでした。自己責任で入手してください。STMのコミュニティにダウンロードリンクはありましたがどういうライセンスなのか良くわかりません。


ライブラリのインポート


DSPライブラリ

この構成でインポートします。

image.png


g8uグラフィックライブラリ

AtmelStudioでu8glibを使用した開発環境を作るを参考にして、STM用のライブラリをダウンロードして、インポートします。

ソースの下にはライブラリで一杯になります。

image.png

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


0.96inch OLEDへの対応

STM用のu8gライブラリではOLEDに対応していませんでした。

こちらのサイトのファイルを追加します


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



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;
}


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


プログラム


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


おわりに

STM32F103C8T6はU$1.5くらいで購入できるのですが、64点2chのFFTを行って、10fpsで表示できるというのは驚きました。10fpsはOLEDの表示更新のボトルネックです。

小さい部品を合せても1000円以下でオーディオスペアナが完成できます。スゴイ!

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