Realtime sound spectrogram using FFT or linear prediction. Spectrogram is displayed on the display of PC. リアルタイム・スペクトログラム.解析の手法:FFT,線形予測法.スペクトログラムは PC のディスプレー装置に表示される.PC 側のプログラム:F446_Spectrogram.

Dependencies:   Array_Matrix mbed SerialTxRxIntr F446_AD_DA UIT_FFT_Real

main.cpp

Committer:
MikamiUitOpen
Date:
2018-10-07
Revision:
5:fcc1b0b4737e
Parent:
4:fe1885675421
Child:
6:c38ec7939609

File content as of revision 5:fcc1b0b4737e:

//---------------------------------------------------------------------
//  スペクトログラム (Nucleo-F446RE 用)
//
//      ● ST-Link Firmware の V2.J28.M16 で動作確認
//
//      ● ST-Link Firmware のアップグレードには stsw-link07.zip
//        に含まれている "ST-LinkUpgrade.exe" を使う 
//
//      ● PC 側のプログラム: "F446_Spectrogram"
//      ● ボーレート: 460800 baud
//      ● 受信データの文字列の終了マーク: "\r"
//
//      ● 入力  A0: 左チャンネル,A1 右チャンネル
//      ● 出力  A2: 左チャンネル,D13 右チャンネル
//             入力をそのまま出力する
//
//  2018/10/07, Copyright (c) 2018 MIKAMI, Naoki
//---------------------------------------------------------------------

#include "mbed.h"
#include <string>
#include "myFunction.hpp"
#include "Array.hpp"
#include "F446_ADC_Interrupt.hpp"
#include "FFT_Analyzer.hpp"
#include "LPC_Analyzer.hpp"
using namespace Mikami;

#ifndef __STM32F446xx_H
#error "Use Nucleo-F446RE"
#endif

const int N_FFT_ = 512;             // FFT の点数
const int N_DATA_ = N_FFT_ + 1;     // スペクトル解析に使うデータ数(差分処理を考慮)
const int N_FRAME_ = N_FFT_/2 + 1;  // 1フレーム当たり標本化するデータ数
const int N_FFT_2_ = N_FFT_/2;      // FFT の点数の半分
const float AMP_ = 1.0f/2048.0f;    // uint16_t 型のデータを float 型に変換する際の定数

uint16_t xPing_[N_FRAME_];  // 標本化したデータのバッファ1
uint16_t xPong_[N_FRAME_];  // 標本化したデータのバッファ2
uint16_t *inPtr_ = xPing_;  // AD 変換データの格納先を指すポインタ
uint16_t *outPtr_ = xPing_; // 取り出すデータを指すポインタ

__IO int inCount_ = 0;      // 入力データのカウンタ
__IO int pingPong_ = 0;     // 入力データの格納先,0: xPing_[], 1: xPong_[]
__IO bool full_ = false;    // AD 変換データが満杯のとき true

const int FS_ = 16000;      // 標本化周波数: 16 kHz
AdcDual_Intr myAdc_(FS_);   // "F446_ADC_Interrupt.hpp" で定義
DacDual myDac_;             // "F446_DAC.cpp/hpp" で定義

// FFT によるスペクトル解析オブジェクトの生成
FftAnalyzer *fftAnlz_ = new FftAnalyzer(N_DATA_, N_FFT_);
// 線形予測法 によるスペクトル解析オブジェクトの生成
LpcAnalyzer *lpcAnlz_ = new LpcAnalyzer(N_DATA_, N_FFT_, 20);
AnalyzerBase *analyzer_ = fftAnlz_;     // 最初は FFT を使う

SerialRxTxIntr rxTx_(32, 115200*4);     // PC との通信用

float empha_ = 1.0f;        // 高域強調器の係数

// 入力チャンネルを選択する関数とそれを割り当てる関数ポインタ
float InputL(float x1, float x2) { return x1; }
float InputR(float x1, float x2) { return x2; }
float InputLR(float x1, float x2) { return (x1 + x2)/2; }
typedef float (*FP_INPUT)(float, float);
FP_INPUT InputCurrent = InputLR;    // 最初は左右チャンネルを使う
FP_INPUT InputNew = InputCurrent;

