はじめに
通称Blue PillとよばれているSTM32F103C8T6ボードと0.96inch OLEDモジュールでオーディオスペクトルアナライザを作ってみました。
STM32F103C8T6ボードと開発環境につにてはこちらを参照ください。
用意するもの
ハードウェア
- 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が増幅調整用のトリマです。
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のケースにこんな感じに組み込みました。
ソフトウェア
CubeMX
設定のみ記述します。
System Core
GPIO
PB12をプルアップの入力に設定。P13は今回は使用していません。
RCC
High Speed Clock(HSE)を"Crystal/Ceramic Resonator"に設定
Analog
ADC1
IN0を選択し、Continuous Conversion ModeをEnableに。これを入れないと連続でADCできません。
ADC2
同様に、IN1を選択し、Continuous Conversion ModeをEnableに。
Timers
変更なし
Connectivity
I2Cを選択し、Speed ModeをFast Modeに、Clockを40,000Hzにします。
Clock Configuration
FFTはパワーが必要なのでClock Configurationはこんな感じに最高速で回します。
STM32F10X DSP Library
STMF10X用のDSPライブラリを入手します。これフリーなのか有料なのか調べてもわかりませんでした。自己責任で入手してください。STMのコミュニティにダウンロードリンクはありましたがどういうライセンスなのか良くわかりません。
ライブラリのインポート
DSPライブラリ
g8uグラフィックライブラリ
AtmelStudioでu8glibを使用した開発環境を作るを参考にして、STM用のライブラリをダウンロードして、インポートします。
ソースの下にはライブラリで一杯になります。
注意: fontデータは使用するものだけを残して、他は削除してください。ArduinoやAtMelStudioと違って、使用していないデータもコンパイルされます。
0.96inch OLEDへの対応
STM用のu8gライブラリではOLEDに対応していませんでした。
こちらのサイトのファイルを追加します
# 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_ */
# 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;
}
プロジェクトにインポートすると、使用できるようになります。
プログラム
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* <h2><center>© 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が表示されます。
おわりに
STM32F103C8T6はU$1.5くらいで購入できるのですが、64点2chのFFTを行って、10fpsで表示できるというのは驚きました。10fpsはOLEDの表示更新のボトルネックです。
小さい部品を合せても1000円以下でオーディオスペアナが完成できます。スゴイ!
だいぶ遅くなりますが256点、1024点のFFTも可能です。