前回の記事ではSTM32L4マイコンのDFSDM機能を使ったPDM方式のデジタルマイクの音声信号の取得方法を紹介しました。
今回はDFSDMを使わず、基本的にどのマイコンにも搭載されているSPI+DMAとソフトウェアCICフィルタを使ってデジタルマイクの信号を取得する方法を紹介します。

はじめに

PDM方式のデジタルマイクは外部から与えられた外部クロックを元にΣΔ方式のAD変換を行い、そのクロックに同期してデータ信号を出力します。
したがって、デジタルマイクのクロックピンをSPIのSCKピンに繋ぎ、デジタルマイクのデータピンをSPIのMISOに繋ぎ、マイコン側はSPI受信(マスターモード)を行うことでPDM信号の1ビットデータ列を得ることができます。
このビット列をソフトウェアCICフィルタを通すことで元のアナログデータに復調できます。
今回は前回の記事で使用した100~80kHzの帯域を持つデジタルマイクKnowles社のSPH0641LU4H-1が搭載されているブレイクアウトボードとNUCLEO-F411REを使用します。

CICフィルタ

CICフィルタは積分器(Integrator)と微分器(Comb filter)を組み合わせたSINCフィルタをカスケード接続したものです。実装時は演算負荷を低減するために以下の図のように積分器と微分器をまとめ、その間にデシメーションフィルタを入れた構成にします。(図は3次のCICフィルタです)

実装の際のポイントは、ビット幅に注意すること、積分器のオーバーフローの影響をなくすことです。

1つ目のビット幅に関しては、前半の積分器が並んでいる部分でゲインが増加するため、そのゲイン以上のビット幅を持つ必要があります。
前回の記事でも紹介しましたが、CICフィルタのビット幅は以下の計算式によって計算されます。

$$ B_{\mathrm{out}} = N \log_{2} (RM) + B_{\mathrm{in}} $$

ここで、\(B_{\mathrm{out}}\):出力ビット幅、\(B_{\mathrm{in}}\):入力ビット幅、\(N\):タップ数(フィルタ次数)、\(R\):デシメーション比、\(M\):微分器の遅延段数 です。
例えば、\(B_{\mathrm{in}}=1\)、\(N=3\)、\(R=20\)、\(M=1\)とすると、\(B_{\text{out}} \fallingdotseq 13.97\)となりますので、最低限必要なビット幅は14となります。

2つ目のオーバーフローに関しては、 2の補数演算系を使い、積分器を通す際は飽和処理を入れずにオーバーフローするに任せておく ことでCICフィルタを安定的に動かすことができます。
参考:

実装

ここからSPI+ソフトウェアでCICフィルタを構成している方法を説明します。

まず、マイコンのSPI設定を行います。

デジタルマイクはクロック信号とデータ信号しか要らないので、Receive Only Masterモードにします。

マイクとは以下の図のように接続します。

次にSPIを設定します。

SPI設定では、First BitをLSB Firstに、Prescalerはボーレートがデジタルマイクのクロック周波数に合うよう設定します。
デジタルマイクのクロック周波数をデータシートから確認します。

超音波モードを使用する場合、3.072MHz~4.8MHzなので、この範囲内に収まるようPrescalerを32 (Baud Rate=3.125MHz)としました。

また、SPIをポーリング受信するとSPI受信関数 HAL_SPI_Receive 自体の処理時間のためクロック周期に誤差が生じるため、SPI受信DMAも設定しておきます。

これで初期設定は完了です。

データを受信する際はHAL_SPI_Receive_DMAを使ってデータ列を受信します。
例:HAL_SPI_Receive_DMA(&hspi1, (uint8_t *)data, PDM_SAMPLE_SIZE * DECIMATION_M / 8);
受信データサイズは、所望のデータ数PDM_SAMPLE_SIZE×デシメーション比DECIMATION_M÷8[bits/Byte]となります。

SPIの受信完了後、以下のようにソフトウェアCICフィルタを通します。(構造体の構成などはGitHubページ上のソースコードを参照してください)

PDM_SoftCICfilter.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
void executeCicFilter(uint8_t* pInBit, uint32_t pInBit_Num, int32_t* pOut_int32, struct CicFilter_t* st)
{
    int32_t in_bit;
    uint8_t in_Byte;
    uint32_t Bit_i, Decimation_count;
    uint8_t i;

    for(Bit_i = 0, Decimation_count = st->decimation - 1; Bit_i < pInBit_Num; ++Bit_i, --Decimation_count)
    {
        /***** 入力 *****/
        // デジタルマイクのビット列 pInBit の Bit_i ビット目が1の時 in_bit=1, 0の時 in_bit=-1
        in_Byte = *(pInBit + (Bit_i >> 3) );
        if( (in_Byte >> (Bit_i & 0x07)) & 0x01 )    // First bit is [b0]
            in_bit = 1;
        else
            in_bit = -1;


        /***** 積分器 *****/
        // 1段目
        *(st->out_i) += in_bit;

        // 2段目以降
        for (i = 1; i < st->order; ++i)
        {
            *(st->out_i + i) += *(st->out_i + i - 1);
        }


        /***** デシメータ *****/
        if(Decimation_count == 0)
        {
            Decimation_count = st->decimation;


            /***** 微分器 *****/
            // 1段目
            *(st->out_c) = *(st->out_i + st->order - 1) - *(st->z1_c);
            *(st->z1_c)  = *(st->out_i + st->order - 1);

            // 2段目以降
            for (i = 1; i < st->order; ++i)
            {
                *(st->out_c + i) = *(st->out_c + i - 1) - *(st->z1_c + i);
                *(st->z1_c  + i) = *(st->out_c + i - 1);
            }


            /***** 出力 *****/
            // 最終段の微分器出力を出力データ pOut_int32 に入れる
            *(pOut_int32 + Bit_i / st->decimation) = *(st->out_c + st->order - 1);
        }
    }
}

このソフトウェアCICフィルタにより、デジタルマイクのデータ列を復調できます。

性能評価

デジタルマイク+ソフトウェアCICフィルタによって得られた音声信号について、FFTによる周波数解析を行いました。

なお、SPIクロック周波数は3.125MHz、CICフィルタの次数\(N=3\)、デシメーション比\(R=20\)、遅延数\(M=1\)としました。
CICフィルタ通過後のサンプルレートは3.125MHz÷20=156.25kHz、最大帯域幅は156.25kHz÷2=78.125kHzです。
以下の図は10kHzの音声信号を与えたときの周波数特性です。

10kHzでピークが立っていることがわかります。

ソースコード

STM32CubeMXの設定データとソースコードは このGitHubページに上げておきます。

まとめ

本記事ではSPIとソフトウェアCICフィルタを使ってデジタルマイクの信号を取得する方法を紹介しました。
また、1ビットPDM出力であればSPIとソフトウェアCICフィルタでデータ取得が可能なため、デジタルマイクのみならずΣΔ型ADCを読み取ることもできます。