// ADC 変換終了割り込みに対する割り込みサービス・ルーチン
void AdcIsr()
{
    uint16_t sn1, sn2;
    myAdc_.Read(sn1, sn2);
    uint16_t xn = InputCurrent(sn1, sn2);
    inPtr_[inCount_] = xn;
    myDac_.Write(xn, xn);

    if (++inCount_ >= N_FRAME_) // データが満杯か調べる
    {
        full_ = true;           // データが満杯
        inCount_ = 0;           // 以降のデータ取得のため
        pingPong_ = (pingPong_+1) & 0x01;               // バッファの切り替えのため
        inPtr_ = (pingPong_ == 0) ? xPing_ : xPong_;    // バッファのポインタ指定
        InputCurrent = InputNew;                // 入力の切り替え
        analyzer_->SetHighEmphasizer(empha_);   // 高域強調の有無の指令
    }
}

int main()
{
    const int DATA_SIZE = N_FFT_/2 + 1;
    Array<uint16_t> txData(DATA_SIZE);     // 送信用データ
    float sn[N_DATA_];      // スペクトル解析の対象となるデータ
    float db[N_FRAME_];     // 解析結果である対数スペクトル [dB]
    for (int n=0; n<N_DATA_; n++) sn[n] = 0;
    for (int n=0; n<N_FRAME_; n++) xPong_[n] = 2048;    // uint16_t 型の 0 に対応

    NVIC_SetPriority(ADC_IRQn, 0);      // AD変換終了割り込みの優先度が最高
    NVIC_SetPriority(USART2_IRQn, 1);

    float levelShift = 20;  // dB 計算の際のシフト量の初期値

    full_ = false;
    __IO bool ready = false;    // スペクトルの計算終了で true
    __IO bool okGo = false;     // "GO" を受信したら true

    myAdc_.SetIntrVec(&AdcIsr); // AD変換終了割り込みの割り当て
    while (true)
    {
        // PC からのコマンドの解析
        if (rxTx_.IsEol())      // 受信バッファのデータが有効になった場合の処理
        {
            string str = rxTx_.GetBuffer();
            if (str == "Spectrogram")
                rxTx_.Tx("ACK\n");  // PC からの "Spectrogram" に対して "ACK" を送信する
            else if (str.substr(0, 2) == "GO")
            {
                // str の内容
                // [0]  'G'
                // [1]  'O'
                // [2]  入力チャンネルの選択:'L', 'R', or '+'
                // [3]  スペクトルの値のレベルシフト:' ' ~ 'I' が -20 ~ 20 に対応
                // [4]  高域強調器の有無:'Y', 'N'
                // [5]  解析方法  F: FFT,L: 線形予測法

                switch (str[2])
                {
                    case 'L': InputNew = InputL;  break;
                    case 'R': InputNew = InputR;  break;
                    case '+': InputNew = InputLR; break;
                    default : InputNew = InputLR; break;
                }

                levelShift = (float)(str[3] - ' ');     // dB 計算の際のシフト量

                if (str[4] == 'Y') empha_ = 1.0f;       // 高域強調器は有
                else               empha_ = 0;          // 高域強調器は無

                if (str[5] == 'F') analyzer_ = fftAnlz_;    // FFT
                else               analyzer_ = lpcAnlz_;    // 線形予測法
 
                okGo = true;            // データの転送要求あり
            }
        }

        if (full_)  // 入力データが満杯かどうか調べる
        {
            full_ = false;

            outPtr_ = (pingPong_ == 1) ? xPing_ : xPong_;
            // フレームの後半のデータを前半に移動する
            for (int n=0; n<N_FFT_2_; n++)
                sn[n] = sn[n+N_FRAME_];
            // フレームの後半には新しいデータを格納する
            for (int n=0; n<N_FRAME_; n++)
                sn[n+N_FFT_2_] = AMP_*(outPtr_[n] - 2048);

            analyzer_->Execute(sn, db); // スペクトル解析の実行

            const float FACTOR = 10000.0f/80.0f;     // 表示範囲: 0 ~ 80 dB
            for (int n=0; n<DATA_SIZE; n++)
            {
                float xDb = FACTOR*(db[n] + 30.0f + levelShift);
                if (xDb > 10000.0f) xDb = 10000.0f;
                if (xDb < 0.0f) xDb = 0.0f;
                uint16_t spc = (uint16_t)xDb;
                txData[n] = spc;
            }
            ready = true;       // スペクトル解析終了
        }

        // 転送要求がありスペクトル解析が終了している場合にデータを PC へ転送する
        if (okGo && ready)
        {
            Xfer(txData);       // データを PC へ転送
            ready = false;
            okGo = false;
        }
    }
}