ADALM PLUTOでFM受信(Julia版)

## 概要
ソフトウエア無線によるFM受信の技術メモです。RFアンプとAD変換部分は、Analog DevicesのPLUTOを使い、Windows環境のJuliaにてFM受信を行ってみました。

※更新
- 実行環境の準備に、PLUTOの環境変数の設定を追加(2024/6/28)

関連記事) python版->[ADALM PLUTOでFM受信してみる](https://qiita.com/0x20FE/items/0e229a4c31889903d019)

### 想定するアプリケーションと実行環境

想定するアプリケーション：SDR、FM受信、音声処理
実行環境：julia, windows10, ADALM PLUTO

### 実行環境の準備
PLUTOを使うために次のリンクに従ってWindows版のlibiioをインストールする。

初期設定では、ローカル周波数の設定範囲が(325 - 3800MHz）であるため、（teratermなど使って）シリアル通信でログインし、環境変数を設定しrebootすることで、周波数設定範囲を(70 - 6000 MHz)に拡大する。
（参考）[Customizing the Pluto configuration](https://wiki.analog.com/university/tools/pluto/users/customizing) Updating to the AD9364
`:シリアルコンソールから
# fw_setenv attr_name compatible
# fw_setenv attr_val ad9364
# reboot
`

## 解説
ＦＭ受信処理を３種類に分解し、それぞれJuliaの非同期タスクとして起動し、Channelで接続しています。

1) PLUTOからIQデータを読み出す処理
2) ＦＭ復調処理
3) 音声出力処理

1の処理では、Plutoがサンプリング周波数の下限が大きいため、オーディオのサンプリング周波数(44.1kHz,48kHz)の16倍でIQデータをとりこむことにしました。
２の復調処理ではこれを４分の１にダウンサンプルしたのちに、遅延検波を行いFM復調します。そのあとで50usのディエンファシス処理を行います。ステレオ復調は今回はおこなっていません。
![diaRxFM2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/115334/5c93e585-e702-f75a-9d5a-55e7e672318d.jpeg)


### サンプルプログラム(復調処理のみ)
音声出力タスクは、PortAudioをオープンしたのち、
`Julia:音声出力
T=Int16

function playSound(chMod,chSnd,FS_AUD)
    stream=PortAudioStream("デバイス名",0,1, eltype=T,samplerate=FS_AUD)
    try
        take!(chSnd)
        sleep(1)
        while true
            dat = take!(chSnd)
            write(stream,dat)
        end
    catch e
        println("sound 例外を受信:",e)
    finally
        close(stream)
    end
end
`
遅延検波の原理はpython版記事に記載しています。
ディエンファシスフィルタは、$Hd(z)=\frac{1+z^{-1}}{\frac{Fs}{10000} + 1 -({\frac{Fs}{10000} - 1})z^{-1}}$

```Julia:遅延検波とディエンファシス
function demodFM(chDem,chSnd,FS_AUD)
    try
        aStI=[]
        aStQ=[]
        aStBB=[]

        cIQdemLast=0 #遅延検波の直前の最終サンプルを保存

        while true
            asmp =take!(chDem)# get raw data

            dIn=convert.(Float32,asmp)
            dI=dIn[1:2:end];dQ=dIn[2:2:end] # demax IQ and downsample by 4

            dId,aStI =downSample4(dI,aStI)
            dQd,aStQ =downSample4(dQ,aStQ)

            cIQdem= dId + im*dQd # complex
            # ===================================================
            # 遅延検波で１サンプル前のデータを必要とするため、
            # 最後のサンプルを保存しておき、処理の直前に先頭に追加する。
            #bbIQ = np.insert(decim,0,demLastIQ)
            pushfirst!(cIQdem,cIQdemLast)
            #bbIQ = decim
            cIQdemLast = cIQdem[end]
            #------------------------
            # 遅延検波
            # -+-[Delay]-- [x]---[ angle ]-->
            #  |            ^
            #  +-[Conj ]----+
            #------------------------
            dem = angle.(cIQdem[2:end] .* conj.(cIQdem[1:end-1])) / π

            # ===========================================
            # decimate by 4   : convert <FS_AUD>   176ksps = 44.1
            # ===========================================

            dBB,aStBB =downSample4(dem,aStBB)
            #de emphasis
            b=[1,1]
            a=[FS_AUD/10000+1, -(FS_AUD/10000-1)]
            dEmp=filt(b,a,dBB)
            x=convert.(T,floor.(dEmp*16000))
            put!(chSnd,x)

        end
    catch e
        println("Dem 例外を受信:",e)
    finally
        close(chSnd)
    end
end
```

## 性能、残課題など
pythonで組んだ場合は、ブチブチと細かいノイズが入っていた気がするのですが、Julia版ではクリアになっています。動作も安定で音もよいのですが、遅延が大きい。チューニングの余地がありそう。

また、ステレオ復調までもっていきたいですが、簡単なパイロット抽出の方法がないか検討中。
-> 記事かきました。[FM ステレオ復調(Julia)](2023-09-10_FM_Julia_SDR_stereo_wireless_27d7183da94fc9758acb.md)

余談ですが、Juliaでのiioライブラリへのアクセスは難儀しました。ニーズがあれば記事書こうかと思うのですが、どうでしょうね。ダウンサンプルフィルタは、juliaのfirライブラリにちょっとしたバグがあったので、手直しして実装しています。こちらもニーズがあれば記事書こうかと。


参考)
KATU(0x20FE),[Julia ライブラリ呼び出し](https://qiita.com/0x20FE/items/0bd17d320af8cf04e249)