電子回路わからん日記

にゃーんと言いながら電子回路いじってます

I2Sを受けることができるようになったので・・・

電子回路主体のブログのはずなんですが、だんだんと組み込み一辺倒になってしまっています。良くない良くない・・・・

 

FPGAの開発の模様も記事にしていけたらと思います。

 

実はAUDIY、FPGAもやっていまして、

ちゃんと評価ボードも持っています。ちなみに愛用している評価ボードはDE10-Liteです。いずれはXilinxの開発知識も身につけたいですね。

www.marutsu.co.jp


何を開発しようか

もともと自作オーディオに興味を持ち、そのために回路の勉強の報告をしていくのが当ブログのモチベーションですから、やはりここはオーディオ関係でいきたいと思います。

 

ここは最も単純なものとして、低次(~2次)のΔΣ変調を利用したPCM-DSD変換から始めてみたいと思います。

 

DSD変換を選んだ理由として

  • 基本的に加算、減算、遅延(フリップフロップ)、ビットシフト、ビット選択の要素だけで作ることができる(シンプル)。
  • PCMサンプリング周波数の64倍のDSDサンプリング周波数であればI2Sのビットクロックに同期してリアルタイムに変換可能(I2SのBCLK周波数がLRCK周波数の64倍であるため)。
  • 音質や特性はさておき、DSD変換後はカップリングコンデンサとアナログフィルタを通せば音声出力可能(DA変換自体は容易)。

 

たとえばこれがDSD→PCM変換だと

  • DSDの高周波ノイズを抑え込んでPCMに変換するには高性能なデジタルフィルタが必要
  • デジタルフィルタの性能を高めるために周波数の高いマスタークロックが必要
  • FPGAのロジックを圧迫しないためにRAM IPなどを上手く組み合わせる必要がある

と、長期化と入念な検討が必要になるのでこれはまたの機会とします。


まずはシミュレーション

そうと決まれば、FPGAに実装する信号処理をまずはソフトウェアに落とし込んでシミュレーションします。

 

こういうときに結果をすぐにプロットできるPythonは便利です。

余談ですが、AUDIYはPythonエンジニア認定基礎試験合格者の資格を持っています。持ってて損はないと思いますので興味のあるかたはぜひ挑戦してみてください。

www.pythonic-exam.com


シミュレーションの流れ

大まかに以下の流れでPCM→DSD変換をシミュレーションします。

  1. 区間[-1, 1]の浮動小数点数で正弦波を作成
  2. 任意の量子化ビット数を持つ符号付き整数(PCM)に変換
  3. PCMに変換した信号をΔΣ変調(パルス密度変調)の計算で使用できるように0次ホールドでアップサンプリング
  4. 0次ホールドした正弦波をΔΣ変調する
  5. 変調して得られたPDM波形にローパスフィルタをかけて正弦波が得られるか確認する

この流れをPythonプログラムに落とし込んでいきます。


作成した関数の説明

今回は一次ΔΣ変調のシミュレーションを紹介したいと思います。

今回のシミュレーションに伴い、以下3つの関数を作成しました。

  1. 浮動小数点数リニアPCMに変換する関数
  2. リニアPCMを0次ホールドで整数倍アップサンプリングする関数
  3. アップサンプリングされた信号を一次ΔΣ変調する関数

この3つの関数について以下説明です。

1. リニアPCM変換関数LPCM(x, nbits)

浮動小数点の信号配列xを任意の量子化ビット数nbitsを持つリニアPCMに変換します。

変換の数式は、現在の離散時間における入力をx[i]、出力をy[i]、所望の量子化ビット数をnとすると

 

f:id:AUDIY:20211216145032p:plain



となります。ここで、はxの床関数を表します。

 

これをPythonの関数にするのは比較的容易です。numpyを利用して配列全体に計算を適用できるので高速です。

import numpy as np
 
# float to Linear PCM Conversion
def LPCM(x, nbits):
    xmax = np.max(x) # Maximum float Amplitude
    xmin = np.min(x) # Minimum float Amplitude
   
    # Quantization
    x_LPCM = np.floor(((x - xmin)/(xmax - xmin)) * (2 ** nbits - 1) + 0.5) \
        - (2 ** (nbits-1))
   
    return x_LPCM.astype(int)

2. 0次ホールド関数ZeroOrderHold(xPCM, OSR)

「0次ホールド」は、内挿するサンプルの数値をもとからあったサンプルに維持する内挿方法です。

これはScipyのsignal内にあるupfirdn関数を使えば高速に実行可能です。

docs.scipy.org

import numpy as np
import scipy.signal as signal
 
# Zero-order Hold Linear PCM Upsampling
def ZeroOrderHold(xPCM, OSR):
    y = signal.upfirdn(np.ones(OSR), xPCM, OSR)
   
    return y

3. ΔΣ変調関数PDM(x, nbits)

こればかりは自作になります。

しかも都度誤差を更新しないといけないので、ループを回すしか方法はないと思われます。

関数はこちらを参考にします。

ja.wikipedia.org

技術系のブログにWikipediaというのも気が引けますが、まずは手を動かすための情報としてこちらを参考にしました。

基本的にはWikipedia内の擬似コードそのままですが、フィードバックの量子化誤差計算で1bitのDSD信号をPCM信号xの振幅nbitsまで最大化して計算するように変更しています。

import numpy as np
 
# 1st-order Pulse Density Modulation
def PDM(x, nbits):
    qe = 0 # Quantization Error
    y = np.zeros(np.shape(x)[0]) # Return Array
   
    # Modulation
    for i in range(np.shape(x)[0]):
        if x[i] >= qe:
            y[i] = 1
        else:
            y[i] = 0
         
        # Maximize the Modulated Signal to PCM max Amplitude
        yi_Analog = np.floor((((y[i]*2 - 1) + 1)/2) * (2**nbits - 1) + 0.5) \
            - (2 ** (nbits-1))
       
        # Calcurate the Quantization Error
        qe = qe + (yi_Analog - x[i])
       
    return y.astype(int), qe

 


と、関数を紹介していたら記事が長くなってしまいました。

シミュレーションそのものは次回としたいと思います。